diff --git a/openvdb_2_3_0_library/openvdb/CHANGES b/openvdb_2_3_0_library/openvdb/CHANGES new file mode 100755 index 0000000..200b5bf --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/CHANGES @@ -0,0 +1,882 @@ +OpenVDB Version History +======================= + +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 Zip and produces comparable + file sizes for level set and fog volume grids. (Zip compression + is still enabled by default for other classes of grids). + - The Filter and LevelSetFilter tools now include a Gaussian filter, + and mean (box) filtering is now 10-50x faster. + - The isosurface meshing tool is now more robust (to level sets + with one voxel wide narrow bands, for example). + - Mesh to volume conversion is on average 1.5x faster and up to 5.5x + faster for high-resolution meshes where the polygon/voxel size ratio + is small. + - Added createLevelSet() and createLevelSetSphere() factory functions + for level set grids. + - tree::ValueAccessor is now faster for trees of height 2, 3 and 4 + (the latter is the default), and it now allows one to specify, + via a template argument, the number of node levels to be cached, + which can also improve performance in special cases. + - Added a toggle to tools::foreach() to specify whether or not + the functor should be shared across threads. + - Added Mat4s and Mat4d metadata types. + - Added explicit pre- and postmultiplication methods to the Transform, + Map and Mat4 classes and deprecated the old accumulation methods. + - Modified NonlinearFrustumMap to be more compatible with Houdini's + frustum transform. + - Fixed a GridTransformer bug that caused it to translate the + output grid incorrectly in some cases. + - Fixed a bug in the tree-level LeafIterator that resulted in + intermittent crashes in tools::dilateVoxels(). + - The Hermite data type and Hermite grids are no longer supported. + - Added tools/GridOperators.h, which includes new, cleaner implementations + of the Cpt, Curl, Divergence, Gradient, Laplacian, Magnitude, + MeanCurvature and Normalize tools. + - Interrupt support has been improved in several tools, including + tools::ParticlesToLevelSet. + - Simplified the API of the Stencil class and added an intersects() + method to test for intersection with a specified isovalue. + - Renamed voxelDimensions to voxelSize in transform classes and elsewhere. + - Deprecated houdini_utils::ParmFactory::setChoiceList() in favor of + houdini_utils::ParmFactory::setChoiceListItems(), which requires + a list of token, label string pairs. + - Made various changes for Visual C++ compatibility. + [Contributed by SESI] + - Fixed a bug in houdini_utils::getNodeChain() that caused the + Offset Level Set, Smooth Level Set and Renormalize Level Set SOPs + to ignore frame changes. + [Contributed by SESI] + - The From Particles SOP now provides the option to write into + an existing grid. + - Added a SOP to edit grid metadata. + - The Fracture SOP now supports multiple cutter objects. + - Added a To Polygons SOP that complements the Fracture SOP and allows + for elimination of seam lines, generation of correct vertex normals + and grouping of polygons when surfacing fracture fragments, using + the original level set or mesh as a reference. + +Version 0.103.1 - January 15 2013 + - tree::ValueAccessor read operations are now faster for four-level trees. + (Preliminary benchmark tests suggest a 30-40% improvement.) + - For vector-valued grids, tools::compMin() and tools::compMax() + now compare vector magnitudes instead of individual components. + - Migrated grid sampling code to a new file, Interpolation.h, + and deprecated old files and classes. + - Added a level-set fracture tool and a Fracture SOP. + - Added tools::sdfInteriorMask(), which creates a mask of the + interior region of a level set grid. + - Fixed a bug in the mesh to volume converter that produced unexpected + nonzero values for voxels at the intersection of two polygons, + and another bug that produced narrow-band widths that didn't respect + the background value when the half-band width was less than three voxels. + - houdini_utils::ParmFactory can now correctly generate ramp multi-parms. + - Made various changes for Visual C++ compatibility. + [Contributed by SESI] + - The Convert SOP can now convert between signed distance fields and + fog volumes and from volumes to meshes. + [Contributed by SESI] + - For level sets, the From Mesh and From Particles SOPs now match + the reference grid's narrow-band width. + - The Scatter SOP can now optionally scatter points in the interior + of a level set. + +Version 0.103.0 - December 21 2012 + - The mesh to volume converter is now 60% faster at generating + level sets with wide bands, and the From Mesh SOP is now interruptible. + - Fixed a threading bug in the recently-added compReplace() tool + that caused it to produce incorrect output. + - Added a probeConstLeaf() method to the Tree, ValueAccessor and + node classes. + - The Houdini VDB primitive doesn't create a "name" attribute + unnecessarily (i.e., if its grid's name is empty), but it now + correctly allows the name to be changed to the empty string. + - Fixed a crash in the Vector Merge SOP when fewer than three grids + were merged. + - The From Particles SOP now features a "maximum half-width" parameter + to help avoid runaway computations. + +Version 0.102.0 - December 13 2012 + - Added tools::compReplace(), which copies the active values of one grid + into another, and added a "Replace A With Active B" mode to the + Combine SOP. + - Grid::signedFloodFill() no longer enters an infinite loop when + filling an empty grid. + - Fixed a bug in the particle to level set converter that sometimes + produced level sets with holes, and fixed a bug in the SOP that + could result in random output. + - Fixed an issue in the frustum preview feature of the Create SOP + whereby rendering very large frustums could cause high CPU usage. + - Added streamline support to the constrained advection scheme + in the Advect Points SOP. + - Added an Advect Level Set SOP. + +Version 0.101.1 - December 11 2012 (DWA internal release) + - Partially reverted the Houdini VDB primitive's grid accessor methods + to their pre-0.98.0 behavior. A primitive's grid can once again + be accessed by shared pointer, but now also by reference. + Accessor methods for grid metadata have also been added, and the + primitive now ensures that metadata and transforms are never shared. + - Fixed an intermittent crash in the From Particles SOP. + +Version 0.101.0 - December 6 2012 (DWA internal release) + - Partially reverted the Grid's tree and transform accessor methods + to their pre-0.98.0 behavior, eliminating copy-on-write but + preserving their return-by-reference semantics. These methods + are now supplemented with a suite of shared pointer accessors. + - Restructured the mesh to volume converter for a 40% speedup + and to be more robust to non-manifold geometry, to better preserve + sharp features, to support arbitrary tree configurations and + to respect narrow-band limits. + - Added a getNodeBoundingBox() method to RootNode, InternalNode + and LeafNode that returns the index space spanned by a node. + - Made various changes for Visual C++ compatibility. + [Contributed by SESI] + - Renamed the Reshape Level Set SOP to Offset Level Set. + - Fixed a crash in the Convert SOP and added support for conversion + of empty grids. + +Version 0.100.0 - November 30 2012 (DWA internal release) + - Greatly improved the performance of the level set to fog volume + converter. + - Improved the performance of the median filter and of level set + CSG operations. + - Reintroduced Tree::pruneLevelSet(), a specialized pruneInactive() + for level-set grids. + - Added utilities to the houdini_utils library to facilitate the + collection of a chain of adjacent nodes of a particular type + so that they can be cooked in a single step. (For example, + adjacent xform SOPs could be collapsed by composing their + transformation matrices into a single matrix.) + - Added pruning and flood-filling options to the Convert SOP. + - Reimplemented the Filter SOP, omitting level-set-specific filters + and adding node chaining (to reduce memory usage when applying + several filters in sequence). + - Added a toggle to the Read SOP to read grid metadata and + transforms only. + - Changed the attribute transfer scheme on the From Mesh and + From Particles SOPs to allow for custom grid names and + vector type metadata. + +Version 0.99.0 - November 21 2012 + - Added Grid methods that return non-const Tree and Transform + references without triggering deep copies, as well as const + methods that return const shared pointers. + - Added Grid methods to populate a grid's metadata with statistics + like the active voxel count, and to retrieve that metadata. + By default, statistics are now computed and added to grids + whenever they are written to .vdb files. + - Added io::File::readGridMetadata() and io::File::readAllGridMetadata() + methods to read just the grid metadata and transforms from a .vdb file. + - Fixed numerical precision issues in the csgUnion, csgIntersection + and csgDifference tools, and added toggles to optionally disable + postprocess pruning. + - Fixed an issue in vdb_view with the ordering of GL vertex buffer calls. + [Contributed by Bill Katz] + - Fixed an intermittent crash in the ParticlesToLevelSet tool, + as well as a race condition that could cause data corruption. + - The ParticlesToLevelSet tool and From Particles SOP can now + transfer arbitrary point attribute values from the input particles + to output voxels. + - Fixed a bug in the Convert SOP whereby the names of primitives + were lost during conversion, and another bug that resulted in + an arithmetic error when converting an empty grid. + - Fixed a bug in the Combine SOP that caused the Operation selection + to be lost. + +Version 0.98.0 - November 16 2012 + - Tree and Transform objects (and Grid objects in the context of + Houdini SOPs) are now passed and accessed primarily by reference + rather than by shared pointer. See the online documentation for + details about this important API change. + [Contributed by SESI] + - Reimplemented CoordBBox to address several off-by-one bugs + related to bounding box dimensions. + - Fixed an off-by-one bug in Grid::evalActiveVoxelBoundingBox(). + - Introduced the LeafManager class, which will eventually replace the + LeafArray class. LeafManager supports dynamic buffers stored as + a structure of arrays (SOA), unlike LeafArray, which supports only + static buffers stored as an array of structures (AOS). + - Improved the performance of the LevelSetFilter and LevelSetTracker + tools by rewriting them to use the new LeafManager class. + - Added Tree and ValueAccessor setValueOnly() methods, which change + the value of a voxel without changing its active state, and + Tree and ValueAccessor probeLeaf() methods that return the leaf node + that contains a given voxel (unless the voxel is represented by a tile). + - Added a LevelSetAdvection tool that propagates and tracks + narrow-band level sets. + - Introduced a new GridSampler class that supports world-space + (or index-space) sampling of grid values. + - Changed the interpretation of the NonlinearFrustumMap's taper + parameter to be the ratio of the near and far plane depths. + - Added a ParmFactory::setChoiceList() overload that accepts + (token, label) string pairs, and a setDefault() overload that + accepts an STL string. + - Fixed a crash in the Combine SOP in Copy B mode. + - Split the Level Set Filter SOP into three separate SOPs, + Level Set Smooth, Level Set Reshape and Level Set Renormalize. + When two or more of these nodes are connected in sequence, they interact + to reduce memory usage: the last node in the sequence performs + all of the operations in one step. + - The Advect Points SOP can now output polyline streamlines + that trace the paths of the points. + - Added an option to the Analysis SOP to specify names for output grids. + - Added camera-derived frustum transform support to the Create SOP. + +Version 0.97.0 - October 18 2012 + - Added a narrow-band level set interface tracking tool (up to + fifth-order in space but currently only first-order in time, + with higher temporal orders to be added soon). + - Added a level set filter tool to perform unrestricted surface + smoothing (e.g., Laplacian flow), filtering (e.g., mean value) + and morphological operations (e.g., morphological opening). + - Added adaptivity to the level set meshing tool for faster mesh + extraction with fewer polygons, without postprocessing. + - Added a ValueAccessor::touchLeaf() method that creates (if necessary) + and returns the leaf node containing a given voxel. It can be used + to preallocate leaf nodes over which to run parallel algorithms. + - Fixed a bug in Grid::merge() whereby active tiles were sometimes lost. + - Added LeafManager, which is similar to LeafArray but supports a + dynamic buffer count and allocates buffers more efficiently. + Useful for temporal integration (e.g., for level set propagation + and interface tracking), LeafManager is meant to replace LeafArray, + which will be deprecated in the next release. + - Added a LeafNode::fill() method to efficiently populate leaf nodes + with constant values. + - Added a Tree::visitActiveBBox() method that applies a functor to the + bounding boxes of all active tiles and leaf nodes and that can be used + to improve the performance of ray intersection tests, rendering of + bounding boxes, etc. + - Added a Tree::voxelizeActiveTiles() method to densify active tiles. + While convenient and fast, this can produce large dense grids, + so use it with caution. + - Repackaged Tree::pruneLevelSet() as a Tree::pruneOp()-compatible + functor. LevelSetPrune is a specialized pruneInactive() for + level-set grids and is used in interface tracking. + - Added a GridBase::pruneGrid() method. + - Added a Grid:hasUniformVoxels() method. + - Renamed tools::dilate() to tools::dilateVoxels() and improved its + performance. The new name reflects the fact that the current + implementation ignores active tiles. + - Added a tools::resampleToMatch() function that resamples an input + grid into an output grid with a different transform such that, after + resampling, the input and output grids coincide, but the output + grid's transform is preserved. + - Significantly improved the performance of depth-bounded value + iterators (ValueOnIter, ValueAllIter, etc.) when the depth bound + excludes leaf nodes. + - Exposed the value buffers inside leaf nodes with LeafNode::buffer(). + This allows for very fast access (const and non-const) to voxel + values using linear array offsets instead of (i,j,k) coordinates. + - In openvdb_houdini/UT_VDBTools.h, added operators for use with + processTypedGrid() that resample grids in several different ways. + - Added a policy mechanism to houdini_utils::OpFactory that allows for + customization of operator names, icons, and Help URLs. + - Renamed many of the Houdini SOPs to make the names more consistent. + - Added an Advect Points SOP. + - Added a Level Set Filter SOP that allows for unrestricted surface + deformations, unlike the older Filter SOP, which restricts surface + motion to the initial narrow band. + - Added staggered vector sampling to the Sample Points SOP. + - Added a minimum radius threshold to the particle voxelization tool + and SOP. + - Merged the Composite and CSG SOPs into a single Combine SOP. + - Added a tool and a SOP to efficiently generate narrow-band level set + representations of spheres. + - In the Visualize SOP, improved the performance of tree topology + generation, which is now enabled by default. + +Version 0.96.0 - September 24 2012 + - Fixed a memory corruption bug in the mesh voxelizer tool. + - Temporarily removed the optional clipping feature from + the level set mesher. + - Added "Staggered Vector Field" to the list of grid classes + in the Create SOP. + +Version 0.95.0 - September 20 2012 + - Added a quad meshing tool for higher-quality level set meshing + and updated the Visualizer SOP to use it. + - Fixed a precision error in the mesh voxelizer and improved + the quality of inside/outside voxel classification. + Output grids are now also tagged as either level sets or fog volumes. + - Modified the GridResampler to use the signed flood fill optimization + only on grids that are tagged as level sets. + - Added a quaternion class to the math library and a method to + return the trace of a Mat3. + - Fixed a bug in the ValueAccessor copy constructor that caused + the copy to reference the original. + - Fixed a bug in RootNode::setActiveState() that caused a crash + when marking a (virtual) background voxel as inactive. + - Added a Tree::pruneLevelSet() method that is similar to but faster than + pruneInactive() for level set grids. + - Added fast leaf node voxel access methods that index by linear offset + (as returned by ValueIter::pos()) instead of by (i, j, k) coordinates. + - Added a Tree::touchLeaf() method that can be used to preallocate a static + tree topology over which to safely perform multithreaded processing. + - Added a grain size argument to the LeafArray class for finer control + of parallelism. + - Modified the Makefile to make it easier to omit the doc, vdb_test + and vdb_view targets. + - Added utility functions (in houdini/UT_VDBUtils.h) to convert + between Houdini and OpenVDB matrix and vector types. + [Contributed by SESI] + - Added accessors to GEO_PrimVDB that make it easier to directly access + voxel data and that are used by the HScript volume expression functions + in Houdini 12.5. + [Contributed by SESI] + - As of Houdini 12.1.77, the native transform SOP operates on OpenVDB + primitives. + [Contributed by SESI] + - Added a Convert SOP that converts OpenVDB grids to Houdini volumes + and vice-versa. + +Version 0.94.1 - September 7 2012 + - Fixed bugs in RootNode and InternalNode setValue*() and fill() methods + that could cause neighboring voxels to become inactive. + - Fixed a bug in Tree::hasSameTopology() that caused false positives + when only active states and not values differed. + - Added a Tree::hasActiveTiles() method. + - For better cross-platform consistency, substituted bitwise AND + operations for right shifts in the ValueAccessor hash key computation. + - vdb_view no longer aborts when asked to surface a vector-valued + grid--but it still doesn't render the surface. + - Made various changes for Visual C++ compatibility. + [Contributed by SESI] + - Added an option to the MeshVoxelizer SOP to convert both open and + closed surfaces to unsigned distance fields. + - The Filter SOP now allows multiple filters to be applied in + user-specified order. + +Version 0.94.0 - August 30 2012 + - Added a method to union just the active states of voxels from + one grid with those of another grid of a possibly different type. + - Fixed an incorrect scale factor in the Laplacian diffusion filter. + - Fixed a bug in Tree::merge that could leave a tree with invalid + value accessors. + - Added TreeValueIteratorBase::setActiveState() and deprecated setValueOn(). + - Removed tools/FastSweeping.h. It will be replaced with a much more + efficient implementation in the near future. + - ZIP compression of .vdb files is now optional, but enabled by default. + [Contributed by SESI] + - Made various changes for Clang and Visual C++ compatibility. + [Contributed by SESI] + - The MeshVoxelizer SOP can now transfer arbitrary point and + primitive attribute values from the input mesh to output voxels. + +Version 0.93.0 - August 24 2012 + - Renamed symbols in math/Operators.h to avoid ambiguities that + GCC 4.4 reports as errors. + - Simplified the API for the stencil version of the + closest-point transform operator. + - Added logic to io::Archive::readGrid() to set the grid name metadata + from the descriptor if the metadata doesn't already exist. + - Added guards to prevent nesting of openvdb_houdini::Interrupter::start() + and end() calls. + + +Version 0.92.0 - August 23 2012 + - Added a Laplacian diffusion filter. + - Fixed a bug in the initialization of the sparse contour tracer + that caused mesh-to-volume conversion to fail in certain cases. + - Fixed a bug in the curvature stencil that caused mean curvature + filtering to produce wrong results. + - Increased the speed of the GridTransformer by as much as 20% + for fog volumes. + - Added optional pruning to the Resample SOP. + - Modified the PointSample SOP to allow it to work with ungrouped, + anonymous grids. + - Fixed a crash in the LevelSetNoise SOP. + +Version 0.91.0 - August 16 2012 + - tools::GridTransformer and tools::GridResampler now correctly + (but not yet efficiently) process tiles in sparse grids. + - Added an optional CopyPolicy argument to GridBase::copyGrid() + and to Grid::copy() that specifies whether and how the grid's tree + should be copied. + - Added a GridBase::newTree() method that replaces a grid's tree with + a new, empty tree of the correct type. + - Fixed a crash in Tree::setValueOff() when the new value was equal to + the background value. + - Fixed bugs in Tree::prune() that could result in output tiles with + incorrect active states. + - Added librt to the link dependencies to address build failures on + Ubuntu systems. + - Made various small changes to the Makefile and the source code + that should help with Mac OS X compatibility. + - The Composite and Resample SOPs now correctly copy the input grid's + metadata to the output grid. + +Version 0.90.1 - August 7 2012 + - Fixed a bug in the BBox::getCenter() method. + - Added missing header files to various files. + - io::File::NameIterator::gridName() now returns a unique name of the + form "name[1]", "name[2]", etc. if a file contains multiple grids with + the same name. + - Fixed a bug in the Writer SOP that caused grid names to be discarded. + - The Resample SOP now correctly sets the background value of the + output grid. + +Version 0.90.0 - August 3 2012 (initial public release) + - Added a basic GL viewer for OpenVDB files. + - Greatly improved the performance of two commonly-used Tree methods, + evalActiveVoxelBoundingBox() and memUsage(). + - Eliminated the GridMap class. File I/O now uses STL containers + of grid pointers instead. + - Refactored stencil-based tools (Gradient, Laplacian, etc.) and rewrote + some of them for generality and better performance. Most now behave + correctly for grids with nonlinear index-to-world transforms. + - Added a library of index-space finite difference operators. + - Added a "Hermite" grid type that compactly stores each voxel's upwind + normals and can be used to convert volumes to and from polygonal meshes. + - Added a tool (and a Houdini SOP) to scatter points randomly throughout + a volume. + diff --git a/openvdb_2_3_0_library/openvdb/COPYRIGHT b/openvdb_2_3_0_library/openvdb/COPYRIGHT new file mode 100755 index 0000000..4bd6195 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/COPYRIGHT @@ -0,0 +1,25 @@ +Copyright (c) 2012-2013 DreamWorks Animation LLC + +All rights reserved. This software is distributed under the +Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) + +Redistributions of source code must retain the above copyright +and license notice and the following restrictions and disclaimer. + +* Neither the name of DreamWorks Animation nor the names of +its contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. diff --git a/openvdb_2_3_0_library/openvdb/Exceptions.h b/openvdb_2_3_0_library/openvdb/Exceptions.h new file mode 100755 index 0000000..dc07642 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/Exceptions.h @@ -0,0 +1,112 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_EXCEPTIONS_HAS_BEEN_INCLUDED +#define OPENVDB_EXCEPTIONS_HAS_BEEN_INCLUDED + +#include +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +class OPENVDB_API Exception: public std::exception +{ +public: + virtual const char* what() const throw() + { + try { return mMessage.c_str(); } catch (...) {}; + return NULL; + } + + virtual ~Exception() throw() {} + +protected: + Exception() throw() {} + explicit Exception(const char* eType, const std::string* const msg = NULL) throw() + { + try { + if (eType) mMessage = eType; + if (msg) mMessage += ": " + (*msg); + } catch (...) {} + } + +private: + std::string mMessage; +}; + + +#define OPENVDB_EXCEPTION(_classname) \ +class OPENVDB_API _classname: public Exception \ +{ \ +public: \ + _classname() throw() : Exception( #_classname ) {} \ + explicit _classname(const std::string &msg) throw() : Exception( #_classname , &msg) {} \ +} + + +OPENVDB_EXCEPTION(ArithmeticError); +OPENVDB_EXCEPTION(IllegalValueException); +OPENVDB_EXCEPTION(IndexError); +OPENVDB_EXCEPTION(IoError); +OPENVDB_EXCEPTION(KeyError); +OPENVDB_EXCEPTION(LookupError); +OPENVDB_EXCEPTION(NotImplementedError); +OPENVDB_EXCEPTION(ReferenceError); +OPENVDB_EXCEPTION(RuntimeError); +OPENVDB_EXCEPTION(TypeError); +OPENVDB_EXCEPTION(ValueError); + + +#undef OPENVDB_EXCEPTION + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + + +#define OPENVDB_THROW(exception, message) \ +{ \ + std::string _openvdb_throw_msg; \ + try { \ + std::ostringstream _openvdb_throw_os; \ + _openvdb_throw_os << message; \ + _openvdb_throw_msg = _openvdb_throw_os.str(); \ + } catch (...) {} \ + throw exception(_openvdb_throw_msg); \ +} // OPENVDB_THROW + +#endif // OPENVDB_EXCEPTIONS_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/Grid.cc b/openvdb_2_3_0_library/openvdb/Grid.cc new file mode 100755 index 0000000..7154c49 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/Grid.cc @@ -0,0 +1,487 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Grid.h" + +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +/// @note For Houdini compatibility, boolean-valued metadata names +/// should begin with "is_". +const char + * const GridBase::META_GRID_CLASS = "class", + * const GridBase::META_GRID_CREATOR = "creator", + * const GridBase::META_GRID_NAME = "name", + * const GridBase::META_SAVE_HALF_FLOAT = "is_saved_as_half_float", + * const GridBase::META_IS_LOCAL_SPACE = "is_local_space", + * const GridBase::META_VECTOR_TYPE = "vector_type", + * const GridBase::META_FILE_BBOX_MIN = "file_bbox_min", + * const GridBase::META_FILE_BBOX_MAX = "file_bbox_max", + * const GridBase::META_FILE_COMPRESSION = "file_compression", + * const GridBase::META_FILE_MEM_BYTES = "file_mem_bytes", + * const GridBase::META_FILE_VOXEL_COUNT = "file_voxel_count"; + +namespace { +/// @todo Remove (deprecated in favor of META_SAVE_HALF_FLOAT) +const char *SAVE_FLOAT_AS_HALF = "write as 16-bit float"; +} + + +//////////////////////////////////////// + + +namespace { + +typedef std::map GridFactoryMap; +typedef GridFactoryMap::const_iterator GridFactoryMapCIter; + +typedef tbb::mutex Mutex; +typedef Mutex::scoped_lock Lock; + +struct LockedGridRegistry { + LockedGridRegistry() {} + ~LockedGridRegistry() {} + Mutex mMutex; + GridFactoryMap mMap; +}; + +// Declare this at file scope to ensure thread-safe initialization. +Mutex sInitGridRegistryMutex; + + +// Global function for accessing the registry +LockedGridRegistry* +getGridRegistry() +{ + Lock lock(sInitGridRegistryMutex); + + static LockedGridRegistry* registry = NULL; + + if (registry == NULL) { + +#ifdef __ICC +// Disable ICC "assignment to statically allocated variable" warning. +// This assignment is mutex-protected and therefore thread-safe. +__pragma(warning(disable:1711)) +#endif + + registry = new LockedGridRegistry(); + +#ifdef __ICC +__pragma(warning(default:1711)) +#endif + + } + + return registry; +} + +} // unnamed namespace + + +bool +GridBase::isRegistered(const Name& name) +{ + LockedGridRegistry* registry = getGridRegistry(); + Lock lock(registry->mMutex); + + return (registry->mMap.find(name) != registry->mMap.end()); +} + + +void +GridBase::registerGrid(const Name& name, GridFactory factory) +{ + LockedGridRegistry* registry = getGridRegistry(); + Lock lock(registry->mMutex); + + if (registry->mMap.find(name) != registry->mMap.end()) { + OPENVDB_THROW(KeyError, "Grid type " << name << " is already registered"); + } + + registry->mMap[name] = factory; +} + + +void +GridBase::unregisterGrid(const Name& name) +{ + LockedGridRegistry* registry = getGridRegistry(); + Lock lock(registry->mMutex); + + registry->mMap.erase(name); +} + + +GridBase::Ptr +GridBase::createGrid(const Name& name) +{ + LockedGridRegistry* registry = getGridRegistry(); + Lock lock(registry->mMutex); + + GridFactoryMapCIter iter = registry->mMap.find(name); + + if (iter == registry->mMap.end()) { + OPENVDB_THROW(LookupError, "Cannot create grid of unregistered type " << name); + } + + return (iter->second)(); +} + + +void +GridBase::clearRegistry() +{ + LockedGridRegistry* registry = getGridRegistry(); + Lock lock(registry->mMutex); + + registry->mMap.clear(); +} + + +//////////////////////////////////////// + + +GridClass +GridBase::stringToGridClass(const std::string& s) +{ + GridClass ret = GRID_UNKNOWN; + std::string str = s; + boost::trim(str); + boost::to_lower(str); + if (str == gridClassToString(GRID_LEVEL_SET)) { + ret = GRID_LEVEL_SET; + } else if (str == gridClassToString(GRID_FOG_VOLUME)) { + ret = GRID_FOG_VOLUME; + } else if (str == gridClassToString(GRID_STAGGERED)) { + ret = GRID_STAGGERED; + } + return ret; +} + + +std::string +GridBase::gridClassToString(GridClass cls) +{ + std::string ret; + switch (cls) { + case GRID_UNKNOWN: ret = "unknown"; break; + case GRID_LEVEL_SET: ret = "level set"; break; + case GRID_FOG_VOLUME: ret = "fog volume"; break; + case GRID_STAGGERED: ret = "staggered"; break; + } + return ret; +} + +std::string +GridBase::gridClassToMenuName(GridClass cls) +{ + std::string ret; + switch (cls) { + case GRID_UNKNOWN: ret = "Other"; break; + case GRID_LEVEL_SET: ret = "Level Set"; break; + case GRID_FOG_VOLUME: ret = "Fog Volume"; break; + case GRID_STAGGERED: ret = "Staggered Vector Field"; break; + } + return ret; +} + + + +GridClass +GridBase::getGridClass() const +{ + GridClass cls = GRID_UNKNOWN; + if (StringMetadata::ConstPtr s = this->getMetadata(META_GRID_CLASS)) { + cls = stringToGridClass(s->value()); + } + return cls; +} + + +void +GridBase::setGridClass(GridClass cls) +{ + this->insertMeta(META_GRID_CLASS, StringMetadata(gridClassToString(cls))); +} + + +void +GridBase::clearGridClass() +{ + this->removeMeta(META_GRID_CLASS); +} + + +//////////////////////////////////////// + + +VecType +GridBase::stringToVecType(const std::string& s) +{ + VecType ret = VEC_INVARIANT; + std::string str = s; + boost::trim(str); + boost::to_lower(str); + if (str == vecTypeToString(VEC_COVARIANT)) { + ret = VEC_COVARIANT; + } else if (str == vecTypeToString(VEC_COVARIANT_NORMALIZE)) { + ret = VEC_COVARIANT_NORMALIZE; + } else if (str == vecTypeToString(VEC_CONTRAVARIANT_RELATIVE)) { + ret = VEC_CONTRAVARIANT_RELATIVE; + } else if (str == vecTypeToString(VEC_CONTRAVARIANT_ABSOLUTE)) { + ret = VEC_CONTRAVARIANT_ABSOLUTE; + } + return ret; +} + + +std::string +GridBase::vecTypeToString(VecType typ) +{ + std::string ret; + switch (typ) { + case VEC_INVARIANT: ret = "invariant"; break; + case VEC_COVARIANT: ret = "covariant"; break; + case VEC_COVARIANT_NORMALIZE: ret = "covariant normalize"; break; + case VEC_CONTRAVARIANT_RELATIVE: ret = "contravariant relative"; break; + case VEC_CONTRAVARIANT_ABSOLUTE: ret = "contravariant absolute"; break; + } + return ret; +} + + +std::string +GridBase::vecTypeExamples(VecType typ) +{ + std::string ret; + switch (typ) { + case VEC_INVARIANT: ret = "Tuple/Color/UVW"; break; + case VEC_COVARIANT: ret = "Gradient/Normal"; break; + case VEC_COVARIANT_NORMALIZE: ret = "Unit Normal"; break; + case VEC_CONTRAVARIANT_RELATIVE: ret = "Displacement/Velocity/Acceleration"; break; + case VEC_CONTRAVARIANT_ABSOLUTE: ret = "Position"; break; + } + return ret; +} + + +std::string +GridBase::vecTypeDescription(VecType typ) +{ + std::string ret; + switch (typ) { + case VEC_INVARIANT: + ret = "Does not transform"; + break; + case VEC_COVARIANT: + ret = "Apply the inverse-transpose transform matrix but ignore translation"; + break; + case VEC_COVARIANT_NORMALIZE: + ret = "Apply the inverse-transpose transform matrix but ignore translation" + " and renormalize vectors"; + break; + case VEC_CONTRAVARIANT_RELATIVE: + ret = "Apply the forward transform matrix but ignore translation"; + break; + case VEC_CONTRAVARIANT_ABSOLUTE: + ret = "Apply the forward transform matrix, including translation"; + break; + } + return ret; +} + + +VecType +GridBase::getVectorType() const +{ + VecType typ = VEC_INVARIANT; + if (StringMetadata::ConstPtr s = this->getMetadata(META_VECTOR_TYPE)) { + typ = stringToVecType(s->value()); + } + return typ; +} + + +void +GridBase::setVectorType(VecType typ) +{ + this->insertMeta(META_VECTOR_TYPE, StringMetadata(vecTypeToString(typ))); +} + + +void +GridBase::clearVectorType() +{ + this->removeMeta(META_VECTOR_TYPE); +} + + +//////////////////////////////////////// + + +std::string +GridBase::getName() const +{ + if (Metadata::ConstPtr meta = (*this)[META_GRID_NAME]) return meta->str(); + return ""; +} + + +void +GridBase::setName(const std::string& name) +{ + this->removeMeta(META_GRID_NAME); + this->insertMeta(META_GRID_NAME, StringMetadata(name)); +} + + +//////////////////////////////////////// + + +std::string +GridBase::getCreator() const +{ + if (Metadata::ConstPtr meta = (*this)[META_GRID_CREATOR]) return meta->str(); + return ""; +} + + +void +GridBase::setCreator(const std::string& creator) +{ + this->removeMeta(META_GRID_CREATOR); + this->insertMeta(META_GRID_CREATOR, StringMetadata(creator)); +} + + +//////////////////////////////////////// + + +bool +GridBase::saveFloatAsHalf() const +{ + bool saveAsHalf = false; + if (Metadata::ConstPtr meta = (*this)[META_SAVE_HALF_FLOAT]) { + saveAsHalf = meta->asBool(); + } else if ((*this)[SAVE_FLOAT_AS_HALF]) { + // Old behavior: saveAsHalf is true if metadata named + // SAVE_FLOAT_AS_HALF exists, regardless of its value. + saveAsHalf = true; + } + return saveAsHalf; +} + + +void +GridBase::setSaveFloatAsHalf(bool saveAsHalf) +{ + this->removeMeta(META_SAVE_HALF_FLOAT); + this->insertMeta(META_SAVE_HALF_FLOAT, BoolMetadata(saveAsHalf)); + + // Remove the old, deprecated metadata. + this->removeMeta(SAVE_FLOAT_AS_HALF); +} + + +//////////////////////////////////////// + + +bool +GridBase::isInWorldSpace() const +{ + bool local = false; + if (Metadata::ConstPtr meta = (*this)[META_IS_LOCAL_SPACE]) { + local = meta->asBool(); + } + return !local; +} + + +void +GridBase::setIsInWorldSpace(bool world) +{ + this->removeMeta(META_IS_LOCAL_SPACE); + this->insertMeta(META_IS_LOCAL_SPACE, BoolMetadata(!world)); +} + + +//////////////////////////////////////// + + +void +GridBase::addStatsMetadata() +{ + const CoordBBox bbox = this->evalActiveVoxelBoundingBox(); + this->removeMeta(META_FILE_BBOX_MIN); + this->removeMeta(META_FILE_BBOX_MAX); + this->removeMeta(META_FILE_MEM_BYTES); + this->removeMeta(META_FILE_VOXEL_COUNT); + this->insertMeta(META_FILE_BBOX_MIN, Vec3IMetadata(bbox.min().asVec3i())); + this->insertMeta(META_FILE_BBOX_MAX, Vec3IMetadata(bbox.max().asVec3i())); + this->insertMeta(META_FILE_MEM_BYTES, Int64Metadata(this->memUsage())); + this->insertMeta(META_FILE_VOXEL_COUNT, Int64Metadata(this->activeVoxelCount())); +} + + +MetaMap::Ptr +GridBase::getStatsMetadata() const +{ + static const char* const sFields[] = { + META_FILE_BBOX_MIN, + META_FILE_BBOX_MAX, + META_FILE_MEM_BYTES, + META_FILE_VOXEL_COUNT, + NULL + }; + + /// @todo Check that the fields are of the correct type? + MetaMap::Ptr ret(new MetaMap); + for (int i = 0; sFields[i] != NULL; ++i) { + if (Metadata::ConstPtr m = (*this)[sFields[i]]) { + ret->insertMeta(sFields[i], *m); + } + } + return ret; +} + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/Grid.h b/openvdb_2_3_0_library/openvdb/Grid.h new file mode 100755 index 0000000..42018f2 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/Grid.h @@ -0,0 +1,1338 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_GRID_HAS_BEEN_INCLUDED +#define OPENVDB_GRID_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +typedef tree::TreeBase TreeBase; + +template class Grid; // forward declaration + + +/// @brief Create a new grid of type @c GridType with a given background value. +/// +/// @note Calling createGrid(background) is equivalent to calling +/// GridType::create(background). +template +inline typename GridType::Ptr createGrid(const typename GridType::ValueType& background); + + +/// @brief Create a new grid of type @c GridType with background value zero. +/// +/// @note Calling createGrid() is equivalent to calling GridType::create(). +template +inline typename GridType::Ptr createGrid(); + + +/// @brief Create a new grid of the appropriate type that wraps the given tree. +/// +/// @note This function can be called without specifying the template argument, +/// i.e., as createGrid(tree). +template +inline typename Grid::Ptr createGrid(TreePtrType); + + +/// @brief Create a new grid of type @c GridType classified as a "Level Set", +/// i.e., a narrow-band level set. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +/// +/// @param voxelSize the size of a voxel in world units +/// @param halfWidth the half width of the narrow band in voxel units +/// +/// @details The voxel size and the narrow band half width define the grid's +/// background value as halfWidth*voxelWidth. The transform is linear +/// with a uniform scaling only corresponding to the specified voxel size. +/// +/// @note It is generally advisable to specify a half-width of the narrow band +/// that is larger than one voxel unit, otherwise zero crossings are not guaranteed. +template +typename GridType::Ptr createLevelSet( + Real voxelSize = 1.0, Real halfWidth = LEVEL_SET_HALF_WIDTH); + + +//////////////////////////////////////// + + +/// @brief Abstract base class for typed grids +class OPENVDB_API GridBase: public MetaMap +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + typedef Ptr (*GridFactory)(); + + + virtual ~GridBase() {} + + /// @brief Return a new grid of the same type as this grid and whose + /// metadata and transform are deep copies of this grid's. + virtual GridBase::Ptr copyGrid(CopyPolicy treePolicy = CP_SHARE) const = 0; + + /// Return a new grid whose metadata, transform and tree are deep copies of this grid's. + virtual GridBase::Ptr deepCopyGrid() const = 0; + + + // + // Registry methods + // + /// Create a new grid of the given (registered) type. + static Ptr createGrid(const Name& type); + + /// Return @c true if the given grid type name is registered. + static bool isRegistered(const Name &type); + + /// Clear the grid type registry. + static void clearRegistry(); + + + // + // Grid type methods + // + /// Return the name of this grid's type. + virtual Name type() const = 0; + /// Return the name of the type of a voxel's value (e.g., "float" or "vec3d"). + virtual Name valueType() const = 0; + + /// Return @c true if this grid is of the same type as the template parameter. + template + bool isType() const { return (this->type() == GridType::gridType()); } + + //@{ + /// @brief Return the result of downcasting a GridBase pointer to a Grid pointer + /// of the specified type, or return a null pointer if the types are incompatible. + template + static typename GridType::Ptr grid(const GridBase::Ptr&); + template + static typename GridType::ConstPtr grid(const GridBase::ConstPtr&); + template + static typename GridType::ConstPtr constGrid(const GridBase::Ptr&); + template + static typename GridType::ConstPtr constGrid(const GridBase::ConstPtr&); + //@} + + //@{ + /// @brief Return a pointer to this grid's tree, which might be + /// shared with other grids. The pointer is guaranteed to be non-null. + TreeBase::Ptr baseTreePtr(); + TreeBase::ConstPtr baseTreePtr() const { return this->constBaseTreePtr(); } + virtual TreeBase::ConstPtr constBaseTreePtr() const = 0; + //@} + + //@{ + /// @brief Return a reference to this grid's tree, which might be + /// shared with other grids. + /// @note Calling setTree() on this grid invalidates all references + /// previously returned by this method. + TreeBase& baseTree() { return const_cast(this->constBaseTree()); } + const TreeBase& baseTree() const { return this->constBaseTree(); } + const TreeBase& constBaseTree() const { return *(this->constBaseTreePtr()); } + //@} + + /// @brief Associate the given tree with this grid, in place of its existing tree. + /// @throw ValueError if the tree pointer is null + /// @throw TypeError if the tree is not of the appropriate type + /// @note Invalidates all references previously returned by baseTree() + /// or constBaseTree(). + virtual void setTree(TreeBase::Ptr) = 0; + + /// Set a new tree with the same background value as the previous tree. + virtual void newTree() = 0; + + /// Return @c true if this grid contains only background voxels. + virtual bool empty() const = 0; + /// Empty this grid, setting all voxels to the background. + virtual void clear() = 0; + + /// @brief Reduce the memory footprint of this grid by increasing its sparseness + /// either losslessly (@a tolerance = 0) or lossily (@a tolerance > 0). + /// @details With @a tolerance > 0, sparsify regions where voxels have the same + /// active state and have values that differ by no more than the tolerance + /// (converted to this grid's value type). + virtual void pruneGrid(float tolerance = 0.0) = 0; + + + // + // Metadata + // + /// Return this grid's user-specified name. + std::string getName() const; + /// Specify a name for this grid. + void setName(const std::string&); + + /// Return the user-specified description of this grid's creator. + std::string getCreator() const; + /// Provide a description of this grid's creator. + void setCreator(const std::string&); + + /// @brief Return @c true if this grid should be written out with floating-point + /// voxel values (including components of vectors) quantized to 16 bits. + bool saveFloatAsHalf() const; + void setSaveFloatAsHalf(bool); + + /// Return the class of volumetric data (level set, fog volume, etc.) stored in this grid. + GridClass getGridClass() const; + /// Specify the class of volumetric data (level set, fog volume, etc.) stored in this grid. + void setGridClass(GridClass); + /// Remove the setting specifying the class of this grid's volumetric data. + void clearGridClass(); + + /// Return the metadata string value for the given class of volumetric data. + static std::string gridClassToString(GridClass); + /// Return a formatted string version of the grid class. + static std::string gridClassToMenuName(GridClass); + /// @brief Return the class of volumetric data specified by the given string. + /// @details If the string is not one of the ones returned by gridClassToString(), + /// return @c GRID_UNKNOWN. + static GridClass stringToGridClass(const std::string&); + + /// @brief Return the type of vector data (invariant, covariant, etc.) stored + /// in this grid, assuming that this grid contains a vector-valued tree. + VecType getVectorType() const; + /// @brief Specify the type of vector data (invariant, covariant, etc.) stored + /// in this grid, assuming that this grid contains a vector-valued tree. + void setVectorType(VecType); + /// Remove the setting specifying the type of vector data stored in this grid. + void clearVectorType(); + + /// Return the metadata string value for the given type of vector data. + static std::string vecTypeToString(VecType); + /// Return a string listing examples of the given type of vector data + /// (e.g., "Gradient/Normal", given VEC_COVARIANT). + static std::string vecTypeExamples(VecType); + /// @brief Return a string describing how the given type of vector data is affected + /// by transformations (e.g., "Does not transform", given VEC_INVARIANT). + static std::string vecTypeDescription(VecType); + static VecType stringToVecType(const std::string&); + + /// Return @c true if this grid's voxel values are in world space and should be + /// affected by transformations, @c false if they are in local space and should + /// not be affected by transformations. + bool isInWorldSpace() const; + /// Specify whether this grid's voxel values are in world space or in local space. + void setIsInWorldSpace(bool); + + // Standard metadata field names + // (These fields should normally not be accessed directly, but rather + // via the accessor methods above, when available.) + // Note: Visual C++ requires these declarations to be separate statements. + static const char* const META_GRID_CLASS; + static const char* const META_GRID_CREATOR; + static const char* const META_GRID_NAME; + static const char* const META_SAVE_HALF_FLOAT; + static const char* const META_IS_LOCAL_SPACE; + static const char* const META_VECTOR_TYPE; + static const char* const META_FILE_BBOX_MIN; + static const char* const META_FILE_BBOX_MAX; + static const char* const META_FILE_COMPRESSION; + static const char* const META_FILE_MEM_BYTES; + static const char* const META_FILE_VOXEL_COUNT; + + + // + // Statistics + // + /// Return the number of active voxels. + virtual Index64 activeVoxelCount() const = 0; + + /// Return the axis-aligned bounding box of all active voxels. If + /// the grid is empty a default bbox is returned. + virtual CoordBBox evalActiveVoxelBoundingBox() const = 0; + + /// Return the dimensions of the axis-aligned bounding box of all active voxels. + virtual Coord evalActiveVoxelDim() const = 0; + + /// Return the number of bytes of memory used by this grid. + virtual Index64 memUsage() const = 0; + + /// @brief Add metadata to this grid comprising the current values + /// of statistics like the active voxel count and bounding box. + /// @note This metadata is not automatically kept up-to-date with + /// changes to this grid. + void addStatsMetadata(); + /// @brief Return a new MetaMap containing just the metadata that + /// was added to this grid with addStatsMetadata(). + /// @details If addStatsMetadata() was never called on this grid, + /// return an empty MetaMap. + MetaMap::Ptr getStatsMetadata() const; + + + // + // Transform methods + // + //@{ + /// @brief Return a pointer to this grid's transform, which might be + /// shared with other grids. + math::Transform::Ptr transformPtr() { return mTransform; } + math::Transform::ConstPtr transformPtr() const { return mTransform; } + math::Transform::ConstPtr constTransformPtr() const { return mTransform; } + //@} + //@{ + /// @brief Return a reference to this grid's transform, which might be + /// shared with other grids. + /// @note Calling setTransform() on this grid invalidates all references + /// previously returned by this method. + math::Transform& transform() { return *mTransform; } + const math::Transform& transform() const { return *mTransform; } + const math::Transform& constTransform() const { return *mTransform; } + //@} + /// @brief Associate the given transform with this grid, in place of + /// its existing transform. + /// @throw ValueError if the transform pointer is null + /// @note Invalidates all references previously returned by transform() + /// or constTransform(). + void setTransform(math::Transform::Ptr); + + /// Return the size of this grid's voxels. + Vec3d voxelSize() const { return transform().voxelSize(); } + /// @brief Return the size of this grid's voxel at position (x, y, z). + /// @note Frustum and perspective transforms have position-dependent voxel size. + Vec3d voxelSize(const Vec3d& xyz) const { return transform().voxelSize(xyz); } + /// Return true if the voxels in world space are uniformly sized cubes + bool hasUniformVoxels() const { return mTransform->hasUniformScale(); } + //@{ + /// Apply this grid's transform to the given coordinates. + Vec3d indexToWorld(const Vec3d& xyz) const { return transform().indexToWorld(xyz); } + Vec3d indexToWorld(const Coord& ijk) const { return transform().indexToWorld(ijk); } + //@} + /// Apply the inverse of this grid's transform to the given coordinates. + Vec3d worldToIndex(const Vec3d& xyz) const { return transform().worldToIndex(xyz); } + + + // + // I/O methods + // + /// @brief Read the grid topology from a stream. + /// This will read only the grid structure, not the actual data buffers. + virtual void readTopology(std::istream&) = 0; + /// @brief Write the grid topology to a stream. + /// This will write only the grid structure, not the actual data buffers. + virtual void writeTopology(std::ostream&) const = 0; + + /// Read all data buffers for this grid. + virtual void readBuffers(std::istream&) = 0; + /// Write out all data buffers for this grid. + virtual void writeBuffers(std::ostream&) const = 0; + + /// Read in the transform for this grid. + void readTransform(std::istream& is) { transform().read(is); } + /// Write out the transform for this grid. + void writeTransform(std::ostream& os) const { transform().write(os); } + + /// Output a human-readable description of this grid. + virtual void print(std::ostream& = std::cout, int verboseLevel = 1) const = 0; + + +protected: + /// @brief Initialize with an identity linear transform. + GridBase(): mTransform(math::Transform::createLinearTransform()) {} + + /// @brief Deep copy another grid's metadata and transform. + GridBase(const GridBase& other): MetaMap(other), mTransform(other.mTransform->copy()) {} + + /// @brief Copy another grid's metadata but share its transform. + GridBase(const GridBase& other, ShallowCopy): MetaMap(other), mTransform(other.mTransform) {} + + /// Register a grid type along with a factory function. + static void registerGrid(const Name& type, GridFactory); + /// Remove a grid type from the registry. + static void unregisterGrid(const Name& type); + + +private: + math::Transform::Ptr mTransform; +}; // class GridBase + + +//////////////////////////////////////// + + +typedef std::vector GridPtrVec; +typedef GridPtrVec::iterator GridPtrVecIter; +typedef GridPtrVec::const_iterator GridPtrVecCIter; +typedef boost::shared_ptr GridPtrVecPtr; + +typedef std::vector GridCPtrVec; +typedef GridCPtrVec::iterator GridCPtrVecIter; +typedef GridCPtrVec::const_iterator GridCPtrVecCIter; +typedef boost::shared_ptr GridCPtrVecPtr; + +typedef std::set GridPtrSet; +typedef GridPtrSet::iterator GridPtrSetIter; +typedef GridPtrSet::const_iterator GridPtrSetCIter; +typedef boost::shared_ptr GridPtrSetPtr; + +typedef std::set GridCPtrSet; +typedef GridCPtrSet::iterator GridCPtrSetIter; +typedef GridCPtrSet::const_iterator GridCPtrSetCIter; +typedef boost::shared_ptr GridCPtrSetPtr; + + +/// @brief Predicate functor that returns @c true for grids that have a specified name +struct OPENVDB_API GridNamePred +{ + GridNamePred(const Name& _name): name(_name) {} + bool operator()(const GridBase::ConstPtr& g) const { return g && g->getName() == name; } + Name name; +}; + +/// Return the first grid in the given container whose name is @a name. +template +inline typename GridPtrContainerT::value_type +findGridByName(const GridPtrContainerT& container, const Name& name) +{ + typedef typename GridPtrContainerT::value_type GridPtrT; + typename GridPtrContainerT::const_iterator it = + std::find_if(container.begin(), container.end(), GridNamePred(name)); + return (it == container.end() ? GridPtrT() : *it); +} + +/// Return the first grid in the given map whose name is @a name. +template +inline GridPtrT +findGridByName(const std::map& container, const Name& name) +{ + typedef std::map GridPtrMapT; + for (typename GridPtrMapT::const_iterator it = container.begin(), end = container.end(); + it != end; ++it) + { + const GridPtrT& grid = it->second; + if (grid && grid->getName() == name) return grid; + } + return GridPtrT(); +} +//@} + + +//////////////////////////////////////// + + +/// @brief Container class that associates a tree with a transform and metadata +template +class Grid: public GridBase +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + typedef _TreeType TreeType; + typedef typename _TreeType::Ptr TreePtrType; + typedef typename _TreeType::ConstPtr ConstTreePtrType; + typedef typename _TreeType::ValueType ValueType; + + typedef typename tree::ValueAccessor<_TreeType> Accessor; + typedef typename tree::ValueAccessor ConstAccessor; + + typedef typename _TreeType::ValueOnIter ValueOnIter; + typedef typename _TreeType::ValueOnCIter ValueOnCIter; + typedef typename _TreeType::ValueOffIter ValueOffIter; + typedef typename _TreeType::ValueOffCIter ValueOffCIter; + typedef typename _TreeType::ValueAllIter ValueAllIter; + typedef typename _TreeType::ValueAllCIter ValueAllCIter; + + /// @brief ValueConverter::Type is the type of a grid having the same + /// hierarchy as this grid but a different value type, T. + /// + /// For example, FloatGrid::ValueConverter::Type is equivalent to DoubleGrid. + /// @note If the source grid type is a template argument, it might be necessary + /// to write "typename SourceGrid::template ValueConverter::Type". + template + struct ValueConverter { + typedef Grid::Type> Type; + }; + + /// Return a new grid with the given background value. + static Ptr create(const ValueType& background); + /// Return a new grid with background value zero. + static Ptr create(); + /// @brief Return a new grid that contains the given tree. + /// @throw ValueError if the tree pointer is null + static Ptr create(TreePtrType); + /// @brief Return a new, empty grid with the same transform and metadata as the + /// given grid and with background value zero. + static Ptr create(const GridBase& other); + + + /// Construct a new grid with background value zero. + Grid(); + /// Construct a new grid with the given background value. + explicit Grid(const ValueType& background); + /// @brief Construct a new grid that shares the given tree and associates with it + /// an identity linear transform. + /// @throw ValueError if the tree pointer is null + explicit Grid(TreePtrType); + /// Deep copy another grid's metadata, transform and tree. + Grid(const Grid&); + /// @brief Deep copy the metadata, transform and tree of another grid whose tree + /// configuration is the same as this grid's but whose value type is different. + /// Cast the other grid's values to this grid's value type. + /// @throw TypeError if the other grid's tree configuration doesn't match this grid's + /// or if this grid's ValueType is not constructible from the other grid's ValueType. + template + explicit Grid(const Grid&); + /// Deep copy another grid's metadata, but share its tree and transform. + Grid(const Grid&, ShallowCopy); + /// @brief Deep copy another grid's metadata and transform, but construct a new tree + /// with background value zero. + Grid(const GridBase&); + + virtual ~Grid() {} + + //@{ + /// @brief Return a new grid of the same type as this grid and whose + /// metadata and transform are deep copies of this grid's. + /// @details If @a treePolicy is @c CP_NEW, give the new grid a new, empty tree; + /// if @c CP_SHARE, the new grid shares this grid's tree and transform; + /// if @c CP_COPY, the new grid's tree is a deep copy of this grid's tree and transform + Ptr copy(CopyPolicy treePolicy = CP_SHARE) const; + virtual GridBase::Ptr copyGrid(CopyPolicy treePolicy = CP_SHARE) const; + //@} + //@{ + /// Return a new grid whose metadata, transform and tree are deep copies of this grid's. + Ptr deepCopy() const { return Ptr(new Grid(*this)); } + virtual GridBase::Ptr deepCopyGrid() const { return this->deepCopy(); } + //@} + + /// Return the name of this grid's type. + virtual Name type() const { return this->gridType(); } + /// Return the name of this type of grid. + static Name gridType() { return TreeType::treeType(); } + + + // + // Voxel access methods + // + /// Return the name of the type of a voxel's value (e.g., "float" or "vec3d"). + virtual Name valueType() const { return tree().valueType(); } + + /// Return this grid's background value. + const ValueType& background() const { return mTree->background(); } + /// Replace this grid's background value. + void setBackground(const ValueType& val) { tree().setBackground(val); } + + /// Return @c true if this grid contains only inactive background voxels. + virtual bool empty() const { return tree().empty(); } + /// Empty this grid, so that all voxels become inactive background voxels. + virtual void clear() { tree().clear(); } + + /// Return an accessor that provides random read and write access to this grid's voxels. + Accessor getAccessor() { return Accessor(tree()); } + //@{ + /// Return an accessor that provides random read-only access to this grid's voxels. + ConstAccessor getAccessor() const { return ConstAccessor(tree()); } + ConstAccessor getConstAccessor() const { return ConstAccessor(tree()); } + //@} + + //@{ + /// Return an iterator over all of this grid's active values (tile and voxel). + ValueOnIter beginValueOn() { return tree().beginValueOn(); } + ValueOnCIter beginValueOn() const { return tree().cbeginValueOn(); } + ValueOnCIter cbeginValueOn() const { return tree().cbeginValueOn(); } + //@} + //@{ + /// Return an iterator over all of this grid's inactive values (tile and voxel). + ValueOffIter beginValueOff() { return tree().beginValueOff(); } + ValueOffCIter beginValueOff() const { return tree().cbeginValueOff(); } + ValueOffCIter cbeginValueOff() const { return tree().cbeginValueOff(); } + //@} + //@{ + /// Return an iterator over all of this grid's values (tile and voxel). + ValueAllIter beginValueAll() { return tree().beginValueAll(); } + ValueAllCIter beginValueAll() const { return tree().cbeginValueAll(); } + ValueAllCIter cbeginValueAll() const { return tree().cbeginValueAll(); } + //@} + + /// Return the minimum and maximum active values in this grid. + void evalMinMax(ValueType& minVal, ValueType& maxVal) const; + + /// @brief Set all voxels within a given axis-aligned box to a constant value. + /// @param bbox inclusive coordinates of opposite corners of an axis-aligned box + /// @param value the value to which to set voxels within the box + /// @param active if true, mark voxels within the box as active, + /// otherwise mark them as inactive + /// @note This operation generates a sparse, but not always optimally sparse, + /// representation of the filled box. Follow fill operations with a prune() + /// operation for optimal sparseness. + void fill(const CoordBBox& bbox, const ValueType& value, bool active = true); + + /// @brief Set the values of all inactive voxels and tiles of a narrow-band + /// level set from the signs of the active voxels, setting outside values to + /// +background and inside values to -background. + /// + /// @note This operation should only be used on closed, narrow-band level sets! + void signedFloodFill() { tree().signedFloodFill(); } + + /// @brief Set the values of all inactive voxels and tiles of a narrow-band + /// level set from the signs of the active voxels, setting outside values to + /// @a outside and inside values to @a inside. + /// @details Also, set this grid's background value to @a outside. + /// + /// @note This operation should only be used on closed, narrow-band level sets! + /// Also, @a inside should be negative, and @a outside should be larger than @a inside. + void signedFloodFill(const ValueType& outside, const ValueType& inside); + + /// @brief Reduce the memory footprint of this grid by increasing its sparseness + /// either losslessly (@a tolerance = 0) or lossily (@a tolerance > 0). + /// @details With @a tolerance > 0, sparsify regions where voxels have the same + /// active state and have values that differ by no more than the tolerance. + void prune(const ValueType& tolerance = zeroVal()) { tree().prune(tolerance); } + /// Reduce the memory footprint of this grid by increasing its sparseness. + virtual void pruneGrid(float tolerance = 0.0); + + /// @brief Efficiently merge another grid into this grid using one of several schemes. + /// @details This operation is primarily intended to combine grids that are mostly + /// non-overlapping (for example, intermediate grids from computations that are + /// parallelized across disjoint regions of space). + /// @warning This operation always empties the other grid. + void merge(Grid& other, MergePolicy policy = MERGE_ACTIVE_STATES); + + /// @brief Union this grid's set of active values with the active values + /// of the other grid, whose value type may be different. + /// @details The resulting state of a value is active if the corresponding value + /// was already active OR if it is active in the other grid. Also, a resulting + /// value maps to a voxel if the corresponding value already mapped to a voxel + /// OR if it is a voxel in the other grid. Thus, a resulting value can only + /// map to a tile if the corresponding value already mapped to a tile + /// AND if it is a tile value in the other grid. + /// + /// @note This operation modifies only active states, not values. + /// Specifically, active tiles and voxels in this grid are not changed, and + /// tiles or voxels that were inactive in this grid but active in the other grid + /// are marked as active in this grid but left with their original values. + template + void topologyUnion(const Grid& other); + + /// @brief Intersect this grid's set of active values with the active values + /// of the other grid, whose value type may be different. + /// @details The resulting state of a value is active 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 of this grid that overlap with + /// inactive tiles in the other grid. Also, because it can deactivate voxels, + /// it can create leaf nodes with no active values. Thus, it is recommended + /// to prune this grid after calling this method. + template + void topologyIntersection(const Grid& other); + + /// @brief Difference this grid's set of active values with the active values + /// of the other grid, whose value type may be different. + /// @details After this method is called, voxels in this grid will be active + /// only if they were active to begin with and if the corresponding voxels + /// in the other grid were inactive. + /// + /// @note This operation can delete branches of this grid that overlap with + /// active tiles in the other grid. Also, because it can deactivate voxels, + /// it can create leaf nodes with no active values. Thus, it is recommended + /// to prune this grid after calling this method. + template + void topologyDifference(const Grid& other); + + // + // Statistics + // + /// Return the number of active voxels. + virtual Index64 activeVoxelCount() const { return tree().activeVoxelCount(); } + /// Return the axis-aligned bounding box of all active voxels. + virtual CoordBBox evalActiveVoxelBoundingBox() const; + /// Return the dimensions of the axis-aligned bounding box of all active voxels. + virtual Coord evalActiveVoxelDim() const; + + /// Return the number of bytes of memory used by this grid. + /// @todo Add transform().memUsage() + virtual Index64 memUsage() const { return tree().memUsage(); } + + + // + // Tree methods + // + //@{ + /// @brief Return a pointer to this grid's tree, which might be + /// shared with other grids. The pointer is guaranteed to be non-null. + TreePtrType treePtr() { return mTree; } + ConstTreePtrType treePtr() const { return mTree; } + ConstTreePtrType constTreePtr() const { return mTree; } + virtual TreeBase::ConstPtr constBaseTreePtr() const { return mTree; } + //@} + //@{ + /// @brief Return a reference to this grid's tree, which might be + /// shared with other grids. + /// @note Calling setTree() on this grid invalidates all references + /// previously returned by this method. + TreeType& tree() { return *mTree; } + const TreeType& tree() const { return *mTree; } + const TreeType& constTree() const { return *mTree; } + //@} + + /// @brief Associate the given tree with this grid, in place of its existing tree. + /// @throw ValueError if the tree pointer is null + /// @throw TypeError if the tree is not of type TreeType + /// @note Invalidates all references previously returned by baseTree(), + /// constBaseTree(), tree() or constTree(). + virtual void setTree(TreeBase::Ptr); + + /// @brief Associate a new, empty tree with this grid, in place of its existing tree. + /// @note The new tree has the same background value as the existing tree. + virtual void newTree(); + + + // + // I/O methods + // + /// @brief Read the grid topology from a stream. + /// This will read only the grid structure, not the actual data buffers. + virtual void readTopology(std::istream&); + /// @brief Write the grid topology to a stream. + /// This will write only the grid structure, not the actual data buffers. + virtual void writeTopology(std::ostream&) const; + + /// Read all data buffers for this grid. + virtual void readBuffers(std::istream&); + /// Write out all data buffers for this grid. + virtual void writeBuffers(std::ostream&) const; + + /// Output a human-readable description of this grid. + virtual void print(std::ostream& = std::cout, int verboseLevel = 1) const; + + + // + // Registry methods + // + /// Return @c true if this grid type is registered. + static bool isRegistered() { return GridBase::isRegistered(Grid::gridType()); } + /// Register this grid type along with a factory function. + static void registerGrid() { GridBase::registerGrid(Grid::gridType(), Grid::factory); } + /// Remove this grid type from the registry. + static void unregisterGrid() { GridBase::unregisterGrid(Grid::gridType()); } + + +private: + /// Disallow assignment, since it wouldn't be obvious whether the copy is deep or shallow. + Grid& operator=(const Grid& other); + + /// Helper function for use with registerGrid() + static GridBase::Ptr factory() { return Grid::create(); } + + TreePtrType mTree; +}; // class Grid + + +//////////////////////////////////////// + + +/// @brief Cast a generic grid pointer to a pointer to a grid of a concrete class. +/// +/// Return a null pointer if the input pointer is null or if it +/// points to a grid that is not of type @c GridType. +/// +/// @note Calling gridPtrCast(grid) is equivalent to calling +/// GridBase::grid(grid). +template +inline typename GridType::Ptr +gridPtrCast(const GridBase::Ptr& grid) +{ + return GridBase::grid(grid); +} + + +/// @brief Cast a generic const grid pointer to a const pointer to a grid +/// of a concrete class. +/// +/// Return a null pointer if the input pointer is null or if it +/// points to a grid that is not of type @c GridType. +/// +/// @note Calling gridConstPtrCast(grid) is equivalent to calling +/// GridBase::constGrid(grid). +template +inline typename GridType::ConstPtr +gridConstPtrCast(const GridBase::ConstPtr& grid) +{ + return GridBase::constGrid(grid); +} + + +//////////////////////////////////////// + + +/// @{ +/// @brief Return a pointer to a deep copy of the given grid, provided that +/// the grid's concrete type is @c GridType. +/// +/// Return a null pointer if the input pointer is null or if it +/// points to a grid that is not of type @c GridType. +template +inline typename GridType::Ptr +deepCopyTypedGrid(const GridBase::ConstPtr& grid) +{ + if (!grid || !grid->isType()) return typename GridType::Ptr(); + return gridPtrCast(grid->deepCopyGrid()); +} + + +template +inline typename GridType::Ptr +deepCopyTypedGrid(const GridBase& grid) +{ + if (!grid.isType()) return typename GridType::Ptr(); + return gridPtrCast(grid.deepCopyGrid()); +} +/// @} + + +//////////////////////////////////////// + + +//@{ +/// @brief This adapter allows code that is templated on a Tree type to +/// accept either a Tree type or a Grid type. +template +struct TreeAdapter +{ + typedef _TreeType TreeType; + typedef typename boost::remove_const::type NonConstTreeType; + typedef typename TreeType::Ptr TreePtrType; + typedef typename TreeType::ConstPtr ConstTreePtrType; + typedef typename NonConstTreeType::Ptr NonConstTreePtrType; + typedef Grid GridType; + typedef Grid NonConstGridType; + typedef typename GridType::Ptr GridPtrType; + typedef typename NonConstGridType::Ptr NonConstGridPtrType; + typedef typename GridType::ConstPtr ConstGridPtrType; + typedef typename TreeType::ValueType ValueType; + typedef typename tree::ValueAccessor AccessorType; + typedef typename tree::ValueAccessor ConstAccessorType; + typedef typename tree::ValueAccessor NonConstAccessorType; + + static TreeType& tree(TreeType& t) { return t; } + static TreeType& tree(GridType& g) { return g.tree(); } + static const TreeType& tree(const TreeType& t) { return t; } + static const TreeType& tree(const GridType& g) { return g.tree(); } + static const TreeType& constTree(TreeType& t) { return t; } + static const TreeType& constTree(GridType& g) { return g.constTree(); } + static const TreeType& constTree(const TreeType& t) { return t; } + static const TreeType& constTree(const GridType& g) { return g.constTree(); } +}; + + +/// Partial specialization for Grid types +template +struct TreeAdapter > +{ + typedef _TreeType TreeType; + typedef typename boost::remove_const::type NonConstTreeType; + typedef typename TreeType::Ptr TreePtrType; + typedef typename TreeType::ConstPtr ConstTreePtrType; + typedef typename NonConstTreeType::Ptr NonConstTreePtrType; + typedef Grid GridType; + typedef Grid NonConstGridType; + typedef typename GridType::Ptr GridPtrType; + typedef typename NonConstGridType::Ptr NonConstGridPtrType; + typedef typename GridType::ConstPtr ConstGridPtrType; + typedef typename TreeType::ValueType ValueType; + typedef typename tree::ValueAccessor AccessorType; + typedef typename tree::ValueAccessor ConstAccessorType; + typedef typename tree::ValueAccessor NonConstAccessorType; + + static TreeType& tree(TreeType& t) { return t; } + static TreeType& tree(GridType& g) { return g.tree(); } + static const TreeType& tree(const TreeType& t) { return t; } + static const TreeType& tree(const GridType& g) { return g.tree(); } + static const TreeType& constTree(TreeType& t) { return t; } + static const TreeType& constTree(GridType& g) { return g.constTree(); } + static const TreeType& constTree(const TreeType& t) { return t; } + static const TreeType& constTree(const GridType& g) { return g.constTree(); } +}; + +/// Partial specialization for ValueAccessor types +template +struct TreeAdapter > +{ + typedef _TreeType TreeType; + typedef typename boost::remove_const::type NonConstTreeType; + typedef typename TreeType::Ptr TreePtrType; + typedef typename TreeType::ConstPtr ConstTreePtrType; + typedef typename NonConstTreeType::Ptr NonConstTreePtrType; + typedef Grid GridType; + typedef Grid NonConstGridType; + typedef typename GridType::Ptr GridPtrType; + typedef typename NonConstGridType::Ptr NonConstGridPtrType; + typedef typename GridType::ConstPtr ConstGridPtrType; + typedef typename TreeType::ValueType ValueType; + typedef typename tree::ValueAccessor AccessorType; + typedef typename tree::ValueAccessor ConstAccessorType; + typedef typename tree::ValueAccessor NonConstAccessorType; + + static TreeType& tree(TreeType& t) { return t; } + static TreeType& tree(GridType& g) { return g.tree(); } + static TreeType& tree(AccessorType& a) { return a.tree(); } + static const TreeType& tree(const TreeType& t) { return t; } + static const TreeType& tree(const GridType& g) { return g.tree(); } + static const TreeType& tree(const AccessorType& a) { return a.tree(); } + static const TreeType& constTree(TreeType& t) { return t; } + static const TreeType& constTree(GridType& g) { return g.constTree(); } + static const TreeType& constTree(const TreeType& t) { return t; } + static const TreeType& constTree(const GridType& g) { return g.constTree(); } +}; + +//@} + + +//////////////////////////////////////// + + +template +inline typename GridType::Ptr +GridBase::grid(const GridBase::Ptr& grid) +{ + // The string comparison on type names is slower than a dynamic_pointer_cast, but + // it is safer when pointers cross dso boundaries, as they do in many Houdini nodes. + if (grid && grid->type() == GridType::gridType()) { + return boost::static_pointer_cast(grid); + } + return typename GridType::Ptr(); +} + + +template +inline typename GridType::ConstPtr +GridBase::grid(const GridBase::ConstPtr& grid) +{ + return boost::const_pointer_cast( + GridBase::grid(boost::const_pointer_cast(grid))); +} + + +template +inline typename GridType::ConstPtr +GridBase::constGrid(const GridBase::Ptr& grid) +{ + return boost::const_pointer_cast(GridBase::grid(grid)); +} + + +template +inline typename GridType::ConstPtr +GridBase::constGrid(const GridBase::ConstPtr& grid) +{ + return boost::const_pointer_cast( + GridBase::grid(boost::const_pointer_cast(grid))); +} + + +inline TreeBase::Ptr +GridBase::baseTreePtr() +{ + return boost::const_pointer_cast(this->constBaseTreePtr()); +} + + +inline void +GridBase::setTransform(math::Transform::Ptr xform) +{ + if (!xform) OPENVDB_THROW(ValueError, "Transform pointer is null"); + mTransform = xform; +} + + +//////////////////////////////////////// + + +template +inline Grid::Grid(): mTree(new TreeType) +{ +} + + +template +inline Grid::Grid(const ValueType &background): mTree(new TreeType(background)) +{ +} + + +template +inline Grid::Grid(TreePtrType tree): mTree(tree) +{ + if (!tree) OPENVDB_THROW(ValueError, "Tree pointer is null"); +} + + +template +inline Grid::Grid(const Grid& other): + GridBase(other), + mTree(boost::static_pointer_cast(other.mTree->copy())) +{ +} + + +template +template +inline Grid::Grid(const Grid& other): + GridBase(other), + mTree(new TreeType(other.constTree())) +{ +} + + +template +inline Grid::Grid(const Grid& other, ShallowCopy): + GridBase(other, ShallowCopy()), + mTree(other.mTree) +{ +} + + +template +inline Grid::Grid(const GridBase& other): + GridBase(other), + mTree(new TreeType) +{ +} + + +//static +template +inline typename Grid::Ptr +Grid::create() +{ + return Grid::create(zeroVal()); +} + + +//static +template +inline typename Grid::Ptr +Grid::create(const ValueType& background) +{ + return Ptr(new Grid(background)); +} + + +//static +template +inline typename Grid::Ptr +Grid::create(TreePtrType tree) +{ + return Ptr(new Grid(tree)); +} + + +//static +template +inline typename Grid::Ptr +Grid::create(const GridBase& other) +{ + return Ptr(new Grid(other)); +} + + +//////////////////////////////////////// + + +template +inline typename Grid::Ptr +Grid::copy(CopyPolicy treePolicy) const +{ + Ptr ret; + switch (treePolicy) { + case CP_NEW: + ret.reset(new Grid(*this, ShallowCopy())); + ret->newTree(); + break; + case CP_COPY: + ret.reset(new Grid(*this)); + break; + case CP_SHARE: + ret.reset(new Grid(*this, ShallowCopy())); + break; + } + return ret; +} + + +template +inline GridBase::Ptr +Grid::copyGrid(CopyPolicy treePolicy) const +{ + return this->copy(treePolicy); +} + + +//////////////////////////////////////// + + +template +inline void +Grid::setTree(TreeBase::Ptr tree) +{ + if (!tree) OPENVDB_THROW(ValueError, "Tree pointer is null"); + if (tree->type() != TreeType::treeType()) { + OPENVDB_THROW(TypeError, "Cannot assign a tree of type " + + tree->type() + " to a grid of type " + this->type()); + } + mTree = boost::static_pointer_cast(tree); +} + + +template +inline void +Grid::newTree() +{ + mTree.reset(new TreeType(this->background())); +} + + +//////////////////////////////////////// + + +template +inline void +Grid::fill(const CoordBBox& bbox, const ValueType& value, bool active) +{ + tree().fill(bbox, value, active); +} + + +template +inline void +Grid::signedFloodFill(const ValueType& outside, const ValueType& inside) +{ + tree().signedFloodFill(outside, inside); +} + + +template +inline void +Grid::pruneGrid(float tolerance) +{ + this->prune(ValueType(zeroVal() + tolerance)); +} + + +template +inline void +Grid::merge(Grid& other, MergePolicy policy) +{ + tree().merge(other.tree(), policy); +} + + +template +template +inline void +Grid::topologyUnion(const Grid& other) +{ + tree().topologyUnion(other.tree()); +} + + +template +template +inline void +Grid::topologyIntersection(const Grid& other) +{ + tree().topologyIntersection(other.tree()); +} + + +template +template +inline void +Grid::topologyDifference(const Grid& other) +{ + tree().topologyDifference(other.tree()); +} + + +//////////////////////////////////////// + + +template +inline void +Grid::evalMinMax(ValueType& minVal, ValueType& maxVal) const +{ + tree().evalMinMax(minVal, maxVal); +} + + +template +inline CoordBBox +Grid::evalActiveVoxelBoundingBox() const +{ + CoordBBox bbox; + tree().evalActiveVoxelBoundingBox(bbox); + return bbox; +} + + +template +inline Coord +Grid::evalActiveVoxelDim() const +{ + Coord dim; + const bool nonempty = tree().evalActiveVoxelDim(dim); + return (nonempty ? dim : Coord()); +} + + +//////////////////////////////////////// + + +/// @internal Consider using the stream tagging mechanism (see io::Archive) +/// to specify the float precision, but note that the setting is per-grid. + +template +inline void +Grid::readTopology(std::istream& is) +{ + tree().readTopology(is, saveFloatAsHalf()); +} + + +template +inline void +Grid::writeTopology(std::ostream& os) const +{ + tree().writeTopology(os, saveFloatAsHalf()); +} + + +template +inline void +Grid::readBuffers(std::istream& is) +{ + tree().readBuffers(is, saveFloatAsHalf()); +} + + +template +inline void +Grid::writeBuffers(std::ostream& os) const +{ + tree().writeBuffers(os, saveFloatAsHalf()); +} + + +template +inline void +Grid::print(std::ostream& os, int verboseLevel) const +{ + tree().print(os, verboseLevel); + + if (metaCount() > 0) { + os << "Additional metadata:" << std::endl; + for (ConstMetaIterator it = beginMeta(), end = endMeta(); it != end; ++it) { + os << " " << it->first; + if (it->second) { + const std::string value = it->second->str(); + if (!value.empty()) os << ": " << value; + } + os << "\n"; + } + } + + os << "Transform:" << std::endl; + transform().print(os, /*indent=*/" "); + os << std::endl; +} + + +//////////////////////////////////////// + + +template +inline typename GridType::Ptr +createGrid(const typename GridType::ValueType& background) +{ + return GridType::create(background); +} + + +template +inline typename GridType::Ptr +createGrid() +{ + return GridType::create(); +} + + +template +inline typename Grid::Ptr +createGrid(TreePtrType tree) +{ + typedef typename TreePtrType::element_type TreeType; + return Grid::create(tree); +} + + +template +typename GridType::Ptr +createLevelSet(Real voxelSize, Real halfWidth) +{ + typedef typename GridType::ValueType ValueType; + + // GridType::ValueType is required to be a floating-point scalar. + BOOST_STATIC_ASSERT(boost::is_floating_point::value); + + typename GridType::Ptr grid = GridType::create( + /*background=*/static_cast(voxelSize * halfWidth)); + grid->setTransform(math::Transform::createLinearTransform(voxelSize)); + grid->setGridClass(GRID_LEVEL_SET); + return grid; +} + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_GRID_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/INSTALL b/openvdb_2_3_0_library/openvdb/INSTALL new file mode 100755 index 0000000..efad6de --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/INSTALL @@ -0,0 +1,231 @@ +Installing OpenVDB +================== + +Requirements +------------ +- GNU GCC (gcc.gnu.org), version 4.1 or later + or Intel ICC (software.intel.com), version 11.1 or later + +- GNU gmake (www.gnu.org/software/make/), version 3.81 or later + +- Boost (www.boost.org), version 1.42.0 or later + (Linux: yum install boost-devel; OS X: port install boost +python26) + +- libz (zlib.net) + (Linux: yum install zlib-devel) + +- OpenEXR (www.openexr.com), for the 16-bit float Half class in half.h + +- Intel Threading Building Blocks (threadingbuildingblocks.org), + version 3.0 or later + +Other compilers or versions might work but have not been tested. + + +Optional: + +- Doxygen 1.8 (www.stack.nl/~dimitri/doxygen/) + +- CppUnit (www.freedesktop.org/wiki/Software/cppunit), version 1.10 or later + (Linux: yum install cppunit-devel) + +- Ghostscript (www.ghostscript.com), version 8.70 or later, for documentation + in PDF format + +- log4cplus (log4cplus.sourceforge.net), version 1.0 or later + +- pdfLaTeX (www.pdftex.org), version 1.21 or later, for documentation + in PDF format + +- GLFW 2.7 (www.glfw.org), for the OpenVDB viewer + +- OpenGL 3.2 or later, for the OpenVDB viewer + +- Python 2.5, 2.6 or 2.7, for the Python module + +- NumPy (www.numpy.org), for the Python module + +- Epydoc (http://epydoc.sourceforge.net/), version 3.0 or later, + for Python module documentation + +Other versions might work but have not been tested. + + +Installation +------------ +1. Set values appropriate to your environment for the following variables + at the top of the Makefile: + + INSTALL_DIR the directory into which to install libraries, + executables and header files (e.g., /usr/local) + + BOOST_INCL_DIR the parent directory of the boost/ header directory + (e.g., /usr/include) + + EXR_INCL_DIR the parent directory of the OpenEXR/ header directory + EXR_LIB_DIR the directory containing libIlmImf, etc. + EXR_LIB linker flags for libIlmImf, libIlmThread, libIex + and libImath + + HALF_INCL_DIR the parent directory of the OpenEXR/ header directory + (which contains half.h) + HALF_LIB_DIR the directory containing libHalf.so and/or libHalf.a + HALF_LIB linker flag(s) for the Half library (e.g., -lHalf) + + TBB_INCL_DIR the parent directory of the tbb/ header directory + TBB_LIB_DIR the directory containing libtbb + TBB_LIB linker flag(s) for the TBB library (e.g., -ltbb) + + CONCURRENT_MALLOC_LIB_DIR + a directory containing a scalable, concurrent malloc + replacement library such as jemalloc or TBB malloc + (leave blank if no such library is available, but + be aware that using standard malloc in concurrent + code incurs a significant performance penalty) + CONCURRENT_MALLOC_LIB + linker flag(s) for the malloc replacement library + (e.g., -ltbbmalloc_proxy -ltbbmalloc) + + CPPUNIT_INCL_DIR the parent directory of the cppunit/ header directory + (leave blank if CppUnit is not available) + CPPUNIT_LIB_DIR the directory containing libcppunit.so or libcppunit.a + CPPUNIT_LIB linker flag(s) for the cppunit library + (e.g., -lcppunit) + + GLFW_INCL_DIR the directory containing glfw.h + (leave blank if GLFW is not available; + GLFW is needed only for the command-line viewer tool) + GLFW_LIB_DIR the directory containing libglfw + GLFW_LIB linker flags for the GLFW library (e.g., -lglfw) + + 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.6) + (leave blank if Python is unavailable) + PYTHON_INCL_DIR the directory containing the Python.h header file + (on OS X, this is usually /System/Library/Frameworks/ + Python.framework/Versions/$(PYTHON_VERSION)/Headers) + PYCONFIG_INCL_DIR the directory containing the pyconfig.h header file + (usually but not always the same as PYTHON_INCL_DIR) + PYTHON_LIB_DIR the directory containing the Python library + (on OS X, this is usually /System/Library/Frameworks/ + Python.framework/Versions/$(PYTHON_VERSION)/lib) + PYTHON_LIB linker flags for the Python library + (e.g., -lpython2.6) + BOOST_PYTHON_LIB_DIR + the directory containing the Boost.Python library + BOOST_PYTHON_LIB linker flags for the Boost.Python library + (e.g., -lboost_python-mt) + NUMPY_INCL_DIR the directory containing the NumPy arrayobject.h + header file (leave blank if NumPy is unavailable) + (on OS X, this is usually /System/Library/Frameworks/ + Python.framework/Versions/$(PYTHON_VERSION)/Extras/ + lib/python/numpy/core/include/numpy) + EPYDOC the path to the Epydoc executable + (leave blank if Epydoc is unavailable) + PYTHON_WRAP_ALL_GRID_TYPES + if set to "no", expose only FloatGrid, BoolGrid + and Vec3SGrid in Python, otherwise expose (most of) + the standard grid types defined in openvdb.h + + DOXYGEN the path to the Doxygen executable + (leave blank if Doxygen is unavailable) + + Note that if you plan to build the Houdini OpenVDB tools (distributed + separately), you must build the OpenVDB library and the Houdini tools + against compatible versions of the Boost, OpenEXR and TBB libraries. + Fortunately, all three are included in the Houdini HDK, so by default + several of the variables above reference the Houdini environment variables + $HDSO, $HFS and $HT. Source the houdini_setup script provided with + your Houdini installation to set those environment variables. + + To build the OpenVDB Python module, you will need local installations of + Python, Boost.Python, and optionally NumPy. As of Houdini 12.5, the HDK + includes versions 2.5 and 2.6 of Python as well as Boost.Python headers. + Unfortunately, it includes neither the libboost_python library nor NumPy, + so both Boost.Python and NumPy have to be built separately. + Point the variables $(BOOST_PYTHON_LIB_DIR), $(BOOST_PYTHON_LIB) and + $(NUMPY_INCL_DIR) to your local installations of those libraries. + +2. From the top-level openvdb/ directory, type "make" (or "make -s" for + less verbose output) to locally build the library and commands. + The Makefile supports parallel builds (e.g. "make -j 8"). + + A default local build generates the following libraries and executables + (but see the Makefile for additional targets and build options): + + openvdb/libopenvdb.so.2.3.0 the OpenVDB library + openvdb/libopenvdb.so symlink to libopenvdb.so.2.3.0 + openvdb/pyopenvdb.so the OpenVDB Python module (if Python + and Boost.Python are available) + openvdb/vdb_print command-line tool that prints info + about OpenVDB .vdb files + openvdb/vdb_render command-line tool that ray-traces + OpenVDB volumes + openvdb/vdb_test unit test runner for libopenvdb + (if CppUnit is available) + + From the openvdb/ directory, type "make test" to run the unit tests + and verify that the library is working correctly. (Alternatively, once + the library has been installed (Step 5), run the unit test executable + directly with "./vdb_test", or "./vdb_test -v" for more verbose output.) + Type "make pytest" to run the Python module unit tests. + +3. From the openvdb/ directory, type "make doc" (or "make -s doc") + to generate HTML library documentation, then open the file + openvdb/doc/html/index.html in a browser. Type "make pydoc" + (or "make -s pydoc") to generate HTML Python module documentation, + then open openvdb/doc/html/python/index.html in a browser. + +4. Optionally (if OpenGL and GLFW are available), from the top-level openvdb/ + directory, type "make vdb_view" (or "make -s vdb_view") to locally build + the OpenVDB viewer tool. Then type "./vdb_view" for usage information. + +5. From the openvdb/ directory, type "make install" (or "make -s install") + to copy generated files into the directory tree rooted at $(INSTALL_DIR). + This creates the following distribution: + + $(INSTALL_DIR)/ + bin/ + vdb_print + vdb_render + vdb_view + include/ + openvdb/ + Exceptions.h + ... + openvdb.h + tools/ + tree/ + ... + version.h + lib/ + libopenvdb.so + libopenvdb.so.2.3 + libopenvdb.so.2.3.0 + + python/ + include/ + python$(PYTHON_VERSION)/ + pyopenvdb.h + lib/ + python$(PYTHON_VERSION)/ + pyopenvdb.so + pyopenvdb.so.2.3 + + share/ + doc/ + openvdb/ + html/ + index.html + ... + python/ + index.html + ... + +EOF diff --git a/openvdb_2_3_0_library/openvdb/LICENSE b/openvdb_2_3_0_library/openvdb/LICENSE new file mode 100755 index 0000000..14e2f77 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/LICENSE @@ -0,0 +1,373 @@ +Mozilla 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. diff --git a/openvdb_2_3_0_library/openvdb/Makefile b/openvdb_2_3_0_library/openvdb/Makefile new file mode 100755 index 0000000..908e37e --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/Makefile @@ -0,0 +1,854 @@ +# Copyright (c) 2012-2013 DreamWorks Animation LLC +# +# All rights reserved. This software is distributed under the +# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +# +# Redistributions of source code must retain the above copyright +# and license notice and the following restrictions and disclaimer. +# +# * Neither the name of DreamWorks Animation nor the names of +# its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +# +# Makefile for the OpenVDB library + +# See INSTALL for a list of requirements. +# +# Targets: +# lib the OpenVDB library +# +# doc HTML documentation (doc/html/index.html) +# pdfdoc PDF documentation (doc/latex/refman.pdf; +# requires LaTeX and ghostscript) +# python OpenVDB Python module +# pytest unit tests for the Python module +# pydoc HTML documentation for the Python module +# (doc/html/python/index.html) +# vdb_print command-line tool to inspect OpenVDB files +# vdb_render command-line tool to ray-trace OpenVDB files +# vdb_view command-line tool to view OpenVDB files +# vdb_test unit tests for the OpenVDB library +# +# all [default target] all of the above +# install install all of the above except vdb_test +# into subdirectories of INSTALL_DIR +# depend recompute source file header dependencies +# clean delete generated files from the local directory +# test run tests +# +# Options: +# shared=no link executables against static OpenVDB libraries +# (default: link against shared libraries) +# debug=yes build with debugging symbols and without optimization +# verbose=yes run commands (e.g., doxygen) in verbose mode + + +# +# The following variables must be defined, either here or on the command line +# (e.g., "make install INSTALL_DIR=/usr/local"): +# +# Note that if you plan to build the Houdini OpenVDB tools (distributed +# separately), you must build the OpenVDB library and the Houdini tools +# against compatible versions of the Boost, OpenEXR and TBB libraries. +# Fortunately, all three are included in the Houdini HDK, so the relevant +# variables below point by default to the HDK library and header directories: +# $(HDSO) and $(HT)/include, respectively. (Source the houdini_setup script +# to set those two environment variables.) +# +# To build the OpenVDB Python module, you will need local distributions of +# Python, Boost.Python, and optionally NumPy. As of Houdini 12.5, the HDK +# includes versions 2.5 and 2.6 of Python as well as the Boost.Python headers. +# Unfortunately, it does not include the libboost_python library, nor does it +# include NumPy, so both Boost.Python and NumPy have to be built separately. +# Point the variables $(BOOST_PYTHON_LIB_DIR), $(BOOST_PYTHON_LIB) and +# $(NUMPY_INCL_DIR) below to your local distributions of those libraries. +# + +# The directory into which to install libraries, executables and header files +INSTALL_DIR := /tmp/OpenVDB + +# The parent directory of the boost/ header directory +BOOST_INCL_DIR := $(HT)/include + +# The parent directory of the OpenEXR/ header directory +EXR_INCL_DIR := $(HT)/include +# The directory containing libIlmImf, libIlmThread, etc. +EXR_LIB_DIR := $(HDSO) +EXR_LIB := -lIlmImf -lIlmThread -lIex -lImath + +# The parent directory of the OpenEXR/ header directory (which contains half.h) +HALF_INCL_DIR := $(EXR_INCL_DIR) +# The directory containing libHalf +HALF_LIB_DIR := $(EXR_LIB_DIR) +HALF_LIB := -lHalf + +# The parent directory of the tbb/ header directory +TBB_INCL_DIR := $(HT)/include +# The directory containing libtbb +TBB_LIB_DIR := $(HDSO) +TBB_LIB := -ltbb + +# A scalable, concurrent malloc replacement library +# such as jemalloc (included in the Houdini HDK) or TBB malloc +# (leave blank if unavailable) +CONCURRENT_MALLOC_LIB := -ljemalloc +#CONCURRENT_MALLOC_LIB := -ltbbmalloc_proxy -ltbbmalloc +# The directory containing the malloc replacement library +CONCURRENT_MALLOC_LIB_DIR := $(HDSO) + +# The parent directory of the cppunit/ header directory +# (leave blank if CppUnit is unavailable) +CPPUNIT_INCL_DIR := /rel/map/generic-2013.22/sys_include +# The directory containing libcppunit +CPPUNIT_LIB_DIR := /rel/depot/third_party_build/cppunit/1.10.2-7/opt-ws5-x86_64-gccWS5_64/lib +CPPUNIT_LIB := -lcppunit + +# The parent directory of the log4cplus/ header directory +# (leave blank if log4cplus is unavailable) +LOG4CPLUS_INCL_DIR := /rel/folio/log4cplus/log4cplus-1.0.3-latest/sys_include +# The directory containing liblog4cplus +LOG4CPLUS_LIB_DIR := /rel/folio/log4cplus/log4cplus-1.0.3-latest/library +LOG4CPLUS_LIB := -llog4cplus + +# The directory containing glfw.h +# (leave blank if GLFW is unavailable) +GLFW_INCL_DIR := /rel/third_party/glfw/current/include +# The directory containing libglfw +GLFW_LIB_DIR := /rel/third_party/glfw/current/lib +GLFW_LIB := -lglfw + +# The version of Python for which to build the OpenVDB module +# (leave blank if Python is unavailable) +PYTHON_VERSION := 2.6 +# The directory containing Python.h +PYTHON_INCL_DIR := $(HFS)/python/include/python$(PYTHON_VERSION) +# The directory containing pyconfig.h +PYCONFIG_INCL_DIR := $(PYTHON_INCL_DIR) +# The directory containing libpython +PYTHON_LIB_DIR := $(HFS)/python/lib +PYTHON_LIB := -lpython$(PYTHON_VERSION) +# The directory containing libboost_python +BOOST_PYTHON_LIB_DIR := /rel/depot/third_party_build/boost/rhel6-1.46.1-0/lib +BOOST_PYTHON_LIB := -lboost_python-gcc41-mt-python26-1_46_1 +# The directory containing arrayobject.h +# (leave blank if NumPy is unavailable) +NUMPY_INCL_DIR := /rel/map/generic-2013.22/include +# The Epydoc executable +# (leave blank if Epydoc is unavailable) +EPYDOC := epydoc +# Set PYTHON_WRAP_ALL_GRID_TYPES to "yes" to specify that the Python module +# should expose (almost) all of the grid types defined in openvdb.h +# Otherwise, only FloatGrid, BoolGrid and Vec3SGrid will be exposed +# (see, e.g., exportIntGrid() in python/pyIntGrid.cc). +# Compiling the Python module with PYTHON_WRAP_ALL_GRID_TYPES set to "yes" +# can be very memory-intensive. +PYTHON_WRAP_ALL_GRID_TYPES := no + +# The Doxygen executable +# (leave blank if Doxygen is unavailable) +DOXYGEN := doxygen + + +# +# Ideally, users shouldn't need to change anything below this line. +# + +SHELL = /bin/bash + +# Turn off implicit rules for speed. +.SUFFIXES: + +# Determine the platform. +ifeq ("$(OS)","Windows_NT") + WINDOWS_NT := 1 +else + UNAME_S := $(shell uname -s) + ifeq ("$(UNAME_S)","Linux") + LINUX := 1 + else + ifeq ("$(UNAME_S)","Darwin") + MBSD := 1 + endif + endif +endif + +ifeq (yes,$(strip $(debug))) + OPTIMIZE := -g +else + OPTIMIZE := -O3 -DNDEBUG +endif + +ifeq (yes,$(strip $(verbose))) + QUIET := + QUIET_TEST := -v +else + QUIET := > /dev/null + QUIET_TEST := $(QUIET) +endif + +has_glfw := no +ifeq (3,$(words $(strip $(GLFW_LIB_DIR) $(GLFW_INCL_DIR) $(GLFW_LIB)))) + has_glfw := yes +endif + +has_log4cplus := no +ifeq (3,$(words $(strip $(LOG4CPLUS_LIB_DIR) $(LOG4CPLUS_INCL_DIR) $(LOG4CPLUS_LIB)))) + has_log4cplus := yes +endif + +has_python := no +ifeq (7,$(words $(strip $(PYTHON_VERSION) $(PYTHON_LIB_DIR) $(PYTHON_INCL_DIR) \ + $(PYCONFIG_INCL_DIR) $(PYTHON_LIB) $(BOOST_PYTHON_LIB_DIR) $(BOOST_PYTHON_LIB)))) + has_python := yes +endif + +INCLDIRS := -I . -I .. -I $(BOOST_INCL_DIR) -I $(HALF_INCL_DIR) -I $(TBB_INCL_DIR) +ifeq (yes,$(has_log4cplus)) + INCLDIRS += -I $(LOG4CPLUS_INCL_DIR) +endif + +CXXFLAGS += -pthread $(OPTIMIZE) $(INCLDIRS) +ifeq (yes,$(has_log4cplus)) + CXXFLAGS += -DOPENVDB_USE_LOG4CPLUS +endif + +LIBS := \ + -ldl -lm -lz \ + -L$(HALF_LIB_DIR) $(HALF_LIB) \ + -L$(TBB_LIB_DIR) $(TBB_LIB) \ +# +LIBS_RPATH := \ + -ldl -lm -lz \ + -Wl,-rpath,$(HALF_LIB_DIR) -L$(HALF_LIB_DIR) $(HALF_LIB) \ + -Wl,-rpath,$(TBB_LIB_DIR) -L$(TBB_LIB_DIR) $(TBB_LIB) \ +# +ifeq (yes,$(has_log4cplus)) + LIBS += -L$(LOG4CPLUS_LIB_DIR) $(LOG4CPLUS_LIB) + LIBS_RPATH += -Wl,-rpath,$(LOG4CPLUS_LIB_DIR) -L$(LOG4CPLUS_LIB_DIR) $(LOG4CPLUS_LIB) +endif +ifneq (,$(strip $(CONCURRENT_MALLOC_LIB))) +ifneq (,$(strip $(CONCURRENT_MALLOC_LIB_DIR))) + LIBS_RPATH += -Wl,-rpath,$(CONCURRENT_MALLOC_LIB_DIR) -L$(CONCURRENT_MALLOC_LIB_DIR) +endif +endif +ifdef LINUX + LIBS += -lrt + LIBS_RPATH += -lrt +endif + +INCLUDE_NAMES := \ + Exceptions.h \ + Grid.h \ + io/Archive.h \ + io/Compression.h \ + io/File.h \ + io/GridDescriptor.h \ + io/Queue.h \ + io/Stream.h \ + math/BBox.h \ + math/Coord.h \ + math/DDA.h \ + math/FiniteDifference.h \ + math/Hermite.h \ + math/LegacyFrustum.h \ + math/Maps.h \ + math/Mat.h \ + math/Mat3.h \ + math/Mat4.h \ + math/Math.h \ + math/Operators.h \ + math/Proximity.h \ + math/QuantizedUnitVec.h \ + math/Quat.h \ + math/Ray.h \ + math/Stats.h \ + math/Stencils.h \ + math/Transform.h\ + math/Tuple.h\ + math/Vec2.h \ + math/Vec3.h \ + math/Vec4.h \ + Metadata.h \ + metadata/Metadata.h \ + metadata/MetaMap.h \ + metadata/StringMetadata.h \ + openvdb.h \ + Platform.h \ + PlatformConfig.h \ + tools/Composite.h \ + tools/Dense.h \ + tools/DenseSparseTools.h \ + tools/Filter.h \ + tools/GridOperators.h \ + tools/GridTransformer.h \ + tools/Interpolation.h \ + tools/LevelSetAdvect.h \ + tools/LevelSetFilter.h \ + tools/LevelSetFracture.h \ + tools/LevelSetMeasure.h \ + tools/LevelSetMorph.h \ + tools/LevelSetRebuild.h \ + tools/LevelSetSphere.h \ + tools/LevelSetTracker.h \ + tools/LevelSetUtil.h \ + tools/MeshToVolume.h \ + tools/Morphology.h \ + tools/ParticlesToLevelSet.h \ + tools/PointAdvect.h \ + tools/PointScatter.h \ + tools/RayIntersector.h \ + tools/RayTracer.h \ + tools/Statistics.h \ + tools/ValueTransformer.h \ + tools/VectorTransformer.h \ + tools/VolumeToMesh.h \ + tools/VolumeToSpheres.h \ + tree/InternalNode.h \ + tree/Iterator.h \ + tree/LeafManager.h \ + tree/LeafNode.h \ + tree/LeafNodeBool.h \ + tree/NodeUnion.h \ + tree/RootNode.h \ + tree/Tree.h \ + tree/TreeIterator.h \ + tree/Util.h \ + tree/ValueAccessor.h \ + Types.h \ + util/Formats.h \ + util/logging.h \ + util/MapsUtil.h \ + util/Name.h \ + util/NodeMasks.h \ + util/NullInterrupter.h \ + util/Util.h \ + version.h \ +# + +SRC_NAMES := \ + Grid.cc \ + io/Archive.cc \ + io/Compression.cc \ + io/File.cc \ + io/GridDescriptor.cc \ + io/Queue.cc \ + io/Stream.cc \ + math/Hermite.cc \ + math/Maps.cc \ + math/Proximity.cc \ + math/QuantizedUnitVec.cc \ + math/Transform.cc \ + metadata/Metadata.cc \ + metadata/MetaMap.cc \ + openvdb.cc \ + Platform.cc \ + util/Formats.cc \ + util/Util.cc \ +# + +UNITTEST_INCLUDE_NAMES := \ + unittest/util.h \ +# + +UNITTEST_SRC_NAMES := \ + unittest/main.cc \ + unittest/TestBBox.cc \ + unittest/TestCoord.cc \ + unittest/TestCpt.cc \ + unittest/TestCurl.cc \ + unittest/TestDense.cc \ + unittest/TestDenseSparseTools.cc \ + unittest/TestDivergence.cc \ + unittest/TestDoubleMetadata.cc \ + unittest/TestExceptions.cc \ + unittest/TestFile.cc \ + unittest/TestFloatMetadata.cc \ + unittest/TestGradient.cc \ + unittest/TestGrid.cc \ + unittest/TestGridBbox.cc \ + unittest/TestGridDescriptor.cc \ + unittest/TestGridIO.cc \ + unittest/TestGridTransformer.cc \ + unittest/TestHermite.cc \ + unittest/TestInit.cc \ + unittest/TestInt32Metadata.cc \ + unittest/TestInt64Metadata.cc \ + unittest/TestInternalOrigin.cc \ + unittest/TestLaplacian.cc \ + unittest/TestLeaf.cc \ + unittest/TestLeafBool.cc \ + unittest/TestLeafIO.cc \ + unittest/TestLeafOrigin.cc \ + unittest/TestLevelSetRayIntersector.cc \ + unittest/TestLevelSetUtil.cc \ + unittest/TestLinearInterp.cc \ + unittest/TestMaps.cc \ + unittest/TestMat4Metadata.cc \ + unittest/TestMath.cc \ + unittest/TestMeanCurvature.cc \ + unittest/TestMeshToVolume.cc \ + unittest/TestMetadata.cc \ + unittest/TestMetadataIO.cc \ + unittest/TestMetaMap.cc \ + unittest/TestName.cc \ + unittest/TestNodeIterator.cc \ + unittest/TestNodeMask.cc \ + unittest/TestParticlesToLevelSet.cc \ + unittest/TestPrePostAPI.cc \ + unittest/TestQuadraticInterp.cc \ + unittest/TestQuantizedUnitVec.cc \ + unittest/TestQuat.cc \ + unittest/TestRay.cc \ + unittest/TestStats.cc \ + unittest/TestStream.cc \ + unittest/TestStringMetadata.cc \ + unittest/TestTools.cc \ + unittest/TestTransform.cc \ + unittest/TestTree.cc \ + unittest/TestTreeCombine.cc \ + unittest/TestTreeGetSetValues.cc \ + unittest/TestTreeIterators.cc \ + unittest/TestTreeVisitor.cc \ + unittest/TestValueAccessor.cc \ + unittest/TestVec2Metadata.cc \ + unittest/TestVec3Metadata.cc \ + unittest/TestVolumeRayIntersector.cc \ + unittest/TestVolumeToMesh.cc \ +# + +DOC_FILES := doc/doc.txt doc/faq.txt doc/changes.txt doc/codingstyle.txt doc/examplecode.txt doc/api_0_98_0.txt doc/math.txt doc/python.txt +DOC_INDEX := doc/html/index.html +DOC_PDF := doc/latex/refman.pdf + +LIBVIEWER_INCLUDE_NAMES := \ + viewer/Camera.h \ + viewer/ClipBox.h \ + viewer/Font.h \ + viewer/RenderModules.h \ + viewer/Viewer.h \ +# +# Used for "install" target only +LIBVIEWER_PUBLIC_INCLUDE_NAMES := \ + viewer/Viewer.h \ +# +LIBVIEWER_SRC_NAMES := \ + viewer/Camera.cc \ + viewer/ClipBox.cc \ + viewer/Font.cc \ + viewer/RenderModules.cc \ + viewer/Viewer.cc \ +# +ifdef MBSD + LIBVIEWER_FLAGS := -framework Cocoa -framework OpenGL -framework IOKit +else + LIBVIEWER_FLAGS := -lGL -lGLU +endif + + +CMD_INCLUDE_NAMES := \ +# + +CMD_SRC_NAMES := \ + cmd/openvdb_print/main.cc \ + cmd/openvdb_render/main.cc \ + cmd/openvdb_view/main.cc \ +# + + +PYTHON_INCLUDE_NAMES := \ + python/pyopenvdb.h \ + python/pyutil.h \ + python/pyAccessor.h \ + python/pyGrid.h \ +# +# Used for "install" target only +PYTHON_PUBLIC_INCLUDE_NAMES := \ + python/pyopenvdb.h \ +# +PYTHON_SRC_NAMES := \ + python/pyFloatGrid.cc \ + python/pyIntGrid.cc \ + python/pyMetadata.cc \ + python/pyOpenVDBModule.cc \ + python/pyTransform.cc \ + python/pyVec3Grid.cc \ +# +PYCXXFLAGS := -fPIC -I python -I $(PYTHON_INCL_DIR) -I $(PYCONFIG_INCL_DIR) +ifneq ($(strip $(NUMPY_INCL_DIR)),) +PYCXXFLAGS += -I $(NUMPY_INCL_DIR) -DPY_OPENVDB_USE_NUMPY +endif +ifneq (no,$(strip $(PYTHON_WRAP_ALL_GRID_TYPES))) +PYCXXFLAGS += -DPY_OPENVDB_WRAP_ALL_GRID_TYPES +endif + + +HEADER_SUBDIRS := $(dir $(INCLUDE_NAMES)) + +ALL_INCLUDE_FILES := \ + $(INCLUDE_NAMES) \ + $(UNITTEST_INCLUDE_NAMES) \ + $(CMD_INCLUDE_NAMES) \ + $(LIBVIEWER_INCLUDE_NAMES) \ + $(PYTHON_INCLUDE_NAMES) \ +# +SRC_FILES := \ + $(SRC_NAMES) \ + $(UNITTEST_SRC_NAMES) \ + $(CMD_SRC_NAMES) \ + $(LIBVIEWER_SRC_NAMES) \ + $(PYTHON_SRC_NAMES) \ +# +ALL_SRC_FILES := $(SRC_FILES) + +OBJ_NAMES := $(SRC_NAMES:.cc=.o) +UNITTEST_OBJ_NAMES := $(UNITTEST_SRC_NAMES:.cc=.o) +LIBVIEWER_OBJ_NAMES := $(LIBVIEWER_SRC_NAMES:.cc=.o) +PYTHON_OBJ_NAMES := $(PYTHON_SRC_NAMES:.cc=.o) + +LIB_MAJOR_VERSION=$(shell grep 'define OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER ' \ + version.h | sed 's/[^0-9]*//g') +LIB_MINOR_VERSION=$(shell grep 'define OPENVDB_LIBRARY_MINOR_VERSION_NUMBER ' \ + version.h | sed 's/[^0-9]*//g') +LIB_PATCH_VERSION=$(shell grep 'define OPENVDB_LIBRARY_PATCH_VERSION_NUMBER ' \ + version.h | sed 's/[^0-9]*//g') + +LIB_VERSION=$(LIB_MAJOR_VERSION).$(LIB_MINOR_VERSION).$(LIB_PATCH_VERSION) +SO_VERSION=$(LIB_MAJOR_VERSION).$(LIB_MINOR_VERSION) + +LIBOPENVDB_NAME=libopenvdb +LIBOPENVDB_STATIC := $(LIBOPENVDB_NAME).a +LIBOPENVDB_SHARED := $(LIBOPENVDB_NAME).so.$(LIB_VERSION) +LIBOPENVDB_SONAME := $(LIBOPENVDB_NAME).so.$(SO_VERSION) +ifndef MBSD +LIBOPENVDB_SONAME_FLAGS := -Wl,-soname,$(LIBOPENVDB_SONAME) +endif + +# TODO: libopenvdb_viewer is currently built into vdb_view and is not installed separately. +LIBVIEWER_NAME=libopenvdb_viewer +LIBVIEWER_STATIC := $(LIBVIEWER_NAME).a +LIBVIEWER_SHARED := $(LIBVIEWER_NAME).so.$(LIB_VERSION) +LIBVIEWER_SONAME := $(LIBVIEWER_NAME).so.$(SO_VERSION) +ifndef MBSD +LIBVIEWER_SONAME_FLAGS := -Wl,-soname,$(LIBVIEWER_SONAME) +endif + +PYTHON_MODULE_NAME=pyopenvdb +PYTHON_MODULE := $(PYTHON_MODULE_NAME).so +PYTHON_SONAME := $(PYTHON_MODULE_NAME).so.$(SO_VERSION) +ifndef MBSD +PYTHON_SONAME_FLAGS := -Wl,-soname,$(PYTHON_SONAME) +endif + +ifeq (no,$(strip $(shared))) + LIBOPENVDB := $(LIBOPENVDB_STATIC) + LIBVIEWER := $(LIBVIEWER_STATIC) +else + LIBOPENVDB := $(LIBOPENVDB_SHARED) + LIBVIEWER := $(LIBVIEWER_SHARED) + LIBOPENVDB_RPATH := -Wl,-rpath,$(INSTALL_DIR)/lib +endif # shared + +DEPEND := dependencies + +# Get the list of dependencies that are newer than the current target, +# but limit the list to at most three entries. +list_deps = $(if $(wordlist 4,5,$(?F)),$(firstword $(?F)) and others,$(wordlist 1,3,$(?F))) + +ALL_PRODUCTS := \ + $(LIBOPENVDB) \ + vdb_test \ + vdb_print \ + vdb_render \ + vdb_view \ + $(DEPEND) \ + $(LIBOPENVDB_NAME).so \ + $(LIBOPENVDB_SONAME) \ + $(PYTHON_MODULE) \ +# + +.SUFFIXES: .o .cc + +.PHONY: all clean depend doc install lib pdfdoc pydoc pytest python test viewerlib + +.cc.o: + @echo "Building $@ because of $(call list_deps)" + $(CXX) -c $(CXXFLAGS) -fPIC -o $@ $< + +all: lib python vdb_print vdb_render vdb_test depend + +$(OBJ_NAMES): %.o: %.cc + @echo "Building $@ because of $(call list_deps)" + $(CXX) -c -DOPENVDB_PRIVATE $(CXXFLAGS) -fPIC -o $@ $< + +ifneq (no,$(strip $(shared))) + +# Build shared library +lib: $(LIBOPENVDB_NAME).so $(LIBOPENVDB_SONAME) + +$(LIBOPENVDB_NAME).so: $(LIBOPENVDB_SHARED) + ln -f -s $< $@ + +$(LIBOPENVDB_SONAME): $(LIBOPENVDB_SHARED) + ln -f -s $< $@ + +$(LIBOPENVDB_SHARED): $(OBJ_NAMES) + @echo "Building $@ because of $(call list_deps)" + $(CXX) $(CXXFLAGS) -shared -o $@ $^ $(LIBS_RPATH) $(LIBOPENVDB_SONAME_FLAGS) + +else + +# Build static library +lib: $(LIBOPENVDB) + +$(LIBOPENVDB_STATIC): $(OBJ_NAMES) + @echo "Building $@ because of $(call list_deps)" + $(AR) cr $@ $^ + +endif # shared + +$(DOC_INDEX): doxygen-config $(INCLUDE_NAMES) $(SRC_NAMES) $(DOC_FILES) + @echo "Generating documentation because of $(call list_deps)" + echo 'OUTPUT_DIRECTORY=./doc' | cat doxygen-config - | $(DOXYGEN) - $(QUIET) + +$(DOC_PDF): doxygen-config $(INCLUDE_NAMES) $(SRC_NAMES) $(DOC_FILES) + @echo "Generating documentation because of $(call list_deps)" + echo -e 'OUTPUT_DIRECTORY=./doc\nGENERATE_LATEX=YES\nGENERATE_HTML=NO' \ + | cat doxygen-config - | $(DOXYGEN) - $(QUIET) \ + && cd ./doc/latex && make refman.pdf $(QUIET) \ + && echo 'Created doc/latex/refman.pdf' + +ifneq ($(strip $(DOXYGEN)),) +doc: $(DOC_INDEX) +pdfdoc: $(DOC_PDF) +else +doc: + @echo "$@"': $$DOXYGEN is undefined' +pdfdoc: + @echo "$@"': $$DOXYGEN is undefined' +endif + +vdb_print: $(LIBOPENVDB) cmd/openvdb_print/main.cc + @echo "Building $@ because of $(call list_deps)" + $(CXX) $(CXXFLAGS) -o $@ cmd/openvdb_print/main.cc -I . \ + $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) \ + $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) + +vdb_render: $(LIBOPENVDB) cmd/openvdb_render/main.cc + @echo "Building $@ because of $(call list_deps)" + $(CXX) $(CXXFLAGS) -o $@ cmd/openvdb_render/main.cc -I . -I $(EXR_INCL_DIR) \ + $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) \ + -Wl,-rpath,$(EXR_LIB_DIR) -L$(EXR_LIB_DIR) $(EXR_LIB) \ + $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) + +ifneq (yes,$(has_glfw)) +vdb_view: + @echo "$@"': GLFW is unavailable' +else +# Create an openvdb_viewer/ symlink to the viewer/ subdirectory, +# to mirror the DWA directory structure. +openvdb_viewer: + ln -f -s viewer openvdb_viewer +$(LIBVIEWER_INCLUDE_NAMES): openvdb_viewer + +$(LIBVIEWER_OBJ_NAMES): $(LIBVIEWER_INCLUDE_NAMES) +$(LIBVIEWER_OBJ_NAMES): %.o: %.cc + @echo "Building $@ because of $(call list_deps)" + $(CXX) -c $(CXXFLAGS) -I . -I $(GLFW_INCL_DIR) -DGL_GLEXT_PROTOTYPES=1 -fPIC -o $@ $< + +vdb_view: $(LIBOPENVDB) $(LIBVIEWER_OBJ_NAMES) cmd/openvdb_view/main.cc + @echo "Building $@ because of $(call list_deps)" + $(CXX) $(CXXFLAGS) -o $@ cmd/openvdb_view/main.cc $(LIBVIEWER_OBJ_NAMES) \ + -I . -Wl,-rpath,$(GLFW_LIB_DIR) -L$(GLFW_LIB_DIR) $(GLFW_LIB) \ + $(LIBVIEWER_FLAGS) $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) \ + $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) +endif + + +# Build the Python module +$(PYTHON_OBJ_NAMES): $(PYTHON_INCLUDE_NAMES) +$(PYTHON_OBJ_NAMES): %.o: %.cc + @echo "Building $@ because of $(call list_deps)" + $(CXX) -c $(CXXFLAGS) -I . $(PYCXXFLAGS) -o $@ $< +$(PYTHON_MODULE): $(LIBOPENVDB) $(PYTHON_OBJ_NAMES) + @echo "Building $@ because of $(call list_deps)" + $(CXX) $(CXXFLAGS) $(PYCXXFLAGS) -shared $(PYTHON_SONAME_FLAGS) \ + -o $@ $(PYTHON_OBJ_NAMES) $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) \ + -Wl,-rpath,$(PYTHON_LIB_DIR) -L$(PYTHON_LIB_DIR) $(PYTHON_LIB) \ + -Wl,-rpath,$(BOOST_PYTHON_LIB_DIR) -L$(BOOST_PYTHON_LIB_DIR) $(BOOST_PYTHON_LIB) \ + $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) + +ifeq (yes,$(has_python)) +ifneq ($(strip $(EPYDOC)),) +pydoc: $(PYTHON_MODULE) $(LIBOPENVDB_SONAME) + @echo "Generating Python module documentation because of $(call list_deps)" + pydocdir=doc/html/python; \ + mkdir -p $${pydocdir}; \ + echo "Created $${pydocdir}"; \ + export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(CURDIR); \ + export PYTHONPATH=${PYTHONPATH}:$(CURDIR); \ + $(EPYDOC) --html -o $${pydocdir} $(PYTHON_MODULE_NAME) $(QUIET) +else +pydoc: + @echo "$@"': $$EPYDOC is undefined' +endif + +pytest: $(PYTHON_MODULE) $(LIBOPENVDB_SONAME) + @echo "Testing Python module $(PYTHON_MODULE)" + export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(CURDIR); \ + export PYTHONPATH=${PYTHONPATH}:$(CURDIR); \ + python$(PYTHON_VERSION) ./python/test/TestOpenVDB.py $(QUIET_TEST) + +python: $(PYTHON_MODULE) +else +python pytest pydoc: + @echo "$@"': Python is unavailable' +endif + + +$(UNITTEST_OBJ_NAMES): %.o: %.cc + @echo "Building $@ because of $(call list_deps)" + $(CXX) -c $(CXXFLAGS) -I $(CPPUNIT_INCL_DIR) -fPIC -o $@ $< + +ifneq ($(strip $(CPPUNIT_INCL_DIR)),) +vdb_test: $(LIBOPENVDB) $(UNITTEST_OBJ_NAMES) + @echo "Building $@ because of $(call list_deps)" + $(CXX) $(CXXFLAGS) -o $@ $(UNITTEST_OBJ_NAMES) \ + $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) \ + -Wl,-rpath,$(CPPUNIT_LIB_DIR) -L$(CPPUNIT_LIB_DIR) $(CPPUNIT_LIB) \ + $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) + +test: vdb_test + @echo "Testing $(LIBOPENVDB_NAME)" + export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(CURDIR); ./vdb_test $(QUIET_TEST) +else +vdb_test: + @echo "$@"': $$(CPPUNIT_INCL_DIR) is undefined' +test: + @echo "$@"': $$(CPPUNIT_INCL_DIR) is undefined' +endif + +install: lib python vdb_print vdb_render vdb_view doc pydoc + mkdir -p $(INSTALL_DIR)/include/openvdb + @echo "Created $(INSTALL_DIR)/include/openvdb" + pushd $(INSTALL_DIR)/include/openvdb > /dev/null; \ + mkdir -p $(HEADER_SUBDIRS); popd > /dev/null + for f in $(INCLUDE_NAMES); \ + do cp -f $$f $(INSTALL_DIR)/include/openvdb/$$f; done + @# + if [ -f $(LIBVIEWER) ]; \ + then \ + mkdir -p $(INSTALL_DIR)/include/openvdb_viewer; \ + echo "Created $(INSTALL_DIR)/include/openvdb_viewer"; \ + cp -f $(LIBVIEWER_PUBLIC_INCLUDE_NAMES) $(INSTALL_DIR)/include/openvdb_viewer/; \ + fi + @echo "Copied header files to $(INSTALL_DIR)/include" + @# + mkdir -p $(INSTALL_DIR)/lib + @echo "Created $(INSTALL_DIR)/lib/" + cp -f $(LIBOPENVDB) $(INSTALL_DIR)/lib + pushd $(INSTALL_DIR)/lib > /dev/null; \ + if [ -f $(LIBOPENVDB_SHARED) ]; then \ + ln -f -s $(LIBOPENVDB_SHARED) $(LIBOPENVDB_NAME).so; \ + ln -f -s $(LIBOPENVDB_SHARED) $(LIBOPENVDB_SONAME); \ + fi; \ + popd > /dev/null + @echo "Copied libopenvdb to $(INSTALL_DIR)/lib/" + @# + if [ -f $(LIBVIEWER) ]; \ + then \ + cp -f $(LIBVIEWER) $(INSTALL_DIR)/lib; \ + pushd $(INSTALL_DIR)/lib > /dev/null; \ + if [ -f $(LIBVIEWER_SHARED) ]; then \ + ln -f -s $(LIBVIEWER_SHARED) $(LIBVIEWER_NAME).so; fi; \ + popd > /dev/null; \ + echo "Copied libopenvdb_viewer to $(INSTALL_DIR)/lib/"; \ + fi + @# + if [ -f $(PYTHON_MODULE) ]; \ + then \ + installdir=$(INSTALL_DIR)/python/include/python$(PYTHON_VERSION); \ + mkdir -p $${installdir}; \ + echo "Created $${installdir}"; \ + cp -f $(PYTHON_PUBLIC_INCLUDE_NAMES) $${installdir}/; \ + echo "Copied Python header files to $${installdir}"; \ + installdir=$(INSTALL_DIR)/python/lib/python$(PYTHON_VERSION); \ + mkdir -p $${installdir}; \ + echo "Created $${installdir}"; \ + cp -f $(PYTHON_MODULE) $${installdir}/; \ + pushd $${installdir} > /dev/null; \ + ln -f -s $(PYTHON_MODULE) $(PYTHON_SONAME); \ + popd > /dev/null; \ + echo "Copied Python module to $${installdir}"; \ + fi + @# + mkdir -p $(INSTALL_DIR)/bin + @echo "Created $(INSTALL_DIR)/bin/" + cp -f vdb_print $(INSTALL_DIR)/bin + @echo "Copied vdb_print to $(INSTALL_DIR)/bin/" + cp -f vdb_render $(INSTALL_DIR)/bin + @echo "Copied vdb_render to $(INSTALL_DIR)/bin/" + if [ -f vdb_view ]; \ + then \ + cp -f vdb_view $(INSTALL_DIR)/bin; \ + echo "Copied vdb_view to $(INSTALL_DIR)/bin/"; \ + fi + @# + if [ -d doc/html ]; \ + then \ + mkdir -p $(INSTALL_DIR)/share/doc/openvdb; \ + echo "Created $(INSTALL_DIR)/share/doc/openvdb/"; \ + cp -r -f doc/html $(INSTALL_DIR)/share/doc/openvdb; \ + echo "Copied documentation to $(INSTALL_DIR)/share/doc/openvdb/"; \ + fi + +# TODO: This accumulates all source file dependencies into a single file +# containing a rule for each *.o file. Consider generating a separate +# dependency file for each *.o file instead. +$(DEPEND): $(ALL_INCLUDE_FILES) $(ALL_SRC_FILES) + @echo "Generating dependencies because of $(call list_deps)" + $(RM) $(DEPEND) + for f in $(SRC_NAMES) $(CMD_SRC_NAMES); \ + do $(CXX) $(CXXFLAGS) -O0 \ + -MM $$f -MT `echo $$f | sed 's%\.[^.]*%.o%'` >> $(DEPEND); \ + done + if [ -d "$(CPPUNIT_INCL_DIR)" ]; \ + then \ + for f in $(UNITTEST_SRC_NAMES); \ + do $(CXX) $(CXXFLAGS) -O0 \ + -MM $$f -MT `echo $$f | sed 's%\.[^.]*%.o%'` \ + -I $(CPPUNIT_INCL_DIR) >> $(DEPEND); \ + done; \ + fi + +depend: $(DEPEND) + +clean: + $(RM) $(OBJ_NAMES) $(ALL_PRODUCTS) $(DEPEND) + $(RM) $(LIBOPENVDB_STATIC) + $(RM) $(LIBOPENVDB_SHARED) + $(RM) $(LIBVIEWER_OBJ_NAMES) + $(RM) $(PYTHON_OBJ_NAMES) + $(RM) $(UNITTEST_OBJ_NAMES) + $(RM) -r ./doc/html ./doc/latex + +ifneq ($(strip $(wildcard $(DEPEND))),) + include $(DEPEND) +endif + +# Copyright (c) 2012-2013 DreamWorks Animation LLC +# All rights reserved. This software is distributed under the +# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/Metadata.h b/openvdb_2_3_0_library/openvdb/Metadata.h new file mode 100755 index 0000000..4668bd1 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/Metadata.h @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_METADATA_HAS_BEEN_INCLUDED +#define OPENVDB_METADATA_HAS_BEEN_INCLUDED + +#include +#include + +#endif // OPENVDB_METADATA_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/Platform.cc b/openvdb_2_3_0_library/openvdb/Platform.cc new file mode 100755 index 0000000..89d73e4 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/Platform.cc @@ -0,0 +1,38 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +// For Windows, we need these includes to ensure all OPENVDB_API +// functions/classes are compiled into the shared library. +#include "openvdb.h" +#include "Exceptions.h" + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/Platform.h b/openvdb_2_3_0_library/openvdb/Platform.h new file mode 100755 index 0000000..d0b7286 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/Platform.h @@ -0,0 +1,202 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +/// +/// @file Platform.h + +#ifndef OPENVDB_PLATFORM_HAS_BEEN_INCLUDED +#define OPENVDB_PLATFORM_HAS_BEEN_INCLUDED + +#include "PlatformConfig.h" + +/// Use OPENVDB_DEPRECATED to mark functions as deprecated. +/// It should be placed right before the signature of the function, +/// e.g., "OPENVDB_DEPRECATED void functionName();". +#ifdef OPENVDB_DEPRECATED +#undef OPENVDB_DEPRECATED +#endif +#ifdef _MSC_VER + #define OPENVDB_DEPRECATED __declspec(deprecated) +#else + #define OPENVDB_DEPRECATED __attribute__ ((deprecated)) +#endif + +/// Macro for determining if GCC version is >= than X.Y +#if defined(__GNUC__) + #define OPENVDB_CHECK_GCC(MAJOR, MINOR) \ + (__GNUC__ > MAJOR || (__GNUC__ == MAJOR && __GNUC_MINOR__ >= MINOR)) +#else + #define OPENVDB_CHECK_GCC(MAJOR, MINOR) 0 +#endif + +/// 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)") +#else + #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +#endif + + +/// Visual C++ does not have constants like M_PI unless this is defined. +/// @note This is needed even though the core library is built with this but +/// hcustom 12.1 doesn't define it. So this is needed for HDK operators. +#ifndef _USE_MATH_DEFINES + #define _USE_MATH_DEFINES +#endif + +/// Visual C++ does not have round +#ifdef _MSC_VER + #include + using boost::math::round; +#endif + +/// Visual C++ uses _copysign() instead of copysign() +#ifdef _MSC_VER + #include + static inline double copysign(double x, double y) { return _copysign(x, y); } +#endif + +/// Visual C++ does not have stdint.h which defines types like uint64_t. +/// So for portability we instead include boost/cstdint.hpp. +#include +using boost::int8_t; +using boost::int16_t; +using boost::int32_t; +using boost::int64_t; +using boost::uint8_t; +using boost::uint16_t; +using boost::uint32_t; +using boost::uint64_t; + +/// Helper macros for defining library symbol visibility +#ifdef OPENVDB_EXPORT +#undef OPENVDB_EXPORT +#endif +#ifdef OPENVDB_IMPORT +#undef OPENVDB_IMPORT +#endif +#ifdef __GNUC__ + #define OPENVDB_EXPORT __attribute__((visibility("default"))) + #define OPENVDB_IMPORT __attribute__((visibility("default"))) +#endif +#ifdef _WIN32 + #ifdef OPENVDB_DLL + #define OPENVDB_EXPORT __declspec(dllexport) + #define OPENVDB_IMPORT __declspec(dllimport) + #else + #define OPENVDB_EXPORT + #define OPENVDB_IMPORT + #endif +#endif + +/// All classes and public free standing functions must be explicitly marked +/// as \_API to be exported. The \_PRIVATE macros are defined when +/// building that particular library. +#ifdef OPENVDB_API +#undef OPENVDB_API +#endif +#ifdef OPENVDB_PRIVATE + #define OPENVDB_API OPENVDB_EXPORT +#else + #define OPENVDB_API OPENVDB_IMPORT +#endif +#ifdef OPENVDB_HOUDINI_API +#undef OPENVDB_HOUDINI_API +#endif +#ifdef OPENVDB_HOUDINI_PRIVATE + #define OPENVDB_HOUDINI_API OPENVDB_EXPORT +#else + #define OPENVDB_HOUDINI_API OPENVDB_IMPORT +#endif + +#endif // OPENVDB_PLATFORM_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/PlatformConfig.h b/openvdb_2_3_0_library/openvdb/PlatformConfig.h new file mode 100755 index 0000000..0a15061 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/PlatformConfig.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +/// +/// @file PlatformConfig.h + +#ifndef OPENVDB_PLATFORMCONFIG_HAS_BEEN_INCLUDED +#define OPENVDB_PLATFORMCONFIG_HAS_BEEN_INCLUDED + +// Windows specific configuration +#ifdef _WIN32 + + // By default, assume we're building OpenVDB as a DLL if we're dynamically + // linking in the CRT, unless OPENVDB_STATICLIB is defined. + #if defined(_DLL) && !defined(OPENVDB_STATICLIB) && !defined(OPENVDB_DLL) + #define OPENVDB_DLL + #endif + + // By default, assume that we're dynamically linking OpenEXR, unless + // OPENVDB_OPENEXR_STATICLIB is defined. + #if !defined(OPENVDB_OPENEXR_STATICLIB) && !defined(OPENEXR_DLL) + #define OPENEXR_DLL + #endif + +#endif // _WIN32 + +#endif // OPENVDB_PLATFORMCONFIG_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/README b/openvdb_2_3_0_library/openvdb/README new file mode 100755 index 0000000..4450746 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/README @@ -0,0 +1,16 @@ +======================================================================== + OpenVDB +======================================================================== + +The OpenVDB library comprises a hierarchical data structure and a suite +of tools for the efficient manipulation of sparse, possibly time-varying, +volumetric data discretized on a three-dimensional grid. + +For instructions on library installation and dependencies see INSTALL + +For documentation of the library and code examples see: +www.openvdb.org/documentation/doxygen + +For more details visit the project's home page: +www.openvdb.org + diff --git a/openvdb_2_3_0_library/openvdb/Types.h b/openvdb_2_3_0_library/openvdb/Types.h new file mode 100755 index 0000000..117c3e5 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/Types.h @@ -0,0 +1,440 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +// One-dimensional scalar types +typedef uint32_t Index32; +typedef uint64_t Index64; +typedef Index32 Index; +typedef int16_t Int16; +typedef int32_t Int32; +typedef int64_t Int64; +typedef Int32 Int; +typedef unsigned char Byte; +typedef double Real; + +// Two-dimensional vector types +typedef math::Vec2 Vec2R; +typedef math::Vec2 Vec2I; +typedef math::Vec2 Vec2f; +typedef math::Vec2 Vec2H; +using math::Vec2i; +using math::Vec2s; +using math::Vec2d; + +// Three-dimensional vector types +typedef math::Vec3 Vec3R; +typedef math::Vec3 Vec3I; +typedef math::Vec3 Vec3f; +typedef math::Vec3 Vec3H; +using math::Vec3i; +using math::Vec3s; +using math::Vec3d; + +using math::Coord; +using math::CoordBBox; +typedef math::BBox BBoxd; + +// Four-dimensional vector types +typedef math::Vec4 Vec4R; +typedef math::Vec4 Vec4I; +typedef math::Vec4 Vec4f; +typedef math::Vec4 Vec4H; +using math::Vec4i; +using math::Vec4s; +using math::Vec4d; + +// Three-dimensional matrix types +typedef math::Mat3 Mat3R; + +// Four-dimensional matrix types +typedef math::Mat4 Mat4R; +typedef math::Mat4 Mat4d; +typedef math::Mat4 Mat4s; + +// Compressed Hermite data +typedef math::Hermite Hermite; + +// Quaternions +typedef math::Quat QuatR; + + +//////////////////////////////////////// + + +template struct VecTraits { + static const bool IsVec = false; + static const int Size = 1; +}; +template struct VecTraits > { + static const bool IsVec = true; + static const int Size = 2; +}; +template struct VecTraits > { + static const bool IsVec = true; + static const int Size = 3; +}; +template struct VecTraits > { + static const bool IsVec = true; + static const int Size = 4; +}; + + +//////////////////////////////////////// + + +/// @brief CanConvertType::value is @c true if a value +/// of type @a ToType can be constructed from a value of type @a FromType. +/// +/// @note @c boost::is_convertible tests for implicit convertibility only. +/// What we want is the equivalent of C++11's @c std::is_constructible, +/// which allows for explicit conversions as well. Unfortunately, not all +/// compilers support @c std::is_constructible yet, so for now, types that +/// can only be converted explicitly have to be indicated with specializations +/// of this template. +template +struct CanConvertType { enum { value = boost::is_convertible::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 }; }; + + +//////////////////////////////////////// + + +// Add new items to the *end* of this list, and update NUM_GRID_CLASSES. +enum GridClass { + GRID_UNKNOWN = 0, + GRID_LEVEL_SET, + GRID_FOG_VOLUME, + GRID_STAGGERED +}; +enum { NUM_GRID_CLASSES = GRID_STAGGERED + 1 }; + +static const Real LEVEL_SET_HALF_WIDTH = 3; + +/// The type of a vector determines how transforms are applied to it: +///
+///
Invariant +///
Does not transform (e.g., tuple, uvw, color) +/// +///
Covariant +///
Apply inverse-transpose transformation: @e w = 0, ignores translation +/// (e.g., gradient/normal) +/// +///
Covariant Normalize +///
Apply inverse-transpose transformation: @e w = 0, ignores translation, +/// vectors are renormalized (e.g., unit normal) +/// +///
Contravariant Relative +///
Apply "regular" transformation: @e w = 0, ignores translation +/// (e.g., displacement, velocity, acceleration) +/// +///
Contravariant Absolute +///
Apply "regular" transformation: @e w = 1, vector translates (e.g., position) +///
+enum VecType { + VEC_INVARIANT = 0, + VEC_COVARIANT, + VEC_COVARIANT_NORMALIZE, + VEC_CONTRAVARIANT_RELATIVE, + VEC_CONTRAVARIANT_ABSOLUTE +}; +enum { NUM_VEC_TYPES = VEC_CONTRAVARIANT_ABSOLUTE + 1 }; + + +/// Specify how grids should be merged during certain (typically multithreaded) operations. +///
+///
MERGE_ACTIVE_STATES +///
The output grid is active wherever any of the input grids is active. +/// +///
MERGE_NODES +///
The output grid's tree has a node wherever any of the input grids' trees +/// has a node, regardless of any active states. +/// +///
MERGE_ACTIVE_STATES_AND_NODES +///
The output grid is active wherever any of the input grids is active, +/// and its tree has a node wherever any of the input grids' trees has a node. +///
+enum MergePolicy { + MERGE_ACTIVE_STATES = 0, + MERGE_NODES, + MERGE_ACTIVE_STATES_AND_NODES +}; + + +//////////////////////////////////////// + + +template const char* typeNameAsString() { return typeid(T).name(); } +template<> inline const char* typeNameAsString() { return "bool"; } +template<> inline const char* typeNameAsString() { return "float"; } +template<> inline const char* typeNameAsString() { return "double"; } +template<> inline const char* typeNameAsString() { return "int32"; } +template<> inline const char* typeNameAsString() { return "uint32"; } +template<> inline const char* typeNameAsString() { return "int64"; } +template<> inline const char* typeNameAsString() { return "Hermite"; } +template<> inline const char* typeNameAsString() { return "vec2i"; } +template<> inline const char* typeNameAsString() { return "vec2s"; } +template<> inline const char* typeNameAsString() { return "vec2d"; } +template<> inline const char* typeNameAsString() { return "vec3i"; } +template<> inline const char* typeNameAsString() { return "vec3s"; } +template<> inline const char* typeNameAsString() { return "vec3d"; } +template<> inline const char* typeNameAsString() { return "string"; } +template<> inline const char* typeNameAsString() { return "mat4s"; } +template<> inline const char* typeNameAsString() { return "mat4d"; } + + +//////////////////////////////////////// + + +/// @brief This struct collects both input and output arguments to "grid combiner" functors +/// used with the tree::TypedGrid::combineExtended() and combine2Extended() methods. +/// 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: + typedef AValueType AValueT; + typedef BValueType BValueT; + + CombineArgs(): + mAValPtr(NULL), mBValPtr(NULL), mResultValPtr(&mResultVal), + mAIsActive(false), mBIsActive(false), mResultIsActive(false) + {} + + /// Use this constructor when the result value is stored externally. + CombineArgs(const AValueType& a, const BValueType& b, AValueType& result, + bool aOn = false, bool bOn = false): + mAValPtr(&a), mBValPtr(&b), mResultValPtr(&result), + mAIsActive(aOn), mBIsActive(bOn) + { updateResultActive(); } + + /// Use this constructor when the result value should be stored in this struct. + CombineArgs(const AValueType& a, const BValueType& b, bool aOn = false, bool bOn = false): + mAValPtr(&a), mBValPtr(&b), mResultValPtr(&mResultVal), + mAIsActive(aOn), mBIsActive(bOn) + { 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; +}; + + +//////////////////////////////////////// + + +/// In copy constructors, members stored as shared pointers can be handled +/// in several ways: +///
+///
CP_NEW +///
Don't copy the member; default construct a new member object instead. +/// +///
CP_SHARE +///
Copy the shared pointer, so that the original and new objects share +/// the same member. +/// +///
CP_COPY +///
Create a deep copy of the member. +///
+enum CopyPolicy { CP_NEW, CP_SHARE, CP_COPY }; + + +// Dummy class that distinguishes shallow copy constructors from +// deep copy constructors +class ShallowCopy {}; +// Dummy class that distinguishes topology copy constructors from +// deep copy constructors +class TopologyCopy {}; + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + + +#if defined(__ICC) + +// Use these defines to bracket a region of code that has safe static accesses. +// Keep the region as small as possible. +#define OPENVDB_START_THREADSAFE_STATIC_REFERENCE __pragma(warning(disable:1710)) +#define OPENVDB_FINISH_THREADSAFE_STATIC_REFERENCE __pragma(warning(default:1710)) +#define OPENVDB_START_THREADSAFE_STATIC_WRITE __pragma(warning(disable:1711)) +#define OPENVDB_FINISH_THREADSAFE_STATIC_WRITE __pragma(warning(default:1711)) +#define OPENVDB_START_THREADSAFE_STATIC_ADDRESS __pragma(warning(disable:1712)) +#define OPENVDB_FINISH_THREADSAFE_STATIC_ADDRESS __pragma(warning(default:1712)) + +// Use these defines to bracket a region of code that has unsafe static accesses. +// Keep the region as small as possible. +#define OPENVDB_START_NON_THREADSAFE_STATIC_REFERENCE __pragma(warning(disable:1710)) +#define OPENVDB_FINISH_NON_THREADSAFE_STATIC_REFERENCE __pragma(warning(default:1710)) +#define OPENVDB_START_NON_THREADSAFE_STATIC_WRITE __pragma(warning(disable:1711)) +#define OPENVDB_FINISH_NON_THREADSAFE_STATIC_WRITE __pragma(warning(default:1711)) +#define OPENVDB_START_NON_THREADSAFE_STATIC_ADDRESS __pragma(warning(disable:1712)) +#define OPENVDB_FINISH_NON_THREADSAFE_STATIC_ADDRESS __pragma(warning(default:1712)) + +// Simpler version for one-line cases +#define OPENVDB_THREADSAFE_STATIC_REFERENCE(CODE) \ + __pragma(warning(disable:1710)); CODE; __pragma(warning(default:1710)) +#define OPENVDB_THREADSAFE_STATIC_WRITE(CODE) \ + __pragma(warning(disable:1711)); CODE; __pragma(warning(default:1711)) +#define OPENVDB_THREADSAFE_STATIC_ADDRESS(CODE) \ + __pragma(warning(disable:1712)); CODE; __pragma(warning(default:1712)) + +#else // GCC does not support these compiler warnings + +#define OPENVDB_START_THREADSAFE_STATIC_REFERENCE +#define OPENVDB_FINISH_THREADSAFE_STATIC_REFERENCE +#define OPENVDB_START_THREADSAFE_STATIC_WRITE +#define OPENVDB_FINISH_THREADSAFE_STATIC_WRITE +#define OPENVDB_START_THREADSAFE_STATIC_ADDRESS +#define OPENVDB_FINISH_THREADSAFE_STATIC_ADDRESS + +#define OPENVDB_START_NON_THREADSAFE_STATIC_REFERENCE +#define OPENVDB_FINISH_NON_THREADSAFE_STATIC_REFERENCE +#define OPENVDB_START_NON_THREADSAFE_STATIC_WRITE +#define OPENVDB_FINISH_NON_THREADSAFE_STATIC_WRITE +#define OPENVDB_START_NON_THREADSAFE_STATIC_ADDRESS +#define OPENVDB_FINISH_NON_THREADSAFE_STATIC_ADDRESS + +#define OPENVDB_THREADSAFE_STATIC_REFERENCE(CODE) CODE +#define OPENVDB_THREADSAFE_STATIC_WRITE(CODE) CODE +#define OPENVDB_THREADSAFE_STATIC_ADDRESS(CODE) CODE + +#endif // defined(__ICC) + +#endif // OPENVDB_TYPES_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/cmd/openvdb_print/main.cc b/openvdb_2_3_0_library/openvdb/cmd/openvdb_print/main.cc new file mode 100755 index 0000000..95c35cb --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/cmd/openvdb_print/main.cc @@ -0,0 +1,358 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#ifdef DWA_OPENVDB +#include +#include +#endif + + +namespace { + +typedef std::vector StringVec; + +const char* INDENT = " "; +const char* gProgName = ""; + + +void +usage(int exitStatus = EXIT_FAILURE) +{ + std::cerr << +"Usage: " << gProgName << " in.vdb [in.vdb ...] [options]\n" << +"Which: prints information about OpenVDB grids\n" << +"Options:\n" << +" -l, -stats long printout, including grid statistics\n" << +" -m, -metadata print per-file and per-grid metadata\n"; + exit(exitStatus); +} + + +std::string +sizeAsString(openvdb::Index64 n, const std::string& units) +{ + std::ostringstream ostr; + ostr << std::setprecision(3); + if (n < 1000) { + ostr << n; + } else if (n < 1000000) { + ostr << (n / 1.0e3) << "K"; + } else if (n < 1000000000) { + ostr << (n / 1.0e6) << "M"; + } else { + ostr << (n / 1.0e9) << "G"; + } + ostr << units; + return ostr.str(); +} + + +std::string +bytesAsString(openvdb::Index64 n) +{ + std::ostringstream ostr; + ostr << std::setprecision(3); + if (n >> 30) { + ostr << (n / double(uint64_t(1) << 30)) << "GB"; + } else if (n >> 20) { + ostr << (n / double(uint64_t(1) << 20)) << "MB"; + } else if (n >> 10) { + ostr << (n / double(uint64_t(1) << 10)) << "KB"; + } else { + ostr << n << "B"; + } + return ostr.str(); +} + + +std::string +coordAsString(const openvdb::Coord ijk, const std::string& sep) +{ + std::ostringstream ostr; + ostr << ijk[0] << sep << ijk[1] << sep << ijk[2]; + return ostr.str(); +} + + +/// Return a string representation of the given metadata key, value pairs +std::string +metadataAsString( + const openvdb::MetaMap::ConstMetaIterator& begin, + const openvdb::MetaMap::ConstMetaIterator& end, + const std::string& indent = "") +{ + std::ostringstream ostr; + char sep[2] = { 0, 0 }; + for (openvdb::MetaMap::ConstMetaIterator it = begin; it != end; ++it) { + ostr << sep << indent << it->first; + if (it->second) { + const std::string value = it->second->str(); + if (!value.empty()) ostr << ": " << value; + } + sep[0] = '\n'; + } + return ostr.str(); +} + + +std::string +bkgdValueAsString(const openvdb::GridBase::ConstPtr& grid) +{ + std::ostringstream ostr; + if (grid) { + const openvdb::TreeBase& tree = grid->baseTree(); + ostr << "background: "; + openvdb::Metadata::Ptr background = tree.getBackgroundValue(); + if (background) ostr << background->str(); + } + return ostr.str(); +} + + +/// Print detailed information about the given VDB files. +/// If @a metadata is true, include file-level metadata key, value pairs. +void +printLongListing(const StringVec& filenames) +{ + bool oneFile = (filenames.size() == 1), firstFile = true; + + for (size_t i = 0, N = filenames.size(); i < N; ++i, firstFile = false) { + openvdb::io::File file(filenames[i]); + std::string version; + openvdb::GridPtrVecPtr grids; + openvdb::MetaMap::Ptr meta; + try { + file.open(); + grids = file.getGrids(); + meta = file.getMetadata(); + version = file.version(); + file.close(); + } catch (openvdb::Exception& e) { + OPENVDB_LOG_ERROR(e.what() << " (" << filenames[i] << ")"); + } + if (!grids) continue; + + if (!oneFile) { + if (!firstFile) { + std::cout << "\n" << std::string(40, '-') << "\n\n"; + } + std::cout << filenames[i] << "\n\n"; + } + + // Print file-level metadata. + std::cout << "VDB version: " << version << "\n"; + if (meta) { + std::string str = metadataAsString(meta->beginMeta(), meta->endMeta()); + if (!str.empty()) std::cout << str << "\n"; + } + std::cout << "\n"; + + // For each grid in the file... + bool firstGrid = true; + for (openvdb::GridPtrVec::const_iterator it = grids->begin(); it != grids->end(); ++it) { + if (openvdb::GridBase::ConstPtr grid = *it) { + if (!firstGrid) std::cout << "\n\n"; + std::cout << "Name: " << grid->getName() << std::endl; + grid->print(std::cout, /*verboseLevel=*/3); + firstGrid = false; + } + } + } +} + + +/// Print condensed information about the given VDB files. +/// If @a metadata is true, include file- and grid-level metadata. +void +printShortListing(const StringVec& filenames, bool metadata) +{ + bool oneFile = (filenames.size() == 1), firstFile = true; + + for (size_t i = 0, N = filenames.size(); i < N; ++i, firstFile = false) { + const std::string + indent(oneFile ? "": INDENT), + indent2(indent + INDENT); + + if (!oneFile) { + if (metadata && !firstFile) std::cout << "\n"; + std::cout << filenames[i] << ":\n"; + } + + openvdb::GridPtrVecPtr grids; + openvdb::MetaMap::Ptr meta; + + openvdb::io::File file(filenames[i]); + try { + file.open(); + grids = file.getGrids(); + meta = file.getMetadata(); + file.close(); + } catch (openvdb::Exception& e) { + OPENVDB_LOG_ERROR(e.what() << " (" << filenames[i] << ")"); + } + if (!grids) continue; + + if (metadata) { + // Print file-level metadata. + std::string str = metadataAsString(meta->beginMeta(), meta->endMeta(), indent); + if (!str.empty()) std::cout << str << "\n"; + } + + // For each grid in the file... + for (openvdb::GridPtrVec::const_iterator it = grids->begin(); it != grids->end(); ++it) { + const openvdb::GridBase::ConstPtr grid = *it; + if (!grid) continue; + + // Print the grid name and its voxel value datatype. + std::cout << indent << std::left << std::setw(11) << grid->getName() + << " " << std::right << std::setw(6) << grid->valueType(); + + // Print the grid's bounding box and dimensions. + openvdb::CoordBBox bbox = grid->evalActiveVoxelBoundingBox(); + std::string + boxStr = coordAsString(bbox.min()," ") + " " + coordAsString(bbox.max()," "), + dimStr = coordAsString(bbox.extents(), "x"); + boxStr += std::string(std::max(1, + 40 - boxStr.size() - dimStr.size()), ' ') + dimStr; + std::cout << " " << std::left << std::setw(40) << boxStr; + + // Print the number of active voxels. + std::cout << " " << std::right << std::setw(8) + << sizeAsString(grid->activeVoxelCount(), "Vox"); + + // Print the grid's in-core size, in bytes. + std::cout << " " << std::right << std::setw(6) << bytesAsString(grid->memUsage()); + + std::cout << std::endl; + + // Print grid-specific metadata. + if (metadata) { + // Print background value. + std::string str = bkgdValueAsString(grid); + if (!str.empty()) { + std::cout << indent2 << str << "\n"; + } + // Print local and world transforms. + grid->transform().print(std::cout, indent2); + // Print custom metadata. + str = metadataAsString(grid->beginMeta(), grid->endMeta(), indent2); + if (!str.empty()) std::cout << str << "\n"; + std::cout << std::flush; + } + } + } +} + +} // unnamed namespace + + +int +main(int argc, char *argv[]) +{ +#ifdef DWA_OPENVDB + USAGETRACK_report_basic_tool_usage(argc, argv, /*duration=*/0); + logging_base::configure(argc, argv); +#endif + + OPENVDB_START_THREADSAFE_STATIC_WRITE + gProgName = argv[0]; + if (const char* ptr = ::strrchr(gProgName, '/')) gProgName = ptr + 1; + OPENVDB_FINISH_THREADSAFE_STATIC_WRITE + + int exitStatus = EXIT_SUCCESS; + + if (argc == 1) usage(); + + bool stats = false, metadata = false; + StringVec filenames; + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg[0] == '-') { + if (arg == "-m" || arg == "-metadata") { + metadata = true; + } else if (arg == "-l" || arg == "-stats") { + stats = true; + } else if (arg == "-h" || arg == "-help" || arg == "--help") { + usage(EXIT_SUCCESS); + } else { + std::cerr << gProgName << ": \"" << arg << "\" is not a valid option\n"; + usage(); + } + } else if (!arg.empty()) { + filenames.push_back(arg); + } + } + if (filenames.empty()) { + std::cerr << gProgName << ": expected one or more OpenVDB files\n"; + usage(); + } + + try { + openvdb::initialize(); + + /// @todo Remove the following at some point: + openvdb::Grid::Type>::registerGrid(); + openvdb::Grid::Type>::registerGrid(); + openvdb::Grid::Type>::registerGrid(); + openvdb::Grid::Type>::registerGrid(); + openvdb::Grid::Type>::registerGrid(); + openvdb::Grid::Type>::registerGrid(); + openvdb::Grid::Type>::registerGrid(); + openvdb::Grid::Type>::registerGrid(); + openvdb::Grid::Type>::registerGrid(); + openvdb::Grid::Type>::registerGrid(); + openvdb::Grid::Type>::registerGrid(); + + if (stats) { + printLongListing(filenames); + } else { + printShortListing(filenames, metadata); + } + } + catch (const std::exception& e) { + OPENVDB_LOG_FATAL(e.what()); + exitStatus = EXIT_FAILURE; + } + catch (...) { + OPENVDB_LOG_FATAL("Exception caught (unexpected type)"); + std::unexpected(); + } + + return exitStatus; +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/cmd/openvdb_render/main.cc b/openvdb_2_3_0_library/openvdb/cmd/openvdb_render/main.cc new file mode 100755 index 0000000..67eff7a --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/cmd/openvdb_render/main.cc @@ -0,0 +1,678 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file main.cc +/// +/// @brief Simple ray tracer for OpenVDB volumes +/// +/// @note This is intended mainly as an example of how to ray-trace +/// OpenVDB volumes. It is not a production-quality renderer. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DWA_OPENVDB +#include +#include +#endif + + +namespace { + +const char* gProgName = ""; + + +struct RenderOpts +{ + std::string shader; + std::string camera; + float aperture, focal, frame, znear, zfar; + double isovalue; + openvdb::Vec3d rotate; + openvdb::Vec3d translate; + openvdb::Vec3d target; + openvdb::Vec3d up; + bool lookat; + size_t samples; + openvdb::Vec3d absorb; + openvdb::Vec3d color; + openvdb::Vec3d light; + openvdb::Vec3d scatter; + double cutoff, gain; + openvdb::Vec2d step; + size_t width, height; + std::string compression; + int threads; + bool verbose; + + RenderOpts(): + shader("diffuse"), + camera("perspective"), + aperture(41.2136), + focal(50.0), + frame(1.0), + znear(1.0e-3), + zfar(std::numeric_limits::max()), + isovalue(0.0), + rotate(0.0), + translate(0.0), + target(0.0), + up(0.0, 1.0, 0.0), + lookat(false), + samples(1), + absorb(0.1), + color(0.7), + light(0.3, 0.3, 0.0), + scatter(1.5), + cutoff(0.005), + gain(0.2), + step(1.0, 3.0), + width(1920), + height(1080), + compression("zip"), + threads(0), + verbose(false) + {} + + std::string validate() const + { + if (shader != "diffuse" && shader != "matte" && shader != "normal" && shader != "position"){ + return "expected diffuse, matte, normal or position shader, got \"" + shader + "\""; + } + if (!boost::starts_with(camera, "ortho") && !boost::starts_with(camera, "persp")) { + return "expected perspective or orthographic camera, got \"" + camera + "\""; + } + if (compression != "none" && compression != "rle" && compression != "zip") { + return "expected none, rle or zip compression, got \"" + compression + "\""; + } + if (width < 1 || height < 1) { + std::ostringstream ostr; + ostr << "expected width > 0 and height > 0, got " << width << "x" << height; + return ostr.str(); + } + return ""; + } + + std::ostream& put(std::ostream& os) const + { + os << " -absorb " << absorb[0] << "," << absorb[1] << "," << absorb[2] + << " -aperture " << aperture + << " -camera " << camera + << " -color " << color[0] << "," << color[1] << "," << color[2] + << " -compression " << compression + << " -cpus " << threads + << " -cutoff " << cutoff + << " -far " << zfar + << " -focal " << focal + << " -frame " << frame + << " -gain " << gain + << " -isovalue " << isovalue + << " -light " << light[0] << "," << light[1] << "," << light[2]; + if (lookat) os << " -lookat " << target[0] << "," << target[1] << "," << target[2]; + os << " -near " << znear + << " -res " << width << "x" << height; + if (!lookat) os << " -rotate " << rotate[0] << "," << rotate[1] << "," << rotate[2]; + os << " -shader " << shader + << " -samples " << samples + << " -scatter " << scatter[0] << "," << scatter[1] << "," << scatter[2] + << " -shadowstep " << step[1] + << " -step " << step[0] + << " -translate " << translate[0] << "," << translate[1] << "," << translate[2]; + if (lookat) os << " -up " << up[0] << "," << up[1] << "," << up[2]; + if (verbose) os << " -v"; + return os; + } +}; + +std::ostream& operator<<(std::ostream& os, const RenderOpts& opts) { return opts.put(os); } + + +void +usage(int exitStatus = EXIT_FAILURE) +{ + RenderOpts opts; // default options + const float fov = openvdb::tools::PerspectiveCamera::focalLengthToFieldOfView( + opts.focal, opts.aperture); + + std::ostringstream ostr; + ostr << std::setprecision(3) << +"Usage: " << gProgName << " in.vdb out.{exr,ppm} [options]\n" << +"Which: ray-traces OpenVDB volumes\n" << +"Options:\n" << +" -aperture F perspective camera aperture in mm (default: " << opts.aperture << ")\n" << +" -camera S camera type; either \"persp[ective]\" or \"ortho[graphic]\"\n" << +" (default: " << opts.camera << ")\n" << +" -compression S EXR compression scheme; either \"none\" (uncompressed),\n" << +" \"rle\" or \"zip\" (default: " << opts.compression << ")\n" << +" -cpus N number of rendering threads, or 1 to disable threading,\n" << +" or 0 to use all available CPUs (default: " << opts.threads << ")\n" << +" -far F camera far plane depth (default: " << opts.zfar << ")\n" << +" -focal F perspective camera focal length in mm (default: " << opts.focal << ")\n" << +" -fov F perspective camera field of view in degrees\n" << +" (default: " << fov << ")\n" << +" -frame F ortho camera frame width in world units (default: " << + opts.frame << ")\n" << +" -lookat X,Y,Z rotate the camera to point to (X, Y, Z)\n" << +" -name S name of the grid to be rendered (default: render\n" << +" the first floating-point grid found in in.vdb)\n" << +" -near F camera near plane depth (default: " << opts.znear << ")\n" << +" -res WxH image dimensions in pixels (default: " << + opts.width << "x" << opts.height << ")\n" << +" -r X,Y,Z \n" << +" -rotate X,Y,Z camera rotation in degrees\n" << +" (default: look at the center of the grid)\n" << +" -t X,Y,Z \n" << +" -translate X,Y,Z camera translation\n" << +" -up X,Y,Z vector that should point up after rotation with -lookat\n" << +" (default: " << opts.up << ")\n" << +"\n" << +" -v verbose (print timing and diagnostics)\n" << +" -h, -help print this usage message and exit\n" << +"\n" << +"Level set options:\n" << +" -isovalue F isovalue in world units for level set ray intersection\n" << +" (default: " << opts.isovalue << ")\n" << +" -samples N number of samples (rays) per pixel (default: " << opts.samples << ")\n" << +" -shader S shader name; either \"diffuse\", \"matte\", \"normal\"\n" << +" or \"position\" (default: " << opts.shader << ")\n" << +"\n" << +"Dense volume options:\n" << +" -absorb R,G,B absorption coefficients (default: " << opts.absorb << ")\n" << +" -color R,G,B light source color (default: " << opts.color << ")\n" << +" -cutoff F density and transmittance cutoff value (default: " << opts.cutoff << ")\n" << +" -gain F amount of scatter along the shadow ray (default: " << opts.gain << ")\n" << +" -light X,Y,Z light source direction (default: " << opts.light << ")\n" << +" -scatter R,G,B scattering coefficients (default: " << opts.scatter << ")\n" << +" -shadowstep F step size in voxels for integration along the shadow ray\n" << +" (default: " << opts.step[1] << ")\n" << +" -step F step size in voxels for integration along the primary ray\n" << +" (default: " << opts.step[0] << ")\n" << +"\n" << +"Examples:\n" << +" " << gProgName << " crawler.vdb crawler.exr -shader diffuse -res 1920x1080 \\\n" << +" -focal 35 -samples 4 -translate 0,210.5,400 -compression rle -v\n" << +"\n" << +" " << gProgName << " bunny_cloud.vdb bunny_cloud.exr -res 1920x1080 \\\n" << +" -translate 0,0,110 -absorb 0.4,0.2,0.1 -gain 0.2 -v\n" << +"\n" << +"This is not (and is not intended to be) a production-quality renderer.\n"; + + std::cerr << ostr.str(); + exit(exitStatus); +} + + +void +saveEXR(const std::string& fname, const openvdb::tools::Film& film, const RenderOpts& opts) +{ + typedef openvdb::tools::Film::RGBA RGBA; + + std::string filename = fname; + if (!boost::iends_with(filename, ".exr")) filename += ".exr"; + + if (opts.verbose) { + std::cout << gProgName << ": writing " << filename << "..." << std::endl; + } + + const tbb::tick_count start = tbb::tick_count::now(); + + int threads = (opts.threads == 0 ? 8 : opts.threads); + Imf::setGlobalThreadCount(threads); + + Imf::Header header(film.width(), film.height()); + if (opts.compression == "none") { + header.compression() = Imf::NO_COMPRESSION; + } else if (opts.compression == "rle") { + header.compression() = Imf::RLE_COMPRESSION; + } else if (opts.compression == "zip") { + header.compression() = Imf::ZIP_COMPRESSION; + } else { + OPENVDB_THROW(openvdb::ValueError, + "expected none, rle or zip compression, got \"" << opts.compression << "\""); + } + header.channels().insert("R", Imf::Channel(Imf::FLOAT)); + header.channels().insert("G", Imf::Channel(Imf::FLOAT)); + header.channels().insert("B", Imf::Channel(Imf::FLOAT)); + header.channels().insert("A", Imf::Channel(Imf::FLOAT)); + + const size_t pixelBytes = sizeof(RGBA), rowBytes = pixelBytes * film.width(); + RGBA& pixel0 = const_cast(film.pixels())[0]; + Imf::FrameBuffer framebuffer; + framebuffer.insert("R", + Imf::Slice(Imf::FLOAT, reinterpret_cast(&pixel0.r), pixelBytes, rowBytes)); + framebuffer.insert("G", + Imf::Slice(Imf::FLOAT, reinterpret_cast(&pixel0.g), pixelBytes, rowBytes)); + framebuffer.insert("B", + Imf::Slice(Imf::FLOAT, reinterpret_cast(&pixel0.b), pixelBytes, rowBytes)); + framebuffer.insert("A", + Imf::Slice(Imf::FLOAT, reinterpret_cast(&pixel0.a), pixelBytes, rowBytes)); + + Imf::OutputFile imgFile(filename.c_str(), header); + imgFile.setFrameBuffer(framebuffer); + imgFile.writePixels(film.height()); + + if (opts.verbose) { + std::ostringstream ostr; + ostr << gProgName << ": ...completed in " << std::setprecision(3) + << (tbb::tick_count::now() - start).seconds() << " sec"; + std::cout << ostr.str() << std::endl; + } +} + + +template +void +render(const GridType& grid, const std::string& imgFilename, const RenderOpts& opts) +{ + using namespace openvdb; + + const bool isLevelSet = (grid.getGridClass() == GRID_LEVEL_SET); + + tools::Film film(opts.width, opts.height); + + boost::scoped_ptr camera; + if (boost::starts_with(opts.camera, "persp")) { + camera.reset(new tools::PerspectiveCamera(film, opts.rotate, opts.translate, + opts.focal, opts.aperture, opts.znear, opts.zfar)); + } else if (boost::starts_with(opts.camera, "ortho")) { + camera.reset(new tools::OrthographicCamera(film, opts.rotate, opts.translate, + opts.frame, opts.znear, opts.zfar)); + } else { + OPENVDB_THROW(ValueError, + "expected perspective or orthographic camera, got \"" << opts.camera << "\""); + } + if (opts.lookat) camera->lookAt(opts.target, opts.up); + + boost::scoped_ptr shader(new tools::DiffuseShader); + if (opts.shader == "matte") { + shader.reset(new tools::MatteShader); + } else if (opts.shader == "normal") { + shader.reset(new tools::NormalShader); + } else if (opts.shader == "position") { + const CoordBBox b = grid.evalActiveVoxelBoundingBox(); + math::BBox bbox(b.min().asVec3d(), b.max().asVec3d()); + shader.reset(new tools::PositionShader(bbox.applyMap(*(grid.transform().baseMap())))); + } + + if (opts.verbose) { + std::cout << gProgName << ": ray-tracing"; + const std::string gridName = grid.getName(); + if (!gridName.empty()) std::cout << " " << gridName; + std::cout << "..." << std::endl; + } + const tbb::tick_count start = tbb::tick_count::now(); + + if (isLevelSet) { + tools::LevelSetRayIntersector intersector(grid, opts.isovalue); + tools::rayTrace(grid, intersector, *shader, *camera, opts.samples, + /*seed=*/0, (opts.threads != 1)); + } else { + typedef tools::VolumeRayIntersector IntersectorType; + IntersectorType intersector(grid); + + tools::VolumeRender renderer(intersector, *camera); + renderer.setLightDir(opts.light[0], opts.light[1], opts.light[2]); + renderer.setLightColor(opts.color[0], opts.color[1], opts.color[2]); + renderer.setPrimaryStep(opts.step[0]); + renderer.setShadowStep(opts.step[1]); + renderer.setScattering(opts.scatter[0], opts.scatter[1], opts.scatter[2]); + renderer.setAbsorption(opts.absorb[0], opts.absorb[1], opts.absorb[2]); + renderer.setLightGain(opts.gain); + renderer.setCutOff(opts.cutoff); + + renderer.render(opts.threads != 1); + } + + if (opts.verbose) { + std::ostringstream ostr; + ostr << gProgName << ": ...completed in " << std::setprecision(3) + << (tbb::tick_count::now() - start).seconds() << " sec"; + std::cout << ostr.str() << std::endl; + } + + if (boost::iends_with(imgFilename, ".ppm")) { + // Save as PPM (fast, but large file size). + std::string filename = imgFilename; + filename.erase(filename.size() - 4); // strip .ppm extension + film.savePPM(filename); + } else if (boost::iends_with(imgFilename, ".exr")) { + // Save as EXR (slow, but small file size). + saveEXR(imgFilename, film, opts); + } else { + OPENVDB_THROW(ValueError, "unsupported image file format (" + imgFilename + ")"); + } +} + + +void +strToSize(const std::string& s, size_t& x, size_t& y) +{ + std::vector elems; + boost::split(elems, s, boost::algorithm::is_any_of(",x")); + const size_t numElems = elems.size(); + if (numElems > 0) x = size_t(std::max(0, atoi(elems[0].c_str()))); + if (numElems > 1) y = size_t(std::max(0, atoi(elems[1].c_str()))); +} + + +std::vector +strToVec(const std::string& s) +{ + std::vector result; + std::vector elems; + boost::split(elems, s, boost::algorithm::is_any_of(",")); + for (size_t i = 0, N = elems.size(); i < N; ++i) { + result.push_back(atof(elems[i].c_str())); + } + return result; +} + + +openvdb::Vec3d +strToVec3d(const std::string& s) +{ + openvdb::Vec3d result(0.0, 0.0, 0.0); + std::vector elems = strToVec(s); + if (!elems.empty()) { + result = openvdb::Vec3d(elems[0]); + for (size_t i = 1, N = std::min(3, elems.size()); i < N; ++i) { + result[i] = elems[i]; + } + } + return result; +} + + +struct OptParse +{ + int argc; + char** argv; + + OptParse(int argc_, char* argv_[]): argc(argc_), argv(argv_) {} + + bool check(int idx, const std::string& name, int numArgs = 1) const + { + if (argv[idx] == name) { + if (idx + numArgs >= argc) { + std::cerr << gProgName << ": option " << name << " requires " + << numArgs << " argument" << (numArgs == 1 ? "" : "s") << "\n"; + usage(); + } + return true; + } + return false; + } +}; + +} // unnamed namespace + + +int +main(int argc, char *argv[]) +{ +#ifdef DWA_OPENVDB + USAGETRACK_report_basic_tool_usage(argc, argv, /*duration=*/0); + logging_base::configure(argc, argv); +#endif + + OPENVDB_START_THREADSAFE_STATIC_WRITE + gProgName = argv[0]; + if (const char* ptr = ::strrchr(gProgName, '/')) gProgName = ptr + 1; + OPENVDB_FINISH_THREADSAFE_STATIC_WRITE + + int retcode = EXIT_SUCCESS; + + if (argc == 1) usage(); + + std::string vdbFilename, imgFilename, gridName; + RenderOpts opts; + + bool hasFocal = false, hasFov = false, hasRotate = false, hasLookAt = false; + float fov = 0.0; + + OptParse parser(argc, argv); + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg[0] == '-') { + if (parser.check(i, "-absorb")) { + ++i; + opts.absorb = strToVec3d(argv[i]); + } else if (parser.check(i, "-aperture")) { + ++i; + opts.aperture = atof(argv[i]); + } else if (parser.check(i, "-camera")) { + ++i; + opts.camera = argv[i]; + } else if (parser.check(i, "-color")) { + ++i; + opts.color = strToVec3d(argv[i]); + } else if (parser.check(i, "-compression")) { + ++i; + opts.compression = argv[i]; + } else if (parser.check(i, "-cpus")) { + ++i; + opts.threads = std::max(0, atoi(argv[i])); + } else if (parser.check(i, "-cutoff")) { + ++i; + opts.cutoff = atof(argv[i]); + } else if (parser.check(i, "-isovalue")) { + ++i; + opts.isovalue = atof(argv[i]); + } else if (parser.check(i, "-far")) { + ++i; + opts.zfar = atof(argv[i]); + } else if (parser.check(i, "-focal")) { + ++i; + opts.focal = atof(argv[i]); + hasFocal = true; + } else if (parser.check(i, "-fov")) { + ++i; + fov = atof(argv[i]); + hasFov = true; + } else if (parser.check(i, "-frame")) { + ++i; + opts.frame = atof(argv[i]); + } else if (parser.check(i, "-gain")) { + ++i; + opts.gain = atof(argv[i]); + } else if (parser.check(i, "-light")) { + ++i; + opts.light = strToVec3d(argv[i]); + } else if (parser.check(i, "-lookat")) { + ++i; + opts.lookat = true; + opts.target = strToVec3d(argv[i]); + hasLookAt = true; + } else if (parser.check(i, "-name")) { + ++i; + gridName = argv[i]; + } else if (parser.check(i, "-near")) { + ++i; + opts.znear = atof(argv[i]); + } else if (parser.check(i, "-r") || parser.check(i, "-rotate")) { + ++i; + opts.rotate = strToVec3d(argv[i]); + hasRotate = true; + } else if (parser.check(i, "-res")) { + ++i; + strToSize(argv[i], opts.width, opts.height); + } else if (parser.check(i, "-scatter")) { + ++i; + opts.scatter = strToVec3d(argv[i]); + } else if (parser.check(i, "-shader")) { + ++i; + opts.shader = argv[i]; + } else if (parser.check(i, "-shadowstep")) { + ++i; + opts.step[1] = atof(argv[i]); + } else if (parser.check(i, "-samples")) { + ++i; + opts.samples = size_t(std::max(0, atoi(argv[i]))); + } else if (parser.check(i, "-step")) { + ++i; + opts.step[0] = atof(argv[i]); + } else if (parser.check(i, "-t") || parser.check(i, "-translate")) { + ++i; + opts.translate = strToVec3d(argv[i]); + } else if (parser.check(i, "-up")) { + ++i; + opts.up = strToVec3d(argv[i]); + } else if (arg == "-v") { + opts.verbose = true; + } else if (arg == "-h" || arg == "-help" || arg == "--help") { + usage(EXIT_SUCCESS); + } else { + std::cerr << gProgName << ": \"" << arg << "\" is not a valid option\n"; + usage(); + } + } else if (vdbFilename.empty()) { + vdbFilename = arg; + } else if (imgFilename.empty()) { + imgFilename = arg; + } else { + usage(); + } + } + if (vdbFilename.empty() || imgFilename.empty()) { + usage(); + } + if (hasFov) { + if (hasFocal) { + OPENVDB_LOG_FATAL("specify -focal or -fov, but not both"); + usage(); + } + opts.focal = + openvdb::tools::PerspectiveCamera::fieldOfViewToFocalLength(fov, opts.aperture); + } + if (hasLookAt && hasRotate) { + OPENVDB_LOG_FATAL("specify -lookat or -r[otate], but not both"); + usage(); + } + { + const std::string err = opts.validate(); + if (!err.empty()) { + OPENVDB_LOG_FATAL(err); + usage(); + } + } + + try { + tbb::task_scheduler_init schedulerInit( + (opts.threads == 0) ? tbb::task_scheduler_init::automatic : opts.threads); + + openvdb::initialize(); + + const tbb::tick_count start = tbb::tick_count::now(); + if (opts.verbose) { + std::cout << gProgName << ": reading "; + if (!gridName.empty()) std::cout << gridName << " from "; + std::cout << vdbFilename << "..." << std::endl; + } + + openvdb::FloatGrid::Ptr grid; + { + openvdb::io::File file(vdbFilename); + + file.open(); + if (!gridName.empty()) { + grid = openvdb::gridPtrCast(file.readGrid(gridName)); + if (!grid) { + OPENVDB_THROW(openvdb::ValueError, + gridName + " is not a scalar, floating-point grid"); + } + } else { + // If no grid was specified by name, retrieve the first float grid from the file. + openvdb::GridPtrVecPtr grids = file.readAllGridMetadata(); + for (size_t i = 0; i < grids->size(); ++i) { + grid = openvdb::gridPtrCast(grids->at(i)); + if (grid) { + gridName = grid->getName(); + file.close(); + file.open(); + grid = openvdb::gridPtrCast(file.readGrid(gridName)); + break; + } + } + if (!grid) { + OPENVDB_THROW(openvdb::ValueError, + "no scalar, floating-point grids in file " + vdbFilename); + } + } + } + + if (opts.verbose) { + std::ostringstream ostr; + ostr << gProgName << ": ...completed in " << std::setprecision(3) + << (tbb::tick_count::now() - start).seconds() << " sec"; + std::cout << ostr.str() << std::endl; + } + + if (grid) { + if (!hasLookAt && !hasRotate) { + // If the user specified neither the camera rotation nor a target + // to look at, orient the camera to point to the center of the grid. + opts.target = grid->evalActiveVoxelBoundingBox().getCenter(); + opts.target = grid->constTransform().indexToWorld(opts.target); + opts.lookat = true; + } + + if (opts.verbose) std::cout << opts << std::endl; + + render(*grid, imgFilename, opts); + } + } catch (std::exception& e) { + OPENVDB_LOG_FATAL(e.what()); + retcode = EXIT_FAILURE; + } catch (...) { + OPENVDB_LOG_FATAL("Exception caught (unexpected type)"); + std::unexpected(); + } + + return retcode; +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/cmd/openvdb_view/main.cc b/openvdb_2_3_0_library/openvdb/cmd/openvdb_view/main.cc new file mode 100755 index 0000000..5ef93dd --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/cmd/openvdb_view/main.cc @@ -0,0 +1,162 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#ifdef DWA_OPENVDB +#include +#include +#endif + + +void +usage(const char* progName, int status) +{ + (status == EXIT_SUCCESS ? std::cout : std::cerr) << +"Usage: " << progName << " file.vdb [file.vdb ...] [options]\n" << +"Which: displays OpenVDB grids\n" << +"Options:\n" << +" -i print grid info\n" << +" -d print debugging info\n" << +"Controls:\n" << +" Esc exit\n" << +" -> (Right) show next grid\n" << +" <- (Left) show previous grid\n" << +" 1 toggle tree topology view on/off\n" << +" 2 toggle surface view on/off\n" << +" 3 toggle data view on/off\n" << +" G (\"geometry\") look at center of geometry\n" << +" H (\"home\") look at origin\n" << +" I toggle on-screen grid info on/off\n" << +" left mouse tumble\n" << +" right mouse pan\n" << +" mouse wheel zoom\n" << +"\n" << +" X + wheel move right cut plane\n" << +" Shift + X + wheel move left cut plane\n" << +" Y + wheel move top cut plane\n" << +" Shift + Y + wheel move bottom cut plane\n" << +" Z + wheel move front cut plane\n" << +" Shift + Z + wheel move back cut plane\n" << +" Ctrl + X + wheel move both X cut planes\n" << +" Ctrl + Y + wheel move both Y cut planes\n" << +" Ctrl + Z + wheel move both Z cut planes\n"; + exit(status); +} + + +//////////////////////////////////////// + + +int +main(int argc, char *argv[]) +{ +#ifdef DWA_OPENVDB + USAGETRACK_report_basic_tool_usage(argc, argv, /*duration=*/0); + logging_base::configure(argc, argv); +#endif + + const char* progName = argv[0]; + if (const char* ptr = ::strrchr(progName, '/')) progName = ptr + 1; + + int status = EXIT_SUCCESS; + + try { + openvdb::initialize(); + + bool printInfo = false, printDebugInfo = false; + + // Parse the command line. + std::vector filenames; + for (int n = 1; n < argc; ++n) { + std::string str(argv[n]); + if (str[0] != '-') { + filenames.push_back(str); + } else if (str == "-i") { + printInfo = true; + } else if (str == "-d") { + printDebugInfo = true; + } else if (str == "-h" || str == "--help") { + usage(progName, EXIT_SUCCESS); + } else { + usage(progName, EXIT_FAILURE); + } + } + + openvdb_viewer::Viewer viewer = openvdb_viewer::init(progName, printDebugInfo); + + const size_t numFiles = filenames.size(); + if (numFiles == 0) usage(progName, EXIT_FAILURE); + + openvdb::GridCPtrVec allGrids; + + // Load VDB files. + std::string indent(numFiles == 1 ? "" : " "); + for (size_t n = 0; n < numFiles; ++n) { + openvdb::io::File file(filenames[n]); + file.open(); + + openvdb::GridPtrVecPtr grids = file.getGrids(); + if (grids->empty()) { + OPENVDB_LOG_WARN(filenames[n] << " is empty"); + continue; + } + allGrids.insert(allGrids.end(), grids->begin(), grids->end()); + + if (printInfo) { + if (numFiles > 1) std::cout << filenames[n] << ":\n"; + for (size_t i = 0; i < grids->size(); ++i) { + const std::string name = (*grids)[i]->getName(); + openvdb::Coord dim = (*grids)[i]->evalActiveVoxelDim(); + std::cout << indent << (name.empty() ? "" : name) + << " (" << dim[0] << " x " << dim[1] << " x " << dim[2] + << " voxels)" << std::endl; + } + } + } + + viewer.view(allGrids); + + } catch (const char* s) { + OPENVDB_LOG_ERROR(progName << ": " << s); + status = EXIT_FAILURE; + } catch (std::exception& e) { + OPENVDB_LOG_ERROR(progName << ": " << e.what()); + status = EXIT_FAILURE; + } + return status; +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/doc/api_0_98_0.txt b/openvdb_2_3_0_library/openvdb/doc/api_0_98_0.txt new file mode 100755 index 0000000..de5b29f --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/doc/api_0_98_0.txt @@ -0,0 +1,196 @@ +/** + +@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. +
+ +*/ diff --git a/openvdb_2_3_0_library/openvdb/doc/changes.txt b/openvdb_2_3_0_library/openvdb/doc/changes.txt new file mode 100755 index 0000000..3bb3a76 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/doc/changes.txt @@ -0,0 +1,1187 @@ +/** + +@page changes Release Notes + + +@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. +- @vdblink::tree::Tree::pruneLevelSet() Tree::pruneLevelSet@endlink now + replaces each pruned node with a tile having the inside or outside + background value, instead of arbitrarily selecting one of the node’s + tile or voxel values. +- When a grid is saved to a file with + @vdblink::Grid::saveFloatAsHalf() saveFloatAsHalf@endlink set to @c true, + the grid’s background value is now also quantized to 16 bits. + (Not quantizing the background value caused a mismatch with the values + of background tiles.) +- As with @vdblink::tools::foreach() tools::foreach@endlink, it is now + possible to specify whether functors passed to + @vdblink::tools::transformValues() tools::transformValues@endlink + should be shared across threads. +- @vdblink::tree::LeafManager tree::LeafManager@endlink can now be + instantiated with a @const tree, although buffer swapping with @const trees + is disabled. +- Added a @vdblink::Grid::signedFloodFill() Grid::signedFloodFill@endlink + overload that allows one to specify inside and outside values. +- Fixed a bug in @vdblink::Grid::setBackground() Grid::setBackground@endlink + so that now only the values of inactive voxels change. +- Fixed @vdblink::Grid::topologyUnion() Grid::topologyUnion@endlink so that + it actually unions tree topology, instead of just the active states + of tiles and voxels. The previous behavior broke multithreaded code + that relied on input and output grids having compatible tree topology. +- @vdblink::math::Transform math::Transform@endlink now includes an + @vdblink::math::Transform::isIdentity() isIdentity@endlink predicate + and methods to @vdblink::math::Transform::preMult(const Mat4d&) pre-@endlink + and @vdblink::math::Transform::postMult(const Mat4d&) postmultiply@endlink + by a matrix. +- Modified the @link NodeMasks.h node mask@endlink classes to permit + octree-like tree configurations (i.e., with a branching factor of two) + and to use 64-bit operations instead of 32-bit operations. +- Implemented a new, more efficient + @vdblink::math::closestPointOnTriangleToPoint() closest point + on triangle@endlink algorithm. +- Implemented a new vertex normal scheme in the volume to mesh + converter, and resolved some overlapping polygon issues. +- The volume to mesh converter now meshes not just active voxels + but also active tiles. +- Fixed a bug in the mesh to volume converter that caused unsigned + distance field conversion to produce empty grids. +- Fixed a bug in the level set fracture tool whereby the cutter overlap + toggle was ignored. +- Fixed an infinite loop bug in @c vdb_view. +- Updated @c vdb_view to use the faster and less memory-intensive + OpenVDB volume to mesh converter instead of marching cubes, + and rewrote the shader to be OpenGL 3.2 and GLSL 1.2 compatible. +- Given multiple input files or a file containing multiple grids, + @c vdb_view now displays one grid at a time. The left and right + arrow keys cycle between grids. +- The To Polygons SOP now has an option to associate the input grid’s + name with each output polygon. + + +@htmlonly @endhtmlonly +@par +Version 1.0.0 - March 14 2013 +- @vdblink::tools::levelSetRebuild() tools::levelSetRebuild@endlink + now throws an exception when given a non-scalar or non-floating-point grid. +- The tools in tools/GridOperators.h are now interruptible, as is + the Analysis SOP. +- Added a + @vdblink::tree::LeafManager::LeafRange::Iterator leaf node iterator@endlink + and a TBB-compatible + @vdblink::tree::LeafManager::LeafRange range class@endlink + to the LeafManager. +- Modified the @vdblink::tools::VolumeToMesh VolumeToMesh@endlink tool + to handle surface topology issues around fracture seam lines. +- Modified the Makefile to allow @c vdb_view to compile on OS X systems + (provided that GLFW is available). +- Fixed a bug in the Create SOP that resulted in "invalid parameter name" + warnings. +- The Combine SOP now optionally resamples the A grid into the B grid’s + index space (or vice-versa) if the A and B transforms differ. +- The Vector Split and Vector Merge SOPs now skip inactive voxels + by default, but they can optionally be made to include inactive voxels, + as they did before. +- The @vdblink::tools::LevelSetFracture LevelSetFracture@endlink tool now + supports custom rotations for each cutter instance, and the Fracture SOP + now uses quaternions to generate uniformly-distributed random rotations. + + +@htmlonly @endhtmlonly +@par +Version 0.104.0 - February 15 2013 +- Added a @vdblink::tools::levelSetRebuild() tool@endlink and a SOP + to rebuild a level set from any scalar volume. +- @c .vdb files are now saved using a mask-based compression scheme + that is an order of magnitude faster than Zip and produces comparable + file sizes for level set and fog volume grids. (Zip compression + is still enabled by default for other classes of grids). +- The @vdblink::tools::Filter Filter@endlink and + @vdblink::tools::LevelSetFilter LevelSetFilter@endlink tools now + include a Gaussian filter, and mean (box) filtering is now 10-50x faster. +- The isosurface @vdblink::tools::VolumeToMesh meshing tool@endlink + is now more robust (to level sets with one voxel wide narrow bands, + for example). +- Mesh to volume conversion is on average 1.5x faster and up to 5.5x + faster for high-resolution meshes where the polygon/voxel size ratio + is small. +- Added @vdblink::createLevelSet() createLevelSet@endlink and + @vdblink::createLevelSetSphere() createLevelSetSphere@endlink + factory functions for level set grids. +- @vdblink::tree::ValueAccessor tree::ValueAccessor@endlink is now faster + for trees of height 2, 3 and 4 (the latter is the default), and it now + allows one to specify, via a template argument, the number of node levels + to be cached, which can also improve performance in special cases. +- Added a toggle to @vdblink::tools::foreach() tools::foreach@endlink + to specify whether or not the functor should be shared across threads. +- Added @vdblink::Mat4SMetadata Mat4s@endlink and + @vdblink::Mat4DMetadata Mat4d@endlink metadata types. +- Added explicit pre- and postmultiplication methods to the @c Transform, + @c Map and @c Mat4 classes and deprecated the old accumulation methods. +- Modified @vdblink::math::NonlinearFrustumMap NonlinearFrustumMap@endlink + to be more compatible with Houdini’s frustum transform. +- Fixed a @vdblink::tools::GridTransformer GridTransformer@endlink bug + that caused it to translate the output grid incorrectly in some cases. +- Fixed a bug in the tree-level + @vdblink::tree::LeafIteratorBase LeafIterator@endlink that resulted in + intermittent crashes in + @vdblink::tools::dilateVoxels() tools::dilateVoxels@endlink. +- The @c Hermite data type and Hermite grids are no longer supported. +- Added tools/GridOperators.h, which includes new, cleaner implementations + of the @vdblink::tools::cpt() closest point transform@endlink, + @vdblink::tools::curl() curl@endlink, + @vdblink::tools::divergence() divergence@endlink, + @vdblink::tools::gradient() gradient@endlink, + @vdblink::tools::laplacian() Laplacian@endlink, + @vdblink::tools::magnitude() magnitude@endlink, + @vdblink::tools::meanCurvature() mean curvature@endlink and + @vdblink::tools::normalize() normalize@endlink tools. +- Interrupt support has been improved in several tools, including + @vdblink::tools::ParticlesToLevelSet tools::ParticlesToLevelSet@endlink. +- Simplified the API of the @vdblink::math::BaseStencil Stencil@endlink class + and added an @vdblink::math::BaseStencil::intersects() intersects@endlink + method to test for intersection with a specified isovalue. +- Renamed @c voxelDimensions to @c voxelSize in transform classes + and elsewhere. +- Deprecated @c houdini_utils::ParmFactory::setChoiceList in favor of + @c houdini_utils::ParmFactory::setChoiceListItems, which requires + a list of token, label string pairs. +- Made various changes for Visual C++ compatibility. + [Contributed by SESI] +- Fixed a bug in @c houdini_utils::getNodeChain() that caused the + Offset Level Set, Smooth Level Set and Renormalize Level Set SOPs + to ignore frame changes. + [Contributed by SESI] +- The From Particles SOP now provides the option to write into + an existing grid. +- Added a SOP to edit grid metadata. +- The Fracture SOP now supports multiple cutter objects. +- Added a To Polygons SOP that complements the Fracture SOP and allows + for elimination of seam lines, generation of correct vertex normals + and grouping of polygons when surfacing fracture fragments, using + the original level set or mesh as a reference. + + +@htmlonly @endhtmlonly +@par +Version 0.103.1 - January 15 2013 +- @vdblink::tree::ValueAccessor tree::ValueAccessor@endlink read operations + are now faster for four-level trees. + (Preliminary benchmark tests suggest a 30-40% improvement.) +- For vector-valued grids, @vdblink::tools::compMin() tools::compMin@endlink + and @vdblink::tools::compMax() tools::compMax@endlink now compare + vector magnitudes instead of individual components. +- Migrated grid sampling code to a new file, Interpolation.h, + and deprecated old files and classes. +- Added a level-set @vdblink::tools::LevelSetFracture fracture tool@endlink + and a Fracture SOP. +- Added @vdblink::tools::sdfInteriorMask() tools::sdfInteriorMask@endlink, + which creates a mask of the interior region of a level set grid. +- Fixed a bug in the mesh to volume converter that produced unexpected + nonzero values for voxels at the intersection of two polygons, + and another bug that produced narrow-band widths that didn’t respect + the background value when the half-band width was less than three voxels. +- @c houdini_utils::ParmFactory can now correctly generate ramp multi-parms. +- Made various changes for Visual C++ compatibility. + [Contributed by SESI] +- The Convert SOP can now convert between signed distance fields and + fog volumes and from volumes to meshes. + [Contributed by SESI] +- For level sets, the From Mesh and From Particles SOPs now match + the reference grid’s narrow-band width. +- The Scatter SOP can now optionally scatter points in the interior + of a level set. + + +@htmlonly @endhtmlonly +@par +Version 0.103.0 - December 21 2012 +- The mesh to volume converter is now 60% faster at generating + level sets with wide bands, and the From Mesh SOP is now interruptible. +- Fixed a threading bug in the recently-added + @vdblink::tools::compReplace() compReplace@endlink tool + that caused it to produce incorrect output. +- Added a @vdblink::tree::Tree::probeConstLeaf() probeConstLeaf@endlink + method to the @vdblink::tree::Tree::probeConstLeaf() Tree@endlink, + @vdblink::tree::ValueAccessor::probeConstLeaf() ValueAccessor@endlink + and @vdblink::tree::RootNode::probeConstLeaf() node@endlink classes. +- The Houdini VDB primitive doesn’t create a @c name attribute + unnecessarily (i.e., if its grid’s name is empty), but it now + correctly allows the name to be changed to the empty string. +- Fixed a crash in the Vector Merge SOP when fewer than three grids + were merged. +- The From Particles SOP now features a "maximum half-width" parameter + to help avoid runaway computations. + + +@htmlonly @endhtmlonly +@par +Version 0.102.0 - December 13 2012 +- Added @vdblink::tools::compReplace() tools::compReplace@endlink, + which copies the active values of one grid into another, and added + a "Replace A With Active B" mode to the Combine SOP. +- @vdblink::Grid::signedFloodFill() Grid::signedFloodFill@endlink + no longer enters an infinite loop when filling an empty grid. +- Fixed a bug in the particle to level set converter that sometimes + produced level sets with holes, and fixed a bug in the SOP that + could result in random output. +- Fixed an issue in the frustum preview feature of the Create SOP + whereby rendering very large frustums could cause high CPU usage. +- Added streamline support to the constrained advection scheme + in the Advect Points SOP. +- Added an Advect Level Set SOP. + + +@htmlonly @endhtmlonly +@par +Version 0.101.1 - December 11 2012 (DWA internal release) +- Partially reverted the Houdini VDB primitive’s grid accessor methods + to their pre-0.98.0 behavior. A primitive’s grid can once again + be accessed by shared pointer, but now also by reference. + Accessor methods for grid metadata have also been added, and the + primitive now ensures that metadata and transforms are never shared. +- Fixed an intermittent crash in the From Particles SOP. + + +@htmlonly @endhtmlonly +@par +Version 0.101.0 - December 6 2012 (DWA internal release) +- Partially reverted the @vdblink::Grid Grid@endlink’s + @vdblink::Grid::tree() tree@endlink and + @vdblink::Grid::transform() transform@endlink accessor methods + to their pre-0.98.0 behavior, eliminating copy-on-write but + preserving their return-by-reference semantics. These methods + are now supplemented with a suite of + @vdblink::Grid::treePtr() shared@endlink + @vdblink::Grid::baseTreePtr() pointer@endlink + @vdblink::Grid::transformPtr() accessors@endlink. +- Restructured the @vdblink::tools::MeshToVolume + mesh to volume converter@endlink for a 40% speedup + and to be more robust to non-manifold geometry, to better preserve + sharp features, to support arbitrary tree configurations and + to respect narrow-band limits. +- Added a @c getNodeBoundingBox method to + @vdblink::tree::RootNode::getNodeBoundingBox() RootNode@endlink, + @vdblink::tree::InternalNode::getNodeBoundingBox() InternalNode@endlink + and @vdblink::tree::LeafNode::getNodeBoundingBox() LeafNode@endlink + that returns the index space spanned by a node. +- Made various changes for Visual C++ compatibility. + [Contributed by SESI] +- Renamed the Reshape Level Set SOP to Offset Level Set. +- Fixed a crash in the Convert SOP and added support for conversion + of empty grids. + + +@htmlonly @endhtmlonly +@par +Version 0.100.0 - November 30 2012 (DWA internal release) +- Greatly improved the performance of the level set to fog volume + @vdblink::tools::sdfToFogVolume() converter@endlink. +- Improved the performance of the + @vdblink::tools::Filter::median() median filter@endlink + and of level set @vdblink::tools::csgUnion() CSG@endlink operations. +- Reintroduced + @vdblink::tree::Tree::pruneLevelSet() Tree::pruneLevelSet@endlink, + a specialized @vdblink::tree::Tree::pruneInactive() pruneInactive@endlink + for level-set grids. +- Added utilities to the @c houdini_utils library to facilitate the + collection of a chain of adjacent nodes of a particular type + so that they can be cooked in a single step. (For example, + adjacent @c xform SOPs could be collapsed by composing their + transformation matrices into a single matrix.) +- Added pruning and flood-filling options to the Convert SOP. +- Reimplemented the Filter SOP, omitting level-set-specific filters + and adding node chaining (to reduce memory usage when applying + several filters in sequence). +- Added a toggle to the Read SOP to read grid metadata and + transforms only. +- Changed the attribute transfer scheme on the From Mesh and + From Particles SOPs to allow for custom grid names and + vector type metadata. + + +@htmlonly @endhtmlonly +@par +Version 0.99.0 - November 21 2012 +- Added @vdblink::Grid Grid@endlink methods that return non-const + tree and transform references without triggering deep copies, + as well as @c const methods that return @c const shared pointers. +- Added @c Grid methods to @vdblink::Grid::addStatsMetadata populate@endlink + a grid’s metadata with statistics like the active voxel count, and to + @vdblink::Grid::getStatsMetadata retrieve@endlink that metadata. + By default, statistics are now computed and added to grids + whenever they are written to .vdb files. +- Added @vdblink::io::File::readGridMetadata io::File::readGridMetadata@endlink + and @vdblink::io::File::readAllGridMetadata + io::File::readAllGridMetadata@endlink methods to read just the + grid metadata and transforms from a .vdb file. +- Fixed numerical precision issues in the + @vdblink::tools::csgUnion csgUnion@endlink, + @vdblink::tools::csgIntersection csgIntersection@endlink + and @vdblink::tools::csgDifference csgDifference@endlink + tools, and added toggles to optionally disable postprocess pruning. +- Fixed an issue in @c vdb_view with the ordering of GL vertex buffer calls. + [Contributed by Bill Katz] +- Fixed an intermittent crash in the + @vdblink::tools::ParticlesToLevelSet ParticlesToLevelSet@endlink tool, + as well as a race condition that could cause data corruption. +- The @c ParticlesToLevelSet tool and From Particles SOP can now transfer + arbitrary point attribute values from the input particles to output voxels. +- Fixed a bug in the Convert SOP whereby the names of primitives + were lost during conversion, and another bug that resulted in + arithmetic errors when converting empty grids. +- Fixed a bug in the Combine SOP that caused the Operation selection + to be lost. + + +@htmlonly @endhtmlonly +@par +Version 0.98.0 - November 16 2012 +- @vdblink::tree::Tree Tree@endlink and + @vdblink::math::Transform Transform@endlink objects (and + @vdblink::Grid Grid@endlink objects in the context of Houdini SOPs) + are now passed and accessed primarily by reference rather than by + shared pointer. See @subpage api_0_98_0 "Porting to OpenVDB 0.98.0" + for details about this important API change. + [Contributed by SESI] +- Reimplemented @vdblink::math::CoordBBox CoordBBox@endlink to address several + off-by-one bugs related to bounding box dimensions. +- Fixed an off-by-one bug in @vdblink::Grid::evalActiveVoxelBoundingBox() + evalActiveVoxelBoundingBox@endlink. +- Introduced the @vdblink::tree::LeafManager LeafManager@endlink class, + which will eventually replace the @c LeafArray class. @c LeafManager supports + dynamic buffers stored as a structure of arrays (SOA), unlike @c LeafArray, + which supports only static buffers stored as an array of structures (AOS). +- Improved the performance of the + @vdblink::tools::LevelSetFilter LevelSetFilter@endlink and + @vdblink::tools::LevelSetTracker LevelSetTracker@endlink tools by rewriting + them to use the new @vdblink::tree::LeafManager LeafManager@endlink class. +- Added @vdblink::tree::Tree::setValueOnly() Tree::setValueOnly@endlink and + @vdblink::tree::ValueAccessor::setValueOnly() + ValueAccessor::setValueOnly@endlink methods, which change the value of + a voxel without changing its active state, and + @vdblink::tree::Tree::probeLeaf() Tree::probeLeaf@endlink and + @vdblink::tree::ValueAccessor::probeLeaf() ValueAccessor::probeLeaf@endlink + methods that return the leaf node that contains a given voxel (unless + the voxel is represented by a tile). +- Added a @vdblink::tools::LevelSetAdvection LevelSetAdvection@endlink tool + that propagates and tracks narrow-band level sets. +- Introduced a new @vdblink::tools::GridSampler GridSampler@endlink class + that supports world-space (or index-space) sampling of grid values. +- Changed the interpretation of the + @vdblink::math::NonlinearFrustumMap NonlinearFrustumMap@endlink’s + @em taper parameter to be the ratio of the near and far plane depths. +- Added a @c ParmFactory::setChoiceList() overload that accepts + (@em token, @em label) string pairs, and a @c setDefault() overload that + accepts an STL string. +- Fixed a crash in the Combine SOP in Copy B mode. +- Split the Level Set Filter SOP into three separate SOPs, + Level Set Smooth, Level Set Reshape and Level Set Renormalize. + When two or more of these nodes are connected in sequence, they interact + to reduce memory usage: the last node in the sequence performs + all of the operations in one step. +- The Advect Points SOP can now output polyline streamlines + that trace the paths of the points. +- Added an option to the Analysis SOP to specify names for output grids. +- Added camera-derived frustum transform support to the Create SOP. + + +@htmlonly @endhtmlonly +@par +Version 0.97.0 - October 18 2012 +- Added a narrow-band @vdblink::tools::LevelSetTracker level set + interface tracking tool@endlink (up to fifth-order in space but currently + only first-order in time, with higher temporal orders to be added soon). +- Added a @vdblink::tools::LevelSetFilter level set filter tool@endlink + to perform unrestricted surface smoothing (e.g., Laplacian flow), + filtering (e.g., mean value) and morphological operations (e.g., + morphological opening). +- Added adaptivity to the @vdblink::tools::VolumeToMesh + level set meshing tool@endlink for faster mesh extraction with fewer + polygons, without postprocessing. +- Added a @vdblink::tree::ValueAccessor::touchLeaf() + ValueAccessor::touchLeaf@endlink method that creates (if necessary) + and returns the leaf node containing a given voxel. It can be used + to preallocate leaf nodes over which to run parallel algorithms. +- Fixed a bug in @vdblink::Grid::merge() Grid::merge@endlink whereby + active tiles were sometimes lost. +- Added @vdblink::tree::LeafManager LeafManager@endlink, which is similar + to @c LeafArray but supports a dynamic buffer count and allocates buffers + more efficiently. Useful for temporal integration (e.g., for level set + propagation and interface tracking), @c LeafManager is meant to replace + @c LeafArray, which will be deprecated in the next release. +- Added a @vdblink::tree::LeafNode::fill() LeafNode::fill@endlink method + to efficiently populate leaf nodes with constant values. +- Added a @vdblink::tree::Tree::visitActiveBBox() Tree::visitActiveBBox@endlink + method that applies a functor to the bounding boxes of all active tiles + and leaf nodes and that can be used to improve the performance of + ray intersection tests, rendering of bounding boxes, etc. +- Added a @vdblink::tree::Tree::voxelizeActiveTiles() + Tree::voxelizeActiveTiles@endlink method to densify active tiles. + While convenient and fast, this can produce large dense grids, so use + it with caution. +- Repackaged @c Tree::pruneLevelSet() as a + @vdblink::tree::Tree::pruneOp() Tree::pruneOp()@endlink-compatible + functor. @vdblink::tree::LevelSetPrune LevelSetPrune@endlink is a + specialized @vdblink::tree::Tree::pruneInactive() pruneInactive@endlink + for level-set grids and is used in interface tracking. +- Added a @vdblink::GridBase::pruneGrid() GridBase::pruneGrid@endlink method. +- Added a @vdblink::Grid::hasUniformVoxels() Grid:hasUniformVoxels@endlink + method. +- Renamed @c tools::dilate to + @vdblink::tools::dilateVoxels() dilateVoxels@endlink and improved its + performance. The new name reflects the fact that the current + implementation ignores active tiles. +- Added a @vdblink::tools::resampleToMatch() tools::resampleToMatch@endlink + function that resamples an input grid into an output grid with a + different transform such that, after resampling, the input and output grids + coincide, but the output grid’s transform is preserved. +- Significantly improved the performance of depth-bounded value + iterators (@vdblink::tree::Tree::ValueOnIter ValueOnIter@endlink, + @vdblink::tree::Tree::ValueAllIter ValueAllIter@endlink, etc.) + when the depth bound excludes leaf nodes. +- Exposed the value buffers inside leaf nodes with + @vdblink::tree::LeafNode::buffer() LeafNode::buffer@endlink. + This allows for very fast access (const and non-const) to voxel + values using linear array offsets instead of @ijk coordinates. +- In openvdb_houdini/UT_VDBTools.h, added operators for use with + @c processTypedGrid that resample grids in several different ways. +- Added a policy mechanism to @c houdini_utils::OpFactory that allows for + customization of operator names, icons, and Help URLs. +- Renamed many of the Houdini SOPs to make the names more consistent. +- Added an Advect Points SOP. +- Added a Level Set Filter SOP that allows for unrestricted surface + deformations, unlike the older Filter SOP, which restricts surface + motion to the initial narrow band. +- Added staggered vector sampling to the Sample Points SOP. +- Added a minimum radius threshold to the particle voxelization tool + and SOP. +- Merged the Composite and CSG SOPs into a single Combine SOP. +- Added a tool and a SOP to efficiently generate narrow-band level set + representations of spheres. +- In the Visualize SOP, improved the performance of tree topology + generation, which is now enabled by default. + + +@htmlonly @endhtmlonly +@par +Version 0.96.0 - September 24 2012 +- Fixed a memory corruption bug in the mesh voxelizer tool. +- Temporarily removed the optional clipping feature from the level set mesher. +- Added "Staggered Vector Field" to the list of grid classes in the Create SOP. + + +@htmlonly @endhtmlonly +@par +Version 0.95.0 - September 20 2012 +- Added a quad @vdblink::tools::VolumeToMesh meshing@endlink tool for + higher-quality level set meshing and updated the Visualizer SOP + to use it. +- Fixed a precision error in the @vdblink::tools::MeshToVolume + mesh voxelizer@endlink and improved the quality of inside/outside + voxel classification. Output grids are now also + @vdblink::Grid::setGridClass() classified@endlink as either level sets + or fog volumes. +- Modified the @vdblink::tools::GridResampler GridResampler@endlink + to use the signed flood fill optimization only on grids that are + tagged as level sets. +- Added a @vdblink::math::Quat quaternion@endlink class to the + math library and a method to return the + @vdblink::math::Mat3::trace trace@endlink of a @c Mat3. +- Fixed a bug in the + @vdblink::tree::ValueAccessor::ValueAccessor(const ValueAccessor&) + ValueAccessor@endlink copy constructor that caused the copy to reference + the original. +- Fixed a bug in @vdblink::tree::RootNode::setActiveState() + RootNode::setActiveState@endlink that caused a crash + when marking a (virtual) background voxel as inactive. +- Added a @c Tree::pruneLevelSet method that is similar to but faster than + @vdblink::tree::Tree::pruneInactive() pruneInactive@endlink + for level set grids. +- Added fast leaf node voxel access + @vdblink::tree::LeafNode::getValue(Index) const methods@endlink + that index by linear offset (as returned by + @vdblink::tree::LeafNode::ValueOnIter::pos() ValueIter::pos@endlink) + instead of by @ijk coordinates. +- Added a @vdblink::tree::Tree::touchLeaf() Tree::touchLeaf@endlink + method that can be used to preallocate a static tree topology over which + to safely perform multithreaded processing. +- Added a grain size argument to @c LeafArray for finer control of parallelism. +- Modified the Makefile to make it easier to omit the doc, + @c vdb_test and @c vdb_view targets. +- Added utility functions (in houdini/UT_VDBUtils.h) to convert + between Houdini and OpenVDB matrix and vector types. + [Contributed by SESI] +- Added accessors to @c GEO_PrimVDB that make it easier to directly access + voxel data and that are used by the HScript volume expression functions + in Houdini 12.5. [Contributed by SESI] +- As of Houdini 12.1.77, the native transform SOP operates on OpenVDB + primitives. [Contributed by SESI] +- Added a Convert SOP that converts OpenVDB grids to Houdini volumes + and vice-versa. + + +@htmlonly @endhtmlonly +@par +Version 0.94.1 - September 7 2012 +- Fixed bugs in @vdblink::tree::RootNode RootNode@endlink and + @vdblink::tree::InternalNode InternalNode@endlink @c setValue*() and + @c fill() methods that could cause neighboring voxels to become inactive. +- Fixed a bug in + @vdblink::tree::Tree::hasSameTopology() Tree::hasSameTopology@endlink + that caused false positives when only active states and not values differed. +- Added a @vdblink::tree::Tree::hasActiveTiles() Tree::hasActiveTiles@endlink + method. +- For better cross-platform consistency, substituted bitwise AND operations + for right shifts in the @vdblink::tree::ValueAccessor ValueAccessor@endlink + hash key computation. +- @c vdb_view no longer aborts when asked to surface a vector-valued + grid—but it still doesn’t render the surface. +- Made various changes for Visual C++ compatibility. + [Contributed by SESI] +- Added an option to the MeshVoxelizer SOP to convert both open and + closed surfaces to unsigned distance fields. +- The Filter SOP now allows multiple filters to be applied in + user-specified order. + + +@htmlonly @endhtmlonly +@par +Version 0.94.0 - August 30 2012 +- Added a @vdblink::Grid::topologyUnion() method@endlink to union + just the active states of voxels from one grid with those of + another grid of a possibly different type. +- Fixed an incorrect scale factor in the Laplacian diffusion + @vdblink::tools::Filter::laplacian() filter@endlink. +- Fixed a bug in @vdblink::tree::Tree::merge() Tree::merge@endlink + that could leave a tree with invalid value accessors. +- Added @vdblink::tree::TreeValueIteratorBase::setActiveState() + TreeValueIteratorBase::setActiveState@endlink and deprecated + @c setValueOn. +- Removed @c tools/FastSweeping.h. It will be replaced with a much more + efficient implementation in the near future. +- ZIP compression of .vdb files is now + @vdblink::io::File::setCompressionEnabled() optional@endlink, + but enabled by default. [Contributed by SESI] +- Made various changes for Clang and Visual C++ compatibility. + [Contributed by SESI] +- The MeshVoxelizer SOP can now transfer arbitrary point and primitive + attribute values from the input mesh to output voxels. + + +@htmlonly @endhtmlonly +@par +Version 0.93.0 - August 24 2012 +- Renamed symbols in math/Operators.h to avoid ambiguities that + GCC 4.4 reports as errors. +- Simplified the API for the stencil version of the + closest-point transform @vdblink::math::CPT operator@endlink. +- Added logic to + @vdblink::io::Archive::readGrid() io::Archive::readGrid@endlink + to set the grid name metadata from the descriptor if the metadata + doesn’t already exist. +- Added guards to prevent nesting of @c openvdb_houdini::Interrupter::start() + and @c end() calls. + + +@htmlonly @endhtmlonly +@par +Version 0.92.0 - August 23 2012 +- Added a Laplacian diffusion + @vdblink::tools::Filter::laplacian() filter@endlink. +- Fixed a bug in the initialization of the sparse contour tracer + that caused mesh-to-volume conversion to fail in certain cases. +- Fixed a bug in the curvature stencil that caused mean curvature + filtering to produce wrong results. +- Increased the speed of the + @vdblink::tools::GridTransformer GridTransformer@endlink + by as much as 20% for fog volumes. +- Added optional pruning to the Resample SOP. +- Modified the PointSample SOP to allow it to work with ungrouped, + anonymous grids. +- Fixed a crash in the LevelSetNoise SOP. + + +@htmlonly @endhtmlonly +@par +Version 0.91.0 - August 16 2012 +- @vdblink::tools::GridTransformer tools::GridTransformer@endlink + and @vdblink::tools::GridResampler tools::GridResampler@endlink + now correctly (but not yet efficiently) process tiles in sparse grids. +- Added an optional @vdblink::CopyPolicy CopyPolicy@endlink argument + to @vdblink::GridBase::copyGrid() GridBase::copyGrid@endlink + and to @vdblink::Grid::copy() Grid::copy@endlink that specifies + whether and how the grid’s tree should be copied. +- Added a @vdblink::GridBase::newTree() GridBase::newTree@endlink + method that replaces a grid’s tree with a new, empty tree of the + correct type. +- Fixed a crash in + @vdblink::tree::Tree::setValueOff(const Coord& xyz, const ValueType& value) + Tree::setValueOff@endlink when the new value was equal to the + background value. +- Fixed bugs in @vdblink::tree::Tree::prune() Tree::prune@endlink + that could result in output tiles with incorrect active states. +- Added @c librt to the link dependencies to address build failures + on Ubuntu systems. +- Made various small changes to the Makefile and the source code + that should help with Mac OS X compatibility. +- The Composite and Resample SOPs now correctly copy the input grid’s + metadata to the output grid. + + +@htmlonly @endhtmlonly +@par +Version 0.90.1 - August 7 2012 +- Fixed a bug in the + @vdblink::math::BBox::getCenter() BBox::getCenter()@endlink method. +- Added missing header files to various files. +- @vdblink::io::File::NameIterator::gridName() + io::File::NameIterator::gridName()@endlink now returns a unique name + of the form "name[1]", "name[2]", etc. if a file + contains multiple grids with the same name. +- Fixed a bug in the Writer SOP that caused grid names to be discarded. +- The Resample SOP now correctly sets the background value of the + output grid. + + +@htmlonly @endhtmlonly +@par +Version 0.90.0 - August 3 2012 (initial public release) +- Added a basic GL viewer for OpenVDB files. +- Greatly improved the performance of two commonly-used @c Tree methods, + @vdblink::tree::Tree::evalActiveVoxelBoundingBox() + evalActiveVoxelBoundingBox()@endlink + and @vdblink::tree::Tree::memUsage() memUsage()@endlink. +- Eliminated the @c GridMap class. File I/O now uses STL containers + of grid pointers instead. +- Refactored stencil-based tools (Gradient, Laplacian, etc.) and rewrote + some of them for generality and better performance. Most now behave + correctly for grids with nonlinear index-to-world transforms. +- Added a @link FiniteDifference.h library@endlink of index-space finite + difference operators. +- Added a @vdblink::math::Hermite "Hermite"@endlink grid type that compactly + stores each voxel’s upwind normals and can be used to convert volumes + to and from polygonal meshes. +- Added a @link PointScatter.h tool@endlink (and a Houdini SOP) + to scatter points randomly throughout a volume. + +*/ diff --git a/openvdb_2_3_0_library/openvdb/doc/codingstyle.txt b/openvdb_2_3_0_library/openvdb/doc/codingstyle.txt new file mode 100755 index 0000000..25f9972 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/doc/codingstyle.txt @@ -0,0 +1,288 @@ +/** + +@page codingStyle Coding Style + +@section Introduction + +This document details the coding practices that are used in the OpenVDB +codebase. Contributed code should conform to these guidelines to maintain +consistency and maintainability. If there is a rule that you would like +clarified, changed, or added, please send a note to openvdb@gmail.com. + + +@section Contents +- @ref sNamingConventions + - @ref sNamespaceConventions + - @ref sClassConventions + - @ref sClassMethods + - @ref sClassInstanceVariables + - @ref sClassStaticVariables + - @ref sLocalVariablesAndArguments + - @ref sConstants + - @ref sEnumerationNames + - @ref sEnumerationValues + - @ref sTypedefs + - @ref sGlobalVariables + - @ref sGlobalFunctions + - @ref sBooleans +- @ref sPractices + - @ref sGeneral + - @ref sFormatting + - @ref sIncludeStatements + - @ref sComments + - @ref sPrimitiveTypes + - @ref sMacros + - @ref sClasses + - @ref sConditionalStatements + - @ref sNamespaces + - @ref sExceptions + - @ref sTemplates + - @ref sMiscellaneous + +@section sNamingConventions Naming Conventions + + +@subsection sNamespaceConventions Namespaces +-# Lowercase words, keep simple and short (e.g., @c tools, @c tree). + + +@subsection sClassConventions Classes and Structs +-# Mixed case; first letter uppercase (e.g., @c AffineMap, @c TreeIterator) +-# Do not use a prefix. + + +@subsection sClassMethods Class Methods +-# Mixed case; first letter lowercase (e.g., getAccessor(), gridType()) +-# Accessors that return a member variable by reference or a primitive type +by value are just the variable name (e.g., Grid::tree()). +-# Accessors that involve construction of objects or other computations are +@c get + the variable name (e.g., Grid::getAccessor()). +-# Simple mutators are @c set + the variable name (e.g., Grid::setTree(tree);). + + +@subsection sClassInstanceVariables Class Instance Variables +-# Mixed case; always prefixed by @c m (e.g., @c mTree, @c mTransform) + + +@subsection sClassStaticVariables Class Static Variables +-# Mixed case; always prefixed by @c s (e.g., @c sInitialized) + + +@subsection sLocalVariablesAndArguments Local Variables and Arguments +-# Use mixed case with an initial lower case (e.g., @c ijk, @c offset, +@c range, @c rhsValue). + + +@subsection sConstants Constants +-# All uppercase; words separated by underscores. If not in a namespace or +local to a source file, then prefixed with the library name in all caps +(e.g., @c HALF_FLOAT_TYPENAME_SUFFIX, @c ZIP_COMPRESSION_LEVEL). + + +@subsection sEnumerationNames Enumeration Names +-# Mixed case; first letter uppercase (e.g., @c GridClass, @c VecType) + + +@subsection sEnumerationValues Enumeration Values +-# Same as constants; all uppercase; words separated by underscores +(e.g., @c GRID_LEVEL_SET, @c VEC_INVARIANT) and with a common prefix +(@c GRID_, @c VEC_, etc.) + + +@subsection sTypedefs Typedefs +-# Mixed case; first letter uppercase (e.g., @c ConstPtr, @c ValueType) +-# Do not use a prefix. + + +@subsection sGlobalVariables Global Variables +-# Mixed case; always prefixed by @c g (e.g., @c gRegistry) +-# In general, try to use class static data instead of globals. + + +@subsection sGlobalFunctions Global Functions +-# Mixed case; always prefixed by @c g (e.g., gFunc()). +-# In general, try to use class static members instead of global functions. + + +@subsection sBooleans Booleans +-# When naming boolean functions and variables, use names that read as a +condition (e.g., if (grid.empty()), +if (matrix.isInvertible())). + + +@section sPractices Practices + + +@subsection sGeneral General +-# Code must compile without any warning messages. +if closely related. +-# Prefer the C++ Standard Library to the C Standard Library. +-# Restrict variables to the smallest scopes possible, and avoid defining +local variables before giving them values. Prefer declarations inside +conditionals: if (Grid::Ptr grid = createGrid()) { ... } instead of +Grid::Ptr grid = createGrid(); if (grid) { ... } +-# For new files, be sure to use the right license boilerplate per our license +policy. + + +@subsection sFormatting Formatting +-# Use Doxygen-style comments to document public code. +-# Indentation is 4 spaces. Do not use tabs. +-# Use Unix-style carriage returns ("\n") rather than Windows/DOS ones ("\r\n"). +-# Don’t put an @c else right after a @c return. It’s unnecessary +and increases indentation level. +-# Do not leave debug printfs or other debugging code lying around. +-# Leave a blank line between a group of variable declarations and other code. +-# Leave a space after the keywords @c if, @c switch, @c while, @c do, @c for, +and @c return. +-# Leave a space on each side of binary operators such as +, -, *, /, &&, +and ||. For clarity in mathematical situations, you may omit the spaces on +either side of * and / operators to illustrate their precedence over + and -. +-# Do not leave a space between any dereferencing operator +(such as *, &, [], ->, or .) and its operands. +-# In parameter lists, leave a space after each comma. +-# Do not leave a space after an opening parenthesis or before a closing +parenthesis. +-# Parentheses should be used to clarify operator precedence in expressions. +-# Do not leave a space before an end-of-statement semicolon. +-# Do not use literal tabs in strings or character constants. Rather, use +spaces or "\t". +-# If a parameter list is too long, break it between parameters. Group related +parameters on lines if appropriate. +-# Modified spacing is allowed to vertically align repetitive expressions. +-# Always begin numeric constants with a digit (e.g., 0.001 not .001). +-# Use K&R-style brace placement in public code. +-# You may leave off braces for simple, single line flow control statements. +-# The return type in a function definition should go on a line by itself. + + +@subsection sIncludeStatements Include Statements +-# Always use double quotes ("") to include header files that live in the same +directory as your source code. +-# Use angle brackets (<>) to include header files from outside a file’s +directory. +-# Do not use absolute paths in include directives. +-# If there is a header corresponding to a given source file, list it first, +followed by other local headers, library headers and then system headers. + + +@subsection sHeaderFiles Header Files +-# Header files have a .h extension. +-# All header files should be bracketed by @c \#ifdef "guard" statements. +-# In class definitions, list public, then protected, then private members. +-# List methods before variables. +-# Fully prototype all public functions and use descriptive naming for each +argument. +-# Declare every function defined in a header but outside a class definition +explicitly as @c inline. +-# Prefer forward declarations to @c \#include directives in headers. +-# Do not take advantage of indirect inclusion of header files. + + +@subsection sSourceFiles Source Files +-# Source files have a .cc extension. +-# Properly prototype all file static functions with usefully named arguments. +-# Whenever possible, put variables and functions in an anonymous namespace. +-# Avoid global variables. +-# For the main file of an executable, define @c main() at the top and then +utility functions below it in a top-down fashion. + + +@subsection sComments Comments +-# Use @c // style comments instead of / * * / style, even for multi-line +comments. +-# Use multi-line comments to describe following paragraphs of code. +-# Use end-of-line comments to describe variable declarations or to clarify a +single statement. +-# Large blocks of code should be commented out with \#if 0 and @c \#endif. +-# Do not leave obsolete code fragments within comments as an historical trail. + + +@subsection sPrimitiveTypes Primitive Types +-# Avoid writing code that is dependent on the bit size of primitive values, +but when specific bit sizes are required, use explicitly-sized types such as +@c int32_t or @c uint64_t. + + +@subsection sMacros Macros +-# Avoid macros for constants. Use static constants instead. +-# Avoid macro functions. Use @c inline and templates instead. + + +@subsection sClasses Classes +-# Constructors that can be called with only one argument should be prefixed +with the @c explicit keyword to avoid unintended type conversions. +-# Always list the destructor. +-# Never call virtual methods from destructors. +-# If you have a copy constructor, make sure you have an assignment operator. +-# If you have an assignment operator, you probably need a copy constructor. +-# If you have data members that are pointers to dynamically allocated memory, +make sure you have a copy constructor and an assignment operator, both of +which do the right thing with that memory. +-# Classes which are to be subclassed always have a virtual destructor, even if +it is empty. +-# Check against self assignment and return *this in assignment +operators. +-# Declare methods as const as much as possible. +-# Declare object-valued input arguments as const references wherever +possible. Primitives may be passed by value, however. +-# Arithmetic, logical, bitwise, dereference, and address of operators should +only be used when their semantics are clear, obvious, and unambiguous. +-# The application operator [ () ] is allowed for functors. +-# Conversion operators should be avoided. +-# Never return const references to stack allocated or implicitly computed +objects. +-# If a class does not have a copy constructor and/or assignment operator, +consider creating a private unimplemented copy constructor and/or assignment +operator to prevent automatically generated versions from being used. + + +@subsection sConditionalStatements Conditional Statements +-# For test expressions, use a form that indicates as clearly as possible the +types of operands by avoiding implicit casts. +-# Assignments that occur within conditional statements must have no effect in +the enclosing scope. +-# Allow for arithmetic imprecision when comparing floating point values. +-# In switch statements, always comment fallthroughs and empty cases. + + +@section sNamespaces Namespaces +-# Namespaces should be used whenever possible. +-# Avoid pulling in entire namespaces with @c using directives +(e.g., using namespace std;). +-# @c using declarations are allowed for individual symbols (e.g., +using std::vector;), but they should never appear in a header file. +-# Define global operators in the namespace of their arguments. +-# Namespaces are not indented. + + +@subsection sExceptions Exceptions +-# Appropriate use of exceptions is encouraged. +-# Methods should declare all exceptions they might throw using comments, +but not exception specifications. +-# Throw scope local exception instances, not pointers or references or globals. +-# Catch exceptions by reference. +-# Never allow an exception to escape from a destructor. + + +@subsection sTemplates Templates +-# Use @c typename rather than @c class when declaring template type parameters. + + +@subsection sMiscellaneous Miscellaneous +-# Don’t use pointers to run through arrays of non-primitive types. Use explicit array indexing, iterators or generic algorithms instead. +-# Use C++ casts (static_cast<int>(x) or int(x)), +not C casts ((int)x). +-# Multiple variables of the same data type may be declared on the same line +-# Library code must never deliberately terminate the application in response +to an error condition. +-# Avoid using malloc/free when new/delete can be used instead. +-# Avoid @c goto. +-# Avoid \"magic numbers\". Use named constants when necessary. +-# If you use typeid/typeinfo, be aware that although all runtimes support +typeinfo::name(), the format of the string it returns varies +between compilers and even for a given compiler the value is not guaranteed +to be unique. + +*/ + diff --git a/openvdb_2_3_0_library/openvdb/doc/doc.txt b/openvdb_2_3_0_library/openvdb/doc/doc.txt new file mode 100755 index 0000000..c0eafea --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/doc/doc.txt @@ -0,0 +1,520 @@ +/** +@mainpage OpenVDB + +The @b OpenVDB library comprises a hierarchical data structure and a suite +of tools for the efficient manipulation of sparse, possibly time-varying, +volumetric data discretized on a three-dimensional grid. It is based on +VDB, which was developed by Ken Museth at DreamWorks Animation, and it +offers an effectively infinite 3D index space, compact storage (both in +memory and on disk), fast data access (both random and sequential), and +a collection of algorithms specifically optimized for the data structure +for common tasks such as filtering, CSG, compositing, sampling and +voxelization from other geometric representations. The technical details +of VDB are described in the paper “VDB: High-Resolution Sparse Volumes with Dynamic Topology”. + +@b OpenVDB is maintained by +DreamWorks Animation +and was developed primarily by +- Ken Museth +- Peter Cucka +- Mihai Aldén +- David Hill + +See the @subpage overview "Overview" for an introduction to the library. + +See @subpage transformsAndMaps "Transforms and Maps" for more +discussion of the transforms used in @b OpenVDB. + +See the @subpage faq "FAQ" for frequently asked questions about @b OpenVDB. + +See the @subpage codeExamples "Cookbook" to get started using @b OpenVDB. + +See @subpage python "Using OpenVDB in Python" to get started with the @b OpenVDB +Python module. + +See the @subpage changes "Release Notes" for what's new in this version +of @b OpenVDB. + +Contributors, please familiarize yourselves with our +@subpage codingStyle "coding standards". + + + +@page overview OpenVDB Overview + +@section Contents +- @ref secOverview +- @ref secTree + - @ref subsecTreeConfig +- @ref secSparsity + - @ref subsecValues + - @ref subsecInactive +- @ref secSpaceAndTrans + - @ref subsecVoxSpace + - @ref subsecWorSpace + - @ref subsecTrans +- @ref 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 @c Tree with a @c Transform and additional metadata. +For instancing purposes (i.e., placing copies +of the same volume in multiple locations), the same tree may be referenced +(via smart pointers) by several different Grids, each having a +unique transform. + +We now proceed to discuss the @c Tree and ideas of sparsity in some +detail, followed by a briefer description of the different spaces and +transforms as well as some of the tools that act on the sparse data. + + +@section secTree The Tree + +In OpenVDB the @c Tree data structure exists to answer the question +What value is stored at location @ijk in three-dimensional index +space? Here i, j and k are arbitrary signed 32-bit +integers, and the data type of the associated value (float, +bool, vector, etc.) is the same for all @ijk. While the @c Tree +serves the same purpose as a large three-dimensional array, it is a +specially designed data structure that, given sparse unique values, +minimizes the overall memory footprint while retaining fast access times. +This is accomplished, as the name suggests, via a tree-based acceleration +structure comprising a @vdblink::tree::RootNode RootNode@endlink, +@vdblink::tree::LeafNode LeafNodes@endlink and usually one or more levels +of @vdblink::tree::InternalNode InternalNodes@endlink with prescribed +branching factors. + +@subsection subsecTreeConfig Tree Configuration + +The tree-based acceleration structure can be configured in various ways, +but with the restriction that for a given tree all the LeafNodes +are at the same depth. Conceptually, the @c RootNode and +@c InternalNodes increasingly subdivide the three-dimensional index +space, and the LeafNodes hold the actual unique voxels. + +The type of a @c Tree encodes both the type of the data to be +stored in the tree (float, bool, etc.) and the +tree's node configuration. In practice a four-level (root, +internal, internal, leaf) configuration is standard, and several +common tree types are defined in openvdb.h. For example, +@code +typedef tree::Tree4::Type FloatTree; +typedef tree::Tree4::Type BoolTree; +@endcode +These predefined tree types share the same branching factors, which dictate +the number of children of a given node. The branching factors (5, 4, 3) +are specified as base two logarithms and should be read backwards from the +leaf nodes up the tree. + +In the default tree configuration, each @c LeafNode holds a +three-dimensional grid of 23 voxels on a side (i.e., an +@f$8\times8\times8@f$ voxel grid). Internally, the @c LeafNode is said +to be at "level 0" of the tree. At "level 1" of this tree is the first +@c InternalNode, and it indexes a @f$2^4\times2^4\times2^4 = +16\times16\times16@f$ grid, each entry of which is either a @c LeafNode +or a constant value that represents an @f$8\times8\times8@f$ block of +voxels. At "level 2" is the second @c InternalNode in this +configuration; it in turn indexes a @f$2^5\times2^5\times2^5 = +32\times32\times32@f$ grid of level-1 InternalNodes and/or values, +and so the @c InternalNode at level 2 subsumes a three-dimensional +block of voxels of size @f$32\times16\times8 = 4096@f$ on a side. Unlike +the InternalNodes and LeafNodes, the @c RootNode ("level +3" for the default configuration) is not explicitly restricted in the number +of children it may have, so the overall index space is limited only by the +range of the integer indices, which are 32-bit by default. + + +@section secSparsity Sparse Values and Voxels + +Like a tree's node configuration, the type of data held by a tree is +determined at compile time. Conceptually the tree itself +employs two different notions of data sparsity to reduce the memory footprint +and at the same time accelerate access to its contents. The first is largely +hidden from the user and concerns ways in which large regions of uniform values +are compactly represented, and the second allows for fast sequential iteration, +skipping user-specified "uninteresting" regions (that may or may not have +uniform values). + +@subsection subsecValues Tile, Voxel, and Background Values + +Although the data in a tree is accessed and set on a per-voxel level +(i.e., the value at @ijk) it need not be internally stored in that way. +To reduce the memory footprint and accelerate data access, data values are +stored in three distinct forms internal to the tree: voxel values, +tile values, and a background value. A voxel value is a unique +value indexed by the location of a voxel and is stored in the @c LeafNode +responsible for that voxel. A tile value is a uniform value assigned to all +voxels subsumed by a given node. (For example, a tile Value belonging to an +@c InternalNode at level 1 is equivalent to a constant-value cube of voxels +of the same size, @f$8\times8\times8@f$, as a @c LeafNode.) The tile value +is returned when a request is made for the data associated with any @ijk +location within the uniform tile. The background value is a unique value +(stored at the root level) that is returned when accessing any @ijk location +that does not resolve to either a tile or a @c LeafNode. + + +@subsection subsecInactive Active and Inactive Voxels + +Any voxel or tile can be classified as either @b active or @b inactive. +The interpretation of this state is application-specific, but generally +active voxels are "interesting", and inactive somehow less so. The locations +of active values may be sparse in the overall voxel topology, and OpenVDB +provides @ref secIterator "iterators" that access active values only (as +well as iterators over inactive values, all values, and general topology). +An example of active vs. inactive: the voxels used to store the distance +values of a narrow-band level set (i.e., close to a given surface) will be +marked as active while the other ("far") voxel locations will be marked as +inactive and will generally represent regions of space with constant distance +values (e.g., two constant distance values of opposite sign to distinguish +the enclosed inside region from the infinite outside or background embedding). + +The @vdblink::tree::Tree::prune() prune()@endlink method replaces +with tile values any nodes that subsume voxels with the same values +and active states. +The resulting tree represents the same volume, but more sparsely. + +@section secSpaceAndTrans Coordinate Systems and Transforms + +The sampled data in the tree is accessed using signed index coordinates +@ijk, but associating each indicial coordinate with a specific physical +location is a job for a @c Transform. A simple linear transform assumes +a lattice-like structure with a fixed physical distance @f$\Delta@f$ between +indices, so that @f$(x,y,z) = (\Delta i, \Delta j, \Delta k)@f$. + +@subsection subsecVoxSpace Index Space + +To simplify transformations between physical space and lattice index +coordinates, a continuous generalization of the index lattice points called +index space is used. +For example, index space coordinate (1.0, 1.0, 1.0) corresponds to the same +point as (1,1,1) in the index lattice, but (1.5,1.0,1.0) also has meaning as +halfway between the index coordinates (1,1,1) and (2,1,1). Index space can +be used in constructing interpolated data values: given an arbitrary +location in physical space, one can use a transform to compute the point in +index space (which need not fall on an exact integer index) that maps to that +location and locally interpolate from values with neighboring index +coordinates. + +@subsection subsecWorSpace World Space + +The interpretation of the data in a tree takes place in world +space. For example, the tree might hold data sampled at discrete +physical locations in world space. @c Transform methods such as +@vdblink::math::Transform::indexToWorld() indexToWorld() @endlink +and its inverse @vdblink::math::Transform::worldToIndex() +worldToIndex() @endlink +may be used to relate coordinates in the two continuous spaces. In +addition, methods such as @vdblink::math::Transform::worldToIndexCellCentered() +worldToIndexCellCentered()@endlink actually return lattice points. + +@subsection subsecTrans Transforms and Maps +A @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 @c Transform is managed by a +@vdblink::math::MapBase Map@endlink object, which is an +encapsulation of a continuous, mostly invertible function of three +variables. A @c Map is required to provide +@vdblink::math::MapBase::applyMap() applyMap()@endlink and +@vdblink::math::MapBase::applyInverseMap() applyInverseMap()@endlink +methods to relate locations in its domain to its range and vice versa. +A @c Map is also required to provide information about its local derivatives. +For more on these classes, see the +@subpage transformsAndMaps "Transforms and Maps" page. + + +@section 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 @c Tree. +These take several forms: iterator classes of various types, functor-based +@b visitor methods, and the +@vdblink::tree::ValueAccessor ValueAccessor@endlink, +an accelerator for indexed @ijk voxel lookups. + +Iterator classes follow a fairly consistent naming scheme. First, the +@b CIter and @b Iter suffixes denote @const and non-@const iterators, i.e., +iterators that offer, respectively, read-only and read/write access to the +underlying tree or node. Second, iterators over tile and voxel values are +denoted either @b On, @b Off or @b All, indicating that they visit only +active values, only inactive values, or both active and inactive values. +So, for example, @c Tree::ValueOnCIter is a read-only iterator over all +active values (both tile and voxel) of a tree, whereas +@c LeafNode::ValueAllIter is a read/write iterator over all values, both +active and inactive, of a single leaf node. + +OpenVDB iterators are not STL-compatible in that one can always request +an iterator that points to the beginning of a collection of elements (nodes, +voxels, etc.), but one usually cannot request an iterator that points to the +end of the collection. (This is because finding the end might require a +full tree traversal.) Instead, all OpenVDB iterators implement a @c test() +method that returns @c true as long as the iterator is not exhausted and +@c false as soon as it is. Typical usage is as follows: +@code +typedef openvdb::FloatGrid GridType; +GridType grid = ...; +for (GridType::ValueOnCIter iter = grid.cbeginValueOn(); iter.test(); ++iter) ... +@endcode +or more compactly +@code +for (GridType::ValueOnCIter iter = grid.cbeginValueOn(); iter; ++iter) ... +@endcode +Note that the naming scheme for methods that return "begin" iterators +closely mirrors that of the iterators themselves. That is, +@c Grid::cbeginValueOn() returns a @const iterator to the first of a grid's +active values, whereas @c LeafNode::beginValueAll() returns a non-@const +iterator to the first of a leaf node's values, both active and inactive. +(Const overloads of @c begin*() methods are usually provided, so that if +the @c Grid is itself @const, @c Grid::begin*() will actually return a +@const iterator. This makes it more convenient to use these methods in +templated code.) + +Finally, note that modifying the tree or node over which one is iterating +typically does not invalidate the iterator, though it might first need +to be incremented to point to the next existing element (for example, +if one deletes a child node to which the iterator is currently pointing). + +@subsection subsecTreeIter Tree Iterators +@anchor treeValueIterRef +@par Tree::ValueIter +Tree-level value iterators traverse an entire tree, visiting each value +(tile or voxel) exactly once. (It is also possible to restrict the +traversal to minimum and maximum levels of the tree.) In addition to the +methods common to all OpenVDB iterators, such as @c test() and @c next(), +a @c Tree::ValueIter provides methods that return the depth in the tree of +the node within which the iterator is pointing (the root node has depth 0) +and the @ijk axis-aligned bounding box of the tile or voxel to which it is +pointing, and methods to get and set both the value and the active state of +the tile or voxel. See the +@vdblink::tree::TreeValueIteratorBase TreeValueIteratorBase @endlink +class for the complete list. + +@anchor treeLeafIterRef +@par Tree::LeafIter +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 @c 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 (@c RootNode, @c InternalNode and @c LeafNode) do not inherit +from a common base class. For efficiency, OpenVDB generally avoids class +inheritance and virtual functions in favor of templates, allowing the +compiler to optimize away function calls. In particular, each node type is +templated on the type of its children, so even two InternalNodes at +different levels of a tree have distinct types. As a result, it is +necessary to know the type of the node to which a node iterator is pointing +in order to request access to that node. See the +@ref sNodeIterator "Cookbook" for an example of how to do this. + +@subsection subsecNodeIter Node Iterators + +Less commonly used than tree-level iterators (but found in the +implementations of some of the narrow-band level set algorithms referred to +@ref treeLeafIterRef "above") are node-level iterators. A node +value iterator visits the values (active, inactive or both) stored in +a single @c RootNode, @c InternalNode or @c LeafNode, whereas a node +child iterator visits the children of a single root or internal node. +(Recall that non-leaf nodes store either a tile value or a child node at +each grid position.) + + +@subsection subsecValueAccessor Value Accessor + +When traversing a grid by @ijk index in a spatially coherent pattern, +such as when iterating over neighboring voxels, request a +@vdblink::tree::ValueAccessor ValueAccessor@endlink from the grid +(with @vdblink::Grid::getAccessor() Grid::getAccessor()@endlink) +and use the accessor's +@vdblink::tree::ValueAccessor::getValue() getValue()@endlink and +@vdblink::tree::ValueAccessor::setValue() setValue()@endlink methods, since +these will usually be significantly faster (a factor of three is typical) +than accessing voxels directly in the grid's tree. +The accessor records the sequence of nodes +visited during the most recent access; on the next access, rather than +traversing the tree from the root node down, it performs an inverted +traversal from the deepest recorded node up. For neighboring voxels, the +traversal need only proceed as far as the voxels' common ancestor node, +which more often than not is the first node in the sequence. + +Multiple accessors may be associated with a single grid. In fact, for +multithreaded, read-only access to a grid, it is recommended that each +thread be assigned its own accessor. A thread-safe, mutex-locked accessor +is provided (see @vdblink::tree::ValueAccessorRW ValueAccessorRW@endlink), +but the locking negates much of the performance benefit of inverted traversal; +and because it is the accessor object that is thread-safe, not the grid, +concurrent reads and writes are not safe unless all threads share a single +accessor. + +All accessors associated with a grid must be cleared after any operation that +removes nodes from the grid's tree, such as pruning, CSG or compositing. +For those and other built-in operations, this is done automatically via +a callback mechanism, but developers must be careful to call +@vdblink::tree::Tree::clearAllAccessors() Tree::clearAllAccessors()@endlink +whenever deleting nodes directly. + + +@subsection subsecTraversal Tree Traversal + +To be written + +*/ diff --git a/openvdb_2_3_0_library/openvdb/doc/examplecode.txt b/openvdb_2_3_0_library/openvdb/doc/examplecode.txt new file mode 100755 index 0000000..0c2a256 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/doc/examplecode.txt @@ -0,0 +1,1179 @@ +/** + +@page codeExamples OpenVDB Cookbook + +This section provides code snippets and some complete programs that +illustrate how to use OpenVDB and how to perform common tasks. + + +@section Contents +- @ref sHelloWorld + - @ref sCompilingHelloWorld +- @ref sAllocatingGrids +- @ref sPopulatingGrids +- @ref sModifyingGrids +- @ref sStreamIO +- @ref sHandlingMetadata + - @ref sAddingMetadata + - @ref sGettingMetadata + - @ref sRemovingMetadata +- @ref sIteration + - @ref sNodeIterator + - @ref sLeafIterator + - @ref sValueIterator +- @ref 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 + + + +@section sHelloWorld "Hello, World" for OpenVDB +This is a very simple example showing how to create a grid and access +its voxels. OpenVDB supports both random access to voxels by coordinates +and sequential access by means of iterators. This example illustrates both +types of access: +@code +#include +#include + +int main() +{ + // Initialize the OpenVDB library. This must be called at least + // once per program and may safely be called multiple times. + openvdb::initialize(); + + // Create an empty floating-point grid with background value 0. + openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(); + + std::cout << "Testing random access:" << std::endl; + + // Get an accessor for coordinate-based access to voxels. + openvdb::FloatGrid::Accessor accessor = grid->getAccessor(); + + // Define a coordinate with large signed indices. + openvdb::Coord xyz(1000, -200000000, 30000000); + + // Set the voxel value at (1000, -200000000, 30000000) to 1. + accessor.setValue(xyz, 1.0); + + // Verify that the voxel value at (1000, -200000000, 30000000) is 1. + std::cout << "Grid" << xyz << " = " << accessor.getValue(xyz) << std::endl; + + // Reset the coordinates to those of a different voxel. + xyz.reset(1000, 200000000, -30000000); + + // Verify that the voxel value at (1000, 200000000, -30000000) is + // the background value, 0. + std::cout << "Grid" << xyz << " = " << accessor.getValue(xyz) << std::endl; + + // Set the voxel value at (1000, 200000000, -30000000) to 2. + accessor.setValue(xyz, 2.0); + + // Set the voxels at the two extremes of the available coordinate space. + // For 32-bit signed coordinates these are (-2147483648, -2147483648, -2147483648) + // and (2147483647, 2147483647, 2147483647). + accessor.setValue(openvdb::Coord::min(), 3.0f); + accessor.setValue(openvdb::Coord::max(), 4.0f); + + std::cout << "Testing sequential access:" << std::endl; + + // Print all active ("on") voxels by means of an iterator. + for (openvdb::FloatGrid::ValueOnCIter iter = grid->cbeginValueOn(); iter; ++iter) { + std::cout << "Grid" << iter.getCoord() << " = " << *iter << std::endl; + } +} +@endcode +Output: +@code +Testing random access: +Grid[1000, -200000000, 30000000] = 1 +Grid[1000, 200000000, -30000000] = 0 +Testing sequential access: +Grid[-2147483648, -2147483648, -2147483648] = 3 +Grid[1000, -200000000, 30000000] = 1 +Grid[1000, 200000000, -30000000] = 2 +Grid[2147483647, 2147483647, 2147483647] = 4 +@endcode + +@subsection sCompilingHelloWorld Compiling +See the @c Makefile and @c INSTALL file included in this distribution for +details on how to build and install the OpenVDB library. +By default, installation is into the directory tree rooted at +/tmp/OpenVDB/, but this can be changed either by editing the value +of the @c INSTALL_DIR variable in the makefile or by setting the desired +value from the command line, as in the following example: +@code +make install INSTALL_DIR=/usr/local +@endcode +Once OpenVDB has been installed, the simplest way to compile a program +like the “Hello, World” example above is to examine the +commands that are used to build the @c vdb_print tool: +@code +rm vdb_print +make verbose=yes vdb_print +@endcode +and then replace “-o vdb_print” with, for example, +“-o helloworld” +and “cmd/openvdb_print/main.cc” +with “helloworld.cc”. + + + +@section sAllocatingGrids Creating and writing a grid +This example is a complete program that illustrates some of the basic steps +to create grids and write them to disk. (See @ref sPopulatingGrids, +below, for the implementation of the @c makeSphere() function.) +@code +#include + +int main() +{ + openvdb::initialize(); + + // Create a shared pointer to a newly-allocated grid of a built-in type: + // in this case, a FloatGrid, which stores one single-precision floating point + // value per voxel. Other built-in grid types include BoolGrid, DoubleGrid, + // Int32Grid and Vec3SGrid (see openvdb.h for the complete list). + // The grid comprises a sparse tree representation of voxel data, + // user-supplied metadata and a voxel space to world space transform, + // which defaults to the identity transform. + openvdb::FloatGrid::Ptr grid = + openvdb::FloatGrid::create(/*background value=*/2.0); + + // Populate the grid with a sparse, narrow-band level set representation + // of a sphere with radius 50 voxels, located at (1.5, 2, 3) in index space. + makeSphere(*grid, /*radius=*/50.0, /*center=*/openvdb::Vec3f(1.5, 2, 3)); + + // Associate some metadata with the grid. + grid->insertMeta("radius", openvdb::FloatMetadata(50.0)); + + // Associate a scaling transform with the grid that sets the voxel size + // to 0.5 units in world space. + grid->setTransform( + openvdb::math::Transform::createLinearTransform(/*voxel size=*/0.5)); + + // Identify the grid as a level set. + grid->setGridClass(openvdb::GRID_LEVEL_SET); + + // Name the grid "LevelSetSphere". + grid->setName("LevelSetSphere"); + + // Create a VDB file object. + openvdb::io::File file("mygrids.vdb"); + + // Add the grid pointer to a container. + openvdb::GridPtrVec grids; + grids.push_back(grid); + + // Write out the contents of the container. + file.write(grids); + file.close(); +} +@endcode + +The OpenVDB library includes optimized routines for many common tasks. +For example, most of the steps given above are encapsulated in the function +@vdblink::tools::createLevelSetSphere() tools::createLevelSetSphere()@endlink, so that +the above can be written simply as follows: + +@code +#include +#include + +int main() +{ + openvdb::initialize(); + + // Create a FloatGrid and populate it with a narrow-band + // signed distance field of a sphere. + openvdb::FloatGrid::Ptr grid = + openvdb::tools::createLevelSetSphere( + /*radius=*/50.0, /*center=*/openvdb::Vec3f(1.5, 2, 3), + /*voxel size=*/0.5, /*width=*/4.0); + + // Associate some metadata with the grid. + grid->insertMeta("radius", openvdb::FloatMetadata(50.0)); + + // Name the grid "LevelSetSphere". + grid->setName("LevelSetSphere"); + + // Create a VDB file object. + openvdb::io::File file("mygrids.vdb"); + + // Add the grid pointer to a container. + openvdb::GridPtrVec grids; + grids.push_back(grid); + + // Write out the contents of the container. + file.write(grids); + file.close(); +} +@endcode + + + +@section sPopulatingGrids Populating a grid with values +The following code is templated so as to operate on grids containing values +of any scalar type, provided that the value type supports negation and +comparison. Note that this algorithm is only meant as an example and should +never be used in production; use the much more efficient routines in +tools/LevelSetSphere.h instead. + +See @ref sGenericProg for more on processing grids of arbitrary type. +@anchor makeSphereCode +@code +// Populate the given grid with a narrow-band level set representation of a sphere. +// The width of the narrow band is determined by the grid's background value. +// (Example code only; use tools::createSphereSDF() in production.) +template +void +makeSphere(GridType& grid, float radius, const openvdb::Vec3f& c) +{ + typedef typename GridType::ValueType ValueT; + + // Distance value for the constant region exterior to the narrow band + const ValueT outside = grid.background(); + + // Distance value for the constant region interior to the narrow band + // (by convention, the signed distance is negative in the interior of + // a level set) + const ValueT inside = -outside; + + // Use the background value as the width in voxels of the narrow band. + // (The narrow band is centered on the surface of the sphere, which + // has distance 0.) + int padding = int(openvdb::math::RoundUp(openvdb::math::Abs(outside))); + // The bounding box of the narrow band is 2*dim voxels on a side. + int dim = int(radius + padding); + + // Get a voxel accessor. + typename GridType::Accessor accessor = grid.getAccessor(); + + // Compute the signed distance from the surface of the sphere of each + // voxel within the bounding box and insert the value into the grid + // if it is smaller in magnitude than the background value. + openvdb::Coord ijk; + int &i = ijk[0], &j = ijk[1], &k = ijk[2]; + for (i = c[0] - dim; i < c[0] + dim; ++i) { + const float x2 = openvdb::math::Pow2(i - c[0]); + for (j = c[1] - dim; j < c[1] + dim; ++j) { + const float x2y2 = openvdb::math::Pow2(j - c[1]) + x2; + for (k = c[2] - dim; k < c[2] + dim; ++k) { + + // The distance from the sphere surface in voxels + const float dist = openvdb::math::Sqrt(x2y2 + + openvdb::math::Pow2(k - c[2])) - radius; + + // Convert the floating-point distance to the grid's value type. + ValueT val = ValueT(dist); + + // Only insert distances that are smaller in magnitude than + // the background value. + if (val < inside || outside < val) continue; + + // Set the distance for voxel (i,j,k). + accessor.setValue(ijk, val); + } + } + } + + // Propagate the outside/inside sign information from the narrow band + // throughout the grid. + grid.signedFloodFill(); +} +@endcode + + + +@section sModifyingGrids Reading and modifying a grid +@code +#include + +openvdb::initialize(); + +// Create a VDB file object. +openvdb::io::File file("mygrids.vdb"); + +// Open the file. This reads the file header, but not any grids. +file.open(); + +// Loop over all grids in the file and retrieve a shared pointer +// to the one named "LevelSetSphere". (This can also be done +// more simply by calling file.readGrid("LevelSetSphere").) +openvdb::GridBase::Ptr baseGrid; +for (openvdb::io::File::NameIterator nameIter = file.beginName(); + nameIter != file.endName(); ++nameIter) +{ + // Read in only the grid we are interested in. + if (nameIter.gridName() == "LevelSetSphere") { + baseGrid = file.readGrid(nameIter.gridName()); + } else { + std::cout << "skipping grid " << nameIter.gridName() << std::endl; + } +} + +file.close(); + +// From the example above, "LevelSetSphere" is known to be a FloatGrid, +// so cast the generic grid pointer to a FloatGrid pointer. +openvdb::FloatGrid::Ptr grid = openvdb::gridPtrCast(baseGrid); + +// Convert the level set sphere to a narrow-band fog volume, in which +// interior voxels have value 1, exterior voxels have value 0, and +// narrow-band voxels have values varying linearly from 0 to 1. + +const float outside = grid->background(); +const float width = 2.0 * outside; + +// Visit and update all of the grid's active values, which correspond to +// voxels on the narrow band. +for (openvdb::FloatGrid::ValueOnIter iter = grid->beginValueOn(); iter; ++iter) { + float dist = iter.getValue(); + iter.setValue((outside - dist) / width); +} + +// Visit all of the grid's inactive tile and voxel values and update the values +// that correspond to the interior region. +for (openvdb::FloatGrid::ValueOffIter iter = grid->beginValueOff(); iter; ++iter) { + if (iter.getValue() < 0.0) { + iter.setValue(1.0); + iter.setValueOff(); + } +} + +// Set exterior voxels to 0. +grid->setBackground(0.0); +@endcode + + + +@section sStreamIO Stream I/O +The @vdblink::io::Stream io::Stream@endlink class allows grids +to be written to and read from streams that do not support random access, +with the restriction that all grids must be written or read at once. +(With @vdblink::io::File io::File@endlink, +grids can be read individually by name, provided that they were originally +written with @c io::File, rather than streamed to a file.) + +@code +#include +#include + +openvdb::initialize(); + +openvdb::GridPtrVecPtr grids(new GridPtrVec); +grids->push_back(...); + +// Stream the grids to a string. +std::ostringstream ostr(std::ios_base::binary); +openvdb::io::Stream(ostr).write(*grids); + +// Stream the grids to a file. +std::ofstream ofile("mygrids.vdb", std::ios_base::binary); +openvdb::io::Stream(ofile).write(*grids); + +// Stream in grids from a string. +// Note that io::Stream::getGrids() returns a shared pointer +// to an openvdb::GridPtrVec. +std::istringstream istr(ostr.str(), std::ios_base::binary); +openvdb::io::Stream strm(istr); +grids = strm.getGrids(); + +// Stream in grids from a file. +std::ifstream ifile("mygrids.vdb", std::ios_base::binary); +grids = openvdb::io::Stream(ifile).getGrids(); +@endcode + + + +@section sHandlingMetadata Handling metadata +Metadata of various types (string, floating point, integer, etc.—see +metadata/Metadata.h for more) can be attached both to individual Grids +and to files on disk. +The examples that follow refer to Grids, but the usage is the same +for the @vdblink::MetaMap MetaMap@endlink that can optionally be supplied +to a @vdblink::io::File::write() file@endlink or +@vdblink::io::Stream::write() stream@endlink for writing. + +@subsection sAddingMetadata Adding metadata +The @vdblink::Grid::insertMeta() Grid::insertMeta()@endlink method either +adds a new (@em name, @em value) pair if the name is unique, or overwrites +the existing value if the name matches an existing one. An existing value +cannot be overwritten with a new value of a different type; the old metadata +must be removed first. +@code +#include + +openvdb::Vec3SGrid::Ptr grid = openvdb::Vec3SGrid::create(); + +grid->insertMeta("vector type", openvdb::StringMetadata("covariant (gradient)")); +grid->insertMeta("radius", openvdb::FloatMetadata(50.0)); +grid->insertMeta("center", openvdb::Vec3SMetadata(openvdb::Vec3S(10, 15, 10))); + +// OK, overwrites existing value: +grid->insertMeta("center", openvdb::Vec3SMetadata(openvdb::Vec3S(10.5, 15, 30))); + +// Error (throws openvdb::TypeError), can't overwrite a value of type Vec3S +// with a value of type float: +grid->insertMeta("center", openvdb::FloatMetadata(0.0)); +@endcode + +@subsection sGettingMetadata Retrieving metadata +Call @vdblink::Grid::metaValue() Grid::metaValue()@endlink to retrieve +the value of metadata of a known type. For example, +@code +std::string s = grid->metaValue("vector type"); + +float r = grid->metaValue("radius"); + +// Error (throws openvdb::TypeError), can't read a value of type Vec3S as a float: +float center = grid->metaValue("center"); +@endcode + +@vdblink::Grid::beginMeta() Grid::beginMeta()@endlink and +@vdblink::Grid::beginMeta() Grid::beginMeta()@endlink return STL @c std::map +iterators over all of the metadata associated with a grid: +@code +for (openvdb::MetaMap::MetaIterator iter = grid->beginMeta(); + iter != grid->endMeta(); ++iter) +{ + const std::string& name = iter->first; + openvdb::Metadata::Ptr value = iter->second; + std::string valueAsString = value->str(); + std::cout << name << " = " << valueAsString << std::endl; +} +@endcode + +If the type of the metadata is not known, use the +@vdblink::Grid::operator[]() index operator@endlink to retrieve +a shared pointer to a generic @vdblink::Metadata Metadata@endlink object, +then query its type: +@code +openvdb::Metadata::Ptr metadata = grid["center"]; + +// See typenameAsString() in Types.h for a list of strings that can be +// returned by the typeName() method. +std::cout << metadata->typeName() << std::endl; // prints "vec3s" + +// One way to process metadata of arbitrary types: +if (metadata->typeName() == openvdb::StringMetadata::staticTypeName()) { + std::string s = static_cast(*metadata).value(); +} else if (metadata->typeName() == openvdb::FloatMetadata::staticTypeName()) { + float f = static_cast(*metadata).value(); +} else if (metadata->typeName() == openvdb::Vec3SMetadata::staticTypeName()) { + openvdb::Vec3S v = static_cast(*metadata).value(); +} +@endcode + +@subsection sRemovingMetadata Removing metadata +@vdblink::Grid::removeMeta() Grid::removeMeta()@endlink removes metadata +by name. If the given name is not found, the call has no effect. +@code +grid->removeMeta("vector type"); +grid->removeMeta("center"); +grid->removeMeta("vector type"); // OK (no effect) +@endcode + + + +@section sIteration Iteration + +@subsection sNodeIterator Node Iterator +A @vdblink::tree::Tree::NodeIter Tree::NodeIter@endlink visits each node in +a tree exactly once. In the following example, the tree is known to have a +depth of 4; see the @ref treeNodeIterRef "Overview" for a discussion of +why node iteration can be complicated when the tree depth is not known. +There are techniques (beyond the scope of this Cookbook) for operating +on trees of arbitrary depth. +@code +#include + +typedef openvdb::FloatGrid GridType; +typedef GridType::TreeType TreeType; +typedef TreeType::RootNodeType RootType; // level 3 RootNode +assert(RootType::LEVEL == 3); +typedef RootType::ChildNodeType Int1Type; // level 2 InternalNode +typedef Int1Type::ChildNodeType Int2Type; // level 1 InternalNode +typedef TreeType::LeafNodeType LeafType; // level 0 LeafNode + +GridType::Ptr grid = ...; + +for (TreeType::NodeIter iter = grid->tree().beginNode(); iter; ++iter) { + switch (iter.getDepth()) { + case 0: { RootType* node = NULL; iter.getNode(node); if (node) ...; break; } + case 1: { Int1Type* node = NULL; iter.getNode(node); if (node) ...; break; } + case 2: { Int2Type* node = NULL; iter.getNode(node); if (node) ...; break; } + case 3: { LeafType* node = NULL; iter.getNode(node); if (node) ...; break; } + } +} +@endcode + + +@subsection sLeafIterator Leaf Node Iterator +A @vdblink::tree::Tree::LeafIter Tree::LeafIter@endlink visits each leaf +node in a tree exactly once. +@code +#include + +typedef openvdb::FloatGrid GridType; +typedef GridType::TreeType TreeType; + +GridType::Ptr grid = ...; + +// Iterate over references to const LeafNodes. +for (TreeType::LeafCIter iter = grid->tree().cbeginLeaf(); iter; ++iter) { + const TreeType::LeafNodeType& leaf = *iter; + ... +} +// Iterate over references to non-const LeafNodes. +for (TreeType::LeafIter iter = grid->tree().beginLeaf(); iter; ++iter) { + TreeType::LeafNodeType& leaf = *iter; + ... +} +// Iterate over pointers to const LeafNodes. +for (TreeType::LeafCIter iter = grid->tree().cbeginLeaf(); iter; ++iter) { + const TreeType::LeafNodeType* leaf = iter.getLeaf(); + ... +} +// Iterate over pointers to non-const LeafNodes. +for (TreeType::LeafIter iter = grid->tree().beginLeaf(); iter; ++iter) { + TreeType::LeafNodeType* leaf = iter.getLeaf(); + ... +} +@endcode +See the @ref treeLeafIterRef "Overview" for more on leaf node iterators. + + +@subsection sValueIterator Value Iterator +A @vdblink::tree::Tree::ValueAllIter Tree::ValueIter@endlink visits each +@ref subsecValues "value" (both tile and voxel) in a tree exactly once. +Iteration can be unrestricted or can be restricted to only active values +or only inactive values. Note that tree-level value iterators (unlike +the node iterators described above) can be accessed either through a +grid's tree or directly through the grid itself, as in the following example: +@code +#include + +typedef openvdb::Vec3SGrid GridType; +typedef GridType::TreeType TreeType; + +GridType::Ptr grid = ...; + +// Iterate over all active values but don't allow them to be changed. +for (GridType::ValueOnCIter iter = grid->cbeginValueOn(); iter.test(); ++iter) { + const openvdb::Vec3f& value = *iter; + + // Print the coordinates of all voxels whose vector value has + // a length greater than 10, and print the bounding box coordinates + // of all tiles whose vector value length is greater than 10. + if (value.length() > 10.0) { + if (iter.isVoxelValue()) { + std::cout << iter.getCoord() << std::endl; + } else { + openvdb::CoordBBox bbox; + iter.getBoundingBox(bbox); + std::cout << bbox << std::endl; + } + } +} + +// Iterate over and normalize all inactive values. +for (GridType::ValueOffIter iter = grid->beginValueOff(); iter.test(); ++iter) { + openvdb::Vec3f value = *iter; + value.normalize(); + iter.setValue(value); +} + +// Normalize the (inactive) background value as well. +grid->setBackground(grid->background().unit()); +@endcode +See the @ref treeValueIterRef "Overview" for more on value iterators. + + + +@section 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 GridSampler + +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 DualGridSampler + +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 (see also +@vdblink::tools::Magnitude tools::Magnitude@endlink): +@code +#include +#include + +// Define a local function that, given an iterator pointing to a vector value +// in an input grid, sets the corresponding tile or voxel in a scalar, +// floating-point output grid to the length of the vector. +struct Local { + static inline void op( + const openvdb::Vec3SGrid::ValueOnCIter& iter, + openvdb::FloatGrid::ValueAccessor& accessor) + { + if (iter.isVoxelValue()) { // set a single voxel + accessor.setValue(iter.getCoord(), iter->length()); + } else { // fill an entire tile + openvdb::CoordBBox bbox; + iter.getBoundingBox(bbox); + accessor.getTree().fill(bbox, iter->length()); + } + } +}; + +openvdb::Vec3SGrid::ConstPtr inGrid = ...; + +// Create a scalar grid to hold the transformed values. +openvdb::FloatGrid::Ptr outGrid = openvdb::FloatGrid::create(); + +// Populate the output grid with transformed values. +openvdb::tools::transformValues(inGrid->cbeginValueOn(), *outGrid, Local::op); +@endcode + + + +@section sCombiningGrids Combining grids + +The following examples show various ways in which a pair of grids can be +combined in @ref subsecVoxSpace "index space". The assumption is that index +coordinates @ijk in both grids correspond to the same physical, @ref +subsecWorSpace "world space" location. When the grids have different +transforms, it is usually necessary to first @ref sResamplingTools "resample" +one grid into the other grid's @ref subsecVoxSpace "index space". + +@subsection sCsgTools Level set CSG operations +The level set CSG functions in tools/Composite.h operate on pairs of grids +of the same type, using sparse traversal for efficiency. These operations +always leave the second grid empty. +@code +#include +#include + +// Two grids of the same type containing level set volumes +openvdb::FloatGrid::Ptr gridA(...), gridB(...); + +// Save copies of the two grids; CSG operations always modify +// the A grid and leave the B grid empty. +openvdb::FloatGrid::ConstPtr + copyOfGridA = gridA->deepCopy(), + copyOfGridB = gridB->deepCopy(); + +// Compute the union (A u B) of the two level sets. +openvdb::tools::csgUnion(*gridA, *gridB); + +// Restore the original level sets. +gridA = copyOfGridA->deepCopy(); +gridB = copyOfGridB->deepCopy(); + +// Compute the intersection (A n B) of the two level sets. +openvdb::tools::csgIntersection(gridA, gridB); + +// Restore the original level sets. +gridA = copyOfGridA->deepCopy(); +gridB = copyOfGridB->deepCopy(); + +// Compute the difference (A / B) of the two level sets. +openvdb::tools::csgDifference(gridA, gridB); +@endcode + + +@subsection sCompTools Compositing operations +Like the @ref sCsgTools "CSG operations", the compositing functions in +tools/Composite.h operate on pairs of grids of the same type, and they +always leave the second grid empty. +@code +#include +#include + +// Two grids of the same type +openvdb::FloatGrid::Ptr gridA = ..., gridB = ...; + +// Save copies of the two grids; compositing operations always +// modify the A grid and leave the B grid empty. +openvdb::FloatGrid::ConstPtr + copyOfGridA = gridA->deepCopy(), + copyOfGridB = gridB->deepCopy(); + +// At each voxel, compute a = max(a, b). +openvdb::tools::compMax(*gridA, *gridB); + +// Restore the original grids. +gridA = copyOfGridA->deepCopy(); +gridB = copyOfGridB->deepCopy(); + +// At each voxel, compute a = min(a, b). +openvdb::tools::compMin(*gridA, *gridB); + +// Restore the original grids. +gridA = copyOfGridA->deepCopy(); +gridB = copyOfGridB->deepCopy(); + +// At each voxel, compute a = a + b. +openvdb::tools::compSum(*gridA, *gridB); + +// Restore the original grids. +gridA = copyOfGridA->deepCopy(); +gridB = copyOfGridB->deepCopy(); + +// At each voxel, compute a = a * b. +openvdb::tools::compMul(*gridA, *gridB); +@endcode + + +@subsection sCombineTools Generic combination +The @vdblink::tree::Tree::combine() Tree::combine()@endlink family of +methods apply a user-supplied operator to pairs of corresponding values +of two trees. These methods are efficient because they take into account +the sparsity of the trees; they are not multithreaded, however. + +This example uses the @vdblink::tree::Tree::combine() Tree::combine()@endlink +method to compute the difference between corresponding voxels of two +floating-point grids: +@code +#include + +// Define a local function that subtracts two floating-point values. +struct Local { + static inline void diff(const float& a, const float& b, float& result) { + result = a - b; + } +}; + +openvdb::FloatGrid::Ptr aGrid = ..., bGrid = ...; + +// Compute the difference between corresponding voxels of aGrid and bGrid +// and store the result in aGrid, leaving bGrid empty. +aGrid->tree().combine(bGrid->tree(), Local::diff); +@endcode + +Another @vdblink::tree::Tree::combine() Tree::combine()@endlink example, +this time using a functor to preserve state: +@code +#include + +// Define a functor that computes f * a + (1 - f) * b for pairs of +// floating-point values a and b. +struct Blend { + Blend(float f): frac(f) {} + inline void operator()(const float& a, const float& b, float& result) const { + result = frac * a + (1.0 - frac) * b; + } + float frac; +}; + +openvdb::FloatGrid::Ptr aGrid = ..., bGrid = ...; + +// Compute a = 0.25 * a + 0.75 * b for all corresponding voxels of +// aGrid and bGrid. Store the result in aGrid and empty bGrid. +aGrid->tree().combine(bGrid->tree(), Blend(0.25)); +@endcode + +The @vdblink::tree::Tree::combineExtended() Tree::combineExtended()@endlink +method invokes a function of the form void f(CombineArgs\& args), +where the @vdblink::CombineArgs CombineArgs@endlink object encapsulates an +@em a and a @em b value and their active states as well as a result value +and its active state. In the following example, voxel values in +floating-point @c aGrid are replaced with corresponding values from +floating-point @c bGrid (leaving @c bGrid empty) wherever the @em b values +are larger. The active states of any transferred values are preserved. +@code +#include + +// Define a local function that retrieves a and b values from a CombineArgs +// struct and then sets the result member to the maximum of a and b. +struct Local { + static inline void max(CombineArgs& args) { + if (args.b() > args.a()) { + // Transfer the B value and its active state. + args.setResult(args.b()); + args.setResultIsActive(args.bIsActive()); + } else { + // Preserve the A value and its active state. + args.setResult(args.a()); + args.setResultIsActive(args.aIsActive()); + } + } +}; + +openvdb::FloatGrid::Ptr aGrid = ..., bGrid = ...; + +aGrid->tree().combineExtended(bGrid->tree(), Local::max); +@endcode + +Like @c combine(), @vdblink::tree::Tree::combine2() Tree::combine2()@endlink +applies an operation to pairs of corresponding values of two trees. +However, @c combine2() writes the result to a third, output tree and does +not modify either of the two input trees. (As a result, it is less +space-efficient than the @c combine() method.) Here, the voxel differencing +example above is repeated using @c combine2(): +@code #include + + +struct Local { + static inline void diff(const float& a, const float& b, float& result) { + result = a - b; + } +}; + +openvdb::FloatGrid::ConstPtr aGrid = ..., bGrid = ...; +openvdb::FloatGrid::Ptr resultGrid = openvdb::FloatGrid::create(); + +// Combine aGrid and bGrid and write the result into resultGrid. +// The input grids are not modified. +resultGrid->tree().combine2(aGrid->tree(), bGrid->tree(), Local::diff); +@endcode +An @vdblink::tree::Tree::combine2Extended() extended combine2()@endlink +is also available. + + + +@section sGenericProg Generic programming + +@subsection sTypedGridMethods Calling Grid methods +A common task is to perform some operation on all of the grids in a file, +where the operation involves @vdblink::Grid Grid@endlink method calls +and the grids are of different types. +Only a handful of @c Grid methods, such as +@vdblink::Grid::activeVoxelCount() activeVoxelCount()@endlink, +are virtual and can be called through a @vdblink::GridBase GridBase@endlink +pointer; most are not, because they require knowledge of the Grid's +value type. +For example, one might want to @vdblink::tree::Tree::prune() prune()@endlink +the trees of all of the grids in a file regardless of their type, but +@c Tree::prune() is non-virtual because it accepts an optional pruning +tolerance argument whose type is the grid's value type. + +The @c processTypedGrid() function below makes this kind of task easier. +It is called with a @c GridBase pointer and a functor whose call operator +accepts a pointer to a @c Grid of arbitrary type. The call operator should +be templated on the grid type and, if necessary, overloaded for specific +grid types. + +@code +template +void processTypedGrid(openvdb::GridBase::Ptr grid, OpType& op) +{ +#define CALL_OP(GridType) \ + op.template operator()(openvdb::gridPtrCast(grid)) + + if (grid->isType()) CALL_OP(openvdb::BoolGrid); + else if (grid->isType()) CALL_OP(openvdb::FloatGrid); + else if (grid->isType()) CALL_OP(openvdb::DoubleGrid); + else if (grid->isType()) CALL_OP(openvdb::Int32Grid); + else if (grid->isType()) CALL_OP(openvdb::Int64Grid); + else if (grid->isType()) CALL_OP(openvdb::Vec3IGrid); + else if (grid->isType()) CALL_OP(openvdb::Vec3SGrid); + else if (grid->isType()) CALL_OP(openvdb::Vec3DGrid); + else if (grid->isType()) CALL_OP(openvdb::StringGrid); + +#undef CALL_OP +} +@endcode + +The following example shows how to use @c processTypedGrid() to implement +a generic pruning operation for grids of all built-in types: +@code +#include + +// Define a functor that prunes the trees of grids of arbitrary type +// with a fixed pruning tolerance. +struct PruneOp { + double tolerance; + PruneOp(double t): tolerance(t) {} + + template + void operator()(typename GridType::Ptr grid) const + { + grid->tree().prune(typename GridType::ValueType(tolerance)); + } + // Overload to handle string-valued grids + void operator()(openvdb::StringGrid::Ptr grid) const + { + grid->tree().prune(); + } +}; + +// Read all grids from a file. +openvdb::io::File file("mygrids.vdb"); +file.open(); +openvdb::GridPtrVecPtr myGrids = file.getGrids(); +file.close(); + +// Prune each grid with a tolerance of 1%. +const PruneOp pruner(/*tolerance=*/0.01); +for (openvdb::GridPtrVecIter iter = myGrids->begin(); + iter != myGrids->end(); ++iter) +{ + openvdb::GridBase::Ptr grid = *iter; + processTypedGrid(grid, pruner); +} +@endcode + +*/ diff --git a/openvdb_2_3_0_library/openvdb/doc/faq.txt b/openvdb_2_3_0_library/openvdb/doc/faq.txt new file mode 100755 index 0000000..1066307 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/doc/faq.txt @@ -0,0 +1,241 @@ +/** + +@page faq Frequently Asked Questions + +@section Contents +- @ref sWhatIsVDB +- @ref sWhatLicense +- @ref sWhatCLA +- @ref sWhyUseVDB +- @ref sVersionNumbering +- @ref sGeneralizedOctree +- @ref sLevelSet +- @ref sCustomizeVDB +- @ref sAdaptiveGrid +- @ref sMeaningOfVDB +- @ref sAccessor +- @ref sValue +- @ref sState +- @ref sVoxel +- @ref sTile +- @ref sBackground +- @ref sThreadSafe +- @ref sMaxRes +- @ref sCompareVDB +- @ref sReplaceDense +- @ref sFuture +- @ref sContribute + +@section sWhatIsVDB What is OpenVDB? +OpenVDB is a library comprising a compact hierarchical data structure and a +suite of tools for the efficient manipulation of sparse, possibly time-varying, +volumetric data discretized on a three-dimensional grid. It is based on +VDB, which was developed by Ken Museth at DreamWorks Animation, and it +offers an effectively infinite 3D index space, compact storage (both in +memory and on disk), fast data access (both random and sequential), and +a collection of algorithms specifically optimized for the data structure +for common tasks such as filtering, constructive solid geometry (CSG), +discretization of partial differential equations, voxelization of polygons, +skinning of particles, volumetric compositing and sampling. The technical +details of VDB are described in the paper +"VDB: High-Resolution Sparse Volumes with Dynamic Topology". + +@section sWhatLicense What license is OpenVDB distributed under? +OpenVDB is released under the Mozilla Public License Version 2.0, which is a +free, open source, and detailed software license developed and maintained by +the Mozilla Foundation. It is characterized as a hybridization of the modified +BSD license and GNU General Public License (GPL) that seeks to balance the +concerns of proprietary and open source developers. For more information +about this license, see the +Mozilla FAQ. + +@section sWhatCLA Is there a Contributor License Agreement for OpenVDB? +Yes, developers who wish to contribute code to be considered for inclusion +in the OpenVDB distribution must first complete this +Contributor License Agreement +and submit it to DreamWorks (directions are in the CLA). + +@section sWhyUseVDB Why should I use OpenVDB? +The typical reasons to adopt OpenVDB are if you are storing sparse +data and/or if you are performing sparse computations. OpenVDB +is also effectively unbounded, which makes it very convenient for +applications where the topology of the data is dynamic or unknown. Unlike +many existing sparse data structures, OpenVDB is also optimized for +numerical simulations, multithreaded volume compositing, near real-time +boolean CSG operations, fast random and sequential data access and +voxelization of points and polygons. + +@section sVersionNumbering What is the version numbering system for OpenVDB? +We currently use a major.minor.patch version numbering system, +and with every release of OpenVDB we change one or more of the three numbers. +The patch number is incremented for new features and bug fixes that change +neither the API nor the file format of the library nor the ABIs of the @c Grid +and @c Transform classes and their constituent classes. +The minor version is incremented for releases that change the API without +changing the @c Grid or @c Transform ABIs, and for releases that change +the file format in such a way that older files can still be read. +The major version is incremented when the @c Grid or @c Transform ABIs +change, or when the file format changes in a non-backward-compatible way +(which should be rare). +No release of OpenVDB guarantees ABI compatibility across the entire library, +but rather only for the @c Grid and @c Transform classes, and primarily +to allow third-party app plugins compiled against different versions of +the library to coexist and exchange data. + +@section sCustomizeVDB Can I customize the configuration of OpenVDB? +Yes! OpenVDB is specifically developed to be highly customizable. That is, +the user can define the sizes of all the nodes at each level of a tree as +well as the number of tree levels and the data type of values stored in the +tree. However, note that since OpenVDB makes extensive use of C++ +templating, configurations are fixed at compile time rather than at run +time. This is a fundamental design characteristic of OpenVDB, and it leads +to very high performance, thanks to techniques like inlining and template +metaprogramming. + +@section sGeneralizedOctree Is OpenVDB merely a generalized octree or N-tree? +No! While OpenVDB can conceptually be configured as a (height-balanced) +octree, it is much more than an octree or N-tree. Whereas octrees and +N-trees have fixed branching factors of respectively two and N in each +coordinate direction, OpenVDB's branching factors typically vary between +tree levels and are only limited to be powers of two. To understand why +and also learn about other unique features of OpenVDB, refer to the +paper in ACM Transactions on Graphics. + +@section sLevelSet Is OpenVDB primarily for level set applications? +No! Don't let the fact that OpenVDB can represent and operate so well on +level sets mislead you into thinking that it is limited to or even focusing +on this special type of sparse volumetric application. OpenVDB was developed +for general-purpose volumetric processing and numerical simulation, and we +have even had success using it for volumetric applications that traditionally +call for blocked or dense grids. However, the fact remains that narrow-band +level sets play an essential role in many volumetric applications, and this +explains why OpenVDB includes so many tools and algorithms specifically +for level sets. + +@section sAdaptiveGrid Is OpenVDB an adaptive grid? +Let's first stress that the term "adaptive grid" is somewhat ambiguous. Some +use it to mean a grid that can store data sampled at adaptive voxel sizes +typically derived from a so-called "refinement oracle", whereas others mean +multiple grids with different fixed voxel sizes all sampling the same data. +An example of the former is an octree and of the latter is a mipmap. Since +OpenVDB stores both data values and child nodes at each level of the tree, it +is adaptive only in the first sense, not the second. The level of adaptivity +or refinement between the tree levels is defined by the branching factors of +the nodes, which are fixed at compile time. + +@section sMeaningOfVDB What does "VDB" stand for? +Over the years VDB has been interpreted to mean different things, none of +which are very descriptive: "Voxel Data Base", "Volumetric Data Blocks", +"Volumetric Dynamic B+tree", etc. In early presentations of VDB we even used +a different name, "DB+Grid", which was abandoned to emphasize its +distinction from similarly named, but different, existing sparse data +structures like DT-Grid or DB-Grid. The simple truth is that "VDB" is just a +name. :-) + +@section sAccessor Why are there no coordinate-based access methods on the grid? +It might surprise you that the @c Grid class doesn't directly provide access +to voxels via their @ijk coordinates. Instead, the recommended procedure +is to ask the grid for a "value accessor", which is an accelerator object +that performs bottom-up tree traversal using cached information from +previous traversals. Caching greatly improves performance, but it is +inherently not thread-safe. However, a single grid may have multiple +value accessors, so each thread can safely be assigned its own value accessor. +Uncached—and therefore slower, but thread-safe—random access is possible +through a grid's tree, for example with a call like +grid.tree().getValue(ijk). + +@section sValue How and where does OpenVDB store values? +OpenVDB stores voxel data in a tree with a fixed maximum height (chosen +at compile time), with a root node that has a dynamic branching factor, +with internal nodes that have fixed branching factors, and with leaf nodes +of fixed dimensions. Values can be stored in nodes at all levels of +the tree. Values stored in leaf nodes correspond to individual voxels; +all other values correspond to "tiles" (see below). + +@section sState What are active and inactive values? +Every value in a grid has a binary state that we refer to as its +"active state". +The interpretation of this binary state is application-specific, but +typically an active (on) value is "interesting" in some sense, and an +inactive (off) value is less interesting or uninteresting. +For example, the values in a narrow-band level set are all active, and +values outside the narrow band are all inactive. +Active states of values are stored in very compact bit masks that +support sparse iteration, so visiting all active (or inactive) values +in a grid can be done very efficiently. + +@section sVoxel How are voxels represented in OpenVDB? +Values stored in nodes of type @c LeafNode (which, when they exist, +have a fixed depth), correspond to individual voxels. +These are the smallest addressable units of index space. + +@section sTile What are tiles? +Values stored in nodes of type @c RootNode or @c InternalNode correspond to +regions of index space with a constant value and active state and with +power of two dimensions. We refer to such regions as "tiles". By +construction, tiles have no child nodes. + +@section sBackground What is the background value? +A tree's background value is the value that is returned whenever +one accesses a region of index space that is not explicitly represented by +voxels or tiles in the tree. Thus, you can think of the background value +as the default value associated with an empty tree. +Note that the background value is always inactive! + +@section sThreadSafe Is OpenVDB thread-safe? +Yes and no. If you're asking if OpenVDB can safely be used in a +multithreaded application then the answer is a resounding yes. In fact, many +of the tools included in OpenVDB are multithreaded (using Intel's Threading +Building Blocks library). However, like virtually all data structures that +employ caching and allocate-on-insert, certain operations in OpenVDB are not +thread-safe in the general sense. In particular, it is not safe to retrieve +voxels from a grid while another thread is inserting voxels. +For multithreaded insertion operations we typically assign a separate grid +to each thread and then merge the grids as threads terminate. +This technique works remarkably well: because OpenVDB +is sparse and hierarchical, merging is very efficient. +For more details, please consult the @subpage codeExamples and +Appendix B in the Transactions on Graphics paper. + +@section sMaxRes Is OpenVDB unbounded? +Yes, to within available memory and the 32-bit precision of the coordinates +used to index voxels. And OpenVDB supports signed coordinates, unlike most +existing sparse data structures, so there are almost no restrictions on the +available grid resolution or the index range of OpenVDB grids. + +@section sCompareVDB How does OpenVDB compare to existing sparse data structures? +OpenVDB is very different from existing sparse data structures that you are +likely to have heard of. Foremost it is hierarchical (unlike DT-Grid and +Field3D), supports simulations and dynamic topology (unlike GigaVoxels), is +effectively unbounded (unlike Field3D), and offers fast random and sequential +voxel access. + +@section sReplaceDense Does OpenVDB replace dense grids? +This depends a lot on your application of dense grids and your configuration +of OpenVDB. Clearly, if you are storing or processing sparse data, OpenVDB +will offer a smaller memory footprint and faster (sparse) data processing. +However, even in some cases where both data and computation are dense, OpenVDB +can offer benefits like improved CPU cache performance due to its underlying +blocking and hierarchical tree structure. Exceptions are of course algorithms +that expect dense data to be laid out linearly in memory, or applications that +use very small grids. The simple truth is only a benchmark comparison can tell +you the preferred data structure, but for what it's worth it is our experience +that for the volumetric applications we encounter in production, OpenVDB is +almost always superior. + +@section sFuture What future improvements to OpenVDB are planned? +We are continuing to expand the OpenVDB toolset and improve its performance. +In the near future we plan to add OpenVDB Maya plugins, Python bindings to +more library tools, support for accelerated particle storage and lookup, level +set morphing and measures (e.g. area and volume), more rendering options, a fluid +pressure solver, improved methods for particle skinning, asynchronous I/O, +optimization by means of SIMD instructions, out-of-core support, advection of +densities (vs. level sets) and much more. We would very much like to hear your +suggestions and preferences. + +@section sContribute How can I contribute to OpenVDB? +If you have bug reports or ideas for improvements or new features, +please contact us! If you want to contribute code, please see our +License page. + +*/ diff --git a/openvdb_2_3_0_library/openvdb/doc/math.txt b/openvdb_2_3_0_library/openvdb/doc/math.txt new file mode 100755 index 0000000..5bd11c2 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/doc/math.txt @@ -0,0 +1,416 @@ +/** + +@page transformsAndMaps Transforms and Maps + +@section Contents +- @ref sTransforms + - @ref sLinearTransforms + - @ref sFrustumTransforms + - @ref sCellVsVertex + - @ref sVoxels + - @ref sStaggered +- @ref sMaps + - @ref sGettingMat4 + - @ref sCostOfMaps + - @ref sGradientAndMaps + +@section sTransforms Transforms in OpenVDB +The OpenVDB @vdblink::tree::Tree Tree@endlink is a sparse representation +of a three-dimensional array of voxels, each element of which is addressed via +discrete, three-dimensional index space coordinates, typically in the form of +a @vdblink::math::Coord Coord@endlink. +For example, the following code retrieves the floating-point value of +a voxel with index coordinates (1, 2, 3): +@code +openvdb::FloatGrid grid = ...; +openvdb::FloatGrid::Accessor accessor = grid.getAccessor(); +openvdb::Coord ijk(1,2,3); +float value = accessor.getValue(ijk); +@endcode + +A @vdblink::math::Transform Transform@endlink relates index space coordinates +to world space coordinates that give a spatial context for the discretized data.Translation from index coordinates @ijk to world space coordinates @xyz is +done with a call to the +@vdblink::math::Transform::indexToWorld() indexToWorld@endlink +method, and from world space coordinates to index space coordinates with +a call to @vdblink::math::Transform::worldToIndex() worldToIndex@endlink: +@code +// Create a linear transform that scales i, j and k by 0.1 +openvdb::math::Transform::Ptr linearTransform = + openvdb::math::Transform::createLinearTransform(0.1); + +// Compute the location in world space that is the image of (1,2,3). +// The result will be (0.1, 0.2, 0.3). +openvdb::Coord ijk(1,2,3); +openvdb::Vec3d worldSpacePoint = linearTransform->indexToWorld(ijk); + +// Compute the location in index space that is the pre-image of (0.1, 0.2, 0.3). +// The result will be (1.0, 2.0, 3.0). +openvdb::Vec3d indexSpacePoint = linearTransform->worldToIndex(worldSpacePoint); +@endcode +In the above example, there are two things to notice. +First, world space locations are specified as three-dimensional, +double-precision, floating-point vectors, and second, @c worldToIndex +does not return discrete coordinates, but rather a floating-point vector. +This is a reflection of the fact that not every location in a continuous +world space, i.e., not every @xyz, is the image of discrete integral +coordinates @ijk. + +@subsection sLinearTransforms Linear Transforms +Currently two different types of transforms are supported: linear and +frustum transforms. +A linear transform can be composed of scale, translation, rotation, and shear; +essentially those things that can be mathematically represented by an +invertible, constant-coefficient, @f$3 \times 3@f$ matrix and a translation +(mathematically, an affine map). +An essential feature of a linear transformation is that it treats all regions +of index space equally: a small box in index space about origin @ijk=(0,0,0) +is mapped (sheared, scaled, rotated, etc.) in just the same way that +a small box about any other index point is mapped. +@code +// Create a linear transform from a 4x4 matrix (identity in this example). +openvdb::math::Mat4d mat = openvdb::math::Mat4d::identity(); +openvdb::math::Transform::Ptr linearTransform = + openvdb::math::Transform::createLinearTransform(mat); + +// Rotate the transform by 90 degrees about the X axis. +// As a result the j-index will now map into the -z physical direction, +// and the k-index will map to the +y physical direction. +linearTransform->preRotate(M_PI/2, openvdb::math::X_AXIS); +@endcode + +@subsection sFrustumTransforms Frustum Transforms +The frustum transform is a nonlinear transform that treats different +index points differently. +And while the linear transform can be applied to any point in index +or world space, the frustum transform is designed to operate on a subset +of space. Specifically, it transforms a given box in index space to +a tapered box in world space that is a frustum of a rectangular pyramid. +@code +// Create the bounding box that will be mapped by the transform into +// the shape of a frustum. +// The points (0,0,0), (100,0,0), (0,50,0) and (100,50,0) will map to +// the corners of the near plane of the frustum, while the corners +// of the far plane will be the images of (0,0,120), (100,0,120), +// (0,50,120) and (100,50,120). +const openvdb::math::BBoxd bbox(/*min=*/openvdb::math::Vec3d(0,0,0), + /*max=*/openvdb::math::Vec3d(100,50,120)); + +// The far plane of the frustum will be twice as big as the near plane. +const double taper = 2; + +// The depth of the frustum will be 10 times the x-width of the near plane. +cosnt double depth = 10; + +// The x-width of the frustum in world space units +const double xWidth = 100; + +// Construct a frustum transform that results in a frustum whose +// near plane is centered on the origin in world space. +openvdb::math::Transform::Ptr frustumTransform = + openvdb::math:::Transform::createFrustumTransform( + bbox, taper, depth, xWidth); + +// The frustum shape can be rotated, scaled, translated and even +// sheared if desired. For example, the following call translates +// the frustum by 10,15,0 in world space: +frustumTransform->postTranslate(openvdb::math::Vec3d(10,15,0)); + +// Compute the world space image of a given point within +// the index space bounding box that defines the frustum. +openvdb::Coord ijk(20,10,18); +openvdb::Vec3d worldLocation = frustumTransform->indexToWorld(ijk); +@endcode + +@subsection sCellVsVertex Cell-Centered vs. Vertex-Centered Transforms +When partitioning world space with a regular grid, two popular +configurations are cell-centered and vertex-centered grids. This is really +a question of interpretation and transforms. + +The cell-centered interpretation imagines world space as divided +into discrete cells (e.g., cubes) centered on the image of the +index-space lattice points. +So the physical location @xyz that is the image (result of @c indexToWorld) +of a lattice point @ijk is the center of a cube. +In the vertex-centered approach, the images of the lattice points form the +vertices of cells, so the location @xyz would be a corner, not the +center, of a cube. + +The link between transforms and cell-centered or vertex-centered +partitioning of world space is tenuous. +It boils down to this: the “first” cell vertex often is aligned +with the origin. +In the cell-centered case, when the cells are cubes of length @f$\Delta@f$ +on a side, the transform +@f$(x,y,z) = (\Delta i + \Delta/2, \Delta j + \Delta/2, \Delta k + \Delta /2 )@f$ +will place the center of the first cube (i.e., the image of @f$(0,0,0)@f$) +at the world space location of @f$(\Delta/2, \Delta/2, \Delta/2)@f$, +and the cube will have walls coincident with the @f$x=0@f$, @f$y=0@f$ +and @f$z=0@f$ planes. +Using the OpenVDB transforms to create a so-called cell-centered transform +could be done like this: +@code +// -- Constructing a uniform, cell-centered transform -- + +// The grid spacing +const double delta = 0.1; + +// The offset to cell-center points +const openvdb::math::Vec3d offset(delta/2., delta/2., delta/2.); + +// A linear transform with the correct spacing +openvdb::math::Transform::Ptr transform = + openvdb::math:::Transform::createLinearTransform(delta); + +// Add the offset. +transform->postTranslate(offset); +@endcode +In contrast, for the vertex-centered partitions of space the first +vertex is just the image of the first lattice point @f$ijk = (0,0,0)@f$, +and the transform would lack the offset used in the cell-centered case; +so it would simply be @f$(x,y,z) = (\Delta i , \Delta j ,\Delta k)@f$ +@code +// -- Constructing a uniform, vertex-centered transform -- + +// The grid spacing +const double delta = 0.1; + +// A linear transform with the correct spacing +openvdb::math::Transform::Ptr transform = + openvdb::math:::Transform::createLinearTransform(delta); +@endcode + +@subsection sVoxels Voxel Interpretations +A similar and often related concept to cell- and vertex-centered +partitioning of world space is the idea of a voxel. +A voxel historically refers to the volumetric equivalent of a pixel +and as such implies a small region of world space. +A voxel could, for instance, be the image under transform of a vertex-centered +(or cell-centered) box in index space. +In this way the voxel can be seen as a generalization of regular grid cells. +The interpretation of data stored in a grid can be related to the concept +of a voxel but need not be. +An application might interpret the grid value indexed by @ijk as the +spatial average of a quantity in a defined world-space voxel centered +on the image of that lattice point. +But in other situations the value stored at @ijk might be a discrete sample +taken from a single point in world space. + +The @vdblink::math::Transform Transform@endlink class does include +methods such as @vdblink::math::Transform::voxelSize() voxelSize@endlink +and @vdblink::math::Transform::voxelVolume() voxelVolume@endlink that suppose +a particular interpretation of a voxel. +They assume a voxel that is the image of a vertex-centered cube in index space, +so the @c voxelSize methods return the lengths of line segments in world space +that connect vertices: +@code +openvdb::Coord ijk(0,0,0); +openvdb::Coord tmp0(1,0,0), tmp1(0,1,0), tmp2(0,0,1); + +openvdb::math::Vec3d size; +size.x() = (xform.indexToWorld(ijk + tmp0) - xform.indexToWorld(ijk)).length(); +size.y() = (xform.indexToWorld(ijk + tmp1) - xform.indexToWorld(ijk)).length(); +size.z() = (xform.indexToWorld(ijk + tmp2) - xform.indexToWorld(ijk)).length(); + +// The voxelSize() for the voxel at (0,0,0) is consistent with +// the computation above. +assert(xform.voxelSize(ijk) == size); +@endcode +In the case where the transform is linear, the result of @c voxelSize +will be independent of the actual location @ijk, but the voxel size for a +nonlinear transform such as a frustum will be spatially varying. +The related @c voxelVolume can not in general be computed from the +@c voxelSize, because there is no guarantee that a general transform +will convert a cube-shaped voxel into another cube. +As a result, the @c voxelVolume actually returns the determinant of the +transform, which will be a correct measure of volume even if the voxel +is sheared into a general parallelepiped. + +@subsection sStaggered Staggered Velocity Grids +Staggered velocity data is often used in fluid simulations, and the +relationship between data interpretation and transforms is +somewhat peculiar when using a vector grid to hold +staggered velocity data. +In this case the lattice point @ijk identifies a cell in world space +by mapping to the cell’s center, but each element of the velocity +vector is identified with a different face of the cell. +The first element of the vector is identified with the image of the +@f$(i-1/2,j,k)@f$ face, the second element with @f$(i,j-1/2,k)@f$, +and the third element with @f$(i,j,k-1/2)@f$. + + +@section sMaps Maps in OpenVDB Transforms +The actual work of a @vdblink::math::Transform Transform@endlink is performed +by an implementation object called a @vdblink::math::MapBase Map@endlink. +The map in turn is a polymorphic object whose derived types are designed to +optimally represent the most common transformations. +Specifically, the @vdblink::math::Transform Transform@endlink holds a +@vdblink::math::MapBase MapBase@endlink pointer to a derived type that +has been simplified to minimize calculations. +When the transform is updated by prepending or appending a linear operation +(e.g., with @vdblink::math::Transform::preRotate() preRotate@endlink), +the implementation map is recomputed and simplified if possible. +For example, in many level-set-oriented applications the transform +between index space and world space is simply a uniform scaling of the +index points, i.e., @f$(x,y,z) = (\Delta i, \Delta j, \Delta k)@f$, +where @f$\Delta@f$ has some world space units. +For transforms such as this, the implementation is a +@vdblink::math::UniformScaleMap UniformScaleMap@endlink. +@code +// Create a linear transform that scales i, j and k by 0.1 +openvdb::math::Transform::Ptr linearTransform = + openvdb::math::Transform::createLinearTransform(0.1); + +// Create an equivalent map. +openvdb::math::UniformScaleMap uniformScale(0.1); + +// At this time the map holds a openvdb::math::UniformScaleMap. +assert(linearTransform->mapType() == openvdb::math::UniformScaleMap::type()); + +openvdb::Coord ijk(1,2,3); + +// Applying the transform... +openvdb::math::Vec3d transformResult = linearTransform->indexToWorld(ijk); + +// ...is equivalent to applying the map. +openvdb::math::Vec3d mapResult = uniformScale.applyMap(ijk); + +assert(mapResult == transformResult); +@endcode + +There are specialized maps for a variety of common linear transforms: +pure translation (@vdblink::math::TranslationMap TranslationMap@endlink), +uniform scale (@vdblink::math::UniformScaleMap UniformScaleMap@endlink), +uniform scale and translation +(@vdblink::math::UniformScaleTranslateMap UniformScaleTranslateMap@endlink), +non-uniform scale (@vdblink::math::ScaleMap ScaleMap@endlink), and +non-uniform scale and translation +(@vdblink::math::ScaleTranslateMap ScaleTranslateMap@endlink). +A general affine map (@vdblink::math::AffineMap AffineMap@endlink) is used +for all other cases (those that include non-degenerate rotation or shear). + +@subsection sGettingMat4 An Equivalent Matrix Representation +The matrix representation used within OpenVDB adheres to the minority +convention of right-multiplication of the matrix against a vector: +@code +// Example matrix transform that scales, next translates, +// and finally rotates an incoming vector +openvdb::math::Mat4d transform = openvdb::math::Mat4d::identity(); +transform.preScale(openvdb::math::Vec3d(2,3,2)); +transform.postTranslate(openvdb::math::Vec3d(1,0,0)); +transform.postRotate(openvdb::math::X_AXIS, M_PI/3.0); + +// Location of a point in index space +openvdb::math::Vec3d indexSpace(1,2,3); + +// Apply the transform by right-multiplying the matrix. +openvdb::math::Vec3d worldSpace = indexSpace * transform; +@endcode +Any linear map can produce an equivalent +@vdblink::math::AffineMap AffineMap@endlink, which in turn can produce +an equivalent @f$4 \times 4@f$ matrix. +Starting with a linear transform one can produce a consistent matrix as follows: +@code +openvdb::math::Mat4d matrix; +if (transform->isLinear()) { + // Get the equivalent 4x4 matrix. + matrix = transform->getBaseMap()->getAffineMap()->getMat4(); +} +@endcode + +This could be used as an intermediate form when constructing new linear +transforms by combining old ones. +@code +// Get the matrix equivalent to linearTransformA. +openvdb::math::Mat4d matrixA = + linearTransformA->getBaseMap()->getAffineMap()->getMat4(); + +// Invert the matrix equivalent to linearTransformB. +openvdb::math::Mat4d matrixBinv = + (linearTransformB->getBaseMap()->getAffineMap()->getMat4()).inverse(); + +// Create a new transform that maps the index space of linearTransformA +// to the index space of linearTransformB. +openvdb::math::Transform::Ptr linearTransformAtoB = + openvdb::math::Trasform::createLinearTransform(matrixA * matrixBinv); +@endcode +Notice that in the above example, the internal representation used by +the transform will be simplified if possible to use one of the various +map types. + +@subsection sCostOfMaps Working Directly with Maps +Accessing a transform’s map through virtual function calls +introduces some overhead and precludes compile-time optimizations +such as inlining. +For this reason, the more computationally intensive OpenVDB tools are +templated to work directly with any underlying map. +This allows the tools direct access to the map’s simplified +representation and gives the compiler a free hand to inline. + +Maps themselves know nothing of index space and world space, but are +simply functions @f$x_{range} = f(x_{domain})@f$ that map 3-vectors from +one space (the domain) to another (the range), or from the range back to +the domain (@f$x_{domain} = f^{-1}(x_{range})@f$), by means of the methods +@vdblink::math::MapBase::applyMap() applyMap@endlink and +@vdblink::math::MapBase::applyInverseMap() applyInverseMap@endlink. + +@code +// Create a simple uniform scale map that scales by 10. +openvdb::math::UniformScaleMap usm(10.0); + +// A point in the domain +openvdb::math::Vec3d domainPoint(0,1,3); + +// The resulting range point +openvdb::math::Vec3d rangePoint = usm.applyMap(domainPoint); + +// The map is inverted thus: +assert(domainPoint == usm.applyInverseMap(rangePoint)); +@endcode + +In many tools, the actual map type is not known a priori and +must be deduced at runtime prior to calling the appropriate +map-specific or map-templated code. The type of map currently being +used by a transform can be determined using the +@vdblink::math::Transform::mapType() mapType@endlink method: +@code +// Test for a uniform scale map. +if (transform->mapType() == openvdb::math::UniformScaleMap::type()) { + + // This call would return a null pointer in the case of a map type mismatch. + openvdb::math::UniformScaleMap::ConstPtr usm = + transform->map(); + + // Call a function that accepts UniformScaleMaps. + dofoo(*usm) +} +@endcode + +To simplify this process, the function +@vdblink::math::processTypedMap processTypedMap@endlink has been provided. +It accepts a transform and a functor templated on the map type. + +@subsection sGradientAndMaps Maps and Mathematical Operations +In addition to supporting the mapping of a point from one space to another, +maps also support mapping of local gradients. +This results from use of the chain rule of calculus in computing the +Jacobian of the map. +Essentially, the gradient calculated in the domain of a map can be converted +to the gradient in the range of the map, allowing one to compute a gradient +in index space and then transform it to a gradient in world space. +@code +// Compute the gradient at a point in index space in a +// floating-point grid using the second-order central difference. +openvdb::FloatGrid grid = ...; +openvdb::Coord ijk(2,3,5) +openvdb::math::Vec3d isGrad = + openvdb::math::ISGradient::result(grid, ijk); + +// Apply the inverse Jacobian transform to convert the result to the +// gradient in the world space defined by a map that scales index space +// to create voxels of size 0.1 x 0.2 x 0.1 +openvdb::math::ScaleMap scalemap(0.1, 0.2, 0.1); +openvdb::math::Vec3d wsGrad = scalemap.applyIJT(isGrad); +@endcode + +*/ diff --git a/openvdb_2_3_0_library/openvdb/doc/python.txt b/openvdb_2_3_0_library/openvdb/doc/python.txt new file mode 100755 index 0000000..333d448 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/doc/python.txt @@ -0,0 +1,540 @@ +/** + +@page python Using OpenVDB in Python + +This section describes the OpenVDB Python module and includes Python code +snippets and some complete programs that illustrate how to perform common tasks. +(An API reference is also available, +if Epydoc is installed.) +As of OpenVDB 2.0, the Python module exposes most of the functionality +of the C++ @vdblink::Grid Grid@endlink class, including I/O, metadata +management, voxel access and iteration, but almost none of the many +@ref secToolUtils "tools". +We expect to add support for tools in forthcoming releases. + +The Python module supports a fixed set of grid types. +If the symbol @c PY_OPENVDB_WRAP_ALL_GRID_TYPES is defined at compile time, +most of the grid types declared in openvdb.h are accessible in Python, +otherwise only @b FloatGrid, @b BoolGrid and @b Vec3SGrid are accessible. +To add support for grids with other value types or configurations, +search for @c PY_OPENVDB_WRAP_ALL_GRID_TYPES in the module source code, +update the code as appropriate and recompile the module. +(It is possible that this process will be streamlined in the future +with a plugin mechanism.) +Note however that adding grid types can significantly increase the time +and memory needed to compile the module and can significantly increase +the size of the resulting executable. +In addition, grids of custom types that are saved to .vdb files +or pickled will not be readable by clients using the standard version +of the module. + +Also note that the @vdblink::tree::Tree Tree@endlink class is not exposed +in Python. +Much of its functionality is either available through the +@vdblink::Grid Grid@endlink or is too low-level to be generally useful +in Python. +Although trees are not accessible in Python, they can of course be operated on +indirectly. +Of note are the grid methods @b copy, which returns a new grid that shares +its tree with the original grid, @b deepCopy, which returns a new grid +that owns its own tree, and @b sharesWith, which reports whether two grids +share a tree. + + + +@section Contents +- @ref sPyBasics +- @ref sPyHandlingMetadata +- @ref sPyAccessors +- @ref sPyIteration +- @ref sPyNumPy +- @ref sPyCppAPI + + + +@section sPyBasics Getting started + +The following example is a complete program that illustrates some of the +basic steps to create grids and write them to disk: +@code{.py} +import pyopenvdb as vdb + +# A grid comprises a sparse tree representation of voxel data, +# user-supplied metadata and a voxel space to world space transform, +# which defaults to the identity transform. +# A FloatGrid stores one single-precision floating point value per voxel. +# Other grid types include BoolGrid and Vec3SGrid. The module-level +# attribute pyopenvdb.GridTypes gives the complete list. +cube = vdb.FloatGrid() +cube.fill(min=(100, 100, 100), max=(199, 199, 199), value=1.0) + +# Name the grid "cube". +cube.name = 'cube' + +# Populate another FloatGrid with a sparse, narrow-band level set +# representation of a sphere with radius 50 voxels, located at +# (1.5, 2, 3) in index space. +sphere = vdb.createLevelSetSphere(radius=50, center=(1.5, 2, 3)) + +# Associate some metadata with the grid. +sphere['radius'] = 50.0 + +# Associate a scaling transform with the grid that sets the voxel size +# to 0.5 units in world space. +sphere.transform = vdb.createLinearTransform(voxelSize=0.5) + +# Name the grid "sphere". +sphere.name = 'sphere' + +# Write both grids to a VDB file. +vdb.write('mygrids.vdb', grids=[cube, sphere]) +@endcode + +This example shows how to read grids from files, and some ways to modify +grids: +@code{.py} +import pyopenvdb as vdb + +# Read a .vdb file and return a list of grids populated with +# their metadata and transforms, but not their trees. +filename = 'mygrids.vdb' +grids = vdb.readAllGridMetadata(filename) + +# Look for and read in a level-set grid that has certain metadata. +sphere = None +for grid in grids: + if (grid.gridClass == vdb.GridClass.LEVEL_SET and 'radius' in grid + and grid['radius'] > 10.0): + sphere = vdb.read(filename, grid.name) + else: + print 'skipping grid', grid.name + +if sphere: + # Convert the level set sphere to a narrow-band fog volume, in which + # interior voxels have value 1, exterior voxels have value 0, and + # narrow-band voxels have values varying linearly from 0 to 1. + + outside = sphere.background + width = 2.0 * outside + + # Visit and update all of the grid's active values, which correspond to + # voxels in the narrow band. + for iter in sphere.iterOnValues(): + dist = iter.value + iter.value = (outside - dist) / width + + # Visit all of the grid's inactive tile and voxel values and update + # the values that correspond to the interior region. + for iter in sphere.iterOffValues(): + if iter.value < 0.0: + iter.value = 1.0 + iter.active = False + + # Set exterior voxels to 0. + sphere.background = 0.0 + + sphere.gridClass = vdb.GridClass.FOG_VOLUME +@endcode + + + +@section sPyHandlingMetadata Handling metadata + +Metadata of various types (string, bool, int, float, and 2- and 3-element +sequences of ints or floats) can be attached both to individual grids +and to files on disk, either by supplying a Python dictionary of +(name, value) pairs or, in the case of grids, through +a dictionary-like interface. + +Add (name, value) metadata pairs to a grid as you would to a dictionary. +A new value overwrites an existing value if the name matches an existing name. +@code{.py} +>>> import pyopenvdb as vdb + +>>> grid = vdb.Vec3SGrid() + +>>> grid['vector'] = 'gradient' +>>> grid['radius'] = 50.0 +>>> grid['center'] = (10, 15, 10) + +>>> grid.metadata +{'vector': 'gradient', 'radius': 50.0, 'center': (10, 15, 10)} + +>>> grid['radius'] +50.0 + +>>> 'radius' in grid, 'misc' in grid +(True, False) + +# OK to overwrite an existing value with a value of another type: +>>> grid['center'] = 0.0 + +# A 4-element sequence is not a supported metadata value type: +>>> grid['center'] = (0, 0, 0, 0) + File "", line 1, in +TypeError: metadata value "(0, 0, 0, 0)" of type tuple is not allowed + +# Metadata names must be strings: +>>> grid[0] = (10.5, 15, 30) + File "", line 1, in +TypeError: expected str, found int as argument 1 to __setitem__() +@endcode +Alternatively, replace all or some of a grid’s metadata by supplying +a (name, value) dictionary: +@code{.py} +>>> metadata = { +... 'vector': 'gradient', +... 'radius': 50.0, +... 'center': (10, 15, 10) +... } + +# Replace all of the grid's metadata. +>>> grid.metadata = metadata + +>>> metadata = { +... 'center': [10.5, 15, 30], +... 'scale': 3.14159 +... } + +# Overwrite "center" and add "scale": +>>> grid.updateMetadata(metadata) +@endcode + +Iterate over a grid’s metadata as you would over a dictionary: +@code{.py} +>>> for key in grid: +... print '%s = %s' % (key, grid[key]) +... +vector = gradient +radius = 50.0 +scale = 3.14159 +center = (10.5, 15.0, 30.0) +@endcode + +Removing metadata is also straightforward: +@code{.py} +>>> del grid['vector'] +>>> del grid['center'] +>>> del grid['vector'] # error: already removed + File "", line 1, in +KeyError: 'vector' + +>>> grid.metadata = {} # remove all metadata +@endcode + +Some grid metadata is exposed in the form of properties, either because +it might be frequently accessed (a grid’s name, for example) +or because its allowed values are somehow restricted: +@code{.py} +>>> grid = vdb.createLevelSetSphere(radius=10.0) +>>> grid.metadata +{'class': 'level set'} + +>>> grid.gridClass = vdb.GridClass.FOG_VOLUME +>>> grid.metadata +{'class': 'fog volume'} + +# The gridClass property requires a string value: +>>> grid.gridClass = 123 + File "", line 1, in +TypeError: expected str, found int as argument 1 to setGridClass() + +# Only certain strings are recognized; see pyopenvdb.GridClass +# for the complete list. +>>> grid.gridClass = 'Hello, world.' +>>> grid.metadata +{'class': 'unknown'} + +>>> grid.metadata = {} +>>> grid.vectorType = vdb.VectorType.COVARIANT +>>> grid.metadata +{'vector_type': 'covariant'} + +>>> grid.name = 'sphere' +>>> grid.creator = 'Python' +>>> grid.metadata +{'vector_type': 'covariant', 'name': 'sphere', 'creator': 'Python'} +@endcode +Setting these properties to @c None removes the corresponding metadata, +but the properties retain default values: +@code{.py} +>>> grid.creator = grid.vectorType = None +>>> grid.metadata +{'name': 'sphere'} + +>>> grid.creator, grid.vectorType +('', 'invariant') +@endcode + +Metadata can be associated with a .vdb file at the time the file +is written, by supplying a (name, value) dictionary +as the optional @c metadata argument to the @b write function: +@code{.py} +>>> metadata = { +... 'creator': 'Python', +... 'time': '11:05:00' +... } +>>> vdb.write('mygrids.vdb', grids=grid, metadata=metadata) +@endcode +File-level metadata can be retrieved with either the @b readMetadata +function or the @b readAll function: +@code{.py} +>>> metadata = vdb.readMetadata('mygrids.vdb') +>>> metadata +{'creator': 'Python', 'time': '11:05:00'} + +>>> grids, metadata = vdb.readAll('mygrids.vdb') +>>> metadata +{'creator': 'Python', 'time': '11:05:00'} +>>> [grid.name for grid in grids] +['sphere'] +@endcode + + + +@section sPyAccessors Voxel access + +Grids provide read-only and read/write accessors for voxel lookup via @ijk +index coordinates. +Accessors store references to their parent grids, so a grid will not +be deleted while it has accessors in use. + +@code{.py} +>>> import pyopenvdb as vdb + +# Read two grids from a file. +>>> grids, metadata = vdb.readAll('smoke2.vdb') +>>> [grid.name for grid in grids] +['density', 'v'] + +# Get read/write accessors to the two grids. +>>> dAccessor = grids[0].getAccessor() +>>> vAccessor = grids[1].getAccessor() + +>>> ijk = (100, 103, 101) + +>>> dAccessor.probeValue(ijk) +(0.17614534497261047, True) +# Change the value of a voxel. +>>> dAccessor.setValueOn(ijk, 0.125) +>>> dAccessor.probeValue(ijk) +(0.125, True) + +>>> vAccessor.probeValue(ijk) +((-2.90625, 9.84375, 0.84228515625), True) +# Change the active state of a voxel. +>>> vAccessor.setActiveState(ijk, False) +>>> vAccessor.probeValue(ijk) +((-2.90625, 9.84375, 0.84228515625), False) + +# Get a read-only accessor to one of the grids. +>>> dAccessor = grids[0].getConstAccessor() +>>> dAccessor.setActiveState(ijk, False) + File "", line 1, in +TypeError: accessor is read-only + +# Delete the accessors once they are no longer needed, +# so that the grids can be garbage-collected. +>>> del dAccessor, vAccessor +@endcode + + + +@section sPyIteration Iteration + +Grids provide read-only and read/write iterators over their values. +Iteration is over sequences of value objects (BoolGrid.Values, +FloatGrid.Values, etc.) that expose properties such as the number +of voxels spanned by a value (one, for a voxel value, more than one +for a tile value), its coordinates and its active state. +Value objects returned by read-only iterators are immutable; those +returned by read/write iterators permit assignment to their active state +and value properties, which modifies the underlying grid. +Value objects store references to their parent grids, so a grid will not +be deleted while one of its value objects is in use. + +@code{.py} +>>> import pyopenvdb as vdb + +>>> grid = vdb.read('smoke2.vdb', gridname='v') +>>> grid.__class__.__name__ +'Vec3SGrid' + +# Iterate over inactive values and print the coordinates of the first +# five voxel values and the bounding boxes of the first five tile values. +>>> voxels = tiles = 0 +... N = 5 +... for item in grid.citerOffValues(): # read-only iterator +... if voxels == N and tiles == N: +... break +... if item.count == 1: +... if voxels < N: +... voxels += 1 +... print 'voxel', item.min +... else: +... if tiles < N: +... tiles += 1 +... print 'tile', item.min, item.max +... +tile (0, 0, 0) (7, 7, 7) +tile (0, 0, 8) (7, 7, 15) +tile (0, 0, 16) (7, 7, 23) +tile (0, 0, 24) (7, 7, 31) +tile (0, 0, 32) (7, 7, 39) +voxel (40, 96, 88) +voxel (40, 96, 89) +voxel (40, 96, 90) +voxel (40, 96, 91) +voxel (40, 96, 92) + +# Iterate over and normalize all active values. +>>> from math import sqrt +>>> for item in grid.iterOnValues(): # read/write iterator +... vector = item.value +... magnitude = sqrt(sum(x * x for x in vector)) +... item.value = [x / magnitude for x in vector] +... +@endcode + +For some operations, it might be more convenient to use one of +the grid methods @b mapOn, @b mapOff or @b mapAll. +These methods iterate over a grid’s tiles and voxels +(active, inactive or both, respectively) and replace each value x +with f(x), where f is a callable object. +These methods are not multithreaded. +@code{.py} +>>> import pyopenvdb as vdb +>>> from math import sqrt + +>>> grid = vdb.read('smoke2.vdb', gridname='v') + +>>> def normalize(vector): +... magnitude = sqrt(sum(x * x for x in vector)) +... return [x / magnitude for x in vector] +... +>>> grid.mapOn(normalize) +@endcode + +Similarly, the @b combine method iterates over corresponding pairs of values +(tile and voxel) of two grids @e A and @e B of the same type +(FloatGrid, Vec3SGrid, etc.), replacing values in @e A +with f(a, b), where f is a callable object. +This operation assumes that index coordinates @ijk in both grids correspond +to the same physical, world space location. +Also, the operation always leaves grid @e B empty. +@code{.py} +>>> import pyopenvdb as vdb + +>>> density = vdb.read('smoke2.vdb', gridname='density') +>>> density.__class__.__name__ +'FloatGrid' + +>>> sphere = vdb.createLevelSetSphere(radius=50.0, center=(100, 300, 100)) + +>>> density.combine(sphere, lambda a, b: min(a, b)) +@endcode +For now, @b combine operates only on tile and voxel values, +not on their active states or other attributes. + + + +@section sPyNumPy Working with NumPy arrays + +Large data sets are often handled in Python using +NumPy. +The OpenVDB Python module can optionally be compiled with NumPy support. +With NumPy enabled, the @b copyFromArray and @b copyToArray grid methods +can be used to exchange data efficiently between scalar-valued grids +and three-dimensional NumPy arrays and between vector-valued grids and +four-dimensional NumPy arrays. +@code{.py} +>>> import pyopenvdb as vdb +>>> import numpy + +>>> array = numpy.random.rand(200, 200, 200) +>>> array.dtype +dtype('float64') + +# Copy values from a three-dimensional array of doubles +# into a grid of floats. +>>> grid = vdb.FloatGrid() +>>> grid.copyFromArray(array) +>>> grid.activeVoxelCount() == array.size +True +>>> grid.evalActiveVoxelBoundingBox() +((0, 0, 0), (199, 199, 199)) + +# Copy values from a four-dimensional array of ints +# into a grid of float vectors. +>>> vecarray = numpy.ndarray((60, 70, 80, 3), int) +>>> vecarray.fill(42) +>>> vecgrid = vdb.Vec3SGrid() +>>> vecgrid.copyFromArray(vecarray) +>>> vecgrid.activeVoxelCount() == 60 * 70 * 80 +True +>>> vecgrid.evalActiveVoxelBoundingBox() +((0, 0, 0), (59, 69, 79)) +@endcode + +When copying from a NumPy array, values in the array that are equal to +the destination grid’s background value (or close to it, if the +@c tolerance argument to @b copyFromArray is nonzero) are set to the +background value and are marked inactive. +All other values are marked active. +@code{.py} +>>> grid.clear() +>>> grid.copyFromArray(array, tolerance=0.2) +>>> print '%d%% of copied voxels are active' % ( +... round(100.0 * grid.activeVoxelCount() / array.size)) +80% of copied voxels are active +@endcode + +The optional @c ijk argument specifies the index coordinates of the voxel +in the destination grid into which to start copying values. +That is, array index (0, 0, 0) maps to voxel @ijk. +@code{.py} +>>> grid.clear() +>>> grid.copyFromArray(array, ijk=(-1, 2, 3)) +>>> grid.evalActiveVoxelBoundingBox() +((-1, 2, 3), (198, 201, 202)) +@endcode + +The @b copyToArray method also accepts an @c ijk argument. +It specifies the index coordinates of the voxel to be copied to array +index (0, 0, 0). +@code{.py} +>>> grid = vdb.createLevelSetSphere(radius=10.0) +>>> array = numpy.ndarray((40, 40, 40), int) +>>> array.fill(0) +# Copy values from a grid of floats into +# a three-dimensional array of ints. +>>> grid.copyToArray(array, ijk=(-15, -20, -35)) +>>> array[15, 20] +array([ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, + -3, -3, -3, -3, -3, -3, -3, -3, -3, -3]) +@endcode +@b copyToArray has no @c tolerance argument, because there is no distinction +between active and inactive values in the destination array. + + + +@section sPyCppAPI C++ glue routines + +Python objects of type @b FloatGrid, @b Vec3SGrid, etc. are backed by +C structs that “inherit” from @c PyObject. +The OpenVDB Python extension module includes public functions that you can +call in your own extension modules to convert between +@vdblink::Grid openvdb::Grids@endlink and PyObjects. +See the pyopenvdb.h reference for a description of these functions +and a usage example. + +Your extension module might need to link against the OpenVDB extension module +in order to access these functions. +On UNIX systems, it might also be necessary to specify the @c RTLD_GLOBAL +flag when importing the OpenVDB module, to allow its symbols to be shared +across modules. +See @b setdlopenflags in the Python @b sys module for one way to do this. + +*/ diff --git a/openvdb_2_3_0_library/openvdb/doxygen-config b/openvdb_2_3_0_library/openvdb/doxygen-config new file mode 100755 index 0000000..b24cfa7 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/doxygen-config @@ -0,0 +1,1242 @@ +# Doxyfile 1.4.7 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = OpenVDB + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 2.3.0 + +ALIASES += vdbnamespace="openvdb::v2_3_0" +PREDEFINED = OPENVDB_VERSION_NAME=v2_3_0 + +PREDEFINED += __declspec(x):= __attribute__(x):= +#EXPAND_AS_DEFINED = \ +# OPENVDB_API \ +# OPENVDB_HOUDINI_API \ +# OPENVDB_EXPORT \ +# OPENVDB_IMPORT \ +# OPENVDB_DEPRECATED +# OPENVDB_STATIC_SPECIALIZATION + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = obj/doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, +# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, +# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, +# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, +# Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES += ijk="@f$(i,j,k)@f$" +ALIASES += xyz="@f$(x,y,z)@f$" +ALIASES += const="const" +# Use this command to create a link to an OpenVDB class, function, etc. +# Usage is "@vdblink:: @endlink", where is a fully +# namespace-qualified symbol minus the openvdb and version number namespaces +# and is the text of the link. +# Example: @vdblink::tree::RootNode root node@endlink +ALIASES += vdblink="@link @vdbnamespace" + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to +# include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = YES + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = YES + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = NO + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from the +# version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . \ + io \ + math \ + metadata \ + python/pyopenvdb.h \ + tools \ + tree \ + util \ + doc/doc.txt \ + doc/faq.txt \ + doc/math.txt \ + doc/changes.txt \ + doc/codingstyle.txt \ + doc/api_0_98_0.txt \ + doc/examplecode.txt \ + doc/python.txt + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py + +FILE_PATTERNS = *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentstion. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +#PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = NO + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a caller dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that a graph may be further truncated if the graph's +# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH +# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), +# the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, which results in a white background. +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/openvdb_2_3_0_library/openvdb/io/Archive.cc b/openvdb_2_3_0_library/openvdb/io/Archive.cc new file mode 100755 index 0000000..c05ed0f --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/Archive.cc @@ -0,0 +1,766 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Archive.h" + +#include // for std::find_if() +#include // for std::memcpy() +#include +#include +#include +#include +#include +#include +#include +#include +#include "GridDescriptor.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +// Indices into a stream's internal extensible array of values used by readers and writers +struct StreamState +{ + static const long MAGIC_NUMBER; + + StreamState(); + ~StreamState(); + + int magicNumber; + int fileVersion; + int libraryMajorVersion; + int libraryMinorVersion; + int dataCompression; + int writeGridStatsMetadata; + int gridBackground; + int gridClass; +} +sStreamState; + +const long StreamState::MAGIC_NUMBER = + long((uint64_t(OPENVDB_MAGIC) << 32) | (uint64_t(OPENVDB_MAGIC))); + +const uint32_t Archive::DEFAULT_COMPRESSION_FLAGS = (COMPRESS_ZIP | COMPRESS_ACTIVE_MASK); + + +//////////////////////////////////////// + + +StreamState::StreamState(): magicNumber(std::ios_base::xalloc()) +{ + // Having reserved an entry (the one at index magicNumber) in the extensible array + // associated with every stream, store a magic number at that location in the + // array belonging to the cout stream. + std::cout.iword(magicNumber) = MAGIC_NUMBER; + std::cout.pword(magicNumber) = this; + + // Search for a lower-numbered entry in cout's array that already contains the magic number. + /// @todo This assumes that the indices returned by xalloc() increase monotonically. + int existingArray = -1; + for (int i = 0; i < magicNumber; ++i) { + if (std::cout.iword(i) == MAGIC_NUMBER) { + existingArray = i; + break; + } + } + + if (existingArray >= 0 && std::cout.pword(existingArray) != NULL) { + // If a lower-numbered entry was found to contain the magic number, + // a coexisting version of this library must have registered it. + // In that case, the corresponding pointer should point to an existing + // StreamState struct. Copy the other array indices from that StreamState + // into this one, so as to share state with the other library. + const StreamState& other = + *static_cast(std::cout.pword(existingArray)); + fileVersion = other.fileVersion; + libraryMajorVersion = other.libraryMajorVersion; + libraryMinorVersion = other.libraryMinorVersion; + dataCompression = other.dataCompression; + writeGridStatsMetadata = other.writeGridStatsMetadata; + gridBackground = other.gridBackground; + gridClass = other.gridClass; + } else { + // Reserve storage for per-stream file format and library version numbers + // and other values of use to readers and writers. Each of the following + // values is an index into the extensible arrays associated with all streams. + // The indices are common to all streams, but the values stored at those indices + // are unique to each stream. + fileVersion = std::ios_base::xalloc(); + libraryMajorVersion = std::ios_base::xalloc(); + libraryMinorVersion = std::ios_base::xalloc(); + dataCompression = std::ios_base::xalloc(); + writeGridStatsMetadata = std::ios_base::xalloc(); + gridBackground = std::ios_base::xalloc(); + gridClass = std::ios_base::xalloc(); + } +} + + +StreamState::~StreamState() +{ + // Ensure that this StreamState struct can no longer be accessed. + std::cout.iword(magicNumber) = 0; + std::cout.pword(magicNumber) = NULL; +} + + +//////////////////////////////////////// + + +Archive::Archive(): + mFileVersion(OPENVDB_FILE_VERSION), + mUuid(boost::uuids::nil_uuid()), + mInputHasGridOffsets(false), + mEnableInstancing(true), + mCompression(DEFAULT_COMPRESSION_FLAGS), + mEnableGridStats(true) +{ + mLibraryVersion.first = OPENVDB_LIBRARY_MAJOR_VERSION; + mLibraryVersion.second = OPENVDB_LIBRARY_MINOR_VERSION; +} + + +Archive::~Archive() +{ +} + + +boost::shared_ptr +Archive::copy() const +{ + return boost::shared_ptr(new Archive(*this)); +} + + +//////////////////////////////////////// + + +std::string +Archive::getUniqueTag() const +{ + /// @todo Once versions of Boost < 1.44.0 are no longer in use, + /// this can be replaced with "return boost::uuids::to_string(mUuid);". + std::ostringstream ostr; + ostr << mUuid; + return ostr.str(); +} + + +bool +Archive::isIdentical(const std::string& uuidStr) const +{ + return uuidStr == getUniqueTag(); +} + + +//////////////////////////////////////// + + +uint32_t +getFormatVersion(std::istream& is) +{ + return static_cast(is.iword(sStreamState.fileVersion)); +} + + +void +Archive::setFormatVersion(std::istream& is) +{ + is.iword(sStreamState.fileVersion) = mFileVersion; +} + + +VersionId +getLibraryVersion(std::istream& is) +{ + VersionId version; + version.first = static_cast(is.iword(sStreamState.libraryMajorVersion)); + version.second = static_cast(is.iword(sStreamState.libraryMinorVersion)); + return version; +} + + +void +Archive::setLibraryVersion(std::istream& is) +{ + is.iword(sStreamState.libraryMajorVersion) = mLibraryVersion.first; + is.iword(sStreamState.libraryMinorVersion) = mLibraryVersion.second; +} + + +std::string +getVersion(std::istream& is) +{ + VersionId version = getLibraryVersion(is); + std::ostringstream ostr; + ostr << version.first << "." << version.second << "/" << getFormatVersion(is); + return ostr.str(); +} + + +void +setCurrentVersion(std::istream& is) +{ + is.iword(sStreamState.fileVersion) = OPENVDB_FILE_VERSION; + is.iword(sStreamState.libraryMajorVersion) = OPENVDB_LIBRARY_MAJOR_VERSION; + is.iword(sStreamState.libraryMinorVersion) = OPENVDB_LIBRARY_MINOR_VERSION; +} + + +void +setVersion(std::ios_base& strm, const VersionId& libraryVersion, uint32_t fileVersion) +{ + strm.iword(sStreamState.fileVersion) = fileVersion; + strm.iword(sStreamState.libraryMajorVersion) = libraryVersion.first; + strm.iword(sStreamState.libraryMinorVersion) = libraryVersion.second; +} + + +std::string +Archive::version() const +{ + std::ostringstream ostr; + ostr << mLibraryVersion.first << "." << mLibraryVersion.second << "/" << mFileVersion; + return ostr.str(); +} + + +//////////////////////////////////////// + + +uint32_t +getDataCompression(std::ios_base& strm) +{ + return uint32_t(strm.iword(sStreamState.dataCompression)); +} + + +void +setDataCompression(std::ios_base& strm, uint32_t compression) +{ + strm.iword(sStreamState.dataCompression) = compression; +} + + +void +Archive::setDataCompression(std::istream& is) +{ + io::setDataCompression(is, mCompression); +} + + +bool +Archive::isCompressionEnabled() const +{ + return (mCompression & COMPRESS_ZIP); +} + + +void +Archive::setCompressionEnabled(bool b) +{ + if (b) mCompression |= COMPRESS_ZIP; + else mCompression &= ~COMPRESS_ZIP; +} + + +void +Archive::setGridCompression(std::ostream& os, const GridBase& grid) const +{ + // Start with the options that are enabled globally for this archive. + uint32_t compression = compressionFlags(); + + // Disable options that are inappropriate for the given grid. + switch (grid.getGridClass()) { + case GRID_LEVEL_SET: + case GRID_FOG_VOLUME: + // Zip compression is not used on level sets or fog volumes. + compression = compression & ~COMPRESS_ZIP; + break; + default: + break; + } + io::setDataCompression(os, compression); + + os.write(reinterpret_cast(&compression), sizeof(uint32_t)); +} + + +void +Archive::readGridCompression(std::istream& is) +{ + if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { + uint32_t compression = COMPRESS_NONE; + is.read(reinterpret_cast(&compression), sizeof(uint32_t)); + io::setDataCompression(is, compression); + } +} + + +//////////////////////////////////////// + + +bool +getWriteGridStatsMetadata(std::ostream& os) +{ + return os.iword(sStreamState.writeGridStatsMetadata) != 0; +} + + +void +Archive::setWriteGridStatsMetadata(std::ostream& os) +{ + os.iword(sStreamState.writeGridStatsMetadata) = mEnableGridStats; +} + + +//////////////////////////////////////// + + +uint32_t +getGridClass(std::ios_base& strm) +{ + const uint32_t val = strm.iword(sStreamState.gridClass); + if (val >= NUM_GRID_CLASSES) return GRID_UNKNOWN; + return val; +} + + +void +setGridClass(std::ios_base& strm, uint32_t cls) +{ + strm.iword(sStreamState.gridClass) = long(cls); +} + + +const void* +getGridBackgroundValuePtr(std::ios_base& strm) +{ + return strm.pword(sStreamState.gridBackground); +} + + +void +setGridBackgroundValuePtr(std::ios_base& strm, const void* background) +{ + strm.pword(sStreamState.gridBackground) = const_cast(background); +} + + +//////////////////////////////////////// + + +bool +Archive::readHeader(std::istream& is) +{ + // 1) Read the magic number for VDB. + int64_t magic; + is.read(reinterpret_cast(&magic), sizeof(int64_t)); + + if (magic != OPENVDB_MAGIC) { + OPENVDB_THROW(IoError, "not a VDB file"); + } + + // 2) Read the file format version number. + is.read(reinterpret_cast(&mFileVersion), sizeof(uint32_t)); + if (mFileVersion > OPENVDB_FILE_VERSION) { + OPENVDB_LOG_WARN("unsupported VDB file format (expected version " + << OPENVDB_FILE_VERSION << " or earlier, got version " << mFileVersion << ")"); + } else if (mFileVersion < 211) { + // Versions prior to 211 stored separate major, minor and patch numbers. + uint32_t version; + is.read(reinterpret_cast(&version), sizeof(uint32_t)); + mFileVersion = 100 * mFileVersion + 10 * version; + is.read(reinterpret_cast(&version), sizeof(uint32_t)); + mFileVersion += version; + } + + // 3) Read the library version numbers (not stored prior to file format version 211). + mLibraryVersion.first = mLibraryVersion.second = 0; + if (mFileVersion >= 211) { + uint32_t version; + is.read(reinterpret_cast(&version), sizeof(uint32_t)); + mLibraryVersion.first = version; // major version + is.read(reinterpret_cast(&version), sizeof(uint32_t)); + mLibraryVersion.second = version; // minor version + } + + // 4) Read the flag indicating whether the stream supports partial reading. + // (Versions prior to 212 have no flag because they always supported partial reading.) + mInputHasGridOffsets = true; + if (mFileVersion >= 212) { + char hasGridOffsets; + is.read(&hasGridOffsets, sizeof(char)); + mInputHasGridOffsets = hasGridOffsets; + } + + // 5) Read the flag that indicates whether data is compressed. + // (From version 222 on, compression information is stored per grid.) + mCompression = DEFAULT_COMPRESSION_FLAGS; + if (mFileVersion >= OPENVDB_FILE_VERSION_SELECTIVE_COMPRESSION && + mFileVersion < OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) + { + char isCompressed; + is.read(&isCompressed, sizeof(char)); + mCompression = (isCompressed != 0 ? COMPRESS_ZIP : COMPRESS_NONE); + } + + // 6) Read the 16-byte (128-bit) uuid. + boost::uuids::uuid oldUuid = mUuid; + if (mFileVersion >= OPENVDB_FILE_VERSION_BOOST_UUID) { + // UUID is stored as an ASCII string. + is >> mUuid; + } else { + // Older versions stored the UUID as a byte string. + char uuidBytes[16]; + is.read(uuidBytes, 16); + std::memcpy(&mUuid.data[0], uuidBytes, std::min(16, mUuid.size())); + } + return oldUuid != mUuid; // true if UUID in input stream differs from old UUID +} + + +void +Archive::writeHeader(std::ostream& os, bool seekable) const +{ + using boost::uint32_t; + using boost::int64_t; + + // 1) Write the magic number for VDB. + int64_t magic = OPENVDB_MAGIC; + os.write(reinterpret_cast(&magic), sizeof(int64_t)); + + // 2) Write the file format version number. + uint32_t version = OPENVDB_FILE_VERSION; + os.write(reinterpret_cast(&version), sizeof(uint32_t)); + + // 3) Write the library version numbers. + version = OPENVDB_LIBRARY_MAJOR_VERSION; + os.write(reinterpret_cast(&version), sizeof(uint32_t)); + version = OPENVDB_LIBRARY_MINOR_VERSION; + os.write(reinterpret_cast(&version), sizeof(uint32_t)); + + // 4) Write a flag indicating that this stream contains no grid offsets. + char hasGridOffsets = seekable; + os.write(&hasGridOffsets, sizeof(char)); + + // 5) Write a flag indicating that this stream contains compressed leaf data. + // (Omitted as of version 222) + + // 6) Generate a new random 16-byte (128-bit) uuid and write it to the stream. + boost::mt19937 ran; + ran.seed(time(NULL)); + boost::uuids::basic_random_generator gen(&ran); + mUuid = gen(); // mUuid is mutable + os << mUuid; +} + + +//////////////////////////////////////// + + +int32_t +Archive::readGridCount(std::istream& is) +{ + int32_t gridCount = 0; + is.read(reinterpret_cast(&gridCount), sizeof(int32_t)); + return gridCount; +} + + +//////////////////////////////////////// + + +void +Archive::connectInstance(const GridDescriptor& gd, const NamedGridMap& grids) const +{ + if (!gd.isInstance() || grids.empty()) return; + + NamedGridMap::const_iterator it = grids.find(gd.uniqueName()); + if (it == grids.end()) return; + GridBase::Ptr grid = it->second; + if (!grid) return; + + it = grids.find(gd.instanceParentName()); + if (it != grids.end()) { + GridBase::Ptr parent = it->second; + if (mEnableInstancing) { + // Share the instance parent's tree. + grid->setTree(parent->baseTreePtr()); + } else { + // Copy the instance parent's tree. + grid->setTree(parent->baseTree().copy()); + } + } else { + OPENVDB_THROW(KeyError, "missing instance parent \"" + << GridDescriptor::nameAsString(gd.instanceParentName()) + << "\" for grid " << GridDescriptor::nameAsString(gd.uniqueName())); + } +} + + +//////////////////////////////////////// + + +void +Archive::readGrid(GridBase::Ptr grid, const GridDescriptor& gd, std::istream& is) +{ + // Read the compression settings for this grid and tag the stream with them + // so that downstream functions can reference them. + readGridCompression(is); + + io::setGridClass(is, GRID_UNKNOWN); + io::setGridBackgroundValuePtr(is, NULL); + + grid->readMeta(is); + + // Add a description of the compression settings to the grid as metadata. + /// @todo Would this be useful? + //const uint32_t compression = getDataCompression(is); + //grid->insertMeta(GridBase::META_FILE_COMPRESSION, + // StringMetadata(compressionToString(compression))); + + const GridClass gridClass = grid->getGridClass(); + io::setGridClass(is, gridClass); + + if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_GRID_INSTANCING) { + grid->readTransform(is); + if (!gd.isInstance()) { + grid->readTopology(is); + grid->readBuffers(is); + } + } else { + grid->readTopology(is); + grid->readTransform(is); + grid->readBuffers(is); + } + if (getFormatVersion(is) < OPENVDB_FILE_VERSION_NO_GRIDMAP) { + // Older versions of the library didn't store grid names as metadata, + // so when reading older files, copy the grid name from the descriptor + // to the grid's metadata. + if (grid->getName().empty()) { + grid->setName(gd.gridName()); + } + } +} + + +void +Archive::write(std::ostream& os, const GridPtrVec& grids, bool seekable, + const MetaMap& metadata) const +{ + this->write(os, GridCPtrVec(grids.begin(), grids.end()), seekable, metadata); +} + + +void +Archive::write(std::ostream& os, const GridCPtrVec& grids, bool seekable, + const MetaMap& metadata) const +{ + // Set stream flags so that downstream functions can reference them. + io::setDataCompression(os, compressionFlags()); + os.iword(sStreamState.writeGridStatsMetadata) = isGridStatsMetadataEnabled(); + + this->writeHeader(os, seekable); + + metadata.writeMeta(os); + + // Write the number of non-null grids. + int32_t gridCount = 0; + for (GridCPtrVecCIter i = grids.begin(), e = grids.end(); i != e; ++i) { + if (*i) ++gridCount; + } + os.write(reinterpret_cast(&gridCount), sizeof(int32_t)); + + typedef std::map TreeMap; + typedef TreeMap::iterator TreeMapIter; + TreeMap treeMap; + + std::set uniqueNames; + + // Write out the non-null grids. + for (GridCPtrVecCIter i = grids.begin(), e = grids.end(); i != e; ++i) { + if (const GridBase::ConstPtr& grid = *i) { + + // Ensure that the grid's descriptor has a unique grid name, by appending + // a number to it if a grid with the same name was already written. + // Always add a number if the grid name is empty, so that the grid can be + // properly identified as an instance parent, if necessary. + std::string name = grid->getName(); + if (name.empty()) 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, compressionFlags()); + } +} + + +void +Archive::writeGrid(GridDescriptor& gd, GridBase::ConstPtr grid, + std::ostream& os, bool seekable) const +{ + // Write out the Descriptor's header information (grid name and type) + gd.writeHeader(os); + + // Save the curent stream position as postion to where the offsets for + // this GridDescriptor will be written to. + int64_t offsetPos = (seekable ? int64_t(os.tellp()) : 0); + + // Write out the offset information. At this point it will be incorrect. + // But we need to write it out to move the stream head forward. + gd.writeStreamPos(os); + + // Now we know the starting grid storage position. + if (seekable) gd.setGridPos(os.tellp()); + + // Save the compression settings for this grid. + setGridCompression(os, *grid); + + // Save the grid's metadata and transform. + if (!getWriteGridStatsMetadata(os)) { + grid->writeMeta(os); + } else { + // Compute and add grid statistics metadata. + GridBase::Ptr copyOfGrid = grid->copyGrid(); // shallow copy + copyOfGrid->addStatsMetadata(); + copyOfGrid->writeMeta(os); + } + grid->writeTransform(os); + + // Save the grid's structure. + grid->writeTopology(os); + + // Now we know the grid block storage position. + if (seekable) gd.setBlockPos(os.tellp()); + + // Save out the data blocks of the grid. + grid->writeBuffers(os); + + // Now we know the end position of this grid. + if (seekable) gd.setEndPos(os.tellp()); + + if (seekable) { + // Now, go back to where the Descriptor's offset information is written + // and write the offsets again. + os.seekp(offsetPos, std::ios_base::beg); + gd.writeStreamPos(os); + + // Now seek back to the end. + gd.seekToEnd(os); + } +} + + +void +Archive::writeGridInstance(GridDescriptor& gd, GridBase::ConstPtr grid, + std::ostream& os, bool seekable) const +{ + // Write out the Descriptor's header information (grid name, type + // and instance parent name). + gd.writeHeader(os); + + // Save the curent stream position as postion to where the offsets for + // this GridDescriptor will be written to. + int64_t offsetPos = (seekable ? int64_t(os.tellp()) : 0); + + // Write out the offset information. At this point it will be incorrect. + // But we need to write it out to move the stream head forward. + gd.writeStreamPos(os); + + // Now we know the starting grid storage position. + if (seekable) gd.setGridPos(os.tellp()); + + // Save the compression settings for this grid. + setGridCompression(os, *grid); + + // Save the grid's metadata and transform. + grid->writeMeta(os); + grid->writeTransform(os); + + // Now we know the end position of this grid. + if (seekable) gd.setEndPos(os.tellp()); + + if (seekable) { + // Now, go back to where the Descriptor's offset information is written + // and write the offsets again. + os.seekp(offsetPos, std::ios_base::beg); + gd.writeStreamPos(os); + + // Now seek back to the end. + gd.seekToEnd(os); + } +} + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/io/Archive.h b/openvdb_2_3_0_library/openvdb/io/Archive.h new file mode 100755 index 0000000..03270a7 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/Archive.h @@ -0,0 +1,273 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_IO_ARCHIVE_HAS_BEEN_INCLUDED +#define OPENVDB_IO_ARCHIVE_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for VersionId +#include "Compression.h" // for COMPRESS_ZIP, etc. + + +class TestFile; + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +class GridDescriptor; + + +/// Return the file format version number associated with the given input stream. +/// @sa File::setFormatVersion() +OPENVDB_API uint32_t getFormatVersion(std::istream&); + +/// Return the library version number associated with the given input stream. +/// @sa File::setLibraryVersion() +OPENVDB_API VersionId getLibraryVersion(std::istream&); + +/// Return a string of the form "./", giving the library +/// and file format version numbers associated with the given input stream. +OPENVDB_API std::string getVersion(std::istream&); + +/// Associate the current file format and library version numbers with the given input stream. +OPENVDB_API void setCurrentVersion(std::istream&); + +/// @brief Associate specific file format and library version numbers with the given stream. +/// @details This is typically called immediately after reading a header that contains +/// the version numbers. Data read subsequently can then be interpreted appropriately. +OPENVDB_API void setVersion(std::ios_base&, const VersionId& libraryVersion, uint32_t fileVersion); + +/// Return @c true if grid statistics (active voxel count and bounding box, etc.) +/// should be computed and stored as grid metadata on output to the given stream. +OPENVDB_API bool getWriteGridStatsMetadata(std::ostream&); + +/// @brief Return a bitwise OR of compression option flags (COMPRESS_ZIP, +/// COMPRESS_ACTIVE_MASK, etc.) specifying whether and how input data is compressed +/// or output data should be compressed. +OPENVDB_API uint32_t getDataCompression(std::ios_base&); + +/// @brief Associate with the given stream a bitwise OR of compression option flags +/// (COMPRESS_ZIP, COMPRESS_ACTIVE_MASK, etc.) specifying whether and how input data +/// is compressed or output data should be compressed. +OPENVDB_API void setDataCompression(std::ios_base&, uint32_t compressionFlags); + +/// @brief Return the class (GRID_LEVEL_SET, GRID_UNKNOWN, etc.) of the grid +/// currently being read from or written to the given stream. +OPENVDB_API uint32_t getGridClass(std::ios_base&); + +/// @brief Associate with the given stream the class (GRID_LEVEL_SET, GRID_UNKNOWN, etc.) +/// of the grid currently being read or written. +OPENVDB_API void setGridClass(std::ios_base&, uint32_t); + +/// @brief Return a pointer to the background value of the grid +/// currently being read from or written to the given stream. +OPENVDB_API const void* getGridBackgroundValuePtr(std::ios_base&); + +/// @brief Specify (a pointer to) the background value of the grid +/// currently being read from or written to the given stream. +/// @note The pointer must remain valid until the entire grid has been read or written. +OPENVDB_API void setGridBackgroundValuePtr(std::ios_base&, const void* background); + + +//////////////////////////////////////// + + +/// Grid serializer/unserializer +class OPENVDB_API Archive +{ +public: + static const uint32_t DEFAULT_COMPRESSION_FLAGS; + + Archive(); + virtual ~Archive(); + + /// @brief Return a copy of this archive. + virtual boost::shared_ptr copy() const; + + /// @brief Return the UUID that was most recently written (or read, + /// if no UUID has been written yet). + std::string getUniqueTag() const; + /// @brief Return @c true if the given UUID matches this archive's UUID. + bool isIdentical(const std::string& uuidStr) const; + + /// @brief Return the file format version number of the input stream. + uint32_t fileVersion() const { return mFileVersion; } + /// @brief Return the (major, minor) version number of the library that was + /// used to write the input stream. + VersionId libraryVersion() const { return mLibraryVersion; } + /// @brief Return a string of the form "./", giving the + /// library and file format version numbers associated with the input stream. + std::string version() const; + + /// @brief Return @c true if trees shared by multiple grids are written out + /// only once, @c false if they are written out once per grid. + bool isInstancingEnabled() const { return mEnableInstancing; } + /// @brief Specify whether trees shared by multiple grids should be + /// written out only once (@c true) or once per grid (@c false). + /// @note Instancing is enabled by default. + void setInstancingEnabled(bool b) { mEnableInstancing = b; } + + /// Return @c true if the data stream is Zip-compressed. + bool isCompressionEnabled() const; + /// @brief Specify whether the data stream should be Zip-compressed. + /// @details Enabling Zip compression makes I/O slower, but saves space. + /// Disable it only if raw I/O speed is a concern. + void setCompressionEnabled(bool); + + /// Return a bit mask specifying compression options for the data stream. + uint32_t compressionFlags() const { return mCompression; } + /// @brief Specify whether and how the data stream should be compressed. + /// [Mainly for internal use] + /// @param c bitwise OR (e.g., COMPRESS_ZIP | COMPRESS_ACTIVE_MASK) of + /// compression option flags (see Compression.h for the available flags) + /// @note Not all combinations of compression options are supported. + void setCompressionFlags(uint32_t c) { mCompression = c; } + + /// @brief Return @c true if grid statistics (active voxel count and + /// bounding box, etc.) are computed and written as grid metadata. + bool isGridStatsMetadataEnabled() const { return mEnableGridStats; } + /// @brief Specify whether grid statistics (active voxel count and + /// bounding box, etc.) should be computed and written as grid metadata. + void setGridStatsMetadataEnabled(bool b) { mEnableGridStats = b; } + + /// @brief Write the grids in the given container to this archive's output stream. + virtual void write(const GridCPtrVec&, const MetaMap& = MetaMap()) const {} + +protected: + /// @brief Return @c true if the input stream contains grid offsets + /// that allow for random access or partial reading. + bool inputHasGridOffsets() const { return mInputHasGridOffsets; } + void setInputHasGridOffsets(bool b) { mInputHasGridOffsets = b; } + + /// @brief Tag the given input stream with the input file format version number. + /// + /// The tag can be retrieved with getFormatVersion(). + /// @sa getFormatVersion() + void setFormatVersion(std::istream&); + + /// @brief Tag the given input stream with the version number of + /// the library with which the input stream was created. + /// + /// The tag can be retrieved with getLibraryVersion(). + /// @sa getLibraryVersion() + void setLibraryVersion(std::istream&); + + /// @brief Tag the given input stream with flags indicating whether + /// the input stream contains compressed data and how it is compressed. + void setDataCompression(std::istream&); + + /// @brief Tag an output stream with flags specifying only those + /// compression options that are applicable to the given grid. + void setGridCompression(std::ostream&, const GridBase&) const; + /// @brief Read in the compression flags for a grid and + /// tag the given input stream with those flags. + static void readGridCompression(std::istream&); + + /// @brief Tag the given output stream with a flag indicating whether + /// to compute and write grid statistics metadata. + void setWriteGridStatsMetadata(std::ostream&); + + /// Read in and return the number of grids on the input stream. + static int32_t readGridCount(std::istream&); + + /// Populate the given grid from the input stream. + static void readGrid(GridBase::Ptr, const GridDescriptor&, std::istream&); + + typedef std::map NamedGridMap; + + /// @brief If the grid represented by the given grid descriptor + /// is an instance, connect it with its instance parent. + void connectInstance(const GridDescriptor&, const NamedGridMap&) const; + + /// Write the given grid descriptor and grid to an output stream + /// and update the GridDescriptor offsets. + /// @param seekable if true, the output stream supports seek operations + void writeGrid(GridDescriptor&, GridBase::ConstPtr, std::ostream&, bool seekable) const; + /// Write the given grid descriptor and grid metadata to an output stream + /// and update the GridDescriptor offsets, but don't write the grid's tree, + /// since it is shared with another grid. + /// @param seekable if true, the output stream supports seek operations + void writeGridInstance(GridDescriptor&, GridBase::ConstPtr, + std::ostream&, bool seekable) const; + + /// @brief Read the magic number, version numbers, UUID, etc. from the given input stream. + /// @return @c true if the input UUID differs from the previously-read UUID. + bool readHeader(std::istream&); + /// @brief Write the magic number, version numbers, UUID, etc. to the given output stream. + /// @param seekable if true, the output stream supports seek operations + /// @todo This method should not be const since it actually redefines the UUID! + void writeHeader(std::ostream&, bool seekable) const; + + //@{ + /// Write the given grids to an output stream. + void write(std::ostream&, const GridPtrVec&, bool seekable, const MetaMap& = MetaMap()) const; + void write(std::ostream&, const GridCPtrVec&, bool seekable, const MetaMap& = MetaMap()) const; + //@} + +private: + friend class ::TestFile; + + /// The version of the file that was read + uint32_t mFileVersion; + /// The version of the library that was used to create the file that was read + VersionId mLibraryVersion; + /// 16-byte (128-bit) UUID + mutable boost::uuids::uuid mUuid;// needs to be mutable since writeHeader is const! + /// Flag indicating whether the input stream contains grid offsets + /// and therefore supports partial reading + bool mInputHasGridOffsets; + /// Flag indicating whether a tree shared by multiple grids should be + /// written out only once (true) or once per grid (false) + bool mEnableInstancing; + /// Flags indicating whether and how the data stream is compressed + uint32_t mCompression; + /// Flag indicating whether grid statistics metadata should be written + bool mEnableGridStats; +}; // class Archive + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_IO_ARCHIVE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/io/Compression.cc b/openvdb_2_3_0_library/openvdb/io/Compression.cc new file mode 100755 index 0000000..ce0eeb0 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/Compression.cc @@ -0,0 +1,143 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Compression.h" + +#include +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +std::string +compressionToString(uint32_t flags) +{ + if (flags == COMPRESS_NONE) return "none"; + + std::vector words; + if (flags & COMPRESS_ZIP) words.push_back("zipped"); + if (flags & COMPRESS_ACTIVE_MASK) { + words.push_back("active values"); + } + return boost::join(words, " "); +} + + +//////////////////////////////////////// + + +namespace { +const int ZIP_COMPRESSION_LEVEL = Z_DEFAULT_COMPRESSION; ///< @todo use Z_BEST_SPEED? +} + +void +zipToStream(std::ostream& os, const char* data, size_t numBytes) +{ + // Get an upper bound on the size of the compressed data. + uLongf numZippedBytes = compressBound(numBytes); + // Compress the data. + boost::shared_array zippedData(new Bytef[numZippedBytes]); + int status = compress2( + /*dest=*/zippedData.get(), &numZippedBytes, + /*src=*/reinterpret_cast(data), numBytes, + /*level=*/ZIP_COMPRESSION_LEVEL); + if (status != Z_OK) { + std::string errDescr; + if (const char* s = zError(status)) errDescr = s; + if (!errDescr.empty()) errDescr = " (" + errDescr + ")"; + OPENVDB_LOG_DEBUG("zlib compress2() returned error code " << status << errDescr); + } + if (status == Z_OK && numZippedBytes < numBytes) { + // Write the size of the compressed data. + Int64 outZippedBytes = numZippedBytes; + os.write(reinterpret_cast(&outZippedBytes), 8); + // Write the compressed data. + os.write(reinterpret_cast(zippedData.get()), outZippedBytes); + } else { + // Write the size of the uncompressed data. + Int64 negBytes = -numBytes; + os.write(reinterpret_cast(&negBytes), 8); + // Write the uncompressed data. + os.write(reinterpret_cast(data), numBytes); + } +} + + +void +unzipFromStream(std::istream& is, char* data, size_t numBytes) +{ + // Read the size of the compressed data. + // A negative size indicates uncompressed data. + Int64 numZippedBytes; + is.read(reinterpret_cast(&numZippedBytes), 8); + + if (numZippedBytes <= 0) { + // Read the uncompressed data. + is.read(data, -numZippedBytes); + if (size_t(-numZippedBytes) != numBytes) { + OPENVDB_THROW(RuntimeError, "Expected to read a " << numBytes + << "-byte chunk, got a " << -numZippedBytes << "-byte chunk"); + } + } else { + // Read the compressed data. + boost::shared_array zippedData(new Bytef[numZippedBytes]); + is.read(reinterpret_cast(zippedData.get()), numZippedBytes); + // Uncompress the data. + uLongf numUnzippedBytes = numBytes; + int status = uncompress( + /*dest=*/reinterpret_cast(data), &numUnzippedBytes, + /*src=*/zippedData.get(), static_cast(numZippedBytes)); + if (status != Z_OK) { + std::string errDescr; + if (const char* s = zError(status)) errDescr = s; + if (!errDescr.empty()) errDescr = " (" + errDescr + ")"; + OPENVDB_LOG_DEBUG("zlib uncompress() returned error code " << status << errDescr); + } + if (numUnzippedBytes != numBytes) { + OPENVDB_THROW(RuntimeError, "Expected to decompress " << numBytes + << " byte" << (numBytes == 1 ? "" : "s") << ", got " + << numZippedBytes << " byte" << (numZippedBytes == 1 ? "" : "s")); + } + } +} + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/io/Compression.h b/openvdb_2_3_0_library/openvdb/io/Compression.h new file mode 100755 index 0000000..ab2d958 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/Compression.h @@ -0,0 +1,571 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_IO_COMPRESSION_HAS_BEEN_INCLUDED +#define OPENVDB_IO_COMPRESSION_HAS_BEEN_INCLUDED + +#include +#include // for negative() +#include +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +/// @brief OR-able bit flags for compression options on input and output streams +/// @details +///
+///
COMPRESS_NONE +///
On write, don't compress data.
+/// On read, the input stream contains uncompressed data. +/// +///
COMPRESS_ZIP +///
When writing grids other than level sets or fog volumes, apply ZIP compression +/// to internal and leaf node value buffers.
+/// On read of grids other than level sets or fog volumes, the value buffers +/// of internal and leaf nodes are ZIP-compressed. +/// +///
COMPRESS_ACTIVE_MASK +///
When writing a grid of any class, don't output a node's inactive values +/// if it has two or fewer distinct values. Instead, output minimal information +/// to permit the lossless reconstruction of inactive values.
+/// On read, nodes might have been stored without inactive values. +/// Where necessary, reconstruct inactive values from available information. +///
+enum { + COMPRESS_NONE = 0, + COMPRESS_ZIP = 0x1, + COMPRESS_ACTIVE_MASK = 0x2 +}; + +/// Return a string describing the given compression flags. +OPENVDB_API std::string compressionToString(uint32_t flags); + + +//////////////////////////////////////// + + +/// @internal Per-node indicator byte that specifies what additional metadata +/// is stored to permit reconstruction of inactive values +enum { + /*0*/ NO_MASK_OR_INACTIVE_VALS, // no inactive vals, or all inactive vals are +background + /*1*/ NO_MASK_AND_MINUS_BG, // all inactive vals are -background + /*2*/ NO_MASK_AND_ONE_INACTIVE_VAL, // all inactive vals have the same non-background val + /*3*/ MASK_AND_NO_INACTIVE_VALS, // mask selects between -background and +background + /*4*/ MASK_AND_ONE_INACTIVE_VAL, // mask selects between backgd and one other inactive val + /*5*/ MASK_AND_TWO_INACTIVE_VALS, // mask selects between two non-background inactive vals + /*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 +}; +template<> struct RealToHalf { enum { isReal = true }; typedef half HalfT; }; +template<> struct RealToHalf { enum { isReal = true }; typedef half HalfT; }; +template<> struct RealToHalf { enum { isReal = true }; typedef Vec2H HalfT; }; +template<> struct RealToHalf { enum { isReal = true }; typedef Vec2H HalfT; }; +template<> struct RealToHalf { enum { isReal = true }; typedef Vec3H HalfT; }; +template<> struct RealToHalf { enum { isReal = true }; typedef Vec3H HalfT; }; + + +/// Return the given value truncated to 16-bit float precision. +template +inline T +truncateRealToHalf(const T& val) +{ + return T(typename RealToHalf::HalfT(val)); +} + + +//////////////////////////////////////// + + +OPENVDB_API void zipToStream(std::ostream&, const char* data, size_t numBytes); +OPENVDB_API void unzipFromStream(std::istream&, char* data, size_t numBytes); + +/// @brief Read data from a stream. +/// @param is the input stream +/// @param data the contiguous array of data to read in +/// @param count the number of elements to read in +/// @param compressed if @c true, assume the data is ZIP compressed and uncompress it +/// This default implementation is instantiated only for types whose size +/// can be determined by the sizeof() operator. +template +inline void +readData(std::istream& is, T* data, Index count, bool compressed) +{ + if (compressed) { + unzipFromStream(is, reinterpret_cast(data), sizeof(T) * count); + } else { + is.read(reinterpret_cast(data), sizeof(T) * count); + } +} + +/// Specialization for std::string input +template<> +inline void +readData(std::istream& is, std::string* data, Index count, bool /*compressed*/) +{ + for (Index i = 0; i < count; ++i) { + size_t len = 0; + is >> len; + //data[i].resize(len); + //is.read(&(data[i][0]), len); + + std::string buffer(len+1, ' '); + is.read(&buffer[0], len+1 ); + data[i].assign(buffer, 0, len); + } +} + +/// HalfReader wraps a static function, read(), that is analogous to readData(), above, +/// except that it is partially specialized for floating-point types in order to promote +/// 16-bit half float values to full float. A wrapper class is required because +/// only classes, not functions, can be partially specialized. +template struct HalfReader; +/// Partial specialization for non-floating-point types (no half to float promotion) +template +struct HalfReader { + static inline void read(std::istream& is, T* data, Index count, bool compressed) { + readData(is, data, count, compressed); + } +}; +/// Partial specialization for floating-point types +template +struct HalfReader { + typedef typename RealToHalf::HalfT HalfT; + static inline void read(std::istream& is, T* data, Index count, bool compressed) { + if (count < 1) return; + std::vector halfData(count); // temp buffer into which to read half float values + readData(is, reinterpret_cast(&halfData[0]), count, compressed); + // Copy half float values from the temporary buffer to the full float output array. + std::copy(halfData.begin(), halfData.end(), data); + } +}; + + +/// Write data to a stream. +/// @param os the output stream +/// @param data the contiguous array of data to write +/// @param count the number of elements to write out +/// @param compress if @c true, apply ZIP compression to the data +/// This default implementation is instantiated only for types whose size +/// can be determined by the sizeof() operator. +template +inline void +writeData(std::ostream &os, const T *data, Index count, bool compress) +{ + if (compress) { + zipToStream(os, reinterpret_cast(data), sizeof(T) * count); + } else { + os.write(reinterpret_cast(data), sizeof(T) * count); + } +} + +/// Specialization for std::string output +/// @todo Add compression +template<> +inline void +writeData(std::ostream& os, const std::string* data, Index count, bool /*compress*/) +{ + for (Index i = 0; i < count; ++i) { + const size_t len = data[i].size(); + os << len; + os.write(data[i].c_str(), len+1); + //os.write(&(data[i][0]), len ); + } +} + +/// HalfWriter wraps a static function, write(), that is analogous to writeData(), above, +/// except that it is partially specialized for floating-point types in order to quantize +/// floating-point values to 16-bit half float. A wrapper class is required because +/// only classes, not functions, can be partially specialized. +template struct HalfWriter; +/// Partial specialization for non-floating-point types (no float to half quantization) +template +struct HalfWriter { + static inline void write(std::ostream& os, const T* data, Index count, bool compress) { + writeData(os, data, count, compress); + } +}; +/// Partial specialization for floating-point types +template +struct HalfWriter { + typedef typename RealToHalf::HalfT HalfT; + static inline void write(std::ostream& os, const T* data, Index count, bool compress) { + if (count < 1) return; + // Convert full float values to half float, then output the half float array. + std::vector halfData(count); + std::copy(data, data + count, halfData.begin()); + writeData(os, reinterpret_cast(&halfData[0]), count, compress); + } +}; +#ifdef _MSC_VER +/// Specialization to avoid double to float warnings in MSVC +template<> +struct HalfWriter { + typedef RealToHalf::HalfT HalfT; + static inline void write(std::ostream& os, const double* data, Index count, bool compress) { + if (count < 1) return; + // Convert full float values to half float, then output the half float array. + std::vector halfData(count); + for (Index i = 0; i < count; ++i) halfData[i] = float(data[i]); + writeData(os, reinterpret_cast(&halfData[0]), count, compress); + } +}; +#endif // _MSC_VER + + +//////////////////////////////////////// + + +/// Populate the given buffer with @a destCount values of type @c ValueT +/// read from the given stream, taking into account that the stream might +/// have been compressed via one of several supported schemes. +/// [Mainly for internal use] +/// @param is a stream from which to read data (possibly compressed, +/// depending on the stream's compression settings) +/// @param destBuf a buffer into which to read values of type @c ValueT +/// @param destCount the number of values to be stored in the buffer +/// @param valueMask a bitmask (typically, a node's value mask) indicating +/// which positions in the buffer correspond to active values +/// @param fromHalf if true, read 16-bit half floats from the input stream +/// and convert them to full floats +template +inline void +readCompressedValues(std::istream& is, ValueT* destBuf, Index destCount, + const MaskT& valueMask, bool fromHalf) +{ + // Get the stream's compression settings. + const uint32_t compression = getDataCompression(is); + const bool + zipped = compression & COMPRESS_ZIP, + maskCompressed = compression & COMPRESS_ACTIVE_MASK; + + int8_t metadata = NO_MASK_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. + 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. + is.read(reinterpret_cast(&inactiveVal0), sizeof(ValueT)); + if (metadata == MASK_AND_TWO_INACTIVE_VALS) { + // Read the second of two distinct inactive values. + is.read(reinterpret_cast(&inactiveVal1), sizeof(ValueT)); + } + } + + MaskT selectionMask; + if (metadata == 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. + 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 (tempCount != destCount) { + // If this node has inactive voxels, allocate a temporary buffer + // into which to read just the active values. + scopedTempBuf.reset(new ValueT[tempCount]); + tempBuf = scopedTempBuf.get(); + } + } + + // Read in the buffer. + if (fromHalf) { + HalfReader::isReal, ValueT>::read(is, tempBuf, tempCount, zipped); + } else { + readData(is, tempBuf, tempCount, zipped); + } + + // If mask compression is enabled and the number of active values read into + // the temp buffer is smaller than the size of the destination buffer, + // then there are missing (inactive) values. + if (maskCompressed && tempCount != destCount) { + // Restore inactive values, using the background value and, if available, + // the inside/outside mask. (For fog volumes, the destination buffer is assumed + // to be initialized to background value zero, so inactive values can be ignored.) + for (Index destIdx = 0, tempIdx = 0; destIdx < MaskT::SIZE; ++destIdx) { + if (valueMask.isOn(destIdx)) { + // Copy a saved active value into this node's buffer. + destBuf[destIdx] = tempBuf[tempIdx]; + ++tempIdx; + } else { + // Reconstruct an unsaved inactive value and copy it into this node's buffer. + destBuf[destIdx] = (selectionMask.isOn(destIdx) ? inactiveVal1 : inactiveVal0); + } + } + } +} + + +/// Write @a srcCount values of type @c ValueT to the given stream, optionally +/// after compressing the values via one of several supported schemes. +/// [Mainly for internal use] +/// @param os a stream to which to write data (possibly compressed, depending +/// on the stream's compression settings) +/// @param srcBuf a buffer containing values of type @c ValueT to be written +/// @param srcCount the number of values stored in the buffer +/// @param valueMask a bitmask (typically, a node's value mask) indicating +/// which positions in the buffer correspond to active values +/// @param childMask a bitmask (typically, a node's child mask) indicating +/// which positions in the buffer correspond to child node pointers +/// @param toHalf if true, convert floating-point values to 16-bit half floats +template +inline void +writeCompressedValues(std::ostream& os, ValueT* srcBuf, Index srcCount, + const MaskT& valueMask, const MaskT& childMask, bool toHalf) +{ + struct Local { + // Comparison function for values + static inline bool eq(const ValueT& a, const ValueT& b) { + return math::isExactlyEqual(a, b); + } + }; + + // Get the stream's compression settings. + const uint32_t compress = getDataCompression(os); + const bool + zip = compress & COMPRESS_ZIP, + maskCompress = compress & COMPRESS_ACTIVE_MASK; + + Index tempCount = srcCount; + ValueT* tempBuf = srcBuf; + boost::scoped_array scopedTempBuf; + + int8_t metadata = NO_MASK_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 = 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, zip); + } else { + writeData(os, tempBuf, tempCount, zip); + } +} + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_IO_COMPRESSION_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/io/File.cc b/openvdb_2_3_0_library/openvdb/io/File.cc new file mode 100755 index 0000000..cb07156 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/File.cc @@ -0,0 +1,629 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file File.cc + +#include "File.h" + +#include +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +File::File(const std::string& filename): + mFilename(filename), + mIsOpen(false) +{ + setInputHasGridOffsets(true); +} + + +File::~File() +{ +} + + +File::File(const File& other) + : Archive(other) + , mFilename(other.mFilename) + , mMeta(other.mMeta) + , mIsOpen(false) // don't want two file objects reading from the same stream + , mGridDescriptors(other.mGridDescriptors) + , mNamedGrids(other.mNamedGrids) + , mGrids(other.mGrids) +{ +} + + +File& +File::operator=(const File& other) +{ + if (&other != this) { + Archive::operator=(other); + mFilename = other.mFilename; + mMeta = other.mMeta; + mIsOpen = false; // don't want two file objects reading from the same stream + mGridDescriptors = other.mGridDescriptors; + mNamedGrids = other.mNamedGrids; + mGrids = other.mGrids; + } + return *this; +} + + +boost::shared_ptr +File::copy() const +{ + return boost::shared_ptr(new File(*this)); +} + + +//////////////////////////////////////// + + +bool +File::open() +{ +#if defined(_MSC_VER) + // The original C++ standard library specified that open() only _sets_ + // the fail bit upon an error. It does not clear any bits upon success. + // This was later addressed by the Library Working Group (LWG) for DR #409 + // and implemented by gcc 4.0. Visual Studio 2008 however is one of those + // which has not caught up. + // See: http://gcc.gnu.org/onlinedocs/libstdc++/ext/lwg-defects.html#22 + mInStream.clear(); +#endif + + // Open the file. + mInStream.open(mFilename.c_str(), std::ios_base::in | std::ios_base::binary); + + if (mInStream.fail()) { + OPENVDB_THROW(IoError, "could not open file " << mFilename); + } + + // Read in the file header. + bool newFile = false; + try { + newFile = Archive::readHeader(mInStream); + } catch (IoError& e) { + mInStream.close(); + if (e.what() && std::string("not a VDB file") == e.what()) { + // Rethrow, adding the filename. + OPENVDB_THROW(IoError, mFilename << " is not a VDB file"); + } + throw; + } + + // Tag the input stream with the file format and library version numbers. + Archive::setFormatVersion(mInStream); + Archive::setLibraryVersion(mInStream); + Archive::setDataCompression(mInStream); + + // Read in the VDB metadata. + mMeta = MetaMap::Ptr(new MetaMap); + mMeta->readMeta(mInStream); + + if (!inputHasGridOffsets()) { + OPENVDB_LOG_WARN("file " << mFilename << " does not support partial reading"); + + mGrids.reset(new GridPtrVec); + mNamedGrids.clear(); + + // Stream in the entire contents of the file and append all grids to mGrids. + const boost::int32_t gridCount = readGridCount(mInStream); + for (boost::int32_t i = 0; i < gridCount; ++i) { + GridDescriptor gd; + gd.read(mInStream); + + GridBase::Ptr grid = createGrid(gd); + Archive::readGrid(grid, gd, mInStream); + + mGridDescriptors.insert(std::make_pair(gd.gridName(), gd)); + mGrids->push_back(grid); + mNamedGrids[gd.uniqueName()] = grid; + } + // Connect instances (grids that share trees with other grids). + for (NameMapCIter it = mGridDescriptors.begin(); it != mGridDescriptors.end(); ++it) { + Archive::connectInstance(it->second, mNamedGrids); + } + } else { + // Read in just the grid descriptors. + readGridDescriptors(mInStream); + } + + mIsOpen = true; + return newFile; // true if file is not identical to opened file +} + + +void +File::close() +{ + // Close the stream. + if (mInStream.is_open()) { + mInStream.close(); + } + + // Reset all data. + mMeta.reset(); + mGridDescriptors.clear(); + mGrids.reset(); + mNamedGrids.clear(); + + mIsOpen = false; + setInputHasGridOffsets(true); +} + + +//////////////////////////////////////// + + +bool +File::hasGrid(const Name& name) const +{ + if (!isOpen()) { + OPENVDB_THROW(IoError, mFilename << " is not open for reading"); + } + return (findDescriptor(name) != mGridDescriptors.end()); +} + + +MetaMap::Ptr +File::getMetadata() const +{ + if (!isOpen()) { + OPENVDB_THROW(IoError, mFilename << " is not open for reading"); + } + // Return a deep copy of the file-level metadata, which was read + // when the file was opened. + return MetaMap::Ptr(new MetaMap(*mMeta)); +} + + +GridPtrVecPtr +File::getGrids() const +{ + if (!isOpen()) { + OPENVDB_THROW(IoError, mFilename << " is not open for reading"); + } + + GridPtrVecPtr ret; + if (!inputHasGridOffsets()) { + // If the input file doesn't have grid offsets, then all of the grids + // have already been streamed in and stored in mGrids. + ret = mGrids; + } else { + ret.reset(new GridPtrVec); + + Archive::NamedGridMap namedGrids; + + // Read all grids represented by the GridDescriptors. + for (NameMapCIter i = mGridDescriptors.begin(), e = mGridDescriptors.end(); i != e; ++i) { + const GridDescriptor& gd = i->second; + GridBase::Ptr grid = readGrid(gd); + ret->push_back(grid); + namedGrids[gd.uniqueName()] = grid; + } + + // Connect instances (grids that share trees with other grids). + for (NameMapCIter i = mGridDescriptors.begin(), e = mGridDescriptors.end(); i != e; ++i) { + Archive::connectInstance(i->second, namedGrids); + } + } + return ret; +} + + +//////////////////////////////////////// + + +GridPtrVecPtr +File::readAllGridMetadata() +{ + if (!isOpen()) { + OPENVDB_THROW(IoError, mFilename << " is not open for reading"); + } + + GridPtrVecPtr ret(new GridPtrVec); + + if (!inputHasGridOffsets()) { + // If the input file doesn't have grid offsets, then all of the grids + // have already been streamed in and stored in mGrids. + for (size_t i = 0, N = mGrids->size(); i < N; ++i) { + // Return copies of the grids, but with empty trees. + ret->push_back((*mGrids)[i]->copyGrid(/*treePolicy=*/CP_NEW)); + } + } else { + // Read just the metadata and transforms for all grids. + for (NameMapCIter i = mGridDescriptors.begin(), e = mGridDescriptors.end(); i != e; ++i) { + const GridDescriptor& gd = i->second; + GridBase::ConstPtr grid = readGridPartial(gd, /*readTopology=*/false); + // Return copies of the grids, but with empty trees. + // (As of 0.98.0, at least, it would suffice to just const cast + // the grid pointers returned by readGridPartial(), but shallow + // copying the grids helps to ensure future compatibility.) + ret->push_back(grid->copyGrid(/*treePolicy=*/CP_NEW)); + } + } + return ret; +} + + +GridBase::Ptr +File::readGridMetadata(const Name& name) +{ + if (!isOpen()) { + OPENVDB_THROW(IoError, mFilename << " is not open for reading."); + } + + GridBase::ConstPtr ret; + if (!inputHasGridOffsets()) { + // Retrieve the grid from mGrids, which should already contain + // the entire contents of the file. + ret = readGrid(name); + } else { + NameMapCIter it = findDescriptor(name); + if (it == mGridDescriptors.end()) { + OPENVDB_THROW(KeyError, mFilename << " has no grid named \"" << name << "\""); + } + + // Seek to and read in the grid from the file. + const GridDescriptor& gd = it->second; + ret = readGridPartial(gd, /*readTopology=*/false); + } + return ret->copyGrid(/*treePolicy=*/CP_NEW); +} + + +//////////////////////////////////////// + + +GridBase::ConstPtr +File::readGridPartial(const Name& name) +{ + if (!isOpen()) { + OPENVDB_THROW(IoError, mFilename << " is not open for reading."); + } + + GridBase::ConstPtr ret; + if (!inputHasGridOffsets()) { + // Retrieve the grid from mGrids, which should already contain + // the entire contents of the file. + if (GridBase::Ptr grid = readGrid(name)) { + ret = boost::const_pointer_cast(grid); + } + } else { + NameMapCIter it = findDescriptor(name); + if (it == mGridDescriptors.end()) { + OPENVDB_THROW(KeyError, mFilename << " has no grid named \"" << name << "\""); + } + + // Seek to and read in the grid from the file. + const GridDescriptor& gd = it->second; + ret = readGridPartial(gd, /*readTopology=*/true); + + if (gd.isInstance()) { + NameMapCIter parentIt = + findDescriptor(GridDescriptor::nameAsString(gd.instanceParentName())); + if (parentIt == mGridDescriptors.end()) { + OPENVDB_THROW(KeyError, "missing instance parent \"" + << GridDescriptor::nameAsString(gd.instanceParentName()) + << "\" for grid " << GridDescriptor::nameAsString(gd.uniqueName()) + << " in file " << mFilename); + } + if (GridBase::ConstPtr parent = + readGridPartial(parentIt->second, /*readTopology=*/true)) + { + if (Archive::isInstancingEnabled()) { + // Share the instance parent's tree. + boost::const_pointer_cast(ret)->setTree( + boost::const_pointer_cast(parent)->baseTreePtr()); + } else { + // Copy the instance parent's tree. + boost::const_pointer_cast(ret)->setTree( + parent->baseTree().copy()); + } + } + } + } + return ret; +} + + +GridBase::Ptr +File::readGrid(const Name& name) +{ + if (!isOpen()) { + OPENVDB_THROW(IoError, mFilename << " is not open for reading."); + } + + GridBase::Ptr ret; + if (!inputHasGridOffsets()) { + // Retrieve the grid from mNamedGrids, which should already contain + // the entire contents of the file. + + // Search by unique name. + Archive::NamedGridMap::const_iterator it = + mNamedGrids.find(GridDescriptor::stringAsUniqueName(name)); + // If not found, search by grid name. + if (it == mNamedGrids.end()) it = mNamedGrids.find(name); + if (it == mNamedGrids.end()) { + OPENVDB_THROW(KeyError, mFilename << " has no grid named \"" << name << "\""); + } + ret = it->second; + } else { + NameMapCIter it = findDescriptor(name); + if (it == mGridDescriptors.end()) { + OPENVDB_THROW(KeyError, mFilename << " has no grid named \"" << name << "\""); + } + + // Seek to and read in the grid from the file. + const GridDescriptor& gd = it->second; + ret = readGrid(gd); + + if (gd.isInstance()) { + NameMapCIter parentIt = + findDescriptor(GridDescriptor::nameAsString(gd.instanceParentName())); + if (parentIt == mGridDescriptors.end()) { + OPENVDB_THROW(KeyError, "missing instance parent \"" + << GridDescriptor::nameAsString(gd.instanceParentName()) + << "\" for grid " << GridDescriptor::nameAsString(gd.uniqueName()) + << " in file " << mFilename); + } + if (GridBase::Ptr parent = readGrid(parentIt->second)) { + if (Archive::isInstancingEnabled()) { + // Share the instance parent's tree. + ret->setTree(parent->baseTreePtr()); + } else { + // Copy the instance parent's tree. + ret->setTree(parent->baseTree().copy()); + } + } + } + } + return ret; +} + + +//////////////////////////////////////// + + +void +File::writeGrids(const GridCPtrVec& grids, const MetaMap& metadata) const +{ + if (isOpen()) { + OPENVDB_THROW(IoError, + mFilename << " cannot be written because it is open for reading"); + } + + // Create a file stream and write it out. + std::ofstream file; + file.open(mFilename.c_str(), + std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + + if (file.fail()) { + OPENVDB_THROW(IoError, "could not open " << mFilename << " for writing"); + } + + // Write out the vdb. + Archive::write(file, grids, /*seekable=*/true, metadata); + + file.close(); +} + + +//////////////////////////////////////// + + +void +File::readGridDescriptors(std::istream& is) +{ + // This method should not be called for files that don't contain grid offsets. + assert(inputHasGridOffsets()); + + mGridDescriptors.clear(); + + for (boost::int32_t i = 0, N = readGridCount(is); i < N; ++i) { + // Read the grid descriptor. + GridDescriptor gd; + gd.read(is); + + // Add the descriptor to the dictionary. + mGridDescriptors.insert(std::make_pair(gd.gridName(), gd)); + + // Skip forward to the next descriptor. + gd.seekToEnd(is); + } +} + + +//////////////////////////////////////// + + +File::NameMapCIter +File::findDescriptor(const Name& name) const +{ + const Name uniqueName = GridDescriptor::stringAsUniqueName(name); + + // Find all descriptors with the given grid name. + std::pair range = mGridDescriptors.equal_range(name); + + if (range.first == range.second) { + // If no descriptors were found with the given grid name, the name might have + // a suffix ("name[N]"). In that case, remove the "[N]" suffix and search again. + range = mGridDescriptors.equal_range(GridDescriptor::stripSuffix(uniqueName)); + } + + const size_t count = size_t(std::distance(range.first, range.second)); + if (count > 1 && name == uniqueName) { + OPENVDB_LOG_WARN(mFilename << " has more than one grid named \"" << name << "\""); + } + + NameMapCIter ret = mGridDescriptors.end(); + + if (count > 0) { + if (name == uniqueName) { + // If the given grid name is unique or if no "[N]" index was given, + // use the first matching descriptor. + ret = range.first; + } else { + // If the given grid name has a "[N]" index, find the descriptor + // with a matching unique name. + for (NameMapCIter it = range.first; it != range.second; ++it) { + const Name candidateName = it->second.uniqueName(); + if (candidateName == uniqueName || candidateName == name) { + ret = it; + break; + } + } + } + } + return ret; +} + + +//////////////////////////////////////// + + +GridBase::Ptr +File::createGrid(const GridDescriptor& gd) const +{ + // Create the grid. + if (!GridBase::isRegistered(gd.gridType())) { + OPENVDB_THROW(KeyError, "Cannot read grid " + << GridDescriptor::nameAsString(gd.uniqueName()) + << " from " << mFilename << ": grid type " + << gd.gridType() << " is not registered"); + } + + GridBase::Ptr grid = GridBase::createGrid(gd.gridType()); + if (grid) grid->setSaveFloatAsHalf(gd.saveFloatAsHalf()); + + return grid; +} + + +GridBase::ConstPtr +File::readGridPartial(const GridDescriptor& gd, bool readTopology) const +{ + // This method should not be called for files that don't contain grid offsets. + assert(inputHasGridOffsets()); + + GridBase::Ptr grid = createGrid(gd); + + // Seek to grid. + gd.seekToGrid(mInStream); + + // Read the grid partially. + readGridPartial(grid, mInStream, gd.isInstance(), readTopology); + + // Promote to a const grid. + GridBase::ConstPtr constGrid = grid; + + return constGrid; +} + + +GridBase::Ptr +File::readGrid(const GridDescriptor& gd) const +{ + // This method should not be called for files that don't contain grid offsets. + assert(inputHasGridOffsets()); + + GridBase::Ptr grid = createGrid(gd); + + // Seek to where the grid is. + gd.seekToGrid(mInStream); + + // Read in the grid. + Archive::readGrid(grid, gd, mInStream); + + return grid; +} + + +void +File::readGridPartial(GridBase::Ptr grid, std::istream& is, + bool isInstance, bool readTopology) const +{ + // This method should not be called for files that don't contain grid offsets. + assert(inputHasGridOffsets()); + + // This code needs to stay in sync with io::Archive::readGrid(), in terms of + // the order of operations. + readGridCompression(is); + grid->readMeta(is); + if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_GRID_INSTANCING) { + grid->readTransform(is); + if (!isInstance && readTopology) { + grid->readTopology(is); + } + } else { + if (readTopology) { + grid->readTopology(is); + grid->readTransform(is); + } + } +} + + +//////////////////////////////////////// + + +File::NameIterator +File::beginName() const +{ + if (!isOpen()) { + OPENVDB_THROW(IoError, mFilename << " is not open for reading"); + } + return File::NameIterator(mGridDescriptors.begin()); +} + + +File::NameIterator +File::endName() const +{ + return File::NameIterator(mGridDescriptors.end()); +} + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/io/File.h b/openvdb_2_3_0_library/openvdb/io/File.h new file mode 100755 index 0000000..e1bb60b --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/File.h @@ -0,0 +1,236 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file File.h + +#ifndef OPENVDB_IO_FILE_HAS_BEEN_INCLUDED +#define OPENVDB_IO_FILE_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include "Archive.h" +#include "GridDescriptor.h" + + +class TestFile; +class TestStream; + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +/// Grid archive associated with a file on disk +class OPENVDB_API File: public Archive +{ +public: + typedef std::multimap NameMap; + typedef NameMap::const_iterator NameMapCIter; + + explicit File(const std::string& filename); + virtual ~File(); + + /// @brief Copy constructor + /// @details The copy will be closed and will not reference the same + /// file descriptor as the original. + File(const File& other); + /// @brief Assignment + /// @details After assignment, this File will be closed and will not + /// reference the same file descriptor as the source File. + File& operator=(const File& other); + + /// @brief Return a copy of this archive. + /// @details The copy will be closed and will not reference the same + /// file descriptor as the original. + virtual boost::shared_ptr copy() const; + + /// @brief Return the name of the file with which this archive is associated. + /// @details The file does not necessarily exist on disk yet. + const std::string& filename() const { return mFilename; } + + /// Open the file, read the file header and the file-level metadata, and + /// populate the grid descriptors, but do not load any grids into memory. + /// @throw IoError if the file is not a valid VDB file. + /// @return @c true if the file's UUID has changed since it was last read. + bool open(); + + /// Return @c true if the file has been opened for reading. + bool isOpen() const { return mIsOpen; } + + /// Close the file once we are done reading from it. + void close(); + + /// Return @c true if a grid of the given name exists in this file. + bool hasGrid(const Name&) const; + + /// Return (in a newly created MetaMap) the file-level metadata. + MetaMap::Ptr getMetadata() const; + + /// Read the entire contents of the file and return a list of grid pointers. + GridPtrVecPtr getGrids() const; + + /// @brief Read just the grid metadata and transforms from the file and return a list + /// of pointers to grids that are empty except for their metadata and transforms. + /// @throw IoError if this file is not open for reading. + GridPtrVecPtr readAllGridMetadata(); + + /// @brief Read a grid's metadata and transform only. + /// @return A pointer to a grid that is empty except for its metadata and transform. + /// @throw IoError if this file is not open for reading. + /// @throw KeyError if no grid with the given name exists in this file. + GridBase::Ptr readGridMetadata(const Name&); + + /// @brief Read a grid's metadata, topology, transform, etc., but not + /// any of its leaf node data blocks. + /// @return the grid pointer to the partially loaded grid. + /// @note This returns a @c const pointer, so that the grid can't be + /// changed before its data blocks have been loaded. A non-const + /// pointer is only returned when readGrid() is called. + GridBase::ConstPtr readGridPartial(const Name&); + + /// Read an entire grid, including all of its data blocks. + GridBase::Ptr readGrid(const Name&); + + /// @todo GridPtrVec readAllGridsPartial(const Name&) + /// @todo GridPtrVec readAllGrids(const Name&) + + /// @brief Write the grids in the given container to the file whose name + /// was given in the constructor. + virtual void write(const GridCPtrVec&, const MetaMap& = MetaMap()) const; + + /// @brief Write the grids in the given container to the file whose name + /// was given in the constructor. + template + void write(const GridPtrContainerT&, const MetaMap& = MetaMap()) const; + + /// A const iterator that iterates over all names in the file. This is only + /// valid once the file has been opened. + class NameIterator + { + public: + NameIterator(const NameMapCIter& iter): mIter(iter) {} + ~NameIterator() {} + + NameIterator& operator++() { mIter++; return *this; } + + bool operator==(const NameIterator& iter) const { return mIter == iter.mIter; } + bool operator!=(const NameIterator& iter) const { return mIter != iter.mIter; } + + Name operator*() const { return this->gridName(); } + + Name gridName() const { return GridDescriptor::nameAsString(mIter->second.uniqueName()); } + + private: + NameMapCIter mIter; + }; + + /// @return a NameIterator to iterate over all grid names in the file. + NameIterator beginName() const; + + /// @return the ending iterator for all grid names in the file. + NameIterator endName() const; + +private: + /// Resets the input stream to the beginning. + void resetInStream() const { mInStream.seekg(0, std::ios::beg); } + + /// Read in all grid descriptors that are stored in the given stream. + void readGridDescriptors(std::istream&); + + /// @brief Return an iterator to the descriptor for the grid with the given name. + /// If the name is non-unique, return an iterator to the first matching descriptor. + NameMapCIter findDescriptor(const Name&) const; + + /// Return a newly created, empty grid of the type specified by the given grid descriptor. + GridBase::Ptr createGrid(const GridDescriptor&) const; + + /// Read in and return the partially-populated grid specified by the given grid descriptor. + GridBase::ConstPtr readGridPartial(const GridDescriptor&, bool readTopology) const; + + /// Read in and return the grid specified by the given grid descriptor. + GridBase::Ptr readGrid(const GridDescriptor&) const; + + /// Partially populate the given grid by reading its metadata and transform and, + /// if the grid is not an instance, its tree structure, but not the tree's leaf nodes. + void readGridPartial(GridBase::Ptr, std::istream&, bool isInstance, bool readTopology) const; + + void writeGrids(const GridCPtrVec&, const MetaMap&) const; + + friend class ::TestFile; + friend class ::TestStream; + + + std::string mFilename; + /// The file-level metadata + MetaMap::Ptr mMeta; + /// The file stream that is open for reading + mutable std::ifstream mInStream; + /// Flag indicating if we have read in the global information (header, + /// metadata, and grid descriptors) for this VDB file + bool mIsOpen; + /// Grid descriptors for all grids stored in the file, indexed by grid name + NameMap mGridDescriptors; + /// All grids, indexed by unique name (used only when mHasGridOffsets is false) + Archive::NamedGridMap mNamedGrids; + /// All grids stored in the file (used only when mHasGridOffsets is false) + GridPtrVecPtr mGrids; +}; + + +//////////////////////////////////////// + + +inline void +File::write(const GridCPtrVec& grids, const MetaMap& metadata) const +{ + this->writeGrids(grids, metadata); +} + + +template +inline void +File::write(const GridPtrContainerT& container, const MetaMap& metadata) const +{ + GridCPtrVec grids; + std::copy(container.begin(), container.end(), std::back_inserter(grids)); + this->writeGrids(grids, metadata); +} + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_IO_FILE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/io/GridDescriptor.cc b/openvdb_2_3_0_library/openvdb/io/GridDescriptor.cc new file mode 100755 index 0000000..93d87a1 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/GridDescriptor.cc @@ -0,0 +1,233 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "GridDescriptor.h" + +#include +#include // for boost::ends_with() +#include // for boost::erase_last() +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +namespace { + +// In order not to break backward compatibility with existing VDB files, +// grids stored using 16-bit half floats are flagged by adding the following +// suffix to the grid's type name on output. The suffix is removed on input +// and the grid's "save float as half" flag set accordingly. +const char* HALF_FLOAT_TYPENAME_SUFFIX = "_HalfFloat"; + +const char* SEP = "\x1e"; // ASCII "record separator" + +} + + +GridDescriptor::GridDescriptor(): + mSaveFloatAsHalf(false), + mGridPos(0), + mBlockPos(0), + mEndPos(0) +{ +} + +GridDescriptor::GridDescriptor(const Name &name, const Name &type, bool half): + mGridName(stripSuffix(name)), + mUniqueName(name), + mGridType(type), + mSaveFloatAsHalf(half), + mGridPos(0), + mBlockPos(0), + mEndPos(0) +{ +} + +GridDescriptor::~GridDescriptor() +{ +} + +void +GridDescriptor::writeHeader(std::ostream &os) const +{ + writeString(os, mUniqueName); + + Name gridType = mGridType; + if (mSaveFloatAsHalf) gridType += HALF_FLOAT_TYPENAME_SUFFIX; + writeString(os, gridType); + + writeString(os, mInstanceParentName); +} + +void +GridDescriptor::writeStreamPos(std::ostream &os) const +{ + os.write(reinterpret_cast(&mGridPos), sizeof(boost::int64_t)); + os.write(reinterpret_cast(&mBlockPos), sizeof(boost::int64_t)); + os.write(reinterpret_cast(&mEndPos), sizeof(boost::int64_t)); +} + +GridBase::Ptr +GridDescriptor::read(std::istream &is) +{ + // Read in the name. + mUniqueName = readString(is); + mGridName = stripSuffix(mUniqueName); + + // Read in the grid type. + mGridType = readString(is); + if (boost::ends_with(mGridType, HALF_FLOAT_TYPENAME_SUFFIX)) { + mSaveFloatAsHalf = true; + boost::erase_last(mGridType, HALF_FLOAT_TYPENAME_SUFFIX); + } + + if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_GRID_INSTANCING) { + mInstanceParentName = readString(is); + } + + // Create the grid of the type if it has been registered. + if (!GridBase::isRegistered(mGridType)) { + OPENVDB_THROW(LookupError, "Cannot read grid." << + " Grid type " << mGridType << " is not registered."); + } + // else + GridBase::Ptr grid = GridBase::createGrid(mGridType); + if (grid) grid->setSaveFloatAsHalf(mSaveFloatAsHalf); + + // Read in the offsets. + is.read(reinterpret_cast(&mGridPos), sizeof(boost::int64_t)); + is.read(reinterpret_cast(&mBlockPos), sizeof(boost::int64_t)); + is.read(reinterpret_cast(&mEndPos), sizeof(boost::int64_t)); + + return grid; +} + +void +GridDescriptor::seekToGrid(std::istream &is) const +{ + is.seekg(mGridPos, std::ios_base::beg); +} + +void +GridDescriptor::seekToBlocks(std::istream &is) const +{ + is.seekg(mBlockPos, std::ios_base::beg); +} + +void +GridDescriptor::seekToEnd(std::istream &is) const +{ + is.seekg(mEndPos, std::ios_base::beg); +} + + +void +GridDescriptor::seekToGrid(std::ostream &os) const +{ + os.seekp(mGridPos, std::ios_base::beg); +} + +void +GridDescriptor::seekToBlocks(std::ostream &os) const +{ + os.seekp(mBlockPos, std::ios_base::beg); +} + +void +GridDescriptor::seekToEnd(std::ostream &os) const +{ + os.seekp(mEndPos, std::ios_base::beg); +} + + +//////////////////////////////////////// + + +// static +Name +GridDescriptor::addSuffix(const Name& name, int n) +{ + std::ostringstream ostr; + ostr << name << SEP << n; + return ostr.str(); +} + + +// static +Name +GridDescriptor::stripSuffix(const Name& name) +{ + return name.substr(0, name.find(SEP)); +} + + +// static +std::string +GridDescriptor::nameAsString(const Name& name) +{ + std::string::size_type pos = name.find(SEP); + if (pos == std::string::npos) return name; + + return name.substr(0, pos) + "[" + name.substr(pos + 1) + "]"; +} + + +//static +Name +GridDescriptor::stringAsUniqueName(const std::string& s) +{ + Name ret = s; + if (!ret.empty() && *ret.rbegin() == ']') { // found trailing ']' + std::string::size_type pos = ret.find("["); + // Replace "[N]" with SEP "N". + if (pos != std::string::npos) { + if (pos != 0 && ret.substr(pos) == "[0]") { + // "name[0]" is equivalent to "name", except in the case of + // the empty name "[0]". + ret.erase(pos); + } else { + ret.resize(ret.size() - 1); // drop trailing ']' + ret.replace(ret.find("["), 1, SEP); + } + } + } + return ret; +} + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/io/GridDescriptor.h b/openvdb_2_3_0_library/openvdb/io/GridDescriptor.h new file mode 100755 index 0000000..a974e4e --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/GridDescriptor.h @@ -0,0 +1,135 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_IO_GRIDDESCRIPTOR_HAS_BEEN_INCLUDED +#define OPENVDB_IO_GRIDDESCRIPTOR_HAS_BEEN_INCLUDED + +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +/// This structure stores useful information that describes a grid on disk. +/// It can be used to retrieve I/O information about the grid such as +/// offsets into the file where the grid is located, its type, etc. +class OPENVDB_API GridDescriptor +{ +public: + GridDescriptor(); + GridDescriptor(const Name& name, const Name& gridType, bool saveFloatAsHalf = false); + + ~GridDescriptor(); + + const Name& gridType() const { return mGridType; } + const Name& gridName() const { return mGridName; } + const Name& uniqueName() const { return mUniqueName; } + + const Name& instanceParentName() const { return mInstanceParentName; } + void setInstanceParentName(const Name& name) { mInstanceParentName = name; } + bool isInstance() const { return !mInstanceParentName.empty(); } + + bool saveFloatAsHalf() const { return mSaveFloatAsHalf; } + + void setGridPos(boost::int64_t pos) { mGridPos = pos; } + boost::int64_t getGridPos() const { return mGridPos; } + + void setBlockPos(boost::int64_t pos) { mBlockPos = pos; } + boost::int64_t getBlockPos() const { return mBlockPos; } + + void setEndPos(boost::int64_t pos) { mEndPos = pos; } + boost::int64_t getEndPos() const { return mEndPos; } + + // These methods seek to the right position in the given stream. + void seekToGrid(std::istream&) const; + void seekToBlocks(std::istream&) const; + void seekToEnd(std::istream&) const; + + void seekToGrid(std::ostream&) const; + void seekToBlocks(std::ostream&) const; + void seekToEnd(std::ostream&) const; + + /// @brief Write out this descriptor's header information (all data except for + /// stream offsets). + void writeHeader(std::ostream&) const; + + /// @brief Since positions into the stream are known at a later time, they are + /// written out separately. + void writeStreamPos(std::ostream&) const; + + /// @brief Read a grid descriptor from the given stream. + /// @return an empty grid of the type specified by the grid descriptor. + GridBase::Ptr read(std::istream&); + + /// @brief Append the number @a n to the given name (separated by an ASCII + /// "record separator" character) and return the resulting name. + static Name addSuffix(const Name&, int n); + /// @brief Strip from the given name any suffix that is separated by an ASCII + /// "record separator" character and return the resulting name. + static Name stripSuffix(const Name&); + /// @brief Given a name with suffix N, return "name[N]", otherwise just return "name". + /// Use this to produce a human-readable string from a descriptor's unique name. + static std::string nameAsString(const Name&); + /// @brief Given a string of the form "name[N]", return "name" with the suffix N + /// separated by an ASCII "record separator" character). Otherwise just return + /// the string as is. + static Name stringAsUniqueName(const std::string&); + +private: + /// Name of the grid + Name mGridName; + /// Unique name for this descriptor + Name mUniqueName; + /// If nonempty, the name of another grid that shares this grid's tree + Name mInstanceParentName; + /// The type of the grid + Name mGridType; + /// Are floats quantized to 16 bits on disk? + bool mSaveFloatAsHalf; + /// Location in the stream where the grid data is stored + boost::int64_t mGridPos; + /// Location in the stream where the grid blocks are stored + boost::int64_t mBlockPos; + /// Location in the stream where the next grid descriptor begins + boost::int64_t mEndPos; +}; + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_IO_GRIDDESCRIPTOR_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/io/Queue.cc b/openvdb_2_3_0_library/openvdb/io/Queue.cc new file mode 100755 index 0000000..6d76cd6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/Queue.cc @@ -0,0 +1,337 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file Queue.cc +/// @author Peter Cucka + +#include "Queue.h" + +#include "File.h" +#include "Stream.h" +#include +#include +#include +#include +#include +#include +#include +#include // for tbb::this_tbb_thread::sleep() +#include +#include // for std::max() +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +namespace { + +typedef tbb::mutex Mutex; +typedef Mutex::scoped_lock Lock; + + +// Abstract base class for queuable TBB tasks that adds a task completion callback +class Task: public tbb::task +{ +public: + Task(Queue::Id id): mId(id) {} + virtual ~Task() {} + + Queue::Id id() const { return mId; } + + void setNotifier(Queue::Notifier& notifier) { mNotify = notifier; } + +protected: + void notify(Queue::Status status) { if (mNotify) mNotify(this->id(), status); } + +private: + Queue::Id mId; + Queue::Notifier mNotify; +}; + + +// Queuable TBB task that writes one or more grids to a .vdb file or an output stream +class OutputTask: public Task +{ +public: + OutputTask(Queue::Id id, const GridCPtrVec& grids, const Archive& archive, + const MetaMap& metadata) + : Task(id) + , mGrids(grids) + , mArchive(archive.copy()) + , mMetadata(metadata) + {} + + virtual tbb::task* execute() + { + Queue::Status status = Queue::FAILED; + try { + mArchive->write(mGrids, mMetadata); + status = Queue::SUCCEEDED; + } catch (std::exception& e) { + if (const char* msg = e.what()) { + OPENVDB_LOG_ERROR(msg); + } + } catch (...) { + } + this->notify(status); + return NULL; // no successor to this task + } + +private: + GridCPtrVec mGrids; + boost::shared_ptr mArchive; + MetaMap mMetadata; +}; + +} // unnamed namespace + + +//////////////////////////////////////// + + +// Private implementation details of a Queue +struct Queue::Impl +{ + typedef std::map NotifierMap; + /// @todo Provide more information than just "succeeded" or "failed"? + typedef tbb::concurrent_hash_map StatusMap; + + + Impl() + : mTimeout(Queue::DEFAULT_TIMEOUT) + , mCapacity(Queue::DEFAULT_CAPACITY) + , mNextId(1) + , mNextNotifierId(1) + { + mNumTasks = 0; // note: must explicitly zero-initialize atomics + } + ~Impl() {} + + // Disallow copying of instances of this class. + Impl(const Impl&); + Impl& operator=(const Impl&); + + // This method might be called from multiple threads. + void setStatus(Queue::Id id, Queue::Status status) + { + StatusMap::accessor acc; + mStatus.insert(acc, id); + acc->second = status; + } + + // This method might be called from multiple threads. + void setStatusWithNotification(Queue::Id id, Queue::Status status) + { + const bool completed = (status == SUCCEEDED || status == FAILED); + + // Update the task's entry in the status map with the new status. + this->setStatus(id, status); + + // If the client registered any callbacks, call them now. + bool didNotify = false; + { + // tbb::concurrent_hash_map does not support concurrent iteration + // (i.e., iteration concurrent with insertion or deletion), + // so we use a mutex-protected STL map instead. But if a callback + // invokes a notifier method such as removeNotifier() on this queue, + // the result will be a deadlock. + /// @todo Is it worth trying to avoid such deadlocks? + Lock lock(mNotifierMutex); + if (!mNotifiers.empty()) { + didNotify = true; + for (NotifierMap::const_iterator it = mNotifiers.begin(); + it != mNotifiers.end(); ++it) + { + it->second(id, status); + } + } + } + // If the task completed and callbacks were called, remove + // the task's entry from the status map. + if (completed) { + if (didNotify) { + StatusMap::accessor acc; + if (mStatus.find(acc, id)) { + mStatus.erase(acc); + } + } + --mNumTasks; + } + } + + bool canEnqueue() const { return mNumTasks < Int64(mCapacity); } + + void enqueue(Task& task) + { + tbb::tick_count start = tbb::tick_count::now(); + while (!canEnqueue()) { + tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(0.5/*sec*/)); + if ((tbb::tick_count::now() - start).seconds() > double(mTimeout)) { + OPENVDB_THROW(RuntimeError, + "unable to queue I/O task; " << mTimeout << "-second time limit expired"); + } + } + Queue::Notifier notify = boost::bind(&Impl::setStatusWithNotification, this, _1, _2); + task.setNotifier(notify); + this->setStatus(task.id(), Queue::PENDING); + tbb::task::enqueue(task); + ++mNumTasks; + } + + Index32 mTimeout; + Index32 mCapacity; + tbb::atomic mNumTasks; + Index32 mNextId; + StatusMap mStatus; + NotifierMap mNotifiers; + Index32 mNextNotifierId; + Mutex mNotifierMutex; +}; + + +//////////////////////////////////////// + + +Queue::Queue(Index32 capacity): mImpl(new Impl) +{ + mImpl->mCapacity = capacity; +} + + +Queue::~Queue() +{ + // Wait for all queued tasks to complete (successfully or unsuccessfully). + /// @todo Allow the queue to be destroyed while there are uncompleted tasks + /// (e.g., by keeping a static registry of queues that also dispatches + /// or blocks notifications)? + while (mImpl->mNumTasks > 0) { + tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(0.5/*sec*/)); + } +} + + +//////////////////////////////////////// + + +bool Queue::empty() const { return (mImpl->mNumTasks == 0); } +Index32 Queue::size() const { return Index32(std::max(0, mImpl->mNumTasks)); } +Index32 Queue::capacity() const { return mImpl->mCapacity; } +void Queue::setCapacity(Index32 n) { mImpl->mCapacity = std::max(1, n); } + +/// @todo void Queue::setCapacity(Index64 bytes); + +/// @todo Provide a way to limit the number of tasks in flight +/// (e.g., by enqueueing tbb::tasks that pop Tasks off a concurrent_queue)? + +/// @todo Remove any tasks from the queue that are not currently executing. +//void clear() const; + +Index32 Queue::timeout() const { return mImpl->mTimeout; } +void Queue::setTimeout(Index32 sec) { mImpl->mTimeout = sec; } + + +//////////////////////////////////////// + + +Queue::Status +Queue::status(Id id) const +{ + Impl::StatusMap::const_accessor acc; + if (mImpl->mStatus.find(acc, id)) { + const Status status = acc->second; + if (status == SUCCEEDED || status == FAILED) { + mImpl->mStatus.erase(acc); + } + return status; + } + return UNKNOWN; +} + + +Queue::Id +Queue::addNotifier(Notifier notify) +{ + Lock lock(mImpl->mNotifierMutex); + Queue::Id id = mImpl->mNextNotifierId++; + mImpl->mNotifiers[id] = notify; + return id; +} + + +void +Queue::removeNotifier(Id id) +{ + Lock lock(mImpl->mNotifierMutex); + Impl::NotifierMap::iterator it = mImpl->mNotifiers.find(id); + if (it != mImpl->mNotifiers.end()) { + mImpl->mNotifiers.erase(it); + } +} + + +void +Queue::clearNotifiers() +{ + Lock lock(mImpl->mNotifierMutex); + mImpl->mNotifiers.clear(); +} + + +//////////////////////////////////////// + + +Queue::Id +Queue::writeGrid(GridBase::ConstPtr grid, const Archive& archive, const MetaMap& metadata) +{ + return writeGridVec(GridCPtrVec(1, grid), archive, metadata); +} + + +Queue::Id +Queue::writeGridVec(const GridCPtrVec& grids, const Archive& archive, const MetaMap& metadata) +{ + // From the "GUI Thread" chapter in the TBB Design Patterns guide + OutputTask* task = + new(tbb::task::allocate_root()) OutputTask(mImpl->mNextId++, grids, archive, metadata); + mImpl->enqueue(*task); + return task->id(); +} + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/io/Queue.h b/openvdb_2_3_0_library/openvdb/io/Queue.h new file mode 100755 index 0000000..ac91fea --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/Queue.h @@ -0,0 +1,277 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file Queue.h +/// @author Peter Cucka + +#ifndef OPENVDB_IO_QUEUE_HAS_BEEN_INCLUDED +#define OPENVDB_IO_QUEUE_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include // for std::copy +#include // for std::back_inserter + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +class Archive; + +/// @brief Queue for asynchronous output of grids to files or streams +/// +/// @warning The queue holds shared pointers to grids. It is not safe +/// to modify a grid that has been placed in the queue. Instead, +/// make a deep copy of the grid (Grid::deepCopy()). +/// +/// @par Example: +/// @code +/// #include +/// #include +/// #include +/// #include +/// +/// using openvdb::io::Queue; +/// +/// struct MyNotifier +/// { +/// // Use a concurrent container, because queue callback functions +/// // must be thread-safe. +/// typedef tbb::concurrent_hash_map FilenameMap; +/// FilenameMap filenames; +/// +/// // Callback function that prints the status of a completed task. +/// void callback(Queue::Id id, Queue::Status status) +/// { +/// const bool ok = (status == Queue::SUCCEEDED); +/// FilenameMap::accessor acc; +/// if (filenames.find(acc, id)) { +/// std::cout << (ok ? "wrote " : "failed to write ") +/// << acc->second << std::endl; +/// filenames.erase(acc); +/// } +/// } +/// }; +/// +/// int main() +/// { +/// // Construct an object to receive notifications from the queue. +/// // The object's lifetime must exceed the queue's. +/// MyNotifier notifier; +/// +/// Queue queue; +/// +/// // Register the callback() method of the MyNotifier object +/// // to receive notifications of completed tasks. +/// queue.addNotifier(boost::bind(&MyNotifier::callback, ¬ifier, _1, _2)); +/// +/// // Queue grids for output (e.g., for each step of a simulation). +/// for (int step = 1; step <= 10; ++step) { +/// openvdb::FloatGrid::Ptr grid = ...; +/// +/// std::ostringstream os; +/// os << "mygrid." << step << ".vdb"; +/// const std::string filename = os.str(); +/// +/// Queue::Id id = queue.writeGrid(grid, openvdb::io::File(filename)); +/// +/// // Associate the filename with the ID of the queued task. +/// MyNotifier::FilenameMap::accessor acc; +/// notifier.filenames.insert(acc, id); +/// acc->second = filename; +/// } +/// } +/// @endcode +/// Output: +/// @code +/// wrote mygrid.1.vdb +/// wrote mygrid.2.vdb +/// wrote mygrid.4.vdb +/// wrote mygrid.3.vdb +/// ... +/// wrote mygrid.10.vdb +/// @endcode +/// Note that tasks do not necessarily complete in the order in which they were queued. +class OPENVDB_API Queue +{ +public: + /// Default maximum queue length (see setCapacity()) + static const Index32 DEFAULT_CAPACITY = 100; + /// @brief Default maximum time in seconds to wait to queue a task + /// when the queue is full (see setTimeout()) + static const Index32 DEFAULT_TIMEOUT = 120; // seconds + + /// ID number of a queued task or of a registered notification callback + typedef Index32 Id; + + /// Status of a queued task + enum Status { UNKNOWN, PENDING, SUCCEEDED, FAILED }; + + + /// Construct a queue with the given capacity. + explicit Queue(Index32 capacity = DEFAULT_CAPACITY); + /// Block until all queued tasks complete (successfully or unsuccessfully). + ~Queue(); + + /// @brief Return @c true if the queue is empty. + bool empty() const; + /// @brief Return the number of tasks currently in the queue. + Index32 size() const; + + /// @brief Return the maximum number of tasks allowed in the queue. + /// @details Once the queue has reached its maximum size, adding + /// a new task will block until an existing task has executed. + Index32 capacity() const; + /// Set the maximum number of tasks allowed in the queue. + void setCapacity(Index32); + + /// Return the maximum number of seconds to wait to queue a task when the queue is full. + Index32 timeout() const; + /// Set the maximum number of seconds to wait to queue a task when the queue is full. + void setTimeout(Index32 seconds = DEFAULT_TIMEOUT); + + /// @brief Return the status of the task with the given ID. + /// @note Querying the status of a task that has already completed + /// (whether successfully or not) removes the task from the status registry. + /// Subsequent queries of its status will return UNKNOWN. + Status status(Id) const; + + typedef boost::function Notifier; + /// @brief Register a function that will be called with a task's ID + /// and status when that task completes, whether successfully or not. + /// @return an ID that can be passed to removeNotifier() to deregister the function + /// @details When multiple notifiers are registered, they are called + /// in the order in which they were registered. + /// @warning Notifiers are called from worker threads, so they must be thread-safe + /// and their lifetimes must exceed that of the queue. They must also not call, + /// directly or indirectly, addNotifier(), removeNotifier() or clearNotifiers(), + /// as that can result in a deadlock. + Id addNotifier(Notifier); + /// Deregister the notifier with the given ID. + void removeNotifier(Id); + /// Deregister all notifiers. + void clearNotifiers(); + + /// @brief Queue a single grid for output to a file or stream. + /// @param grid the grid to be serialized + /// @param archive the io::File or io::Stream to which to output the grid + /// @param fileMetadata optional file-level metadata + /// @return an ID with which the status of the queued task can be queried + /// @throw RuntimeError if the task cannot be queued within the time limit + /// (see setTimeout()) because the queue is full + /// @par Example: + /// @code + /// openvdb::FloatGrid::Ptr grid = ...; + /// + /// openvdb::io::Queue queue; + /// + /// // Write the grid to the file mygrid.vdb. + /// queue.writeGrid(grid, openvdb::io::File("mygrid.vdb")); + /// + /// // Stream the grid to a binary string. + /// std::ostringstream ostr(std::ios_base::binary); + /// queue.writeGrid(grid, openvdb::io::Stream(ostr)); + /// @endcode + Id writeGrid(GridBase::ConstPtr grid, const Archive& archive, + const MetaMap& fileMetadata = MetaMap()); + + /// @brief Queue a container of grids for output to a file. + /// @param grids any iterable container of grid pointers + /// (e.g., a GridPtrVec or GridPtrSet) + /// @param archive the io::File or io::Stream to which to output the grids + /// @param fileMetadata optional file-level metadata + /// @return an ID with which the status of the queued task can be queried + /// @throw RuntimeError if the task cannot be queued within the time limit + /// (see setTimeout()) because the queue is full + /// @par Example: + /// @code + /// openvdb::FloatGrid::Ptr floatGrid = ...; + /// openvdb::BoolGrid::Ptr boolGrid = ...; + /// openvdb::GridPtrVec grids; + /// grids.push_back(floatGrid); + /// grids.push_back(boolGrid); + /// + /// openvdb::io::Queue queue; + /// + /// // Write the grids to the file mygrid.vdb. + /// queue.write(grids, openvdb::io::File("mygrid.vdb")); + /// + /// // Stream the grids to a (binary) string. + /// std::ostringstream ostr(std::ios_base::binary); + /// queue.write(grids, openvdb::io::Stream(ostr)); + /// @endcode + template + Id write(const GridPtrContainer& grids, const Archive& archive, + const MetaMap& fileMetadata = MetaMap()); + +private: + // Disallow copying of instances of this class. + Queue(const Queue&); + Queue& operator=(const Queue&); + + Id writeGridVec(const GridCPtrVec&, const Archive&, const MetaMap&); + + class Impl; + boost::shared_ptr mImpl; +}; // class Queue + + +template +inline Queue::Id +Queue::write(const GridPtrContainer& container, + const Archive& archive, const MetaMap& metadata) +{ + GridCPtrVec grids; + std::copy(container.begin(), container.end(), std::back_inserter(grids)); + return this->writeGridVec(grids, archive, metadata); +} + +// Specialization for vectors of const Grid pointers; no copying necessary +template<> +inline Queue::Id +Queue::write(const GridCPtrVec& grids, + const Archive& archive, const MetaMap& metadata) +{ + return this->writeGridVec(grids, archive, metadata); +} + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_IO_QUEUE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/io/Stream.cc b/openvdb_2_3_0_library/openvdb/io/Stream.cc new file mode 100755 index 0000000..eb28cda --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/Stream.cc @@ -0,0 +1,152 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Stream.h" + +#include +#include +#include +#include +#include "GridDescriptor.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +Stream::Stream(std::istream& is): mOutputStream(NULL) +{ + if (!is) return; + + readHeader(is); + + // Tag the input stream with the library and file format version numbers + // and the compression options specified in the header. + io::setVersion(is, libraryVersion(), fileVersion()); + io::setDataCompression(is, compressionFlags()); + + // Read in the VDB metadata. + mMeta.reset(new MetaMap); + mMeta->readMeta(is); + + // Read in the number of grids. + const boost::int32_t gridCount = readGridCount(is); + + // Read in all grids and insert them into mGrids. + mGrids.reset(new GridPtrVec); + std::vector descriptors; + descriptors.reserve(gridCount); + Archive::NamedGridMap namedGrids; + for (boost::int32_t i = 0; i < gridCount; ++i) { + GridDescriptor gd; + gd.read(is); + descriptors.push_back(gd); + GridBase::Ptr grid = readGrid(gd, is); + mGrids->push_back(grid); + namedGrids[gd.uniqueName()] = grid; + } + + // Connect instances (grids that share trees with other grids). + for (size_t i = 0, N = descriptors.size(); i < N; ++i) { + Archive::connectInstance(descriptors[i], namedGrids); + } +} + + +Stream::Stream(): mOutputStream(NULL) +{ +} + + +Stream::Stream(std::ostream& os): mOutputStream(&os) +{ +} + + +Stream::~Stream() +{ +} + + +boost::shared_ptr +Stream::copy() const +{ + return boost::shared_ptr(new Stream(*this)); +} + + +//////////////////////////////////////// + + +GridBase::Ptr +Stream::readGrid(const GridDescriptor& gd, std::istream& is) const +{ + GridBase::Ptr grid; + + if (!GridBase::isRegistered(gd.gridType())) { + OPENVDB_THROW(TypeError, "can't read grid \"" + << GridDescriptor::nameAsString(gd.uniqueName()) << + "\" from input stream because grid type " << gd.gridType() << " is unknown"); + } else { + grid = GridBase::createGrid(gd.gridType()); + if (grid) grid->setSaveFloatAsHalf(gd.saveFloatAsHalf()); + + Archive::readGrid(grid, gd, is); + } + return grid; +} + + +void +Stream::writeGrids(std::ostream& os, const GridCPtrVec& grids, const MetaMap& metadata) const +{ + Archive::write(os, grids, /*seekable=*/false, metadata); +} + + +//////////////////////////////////////// + + +MetaMap::Ptr +Stream::getMetadata() const +{ + // Return a deep copy of the file-level metadata, which was read + // when this object was constructed. + return MetaMap::Ptr(new MetaMap(*mMeta)); +} + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/io/Stream.h b/openvdb_2_3_0_library/openvdb/io/Stream.h new file mode 100755 index 0000000..c786e46 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/io/Stream.h @@ -0,0 +1,151 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_IO_STREAM_HAS_BEEN_INCLUDED +#define OPENVDB_IO_STREAM_HAS_BEEN_INCLUDED + +#include +#include "Archive.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace io { + +class GridDescriptor; + + +/// Grid archive associated with arbitrary input and output streams (not necessarily files) +class OPENVDB_API Stream: public Archive +{ +public: + /// Read grids from an input stream. + explicit Stream(std::istream&); + + /// Construct an archive for stream output. + Stream(); + /// Construct an archive for output to the given stream. + explicit Stream(std::ostream&); + + virtual ~Stream(); + + /// @brief Return a copy of this archive. + virtual boost::shared_ptr copy() const; + + /// Return the file-level metadata in a newly created MetaMap. + MetaMap::Ptr getMetadata() const; + + /// Return pointers to the grids that were read from the input stream. + GridPtrVecPtr getGrids() { return mGrids; } + + /// @brief Write the grids in the given container to this archive's output stream. + /// @throw ValueError if this archive was constructed without specifying an output stream. + virtual void write(const GridCPtrVec&, const MetaMap& = MetaMap()) const; + + /// @brief Write the grids in the given container to this archive's output stream. + /// @throw ValueError if this archive was constructed without specifying an output stream. + template + void write(const GridPtrContainerT&, const MetaMap& = MetaMap()) const; + + /// @brief Write the grids in the given container to an output stream. + /// @deprecated Use Stream(os).write(grids) instead. + template + OPENVDB_DEPRECATED void write(std::ostream&, + const GridPtrContainerT&, const MetaMap& = MetaMap()) const; + +private: + /// Create a new grid of the type specified by the given descriptor, + /// then populate the grid from the given input stream. + /// @return the newly created grid. + GridBase::Ptr readGrid(const GridDescriptor&, std::istream&) const; + + void writeGrids(std::ostream&, const GridCPtrVec&, const MetaMap&) const; + + + MetaMap::Ptr mMeta; + GridPtrVecPtr mGrids; + std::ostream* mOutputStream; +}; + + +//////////////////////////////////////// + + +inline void +Stream::write(const GridCPtrVec& grids, const MetaMap& metadata) const +{ + if (mOutputStream == NULL) { + OPENVDB_THROW(ValueError, "no output stream was specified"); + } + this->writeGrids(*mOutputStream, grids, metadata); +} + + +template +inline void +Stream::write(const GridPtrContainerT& container, const MetaMap& metadata) const +{ + if (mOutputStream == NULL) { + OPENVDB_THROW(ValueError, "no output stream was specified"); + } + GridCPtrVec grids; + std::copy(container.begin(), container.end(), std::back_inserter(grids)); + this->writeGrids(*mOutputStream, grids, metadata); +} + + +template +inline void +Stream::write(std::ostream& os, const GridPtrContainerT& container, + const MetaMap& metadata) const +{ + GridCPtrVec grids; + std::copy(container.begin(), container.end(), std::back_inserter(grids)); + this->writeGrids(os, grids, metadata); +} + +template<> +inline void +Stream::write(std::ostream& os, const GridCPtrVec& grids, + const MetaMap& metadata) const +{ + this->writeGrids(os, grids, metadata); +} + +} // namespace io +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_IO_STREAM_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/BBox.h b/openvdb_2_3_0_library/openvdb/math/BBox.h new file mode 100755 index 0000000..b3a32a3 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/BBox.h @@ -0,0 +1,467 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_MATH_BBOX_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_BBOX_HAS_BEEN_INCLUDED + +#include "Math.h" // for math::isApproxEqual() and math::Tolerance() +#include "Vec3.h" +#include +#include // for min/max +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +/// @brief Axis-aligned bounding box +template +class BBox +{ +public: + typedef Vec3T Vec3Type; + typedef Vec3T ValueType; + typedef Vec3T VectorType; + typedef typename Vec3Type::ValueType ElementType; + + /// @brief Default constructor creates an invalid BBox + BBox(); + + /// @brief Constructor based on a minimum and maximum point. + BBox(const Vec3T& xyzMin, const Vec3T& xyzMax); + + /// @brief Constructor based on a minimum and maximum point. + /// If sorted is false the points will be sorted by x,y,z component. + BBox(const Vec3T& xyzMin, const Vec3T& xyzMax, bool sorted); + + /// @brief Contruct a cubical BBox from a minimum coordinate and a + /// single edge length. + /// @note inclusive for integral ElementTypes + BBox(const Vec3T& xyzMin, const ElementType& length); + + /// @brief Constructor based on a raw array of six points. If + /// sorted is false the points will be sorted by x,y,z component. + explicit BBox(const ElementType* xyz, bool sorted = true); + + /// @brief Copy constructor + BBox(const BBox& other); + + /// @brief Sort the min/max by x,y,z component. + void sort(); + + /// @brief Return a const reference to the minimum point of the BBox + const Vec3T& min() const { return mMin; } + + /// @brief Return a const reference to the maximum point of the BBox + const Vec3T& max() const { return mMax; } + + /// @brief Return a non-const reference to the minimum point of the BBox + Vec3T& min() { return mMin; } + + /// @brief Return a non-const reference to the maximum point of the BBox + Vec3T& max() { return mMax; } + + /// @brief Return true if the two BBox'es are identical + bool operator==(const BBox& rhs) const; + + /// @brief Return true if the two BBox'es are not identical + bool operator!=(const BBox& rhs) const { return !(*this == rhs); } + + /// @brief Return true if the BBox is empty, i.e. has no + /// (positive) volume. + bool empty() const; + + /// @brief Return true if the BBox has a (positive) volume. + bool hasVolume() const { return !this->empty(); } + + /// @brief Return true if the BBox is valid, i.e. as a (positive) volume. + operator bool() const { return !this->empty(); } + + /// @brief Return true if the all components of mMin <= mMax, + /// i.e. the volume is not negative. + /// @note For floating point values a tolerance is used for this test. + bool isSorted() const; + + /// @brief Return the center point of the BBox + Vec3d getCenter() const; + + /// @brief Returns the extents of the BBox, i.e. the length per axis + /// for floating points values or number of grids per axis points + /// integral values. + /// @note inclusive for integral ElementTypes + Vec3T extents() const; + + /// @brief Return the volume spanned by this BBox. + ElementType volume() const { Vec3T e = this->extents(); return e[0] * e[1] * e[2]; } + + /// Return the index (0, 1 or 2) of the longest axis. + size_t maxExtent() const { return MaxIndex(mMax - mMin); } + + /// Return the index (0, 1 or 2) of the shortest axis. + size_t minExtent() const { return MinIndex(mMax - mMin); } + + /// Return @c true if point (x, y, z) is inside this bounding box. + bool isInside(const Vec3T& xyz) const; + + /// Return @c true if the given bounding box is inside this bounding box. + bool isInside(const BBox&) const; + + /// Return @c true if the given bounding box overlaps with this bounding box. + bool hasOverlap(const BBox&) const; + + /// Pad this bounding box. + void expand(ElementType padding); + + /// Expand this bounding box to enclose point (x, y, z). + void expand(const Vec3T& xyz); + + /// Union this bounding box with the given bounding box. + void expand(const BBox&); + // @brief Union this bbox with the cubical bbox defined from xyzMin and + // length + /// @note inclusive for integral ElementTypes + void expand(const Vec3T& xyzMin, const ElementType& length); + + /// Translate this bounding box by \f$(t_x, t_y, t_z)\f$. + void translate(const Vec3T& t); + + /// Apply a map to this bounding box + template + BBox applyMap(const MapType& map) const; + + /// Apply the inverse of a map to this bounding box + template + BBox applyInverseMap(const MapType& map) const; + + /// Unserialize this bounding box from the given stream. + void read(std::istream& is) { mMin.read(is); mMax.read(is); } + + /// Serialize this bounding box to the given stream. + void write(std::ostream& os) const { mMin.write(os); mMax.write(os); } + +private: + Vec3T mMin, mMax; +}; // class BBox + + +//////////////////////////////////////// + + +template +inline +BBox::BBox(): + mMin( std::numeric_limits::max()), + mMax(-std::numeric_limits::max()) +{ +} + +template +inline +BBox::BBox(const Vec3T& xyzMin, const Vec3T& xyzMax): + mMin(xyzMin), mMax(xyzMax) +{ +} + +template +inline +BBox::BBox(const Vec3T& xyzMin, const Vec3T& xyzMax, bool sorted): + mMin(xyzMin), mMax(xyzMax) +{ + if (!sorted) this->sort(); +} + +template +inline +BBox::BBox(const Vec3T& xyzMin, const ElementType& length): + mMin(xyzMin), mMax(xyzMin) +{ + // min and max are inclusive for integral ElementType + const ElementType size = boost::is_integral::value ? length-1 : length; + mMax[0] += size; + mMax[1] += size; + mMax[2] += size; +} + +template +inline +BBox::BBox(const ElementType* xyz, bool sorted): + mMin(xyz[0], xyz[1], xyz[2]), + mMax(xyz[3], xyz[4], xyz[5]) +{ + if (!sorted) this->sort(); +} + + +template +inline +BBox::BBox(const BBox& other): + mMin(other.mMin), mMax(other.mMax) +{ +} + + +//////////////////////////////////////// + + +template +inline bool +BBox::empty() const +{ + if (boost::is_integral::value) { + // min and max are inclusive for integral ElementType + return (mMin[0] > mMax[0] || mMin[1] > mMax[1] || mMin[2] > mMax[2]); + } + return mMin[0] >= mMax[0] || mMin[1] >= mMax[1] || mMin[2] >= mMax[2]; +} + + +template +inline bool +BBox::operator==(const BBox& rhs) const +{ + if (boost::is_integral::value) { + return mMin == rhs.min() && mMax == rhs.max(); + } else { + return math::isApproxEqual(mMin, rhs.min()) && math::isApproxEqual(mMax, rhs.max()); + } +} + + +template +inline void +BBox::sort() +{ + Vec3T tMin(mMin), tMax(mMax); + for (size_t i = 0; i < 3; ++i) { + mMin[i] = std::min(tMin[i], tMax[i]); + mMax[i] = std::max(tMin[i], tMax[i]); + } +} + + +template +inline bool +BBox::isSorted() const +{ + if (boost::is_integral::value) { + return (mMin[0] <= mMax[0] && mMin[1] <= mMax[1] && mMin[2] <= mMax[2]); + } else { + ElementType t = math::Tolerance::value(); + return (mMin[0] < (mMax[0] + t) && mMin[1] < (mMax[1] + t) && mMin[2] < (mMax[2] + t)); + } +} + + +template +inline Vec3d +BBox::getCenter() const +{ + return (Vec3d(mMin.asPointer()) + Vec3d(mMax.asPointer())) * 0.5; +} + + +template +inline Vec3T +BBox::extents() const +{ + if (boost::is_integral::value) { + return (mMax - mMin) + Vec3T(1, 1, 1); + } else { + return (mMax - mMin); + } +} + +//////////////////////////////////////// + + +template +inline bool +BBox::isInside(const Vec3T& xyz) const +{ + if (boost::is_integral::value) { + return xyz[0] >= mMin[0] && xyz[0] <= mMax[0] && + xyz[1] >= mMin[1] && xyz[1] <= mMax[1] && + xyz[2] >= mMin[2] && xyz[2] <= mMax[2]; + } else { + ElementType t = math::Tolerance::value(); + return xyz[0] > (mMin[0]-t) && xyz[0] < (mMax[0]+t) && + xyz[1] > (mMin[1]-t) && xyz[1] < (mMax[1]+t) && + xyz[2] > (mMin[2]-t) && xyz[2] < (mMax[2]+t); + } +} + + +template +inline bool +BBox::isInside(const BBox& b) const +{ + if (boost::is_integral::value) { + return b.min()[0] >= mMin[0] && b.max()[0] <= mMax[0] && + b.min()[1] >= mMin[1] && b.max()[1] <= mMax[1] && + b.min()[2] >= mMin[2] && b.max()[2] <= mMax[2]; + } else { + ElementType t = math::Tolerance::value(); + return (b.min()[0]-t) > mMin[0] && (b.max()[0]+t) < mMax[0] && + (b.min()[1]-t) > mMin[1] && (b.max()[1]+t) < mMax[1] && + (b.min()[2]-t) > mMin[2] && (b.max()[2]+t) < mMax[2]; + } +} + + +template +inline bool +BBox::hasOverlap(const BBox& b) const +{ + if (boost::is_integral::value) { + return mMax[0] >= b.min()[0] && mMin[0] <= b.max()[0] && + mMax[1] >= b.min()[1] && mMin[1] <= b.max()[1] && + mMax[2] >= b.min()[2] && mMin[2] <= b.max()[2]; + } else { + ElementType t = math::Tolerance::value(); + return mMax[0] > (b.min()[0]-t) && mMin[0] < (b.max()[0]+t) && + mMax[1] > (b.min()[1]-t) && mMin[1] < (b.max()[1]+t) && + mMax[2] > (b.min()[2]-t) && mMin[2] < (b.max()[2]+t); + } +} + + +//////////////////////////////////////// + + +template +inline void +BBox::expand(ElementType dx) +{ + dx = std::abs(dx); + for (size_t i = 0; i < 3; ++i) { + mMin[i] -= dx; + mMax[i] += dx; + } +} + + +template +inline void +BBox::expand(const Vec3T& xyz) +{ + for (size_t i = 0; i < 3; ++i) { + mMin[i] = std::min(mMin[i], xyz[i]); + mMax[i] = std::max(mMax[i], xyz[i]); + } +} + + +template +inline void +BBox::expand(const BBox& b) +{ + for (size_t i = 0; i < 3; ++i) { + mMin[i] = std::min(mMin[i], b.min()[i]); + mMax[i] = std::max(mMax[i], b.max()[i]); + } +} + +template +inline void +BBox::expand(const Vec3T& xyzMin, const ElementType& length) +{ + const ElementType size = boost::is_integral::value ? length-1 : length; + for (size_t i = 0; i < 3; ++i) { + mMin[i] = std::min(mMin[i], xyzMin[i]); + mMax[i] = std::max(mMax[i], xyzMin[i] + size); + } +} + + +template +inline void +BBox::translate(const Vec3T& dx) +{ + mMin += dx; + mMax += dx; +} + +template +template +inline BBox +BBox::applyMap(const MapType& map) const +{ + typedef Vec3 Vec3R; + BBox bbox; + bbox.expand(map.applyMap(Vec3R(mMin[0], mMin[1], mMin[2]))); + bbox.expand(map.applyMap(Vec3R(mMin[0], mMin[1], mMax[2]))); + bbox.expand(map.applyMap(Vec3R(mMin[0], mMax[1], mMin[2]))); + bbox.expand(map.applyMap(Vec3R(mMax[0], mMin[1], mMin[2]))); + bbox.expand(map.applyMap(Vec3R(mMax[0], mMax[1], mMin[2]))); + bbox.expand(map.applyMap(Vec3R(mMax[0], mMin[1], mMax[2]))); + bbox.expand(map.applyMap(Vec3R(mMin[0], mMax[1], mMax[2]))); + bbox.expand(map.applyMap(Vec3R(mMax[0], mMax[1], mMax[2]))); + return bbox; +} + +template +template +inline BBox +BBox::applyInverseMap(const MapType& map) const +{ + typedef Vec3 Vec3R; + BBox bbox; + bbox.expand(map.applyInverseMap(Vec3R(mMin[0], mMin[1], mMin[2]))); + bbox.expand(map.applyInverseMap(Vec3R(mMin[0], mMin[1], mMax[2]))); + bbox.expand(map.applyInverseMap(Vec3R(mMin[0], mMax[1], mMin[2]))); + bbox.expand(map.applyInverseMap(Vec3R(mMax[0], mMin[1], mMin[2]))); + bbox.expand(map.applyInverseMap(Vec3R(mMax[0], mMax[1], mMin[2]))); + bbox.expand(map.applyInverseMap(Vec3R(mMax[0], mMin[1], mMax[2]))); + bbox.expand(map.applyInverseMap(Vec3R(mMin[0], mMax[1], mMax[2]))); + bbox.expand(map.applyInverseMap(Vec3R(mMax[0], mMax[1], mMax[2]))); + return bbox; +} + +//////////////////////////////////////// + + +template +inline std::ostream& +operator<<(std::ostream& os, const BBox& b) +{ + os << b.min() << " -> " << b.max(); + return os; +} + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_BBOX_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Coord.h b/openvdb_2_3_0_library/openvdb/math/Coord.h new file mode 100755 index 0000000..99e5872 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Coord.h @@ -0,0 +1,444 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_MATH_COORD_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_COORD_HAS_BEEN_INCLUDED + +#include +#include "Math.h" +#include "Vec3.h" + +namespace tbb { class split; } // forward declaration + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +/// @brief Signed (x, y, z) 32-bit integer coordinates +class Coord +{ +public: + typedef int32_t Int32; + typedef uint32_t Index32; + typedef Vec3 Vec3i; + typedef Vec3 Vec3I; + + typedef Int32 ValueType; + typedef std::numeric_limits Limits; + + Coord() + { mVec[0] = mVec[1] = mVec[2] = 0; } + explicit Coord(Int32 xyz) + { mVec[0] = mVec[1] = mVec[2] = xyz; } + Coord(Int32 x, Int32 y, Int32 z) + { mVec[0] = x; mVec[1] = y; mVec[2] = z; } + explicit Coord(const Vec3i& v) + { mVec[0] = v[0]; mVec[1] = v[1]; mVec[2] = v[2]; } + explicit Coord(const Vec3I& v) + { mVec[0] = Int32(v[0]); mVec[1] = Int32(v[1]); mVec[2] = Int32(v[2]); } + explicit Coord(const Int32* v) + { mVec[0] = v[0]; mVec[1] = v[1]; mVec[2] = v[2]; } + + /// @brief Return the smallest possible coordinate + static const Coord& min() { static const Coord sMin(Limits::min()); return sMin; } + + /// @brief Return the largest possible coordinate + static const Coord& max() { static const Coord sMax(Limits::max()); return sMax; } + + /// @brief Return @a xyz rounded to the closest integer coordinates + /// (cell centered conversion). + template static Coord round(const Vec3& xyz) + { return Coord(Int32(Round(xyz[0])), Int32(Round(xyz[1])), Int32(Round(xyz[2]))); } + /// @brief Return the largest integer coordinates that are not greater + /// than @a xyz (node centered conversion). + template static Coord floor(const Vec3& xyz) + { return Coord(Int32(Floor(xyz[0])), Int32(Floor(xyz[1])), Int32(Floor(xyz[2]))); } + + /// @brief Return the largest integer coordinates that are not greater + /// than @a xyz+1 (node centered conversion). + template static Coord ceil(const Vec3& xyz) + { return Coord(Int32(Ceil(xyz[0])), Int32(Ceil(xyz[1])), Int32(Ceil(xyz[2]))); } + + Coord& reset(Int32 x, Int32 y, Int32 z) + { mVec[0] = x; mVec[1] = y; mVec[2] = z; this->dirty(); return *this; } + Coord& reset(Int32 xyz) { return this->reset(xyz, xyz, xyz); } + + Coord& setX(Int32 x) { mVec[0] = x; dirty(); return *this; } + Coord& setY(Int32 y) { mVec[1] = y; dirty(); return *this; } + Coord& setZ(Int32 z) { mVec[2] = z; dirty(); return *this; } + + Coord& offset(Int32 dx, Int32 dy, Int32 dz) + { mVec[0]+=dx; mVec[1]+=dy; mVec[2]+=dz; this->dirty(); return *this; } + Coord& offset(Int32 n) { return this->offset(n, n, n); } + Coord offsetBy(Int32 dx, Int32 dy, Int32 dz) const + { return Coord(mVec[0] + dx, mVec[1] + dy, mVec[2] + dz); } + Coord offsetBy(Int32 n) const { return offsetBy(n, n, n); } + + Coord& operator+=(const Coord& rhs) + { mVec[0] += rhs[0]; mVec[1] += rhs[1]; mVec[2] += rhs[2]; return *this; } + Coord& operator-=(const Coord& rhs) + { mVec[0] -= rhs[0]; mVec[1] -= rhs[1]; mVec[2] -= rhs[2]; return *this; } + Coord operator+(const Coord& rhs) const + { return Coord(mVec[0] + rhs[0], mVec[1] + rhs[1], mVec[2] + rhs[2]); } + Coord operator-(const Coord& rhs) const + { return Coord(mVec[0] - rhs[0], mVec[1] - rhs[1], mVec[2] - rhs[2]); } + Coord operator-() const { return Coord(-mVec[0], -mVec[1], -mVec[2]); } + + Coord operator>> (size_t n) const { return Coord(mVec[0]>>n, mVec[1]>>n, mVec[2]>>n); } + Coord operator<< (size_t n) const { return Coord(mVec[0]<>=(size_t n) { mVec[0]>>=n; mVec[1]>>=n; mVec[2]>>=n; dirty(); return *this; } + Coord operator& (Int32 n) const { return Coord(mVec[0] & n, mVec[1] & n, mVec[2] & n); } + Coord operator| (Int32 n) const { return Coord(mVec[0] | n, mVec[1] | n, mVec[2] | n); } + Coord& operator&= (Int32 n) { mVec[0]&=n; mVec[1]&=n; mVec[2]&=n; dirty(); return *this; } + Coord& operator|= (Int32 n) { mVec[0]|=n; mVec[1]|=n; mVec[2]|=n; dirty(); return *this; } + + Int32 x() const { return mVec[0]; } + Int32 y() const { return mVec[1]; } + Int32 z() const { return mVec[2]; } + Int32 operator[](size_t i) const { assert(i < 3); return mVec[i]; } + Int32& x() { dirty(); return mVec[0]; } + Int32& y() { dirty(); return mVec[1]; } + Int32& z() { dirty(); return mVec[2]; } + Int32& operator[](size_t i) { assert(i < 3); dirty(); return mVec[i]; } + + const Int32* asPointer() const { return mVec; } + Int32* asPointer() { dirty(); return mVec; } + Vec3d asVec3d() const { return Vec3d(double(mVec[0]), double(mVec[1]), double(mVec[2])); } + Vec3s asVec3s() const { return Vec3s(float(mVec[0]), float(mVec[1]), float(mVec[2])); } + Vec3i asVec3i() const { return Vec3i(mVec); } + Vec3I asVec3I() const { return Vec3I(Index32(mVec[0]), Index32(mVec[1]), Index32(mVec[2])); } + void asXYZ(Int32& x, Int32& y, Int32& z) const { x = mVec[0]; y = mVec[1]; z = mVec[2]; } + + bool operator==(const Coord& rhs) const + { return (mVec[0] == rhs.mVec[0] && mVec[1] == rhs.mVec[1] && mVec[2] == rhs.mVec[2]); } + bool operator!=(const Coord& rhs) const { return !(*this == rhs); } + + /// Lexicographic less than + bool operator<(const Coord& rhs) const + { + return this->x() < rhs.x() ? true : this->x() > rhs.x() ? false + : this->y() < rhs.y() ? true : this->y() > rhs.y() ? false + : this->z() < rhs.z() ? true : false; + } + /// Lexicographic less than or equal to + bool operator<=(const Coord& rhs) const + { + return this->x() < rhs.x() ? true : this->x() > rhs.x() ? false + : this->y() < rhs.y() ? true : this->y() > rhs.y() ? false + : this->z() <=rhs.z() ? true : false; + } + /// Lexicographic greater than + bool operator>(const Coord& rhs) const { return !(*this <= rhs); } + /// Lexicographic greater than or equal to + bool operator>=(const Coord& rhs) const { return !(*this < rhs); } + + //HashType hash() { if (!mHash) { mHash = ...; } return mHash; } + + /// Perform a component-wise minimum with the other Coord. + void minComponent(const Coord& other) + { + mVec[0] = std::min(mVec[0], other.mVec[0]); + mVec[1] = std::min(mVec[1], other.mVec[1]); + mVec[2] = std::min(mVec[2], other.mVec[2]); + } + + /// Perform a component-wise maximum with the other Coord. + void maxComponent(const Coord& other) + { + mVec[0] = std::max(mVec[0], other.mVec[0]); + mVec[1] = std::max(mVec[1], other.mVec[1]); + mVec[2] = std::max(mVec[2], other.mVec[2]); + } + + /// Return the component-wise minimum of the two Coords. + static inline Coord minComponent(const Coord& lhs, const Coord& rhs) + { + return Coord(std::min(lhs.x(), rhs.x()), + std::min(lhs.y(), rhs.y()), + std::min(lhs.z(), rhs.z())); + } + + /// Return the component-wise maximum of the two Coords. + static inline Coord maxComponent(const Coord& lhs, const Coord& rhs) + { + return Coord(std::max(lhs.x(), rhs.x()), + std::max(lhs.y(), rhs.y()), + std::max(lhs.z(), rhs.z())); + } + static inline bool lessThan(const Coord& a, const Coord& b) + { + return (a[0] < b[0] || a[1] < b[1] || a[2] < b[2]); + } + + /// @brief Return the index (0, 1 or 2) with the smallest value. + size_t minIndex() const { return MinIndex(mVec); } + + /// @brief Return the index (0, 1 or 2) with the largest value. + size_t maxIndex() const { return MaxIndex(mVec); } + + void read(std::istream& is) { is.read(reinterpret_cast(mVec), sizeof(mVec)); } + void write(std::ostream& os) const + { os.write(reinterpret_cast(mVec), sizeof(mVec)); } + +private: + //no-op for now + void dirty() { /*mHash.clear();*/ } + + Int32 mVec[3]; + //HashType mHash; +}; // class Coord + + +//////////////////////////////////////// + + +/// @brief Axis-aligned bounding box of signed integer coordinates +/// @note The range of the integer coordinates, [min, max], is inclusive. +/// Thus, a bounding box with min = max is not empty but rather encloses +/// a single coordinate. +class CoordBBox +{ +public: + typedef uint64_t Index64; + typedef Coord::ValueType ValueType; + + /// @brief The default constructor produces an empty bounding box. + CoordBBox(): mMin(Coord::max()), mMax(Coord::min()) {} + /// @brief Construct a bounding box with the given @a min and @a max bounds. + CoordBBox(const Coord& min, const Coord& max): mMin(min), mMax(max) {} + /// @brief Splitting constructor for use in TBB ranges + /// @note The other bounding box is assumed to be divisible. + CoordBBox(CoordBBox& other, const tbb::split&): mMin(other.mMin), mMax(other.mMax) + { + assert(this->is_divisible()); + const size_t n = this->maxExtent(); + mMax[n] = (mMin[n] + mMax[n]) >> 1; + other.mMin[n] = mMax[n] + 1; + } + + static CoordBBox createCube(const Coord& min, ValueType dim) + { + return CoordBBox(min, min.offsetBy(dim - 1)); + } + + /// Return an "infinite" bounding box, as defined by the Coord value range. + static CoordBBox inf() { return CoordBBox(Coord::min(), Coord::max()); } + + const Coord& min() const { return mMin; } + const Coord& max() const { return mMax; } + + Coord& min() { return mMin; } + Coord& max() { return mMax; } + + void reset() { mMin = Coord::max(); mMax = Coord::min(); } + void reset(const Coord& min, const Coord& max) { mMin = min; mMax = max; } + void resetToCube(const Coord& min, ValueType dim) { mMin = min; mMax = min.offsetBy(dim - 1); } + + /// @note The start coordinate is inclusive. + Coord getStart() const { return mMin; } + /// @note The end coordinate is exclusive. + Coord getEnd() const { return mMax.offsetBy(1); } + + bool operator==(const CoordBBox& rhs) const { return mMin == rhs.mMin && mMax == rhs.mMax; } + bool operator!=(const CoordBBox& rhs) const { return !(*this == rhs); } + + bool empty() const { return (mMin[0] > mMax[0] || mMin[1] > mMax[1] || mMin[2] > mMax[2]); } + //@{ + /// Return @c true if this bounding box is nonempty + operator bool() const { return !this->empty(); } + bool hasVolume() const { return !this->empty(); } + //@} + + /// Return the floating-point position of the center of this bounding box. + Vec3d getCenter() const { return 0.5 * Vec3d((mMin + mMax).asPointer()); } + + /// @brief Return the dimensions of the coordinates spanned by this bounding box. + /// @note Since coordinates are inclusive, a bounding box with min = max + /// has dimensions of (1, 1, 1). + Coord dim() const { return mMax.offsetBy(1) - mMin; } + /// @todo deprecate - use dim instead + Coord extents() const { return this->dim(); } + /// @brief Return the integer volume of coordinates spanned by this bounding box. + /// @note Since coordinates are inclusive, a bounding box with min = max has volume one. + Index64 volume() const + { + const Coord d = this->dim(); + return Index64(d[0]) * Index64(d[1]) * Index64(d[2]); + } + /// Return @c true if this bounding box can be subdivided [mainly for use by TBB]. + bool is_divisible() const { return mMin[0]dim().minIndex(); } + + /// @brief Return the index (0, 1 or 2) of the longest axis. + size_t maxExtent() const { return this->dim().maxIndex(); } + + /// Return @c true if point (x, y, z) is inside this bounding box. + bool isInside(const Coord& xyz) const + { + return !(Coord::lessThan(xyz,mMin) || Coord::lessThan(mMax,xyz)); + } + + /// Return @c true if the given bounding box is inside this bounding box. + bool isInside(const CoordBBox& b) const + { + return !(Coord::lessThan(b.mMin,mMin) || Coord::lessThan(mMax,b.mMax)); + } + + /// Return @c true if the given bounding box overlaps with this bounding box. + bool hasOverlap(const CoordBBox& b) const + { + return !(Coord::lessThan(mMax,b.mMin) || Coord::lessThan(b.mMax,mMin)); + } + + /// Pad this bounding box with the specified padding. + void expand(ValueType padding) + { + mMin.offset(-padding); + mMax.offset( padding); + } + /// Expand this bounding box to enclose point (x, y, z). + void expand(const Coord& xyz) + { + mMin.minComponent(xyz); + mMax.maxComponent(xyz); + } + /// Union this bounding box with the given bounding box. + void expand(const CoordBBox& bbox) + { + mMin.minComponent(bbox.min()); + mMax.maxComponent(bbox.max()); + } + /// Intersect this bounding box with the given bounding box. + void intersect(const CoordBBox& bbox) + { + mMin.maxComponent(bbox.min()); + mMax.minComponent(bbox.max()); + } + /// @brief Union this bounding box with the cubical bounding box + /// of the given size and with the given minimum coordinates. + void expand(const Coord& min, Coord::ValueType dim) + { + mMin.minComponent(min); + mMax.maxComponent(min.offsetBy(dim-1)); + } + /// Translate this bounding box by @f$(t_x, t_y, t_z)@f$. + void translate(const Coord& t) { mMin += t; mMax += t; } + + /// Unserialize this bounding box from the given stream. + void read(std::istream& is) { mMin.read(is); mMax.read(is); } + /// Serialize this bounding box to the given stream. + void write(std::ostream& os) const { mMin.write(os); mMax.write(os); } + +private: + Coord mMin, mMax; +}; // class CoordBBox + + +//////////////////////////////////////// + + +inline std::ostream& operator<<(std::ostream& os, const Coord& xyz) +{ + os << xyz.asVec3i(); return os; +} + + +//@{ +/// Allow a Coord to be added to or subtracted from a Vec3. +template +inline Vec3::type> +operator+(const Vec3& v0, const Coord& v1) +{ + Vec3::type> result(v0); + result[0] += v1[0]; + result[1] += v1[1]; + result[2] += v1[2]; + return result; +} + +template +inline Vec3::type> +operator+(const Coord& v1, const Vec3& v0) +{ + Vec3::type> result(v0); + result[0] += v1[0]; + result[1] += v1[1]; + result[2] += v1[2]; + return result; +} +//@} + + +//@{ +/// Allow a Coord to be subtracted from a Vec3. +template +inline Vec3::type> +operator-(const Vec3& v0, const Coord& v1) +{ + Vec3::type> result(v0); + result[0] -= v1[0]; + result[1] -= v1[1]; + result[2] -= v1[2]; + return result; +} + +template +inline Vec3::type> +operator-(const Coord& v1, const Vec3& v0) +{ + Vec3::type> result(v0); + result[0] -= v1[0]; + result[1] -= v1[1]; + result[2] -= v1[2]; + return -result; +} +//@} + +inline std::ostream& +operator<<(std::ostream& os, const CoordBBox& b) +{ + os << b.min() << " -> " << b.max(); + return os; +} + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_COORD_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/DDA.h b/openvdb_2_3_0_library/openvdb/math/DDA.h new file mode 100755 index 0000000..562e32e --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/DDA.h @@ -0,0 +1,351 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 (size_t 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 size_t stepAxis = 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 "Math.h" +#include "Coord.h" +#include "Vec3.h" + +#include +#include + +#ifdef DWA_OPENVDB +#include +#endif + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + + +//////////////////////////////////////// + + +/// @brief Different discrete schemes used in the first derivatives. +// Add new items to the *end* of this list, and update NUM_DS_SCHEMES. +enum DScheme { + UNKNOWN_DS = -1, + CD_2NDT = 0, // center difference, 2nd order, but the result must be divided by 2 + CD_2ND, // center difference, 2nd order + CD_4TH, // center difference, 4th order + CD_6TH, // center difference, 6th order + FD_1ST, // forward difference, 1st order + FD_2ND, // forward difference, 2nd order + FD_3RD, // forward difference, 3rd order + BD_1ST, // backward difference, 1st order + BD_2ND, // backward difference, 2nd order + BD_3RD, // backward difference, 3rd order + FD_WENO5, // forward difference, weno5 + BD_WENO5, // backward difference, weno5 + FD_HJWENO5, // forward differene, HJ-weno5 + BD_HJWENO5 // backward difference, HJ-weno5 +}; + +enum { NUM_DS_SCHEMES = BD_HJWENO5 + 1 }; + + +inline std::string +dsSchemeToString(DScheme dss) +{ + std::string ret; + switch (dss) { + case UNKNOWN_DS: ret = "unknown_ds"; break; + case CD_2NDT: ret = "cd_2ndt"; break; + case CD_2ND: ret = "cd_2nd"; break; + case CD_4TH: ret = "cd_4th"; break; + case CD_6TH: ret = "cd_6th"; break; + case FD_1ST: ret = "fd_1st"; break; + case FD_2ND: ret = "fd_2nd"; break; + case FD_3RD: ret = "fd_3rd"; break; + case BD_1ST: ret = "bd_1st"; break; + case BD_2ND: ret = "bd_2nd"; break; + case BD_3RD: ret = "bd_3rd"; break; + case FD_WENO5: ret = "fd_weno5"; break; + case BD_WENO5: ret = "bd_weno5"; break; + case FD_HJWENO5: ret = "fd_hjweno5"; break; + case BD_HJWENO5: ret = "bd_hjweno5"; break; + } + return ret; +} + +inline DScheme +stringToDScheme(const std::string& s) +{ + DScheme ret = UNKNOWN_DS; + + std::string str = s; + boost::trim(str); + boost::to_lower(str); + + if (str == dsSchemeToString(CD_2NDT)) { + ret = CD_2NDT; + } else if (str == dsSchemeToString(CD_2ND)) { + ret = CD_2ND; + } else if (str == dsSchemeToString(CD_4TH)) { + ret = CD_4TH; + } else if (str == dsSchemeToString(CD_6TH)) { + ret = CD_6TH; + } else if (str == dsSchemeToString(FD_1ST)) { + ret = FD_1ST; + } else if (str == dsSchemeToString(FD_2ND)) { + ret = FD_2ND; + } else if (str == dsSchemeToString(FD_3RD)) { + ret = FD_3RD; + } else if (str == dsSchemeToString(BD_1ST)) { + ret = BD_1ST; + } else if (str == dsSchemeToString(BD_2ND)) { + ret = BD_2ND; + } else if (str == dsSchemeToString(BD_3RD)) { + ret = BD_3RD; + } else if (str == dsSchemeToString(FD_WENO5)) { + ret = FD_WENO5; + } else if (str == dsSchemeToString(BD_WENO5)) { + ret = BD_WENO5; + } else if (str == dsSchemeToString(FD_HJWENO5)) { + ret = FD_HJWENO5; + } else if (str == dsSchemeToString(BD_HJWENO5)) { + ret = BD_HJWENO5; + } + + return ret; +} + +inline std::string +dsSchemeToMenuName(DScheme dss) +{ + std::string ret; + switch (dss) { + case UNKNOWN_DS: ret = "Unknown DS scheme"; break; + case CD_2NDT: ret = "Twice 2nd-order center difference"; break; + case CD_2ND: ret = "2nd-order center difference"; break; + case CD_4TH: ret = "4th-order center difference"; break; + case CD_6TH: ret = "6th-order center difference"; break; + case FD_1ST: ret = "1st-order forward difference"; break; + case FD_2ND: ret = "2nd-order forward difference"; break; + case FD_3RD: ret = "3rd-order forward difference"; break; + case BD_1ST: ret = "1st-order backward difference"; break; + case BD_2ND: ret = "2nd-order backward difference"; break; + case BD_3RD: ret = "3rd-order backward difference"; break; + case FD_WENO5: ret = "5th-order WENO forward difference"; break; + case BD_WENO5: ret = "5th-order WENO backward difference"; break; + case FD_HJWENO5: ret = "5th-order HJ-WENO forward difference"; break; + case BD_HJWENO5: ret = "5th-order HJ-WENO backward difference"; break; + } + return ret; +} + + + +//////////////////////////////////////// + + +/// @brief Different discrete schemes used in the second derivatives. +// Add new items to the *end* of this list, and update NUM_DD_SCHEMES. +enum DDScheme { + UNKNOWN_DD = -1, + CD_SECOND = 0, // center difference, 2nd order + CD_FOURTH, // center difference, 4th order + CD_SIXTH // center difference, 6th order +}; + +enum { NUM_DD_SCHEMES = CD_SIXTH + 1 }; + + +//////////////////////////////////////// + + +/// @brief Biased Gradients are limited to non-centered differences +// Add new items to the *end* of this list, and update NUM_BIAS_SCHEMES. +enum BiasedGradientScheme { + UNKNOWN_BIAS = -1, + FIRST_BIAS = 0, // uses FD_1ST & BD_1ST + SECOND_BIAS, // uses FD_2ND & BD_2ND + THIRD_BIAS, // uses FD_3RD & BD_3RD + WENO5_BIAS, // uses WENO5 + HJWENO5_BIAS // uses HJWENO5 +}; + +enum { NUM_BIAS_SCHEMES = HJWENO5_BIAS + 1 }; + +inline std::string +biasedGradientSchemeToString(BiasedGradientScheme bgs) +{ + std::string ret; + switch (bgs) { + case UNKNOWN_BIAS: ret = "unknown_bias"; break; + case FIRST_BIAS: ret = "first_bias"; break; + case SECOND_BIAS: ret = "second_bias"; break; + case THIRD_BIAS: ret = "third_bias"; break; + case WENO5_BIAS: ret = "weno5_bias"; break; + case HJWENO5_BIAS: ret = "hjweno5_bias"; break; + } + return ret; +} + +inline BiasedGradientScheme +stringToBiasedGradientScheme(const std::string& s) +{ + BiasedGradientScheme ret = UNKNOWN_BIAS; + + std::string str = s; + boost::trim(str); + boost::to_lower(str); + + if (str == biasedGradientSchemeToString(FIRST_BIAS)) { + ret = FIRST_BIAS; + } else if (str == biasedGradientSchemeToString(SECOND_BIAS)) { + ret = SECOND_BIAS; + } else if (str == biasedGradientSchemeToString(THIRD_BIAS)) { + ret = THIRD_BIAS; + } else if (str == biasedGradientSchemeToString(WENO5_BIAS)) { + ret = WENO5_BIAS; + } else if (str == biasedGradientSchemeToString(HJWENO5_BIAS)) { + ret = HJWENO5_BIAS; + } + return ret; +} + +inline std::string +biasedGradientSchemeToMenuName(BiasedGradientScheme bgs) +{ + std::string ret; + switch (bgs) { + case UNKNOWN_BIAS: ret = "Unknown biased gradient"; break; + case FIRST_BIAS: ret = "1st-order biased gradient"; break; + case SECOND_BIAS: ret = "2nd-order biased gradient"; break; + case THIRD_BIAS: ret = "3rd-order biased gradient"; break; + case WENO5_BIAS: ret = "5th-order WENO biased gradient"; break; + case HJWENO5_BIAS: ret = "5th-order HJ-WENO biased gradient"; break; + } + return ret; +} + +//////////////////////////////////////// + + +/// @brief Temporal 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.01) +{ + static const double C=13.0/12.0; + // Weno is formulated for non-dimensional equations, here the optional scale2 + // is a reference value (squared) for the function being interpolated. For + // example if 'v' is of order 1000, then scale2 = 10^6 is ok. But in practice + // leave scale2 = 1. + const double eps=1e-6*scale2; + // {\tilde \omega_k} = \gamma_k / ( \beta_k + \epsilon)^2 in Shu's ICASE report) + const double A1=0.1/math::Pow2(C*math::Pow2(v1-2*v2+v3)+0.25*math::Pow2(v1-4*v2+3.0*v3)+eps), + A2=0.6/math::Pow2(C*math::Pow2(v2-2*v3+v4)+0.25*math::Pow2(v2-v4)+eps), + A3=0.3/math::Pow2(C*math::Pow2(v3-2*v4+v5)+0.25*math::Pow2(3.0*v3-4*v4+v5)+eps); + + return ValueType(A1*(2.0*v1 - 7.0*v2 + 11.0*v3) + + A2*(5.0*v3 - v2 + 2.0*v4) + + A3*(2.0*v3 + 5.0*v4 - v5))/(6.0*(A1+A2+A3)); +} + + +template +inline Real GudonovsNormSqrd(bool isOutside, + Real dP_xm, Real dP_xp, + Real dP_ym, Real dP_yp, + Real dP_zm, Real dP_zp) +{ + using math::Max; + using math::Min; + using math::Pow2; + + const Real zero(0); + Real dPLen2; + if (isOutside) { // outside + dPLen2 = Max(Pow2(Max(dP_xm, zero)), Pow2(Min(dP_xp,zero))); // (dP/dx)2 + dPLen2 += Max(Pow2(Max(dP_ym, zero)), Pow2(Min(dP_yp,zero))); // (dP/dy)2 + dPLen2 += Max(Pow2(Max(dP_zm, zero)), Pow2(Min(dP_zp,zero))); // (dP/dz)2 + } else { // inside + dPLen2 = Max(Pow2(Min(dP_xm, zero)), Pow2(Max(dP_xp,zero))); // (dP/dx)2 + dPLen2 += Max(Pow2(Min(dP_ym, zero)), Pow2(Max(dP_yp,zero))); // (dP/dy)2 + dPLen2 += Max(Pow2(Min(dP_zm, zero)), Pow2(Max(dP_zp,zero))); // (dP/dz)2 + } + return dPLen2; // |\nabla\phi|^2 +} + + +template +inline Real +GudonovsNormSqrd(bool isOutside, const Vec3& gradient_m, const Vec3& gradient_p) +{ + return GudonovsNormSqrd(isOutside, + gradient_m[0], gradient_p[0], + gradient_m[1], gradient_p[1], + gradient_m[2], gradient_p[2]); +} + + +#ifdef DWA_OPENVDB +inline simd::Float4 simdMin(const simd::Float4& a, const simd::Float4& b) { + return simd::Float4(_mm_min_ps(a.base(), b.base())); +} +inline simd::Float4 simdMax(const simd::Float4& a, const simd::Float4& b) { + return simd::Float4(_mm_max_ps(a.base(), b.base())); +} + +inline float simdSum(const simd::Float4& v); + +inline simd::Float4 Pow2(const simd::Float4& v) { return v * v; } + +template<> +inline simd::Float4 +WENO5(const simd::Float4& v1, const simd::Float4& v2, const simd::Float4& v3, + const simd::Float4& v4, const simd::Float4& v5, float scale2) +{ + using math::Pow2; + typedef simd::Float4 F4; + const F4 + C(13.0 / 12.0), + eps(1e-6 * scale2), + two(2.0), three(3.0), four(4.0), five(5.0), fourth(0.25), + A1 = F4(0.1) / Pow2(C*Pow2(v1-two*v2+v3) + fourth*Pow2(v1-four*v2+three*v3) + eps), + A2 = F4(0.6) / Pow2(C*Pow2(v2-two*v3+v4) + fourth*Pow2(v2-v4) + eps), + A3 = F4(0.3) / Pow2(C*Pow2(v3-two*v4+v5) + fourth*Pow2(three*v3-four*v4+v5) + eps); + return (A1 * (two * v1 - F4(7.0) * v2 + F4(11.0) * v3) + + A2 * (five * v3 - v2 + two * v4) + + A3 * (two * v3 + five * v4 - v5)) / (F4(6.0) * (A1 + A2 + A3)); +} + + +inline float +simdSum(const simd::Float4& v) +{ + // temp = { v3+v3, v2+v2, v1+v3, v0+v2 } + __m128 temp = _mm_add_ps(v.base(), _mm_movehl_ps(v.base(), v.base())); + // temp = { v3+v3, v2+v2, v1+v3, (v0+v2)+(v1+v3) } + temp = _mm_add_ss(temp, _mm_shuffle_ps(temp, temp, 1)); + return _mm_cvtss_f32(temp); +} + +inline float +GudonovsNormSqrd(bool isOutside, const simd::Float4& dP_m, const simd::Float4& dP_p) +{ + const simd::Float4 zero(0.0); + simd::Float4 v = isOutside + ? simdMax(math::Pow2(simdMax(dP_m, zero)), math::Pow2(simdMin(dP_p, zero))) + : simdMax(math::Pow2(simdMin(dP_m, zero)), math::Pow2(simdMax(dP_p, zero))); + return simdSum(v);//should be v[0]+v[1]+v[2] +} +#endif + +template +struct D1 +{ + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk); + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk); + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk); + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S); + + template + static typename Stencil::ValueType inY(const Stencil& S); + + template + static typename Stencil::ValueType inZ(const Stencil& S); +}; + +template<> +struct D1 +{ + // the difference opperator + template + static ValueType difference(const ValueType& xp1, const ValueType& xm1) { + return xp1 - xm1; + } + + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + return difference( + grid.getValue(ijk.offsetBy(1, 0, 0)), + grid.getValue(ijk.offsetBy(-1, 0, 0))); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + return difference( + grid.getValue(ijk.offsetBy(0, 1, 0)), + grid.getValue(ijk.offsetBy( 0, -1, 0))); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + return difference( + grid.getValue(ijk.offsetBy(0, 0, 1)), + grid.getValue(ijk.offsetBy( 0, 0, -1))); + } + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + return difference( S.template getValue< 1, 0, 0>(), S.template getValue<-1, 0, 0>()); + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + return difference( S.template getValue< 0, 1, 0>(), S.template getValue< 0,-1, 0>()); + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + return difference( S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0,-1>()); + } +}; + +template<> +struct D1 +{ + + // the difference opperator + template + static ValueType difference(const ValueType& xp1, const ValueType& xm1) { + return (xp1 - xm1)*ValueType(0.5); + } + + + // random access + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + return difference( + grid.getValue(ijk.offsetBy(1, 0, 0)), + grid.getValue(ijk.offsetBy(-1, 0, 0))); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + return difference( + grid.getValue(ijk.offsetBy(0, 1, 0)), + grid.getValue(ijk.offsetBy( 0, -1, 0))); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + return difference( + grid.getValue(ijk.offsetBy(0, 0, 1)), + grid.getValue(ijk.offsetBy( 0, 0, -1))); + } + + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + 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 ValueType(1./3.)*xp3 - ValueType(1.5)*xp2 + + ValueType(3.)*xp1 - ValueType(11./6.)*xp0; + } + + + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + return difference( grid.getValue(ijk.offsetBy(3,0,0)), + grid.getValue(ijk.offsetBy(2,0,0)), + grid.getValue(ijk.offsetBy(1,0,0)), + grid.getValue(ijk) ); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + return difference( grid.getValue(ijk.offsetBy(0,3,0)), + grid.getValue(ijk.offsetBy(0,2,0)), + grid.getValue(ijk.offsetBy(0,1,0)), + grid.getValue(ijk) ); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + return difference( grid.getValue(ijk.offsetBy(0,0,3)), + grid.getValue(ijk.offsetBy(0,0,2)), + grid.getValue(ijk.offsetBy(0,0,1)), + grid.getValue(ijk) ); + } + + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + return difference(S.template getValue< 3, 0, 0>(), + S.template getValue< 2, 0, 0>(), + S.template getValue< 1, 0, 0>(), + S.template getValue< 0, 0, 0>() ); + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + return difference(S.template getValue< 0, 3, 0>(), + S.template getValue< 0, 2, 0>(), + S.template getValue< 0, 1, 0>(), + S.template getValue< 0, 0, 0>() ); + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + return difference( S.template getValue< 0, 0, 3>(), + S.template getValue< 0, 0, 2>(), + S.template getValue< 0, 0, 1>(), + S.template getValue< 0, 0, 0>() ); + } +}; + + +template<> +struct D1 +{ + + // the difference opperator + template + static ValueType difference(const ValueType& xm1, const ValueType& xm0) { + return -D1::difference(xm1, xm0); + } + + + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + return difference(grid.getValue(ijk.offsetBy(-1,0,0)), grid.getValue(ijk)); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + return difference(grid.getValue(ijk.offsetBy(0,-1,0)), grid.getValue(ijk)); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + return difference(grid.getValue(ijk.offsetBy(0, 0,-1)), grid.getValue(ijk)); + } + + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + return difference(S.template getValue<-1, 0, 0>(), S.template getValue< 0, 0, 0>()); + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + return difference(S.template getValue< 0,-1, 0>(), S.template getValue< 0, 0, 0>()); + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + return difference(S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0, 0>()); + } +}; + + +template<> +struct D1 +{ + + // the difference opperator + template + static ValueType difference(const ValueType& xm2, const ValueType& xm1, const ValueType& xm0) + { + return -D1::difference(xm2, xm1, xm0); + } + + + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + return difference( grid.getValue(ijk.offsetBy(-2,0,0)), + grid.getValue(ijk.offsetBy(-1,0,0)), + grid.getValue(ijk) ); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + return difference( grid.getValue(ijk.offsetBy(0,-2,0)), + grid.getValue(ijk.offsetBy(0,-1,0)), + grid.getValue(ijk) ); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + return difference( grid.getValue(ijk.offsetBy(0,0,-2)), + grid.getValue(ijk.offsetBy(0,0,-1)), + grid.getValue(ijk) ); + } + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + return difference( S.template getValue<-2, 0, 0>(), + S.template getValue<-1, 0, 0>(), + S.template getValue< 0, 0, 0>() ); + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + return difference( S.template getValue< 0,-2, 0>(), + S.template getValue< 0,-1, 0>(), + S.template getValue< 0, 0, 0>() ); + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + return difference( S.template getValue< 0, 0,-2>(), + S.template getValue< 0, 0,-1>(), + S.template getValue< 0, 0, 0>() ); + } +}; + + +template<> +struct D1 +{ + + // the difference opperator + template + static ValueType difference(const ValueType& xm3, const ValueType& xm2, + const ValueType& xm1, const ValueType& xm0) + { + return -D1::difference(xm3, xm2, xm1, xm0); + } + + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + return difference( grid.getValue(ijk.offsetBy(-3,0,0)), + grid.getValue(ijk.offsetBy(-2,0,0)), + grid.getValue(ijk.offsetBy(-1,0,0)), + grid.getValue(ijk) ); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + return difference( grid.getValue(ijk.offsetBy( 0,-3,0)), + grid.getValue(ijk.offsetBy( 0,-2,0)), + grid.getValue(ijk.offsetBy( 0,-1,0)), + grid.getValue(ijk) ); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + return difference( grid.getValue(ijk.offsetBy( 0, 0,-3)), + grid.getValue(ijk.offsetBy( 0, 0,-2)), + grid.getValue(ijk.offsetBy( 0, 0,-1)), + grid.getValue(ijk) ); + } + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + return difference( S.template getValue<-3, 0, 0>(), + S.template getValue<-2, 0, 0>(), + S.template getValue<-1, 0, 0>(), + S.template getValue< 0, 0, 0>() ); + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + return difference( S.template getValue< 0,-3, 0>(), + S.template getValue< 0,-2, 0>(), + S.template getValue< 0,-1, 0>(), + S.template getValue< 0, 0, 0>() ); + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + return difference( S.template getValue< 0, 0,-3>(), + S.template getValue< 0, 0,-2>(), + S.template getValue< 0, 0,-1>(), + S.template getValue< 0, 0, 0>() ); + } + +}; + +template<> +struct D1 +{ + // the difference opperator + template + static ValueType difference(const ValueType& xp3, const ValueType& xp2, + const ValueType& xp1, const ValueType& xp0, + const ValueType& xm1, const ValueType& xm2) { + return WENO5(xp3, xp2, xp1, xp0, xm1) + - WENO5(xp2, xp1, xp0, xm1, xm2); + } + + + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(3,0,0)); + V[1] = grid.getValue(ijk.offsetBy(2,0,0)); + V[2] = grid.getValue(ijk.offsetBy(1,0,0)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(-1,0,0)); + V[5] = grid.getValue(ijk.offsetBy(-2,0,0)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(0,3,0)); + V[1] = grid.getValue(ijk.offsetBy(0,2,0)); + V[2] = grid.getValue(ijk.offsetBy(0,1,0)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(0,-1,0)); + V[5] = grid.getValue(ijk.offsetBy(0,-2,0)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(0,0,3)); + V[1] = grid.getValue(ijk.offsetBy(0,0,2)); + V[2] = grid.getValue(ijk.offsetBy(0,0,1)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(0,0,-1)); + V[5] = grid.getValue(ijk.offsetBy(0,0,-2)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + + return difference( S.template getValue< 3, 0, 0>(), + S.template getValue< 2, 0, 0>(), + S.template getValue< 1, 0, 0>(), + S.template getValue< 0, 0, 0>(), + S.template getValue<-1, 0, 0>(), + S.template getValue<-2, 0, 0>() ); + + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + return difference( S.template getValue< 0, 3, 0>(), + S.template getValue< 0, 2, 0>(), + S.template getValue< 0, 1, 0>(), + S.template getValue< 0, 0, 0>(), + S.template getValue< 0,-1, 0>(), + S.template getValue< 0,-2, 0>() ); + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + + return difference( S.template getValue< 0, 0, 3>(), + S.template getValue< 0, 0, 2>(), + S.template getValue< 0, 0, 1>(), + S.template getValue< 0, 0, 0>(), + S.template getValue< 0, 0,-1>(), + S.template getValue< 0, 0,-2>() ); + } +}; + +template<> +struct D1 +{ + + // the difference opperator + template + static ValueType difference(const ValueType& xp3, const ValueType& xp2, + const ValueType& xp1, const ValueType& xp0, + const ValueType& xm1, const ValueType& xm2) { + return WENO5(xp3 - xp2, xp2 - xp1, xp1 - xp0, xp0-xm1, xm1-xm2); + } + + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(3,0,0)); + V[1] = grid.getValue(ijk.offsetBy(2,0,0)); + V[2] = grid.getValue(ijk.offsetBy(1,0,0)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(-1,0,0)); + V[5] = grid.getValue(ijk.offsetBy(-2,0,0)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(0,3,0)); + V[1] = grid.getValue(ijk.offsetBy(0,2,0)); + V[2] = grid.getValue(ijk.offsetBy(0,1,0)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(0,-1,0)); + V[5] = grid.getValue(ijk.offsetBy(0,-2,0)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(0,0,3)); + V[1] = grid.getValue(ijk.offsetBy(0,0,2)); + V[2] = grid.getValue(ijk.offsetBy(0,0,1)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(0,0,-1)); + V[5] = grid.getValue(ijk.offsetBy(0,0,-2)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + + return difference( S.template getValue< 3, 0, 0>(), + S.template getValue< 2, 0, 0>(), + S.template getValue< 1, 0, 0>(), + S.template getValue< 0, 0, 0>(), + S.template getValue<-1, 0, 0>(), + S.template getValue<-2, 0, 0>() ); + + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + return difference( S.template getValue< 0, 3, 0>(), + S.template getValue< 0, 2, 0>(), + S.template getValue< 0, 1, 0>(), + S.template getValue< 0, 0, 0>(), + S.template getValue< 0,-1, 0>(), + S.template getValue< 0,-2, 0>() ); + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + + return difference( S.template getValue< 0, 0, 3>(), + S.template getValue< 0, 0, 2>(), + S.template getValue< 0, 0, 1>(), + S.template getValue< 0, 0, 0>(), + S.template getValue< 0, 0,-1>(), + S.template getValue< 0, 0,-2>() ); + } + +}; + +template<> +struct D1 +{ + + template + static ValueType difference(const ValueType& xm3, const ValueType& xm2, const ValueType& xm1, + const ValueType& xm0, const ValueType& xp1, const ValueType& xp2) + { + return -D1::difference(xm3, xm2, xm1, xm0, xp1, xp2); + } + + + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(-3,0,0)); + V[1] = grid.getValue(ijk.offsetBy(-2,0,0)); + V[2] = grid.getValue(ijk.offsetBy(-1,0,0)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(1,0,0)); + V[5] = grid.getValue(ijk.offsetBy(2,0,0)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(0,-3,0)); + V[1] = grid.getValue(ijk.offsetBy(0,-2,0)); + V[2] = grid.getValue(ijk.offsetBy(0,-1,0)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(0,1,0)); + V[5] = grid.getValue(ijk.offsetBy(0,2,0)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(0,0,-3)); + V[1] = grid.getValue(ijk.offsetBy(0,0,-2)); + V[2] = grid.getValue(ijk.offsetBy(0,0,-1)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(0,0,1)); + V[5] = grid.getValue(ijk.offsetBy(0,0,2)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + typedef typename Stencil::ValueType ValueType; + ValueType V[6]; + V[0] = S.template getValue<-3, 0, 0>(); + V[1] = S.template getValue<-2, 0, 0>(); + V[2] = S.template getValue<-1, 0, 0>(); + V[3] = S.template getValue< 0, 0, 0>(); + V[4] = S.template getValue< 1, 0, 0>(); + V[5] = S.template getValue< 2, 0, 0>(); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + typedef typename Stencil::ValueType ValueType; + ValueType V[6]; + V[0] = S.template getValue< 0,-3, 0>(); + V[1] = S.template getValue< 0,-2, 0>(); + V[2] = S.template getValue< 0,-1, 0>(); + V[3] = S.template getValue< 0, 0, 0>(); + V[4] = S.template getValue< 0, 1, 0>(); + V[5] = S.template getValue< 0, 2, 0>(); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + typedef typename Stencil::ValueType ValueType; + ValueType V[6]; + V[0] = S.template getValue< 0, 0,-3>(); + V[1] = S.template getValue< 0, 0,-2>(); + V[2] = S.template getValue< 0, 0,-1>(); + V[3] = S.template getValue< 0, 0, 0>(); + V[4] = S.template getValue< 0, 0, 1>(); + V[5] = S.template getValue< 0, 0, 2>(); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } +}; + + +template<> +struct D1 +{ + template + static ValueType difference(const ValueType& xm3, const ValueType& xm2, const ValueType& xm1, + const ValueType& xm0, const ValueType& xp1, const ValueType& xp2) + { + return -D1::difference(xm3, xm2, xm1, xm0, xp1, xp2); + } + + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(-3,0,0)); + V[1] = grid.getValue(ijk.offsetBy(-2,0,0)); + V[2] = grid.getValue(ijk.offsetBy(-1,0,0)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(1,0,0)); + V[5] = grid.getValue(ijk.offsetBy(2,0,0)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(0,-3,0)); + V[1] = grid.getValue(ijk.offsetBy(0,-2,0)); + V[2] = grid.getValue(ijk.offsetBy(0,-1,0)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(0,1,0)); + V[5] = grid.getValue(ijk.offsetBy(0,2,0)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType V[6]; + V[0] = grid.getValue(ijk.offsetBy(0,0,-3)); + V[1] = grid.getValue(ijk.offsetBy(0,0,-2)); + V[2] = grid.getValue(ijk.offsetBy(0,0,-1)); + V[3] = grid.getValue(ijk); + V[4] = grid.getValue(ijk.offsetBy(0,0,1)); + V[5] = grid.getValue(ijk.offsetBy(0,0,2)); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + typedef typename Stencil::ValueType ValueType; + ValueType V[6]; + V[0] = S.template getValue<-3, 0, 0>(); + V[1] = S.template getValue<-2, 0, 0>(); + V[2] = S.template getValue<-1, 0, 0>(); + V[3] = S.template getValue< 0, 0, 0>(); + V[4] = S.template getValue< 1, 0, 0>(); + V[5] = S.template getValue< 2, 0, 0>(); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + typedef typename Stencil::ValueType ValueType; + ValueType V[6]; + V[0] = S.template getValue< 0,-3, 0>(); + V[1] = S.template getValue< 0,-2, 0>(); + V[2] = S.template getValue< 0,-1, 0>(); + V[3] = S.template getValue< 0, 0, 0>(); + V[4] = S.template getValue< 0, 1, 0>(); + V[5] = S.template getValue< 0, 2, 0>(); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + typedef typename Stencil::ValueType ValueType; + ValueType V[6]; + V[0] = S.template getValue< 0, 0,-3>(); + V[1] = S.template getValue< 0, 0,-2>(); + V[2] = S.template getValue< 0, 0,-1>(); + V[3] = S.template getValue< 0, 0, 0>(); + V[4] = S.template getValue< 0, 0, 1>(); + V[5] = S.template getValue< 0, 0, 2>(); + + return difference(V[0], V[1], V[2], V[3], V[4], V[5]); + } +}; + + +template +struct D1Vec +{ + // random access version + template + static typename Accessor::ValueType::value_type + inX(const Accessor& grid, const Coord& ijk, int n) + { + return D1::inX(grid, ijk)[n]; + } + + template + static typename Accessor::ValueType::value_type + inY(const Accessor& grid, const Coord& ijk, int n) + { + return D1::inY(grid, ijk)[n]; + } + template + static typename Accessor::ValueType::value_type + inZ(const Accessor& grid, const Coord& ijk, int n) + { + return D1::inZ(grid, ijk)[n]; + } + + + // stencil access version + template + static typename Stencil::ValueType::value_type inX(const Stencil& S, int n) + { + return D1::inX(S)[n]; + } + + template + static typename Stencil::ValueType::value_type inY(const Stencil& S, int n) + { + return D1::inY(S)[n]; + } + + template + static typename Stencil::ValueType::value_type inZ(const Stencil& S, int n) + { + return D1::inZ(S)[n]; + } +}; + + +template<> +struct D1Vec +{ + + // random access version + template + static typename Accessor::ValueType::value_type + inX(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( grid.getValue(ijk.offsetBy( 1, 0, 0))[n], + grid.getValue(ijk.offsetBy(-1, 0, 0))[n] ); + } + + template + static typename Accessor::ValueType::value_type + inY(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( grid.getValue(ijk.offsetBy(0, 1, 0))[n], + grid.getValue(ijk.offsetBy(0,-1, 0))[n] ); + } + + template + static typename Accessor::ValueType::value_type + inZ(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( grid.getValue(ijk.offsetBy(0, 0, 1))[n], + grid.getValue(ijk.offsetBy(0, 0,-1))[n] ); + } + + // stencil access version + template + static typename Stencil::ValueType::value_type inX(const Stencil& S, int n) + { + return D1::difference( S.template getValue< 1, 0, 0>()[n], + S.template getValue<-1, 0, 0>()[n] ); + } + + template + static typename Stencil::ValueType::value_type inY(const Stencil& S, int n) + { + return D1::difference( S.template getValue< 0, 1, 0>()[n], + S.template getValue< 0,-1, 0>()[n] ); + } + + template + static typename Stencil::ValueType::value_type inZ(const Stencil& S, int n) + { + return D1::difference( S.template getValue< 0, 0, 1>()[n], + S.template getValue< 0, 0,-1>()[n] ); + } +}; + +template<> +struct D1Vec +{ + + // random access version + template + static typename Accessor::ValueType::value_type + inX(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( grid.getValue(ijk.offsetBy( 1, 0, 0))[n] , + grid.getValue(ijk.offsetBy(-1, 0, 0))[n] ); + } + + template + static typename Accessor::ValueType::value_type + inY(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( grid.getValue(ijk.offsetBy(0, 1, 0))[n] , + grid.getValue(ijk.offsetBy(0,-1, 0))[n] ); + } + + template + static typename Accessor::ValueType::value_type + inZ(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( grid.getValue(ijk.offsetBy(0, 0, 1))[n] , + grid.getValue(ijk.offsetBy(0, 0,-1))[n] ); + } + + + // stencil access version + template + static typename Stencil::ValueType::value_type inX(const Stencil& S, int n) + { + return D1::difference( S.template getValue< 1, 0, 0>()[n], + S.template getValue<-1, 0, 0>()[n] ); + } + + template + static typename Stencil::ValueType::value_type inY(const Stencil& S, int n) + { + return D1::difference( S.template getValue< 0, 1, 0>()[n], + S.template getValue< 0,-1, 0>()[n] ); + } + + template + static typename Stencil::ValueType::value_type inZ(const Stencil& S, int n) + { + return D1::difference( S.template getValue< 0, 0, 1>()[n], + S.template getValue< 0, 0,-1>()[n] ); + } +}; + + +template<> +struct D1Vec { + // typedef typename Accessor::ValueType::value_type value_type; + + + // random access version + template + static typename Accessor::ValueType::value_type + inX(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( + grid.getValue(ijk.offsetBy(2, 0, 0))[n], grid.getValue(ijk.offsetBy( 1, 0, 0))[n], + grid.getValue(ijk.offsetBy(-1,0, 0))[n], grid.getValue(ijk.offsetBy(-2, 0, 0))[n]); + } + + template + static typename Accessor::ValueType::value_type + inY(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( + grid.getValue(ijk.offsetBy( 0, 2, 0))[n], grid.getValue(ijk.offsetBy( 0, 1, 0))[n], + grid.getValue(ijk.offsetBy( 0,-1, 0))[n], grid.getValue(ijk.offsetBy( 0,-2, 0))[n]); + } + + template + static typename Accessor::ValueType::value_type + inZ(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( + grid.getValue(ijk.offsetBy(0,0, 2))[n], grid.getValue(ijk.offsetBy( 0, 0, 1))[n], + grid.getValue(ijk.offsetBy(0,0,-1))[n], grid.getValue(ijk.offsetBy( 0, 0,-2))[n]); + } + + // stencil access version + template + static typename Stencil::ValueType::value_type inX(const Stencil& S, int n) + { + return D1::difference( + S.template getValue< 2, 0, 0>()[n], S.template getValue< 1, 0, 0>()[n], + S.template getValue<-1, 0, 0>()[n], S.template getValue<-2, 0, 0>()[n] ); + } + + template + static typename Stencil::ValueType::value_type inY(const Stencil& S, int n) + { + return D1::difference( + S.template getValue< 0, 2, 0>()[n], S.template getValue< 0, 1, 0>()[n], + S.template getValue< 0,-1, 0>()[n], S.template getValue< 0,-2, 0>()[n]); + } + + template + static typename Stencil::ValueType::value_type inZ(const Stencil& S, int n) + { + return D1::difference( + S.template getValue< 0, 0, 2>()[n], S.template getValue< 0, 0, 1>()[n], + S.template getValue< 0, 0,-1>()[n], S.template getValue< 0, 0,-2>()[n]); + } +}; + + +template<> +struct D1Vec +{ + //typedef typename Accessor::ValueType::value_type::value_type ValueType; + + // random access version + template + static typename Accessor::ValueType::value_type + inX(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( + grid.getValue(ijk.offsetBy( 3, 0, 0))[n], grid.getValue(ijk.offsetBy( 2, 0, 0))[n], + grid.getValue(ijk.offsetBy( 1, 0, 0))[n], grid.getValue(ijk.offsetBy(-1, 0, 0))[n], + grid.getValue(ijk.offsetBy(-2, 0, 0))[n], grid.getValue(ijk.offsetBy(-3, 0, 0))[n] ); + } + + template + static typename Accessor::ValueType::value_type + inY(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( + grid.getValue(ijk.offsetBy( 0, 3, 0))[n], grid.getValue(ijk.offsetBy( 0, 2, 0))[n], + grid.getValue(ijk.offsetBy( 0, 1, 0))[n], grid.getValue(ijk.offsetBy( 0,-1, 0))[n], + grid.getValue(ijk.offsetBy( 0,-2, 0))[n], grid.getValue(ijk.offsetBy( 0,-3, 0))[n] ); + } + + template + static typename Accessor::ValueType::value_type + inZ(const Accessor& grid, const Coord& ijk, int n) + { + return D1::difference( + grid.getValue(ijk.offsetBy( 0, 0, 3))[n], grid.getValue(ijk.offsetBy( 0, 0, 2))[n], + grid.getValue(ijk.offsetBy( 0, 0, 1))[n], grid.getValue(ijk.offsetBy( 0, 0,-1))[n], + grid.getValue(ijk.offsetBy( 0, 0,-2))[n], grid.getValue(ijk.offsetBy( 0, 0,-3))[n] ); + } + + + // stencil access version + template + static typename Stencil::ValueType::value_type inX(const Stencil& S, int n) + { + return D1::difference( + S.template getValue< 3, 0, 0>()[n], S.template getValue< 2, 0, 0>()[n], + S.template getValue< 1, 0, 0>()[n], S.template getValue<-1, 0, 0>()[n], + S.template getValue<-2, 0, 0>()[n], S.template getValue<-3, 0, 0>()[n] ); + } + + template + static typename Stencil::ValueType::value_type inY(const Stencil& S, int n) + { + return D1::difference( + S.template getValue< 0, 3, 0>()[n], S.template getValue< 0, 2, 0>()[n], + S.template getValue< 0, 1, 0>()[n], S.template getValue< 0,-1, 0>()[n], + S.template getValue< 0,-2, 0>()[n], S.template getValue< 0,-3, 0>()[n] ); + } + + template + static typename Stencil::ValueType::value_type inZ(const Stencil& S, int n) + { + return D1::difference( + S.template getValue< 0, 0, 3>()[n], S.template getValue< 0, 0, 2>()[n], + S.template getValue< 0, 0, 1>()[n], S.template getValue< 0, 0,-1>()[n], + S.template getValue< 0, 0,-2>()[n], S.template getValue< 0, 0,-3>()[n] ); + } +}; + +template +struct D2 +{ + + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk); + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk); + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk); + + // cross derivatives + template + static typename Accessor::ValueType inXandY(const Accessor& grid, const Coord& ijk); + + template + static typename Accessor::ValueType inXandZ(const Accessor& grid, const Coord& ijk); + + template + static typename Accessor::ValueType inYandZ(const Accessor& grid, const Coord& ijk); + + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S); + template + static typename Stencil::ValueType inY(const Stencil& S); + template + static typename Stencil::ValueType inZ(const Stencil& S); + + // cross derivatives + template + static typename Stencil::ValueType inXandY(const Stencil& S); + + template + static typename Stencil::ValueType inXandZ(const Stencil& S); + + template + static typename Stencil::ValueType inYandZ(const Stencil& S); +}; + +template<> +struct D2 +{ + + // the difference opperator + template + static ValueType difference(const ValueType& xp1, const ValueType& xp0, const ValueType& xm1) + { + return xp1 + xm1 - ValueType(2)*xp0; + } + + template + static ValueType crossdifference(const ValueType& xpyp, const ValueType& xpym, + const ValueType& xmyp, const ValueType& xmym) + { + return ValueType(0.25)*(xpyp + xmym - xpym - xmyp); + } + + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + return difference( grid.getValue(ijk.offsetBy( 1,0,0)), grid.getValue(ijk), + grid.getValue(ijk.offsetBy(-1,0,0)) ); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + + return difference( grid.getValue(ijk.offsetBy(0, 1,0)), grid.getValue(ijk), + grid.getValue(ijk.offsetBy(0,-1,0)) ); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + return difference( grid.getValue(ijk.offsetBy( 0,0, 1)), grid.getValue(ijk), + grid.getValue(ijk.offsetBy( 0,0,-1)) ); + } + + // cross derivatives + template + static typename Accessor::ValueType inXandY(const Accessor& grid, const Coord& ijk) + { + return crossdifference( + grid.getValue(ijk.offsetBy(1, 1,0)), grid.getValue(ijk.offsetBy( 1,-1,0)), + grid.getValue(ijk.offsetBy(-1,1,0)), grid.getValue(ijk.offsetBy(-1,-1,0))); + + } + + template + static typename Accessor::ValueType inXandZ(const Accessor& grid, const Coord& ijk) + { + return crossdifference( + grid.getValue(ijk.offsetBy(1,0, 1)), grid.getValue(ijk.offsetBy(1, 0,-1)), + grid.getValue(ijk.offsetBy(-1,0,1)), grid.getValue(ijk.offsetBy(-1,0,-1)) ); + } + + template + static typename Accessor::ValueType inYandZ(const Accessor& grid, const Coord& ijk) + { + return crossdifference( + grid.getValue(ijk.offsetBy(0, 1,1)), grid.getValue(ijk.offsetBy(0, 1,-1)), + grid.getValue(ijk.offsetBy(0,-1,1)), grid.getValue(ijk.offsetBy(0,-1,-1)) ); + } + + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + return difference( S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>(), + S.template getValue<-1, 0, 0>() ); + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + return difference( S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>(), + S.template getValue< 0,-1, 0>() ); + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + return difference( S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>(), + S.template getValue< 0, 0,-1>() ); + } + + // cross derivatives + template + static typename Stencil::ValueType inXandY(const Stencil& S) + { + return crossdifference(S.template getValue< 1, 1, 0>(), S.template getValue< 1,-1, 0>(), + S.template getValue<-1, 1, 0>(), S.template getValue<-1,-1, 0>() ); + } + + template + static typename Stencil::ValueType inXandZ(const Stencil& S) + { + return crossdifference(S.template getValue< 1, 0, 1>(), S.template getValue< 1, 0,-1>(), + S.template getValue<-1, 0, 1>(), S.template getValue<-1, 0,-1>() ); + } + + template + static typename Stencil::ValueType inYandZ(const Stencil& S) + { + return crossdifference(S.template getValue< 0, 1, 1>(), S.template getValue< 0, 1,-1>(), + S.template getValue< 0,-1, 1>(), S.template getValue< 0,-1,-1>() ); + } +}; + + +template<> +struct D2 +{ + + // the difference opperator + template + static ValueType difference(const ValueType& xp2, const ValueType& xp1, const ValueType& xp0, + const ValueType& xm1, const ValueType& xm2) { + return ValueType(-1./12.)*(xp2 + xm2) + ValueType(4./3.)*(xp1 + xm1) -ValueType(2.5)*xp0; + } + + template + static ValueType crossdifference(const ValueType& xp2yp2, const ValueType& xp2yp1, + const ValueType& xp2ym1, const ValueType& xp2ym2, + const ValueType& xp1yp2, const ValueType& xp1yp1, + const ValueType& xp1ym1, const ValueType& xp1ym2, + const ValueType& xm2yp2, const ValueType& xm2yp1, + const ValueType& xm2ym1, const ValueType& xm2ym2, + const ValueType& xm1yp2, const ValueType& xm1yp1, + const ValueType& xm1ym1, const ValueType& xm1ym2 ) { + ValueType tmp1 = + ValueType(2./3.0)*(xp1yp1 - xm1yp1 - xp1ym1 + xm1ym1)- + ValueType(1./12.)*(xp2yp1 - xm2yp1 - xp2ym1 + xm2ym1); + ValueType tmp2 = + ValueType(2./3.0)*(xp1yp2 - xm1yp2 - xp1ym2 + xm1ym2)- + ValueType(1./12.)*(xp2yp2 - xm2yp2 - xp2ym2 + xm2ym2); + + return ValueType(2./3.)*tmp1 - ValueType(1./12.)*tmp2; + } + + + + // random access version + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + return difference( + grid.getValue(ijk.offsetBy(2,0,0)), grid.getValue(ijk.offsetBy( 1,0,0)), + grid.getValue(ijk), + grid.getValue(ijk.offsetBy(-1,0,0)), grid.getValue(ijk.offsetBy(-2, 0, 0))); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + return difference( + grid.getValue(ijk.offsetBy(0, 2,0)), grid.getValue(ijk.offsetBy(0, 1,0)), + grid.getValue(ijk), + grid.getValue(ijk.offsetBy(0,-1,0)), grid.getValue(ijk.offsetBy(0,-2, 0))); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + return difference( + grid.getValue(ijk.offsetBy(0,0, 2)), grid.getValue(ijk.offsetBy(0, 0,1)), + grid.getValue(ijk), + grid.getValue(ijk.offsetBy(0,0,-1)), grid.getValue(ijk.offsetBy(0,0,-2))); + } + + // cross derivatives + template + static typename Accessor::ValueType inXandY(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + typename Accessor::ValueType tmp1 = + D1::inX(grid, ijk.offsetBy(0, 1, 0)) - + D1::inX(grid, ijk.offsetBy(0,-1, 0)); + typename Accessor::ValueType tmp2 = + D1::inX(grid, ijk.offsetBy(0, 2, 0)) - + D1::inX(grid, ijk.offsetBy(0,-2, 0)); + return ValueType(2./3.)*tmp1 - ValueType(1./12.)*tmp2; + } + + template + static typename Accessor::ValueType inXandZ(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + typename Accessor::ValueType tmp1 = + D1::inX(grid, ijk.offsetBy(0, 0, 1)) - + D1::inX(grid, ijk.offsetBy(0, 0,-1)); + typename Accessor::ValueType tmp2 = + D1::inX(grid, ijk.offsetBy(0, 0, 2)) - + D1::inX(grid, ijk.offsetBy(0, 0,-2)); + return ValueType(2./3.)*tmp1 - ValueType(1./12.)*tmp2; + } + + template + static typename Accessor::ValueType inYandZ(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + typename Accessor::ValueType tmp1 = + D1::inY(grid, ijk.offsetBy(0, 0, 1)) - + D1::inY(grid, ijk.offsetBy(0, 0,-1)); + typename Accessor::ValueType tmp2 = + D1::inY(grid, ijk.offsetBy(0, 0, 2)) - + D1::inY(grid, ijk.offsetBy(0, 0,-2)); + return ValueType(2./3.)*tmp1 - ValueType(1./12.)*tmp2; + } + + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + return difference(S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), + S.template getValue< 0, 0, 0>(), + S.template getValue<-1, 0, 0>(), S.template getValue<-2, 0, 0>() ); + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + return difference(S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), + S.template getValue< 0, 0, 0>(), + S.template getValue< 0,-1, 0>(), S.template getValue< 0,-2, 0>() ); + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + return difference(S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), + S.template getValue< 0, 0, 0>(), + S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0,-2>() ); + } + + // cross derivatives + template + static typename Stencil::ValueType inXandY(const Stencil& S) + { + return crossdifference( + S.template getValue< 2, 2, 0>(), S.template getValue< 2, 1, 0>(), + S.template getValue< 2,-1, 0>(), S.template getValue< 2,-2, 0>(), + S.template getValue< 1, 2, 0>(), S.template getValue< 1, 1, 0>(), + S.template getValue< 1,-1, 0>(), S.template getValue< 1,-2, 0>(), + S.template getValue<-2, 2, 0>(), S.template getValue<-2, 1, 0>(), + S.template getValue<-2,-1, 0>(), S.template getValue<-2,-2, 0>(), + S.template getValue<-1, 2, 0>(), S.template getValue<-1, 1, 0>(), + S.template getValue<-1,-1, 0>(), S.template getValue<-1,-2, 0>() ); + } + + template + static typename Stencil::ValueType inXandZ(const Stencil& S) + { + return crossdifference( + S.template getValue< 2, 0, 2>(), S.template getValue< 2, 0, 1>(), + S.template getValue< 2, 0,-1>(), S.template getValue< 2, 0,-2>(), + S.template getValue< 1, 0, 2>(), S.template getValue< 1, 0, 1>(), + S.template getValue< 1, 0,-1>(), S.template getValue< 1, 0,-2>(), + S.template getValue<-2, 0, 2>(), S.template getValue<-2, 0, 1>(), + S.template getValue<-2, 0,-1>(), S.template getValue<-2, 0,-2>(), + S.template getValue<-1, 0, 2>(), S.template getValue<-1, 0, 1>(), + S.template getValue<-1, 0,-1>(), S.template getValue<-1, 0,-2>() ); + } + + template + static typename Stencil::ValueType inYandZ(const Stencil& S) + { + return crossdifference( + S.template getValue< 0, 2, 2>(), S.template getValue< 0, 2, 1>(), + S.template getValue< 0, 2,-1>(), S.template getValue< 0, 2,-2>(), + S.template getValue< 0, 1, 2>(), S.template getValue< 0, 1, 1>(), + S.template getValue< 0, 1,-1>(), S.template getValue< 0, 1,-2>(), + S.template getValue< 0,-2, 2>(), S.template getValue< 0,-2, 1>(), + S.template getValue< 0,-2,-1>(), S.template getValue< 0,-2,-2>(), + S.template getValue< 0,-1, 2>(), S.template getValue< 0,-1, 1>(), + S.template getValue< 0,-1,-1>(), S.template getValue< 0,-1,-2>() ); + } +}; + + +template<> +struct D2 +{ + // the difference opperator + template + static ValueType difference(const ValueType& xp3, const ValueType& xp2, const ValueType& xp1, + const ValueType& xp0, + const ValueType& xm1, const ValueType& xm2, const ValueType& xm3) + { + return ValueType(1./90.)*(xp3 + xm3) - ValueType(3./20.)*(xp2 + xm2) + + ValueType(1.5)*(xp1 + xm1) - ValueType(49./18.)*xp0; + } + + template + static ValueType crossdifference( const ValueType& xp1yp1,const ValueType& xm1yp1, + const ValueType& xp1ym1,const ValueType& xm1ym1, + const ValueType& xp2yp1,const ValueType& xm2yp1, + const ValueType& xp2ym1,const ValueType& xm2ym1, + const ValueType& xp3yp1,const ValueType& xm3yp1, + const ValueType& xp3ym1,const ValueType& xm3ym1, + const ValueType& xp1yp2,const ValueType& xm1yp2, + const ValueType& xp1ym2,const ValueType& xm1ym2, + const ValueType& xp2yp2,const ValueType& xm2yp2, + const ValueType& xp2ym2,const ValueType& xm2ym2, + const ValueType& xp3yp2,const ValueType& xm3yp2, + const ValueType& xp3ym2,const ValueType& xm3ym2, + const ValueType& xp1yp3,const ValueType& xm1yp3, + const ValueType& xp1ym3,const ValueType& xm1ym3, + const ValueType& xp2yp3,const ValueType& xm2yp3, + const ValueType& xp2ym3,const ValueType& xm2ym3, + const ValueType& xp3yp3,const ValueType& xm3yp3, + const ValueType& xp3ym3,const ValueType& xm3ym3 ) + { + ValueType tmp1 = + ValueType(0.7500)*(xp1yp1 - xm1yp1 - xp1ym1 + xm1ym1) - + ValueType(0.1500)*(xp2yp1 - xm2yp1 - xp2ym1 + xm2ym1) + + ValueType(1./60.)*(xp3yp1 - xm3yp1 - xp3ym1 + xm3ym1); + + ValueType tmp2 = + ValueType(0.7500)*(xp1yp2 - xm1yp2 - xp1ym2 + xm1ym2) - + ValueType(0.1500)*(xp2yp2 - xm2yp2 - xp2ym2 + xm2ym2) + + ValueType(1./60.)*(xp3yp2 - xm3yp2 - xp3ym2 + xm3ym2); + + ValueType tmp3 = + ValueType(0.7500)*(xp1yp3 - xm1yp3 - xp1ym3 + xm1ym3) - + ValueType(0.1500)*(xp2yp3 - xm2yp3 - xp2ym3 + xm2ym3) + + ValueType(1./60.)*(xp3yp3 - xm3yp3 - xp3ym3 + xm3ym3); + + return ValueType(0.75)*tmp1 - ValueType(0.15)*tmp2 + ValueType(1./60)*tmp3; + } + + // random access version + + template + static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) + { + return difference( + grid.getValue(ijk.offsetBy( 3, 0, 0)), grid.getValue(ijk.offsetBy( 2, 0, 0)), + grid.getValue(ijk.offsetBy( 1, 0, 0)), grid.getValue(ijk), + grid.getValue(ijk.offsetBy(-1, 0, 0)), grid.getValue(ijk.offsetBy(-2, 0, 0)), + grid.getValue(ijk.offsetBy(-3, 0, 0)) ); + } + + template + static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) + { + return difference( + grid.getValue(ijk.offsetBy( 0, 3, 0)), grid.getValue(ijk.offsetBy( 0, 2, 0)), + grid.getValue(ijk.offsetBy( 0, 1, 0)), grid.getValue(ijk), + grid.getValue(ijk.offsetBy( 0,-1, 0)), grid.getValue(ijk.offsetBy( 0,-2, 0)), + grid.getValue(ijk.offsetBy( 0,-3, 0)) ); + } + + template + static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) + { + + return difference( + grid.getValue(ijk.offsetBy( 0, 0, 3)), grid.getValue(ijk.offsetBy( 0, 0, 2)), + grid.getValue(ijk.offsetBy( 0, 0, 1)), grid.getValue(ijk), + grid.getValue(ijk.offsetBy( 0, 0,-1)), grid.getValue(ijk.offsetBy( 0, 0,-2)), + grid.getValue(ijk.offsetBy( 0, 0,-3)) ); + } + + template + static typename Accessor::ValueType inXandY(const Accessor& grid, const Coord& ijk) + { + typename Accessor::ValueType tmp1 = + D1::inX(grid, ijk.offsetBy(0, 1, 0)) - + D1::inX(grid, ijk.offsetBy(0,-1, 0)); + typename Accessor::ValueType tmp2 = + D1::inX(grid, ijk.offsetBy(0, 2, 0)) - + D1::inX(grid, ijk.offsetBy(0,-2, 0)); + typename Accessor::ValueType tmp3 = + D1::inX(grid, ijk.offsetBy(0, 3, 0)) - + D1::inX(grid, ijk.offsetBy(0,-3, 0)); + return 0.75*tmp1 - 0.15*tmp2 + 1./60*tmp3; + } + + template + static typename Accessor::ValueType inXandZ(const Accessor& grid, const Coord& ijk) + { + typename Accessor::ValueType tmp1 = + D1::inX(grid, ijk.offsetBy(0, 0, 1)) - + D1::inX(grid, ijk.offsetBy(0, 0,-1)); + typename Accessor::ValueType tmp2 = + D1::inX(grid, ijk.offsetBy(0, 0, 2)) - + D1::inX(grid, ijk.offsetBy(0, 0,-2)); + typename Accessor::ValueType tmp3 = + D1::inX(grid, ijk.offsetBy(0, 0, 3)) - + D1::inX(grid, ijk.offsetBy(0, 0,-3)); + return 0.75*tmp1 - 0.15*tmp2 + 1./60*tmp3; + } + + template + static typename Accessor::ValueType inYandZ(const Accessor& grid, const Coord& ijk) + { + typename Accessor::ValueType tmp1 = + D1::inY(grid, ijk.offsetBy(0, 0, 1)) - + D1::inY(grid, ijk.offsetBy(0, 0,-1)); + typename Accessor::ValueType tmp2 = + D1::inY(grid, ijk.offsetBy(0, 0, 2)) - + D1::inY(grid, ijk.offsetBy(0, 0,-2)); + typename Accessor::ValueType tmp3 = + D1::inY(grid, ijk.offsetBy(0, 0, 3)) - + D1::inY(grid, ijk.offsetBy(0, 0,-3)); + return 0.75*tmp1 - 0.15*tmp2 + 1./60*tmp3; + } + + + // stencil access version + template + static typename Stencil::ValueType inX(const Stencil& S) + { + return difference( S.template getValue< 3, 0, 0>(), S.template getValue< 2, 0, 0>(), + S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>(), + S.template getValue<-1, 0, 0>(), S.template getValue<-2, 0, 0>(), + S.template getValue<-3, 0, 0>() ); + } + + template + static typename Stencil::ValueType inY(const Stencil& S) + { + return difference( S.template getValue< 0, 3, 0>(), S.template getValue< 0, 2, 0>(), + S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>(), + S.template getValue< 0,-1, 0>(), S.template getValue< 0,-2, 0>(), + S.template getValue< 0,-3, 0>() ); + + } + + template + static typename Stencil::ValueType inZ(const Stencil& S) + { + return difference( S.template getValue< 0, 0, 3>(), S.template getValue< 0, 0, 2>(), + S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>(), + S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0,-2>(), + S.template getValue< 0, 0,-3>() ); + } + + template + static typename Stencil::ValueType inXandY(const Stencil& S) + { + return crossdifference( S.template getValue< 1, 1, 0>(), S.template getValue<-1, 1, 0>(), + S.template getValue< 1,-1, 0>(), S.template getValue<-1,-1, 0>(), + S.template getValue< 2, 1, 0>(), S.template getValue<-2, 1, 0>(), + S.template getValue< 2,-1, 0>(), S.template getValue<-2,-1, 0>(), + S.template getValue< 3, 1, 0>(), S.template getValue<-3, 1, 0>(), + S.template getValue< 3,-1, 0>(), S.template getValue<-3,-1, 0>(), + S.template getValue< 1, 2, 0>(), S.template getValue<-1, 2, 0>(), + S.template getValue< 1,-2, 0>(), S.template getValue<-1,-2, 0>(), + S.template getValue< 2, 2, 0>(), S.template getValue<-2, 2, 0>(), + S.template getValue< 2,-2, 0>(), S.template getValue<-2,-2, 0>(), + S.template getValue< 3, 2, 0>(), S.template getValue<-3, 2, 0>(), + S.template getValue< 3,-2, 0>(), S.template getValue<-3,-2, 0>(), + S.template getValue< 1, 3, 0>(), S.template getValue<-1, 3, 0>(), + S.template getValue< 1,-3, 0>(), S.template getValue<-1,-3, 0>(), + S.template getValue< 2, 3, 0>(), S.template getValue<-2, 3, 0>(), + S.template getValue< 2,-3, 0>(), S.template getValue<-2,-3, 0>(), + S.template getValue< 3, 3, 0>(), S.template getValue<-3, 3, 0>(), + S.template getValue< 3,-3, 0>(), S.template getValue<-3,-3, 0>() ); + } + + template + static typename Stencil::ValueType inXandZ(const Stencil& S) + { + return crossdifference( S.template getValue< 1, 0, 1>(), S.template getValue<-1, 0, 1>(), + S.template getValue< 1, 0,-1>(), S.template getValue<-1, 0,-1>(), + S.template getValue< 2, 0, 1>(), S.template getValue<-2, 0, 1>(), + S.template getValue< 2, 0,-1>(), S.template getValue<-2, 0,-1>(), + S.template getValue< 3, 0, 1>(), S.template getValue<-3, 0, 1>(), + S.template getValue< 3, 0,-1>(), S.template getValue<-3, 0,-1>(), + S.template getValue< 1, 0, 2>(), S.template getValue<-1, 0, 2>(), + S.template getValue< 1, 0,-2>(), S.template getValue<-1, 0,-2>(), + S.template getValue< 2, 0, 2>(), S.template getValue<-2, 0, 2>(), + S.template getValue< 2, 0,-2>(), S.template getValue<-2, 0,-2>(), + S.template getValue< 3, 0, 2>(), S.template getValue<-3, 0, 2>(), + S.template getValue< 3, 0,-2>(), S.template getValue<-3, 0,-2>(), + S.template getValue< 1, 0, 3>(), S.template getValue<-1, 0, 3>(), + S.template getValue< 1, 0,-3>(), S.template getValue<-1, 0,-3>(), + S.template getValue< 2, 0, 3>(), S.template getValue<-2, 0, 3>(), + S.template getValue< 2, 0,-3>(), S.template getValue<-2, 0,-3>(), + S.template getValue< 3, 0, 3>(), S.template getValue<-3, 0, 3>(), + S.template getValue< 3, 0,-3>(), S.template getValue<-3, 0,-3>() ); + } + + template + static typename Stencil::ValueType inYandZ(const Stencil& S) + { + return crossdifference( S.template getValue< 0, 1, 1>(), S.template getValue< 0,-1, 1>(), + S.template getValue< 0, 1,-1>(), S.template getValue< 0,-1,-1>(), + S.template getValue< 0, 2, 1>(), S.template getValue< 0,-2, 1>(), + S.template getValue< 0, 2,-1>(), S.template getValue< 0,-2,-1>(), + S.template getValue< 0, 3, 1>(), S.template getValue< 0,-3, 1>(), + S.template getValue< 0, 3,-1>(), S.template getValue< 0,-3,-1>(), + S.template getValue< 0, 1, 2>(), S.template getValue< 0,-1, 2>(), + S.template getValue< 0, 1,-2>(), S.template getValue< 0,-1,-2>(), + S.template getValue< 0, 2, 2>(), S.template getValue< 0,-2, 2>(), + S.template getValue< 0, 2,-2>(), S.template getValue< 0,-2,-2>(), + S.template getValue< 0, 3, 2>(), S.template getValue< 0,-3, 2>(), + S.template getValue< 0, 3,-2>(), S.template getValue< 0,-3,-2>(), + S.template getValue< 0, 1, 3>(), S.template getValue< 0,-1, 3>(), + S.template getValue< 0, 1,-3>(), S.template getValue< 0,-1,-3>(), + S.template getValue< 0, 2, 3>(), S.template getValue< 0,-2, 3>(), + S.template getValue< 0, 2,-3>(), S.template getValue< 0,-2,-3>(), + S.template getValue< 0, 3, 3>(), S.template getValue< 0,-3, 3>(), + S.template getValue< 0, 3,-3>(), S.template getValue< 0,-3,-3>() ); + } + +}; + +} // end math namespace +} // namespace OPENVDB_VERSION_NAME +} // end openvdb namespace + +#endif // OPENVDB_MATH_FINITEDIFFERENCE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Hermite.cc b/openvdb_2_3_0_library/openvdb/math/Hermite.cc new file mode 100755 index 0000000..435ab1f --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Hermite.cc @@ -0,0 +1,167 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Hermite.h" + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + + +//////////////////////////////////////// + +// min and max on compressd data + +Hermite +min(const Hermite& lhs, const Hermite& rhs) +{ + Hermite ret; + + if(!lhs && !rhs) { + + if(lhs.isInside()) ret = lhs; + else ret = rhs; + + return ret; + } + + ret.setIsInside(lhs.isInside() || rhs.isInside()); + + if(lhs.isGreaterX(rhs)) ret.setX(rhs); + else ret.setX(lhs); + + if(lhs.isGreaterY(rhs)) ret.setY(rhs); + else ret.setY(lhs); + + if(lhs.isGreaterZ(rhs)) ret.setZ(rhs); + else ret.setZ(lhs); + + return ret; +} + +Hermite +max(const Hermite& lhs, const Hermite& rhs) +{ + Hermite ret; + + if(!lhs && !rhs) { + + if(!lhs.isInside()) ret = lhs; + else ret = rhs; + + return ret; + } + + ret.setIsInside(lhs.isInside() && rhs.isInside()); + + if(rhs.isGreaterX(lhs)) ret.setX(rhs); + else ret.setX(lhs); + + if(rhs.isGreaterY(lhs)) ret.setY(rhs); + else ret.setY(lhs); + + if(rhs.isGreaterZ(lhs)) ret.setZ(rhs); + else ret.setZ(lhs); + + return ret; +} + + +//////////////////////////////////////// + +// constructors + +Hermite::Hermite(): + mXNormal(0), + mYNormal(0), + mZNormal(0), + mData(0) +{ +} + +Hermite::Hermite(const Hermite& rhs): + mXNormal(rhs.mXNormal), + mYNormal(rhs.mYNormal), + mZNormal(rhs.mZNormal), + mData(rhs.mData) +{ +} + + +//////////////////////////////////////// + +// string representation + +std::string +Hermite::str() const +{ + std::ostringstream ss; + + ss << "{ " << (isInside() ? "inside" : "outside"); + if(hasOffsetX()) ss << " |x " << getOffsetX() << " " << getNormalX(); + if(hasOffsetY()) ss << " |y " << getOffsetY() << " " << getNormalY(); + if(hasOffsetZ()) ss << " |z " << getOffsetZ() << " " << getNormalZ(); + ss << " }"; + + return ss.str(); +} + + +//////////////////////////////////////// + + +void +Hermite::read(std::istream& is) +{ + is.read(reinterpret_cast(&mXNormal), sizeof(uint16_t)); + is.read(reinterpret_cast(&mYNormal), sizeof(uint16_t)); + is.read(reinterpret_cast(&mZNormal), sizeof(uint16_t)); + is.read(reinterpret_cast(&mData), sizeof(uint32_t)); +} + +void +Hermite::write(std::ostream& os) const +{ + os.write(reinterpret_cast(&mXNormal), sizeof(uint16_t)); + os.write(reinterpret_cast(&mYNormal), sizeof(uint16_t)); + os.write(reinterpret_cast(&mZNormal), sizeof(uint16_t)); + os.write(reinterpret_cast(&mData), sizeof(uint32_t)); +} + + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Hermite.h b/openvdb_2_3_0_library/openvdb/math/Hermite.h new file mode 100755 index 0000000..edb99e6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Hermite.h @@ -0,0 +1,493 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_MATH_HERMITE_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_HERMITE_HAS_BEEN_INCLUDED + +#include +#include +#include "QuantizedUnitVec.h" +#include "Math.h" + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + + +// Forward declaration +class Hermite; + + +//////////////////////////////////////// + +// Utility methods + + +//@{ +/// min and max operations done directly on the compressed data. +OPENVDB_API Hermite min(const Hermite&, const Hermite&); +OPENVDB_API Hermite max(const Hermite&, const Hermite&); +//@} + + +//////////////////////////////////////// + + +/// @brief Quantized Hermite data object that stores compressed intersection +/// information (offsets and normlas) for the up-wind edges of a voxel. (Size 10 bytes) +class OPENVDB_API Hermite +{ +public: + + Hermite(); + Hermite(const Hermite&); + const Hermite& operator=(const Hermite&); + + /// clears all intersection data + void clear(); + + /// @return true if this Hermite objet has any edge intersection data. + operator bool() const; + + /// equality operator + inline bool operator==(const Hermite&) const; + /// inequality operator + bool operator!=(const Hermite& rhs) const { return !(*this == rhs); } + + /// unary negation operator, flips inside/outside state and normals. + Hermite operator-() const; + + //@{ + /// @brief methods to compress and store edge data. + /// @note @c offset is expected to be in the [0 to 1) range. + template + void setX(T offset, const Vec3&); + + template + void setY(T offset, const Vec3&); + + template + void setZ(T offset, const Vec3&); + //@} + + /// @return true if the current Hermite object is classified + // as being inside a contour. + bool isInside() const { return MASK_SIGN & mData; } + /// Set the inside/outside state to reflect if this Hermite object + /// is located at a point in space that is inside/outside a contour. + void setIsInside(bool); + + //@{ + /// @return true if this Hermite object has intersection data + /// for the corresponding edge. + bool hasOffsetX() const { return mXNormal; }; + bool hasOffsetY() const { return mYNormal; } + bool hasOffsetZ() const { return MASK_ZFLAG & mData; } + //@} + + //@{ + /// Edge offset greater-than comparisson operators + /// @note is @c this offset > than @c other offset + bool isGreaterX(const Hermite& other) const; + bool isGreaterY(const Hermite& other) const; + bool isGreaterZ(const Hermite& other) const; + //@} + + //@{ + /// Edge offset less-than comparisson operators + /// @note is @c this offset < than @c other offset + bool isLessX(const Hermite& other) const; + bool isLessY(const Hermite& other) const; + bool isLessZ(const Hermite& other) const; + //@} + + //@{ + /// @return uncompressed edge intersection offsets + float getOffsetX() const; + float getOffsetY() const; + float getOffsetZ() const; + //@} + + //@{ + /// @return uncompressed edge intersection normals + Vec3s getNormalX() const { return QuantizedUnitVec::unpack(mXNormal); } + Vec3s getNormalY() const { return QuantizedUnitVec::unpack(mYNormal); } + Vec3s getNormalZ() const { return QuantizedUnitVec::unpack(mZNormal); } + //@} + + //@{ + /// copy edge data from other Hermite object + /// @note copies data in the compressed form + void setX(const Hermite&); + void setY(const Hermite&); + void setZ(const Hermite&); + //@} + + /// String representation. + std::string str() const; + + /// Unserialize this transform from the given stream. + void read(std::istream&); + /// Serialize this transform to the given stream. + void write(std::ostream&) const; + + //@{ + /// Operators required by OpenVDB. + /// @note These methods don't perform meaningful operations on Hermite data. + bool operator< (const Hermite&) const { return false; }; + bool operator> (const Hermite&) const { return false; }; + template Hermite operator+(const T&) const { return *this; } + template Hermite operator-(const T&) const { return *this; } + //@} + +private: + /// Helper function that quantizes a [0, 1) offset using 10-bits. + template + static uint32_t quantizeOffset(T offset); + + /// Helper function that returns (signed) compressed-offsets, + /// used by comparisson operators. + static void getSignedOffsets(const Hermite& lhs, const Hermite& rhs, + const uint32_t bitmask, int& lhsV, int& rhsV); + + + // Bits masks + // 10000000000000000000000000000000 + static const uint32_t MASK_SIGN = 0x80000000; + // 01000000000000000000000000000000 + static const uint32_t MASK_ZFLAG = 0x40000000; + // 00111111111100000000000000000000 + static const uint32_t MASK_XSLOT = 0x3FF00000; + // 00000000000011111111110000000000 + static const uint32_t MASK_YSLOT = 0x000FFC00; + // 00000000000000000000001111111111 + static const uint32_t MASK_ZSLOT = 0x000003FF; + // 00111111111111111111111111111111 + static const uint32_t MASK_SLOTS = 0x3FFFFFFF; + + + uint16_t mXNormal, mYNormal, mZNormal; + uint32_t mData; + +}; // class Hermite + + +//////////////////////////////////////// + +// output-stream insertion operator + +inline std::ostream& +operator<<(std::ostream& ostr, const Hermite& rhs) +{ + ostr << rhs.str(); + return ostr; +} + + +//////////////////////////////////////// + +// construction and assignment + +inline const Hermite& +Hermite::operator=(const Hermite& rhs) +{ + mData = rhs.mData; + mXNormal = rhs.mXNormal; + mYNormal = rhs.mYNormal; + mZNormal = rhs.mZNormal; + return *this; +} + + +inline void +Hermite::clear() +{ + mXNormal = 0; + mYNormal = 0; + mZNormal = 0; + mData = 0; +} + + +//////////////////////////////////////// + +// bool operator and equality + +inline +Hermite::operator bool() const +{ + if (0 != (mXNormal | mYNormal)) return true; + return hasOffsetZ(); +} + + +inline bool +Hermite::operator==(const Hermite& rhs) const +{ + if(mXNormal != rhs.mXNormal) return false; + if(mYNormal != rhs.mYNormal) return false; + if(mZNormal != rhs.mZNormal) return false; + return mData == rhs.mData; +} + + +//////////////////////////////////////// + +// unary negation operator + +inline Hermite +Hermite::operator-() const +{ + Hermite ret(*this); + QuantizedUnitVec::flipSignBits(ret.mXNormal); + QuantizedUnitVec::flipSignBits(ret.mYNormal); + QuantizedUnitVec::flipSignBits(ret.mZNormal); + ret.mData = (~MASK_SIGN & ret.mData) | (MASK_SIGN & ~ret.mData); + return ret; +} + + +//////////////////////////////////////// + +// Helper funcions + +template +inline uint32_t +Hermite::quantizeOffset(T offset) +{ + // the offset is expected to be normalized [0 to 1) + assert(offset < 1.0); + assert(offset > -1.0e-8); + + // quantize the offset using 10-bits. (higher bits are masked out) + return uint32_t(1023 * offset) & MASK_ZSLOT; +} + +inline void +Hermite::getSignedOffsets(const Hermite& lhs, const Hermite& rhs, + const uint32_t bitmask, int& lhsV, int& rhsV) +{ + lhsV = bitmask & lhs.mData; + rhsV = bitmask & rhs.mData; + + if(lhs.isInside()) lhsV = -lhsV; + if(rhs.isInside()) rhsV = -rhsV; +} + + +//////////////////////////////////////// + +// compress and set edge data + +template +inline void +Hermite::setX(T offset, const Vec3& n) +{ + mData &= ~MASK_XSLOT; // clear xslot + mData |= quantizeOffset(offset) << 20; + mXNormal = QuantizedUnitVec::pack(n); +} + +template +inline void +Hermite::setY(T offset, const Vec3& n) +{ + mData &= ~MASK_YSLOT; // clear yslot + mData |= quantizeOffset(offset) << 10; + mYNormal = QuantizedUnitVec::pack(n); +} + +template +inline void +Hermite::setZ(T offset, const Vec3& n) +{ + mData &= ~MASK_ZSLOT; // clear zslot + mData |= MASK_ZFLAG | quantizeOffset(offset); + mZNormal = QuantizedUnitVec::pack(n); +} + + +//////////////////////////////////////// + +// change inside/outside state + +inline void +Hermite::setIsInside(bool isInside) +{ + mData &= ~MASK_SIGN; // clear sign-bit + mData |= uint32_t(isInside) * MASK_SIGN; +} + + +//////////////////////////////////////// + +// Uncompress and return the edge intersection-offsets +// 0.000977517 = 1.0 / 1023 + +inline float +Hermite::getOffsetX() const +{ + return float((mData >> 20) & MASK_ZSLOT) * 0.000977517; +} + +inline float +Hermite::getOffsetY() const +{ + return float((mData >> 10) & MASK_ZSLOT) * 0.000977517; +} + +inline float +Hermite::getOffsetZ() const +{ + return float(mData & MASK_ZSLOT) * 0.000977517; +} + + +//////////////////////////////////////// + +// copy compressed edge data from other object + +inline void +Hermite::setX(const Hermite& rhs) +{ + mData &= ~MASK_XSLOT; // clear xslot + mData |= MASK_XSLOT & rhs.mData; // copy xbits from rhs + mXNormal = rhs.mXNormal; // copy compressed normal + + // Flip the copied normal if the rhs object has + // a different inside/outside state. + if(hasOffsetX() && isInside() != rhs.isInside()) + QuantizedUnitVec::flipSignBits(mXNormal); +} + +inline void +Hermite::setY(const Hermite& rhs) +{ + mData &= ~MASK_YSLOT; + mData |= MASK_YSLOT & rhs.mData; + mYNormal = rhs.mYNormal; + + if(hasOffsetY() && isInside() != rhs.isInside()) + QuantizedUnitVec::flipSignBits(mYNormal); +} + +inline void +Hermite::setZ(const Hermite& rhs) +{ + mData &= ~MASK_ZSLOT; + mData |= (MASK_ZFLAG | MASK_ZSLOT) & rhs.mData; + mZNormal = rhs.mZNormal; + if(hasOffsetZ() && isInside() != rhs.isInside()) + QuantizedUnitVec::flipSignBits(mZNormal); +} + + +//////////////////////////////////////// + +// edge comparison operators + +inline bool +Hermite::isGreaterX(const Hermite& rhs) const +{ + int lhsV, rhsV; + getSignedOffsets(*this, rhs, MASK_XSLOT, lhsV, rhsV); + return lhsV > rhsV; +} + +inline bool +Hermite::isGreaterY(const Hermite& rhs) const +{ + int lhsV, rhsV; + getSignedOffsets(*this, rhs, MASK_YSLOT, lhsV, rhsV); + return lhsV > rhsV; +} + +inline bool +Hermite::isGreaterZ(const Hermite& rhs) const +{ + int lhsV, rhsV; + getSignedOffsets(*this, rhs, MASK_ZSLOT, lhsV, rhsV); + return lhsV > rhsV; +} + +inline bool +Hermite::isLessX(const Hermite& rhs) const +{ + int lhsV, rhsV; + getSignedOffsets(*this, rhs, MASK_XSLOT, lhsV, rhsV); + return lhsV < rhsV; +} + +inline bool +Hermite::isLessY(const Hermite& rhs) const +{ + int lhsV, rhsV; + getSignedOffsets(*this, rhs, MASK_YSLOT, lhsV, rhsV); + return lhsV < rhsV; +} + +inline bool +Hermite::isLessZ(const Hermite& rhs) const +{ + int lhsV, rhsV; + getSignedOffsets(*this, rhs, MASK_ZSLOT, lhsV, rhsV); + return lhsV < rhsV; +} + + +//////////////////////////////////////// + + +inline bool +isApproxEqual(const Hermite& lhs, const Hermite& rhs) { return lhs == rhs; } + +inline bool +isApproxEqual(const Hermite& lhs, const Hermite& rhs, const Hermite& /*tolerance*/) + { return isApproxEqual(lhs, rhs); } + + +} // namespace math + + +//////////////////////////////////////// + + +template<> inline math::Hermite zeroVal() { return math::Hermite(); } + + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_HERMITE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/LegacyFrustum.h b/openvdb_2_3_0_library/openvdb/math/LegacyFrustum.h new file mode 100755 index 0000000..f0e5f7b --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/LegacyFrustum.h @@ -0,0 +1,196 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file math/LegacyFrustum.h + +#ifndef OPENVDB_MATH_LEGACYFRUSTUM_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_LEGACYFRUSTUM_HAS_BEEN_INCLUDED + +#include +#include // for Real typedef +#include "Coord.h" +#include "Mat4.h" +#include "Vec3.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { +namespace internal { + +/// @brief LegacyFrustum class used at DreamWorks for converting old vdb files. +class LegacyFrustum +{ +public: + LegacyFrustum(std::istream& is) + { + // First read in the old transform's base class. + // the "extents" + Vec3i tmpMin, tmpMax; + is.read(reinterpret_cast(&tmpMin), sizeof(Vec3i::ValueType) * 3); + is.read(reinterpret_cast(&tmpMax), sizeof(Vec3i::ValueType) * 3); + + Coord tmpMinCoord(tmpMin); + Coord tmpMaxCoord(tmpMax); + + // set the extents + mExtents = CoordBBox(tmpMinCoord, tmpMaxCoord); + + // read the old-frustum class member data + //Mat4d tmpW2C; + Mat4d tmpW2C, tmpC2S, tmpS2C, tmpWorldToLocal; + Mat4d tmpS2U, tmpXYLocalToUnit, tmpZLocalToUnit; + Real tmpWindow[6]; + Real tmpPadding; + + //Mat4d tmpXYUnitToLocal, tmpZUnitToLocal + + // read in each matrix. + is.read(reinterpret_cast(&tmpW2C), + sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); + is.read(reinterpret_cast(&mC2W), + sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); + is.read(reinterpret_cast(&tmpC2S), + sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); + is.read(reinterpret_cast(&tmpS2C), + sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); + is.read(reinterpret_cast(&tmpWorldToLocal), + sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); + is.read(reinterpret_cast(&mLocalToWorld), + sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); + + is.read(reinterpret_cast(&tmpWindow[0]), sizeof(Real)); + is.read(reinterpret_cast(&tmpWindow[1]), sizeof(Real)); + is.read(reinterpret_cast(&tmpWindow[2]), sizeof(Real)); + is.read(reinterpret_cast(&tmpWindow[3]), sizeof(Real)); + is.read(reinterpret_cast(&tmpWindow[4]), sizeof(Real)); + is.read(reinterpret_cast(&tmpWindow[5]), sizeof(Real)); + + is.read(reinterpret_cast(&tmpPadding), sizeof(Real)); + + is.read(reinterpret_cast(&tmpS2U), + sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); + is.read(reinterpret_cast(&mXYUnitToLocal), + sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); + is.read(reinterpret_cast(&tmpXYLocalToUnit), + sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); + is.read(reinterpret_cast(&mZUnitToLocal), + sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); + is.read(reinterpret_cast(&tmpZLocalToUnit), + sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); + + + mNearPlane = tmpWindow[4]; + mFarPlane = tmpWindow[5]; + + // Look up the world space corners of the + // frustum grid. + mFrNearOrigin = unitToLocalFrustum(Vec3R(0,0,0)); + mFrFarOrigin = unitToLocalFrustum(Vec3R(0,0,1)); + + Vec3d frNearXTip = unitToLocalFrustum(Vec3R(1,0,0)); + Vec3d frNearYTip = unitToLocalFrustum(Vec3R(0,1,0)); + mFrNearXBasis = frNearXTip - mFrNearOrigin; + mFrNearYBasis = frNearYTip - mFrNearOrigin; + + Vec3R frFarXTip = unitToLocalFrustum(Vec3R(1,0,1)); + Vec3R frFarYTip = unitToLocalFrustum(Vec3R(0,1,1)); + mFrFarXBasis = frFarXTip - mFrFarOrigin; + mFrFarYBasis = frFarYTip - mFrFarOrigin; + } + + ~LegacyFrustum(){}; + + const Mat4d& getCamXForm() const {return mC2W; } + + double getDepth() const {return (mFarPlane - mNearPlane); } + double getTaper() const { + + return getNearPlaneWidth() / getFarPlaneWidth(); + } + + double getNearPlaneWidth() const { + double nearPlaneWidth = (unitToWorld(Vec3d(0,0,0)) - unitToWorld(Vec3d(1,0,0))).length(); + return nearPlaneWidth; + } + + double getFarPlaneWidth() const { + double farPlaneWidth = (unitToWorld(Vec3d(0,0,1)) - unitToWorld(Vec3d(1,0,1))).length(); + return farPlaneWidth; + } + + double getNearPlaneDist() const { return mNearPlane; } + + const CoordBBox& getBBox() const {return mExtents; } + + Vec3d unitToWorld(const Vec3d& in) const {return mLocalToWorld.transform( unitToLocal(in) ); } + +private: + LegacyFrustum(){}; + + Vec3d unitToLocal(const Vec3d& U) const { + + // We first find the local space coordinates + // of the unit point projected onto the near + // and far planes of the frustum by using a + // linear combination of the planes basis vectors + Vec3d nearLS = ( U[0] * mFrNearXBasis ) + ( U[1] * mFrNearYBasis ) + mFrNearOrigin; + Vec3d farLS = ( U[0] * mFrFarXBasis ) + ( U[1] * mFrFarYBasis ) + mFrFarOrigin; + + // then we lerp the two ws points in frustum z space + return U[2] * farLS + ( 1.0 - U[2] ) * nearLS; + } + + Vec3d unitToLocalFrustum(const Vec3d& u) const { + Vec3d fzu = mZUnitToLocal.transformH(u); + Vec3d fu = u; + fu[2] = fzu.z(); + return mXYUnitToLocal.transformH(fu); + } + +private: + Mat4d mC2W, mLocalToWorld, mXYUnitToLocal, mZUnitToLocal; + CoordBBox mExtents; + Vec3d mFrNearXBasis, mFrNearYBasis, mFrFarXBasis, mFrFarYBasis; + Vec3d mFrNearOrigin, mFrFarOrigin; + double mNearPlane, mFarPlane; +}; + +} // namespace internal +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_LEGACYFRUSTUM_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Maps.cc b/openvdb_2_3_0_library/openvdb/math/Maps.cc new file mode 100755 index 0000000..5818cab --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Maps.cc @@ -0,0 +1,302 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Maps.h" +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +namespace { + +typedef tbb::mutex Mutex; +typedef Mutex::scoped_lock Lock; + +// Declare this at file scope to ensure thread-safe initialization. +// NOTE: Do *NOT* move this into Maps.h or else we will need to pull in +// Windows.h with things like 'rad2' defined! +Mutex sInitMapRegistryMutex; + +} // unnamed namespace + + +//////////////////////////////////////// + + +MapRegistry* MapRegistry::mInstance = NULL; + + +// Caller is responsible for calling this function serially. +MapRegistry* +MapRegistry::staticInstance() +{ + if (mInstance == NULL) { + OPENVDB_START_THREADSAFE_STATIC_WRITE + mInstance = new MapRegistry(); + OPENVDB_FINISH_THREADSAFE_STATIC_WRITE + return mInstance; + } + return mInstance; +} + + +MapRegistry* +MapRegistry::instance() +{ + Lock lock(sInitMapRegistryMutex); + return staticInstance(); +} + + +MapBase::Ptr +MapRegistry::createMap(const Name& name) +{ + Lock lock(sInitMapRegistryMutex); + MapDictionary::const_iterator iter = staticInstance()->mMap.find(name); + + if (iter == staticInstance()->mMap.end()) { + OPENVDB_THROW(LookupError, "Cannot create map of unregistered type " << name); + } + + return (iter->second)(); +} + + +bool +MapRegistry::isRegistered(const Name& name) +{ + Lock lock(sInitMapRegistryMutex); + return (staticInstance()->mMap.find(name) != staticInstance()->mMap.end()); +} + + +void +MapRegistry::registerMap(const Name& name, MapBase::MapFactory factory) +{ + Lock lock(sInitMapRegistryMutex); + + if (staticInstance()->mMap.find(name) != staticInstance()->mMap.end()) { + OPENVDB_THROW(KeyError, "Map type " << name << " is already registered"); + } + + staticInstance()->mMap[name] = factory; +} + + +void +MapRegistry::unregisterMap(const Name& name) +{ + Lock lock(sInitMapRegistryMutex); + staticInstance()->mMap.erase(name); +} + + +void +MapRegistry::clear() +{ + Lock lock(sInitMapRegistryMutex); + staticInstance()->mMap.clear(); +} + + +//////////////////////////////////////// + +// Utility methods for decomposition + + +SymmetricMap::Ptr +createSymmetricMap(const Mat3d& m) +{ + // test that the mat3 is a rotation || reflection + if (!isSymmetric(m)) { + OPENVDB_THROW(ArithmeticError, + "3x3 Matrix initializing symmetric map was not symmetric"); + } + Vec3d eigenValues; + Mat3d Umatrix; + + bool converged = math::diagonalizeSymmetricMatrix(m, Umatrix, eigenValues); + if (!converged) { + OPENVDB_THROW(ArithmeticError, "Diagonalization of the symmetric matrix failed"); + } + + UnitaryMap rotation(Umatrix); + ScaleMap diagonal(eigenValues); + CompoundMap first(rotation, diagonal); + + UnitaryMap rotationInv(Umatrix.transpose()); + return SymmetricMap::Ptr( new SymmetricMap(first, rotationInv)); +} + + +PolarDecomposedMap::Ptr +createPolarDecomposedMap(const Mat3d& m) +{ + // Because our internal libary left-multiplies vectors against matrices + // we are constructing M = Symmetric * Unitary instead of the more + // standard M = Unitary * Symmetric + Mat3d unitary, symmetric, mat3 = m.transpose(); + + // factor mat3 = U * S where U is unitary and S is symmetric + bool gotPolar = math::polarDecomposition(mat3, unitary, symmetric); + if (!gotPolar) { + OPENVDB_THROW(ArithmeticError, "Polar decomposition of transform failed"); + } + // put the result in a polar map and then copy it into the output polar + UnitaryMap unitary_map(unitary.transpose()); + SymmetricMap::Ptr symmetric_map = createSymmetricMap(symmetric); + + return PolarDecomposedMap::Ptr(new PolarDecomposedMap(*symmetric_map, unitary_map)); +} + + +FullyDecomposedMap::Ptr +createFullyDecomposedMap(const Mat4d& m) +{ + if (!isAffine(m)) { + OPENVDB_THROW(ArithmeticError, + "4x4 Matrix initializing Decomposition map was not affine"); + } + + TranslationMap translate(m.getTranslation()); + PolarDecomposedMap::Ptr polar = createPolarDecomposedMap(m.getMat3()); + + UnitaryAndTranslationMap rotationAndTranslate(polar->secondMap(), translate); + + return FullyDecomposedMap::Ptr(new FullyDecomposedMap(polar->firstMap(), rotationAndTranslate)); +} + + +MapBase::Ptr +simplify(AffineMap::Ptr affine) +{ + if (affine->isScale()) { // can be simplified into a ScaleMap + + Vec3d scale = affine->applyMap(Vec3d(1,1,1)); + if (isApproxEqual(scale[0], scale[1]) && isApproxEqual(scale[0], scale[2])) { + return MapBase::Ptr(new UniformScaleMap(scale[0])); + } else { + return MapBase::Ptr(new ScaleMap(scale)); + } + + } else if (affine->isScaleTranslate()) { // can be simplified into a ScaleTranslateMap + + Vec3d translate = affine->applyMap(Vec3d(0,0,0)); + Vec3d scale = affine->applyMap(Vec3d(1,1,1)) - translate; + + if (isApproxEqual(scale[0], scale[1]) && isApproxEqual(scale[0], scale[2])) { + return MapBase::Ptr(new UniformScaleTranslateMap(scale[0], translate)); + } else { + return MapBase::Ptr(new ScaleTranslateMap(scale, translate)); + } + } + + // could not simplify the general Affine map. + return boost::static_pointer_cast(affine); +} + + +Mat4d +approxInverse(const Mat4d& mat4d) +{ + if (std::abs(mat4d.det()) >= 3 * math::Tolerance::value()) { + try { + Mat4d result = mat4d.inverse(); + return result; + } catch (ArithmeticError& ) { + // Mat4 code couldn't invert. + } + } + + const Mat3d mat3 = mat4d.getMat3(); + const Mat3d mat3T = mat3.transpose(); + const Vec3d trans = mat4d.getTranslation(); + + // absolute tolerance used for the symmetric test. + const double tol = 1.e-6; + + // only create the pseudoInverse for symmetric + bool symmetric = true; + for (int i = 0; i < 3; ++i ) { + for (int j = 0; j < 3; ++j ) { + if (!isApproxEqual(mat3[i][j], mat3T[i][j], tol)) { + symmetric = false; + } + } + } + + if (!symmetric) { + + // not symmetric, so just zero out the mat3 inverse and reverse the translation + + Mat4d result = Mat4d::zero(); + result.setTranslation(-trans); + result[3][3] = 1.f; + return result; + + } else { + + // compute the pseudo inverse + + Mat3d eigenVectors; + Vec3d eigenValues; + + diagonalizeSymmetricMatrix(mat3, eigenVectors, eigenValues); + + Mat3d d = Mat3d::identity(); + for (int i = 0; i < 3; ++i ) { + if (std::abs(eigenValues[i]) < 10.0 * math::Tolerance::value()) { + d[i][i] = 0.f; + } else { + d[i][i] = 1.f/eigenValues[i]; + } + } + // assemble the pseudo inverse + + Mat3d pseudoInv = eigenVectors * d * eigenVectors.transpose(); + Vec3d invTrans = -trans * pseudoInv; + + Mat4d result = Mat4d::identity(); + result.setMat3(pseudoInv); + result.setTranslation(invTrans); + + return result; + } +} + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Maps.h b/openvdb_2_3_0_library/openvdb/math/Maps.h new file mode 100755 index 0000000..109d021 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Maps.h @@ -0,0 +1,2684 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file Maps.h + +#ifndef OPENVDB_MATH_MAPS_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_MAPS_HAS_BEEN_INCLUDED + +#include "Math.h" +#include "Mat4.h" +#include "Vec3.h" +#include "BBox.h" +#include "Coord.h" +#include +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + + +//////////////////////////////////////// + +/// Forward declarations of the different map types + +class MapBase; +class ScaleMap; +class TranslationMap; +class ScaleTranslateMap; +class UniformScaleMap; +class UniformScaleTranslateMap; +class AffineMap; +class UnitaryMap; +class NonlinearFrustumMap; + +template class CompoundMap; + +typedef CompoundMap UnitaryAndTranslationMap; +typedef CompoundMap, UnitaryMap> SpectralDecomposedMap; +typedef SpectralDecomposedMap SymmetricMap; +typedef CompoundMap FullyDecomposedMap; +typedef CompoundMap PolarDecomposedMap; + + +//////////////////////////////////////// + +/// Map traits + +template struct is_linear { static const bool value = false; }; +template<> struct is_linear { static const bool value = true; }; +template<> struct is_linear { static const bool value = true; }; +template<> struct is_linear { static const bool value = true; }; +template<> struct is_linear { static const bool value = true; }; +template<> struct is_linear { static const bool value = true; }; +template<> struct is_linear { static const bool value = true; }; +template<> struct is_linear { static const bool value = true; }; + +template struct is_linear > { + static const bool value = is_linear::value && is_linear::value; +}; + + +template struct is_uniform_scale { static const bool value = false; }; +template<> struct is_uniform_scale { static const bool value = true; }; + +template struct is_uniform_scale_translate { static const bool value = false; }; +template<> struct is_uniform_scale_translate { static const bool value = true; }; +template<> struct is_uniform_scale_translate { + static const bool value = true; +}; + + +template struct is_scale { static const bool value = false; }; +template<> struct is_scale { static const bool value = true; }; + +template struct is_scale_translate { static const bool value = false; }; +template<> struct is_scale_translate { static const bool value = true; }; + + +template struct is_uniform_diagonal_jacobian { + static const bool value = is_uniform_scale::value || is_uniform_scale_translate::value; +}; + +template struct is_diagonal_jacobian { + static const bool value = is_scale::value || is_scale_translate::value; +}; + + +//////////////////////////////////////// + +/// Utility methods + +/// @brief Create a SymmetricMap from a symmetric matrix. +/// Decomposes the map into Rotation Diagonal Rotation^T +OPENVDB_API boost::shared_ptr createSymmetricMap(const Mat3d& m); + + +/// @brief General decomposition of a Matrix into a Unitary (e.g. rotation) +/// following a Symmetric (e.g. stretch & shear) +OPENVDB_API boost::shared_ptr createFullyDecomposedMap(const Mat4d& m); + + +/// @brief Decomposes a general linear into translation following polar decomposition. +/// +/// T U S where: +/// +/// T: Translation +/// U: Unitary (rotation or reflection) +/// S: Symmetric +/// +/// @note: the Symmetric is automatically decomposed into Q D Q^T, where +/// Q is rotation and D is diagonal. +OPENVDB_API boost::shared_ptr createPolarDecomposedMap(const Mat3d& m); + + +/// @brief reduces an AffineMap to a ScaleMap or a ScaleTranslateMap when it can +OPENVDB_API boost::shared_ptr simplify(boost::shared_ptr affine); + +/// @brief Returns the left pseudoInverse of the input matrix when the 3x3 part is symmetric +/// otherwise it zeros the 3x3 and reverses the translation. +OPENVDB_API Mat4d approxInverse(const Mat4d& mat); + + +//////////////////////////////////////// + + +/// @brief Abstract base class for maps +class OPENVDB_API MapBase +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + typedef Ptr (*MapFactory)(); + + virtual ~MapBase(){} + + virtual boost::shared_ptr getAffineMap() const = 0; + + /// Return the name of this map's concrete type (e.g., @c "AffineMap"). + virtual Name type() const = 0; + + /// Return @c true if this map is of concrete type @c MapT (e.g., AffineMap). + template bool isType() const { return this->type() == MapT::mapType(); } + + /// Return @c true if this map is equal to the given map. + virtual bool isEqual(const MapBase& other) const = 0; + + /// Return @c true if this map is linear. + virtual bool isLinear() const = 0; + /// Return @c true if the spacing between the image of latice is uniform in all directions + virtual bool hasUniformScale() const = 0; + + virtual Vec3d applyMap(const Vec3d& in) const = 0; + virtual Vec3d applyInverseMap(const Vec3d& in) const = 0; + + //@{ + /// @brief Apply the Inverse Jacobian Transpose of this map to a vector. + /// For a linear map this is equivalent to applying the transpose of + /// inverse map excluding translation. + virtual Vec3d applyIJT(const Vec3d& in) const = 0; + virtual Vec3d applyIJT(const Vec3d& in, const Vec3d& domainPos) const = 0; + //@} + + virtual Mat3d applyIJC(const Mat3d& m) const = 0; + virtual Mat3d applyIJC(const Mat3d& m, const Vec3d& v, const Vec3d& domainPos) const = 0; + + + virtual double determinant() const = 0; + virtual double determinant(const Vec3d&) const = 0; + + + //@{ + /// @brief Method to return the local size of a voxel. + /// When a location is specified as an argument, it is understood to be + /// be in the domain of the map (i.e. index space) + virtual Vec3d voxelSize() const = 0; + virtual Vec3d voxelSize(const Vec3d&) const = 0; + //@} + + virtual void read(std::istream&) = 0; + virtual void write(std::ostream&) const = 0; + + virtual std::string str() const = 0; + + virtual MapBase::Ptr copy() const = 0; + + //@{ + /// @brief Methods to update the map + virtual MapBase::Ptr preRotate(double radians, Axis axis = X_AXIS) const = 0; + virtual MapBase::Ptr preTranslate(const Vec3d&) const = 0; + virtual MapBase::Ptr preScale(const Vec3d&) const = 0; + virtual MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const = 0; + + virtual MapBase::Ptr postRotate(double radians, Axis axis = X_AXIS) const = 0; + virtual MapBase::Ptr postTranslate(const Vec3d&) const = 0; + virtual MapBase::Ptr postScale(const Vec3d&) const = 0; + virtual MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const = 0; + //@} + + //@{ + /// @brief Apply the Jacobian of this map to a vector. + /// For a linear map this is equivalent to applying the map excluding translation. + /// @warning Houdini 12.5 uses an earlier version of OpenVDB, and maps created + /// with that version lack a virtual table entry for this method. Do not call + /// this method from Houdini 12.5. + virtual Vec3d applyJacobian(const Vec3d& in) const = 0; + virtual Vec3d applyJacobian(const Vec3d& in, const Vec3d& domainPos) const = 0; + //@} + + //@{ + /// @brief Apply the InverseJacobian of this map to a vector. + /// For a linear map this is equivalent to applying the map inverse excluding translation. + /// @warning Houdini 12.5 uses an earlier version of OpenVDB, and maps created + /// with that version lack a virtual table entry for this method. Do not call + /// this method from Houdini 12.5. + virtual Vec3d applyInverseJacobian(const Vec3d& in) const = 0; + virtual Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d& domainPos) const = 0; + //@} + + + //@{ + /// @brief Apply the Jacobian transpose of this map to a vector. + /// For a linear map this is equivalent to applying the transpose of the map + /// excluding translation. + /// @warning Houdini 12.5 uses an earlier version of OpenVDB, and maps created + /// with that version lack a virtual table entry for this method. Do not call + /// this method from Houdini 12.5. + virtual Vec3d applyJT(const Vec3d& in) const = 0; + virtual Vec3d applyJT(const Vec3d& in, const Vec3d& domainPos) const = 0; + //@} + + /// @brief Return a new map representing the inverse of this map. + /// @throw NotImplementedError if the map is a NonlinearFrustumMap. + /// @warning Houdini 12.5 uses an earlier version of OpenVDB, and maps created + /// with that version lack a virtual table entry for this method. Do not call + /// this method from Houdini 12.5. + virtual MapBase::Ptr inverseMap() const = 0; + +protected: + MapBase() {} + + template + static bool isEqualBase(const MapT& self, const MapBase& other) + { + return other.isType() && (self == *static_cast(&other)); + } +}; + + +//////////////////////////////////////// + + +/// @brief Threadsafe singleton object for accessing the map type-name dictionary. +/// Associates a map type-name with a factory function. +class OPENVDB_API MapRegistry +{ +public: + typedef std::map MapDictionary; + + static MapRegistry* instance(); + + /// Create a new map of the given (registered) type name. + static MapBase::Ptr createMap(const Name&); + + /// Return @c true if the given map type name is registered. + static bool isRegistered(const Name&); + + /// Register a map type along with a factory function. + static void registerMap(const Name&, MapBase::MapFactory); + + /// Remove a map type from the registry. + static void unregisterMap(const Name&); + + /// Clear the map type registry. + static void clear(); + +private: + MapRegistry() {} + + static MapRegistry* staticInstance(); + + static MapRegistry* mInstance; + + MapDictionary mMap; +}; + + +//////////////////////////////////////// + + +/// @brief A general linear transform using homogeneous coordinates to perform +/// rotation, scaling, shear and translation +class OPENVDB_API AffineMap: public MapBase +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + AffineMap(): + mMatrix(Mat4d::identity()), + mMatrixInv(Mat4d::identity()), + mJacobianInv(Mat3d::identity()), + mDeterminant(1), + mVoxelSize(Vec3d(1,1,1)), + mIsDiagonal(true), + mIsIdentity(true) + // the default constructor for translation is zero + { + } + + AffineMap(const Mat3d& m) + { + Mat4d mat4(Mat4d::identity()); + mat4.setMat3(m); + mMatrix = mat4; + updateAcceleration(); + } + + AffineMap(const Mat4d& m): mMatrix(m) + { + if (!isAffine(m)) { + OPENVDB_THROW(ArithmeticError, + "Tried to initialize an affine transform from a non-affine 4x4 matrix"); + } + updateAcceleration(); + } + + AffineMap(const AffineMap& other): + MapBase(other), + mMatrix(other.mMatrix), + mMatrixInv(other.mMatrixInv), + mJacobianInv(other.mJacobianInv), + mDeterminant(other.mDeterminant), + mVoxelSize(other.mVoxelSize), + mIsDiagonal(other.mIsDiagonal), + mIsIdentity(other.mIsIdentity) + { + } + + /// @brief constructor that merges the matrixes for two affine maps + AffineMap(const AffineMap& first, const AffineMap& second): + mMatrix(first.mMatrix * second.mMatrix) + { + updateAcceleration(); + } + + ~AffineMap() {} + + /// Return a MapBase::Ptr to a new AffineMap + static MapBase::Ptr create() { return MapBase::Ptr(new AffineMap()); } + /// Return a MapBase::Ptr to a deep copy of this map + MapBase::Ptr copy() const { return MapBase::Ptr(new AffineMap(*this)); } + + MapBase::Ptr inverseMap() const { return MapBase::Ptr(new AffineMap(mMatrixInv)); } + + static bool isRegistered() { return MapRegistry::isRegistered(AffineMap::mapType()); } + + static void registerMap() + { + MapRegistry::registerMap( + AffineMap::mapType(), + AffineMap::create); + } + + Name type() const { return mapType(); } + static Name mapType() { return Name("AffineMap"); } + + /// Return @c true (an AffineMap is always linear). + bool isLinear() const { return true; } + + /// Return @c false ( test if this is unitary with translation ) + bool hasUniformScale() const + { + Mat3d mat = mMatrix.getMat3(); + const double det = mat.det(); + if (isApproxEqual(det, double(0))) { + return false; + } else { + mat *= (1.f / pow(std::abs(det),1./3.)); + return isUnitary(mat); + } + } + + virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } + + bool operator==(const AffineMap& other) const + { + // the Mat.eq() is approximate + if (!mMatrix.eq(other.mMatrix)) { return false; } + if (!mMatrixInv.eq(other.mMatrixInv)) { return false; } + return true; + } + + bool operator!=(const AffineMap& other) const { return !(*this == other); } + + AffineMap& operator=(const AffineMap& other) + { + mMatrix = other.mMatrix; + mMatrixInv = other.mMatrixInv; + + mJacobianInv = other.mJacobianInv; + mDeterminant = other.mDeterminant; + mVoxelSize = other.mVoxelSize; + mIsDiagonal = other.mIsDiagonal; + mIsIdentity = other.mIsIdentity; + return *this; + } + /// Return the image of @c in under the map + Vec3d applyMap(const Vec3d& in) const { return in * mMatrix; } + /// Return the pre-image of @c in under the map + Vec3d applyInverseMap(const Vec3d& in) const {return in * mMatrixInv; } + + /// Return the Jacobian of the map applied to @a in. + Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const { return applyJacobian(in); } + /// Return the Jacobian of the map applied to @a in. + Vec3d applyJacobian(const Vec3d& in) const { return mMatrix.transform3x3(in); } + + /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) + Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d&) const { return applyInverseJacobian(in); } + /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) + Vec3d applyInverseJacobian(const Vec3d& in) const { return mMatrixInv.transform3x3(in); } + + /// Return the Jacobian Transpose of the map applied to @a in. + /// This tranforms range-space gradients to domain-space gradients + Vec3d applyJT(const Vec3d& in, const Vec3d&) const { return applyJT(in); } + /// Return the Jacobian Transpose of the map applied to @a in. + Vec3d applyJT(const Vec3d& in) const { + const double* m = mMatrix.asPointer(); + return Vec3d( m[ 0] * in[0] + m[ 1] * in[1] + m[ 2] * in[2], + m[ 4] * in[0] + m[ 5] * in[1] + m[ 6] * in[2], + m[ 8] * in[0] + m[ 9] * in[1] + m[10] * in[2] ); + } + + /// Return the transpose of the inverse Jacobian of the map applied to @a in. + Vec3d applyIJT(const Vec3d& in, const Vec3d&) const { return applyIJT(in); } + /// Return the transpose of the inverse Jacobian of the map applied to @c in + Vec3d applyIJT(const Vec3d& in) const { return in * mJacobianInv; } + /// Return the Jacobian Curvature: zero for a linear map + Mat3d applyIJC(const Mat3d& m) const { + return mJacobianInv.transpose()* m * mJacobianInv; + } + Mat3d applyIJC(const Mat3d& in, const Vec3d& , const Vec3d& ) const { + return applyIJC(in); + } + /// Return the determinant of the Jacobian, ignores argument + double determinant(const Vec3d& ) const { return determinant(); } + /// Return the determinant of the Jacobian + double determinant() const { return mDeterminant; } + + //@{ + /// @brief Return the lengths of the images of the segments + /// (0,0,0)-(1,0,0), (0,0,0)-(0,1,0) and (0,0,0)-(0,0,1). + Vec3d voxelSize() const { return mVoxelSize; } + Vec3d voxelSize(const Vec3d&) const { return voxelSize(); } + //@} + + /// Return @c true if the underlying matrix is approximately an identity + bool isIdentity() const { return mIsIdentity; } + /// Return @c true if the underylying matrix is diagonal + bool isDiagonal() const { return mIsDiagonal; } + /// Return @c true if the map is equivalent to a ScaleMap + bool isScale() const { return isDiagonal(); } + /// Return @c true if the map is equivalent to a ScaleTranslateMap + bool isScaleTranslate() const { return math::isDiagonal(mMatrix.getMat3()); } + + + // Methods that modify the existing affine map + + //@{ + /// @brief Modify the existing affine map by pre-applying the given operation. + void accumPreRotation(Axis axis, double radians) + { + mMatrix.preRotate(axis, radians); + updateAcceleration(); + } + void accumPreScale(const Vec3d& v) + { + mMatrix.preScale(v); + updateAcceleration(); + } + void accumPreTranslation(const Vec3d& v) + { + mMatrix.preTranslate(v); + updateAcceleration(); + } + void accumPreShear(Axis axis0, Axis axis1, double shear) + { + mMatrix.preShear(axis0, axis1, shear); + updateAcceleration(); + } + //@} + + + //@{ + /// @brief Modify the existing affine map by post-applying the given operation. + void accumPostRotation(Axis axis, double radians) + { + mMatrix.postRotate(axis, radians); + updateAcceleration(); + } + void accumPostScale(const Vec3d& v) + { + mMatrix.postScale(v); + updateAcceleration(); + } + void accumPostTranslation(const Vec3d& v) + { + mMatrix.postTranslate(v); + updateAcceleration(); + } + void accumPostShear(Axis axis0, Axis axis1, double shear) + { + mMatrix.postShear(axis0, axis1, shear); + updateAcceleration(); + } + //@} + + + /// read serialization + void read(std::istream& is) + { + mMatrix.read(is); + updateAcceleration(); + } + + /// write serialization + void write(std::ostream& os) const + { + mMatrix.write(os); + } + + /// string serialization, useful for debugging + std::string str() const + { + std::ostringstream buffer; + buffer << " - mat4:\n" << mMatrix.str() << std::endl; + buffer << " - voxel dimensions: " << mVoxelSize << std::endl; + return buffer.str(); + } + + /// on-demand decomposition of the affine map + boost::shared_ptr createDecomposedMap() + { + return createFullyDecomposedMap(mMatrix); + } + + /// Return AffineMap::Ptr to a deep copy of the current AffineMap + AffineMap::Ptr getAffineMap() const { return AffineMap::Ptr(new AffineMap(*this)); } + + /// Return AffineMap::Ptr to the inverse of this map + AffineMap::Ptr inverse() const { return AffineMap::Ptr(new AffineMap(mMatrixInv)); } + + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of prepending the appropraite operation. + MapBase::Ptr preRotate(double radians, Axis axis = X_AXIS) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreRotation(axis, radians); + return simplify(affineMap); + } + MapBase::Ptr preTranslate(const Vec3d& t) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreTranslation(t); + return boost::static_pointer_cast(affineMap); + } + MapBase::Ptr preScale(const Vec3d& s) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreScale(s); + return boost::static_pointer_cast(affineMap); + } + MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreShear(axis0, axis1, shear); + return simplify(affineMap); + } + //@} + + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of postfixing the appropraite operation. + MapBase::Ptr postRotate(double radians, Axis axis = X_AXIS) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostRotation(axis, radians); + return simplify(affineMap); + } + MapBase::Ptr postTranslate(const Vec3d& t) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostTranslation(t); + return boost::static_pointer_cast(affineMap); + } + MapBase::Ptr postScale(const Vec3d& s) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostScale(s); + return boost::static_pointer_cast(affineMap); + } + MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostShear(axis0, axis1, shear); + return simplify(affineMap); + } + //@} + + /// Return the matrix representation of this AffineMap + Mat4d getMat4() const { return mMatrix;} + const Mat4d& getConstMat4() const {return mMatrix;} + const Mat3d& getConstJacobianInv() const {return mJacobianInv;} + +private: + void updateAcceleration() { + Mat3d mat3 = mMatrix.getMat3(); + mDeterminant = mat3.det(); + + if (std::abs(mDeterminant) < (3.0 * math::Tolerance::value())) { + OPENVDB_THROW(ArithmeticError, + "Tried to initialize an affine transform from a nearly singular matrix"); + } + mMatrixInv = mMatrix.inverse(); + mJacobianInv = mat3.inverse().transpose(); + mIsDiagonal = math::isDiagonal(mMatrix); + mIsIdentity = math::isIdentity(mMatrix); + Vec3d pos = applyMap(Vec3d(0,0,0)); + mVoxelSize(0) = (applyMap(Vec3d(1,0,0)) - pos).length(); + mVoxelSize(1) = (applyMap(Vec3d(0,1,0)) - pos).length(); + mVoxelSize(2) = (applyMap(Vec3d(0,0,1)) - pos).length(); + } + + // the underlying matrix + Mat4d mMatrix; + + // stored for acceleration + Mat4d mMatrixInv; + Mat3d mJacobianInv; + double mDeterminant; + Vec3d mVoxelSize; + bool mIsDiagonal, mIsIdentity; +}; // class AffineMap + + +//////////////////////////////////////// + + +/// @brief A specialized Affine transform that scales along the principal axis +/// the scaling need not be uniform in the three-directions +class OPENVDB_API ScaleMap: public MapBase +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + ScaleMap(): MapBase(), mScaleValues(Vec3d(1,1,1)), mVoxelSize(Vec3d(1,1,1)), + mScaleValuesInverse(Vec3d(1,1,1)), + mInvScaleSqr(1,1,1), mInvTwiceScale(0.5,0.5,0.5){} + + ScaleMap(const Vec3d& scale): + MapBase(), + mScaleValues(scale), + mVoxelSize(Vec3d(std::abs(scale(0)),std::abs(scale(1)), std::abs(scale(2)))) + { + double determinant = scale[0]* scale[1] * scale[2]; + if (std::abs(determinant) < 3.0 * math::Tolerance::value()) { + OPENVDB_THROW(ArithmeticError, "Non-zero scale values required"); + } + mScaleValuesInverse = 1.0 / mScaleValues; + mInvScaleSqr = mScaleValuesInverse * mScaleValuesInverse; + mInvTwiceScale = mScaleValuesInverse / 2; + } + + ScaleMap(const ScaleMap& other): + MapBase(), + mScaleValues(other.mScaleValues), + mVoxelSize(other.mVoxelSize), + mScaleValuesInverse(other.mScaleValuesInverse), + mInvScaleSqr(other.mInvScaleSqr), + mInvTwiceScale(other.mInvTwiceScale) + { + } + + ~ScaleMap() {} + + /// Return a MapBase::Ptr to a new ScaleMap + static MapBase::Ptr create() { return MapBase::Ptr(new ScaleMap()); } + /// Return a MapBase::Ptr to a deep copy of this map + MapBase::Ptr copy() const { return MapBase::Ptr(new ScaleMap(*this)); } + + MapBase::Ptr inverseMap() const { return MapBase::Ptr(new ScaleMap(mScaleValuesInverse)); } + + static bool isRegistered() { return MapRegistry::isRegistered(ScaleMap::mapType()); } + + static void registerMap() + { + MapRegistry::registerMap( + ScaleMap::mapType(), + ScaleMap::create); + } + + Name type() const { return mapType(); } + static Name mapType() { return Name("ScaleMap"); } + + /// Return @c true (a ScaleMap is always linear). + bool isLinear() const { return true; } + + /// Return @c true if the values have the same magitude (eg. -1, 1, -1 would be a rotation). + bool hasUniformScale() const + { + bool value = isApproxEqual( + std::abs(mScaleValues.x()), std::abs(mScaleValues.y()), double(5e-7)); + value = value && isApproxEqual( + std::abs(mScaleValues.x()), std::abs(mScaleValues.z()), double(5e-7)); + return value; + } + + /// Return the image of @c in under the map + Vec3d applyMap(const Vec3d& in) const + { + return Vec3d( + in.x() * mScaleValues.x(), + in.y() * mScaleValues.y(), + in.z() * mScaleValues.z()); + } + /// Return the pre-image of @c in under the map + Vec3d applyInverseMap(const Vec3d& in) const + { + return Vec3d( + in.x() * mScaleValuesInverse.x(), + in.y() * mScaleValuesInverse.y(), + in.z() * mScaleValuesInverse.z()); + } + /// Return the Jacobian of the map applied to @a in. + Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const { return applyJacobian(in); } + /// Return the Jacobian of the map applied to @a in. + Vec3d applyJacobian(const Vec3d& in) const { return applyMap(in); } + + /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) + Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d&) const { return applyInverseJacobian(in); } + /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) + Vec3d applyInverseJacobian(const Vec3d& in) const { return applyInverseMap(in); } + + /// Return the Jacobian Transpose of the map applied to @a in. + /// This tranforms range-space gradients to domain-space gradients + Vec3d applyJT(const Vec3d& in, const Vec3d&) const { return applyJT(in); } + /// Return the Jacobian Transpose of the map applied to @a in. + Vec3d applyJT(const Vec3d& in) const { return applyMap(in); } + + + + /// @brief Return the transpose of the inverse Jacobian of the map applied to @a in. + /// @details Ignores second argument + Vec3d applyIJT(const Vec3d& in, const Vec3d&) const { return applyIJT(in);} + /// Return the transpose of the inverse Jacobian of the map applied to @c in + Vec3d applyIJT(const Vec3d& in) const { return applyInverseMap(in); } + /// Return the Jacobian Curvature: zero for a linear map + Mat3d applyIJC(const Mat3d& in) const + { + Mat3d tmp; + for (int i = 0; i < 3; i++) { + tmp.setRow(i, in.row(i) * mScaleValuesInverse(i)); + } + for (int i = 0; i < 3; i++) { + tmp.setCol(i, tmp.col(i) * mScaleValuesInverse(i)); + } + return tmp; + } + Mat3d applyIJC(const Mat3d& in, const Vec3d&, const Vec3d&) const { return applyIJC(in); } + /// Return the product of the scale values, ignores argument + double determinant(const Vec3d&) const { return determinant(); } + /// Return the product of the scale values + double determinant() const { return mScaleValues.x() * mScaleValues.y() * mScaleValues.z(); } + + /// Return the scale values that define the map + const Vec3d& getScale() const {return mScaleValues;} + + /// Return the square of the scale. Used to optimize some finite difference calculations + const Vec3d& getInvScaleSqr() const { return mInvScaleSqr; } + /// Return 1/(2 scale). Used to optimize some finite difference calculations + const Vec3d& getInvTwiceScale() const { return mInvTwiceScale; } + /// Return 1/(scale) + const Vec3d& getInvScale() const { return mScaleValuesInverse; } + + //@{ + /// @brief Returns the lengths of the images + /// of the segments + /// \f$(0,0,0)-(1,0,0)\f$, \f$(0,0,0)-(0,1,0)\f$, \f$(0,0,0)-(0,0,1)\f$ + /// this is equivalent to the absolute values of the scale values + Vec3d voxelSize() const { return mVoxelSize; } + Vec3d voxelSize(const Vec3d&) const { return voxelSize(); } + //@} + + /// read serialization + void read(std::istream& is) + { + mScaleValues.read(is); + mVoxelSize.read(is); + mScaleValuesInverse.read(is); + mInvScaleSqr.read(is); + mInvTwiceScale.read(is); + } + /// write serialization + void write(std::ostream& os) const + { + mScaleValues.write(os); + mVoxelSize.write(os); + mScaleValuesInverse.write(os); + mInvScaleSqr.write(os); + mInvTwiceScale.write(os); + } + /// string serialization, useful for debuging + std::string str() const + { + std::ostringstream buffer; + buffer << " - scale: " << mScaleValues << std::endl; + buffer << " - voxel dimensions: " << mVoxelSize << std::endl; + return buffer.str(); + } + + virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } + + bool operator==(const ScaleMap& other) const + { + // ::eq() uses a tolerance + if (!mScaleValues.eq(other.mScaleValues)) { return false; } + return true; + } + + bool operator!=(const ScaleMap& other) const { return !(*this == other); } + + /// Return a AffineMap equivalent to this map + AffineMap::Ptr getAffineMap() const + { + return AffineMap::Ptr(new AffineMap(math::scale(mScaleValues))); + } + + + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of prepending the appropraite operation to the existing map + MapBase::Ptr preRotate(double radians, Axis axis) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreRotation(axis, radians); + return simplify(affineMap); + } + + MapBase::Ptr preTranslate(const Vec3d& tr) const; + + MapBase::Ptr preScale(const Vec3d& v) const; + + MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreShear(axis0, axis1, shear); + return simplify(affineMap); + } + //@} + + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of prepending the appropraite operation to the existing map. + MapBase::Ptr postRotate(double radians, Axis axis) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostRotation(axis, radians); + return simplify(affineMap); + } + + MapBase::Ptr postTranslate(const Vec3d& tr) const; + + MapBase::Ptr postScale(const Vec3d& v) const; + + MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostShear(axis0, axis1, shear); + return simplify(affineMap); + } + //@} + +private: + Vec3d mScaleValues, mVoxelSize, mScaleValuesInverse, mInvScaleSqr, mInvTwiceScale; +}; // class ScaleMap + + +/// @brief A specialized Affine transform that scales along the principal axis +/// the scaling is uniform in the three-directions +class OPENVDB_API UniformScaleMap: public ScaleMap +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + UniformScaleMap(): ScaleMap(Vec3d(1,1,1)) {} + UniformScaleMap(double scale): ScaleMap(Vec3d(scale, scale, scale)) {} + UniformScaleMap(const UniformScaleMap& other): ScaleMap(other) {} + ~UniformScaleMap() {} + + /// Return a MapBase::Ptr to a new UniformScaleMap + static MapBase::Ptr create() { return MapBase::Ptr(new UniformScaleMap()); } + /// Return a MapBase::Ptr to a deep copy of this map + MapBase::Ptr copy() const { return MapBase::Ptr(new UniformScaleMap(*this)); } + + MapBase::Ptr inverseMap() const + { + const Vec3d& invScale = getInvScale(); + return MapBase::Ptr(new UniformScaleMap( invScale[0])); + } + + static bool isRegistered() { return MapRegistry::isRegistered(UniformScaleMap::mapType()); } + static void registerMap() + { + MapRegistry::registerMap( + UniformScaleMap::mapType(), + UniformScaleMap::create); + } + + Name type() const { return mapType(); } + static Name mapType() { return Name("UniformScaleMap"); } + + virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } + + bool operator==(const UniformScaleMap& other) const { return ScaleMap::operator==(other); } + bool operator!=(const UniformScaleMap& other) const { return !(*this == other); } + + /// Return a MapBase::Ptr to a UniformScaleTraslateMap that is the result of + /// pre-translation on this map + MapBase::Ptr preTranslate(const Vec3d& tr) const; + + /// Return a MapBase::Ptr to a UniformScaleTraslateMap that is the result of + /// post-translation on this map + MapBase::Ptr postTranslate(const Vec3d& tr) const; + +}; // class UniformScaleMap + + +//////////////////////////////////////// + + +inline MapBase::Ptr +ScaleMap::preScale(const Vec3d& v) const +{ + const Vec3d new_scale(v * mScaleValues); + if (isApproxEqual(new_scale[0],new_scale[1]) && isApproxEqual(new_scale[0],new_scale[2])) { + return MapBase::Ptr(new UniformScaleMap(new_scale[0])); + } else { + return MapBase::Ptr(new ScaleMap(new_scale)); + } +} + + +inline MapBase::Ptr +ScaleMap::postScale(const Vec3d& v) const +{ // pre-post Scale are the same for a scale map + return preScale(v); +} + + +/// @brief A specialized linear transform that performs a translation +class OPENVDB_API TranslationMap: public MapBase +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + // default constructor is a translation by zero. + TranslationMap(): MapBase(), mTranslation(Vec3d(0,0,0)) {} + TranslationMap(const Vec3d& t): MapBase(), mTranslation(t) {} + TranslationMap(const TranslationMap& other): MapBase(), mTranslation(other.mTranslation) {} + + ~TranslationMap() {} + + /// Return a MapBase::Ptr to a new TranslationMap + static MapBase::Ptr create() { return MapBase::Ptr(new TranslationMap()); } + /// Return a MapBase::Ptr to a deep copy of this map + MapBase::Ptr copy() const { return MapBase::Ptr(new TranslationMap(*this)); } + + MapBase::Ptr inverseMap() const { return MapBase::Ptr(new TranslationMap(-mTranslation)); } + + static bool isRegistered() { return MapRegistry::isRegistered(TranslationMap::mapType()); } + + static void registerMap() + { + MapRegistry::registerMap( + TranslationMap::mapType(), + TranslationMap::create); + } + + Name type() const { return mapType(); } + static Name mapType() { return Name("TranslationMap"); } + + /// Return @c true (a TranslationMap is always linear). + bool isLinear() const { return true; } + + /// Return @c false (by convention true) + bool hasUniformScale() const { return true; } + + /// Return the image of @c in under the map + Vec3d applyMap(const Vec3d& in) const { return in + mTranslation; } + /// Return the pre-image of @c in under the map + Vec3d applyInverseMap(const Vec3d& in) const { return in - mTranslation; } + /// Return the Jacobian of the map applied to @a in. + Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const { return applyJacobian(in); } + /// Return the Jacobian of the map applied to @a in. + Vec3d applyJacobian(const Vec3d& in) const { return in; } + + /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) + Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d&) const { return applyInverseJacobian(in); } + /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) + Vec3d applyInverseJacobian(const Vec3d& in) const { return in; } + + + /// Return the Jacobian Transpose of the map applied to @a in. + /// This tranforms range-space gradients to domain-space gradients + Vec3d applyJT(const Vec3d& in, const Vec3d&) const { return applyJT(in); } + /// Return the Jacobian Transpose of the map applied to @a in. + Vec3d applyJT(const Vec3d& in) const { return in; } + + /// @brief Return the transpose of the inverse Jacobian (Identity for TranslationMap) + /// of the map applied to @c in, ignores second argument + Vec3d applyIJT(const Vec3d& in, const Vec3d& ) const { return applyIJT(in);} + /// @brief Return the transpose of the inverse Jacobian (Identity for TranslationMap) + /// of the map applied to @c in + Vec3d applyIJT(const Vec3d& in) const {return in;} + /// Return the Jacobian Curvature: zero for a linear map + Mat3d applyIJC(const Mat3d& mat) const {return mat;} + Mat3d applyIJC(const Mat3d& mat, const Vec3d&, const Vec3d&) const { return applyIJC(mat); } + + /// Return @c 1 + double determinant(const Vec3d& ) const { return determinant(); } + /// Return @c 1 + double determinant() const { return 1.0; } + + /// Return \f$ (1,1,1) \f$ + Vec3d voxelSize() const { return Vec3d(1,1,1);} + /// Return \f$ (1,1,1) \f$ + Vec3d voxelSize(const Vec3d&) const { return voxelSize();} + + /// Return the translation vector + const Vec3d& getTranslation() const { return mTranslation; } + /// read serialization + void read(std::istream& is) { mTranslation.read(is); } + /// write serialization + void write(std::ostream& os) const { mTranslation.write(os); } + + /// string serialization, useful for debuging + std::string str() const + { + std::ostringstream buffer; + buffer << " - translation: " << mTranslation << std::endl; + return buffer.str(); + } + + virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } + + bool operator==(const TranslationMap& other) const + { + // ::eq() uses a tolerance + return mTranslation.eq(other.mTranslation); + } + + bool operator!=(const TranslationMap& other) const { return !(*this == other); } + + /// Return AffineMap::Ptr to an AffineMap equivalent to *this + AffineMap::Ptr getAffineMap() const + { + Mat4d matrix(Mat4d::identity()); + matrix.setTranslation(mTranslation); + + AffineMap::Ptr affineMap(new AffineMap(matrix)); + return affineMap; + } + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of prepending the appropriate operation. + MapBase::Ptr preRotate(double radians, Axis axis) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreRotation(axis, radians); + return simplify(affineMap); + + } + MapBase::Ptr preTranslate(const Vec3d& t) const + { + return MapBase::Ptr(new TranslationMap(t + mTranslation)); + } + + MapBase::Ptr preScale(const Vec3d& v) const; + + MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreShear(axis0, axis1, shear); + return simplify(affineMap); + } + //@} + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of postfixing the appropriate operation. + MapBase::Ptr postRotate(double radians, Axis axis) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostRotation(axis, radians); + return simplify(affineMap); + + } + MapBase::Ptr postTranslate(const Vec3d& t) const + { // post and pre are the same for this + return MapBase::Ptr(new TranslationMap(t + mTranslation)); + } + + MapBase::Ptr postScale(const Vec3d& v) const; + + MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostShear(axis0, axis1, shear); + return simplify(affineMap); + } + //@} + +private: + Vec3d mTranslation; +}; // class TranslationMap + + +//////////////////////////////////////// + + +/// @brief A specialized Affine transform that scales along the principal axis +/// the scaling need not be uniform in the three-directions, and then +/// translates the result. +class OPENVDB_API ScaleTranslateMap: public MapBase +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + ScaleTranslateMap(): + MapBase(), + mTranslation(Vec3d(0,0,0)), + mScaleValues(Vec3d(1,1,1)), + mVoxelSize(Vec3d(1,1,1)), + mScaleValuesInverse(Vec3d(1,1,1)), + mInvScaleSqr(1,1,1), + mInvTwiceScale(0.5,0.5,0.5) + { + } + + ScaleTranslateMap(const Vec3d& scale, const Vec3d& translate): + MapBase(), + mTranslation(translate), + mScaleValues(scale), + mVoxelSize(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2))) + { + const double determinant = scale[0]* scale[1] * scale[2]; + if (std::abs(determinant) < 3.0 * math::Tolerance::value()) { + OPENVDB_THROW(ArithmeticError, "Non-zero scale values required"); + } + mScaleValuesInverse = 1.0 / mScaleValues; + mInvScaleSqr = mScaleValuesInverse * mScaleValuesInverse; + mInvTwiceScale = mScaleValuesInverse / 2; + } + + ScaleTranslateMap(const ScaleMap& scale, const TranslationMap& translate): + MapBase(), + mTranslation(translate.getTranslation()), + mScaleValues(scale.getScale()), + mVoxelSize(std::abs(mScaleValues(0)), + std::abs(mScaleValues(1)), + std::abs(mScaleValues(2))), + mScaleValuesInverse(1.0 / scale.getScale()) + { + mInvScaleSqr = mScaleValuesInverse * mScaleValuesInverse; + mInvTwiceScale = mScaleValuesInverse / 2; + } + + ScaleTranslateMap(const ScaleTranslateMap& other): + MapBase(), + mTranslation(other.mTranslation), + mScaleValues(other.mScaleValues), + mVoxelSize(other.mVoxelSize), + mScaleValuesInverse(other.mScaleValuesInverse), + mInvScaleSqr(other.mInvScaleSqr), + mInvTwiceScale(other.mInvTwiceScale) + {} + + ~ScaleTranslateMap() {} + + /// Return a MapBase::Ptr to a new ScaleTranslateMap + static MapBase::Ptr create() { return MapBase::Ptr(new ScaleTranslateMap()); } + /// Return a MapBase::Ptr to a deep copy of this map + MapBase::Ptr copy() const { return MapBase::Ptr(new ScaleTranslateMap(*this)); } + + MapBase::Ptr inverseMap() const + { + return MapBase::Ptr(new ScaleTranslateMap( + mScaleValuesInverse, -mScaleValuesInverse * mTranslation)); + } + + static bool isRegistered() { return MapRegistry::isRegistered(ScaleTranslateMap::mapType()); } + + static void registerMap() + { + MapRegistry::registerMap( + ScaleTranslateMap::mapType(), + ScaleTranslateMap::create); + } + + Name type() const { return mapType(); } + static Name mapType() { return Name("ScaleTranslateMap"); } + + /// Return @c true (a ScaleTranslateMap is always linear). + bool isLinear() const { return true; } + + /// @brief Return @c true if the scale values have the same magnitude + /// (eg. -1, 1, -1 would be a rotation). + bool hasUniformScale() const + { + bool value = isApproxEqual( + std::abs(mScaleValues.x()), std::abs(mScaleValues.y()), double(5e-7)); + value = value && isApproxEqual( + std::abs(mScaleValues.x()), std::abs(mScaleValues.z()), double(5e-7)); + return value; + } + + /// Return the image of @c under the map + Vec3d applyMap(const Vec3d& in) const + { + return Vec3d( + in.x() * mScaleValues.x() + mTranslation.x(), + in.y() * mScaleValues.y() + mTranslation.y(), + in.z() * mScaleValues.z() + mTranslation.z()); + } + /// Return the pre-image of @c under the map + Vec3d applyInverseMap(const Vec3d& in) const + { + return Vec3d( + (in.x() - mTranslation.x() ) * mScaleValuesInverse.x(), + (in.y() - mTranslation.y() ) * mScaleValuesInverse.y(), + (in.z() - mTranslation.z() ) * mScaleValuesInverse.z()); + } + + /// Return the Jacobian of the map applied to @a in. + Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const { return applyJacobian(in); } + /// Return the Jacobian of the map applied to @a in. + Vec3d applyJacobian(const Vec3d& in) const { return in * mScaleValues; } + + /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) + Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d&) const { return applyInverseJacobian(in); } + /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) + Vec3d applyInverseJacobian(const Vec3d& in) const { return in * mScaleValuesInverse; } + + /// Return the Jacobian Transpose of the map applied to @a in. + /// This tranforms range-space gradients to domain-space gradients + Vec3d applyJT(const Vec3d& in, const Vec3d&) const { return applyJT(in); } + /// Return the Jacobian Transpose of the map applied to @a in. + Vec3d applyJT(const Vec3d& in) const { return applyJacobian(in); } + + /// @brief Return the transpose of the inverse Jacobian of the map applied to @a in + /// @details Ignores second argument + Vec3d applyIJT(const Vec3d& in, const Vec3d& ) const { return applyIJT(in);} + /// Return the transpose of the inverse Jacobian of the map applied to @c in + Vec3d applyIJT(const Vec3d& in) const + { + return Vec3d( + in.x() * mScaleValuesInverse.x(), + in.y() * mScaleValuesInverse.y(), + in.z() * mScaleValuesInverse.z()); + } + /// Return the Jacobian Curvature: zero for a linear map + Mat3d applyIJC(const Mat3d& in) const + { + Mat3d tmp; + for (int i=0; i<3; i++){ + tmp.setRow(i, in.row(i)*mScaleValuesInverse(i)); + } + for (int i=0; i<3; i++){ + tmp.setCol(i, tmp.col(i)*mScaleValuesInverse(i)); + } + return tmp; + } + Mat3d applyIJC(const Mat3d& in, const Vec3d&, const Vec3d& ) const { return applyIJC(in); } + + /// Return the product of the scale values, ignores argument + double determinant(const Vec3d& ) const { return determinant(); } + /// Return the product of the scale values + double determinant() const { return mScaleValues.x()*mScaleValues.y()*mScaleValues.z(); } + /// Return the absolute values of the scale values + Vec3d voxelSize() const { return mVoxelSize;} + /// Return the absolute values of the scale values, ignores + ///argument + Vec3d voxelSize(const Vec3d&) const { return voxelSize();} + + /// Returns the scale values + const Vec3d& getScale() const { return mScaleValues; } + /// Returns the translation + const Vec3d& getTranslation() const { return mTranslation; } + + /// Return the square of the scale. Used to optimize some finite difference calculations + const Vec3d& getInvScaleSqr() const {return mInvScaleSqr;} + /// Return 1/(2 scale). Used to optimize some finite difference calculations + const Vec3d& getInvTwiceScale() const {return mInvTwiceScale;} + /// Return 1/(scale) + const Vec3d& getInvScale() const {return mScaleValuesInverse; } + + /// read serialization + void read(std::istream& is) + { + mTranslation.read(is); + mScaleValues.read(is); + mVoxelSize.read(is); + mScaleValuesInverse.read(is); + mInvScaleSqr.read(is); + mInvTwiceScale.read(is); + } + /// write serialization + void write(std::ostream& os) const + { + mTranslation.write(os); + mScaleValues.write(os); + mVoxelSize.write(os); + mScaleValuesInverse.write(os); + mInvScaleSqr.write(os); + mInvTwiceScale.write(os); + } + /// string serialization, useful for debuging + std::string str() const + { + std::ostringstream buffer; + buffer << " - translation: " << mTranslation << std::endl; + buffer << " - scale: " << mScaleValues << std::endl; + buffer << " - voxel dimensions: " << mVoxelSize << std::endl; + return buffer.str(); + } + + virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } + + bool operator==(const ScaleTranslateMap& other) const + { + // ::eq() uses a tolerance + if (!mScaleValues.eq(other.mScaleValues)) { return false; } + if (!mTranslation.eq(other.mTranslation)) { return false; } + return true; + } + + bool operator!=(const ScaleTranslateMap& other) const { return !(*this == other); } + + /// Return AffineMap::Ptr to an AffineMap equivalent to *this + AffineMap::Ptr getAffineMap() const + { + AffineMap::Ptr affineMap(new AffineMap(math::scale(mScaleValues))); + affineMap->accumPostTranslation(mTranslation); + return affineMap; + } + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of prepending the appropraite operation. + MapBase::Ptr preRotate(double radians, Axis axis) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreRotation(axis, radians); + return simplify(affineMap); + } + MapBase::Ptr preTranslate(const Vec3d& t) const + { + const Vec3d& s = mScaleValues; + const Vec3d scaled_trans( t.x() * s.x(), + t.y() * s.y(), + t.z() * s.z() ); + return MapBase::Ptr( new ScaleTranslateMap(mScaleValues, mTranslation + scaled_trans)); + } + + MapBase::Ptr preScale(const Vec3d& v) const; + + MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreShear(axis0, axis1, shear); + return simplify(affineMap); + } + //@} + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of postfixing the appropraite operation. + MapBase::Ptr postRotate(double radians, Axis axis) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostRotation(axis, radians); + return simplify(affineMap); + } + MapBase::Ptr postTranslate(const Vec3d& t) const + { + return MapBase::Ptr( new ScaleTranslateMap(mScaleValues, mTranslation + t)); + } + + MapBase::Ptr postScale(const Vec3d& v) const; + + MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostShear(axis0, axis1, shear); + return simplify(affineMap); + } + //@} + +private: + Vec3d mTranslation, mScaleValues, mVoxelSize, mScaleValuesInverse, + mInvScaleSqr, mInvTwiceScale; +}; // class ScaleTanslateMap + + +inline MapBase::Ptr +ScaleMap::postTranslate(const Vec3d& t) const +{ + return MapBase::Ptr(new ScaleTranslateMap(mScaleValues, t)); +} + + +inline MapBase::Ptr +ScaleMap::preTranslate(const Vec3d& t) const +{ + + const Vec3d& s = mScaleValues; + const Vec3d scaled_trans( t.x() * s.x(), + t.y() * s.y(), + t.z() * s.z() ); + return MapBase::Ptr(new ScaleTranslateMap(mScaleValues, scaled_trans)); +} + + +/// @brief A specialized Affine transform that uniformaly scales along the principal axis +/// and then translates the result. +class OPENVDB_API UniformScaleTranslateMap: public ScaleTranslateMap +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + UniformScaleTranslateMap():ScaleTranslateMap(Vec3d(1,1,1), Vec3d(0,0,0)) {} + UniformScaleTranslateMap(double scale, const Vec3d& translate): + ScaleTranslateMap(Vec3d(scale,scale,scale), translate) {} + UniformScaleTranslateMap(const UniformScaleMap& scale, const TranslationMap& translate): + ScaleTranslateMap(scale.getScale(), translate.getTranslation()) {} + + UniformScaleTranslateMap(const UniformScaleTranslateMap& other):ScaleTranslateMap(other) {} + ~UniformScaleTranslateMap() {} + + /// Return a MapBase::Ptr to a new UniformScaleTranslateMap + static MapBase::Ptr create() { return MapBase::Ptr(new UniformScaleTranslateMap()); } + /// Return a MapBase::Ptr to a deep copy of this map + MapBase::Ptr copy() const { return MapBase::Ptr(new UniformScaleTranslateMap(*this)); } + + MapBase::Ptr inverseMap() const + { + const Vec3d& scaleInv = getInvScale(); + const Vec3d& trans = getTranslation(); + return MapBase::Ptr(new UniformScaleTranslateMap(scaleInv[0], -scaleInv[0] * trans)); + } + + static bool isRegistered() + { + return MapRegistry::isRegistered(UniformScaleTranslateMap::mapType()); + } + + static void registerMap() + { + MapRegistry::registerMap( + UniformScaleTranslateMap::mapType(), + UniformScaleTranslateMap::create); + } + + Name type() const { return mapType(); } + static Name mapType() { return Name("UniformScaleTranslateMap"); } + + virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } + + bool operator==(const UniformScaleTranslateMap& other) const + { + return ScaleTranslateMap::operator==(other); + } + bool operator!=(const UniformScaleTranslateMap& other) const { return !(*this == other); } + + /// @brief Return a MapBase::Ptr to a UniformScaleTranslateMap that is + /// the result of prepending translation on this map. + MapBase::Ptr preTranslate(const Vec3d& t) const + { + const double scale = this->getScale().x(); + const Vec3d new_trans = this->getTranslation() + scale * t; + return MapBase::Ptr( new UniformScaleTranslateMap(scale, new_trans)); + } + + /// @brief Return a MapBase::Ptr to a UniformScaleTranslateMap that is + /// the result of postfixing translation on this map. + MapBase::Ptr postTranslate(const Vec3d& t) const + { + const double scale = this->getScale().x(); + return MapBase::Ptr( new UniformScaleTranslateMap(scale, this->getTranslation() + t)); + } +}; // class UniformScaleTanslateMap + + +inline MapBase::Ptr +UniformScaleMap::postTranslate(const Vec3d& t) const +{ + const double scale = this->getScale().x(); + return MapBase::Ptr(new UniformScaleTranslateMap(scale, t)); +} + + +inline MapBase::Ptr +UniformScaleMap::preTranslate(const Vec3d& t) const +{ + const double scale = this->getScale().x(); + return MapBase::Ptr(new UniformScaleTranslateMap(scale, scale*t)); +} + + +inline MapBase::Ptr +TranslationMap::preScale(const Vec3d& v) const +{ + if (isApproxEqual(v[0],v[1]) && isApproxEqual(v[0],v[2])) { + return MapBase::Ptr(new UniformScaleTranslateMap(v[0], mTranslation)); + } else { + return MapBase::Ptr(new ScaleTranslateMap(v, mTranslation)); + } +} + + +inline MapBase::Ptr +TranslationMap::postScale(const Vec3d& v) const +{ + if (isApproxEqual(v[0],v[1]) && isApproxEqual(v[0],v[2])) { + return MapBase::Ptr(new UniformScaleTranslateMap(v[0], v[0]*mTranslation)); + } else { + const Vec3d trans(mTranslation.x()*v.x(), + mTranslation.y()*v.y(), + mTranslation.z()*v.z()); + return MapBase::Ptr(new ScaleTranslateMap(v, trans)); + } +} + + +inline MapBase::Ptr +ScaleTranslateMap::preScale(const Vec3d& v) const +{ + const Vec3d new_scale( v * mScaleValues ); + if (isApproxEqual(new_scale[0],new_scale[1]) && isApproxEqual(new_scale[0],new_scale[2])) { + return MapBase::Ptr( new UniformScaleTranslateMap(new_scale[0], mTranslation)); + } else { + return MapBase::Ptr( new ScaleTranslateMap(new_scale, mTranslation)); + } +} + + +inline MapBase::Ptr +ScaleTranslateMap::postScale(const Vec3d& v) const +{ + const Vec3d new_scale( v * mScaleValues ); + const Vec3d new_trans( mTranslation.x()*v.x(), + mTranslation.y()*v.y(), + mTranslation.z()*v.z() ); + + if (isApproxEqual(new_scale[0],new_scale[1]) && isApproxEqual(new_scale[0],new_scale[2])) { + return MapBase::Ptr( new UniformScaleTranslateMap(new_scale[0], new_trans)); + } else { + return MapBase::Ptr( new ScaleTranslateMap(new_scale, new_trans)); + } +} + + +//////////////////////////////////////// + + +/// @brief A specialized linear transform that performs a unitary maping +/// i.e. rotation and or reflection. +class OPENVDB_API UnitaryMap: public MapBase +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + /// default constructor makes an Idenity. + UnitaryMap(): mAffineMap(Mat4d::identity()) + { + } + + UnitaryMap(const Vec3d& axis, double radians) + { + Mat3d matrix; + matrix.setToRotation(axis, radians); + mAffineMap = AffineMap(matrix); + } + + UnitaryMap(Axis axis, double radians) + { + Mat4d matrix; + matrix.setToRotation(axis, radians); + mAffineMap = AffineMap(matrix); + } + + UnitaryMap(const Mat3d& m) + { + // test that the mat3 is a rotation || reflection + if (!isUnitary(m)) { + OPENVDB_THROW(ArithmeticError, "Matrix initializing unitary map was not unitary"); + } + + Mat4d matrix(Mat4d::identity()); + matrix.setMat3(m); + mAffineMap = AffineMap(matrix); + } + + UnitaryMap(const Mat4d& m) + { + if (!isInvertible(m)) { + OPENVDB_THROW(ArithmeticError, + "4x4 Matrix initializing unitary map was not unitary: not invertible"); + } + + if (!isAffine(m)) { + OPENVDB_THROW(ArithmeticError, + "4x4 Matrix initializing unitary map was not unitary: not affine"); + } + + if (hasTranslation(m)) { + OPENVDB_THROW(ArithmeticError, + "4x4 Matrix initializing unitary map was not unitary: had translation"); + } + + if (!isUnitary(m.getMat3())) { + OPENVDB_THROW(ArithmeticError, + "4x4 Matrix initializing unitary map was not unitary"); + } + + mAffineMap = AffineMap(m); + } + + UnitaryMap(const UnitaryMap& other): + MapBase(other), + mAffineMap(other.mAffineMap) + { + } + + UnitaryMap(const UnitaryMap& first, const UnitaryMap& second): + mAffineMap(*(first.getAffineMap()), *(second.getAffineMap())) + { + } + + ~UnitaryMap() {} + /// Return a MapBase::Ptr to a new UnitaryMap + static MapBase::Ptr create() { return MapBase::Ptr(new UnitaryMap()); } + /// Returns a MapBase::Ptr to a deep copy of *this + MapBase::Ptr copy() const { return MapBase::Ptr(new UnitaryMap(*this)); } + + MapBase::Ptr inverseMap() const + { + return MapBase::Ptr(new UnitaryMap(mAffineMap.getMat4().inverse())); + } + + static bool isRegistered() { return MapRegistry::isRegistered(UnitaryMap::mapType()); } + + static void registerMap() + { + MapRegistry::registerMap( + UnitaryMap::mapType(), + UnitaryMap::create); + } + + /// Return @c UnitaryMap + Name type() const { return mapType(); } + /// Return @c UnitaryMap + static Name mapType() { return Name("UnitaryMap"); } + + /// Return @c true (a UnitaryMap is always linear). + bool isLinear() const { return true; } + + /// Return @c false (by convention true) + bool hasUniformScale() const { return true; } + + virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } + + bool operator==(const UnitaryMap& other) const + { + // compare underlying linear map. + if (mAffineMap!=other.mAffineMap) return false; + return true; + } + + bool operator!=(const UnitaryMap& other) const { return !(*this == other); } + /// Return the image of @c in under the map + Vec3d applyMap(const Vec3d& in) const { return mAffineMap.applyMap(in); } + /// Return the pre-image of @c in under the map + Vec3d applyInverseMap(const Vec3d& in) const { return mAffineMap.applyInverseMap(in); } + + Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const { return applyJacobian(in); } + /// Return the Jacobian of the map applied to @a in. + Vec3d applyJacobian(const Vec3d& in) const { return mAffineMap.applyJacobian(in); } + + /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) + Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d&) const { return applyInverseJacobian(in); } + /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) + Vec3d applyInverseJacobian(const Vec3d& in) const { return mAffineMap.applyInverseJacobian(in); } + + + /// Return the Jacobian Transpose of the map applied to @a in. + /// This tranforms range-space gradients to domain-space gradients + Vec3d applyJT(const Vec3d& in, const Vec3d&) const { return applyJT(in); } + /// Return the Jacobian Transpose of the map applied to @a in. + Vec3d applyJT(const Vec3d& in) const { + // The transpose of the unitary map is its inverse + return applyInverseMap(in); + } + + + /// @brief Return the transpose of the inverse Jacobian of the map applied to @a in + /// @details Ignores second argument + Vec3d applyIJT(const Vec3d& in, const Vec3d& ) const { return applyIJT(in);} + /// Return the transpose of the inverse Jacobian of the map applied to @c in + Vec3d applyIJT(const Vec3d& in) const { return mAffineMap.applyIJT(in); } + /// Return the Jacobian Curvature: zero for a linear map + Mat3d applyIJC(const Mat3d& in) const { return mAffineMap.applyIJC(in); } + Mat3d applyIJC(const Mat3d& in, const Vec3d&, const Vec3d& ) const { return applyIJC(in); } + /// Return the determinant of the Jacobian, ignores argument + double determinant(const Vec3d& ) const { return determinant(); } + /// Return the determinant of the Jacobian + double determinant() const { return mAffineMap.determinant(); } + + + /// @brief Returns the lengths of the images + /// of the segments + /// \f$(0,0,0)-(1,0,0)\f$, \f$(0,0,0)-(0,1,0)\f$, + /// \f$(0,0,0)-(0,0,1)\f$ + Vec3d voxelSize() const { return mAffineMap.voxelSize();} + Vec3d voxelSize(const Vec3d&) const { return voxelSize();} + + /// read serialization + void read(std::istream& is) + { + mAffineMap.read(is); + } + + /// write serialization + void write(std::ostream& os) const + { + mAffineMap.write(os); + } + /// string serialization, useful for debuging + std::string str() const + { + std::ostringstream buffer; + buffer << mAffineMap.str(); + return buffer.str(); + } + /// Return AffineMap::Ptr to an AffineMap equivalent to *this + AffineMap::Ptr getAffineMap() const { return AffineMap::Ptr(new AffineMap(mAffineMap)); } + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of prepending the appropraite operation. + MapBase::Ptr preRotate(double radians, Axis axis) const + { + UnitaryMap first(axis, radians); + UnitaryMap::Ptr unitaryMap(new UnitaryMap(first, *this)); + return boost::static_pointer_cast(unitaryMap); + } + MapBase::Ptr preTranslate(const Vec3d& t) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreTranslation(t); + return simplify(affineMap); + } + MapBase::Ptr preScale(const Vec3d& v) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreScale(v); + return simplify(affineMap); + } + MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPreShear(axis0, axis1, shear); + return simplify(affineMap); + } + //@} + + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of postfixing the appropraite operation. + MapBase::Ptr postRotate(double radians, Axis axis) const + { + UnitaryMap second(axis, radians); + UnitaryMap::Ptr unitaryMap(new UnitaryMap(*this, second)); + return boost::static_pointer_cast(unitaryMap); + } + MapBase::Ptr postTranslate(const Vec3d& t) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostTranslation(t); + return simplify(affineMap); + } + MapBase::Ptr postScale(const Vec3d& v) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostScale(v); + return simplify(affineMap); + } + MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const + { + AffineMap::Ptr affineMap = getAffineMap(); + affineMap->accumPostShear(axis0, axis1, shear); + return simplify(affineMap); + } + //@} + +private: + AffineMap mAffineMap; +}; // class UnitaryMap + + +//////////////////////////////////////// + + +/// @brief This map is composed of three steps. +/// Frist it will take a box of size (Lx X Ly X Lz) defined by an member data bounding box +/// and map it into a frustum with near plane (1 X Ly/Lx) and precribed depth +/// Then this frustum is transformed by an internal second map: most often a uniform scale, +/// but other affects can be achieved by accumulating translation, shear and rotation: these +/// are all applied to the second map +class OPENVDB_API NonlinearFrustumMap: public MapBase +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + NonlinearFrustumMap(): + MapBase(), + mBBox(Vec3d(0), Vec3d(1)), + mTaper(1), + mDepth(1) + { + init(); + } + + /// @brief Constructor that takes an index-space bounding box + /// to be mapped into a frustum with a given @a depth and @a taper + /// (defined as ratio of nearplane/farplane). + NonlinearFrustumMap(const BBoxd& bb, double taper, double depth): + MapBase(),mBBox(bb), mTaper(taper), mDepth(depth) + { + init(); + } + + /// @brief Constructor that takes an index-space bounding box + /// to be mapped into a frustum with a given @a depth and @a taper + /// (defined as ratio of nearplane/farplane). + /// @details This frustum is further modifed by the @a secondMap, + /// intended to be a simple translation and rotation and uniform scale + NonlinearFrustumMap(const BBoxd& bb, double taper, double depth, + const MapBase::Ptr& secondMap): + mBBox(bb), mTaper(taper), mDepth(depth) + { + if (!secondMap->isLinear() ) { + OPENVDB_THROW(ArithmeticError, + "The second map in the Frustum transfrom must be linear"); + } + mSecondMap = *( secondMap->getAffineMap() ); + init(); + } + + NonlinearFrustumMap(const NonlinearFrustumMap& other): + MapBase(), + mBBox(other.mBBox), + mTaper(other.mTaper), + mDepth(other.mDepth), + mSecondMap(other.mSecondMap), + mHasSimpleAffine(other.mHasSimpleAffine) + { + init(); + } + + /// @brief Constructor from a camera frustum + /// + /// @param position the tip of the frustum (i.e., the camera's position). + /// @param direction a vector pointing from @a position toward the near plane. + /// @param up a non-unit vector describing the direction and extent of + /// the frustum's intersection on the near plane. Together, + /// @a up must be orthogonal to @a direction. + /// @param aspect the aspect ratio of the frustum intersection with near plane + /// defined as width / height + /// @param z_near,depth the distance from @a position along @a direction to the + /// near and far planes of the frustum. + /// @param x_count the number of voxels, aligned with @a left, + /// across the face of the frustum + /// @param z_count the number of voxels, aligned with @a direction, + /// between the near and far planes + NonlinearFrustumMap(const Vec3d& position, + const Vec3d& direction, + const Vec3d& up, + double aspect /* width / height */, + double z_near, double depth, + Coord::ValueType x_count, Coord::ValueType z_count) { + + /// @todo check that depth > 0 + /// @todo check up.length > 0 + /// @todo check that direction dot up = 0 + if (!(depth > 0)) { + OPENVDB_THROW(ArithmeticError, + "The frustum depth must be non-zero and positive"); + } + if (!(up.length() > 0)) { + OPENVDB_THROW(ArithmeticError, + "The frustum height must be non-zero and positive"); + } + if (!(aspect > 0)) { + OPENVDB_THROW(ArithmeticError, + "The frustum aspect ratio must be non-zero and positive"); + } + if (!(isApproxEqual(up.dot(direction), 0.))) { + OPENVDB_THROW(ArithmeticError, + "The frustum up orientation must be perpendicular to into-frustum direction"); + } + + double near_plane_height = 2 * up.length(); + double near_plane_width = aspect * near_plane_height; + + Coord::ValueType y_count = static_cast(Round(x_count / aspect)); + + mBBox = BBoxd(Vec3d(0,0,0), Vec3d(x_count, y_count, z_count)); + mDepth = depth / near_plane_width; // depth non-dimensionalized on width + double gamma = near_plane_width / z_near; + mTaper = 1./(mDepth*gamma + 1.); + + Vec3d direction_unit = direction; + direction_unit.normalize(); + + Mat4d r1(Mat4d::identity()); + r1.setToRotation(/*from*/Vec3d(0,0,1), /*to */direction_unit); + Mat4d r2(Mat4d::identity()); + Vec3d temp = r1.inverse().transform(up); + r2.setToRotation(/*from*/Vec3d(0,1,0), /*to*/temp ); + Mat4d scale = math::scale( + Vec3d(near_plane_width, near_plane_width, near_plane_width)); + + // move the near plane to origin, rotate to align with axis, and scale down + // T_inv * R1_inv * R2_inv * scale_inv + Mat4d mat = scale * r2 * r1; + mat.setTranslation(position + z_near*direction_unit); + + mSecondMap = AffineMap(mat); + + init(); + } + + ~NonlinearFrustumMap(){} + /// Return a MapBase::Ptr to a new NonlinearFrustumMap + static MapBase::Ptr create() { return MapBase::Ptr(new NonlinearFrustumMap()); } + /// Return a MapBase::Ptr to a deep copy of this map + MapBase::Ptr copy() const { return MapBase::Ptr(new NonlinearFrustumMap(*this)); } + + /// @brief Not implemented, since there is currently no map type that can + /// represent the inverse of a frustum + /// @throw NotImplementedError + MapBase::Ptr inverseMap() const + { + OPENVDB_THROW(NotImplementedError, + "inverseMap() is not implemented for NonlinearFrustumMap"); + } + static bool isRegistered() { return MapRegistry::isRegistered(NonlinearFrustumMap::mapType()); } + + static void registerMap() + { + MapRegistry::registerMap( + NonlinearFrustumMap::mapType(), + NonlinearFrustumMap::create); + } + /// Return @c NonlinearFrustumMap + Name type() const { return mapType(); } + /// Return @c NonlinearFrustumMap + static Name mapType() { return Name("NonlinearFrustumMap"); } + + /// Return @c false (a NonlinearFrustumMap is never linear). + bool isLinear() const { return false; } + + /// Return @c false (by convention false) + bool hasUniformScale() const { return false; } + + /// Return @c true if the map is equivalent to an identity + bool isIdentity() const + { + // The frustum can only be consistent with a linear map if the taper value is 1 + if (!isApproxEqual(mTaper, double(1)) ) return false; + + // There are various ways an identity can decomposed between the two parts of the + // map. Best to just check that the principle vectors are stationary. + const Vec3d e1(1,0,0); + if (!applyMap(e1).eq(e1)) return false; + + const Vec3d e2(0,1,0); + if (!applyMap(e2).eq(e2)) return false; + + const Vec3d e3(0,0,1); + if (!applyMap(e3).eq(e3)) return false; + + return true; + } + + virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } + + bool operator==(const NonlinearFrustumMap& other) const + { + if (mBBox!=other.mBBox) return false; + if (!isApproxEqual(mTaper, other.mTaper)) return false; + if (!isApproxEqual(mDepth, other.mDepth)) return false; + + // Two linear transforms are equivalent iff they have the same translation + // and have the same affects on orthongal spanning basis check translation + Vec3d e(0,0,0); + if (!mSecondMap.applyMap(e).eq(other.mSecondMap.applyMap(e))) return false; + /// check spanning vectors + e(0) = 1; + if (!mSecondMap.applyMap(e).eq(other.mSecondMap.applyMap(e))) return false; + e(0) = 0; + e(1) = 1; + if (!mSecondMap.applyMap(e).eq(other.mSecondMap.applyMap(e))) return false; + e(1) = 0; + e(2) = 1; + if (!mSecondMap.applyMap(e).eq(other.mSecondMap.applyMap(e))) return false; + return true; + } + + bool operator!=(const NonlinearFrustumMap& other) const { return !(*this == other); } + + /// Return the image of @c in under the map + Vec3d applyMap(const Vec3d& in) const + { + return mSecondMap.applyMap(applyFrustumMap(in)); + } + + /// Return the pre-image of @c in under the map + Vec3d applyInverseMap(const Vec3d& in) const + { + return applyFrustumInverseMap(mSecondMap.applyInverseMap(in)); + } + /// Return the Jacobian of the linear second map applied to @c in + Vec3d applyJacobian(const Vec3d& in) const { return mSecondMap.applyJacobian(in); } + /// Return the Jacobian defined at @c isloc applied to @c in + Vec3d applyJacobian(const Vec3d& in, const Vec3d& isloc) const + { + // Move the center of the x-face of the bbox + // to the origin in index space. + Vec3d centered(isloc); + centered = centered - mBBox.min(); + centered.x() -= mXo; + centered.y() -= mYo; + + // scale the z-direction on depth / K count + const double zprime = centered.z()*mDepthOnLz; + + const double scale = (mGamma * zprime + 1.) / mLx; + const double scale2 = mGamma * mDepthOnLz / mLx; + + const Vec3d tmp(scale * in.x() + scale2 * centered.x()* in.z(), + scale * in.y() + scale2 * centered.y()* in.z(), + mDepthOnLz * in.z()); + + return mSecondMap.applyJacobian(tmp); + } + + + /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) + Vec3d applyInverseJacobian(const Vec3d& in) const { return mSecondMap.applyInverseJacobian(in); } + /// Return the Inverse Jacobian defined at @c isloc of the map applied to @a in. + Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d& isloc) const { + + // Move the center of the x-face of the bbox + // to the origin in index space. + Vec3d centered(isloc); + centered = centered - mBBox.min(); + centered.x() -= mXo; + centered.y() -= mYo; + + // scale the z-direction on depth / K count + const double zprime = centered.z()*mDepthOnLz; + + const double scale = (mGamma * zprime + 1.) / mLx; + const double scale2 = mGamma * mDepthOnLz / mLx; + + + Vec3d out = mSecondMap.applyInverseJacobian(in); + + out.x() = (out.x() - scale2 * centered.x() * out.z() / mDepthOnLz) / scale; + out.y() = (out.y() - scale2 * centered.y() * out.z() / mDepthOnLz) / scale; + out.z() = out.z() / mDepthOnLz; + + return out; + } + + + + /// Return the Jacobian Transpose of the map applied to vector @c in at @c indexloc. + /// This tranforms range-space gradients to domain-space gradients. + /// + Vec3d applyJT(const Vec3d& in, const Vec3d& isloc) const { + const Vec3d tmp = mSecondMap.applyJT(in); + // Move the center of the x-face of the bbox + // to the origin in index space. + Vec3d centered(isloc); + centered = centered - mBBox.min(); + centered.x() -= mXo; + centered.y() -= mYo; + + // scale the z-direction on depth / K count + const double zprime = centered.z()*mDepthOnLz; + + const double scale = (mGamma * zprime + 1.) / mLx; + const double scale2 = mGamma * mDepthOnLz / mLx; + + return Vec3d(scale * tmp.x(), + scale * tmp.y(), + scale2 * centered.x()* tmp.x() + + scale2 * centered.y()* tmp.y() + + mDepthOnLz * tmp.z()); + } + /// Return the Jacobian Transpose of the second map applied to @c in. + Vec3d applyJT(const Vec3d& in) const { + return mSecondMap.applyJT(in); + } + + /// Return the transpose of the inverse Jacobian of the linear second map applied to @c in + Vec3d applyIJT(const Vec3d& in) const { return mSecondMap.applyIJT(in); } + + // the Jacobian of the nonlinear part of the transform is a sparse matrix + // Jacobian^(-T) = + // + // (Lx)( 1/s 0 0 ) + // ( 0 1/s 0 ) + // ( -(x-xo)g/(sLx) -(y-yo)g/(sLx) Lz/(Depth Lx) ) + /// Return the transpose of the inverse Jacobain (at @c locW applied to @c in. + /// @c ijk is the location in the pre-image space (e.g. index space) + Vec3d applyIJT(const Vec3d& d1_is, const Vec3d& ijk) const + { + const Vec3d loc = applyFrustumMap(ijk); + const double s = mGamma * loc.z() + 1.; + + // verify that we aren't at the singularity + if (isApproxEqual(s, 0.)) { + OPENVDB_THROW(ArithmeticError, "Tried to evaluate the frustum transform" + " at the singular focal point (e.g. camera)"); + } + + const double sinv = 1.0/s; // 1/(z*gamma + 1) + const double pt0 = mLx * sinv; // Lx / (z*gamma +1) + const double pt1 = mGamma * pt0; // gamma * Lx / ( z*gamma +1) + const double pt2 = pt1 * sinv; // gamma * Lx / ( z*gamma +1)**2 + + const Mat3d& jacinv = mSecondMap.getConstJacobianInv(); + + // compute \frac{\partial E_i}{\partial x_j} + Mat3d gradE(Mat3d::zero()); + for (int j = 0; j < 3; ++j ) { + gradE(0,j) = pt0 * jacinv(0,j) - pt2 * loc.x()*jacinv(2,j); + gradE(1,j) = pt0 * jacinv(1,j) - pt2 * loc.y()*jacinv(2,j); + gradE(2,j) = (1./mDepthOnLz) * jacinv(2,j); + } + + Vec3d result; + for (int i = 0; i < 3; ++i) { + result(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 { return mSecondMap.applyIJC(in); } + /// Return the Jacobian Curvature: all the second derivatives in range space + /// @param d2_is second derivative matrix computed in index space + /// @param d1_is gradient computed in index space + /// @param ijk the index space location where the result is computed + Mat3d applyIJC(const Mat3d& d2_is, const Vec3d& d1_is, const Vec3d& ijk) const + { + const Vec3d loc = applyFrustumMap(ijk); + + const double s = mGamma * loc.z() + 1.; + + // verify that we aren't at the singularity + if (isApproxEqual(s, 0.)) { + OPENVDB_THROW(ArithmeticError, "Tried to evaluate the frustum transform" + " at the singular focal point (e.g. camera)"); + } + + // precompute + const double sinv = 1.0/s; // 1/(z*gamma + 1) + const double pt0 = mLx * sinv; // Lx / (z*gamma +1) + const double pt1 = mGamma * pt0; // gamma * Lx / ( z*gamma +1) + const double pt2 = pt1 * sinv; // gamma * Lx / ( z*gamma +1)**2 + const double pt3 = pt2 * sinv; // gamma * Lx / ( z*gamma +1)**3 + + const Mat3d& jacinv = mSecondMap.getConstJacobianInv(); + + // compute \frac{\partial^2 E_i}{\partial x_j \partial x_k} + + Mat3d matE0(Mat3d::zero()); + Mat3d matE1(Mat3d::zero()); // matE2 = 0 + for(int j = 0; j < 3; j++) { + for (int k = 0; k < 3; k++) { + + const double pt4 = 2. * jacinv(2,j) * jacinv(2,k) * pt3; + + matE0(j,k) = -(jacinv(0,j) * jacinv(2,k) + jacinv(2,j) * jacinv(0,k)) * pt2 + + pt4 * loc.x(); + + matE1(j,k) = -(jacinv(1,j) * jacinv(2,k) + jacinv(2,j) * jacinv(1,k)) * pt2 + + pt4 * loc.y(); + } + } + + // compute \frac{\partial E_i}{\partial x_j} + Mat3d gradE(Mat3d::zero()); + for (int j = 0; j < 3; ++j ) { + gradE(0,j) = pt0 * jacinv(0,j) - pt2 * loc.x()*jacinv(2,j); + gradE(1,j) = pt0 * jacinv(1,j) - pt2 * loc.y()*jacinv(2,j); + gradE(2,j) = (1./mDepthOnLz) * jacinv(2,j); + } + + Mat3d result(Mat3d::zero()); + // compute \fac{\partial E_j}{\partial x_m} \fac{\partial E_i}{\partial x_n} + // \frac{\partial^2 input}{\partial E_i \partial E_j} + for (int m = 0; m < 3; ++m ) { + for ( int n = 0; n < 3; ++n) { + for (int i = 0; i < 3; ++i ) { + for (int j = 0; j < 3; ++j) { + result(m, n) += gradE(j, m) * gradE(i, n) * d2_is(i, j); + } + } + } + } + + for (int m = 0; m < 3; ++m ) { + for ( int n = 0; n < 3; ++n) { + result(m, n) += + matE0(m, n) * d1_is(0) + matE1(m, n) * d1_is(1);// + matE2(m, n) * d1_is(2); + } + } + + return result; + } + + /// Return the determinant of the Jacobian of linear second map + double determinant() const {return mSecondMap.determinant();} // no implementation + + /// Return the determinate of the Jacobian evaluated at @c loc + /// @c loc is a location in the pre-image space (e.g., index space) + double determinant(const Vec3d& loc) const + { + double s = mGamma * loc.z() + 1.0; + double frustum_determinant = s * s * mDepthOnLzLxLx; + return mSecondMap.determinant() * frustum_determinant; + } + + /// Return the size of a voxel at the center of the near plane + Vec3d voxelSize() const + { + const Vec3d loc( 0.5*(mBBox.min().x() + mBBox.max().x()), + 0.5*(mBBox.min().y() + mBBox.max().y()), + mBBox.min().z()); + + return voxelSize(loc); + + } + + /// @brief Returns the lengths of the images of the three segments + /// from @a loc to @a loc + (1,0,0), from @a loc to @a loc + (0,1,0) + /// and from @a loc to @a loc + (0,0,1) + /// @param loc a location in the pre-image space (e.g., index space) + Vec3d voxelSize(const Vec3d& loc) const + { + Vec3d out, pos = applyMap(loc); + out(0) = (applyMap(loc + Vec3d(1,0,0)) - pos).length(); + out(1) = (applyMap(loc + Vec3d(0,1,0)) - pos).length(); + out(2) = (applyMap(loc + Vec3d(0,0,1)) - pos).length(); + return out; + } + + AffineMap::Ptr getAffineMap() const { return mSecondMap.getAffineMap(); } + + /// set the taper value, the ratio of nearplane width / far plane width + void setTaper(double t) { mTaper = t; init();} + /// Return the taper value. + double getTaper() const { return mTaper; } + /// set the frustum depth: distance between near and far plane = frustm depth * frustm x-width + void setDepth(double d) { mDepth = d; init();} + /// Return the unscaled frustm depth + double getDepth() const { return mDepth; } + // gamma a non-dimensional number: nearplane x-width / camera to near plane distance + double getGamma() const { return mGamma; } + + /// Return the bounding box that defines the frustum in pre-image space + const BBoxd& getBBox() const { return mBBox; } + + /// Return MapBase::Ptr& to the second map + const AffineMap& secondMap() const { return mSecondMap; } + /// Return @c true if the the bounding box in index space that defines the region that + /// is maped into the frustum is non-zero, otherwise @c false + bool isValid() const { return !mBBox.empty();} + + /// Return @c true if the second map is a uniform scale, Rotation and translation + bool hasSimpleAffine() const { return mHasSimpleAffine; } + + /// read serialization + void read(std::istream& is) + { + // for backward compatibility with earlier version + if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_FLOAT_FRUSTUM_BBOX ) { + CoordBBox bb; + bb.read(is); + mBBox = BBoxd(bb.min().asVec3d(), bb.max().asVec3d()); + } else { + mBBox.read(is); + } + + is.read(reinterpret_cast(&mTaper), sizeof(double)); + is.read(reinterpret_cast(&mDepth), sizeof(double)); + + // Read the second maps type. + Name type = readString(is); + + // Check if the map has been registered. + if(!MapRegistry::isRegistered(type)) { + OPENVDB_THROW(KeyError, "Map " << type << " is not registered"); + } + + // Create the second map of the type and then read it in. + MapBase::Ptr proxy = math::MapRegistry::createMap(type); + proxy->read(is); + mSecondMap = *(proxy->getAffineMap()); + init(); + } + + /// write serialization + void write(std::ostream& os) const + { + mBBox.write(os); + os.write(reinterpret_cast(&mTaper), sizeof(double)); + os.write(reinterpret_cast(&mDepth), sizeof(double)); + + writeString(os, mSecondMap.type()); + mSecondMap.write(os); + } + + /// string serialization, useful for debuging + std::string str() const + { + std::ostringstream buffer; + buffer << " - taper: " << mTaper << std::endl; + buffer << " - depth: " << mDepth << std::endl; + buffer << " SecondMap: "<< mSecondMap.type() << std::endl; + buffer << mSecondMap.str() << std::endl; + return buffer.str(); + } + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of prepending the appropriate operation to the linear part of this map + MapBase::Ptr preRotate(double radians, Axis axis = X_AXIS) const + { + return MapBase::Ptr( + new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.preRotate(radians, axis))); + } + MapBase::Ptr preTranslate(const Vec3d& t) const + { + return MapBase::Ptr( + new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.preTranslate(t))); + } + MapBase::Ptr preScale(const Vec3d& s) const + { + return MapBase::Ptr( + new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.preScale(s))); + } + MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const + { + return MapBase::Ptr(new NonlinearFrustumMap( + mBBox, mTaper, mDepth, mSecondMap.preShear(shear, axis0, axis1))); + } + //@} + + //@{ + /// @brief Return a MapBase::Ptr to a new map that is the result + /// of postfixing the appropiate operation to the linear part of this map. + MapBase::Ptr postRotate(double radians, Axis axis = X_AXIS) const + { + return MapBase::Ptr( + new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.postRotate(radians, axis))); + } + MapBase::Ptr postTranslate(const Vec3d& t) const + { + return MapBase::Ptr( + new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.postTranslate(t))); + } + MapBase::Ptr postScale(const Vec3d& s) const + { + return MapBase::Ptr( + new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.postScale(s))); + } + MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const + { + return MapBase::Ptr(new NonlinearFrustumMap( + mBBox, mTaper, mDepth, mSecondMap.postShear(shear, axis0, axis1))); + } + //@} + +private: + void init() + { + // set up as a frustum + mLx = mBBox.extents().x(); + mLy = mBBox.extents().y(); + mLz = mBBox.extents().z(); + + if (isApproxEqual(mLx,0.) || isApproxEqual(mLy,0.) || isApproxEqual(mLz,0.) ) { + OPENVDB_THROW(ArithmeticError, "The index space bounding box" + " must have at least two index points in each direction."); + } + + mXo = 0.5* mLx; + mYo = 0.5* mLy; + + // mDepth is non-dimensionalized on near + mGamma = (1./mTaper - 1) / mDepth; + + mDepthOnLz = mDepth/mLz; + mDepthOnLzLxLx = mDepthOnLz/(mLx * mLx); + + /// test for shear and non-uniform scale + mHasSimpleAffine = true; + Vec3d tmp = mSecondMap.voxelSize(); + + /// false if there is non-uniform scale + if (!isApproxEqual(tmp(0), tmp(1))) { mHasSimpleAffine = false; return; } + if (!isApproxEqual(tmp(0), tmp(2))) { mHasSimpleAffine = false; return; } + + Vec3d trans = mSecondMap.applyMap(Vec3d(0,0,0)); + /// look for shear + Vec3d tmp1 = mSecondMap.applyMap(Vec3d(1,0,0)) - trans; + Vec3d tmp2 = mSecondMap.applyMap(Vec3d(0,1,0)) - trans; + Vec3d tmp3 = mSecondMap.applyMap(Vec3d(0,0,1)) - trans; + + /// false if there is shear + if (!isApproxEqual(tmp1.dot(tmp2), 0., 1.e-7)) { mHasSimpleAffine = false; return; } + if (!isApproxEqual(tmp2.dot(tmp3), 0., 1.e-7)) { mHasSimpleAffine = false; return; } + if (!isApproxEqual(tmp3.dot(tmp1), 0., 1.e-7)) { mHasSimpleAffine = false; return; } + } + + Vec3d applyFrustumMap(const Vec3d& in) const + { + + // Move the center of the x-face of the bbox + // to the origin in index space. + Vec3d out(in); + out = out - mBBox.min(); + out.x() -= mXo; + out.y() -= mYo; + + // scale the z-direction on depth / K count + out.z() *= mDepthOnLz; + + double scale = (mGamma * out.z() + 1.)/ mLx; + + // scale the x-y on the length I count and apply tapper + out.x() *= scale ; + out.y() *= scale ; + + return out; + } + + Vec3d applyFrustumInverseMap(const Vec3d& in) const + { + // invert taper and resize: scale = 1/( (z+1)/2 (mt-1) + 1) + Vec3d out(in); + double invScale = mLx / (mGamma * out.z() + 1.); + out.x() *= invScale; + out.y() *= invScale; + + out.x() += mXo; + out.y() += mYo; + + out.z() /= mDepthOnLz; + + // move back + out = out + mBBox.min(); + return out; + } + + // bounding box in index space used in Frustum transforms. + BBoxd mBBox; + + // taper value used in constructing Frustums. + double mTaper; + double mDepth; + + // defines the second map + AffineMap mSecondMap; + + // these are derived from the above. + double mLx, mLy, mLz; + double mXo, mYo, mGamma, mDepthOnLz, mDepthOnLzLxLx; + + // true: if the mSecondMap is linear and has no shear, and has no non-uniform scale + bool mHasSimpleAffine; +}; // class NonlinearFrustumMap + + +//////////////////////////////////////// + + +/// @brief Creates the composition of two maps, each of which could be a composition. +/// In the case that each component of the composition classified as linear an +/// acceleration AffineMap is stored. +template +class CompoundMap +{ +public: + typedef CompoundMap MyType; + + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + + CompoundMap() { updateAffineMatrix(); } + + CompoundMap(const FirstMapType& f, const SecondMapType& s): mFirstMap(f), mSecondMap(s) + { + updateAffineMatrix(); + } + + CompoundMap(const MyType& other): + mFirstMap(other.mFirstMap), + mSecondMap(other.mSecondMap), + mAffineMap(other.mAffineMap) + {} + + Name type() const { return mapType(); } + static Name mapType() + { + return (FirstMapType::mapType() + Name(":") + SecondMapType::mapType()); + } + + bool operator==(const MyType& other) const + { + if (mFirstMap != other.mFirstMap) return false; + if (mSecondMap != other.mSecondMap) return false; + if (mAffineMap != other.mAffineMap) return false; + return true; + } + + bool operator!=(const MyType& other) const { return !(*this == other); } + + MyType& operator=(const MyType& other) + { + mFirstMap = other.mFirstMap; + mSecondMap = other.mSecondMap; + mAffineMap = other.mAffineMap; + return *this; + } + + bool isIdentity() const + { + if (is_linear::value) { + return mAffineMap.isIdentity(); + } else { + return mFirstMap.isIdentity()&&mSecondMap.isIdentity(); + } + } + + bool isDiagonal() const { + if (is_linear::value) { + return mAffineMap.isDiagonal(); + } else { + return mFirstMap.isDiagonal()&&mSecondMap.isDiagonal(); + } + } + + AffineMap::Ptr getAffineMap() const + { + if (is_linear::value) { + AffineMap::Ptr affine(new AffineMap(mAffineMap)); + return affine; + } else { + OPENVDB_THROW(ArithmeticError, + "Constant affine matrix representation not possible for this nonlinear map"); + } + } + + // direct decompotion + const FirstMapType& firstMap() const { return mFirstMap; } + const SecondMapType& secondMap() const {return mSecondMap; } + + void setFirstMap(const FirstMapType& first) { mFirstMap = first; updateAffineMatrix(); } + void setSecondMap(const SecondMapType& second) { mSecondMap = second; updateAffineMatrix(); } + + void read(std::istream& is) + { + mAffineMap.read(is); + mFirstMap.read(is); + mSecondMap.read(is); + } + void write(std::ostream& os) const + { + mAffineMap.write(os); + mFirstMap.write(os); + mSecondMap.write(os); + } + +private: + void updateAffineMatrix() + { + if (is_linear::value) { + // both maps need to be linear, these methods are only defined for linear maps + AffineMap::Ptr first = mFirstMap.getAffineMap(); + AffineMap::Ptr second= mSecondMap.getAffineMap(); + mAffineMap = AffineMap(*first, *second); + } + } + + FirstMapType mFirstMap; + SecondMapType mSecondMap; + // used for acceleration + AffineMap mAffineMap; +}; // class CompoundMap + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_MAPS_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Mat.h b/openvdb_2_3_0_library/openvdb/math/Mat.h new file mode 100755 index 0000000..29de4a8 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Mat.h @@ -0,0 +1,991 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file Mat.h +/// @author Joshua Schpok + +#ifndef OPENVDB_MATH_MAT_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_MAT_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include "Math.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +/// @class Mat "Mat.h" +/// A base class for square matrices. +template +class Mat +{ +public: + typedef T value_type; + typedef T ValueType; + enum SIZE_ { size = SIZE }; + + // Number of cols, rows, elements + static unsigned numRows() { return SIZE; } + static unsigned numColumns() { return SIZE; } + static unsigned numElements() { return SIZE*SIZE; } + + /// Default ctor. Does nothing. Required because declaring a copy (or + /// other) constructor means the default constructor gets left out. + Mat() { } + + /// Copy constructor. Used when the class signature matches exactly. + Mat(Mat const &src) { + for (unsigned i(0); i < numElements(); ++i) { + mm[i] = src.mm[i]; + } + } + + /// @return string representation of matrix + /// Since output is multiline, optional indentation argument prefixes + /// each newline with that much white space. It does not indent + /// the first line, since you might be calling this inline: + /// + /// cout << "matrix: " << mat.str(7) + /// + /// matrix: [[1 2] + /// [3 4]] + std::string + str(unsigned indentation = 0) const { + + std::string ret; + std::string indent; + + // We add +1 since we're indenting one for the first '[' + indent.append(indentation+1, ' '); + + ret.append("["); + + // For each row, + for (unsigned i(0); i < SIZE; i++) { + + ret.append("["); + + // For each column + for (unsigned j(0); j < SIZE; j++) { + + // Put a comma after everything except the last + if (j) ret.append(", "); + ret.append((boost::format("%1%") % mm[(i*SIZE)+j]).str()); + } + + ret.append("]"); + + // At the end of every row (except the last)... + if (i < SIZE-1 ) + // ...suffix the row bracket with a comma, newline, and + // advance indentation + ret.append((boost::format(",\n%1%") % indent).str()); + } + + ret.append("]"); + + return ret; + } + + /// Write a Mat to an output stream + friend std::ostream& operator<<( + std::ostream& ostr, + const Mat& m) + { + ostr << m.str(); + return ostr; + } + + void write(std::ostream& os) const { + os.write(reinterpret_cast(&mm), sizeof(T)*SIZE*SIZE); + } + + void read(std::istream& is) { + is.read(reinterpret_cast(&mm), sizeof(T)*SIZE*SIZE); + } + + +protected: + T mm[SIZE*SIZE]; +}; + + +template class Quat; +template class Vec3; + +/// @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 = 1.0e-8) +{ + typedef typename MatType::value_type T; + + T qdot(q.dot(q)); + T s(0); + + if (!isApproxEqual(qdot, T(0.0),eps)) { + s = 2.0 / qdot; + } + + T x = s*q.x(); + T y = s*q.y(); + T z = s*q.z(); + T wx = x*q.w(); + T wy = y*q.w(); + T wz = z*q.w(); + T xx = x*q.x(); + T xy = y*q.x(); + T xz = z*q.x(); + T yy = y*q.y(); + T yz = z*q.y(); + T zz = z*q.z(); + + MatType r; + r[0][0]=T(1) - (yy+zz); r[0][1]=xy + wz; r[0][2]=xz - wy; + r[1][0]=xy - wz; r[1][1]=T(1) - (xx+zz); r[1][2]=yz + wx; + r[2][0]=xz + wy; r[2][1]=yz - wx; r[2][2]=T(1) - (xx+yy); + + if(MatType::numColumns() == 4) padMat4(r); + return r; +} + + + +/// @brief 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) +{ + typedef typename MatType::value_type T; + T c = static_cast(cos(angle)); + T s = static_cast(sin(angle)); + + MatType result; + result.setIdentity(); + + switch (axis) { + case X_AXIS: + result[1][1] = c; + result[1][2] = s; + result[2][1] = -s; + result[2][2] = c; + return result; + case Y_AXIS: + result[0][0] = c; + result[0][2] = -s; + result[2][0] = s; + result[2][2] = c; + return result; + case Z_AXIS: + result[0][0] = c; + result[0][1] = s; + result[1][0] = -s; + result[1][1] = c; + return result; + default: + throw ValueError("Unrecognized rotation axis"); + } +} + + +/// @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) +{ + typedef typename MatType::value_type T; + T txy, txz, tyz, sx, sy, sz; + + Vec3 axis(_axis.unit()); + + // compute trig properties of angle: + T c(cos(double(angle))); + T s(sin(double(angle))); + T t(1 - c); + + MatType result; + // handle diagonal elements + result[0][0] = axis[0]*axis[0] * t + c; + result[1][1] = axis[1]*axis[1] * t + c; + result[2][2] = axis[2]*axis[2] * t + c; + + txy = axis[0]*axis[1] * t; + sz = axis[2] * s; + + txz = axis[0]*axis[2] * t; + sy = axis[1] * s; + + tyz = axis[1]*axis[2] * t; + sx = axis[0] * s; + + // right handed space + // Contribution from rotation about 'z' + result[0][1] = txy + sz; + result[1][0] = txy - sz; + // Contribution from rotation about 'y' + result[0][2] = txz - sy; + result[2][0] = txz + sy; + // Contribution from rotation about 'x' + result[1][2] = tyz + sx; + result[2][1] = tyz - sx; + + if(MatType::numColumns() == 4) padMat4(result); + return MatType(result); +} + + +/// @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=1.0e-8) +{ + typedef typename MatType::value_type ValueType; + typedef Vec3 V; + ValueType phi, theta, psi; + + switch(rotationOrder) + { + case XYZ_ROTATION: + if (isApproxEqual(mat[2][0], ValueType(1.0), eps)) { + theta = M_PI_2; + phi = 0.5 * atan2(mat[1][2], mat[1][1]); + psi = phi; + } else if (isApproxEqual(mat[2][0], ValueType(-1.0), eps)) { + theta = -M_PI_2; + phi = 0.5 * atan2(mat[1][2], mat[1][1]); + psi = -phi; + } else { + psi = atan2(-mat[1][0],mat[0][0]); + phi = atan2(-mat[2][1],mat[2][2]); + theta = atan2(mat[2][0], + sqrt( mat[2][1]*mat[2][1] + + mat[2][2]*mat[2][2])); + } + return V(phi, theta, psi); + case ZXY_ROTATION: + if (isApproxEqual(mat[1][2], ValueType(1.0), eps)) { + theta = M_PI_2; + phi = 0.5 * atan2(mat[0][1], mat[0][0]); + psi = phi; + } else if (isApproxEqual(mat[1][2], ValueType(-1.0), eps)) { + theta = -M_PI/2; + phi = 0.5 * atan2(mat[0][1],mat[2][1]); + psi = -phi; + } else { + psi = atan2(-mat[0][2], mat[2][2]); + phi = atan2(-mat[1][0], mat[1][1]); + theta = atan2(mat[1][2], + sqrt(mat[0][2] * mat[0][2] + + mat[2][2] * mat[2][2])); + } + return V(theta, psi, phi); + + case YZX_ROTATION: + if (isApproxEqual(mat[0][1], ValueType(1.0), eps)) { + theta = M_PI_2; + phi = 0.5 * atan2(mat[2][0], mat[2][2]); + psi = phi; + } else if (isApproxEqual(mat[0][1], ValueType(-1.0), eps)) { + theta = -M_PI/2; + phi = 0.5 * atan2(mat[2][0], mat[1][0]); + psi = -phi; + } else { + psi = atan2(-mat[2][1], mat[1][1]); + phi = atan2(-mat[0][2], mat[0][0]); + theta = atan2(mat[0][1], + sqrt(mat[0][0] * mat[0][0] + + mat[0][2] * mat[0][2])); + } + return V(psi, phi, theta); + + case XZX_ROTATION: + + if (isApproxEqual(mat[0][0], ValueType(1.0), eps)) { + theta = 0.0; + phi = 0.5 * atan2(mat[1][2], mat[1][1]); + psi = phi; + } else if (isApproxEqual(mat[0][0], ValueType(-1.0), eps)) { + theta = M_PI; + psi = 0.5 * atan2(mat[2][1], -mat[1][1]); + phi = - psi; + } else { + psi = atan2(mat[2][0], -mat[1][0]); + phi = atan2(mat[0][2], mat[0][1]); + theta = atan2(sqrt(mat[0][1] * mat[0][1] + + mat[0][2] * mat[0][2]), + mat[0][0]); + } + return V(phi, psi, theta); + + case ZXZ_ROTATION: + + if (isApproxEqual(mat[2][2], ValueType(1.0), eps)) { + theta = 0.0; + phi = 0.5 * atan2(mat[0][1], mat[0][0]); + psi = phi; + } else if (isApproxEqual(mat[2][2], ValueType(-1.0), eps)) { + theta = M_PI; + phi = 0.5 * atan2(mat[0][1], mat[0][0]); + psi = -phi; + } else { + psi = atan2(mat[0][2], mat[1][2]); + phi = atan2(mat[2][0], -mat[2][1]); + theta = atan2(sqrt(mat[0][2] * mat[0][2] + + mat[1][2] * mat[1][2]), + mat[2][2]); + } + return V(theta, psi, phi); + + case YXZ_ROTATION: + + if (isApproxEqual(mat[2][1], ValueType(1.0), eps)) { + theta = - M_PI_2; + phi = 0.5 * atan2(-mat[1][0], mat[0][0]); + psi = phi; + } else if (isApproxEqual(mat[2][1], ValueType(-1.0), eps)) { + theta = M_PI_2; + phi = 0.5 * atan2(mat[1][0], mat[0][0]); + psi = -phi; + } else { + psi = atan2(mat[0][1], mat[1][1]); + phi = atan2(mat[2][0], mat[2][2]); + theta = atan2(-mat[2][1], + sqrt(mat[0][1] * mat[0][1] + + mat[1][1] * mat[1][1])); + } + return V(theta, phi, psi); + + case ZYX_ROTATION: + + if (isApproxEqual(mat[0][2], ValueType(1.0), eps)) { + theta = -M_PI_2; + phi = 0.5 * atan2(-mat[1][0], mat[1][1]); + psi = phi; + } else if (isApproxEqual(mat[0][2], ValueType(-1.0), eps)) { + theta = M_PI_2; + phi = 0.5 * atan2(mat[2][1], mat[2][0]); + psi = - phi; + } else { + psi = atan2(mat[1][2], mat[2][2]); + phi = atan2(mat[0][1], mat[0][0]); + theta = atan2(-mat[0][2], + sqrt(mat[0][1] * mat[0][1] + + mat[0][0] * mat[0][0])); + } + return V(psi, theta, phi); + + case XZY_ROTATION: + + if (isApproxEqual(mat[1][0], ValueType(-1.0), eps)) { + theta = M_PI_2; + psi = 0.5 * atan2(mat[2][1], mat[2][2]); + phi = - psi; + } else if (isApproxEqual(mat[1][0], ValueType(1.0), eps)) { + theta = - M_PI_2; + psi = 0.5 * atan2(- mat[2][1], mat[2][2]); + phi = psi; + } else { + psi = atan2(mat[2][0], mat[0][0]); + phi = atan2(mat[1][2], mat[1][1]); + theta = atan2(- mat[1][0], + sqrt(mat[1][1] * mat[1][1] + + mat[1][2] * mat[1][2])); + } + return V(phi, psi, theta); + } + + OPENVDB_THROW(NotImplementedError, "Euler extraction sequence not implemented"); +} + + +/// @brief 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) +{ + typedef typename MatType::value_type T; + Vec3 v1(_v1); + Vec3 v2(_v2); + + // Check if v1 and v2 are unit length + if (!isApproxEqual(1.0, v1.dot(v1), eps)) { + v1.normalize(); + } + if (!isApproxEqual(1.0, v2.dot(v2), eps)) { + v2.normalize(); + } + + Vec3 cross; + cross.cross(v1, v2); + + if (isApproxEqual(cross[0], 0.0, eps) && + isApproxEqual(cross[1], 0.0, eps) && + isApproxEqual(cross[2], 0.0, eps)) { + + + // Given two unit vectors v1 and v2 that are nearly parallel, build a + // rotation matrix that maps v1 onto v2. First find which principal axis + // p is closest to perpendicular to v1. Find a reflection that exchanges + // v1 and p, and find a reflection that exchanges p2 and v2. The desired + // rotation matrix is the composition of these two reflections. See the + // paper "Efficiently Building a Matrix to Rotate One Vector to + // Another" by Tomas Moller and John Hughes in Journal of Graphics + // Tools Vol 4, No 4 for details. + + Vec3 u, v, p(0.0, 0.0, 0.0); + + double x = Abs(v1[0]); + double y = Abs(v1[1]); + double z = Abs(v1[2]); + + if (x < y) { + if (z < x) { + p[2] = 1; + } else { + p[0] = 1; + } + } else { + if (z < y) { + p[2] = 1; + } else { + p[1] = 1; + } + } + u = p - v1; + v = p - v2; + + double udot = u.dot(u); + double vdot = v.dot(v); + + double a = -2 / udot; + double b = -2 / vdot; + double c = 4 * u.dot(v) / (udot * vdot); + + MatType result; + result.setIdentity(); + + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) + result[i][j] = + a * u[i] * u[j] + b * v[i] * v[j] + c * v[j] * u[i]; + } + result[0][0] += 1.0; + result[1][1] += 1.0; + result[2][2] += 1.0; + + if(MatType::numColumns() == 4) padMat4(result); + return result; + + } else { + double c = v1.dot(v2); + double a = (1.0 - c) / cross.dot(cross); + + double a0 = a * cross[0]; + double a1 = a * cross[1]; + double a2 = a * cross[2]; + + double a01 = a0 * cross[1]; + double a02 = a0 * cross[2]; + double a12 = a1 * cross[2]; + + MatType r; + + r[0][0] = c + a0 * cross[0]; + r[0][1] = a01 + cross[2]; + r[0][2] = a02 - cross[1], + r[1][0] = a01 - cross[2]; + r[1][1] = c + a1 * cross[1]; + r[1][2] = a12 + cross[0]; + r[2][0] = a02 + cross[1]; + r[2][1] = a12 - cross[0]; + r[2][2] = c + a2 * cross[2]; + + if(MatType::numColumns() == 4) padMat4(r); + return r; + + } +} + + +/// Return 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 3x3's rows. +template +Vec3 +getScale(const MatType &mat) +{ + typedef Vec3 V; + return V( + V(mat[0][0], mat[0][1], mat[0][2]).length(), + V(mat[1][0], mat[1][1], mat[1][2]).length(), + V(mat[2][0], mat[2][1], mat[2][2]).length()); +} + + +/// @brief Return a copy of the given matrix with its upper 3x3 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 3x3 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) +{ + typedef typename MatType::value_type T; + MatType result(in); + + for (int i(0); i < 3; i++) { + try { + const Vec3 u( + Vec3(in[i][0], in[i][1], in[i][2]).unit(eps, scaling[i])); + for (int j=0; j<3; j++) result[i][j] = u[j]; + } catch (ArithmeticError&) { + for (int j=0; j<3; j++) result[i][j] = 0; + } + } + return result; +} + + +/// @brief Set the matrix to a shear along @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) +{ + typedef typename MatType::value_type T; + + MatType r; + r[0][0] = T(0); r[0][1] = skew.z(); r[0][2] = -skew.y(); + r[1][0] = -skew.z(); r[1][1] = T(0); r[2][1] = skew.x(); + r[2][0] = skew.y(); r[2][1] = -skew.x(); r[2][2] = T(0); + + if(MatType::numColumns() == 4) padMat4(r); + return r; +} + + +/// @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) +{ + typedef typename MatType::value_type T; + Vec3 forward(direction.unit()); + Vec3 horizontal(vertical.unit().cross(forward).unit()); + Vec3 up(forward.cross(horizontal).unit()); + + MatType r; + + r[0][0]=horizontal.x(); r[0][1]=horizontal.y(); r[0][2]=horizontal.z(); + r[1][0]=up.x(); r[1][1]=up.y(); r[1][2]=up.z(); + r[2][0]=forward.x(); r[2][1]=forward.y(); r[2][2]=forward.z(); + + if(MatType::numColumns() == 4) padMat4(r); + return r; +} + + +/// @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 3x3 block. +template +static MatType& +padMat4(MatType& dest) +{ + dest[0][3] = dest[1][3] = dest[2][3] = 0; + dest[3][2] = dest[3][1] = dest[3][0] = 0; + dest[3][3] = 1; + + return dest; +} + + +/// @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 = (unsigned int)(log(aTol)/log(0.5)); + MatType Y[2]; + MatType Z[2]; + MatType invY; + MatType invZ; + + unsigned int current = 0; + + Y[0]=aA; + Z[0] = MatType::identity(); + + unsigned int iteration; + for (iteration=0; iteration +inline void +powSolve(const MatType &aA, MatType &aB, double aPower, double aTol=0.01) +{ + unsigned int iterations = (unsigned int)(log(aTol)/log(0.5)); + + const bool inverted = ( aPower < 0.0 ); + + if (inverted) { + aPower = -aPower; + } + + unsigned int whole = (unsigned int)aPower; + double fraction = aPower - whole; + + MatType R; + R = MatType::identity(); + + MatType partial = aA; + + double contribution = 1.0; + + unsigned int iteration; + + for (iteration=0; iteration< iterations; iteration++) + { + sqrtSolve(partial, partial, aTol); + contribution *= 0.5; + + if (fraction>=contribution) + { + R *= partial; + fraction-=contribution; + } + } + + partial = aA; + while (whole) + { + if (whole & 1) { + R *= partial; + } + whole>>=1; + if(whole) { + partial*=partial; + } + } + + if (inverted) { + aB = R.inverse(); + } + else { + aB = R; + } +} + + +/// @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) +{ + typedef typename MatType::ValueType value_type; + return !isApproxEqual(m.det(), (value_type)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) +{ + typedef typename MatType::ValueType value_type; + if (!isApproxEqual(std::abs(m.det()), value_type(1.0))) return false; + // check that the matrix transpose is the inverse + MatType temp = m * m.transpose(); + return temp.eq(MatType::identity()); +} + + +/// Determine if a matrix is diagonal. +template +inline bool +isDiagonal(const MatType& mat) +{ + int n = MatType::size; + typename MatType::ValueType temp(0); + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + if (i != j) { + temp+=std::abs(mat(i,j)); + } + } + } + return isApproxEqual(temp, typename MatType::ValueType(0.0)); +} + + +/// Return the @f$L_\infty@f$ norm of an N x N matrix. +template +typename MatType::ValueType +lInfinityNorm(const MatType& matrix) +{ + int n = MatType::size; + typename MatType::ValueType norm = 0; + + for( int j = 0; j +typename MatType::ValueType +lOneNorm(const MatType& matrix) +{ + int n = MatType::size; + typename MatType::ValueType norm = 0; + + for( int i = 0; i +bool +polarDecomposition(const MatType& input, MatType& unitary, + MatType& positive_hermitian, unsigned int MAX_ITERATIONS=100) +{ + unitary = input; + MatType new_unitary(input); + MatType unitary_inv; + + if (fabs(unitary.det()) < math::Tolerance::value()) return false; + + unsigned int iteration(0); + + typename MatType::ValueType linf_of_u; + typename MatType::ValueType l1nm_of_u; + typename MatType::ValueType linf_of_u_inv; + typename MatType::ValueType l1nm_of_u_inv; + typename MatType::ValueType l1_error = 100; + double gamma; + + do { + unitary_inv = unitary.inverse(); + linf_of_u = lInfinityNorm(unitary); + l1nm_of_u = lOneNorm(unitary); + + linf_of_u_inv = lInfinityNorm(unitary_inv); + l1nm_of_u_inv = lOneNorm(unitary_inv); + + gamma = sqrt( sqrt( (l1nm_of_u_inv * linf_of_u_inv ) / (l1nm_of_u * linf_of_u) )); + + new_unitary = 0.5*(gamma * unitary + (1./gamma) * unitary_inv.transpose() ); + + l1_error = lInfinityNorm(unitary - new_unitary); + unitary = new_unitary; + + /// this generally converges in less than ten iterations + if (iteration > MAX_ITERATIONS) return false; + iteration++; + } while (l1_error > math::Tolerance::value()); + + positive_hermitian = unitary.transpose() * input; + return true; +} + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_MAT_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Mat3.h b/openvdb_2_3_0_library/openvdb/math/Mat3.h new file mode 100755 index 0000000..94729eb --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Mat3.h @@ -0,0 +1,821 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_MATH_MAT3_H_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_MAT3_H_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include "Vec3.h" +#include "Mat.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +template class Vec3; +template class Mat4; +template class Quat; + +/// @class Mat3 Mat3.h +/// @brief 3x3 matrix class. +template +class Mat3: public Mat<3, T> +{ +public: + /// Data type held by the matrix. + typedef T value_type; + typedef T ValueType; + typedef Mat<3, T> MyBase; + /// Trivial constructor, the matrix is NOT initialized + Mat3() {} + + /// Constructor given the quaternion rotation, e.g. Mat3f m(q); + /// The quaternion is normalized and used to construct the matrix + Mat3(const Quat &q) + { setToRotation(q); } + + + /// Constructor given array of elements, the ordering is in row major form: + /** @verbatim + a b c + d e f + g h i + @endverbatim */ + template + Mat3(Source a, Source b, Source c, + Source d, Source e, Source f, + Source g, Source h, Source i) + { + MyBase::mm[0] = a; + MyBase::mm[1] = b; + MyBase::mm[2] = c; + MyBase::mm[3] = d; + MyBase::mm[4] = e; + MyBase::mm[5] = f; + MyBase::mm[6] = g; + MyBase::mm[7] = h; + MyBase::mm[8] = i; + } // constructor1Test + + /// Construct matrix given basis vectors (columns) + template + Mat3(const Vec3 &v1, const Vec3 &v2, const Vec3 &v3) + { setBasis(v1, v2, v3); } + + /// Constructor given array of elements, the ordering is in row major form:\n + /// a[0] a[1] a[2]\n + /// a[3] a[4] a[5]\n + /// a[6] a[7] a[8]\n + template + Mat3(Source *a) + { + MyBase::mm[0] = a[0]; + MyBase::mm[1] = a[1]; + MyBase::mm[2] = a[2]; + MyBase::mm[3] = a[3]; + MyBase::mm[4] = a[4]; + MyBase::mm[5] = a[5]; + MyBase::mm[6] = a[6]; + MyBase::mm[7] = a[7]; + MyBase::mm[8] = a[8]; + } // constructor1Test + + /// Copy constructor + Mat3(const Mat<3, T> &m) + { + for (int i=0; i<3; ++i) { + for (int j=0; j<3; ++j) { + MyBase::mm[i*3 + j] = m[i][j]; + } + } + } + + /// Conversion constructor + template + explicit Mat3(const Mat3 &m) + { + for (int i=0; i<3; ++i) { + for (int j=0; j<3; ++j) { + MyBase::mm[i*3 + j] = m[i][j]; + } + } + } + + /// Conversion from Mat4 (copies top left) + explicit Mat3(const Mat4 &m) + { + for (int i=0; i<3; ++i) { + for (int j=0; j<3; ++j) { + MyBase::mm[i*3 + j] = m[i][j]; + } + } + } + + /// Predefined constant for identity matrix + static const Mat3& identity() { + return sIdentity; + } + + /// Predefined constant for zero matrix + static const Mat3& zero() { + return sZero; + } + + /// Set ith row to vector v + void setRow(int i, const Vec3 &v) + { + // assert(i>=0 && i<3); + int i3 = i * 3; + + MyBase::mm[i3+0] = v[0]; + MyBase::mm[i3+1] = v[1]; + MyBase::mm[i3+2] = v[2]; + } // rowColumnTest + + /// Get ith row, e.g. Vec3d v = m.row(1); + Vec3 row(int i) const + { + // assert(i>=0 && i<3); + return Vec3((*this)(i,0), (*this)(i,1), (*this)(i,2)); + } // rowColumnTest + + /// Set jth column to vector v + void setCol(int j, const Vec3& v) + { + // assert(j>=0 && j<3); + MyBase::mm[0+j] = v[0]; + MyBase::mm[3+j] = v[1]; + MyBase::mm[6+j] = v[2]; + } // rowColumnTest + + /// Get jth column, e.g. Vec3d v = m.col(0); + Vec3 col(int j) const + { + // assert(j>=0 && j<3); + return Vec3((*this)(0,j), (*this)(1,j), (*this)(2,j)); + } // rowColumnTest + + // NB: The following two methods were changed to + // work around a gccWS5 compiler issue related to strict + // aliasing (see FX-475). + + //@{ + /// Array style reference to ith row + /// e.g. m[1][2] = 4; + T* operator[](int i) { return &(MyBase::mm[i*3]); } + const T* operator[](int i) const { return &(MyBase::mm[i*3]); } + //@} + + T* asPointer() {return MyBase::mm;} + const T* asPointer() const {return MyBase::mm;} + + /// Alternative indexed reference to the elements + /// Note that the indices are row first and column second. + /// e.g. m(0,0) = 1; + T& operator()(int i, int j) + { + // assert(i>=0 && i<3); + // assert(j>=0 && j<3); + return MyBase::mm[3*i+j]; + } // trivial + + /// Alternative indexed constant reference to the elements, + /// Note that the indices are row first and column second. + /// e.g. float f = m(1,0); + T operator()(int i, int j) const + { + // assert(i>=0 && i<3); + // assert(j>=0 && j<3); + return MyBase::mm[3*i+j]; + } // trivial + + /// Set the columns of "this" matrix to the vectors v1, v2, v3 + void setBasis(const Vec3 &v1, const Vec3 &v2, const Vec3 &v3) + { + MyBase::mm[0] = v1[0]; + MyBase::mm[1] = v1[1]; + MyBase::mm[2] = v1[2]; + MyBase::mm[3] = v2[0]; + MyBase::mm[4] = v2[1]; + MyBase::mm[5] = v2[2]; + MyBase::mm[6] = v3[0]; + MyBase::mm[7] = v3[1]; + MyBase::mm[8] = v3[2]; + } // setBasisTest + + /// Set diagonal and symmetric triangular components + void setSymmetric(const Vec3 &vdiag, const Vec3 &vtri) + { + MyBase::mm[0] = vdiag[0]; + MyBase::mm[1] = vtri[0]; + MyBase::mm[2] = vtri[1]; + MyBase::mm[3] = vtri[0]; + MyBase::mm[4] = vdiag[1]; + MyBase::mm[5] = vtri[2]; + MyBase::mm[6] = vtri[1]; + MyBase::mm[7] = vtri[2]; + MyBase::mm[8] = vdiag[2]; + } // setSymmetricTest + + /// Returns matrix with prescribed diagonal and symmetric triangular + /// components + static Mat3 symmetric(const Vec3 &vdiag, const Vec3 &vtri) + { + return Mat3( + vdiag[0], vtri[0], vtri[1], + vtri[0], vdiag[1], vtri[2], + vtri[1], vtri[2], vdiag[2] + ); + } + + /// Set the matrix as cross product of the given vector + void setSkew(const Vec3 &v) + {*this = skew(v);} + + /// @brief Set this matrix to the rotation matrix specified by the quaternion + /// @details The quaternion is normalized and used to construct the matrix. + /// Note that the matrix is transposed to match post-multiplication semantics. + void setToRotation(const Quat &q) + {*this = rotation >(q);} + + /// @brief Set this matrix to the rotation specified by @a axis and @a angle + /// @details The axis must be unit vector + void setToRotation(const Vec3 &axis, T angle) + {*this = rotation >(axis, angle);} + + /// Set this matrix to zero + void setZero() + { + MyBase::mm[0] = 0; + MyBase::mm[1] = 0; + MyBase::mm[2] = 0; + MyBase::mm[3] = 0; + MyBase::mm[4] = 0; + MyBase::mm[5] = 0; + MyBase::mm[6] = 0; + MyBase::mm[7] = 0; + MyBase::mm[8] = 0; + } // trivial + + /// Set "this" matrix to identity + void setIdentity() + { + MyBase::mm[0] = 1; + MyBase::mm[1] = 0; + MyBase::mm[2] = 0; + MyBase::mm[3] = 0; + MyBase::mm[4] = 1; + MyBase::mm[5] = 0; + MyBase::mm[6] = 0; + MyBase::mm[7] = 0; + MyBase::mm[8] = 1; + } // trivial + + /// Assignment operator + template + const Mat3& operator=(const Mat3 &m) + { + const Source *src = m.asPointer(); + + // don't suppress type conversion warnings + std::copy(src, (src + this->numElements()), MyBase::mm); + return *this; + } // opEqualToTest + + /// Test if "this" is equivalent to m with tolerance of eps value + bool eq(const Mat3 &m, T eps=1.0e-8) const + { + return (isApproxEqual(MyBase::mm[0],m.mm[0],eps) && + isApproxEqual(MyBase::mm[1],m.mm[1],eps) && + isApproxEqual(MyBase::mm[2],m.mm[2],eps) && + isApproxEqual(MyBase::mm[3],m.mm[3],eps) && + isApproxEqual(MyBase::mm[4],m.mm[4],eps) && + isApproxEqual(MyBase::mm[5],m.mm[5],eps) && + isApproxEqual(MyBase::mm[6],m.mm[6],eps) && + isApproxEqual(MyBase::mm[7],m.mm[7],eps) && + isApproxEqual(MyBase::mm[8],m.mm[8],eps)); + } // trivial + + /// Negation operator, for e.g. m1 = -m2; + Mat3 operator-() const + { + return Mat3( + -MyBase::mm[0], -MyBase::mm[1], -MyBase::mm[2], + -MyBase::mm[3], -MyBase::mm[4], -MyBase::mm[5], + -MyBase::mm[6], -MyBase::mm[7], -MyBase::mm[8] + ); + } // trivial + + /// Multiplication operator, e.g. M = scalar * M; + // friend Mat3 operator*(T scalar, const Mat3& m) { + // return m*scalar; + // } + + /// @brief Returns m, where \f$m_{i,j} *= scalar\f$ for \f$i, j \in [0, 2]\f$ + template + const Mat3& operator*=(S scalar) + { + MyBase::mm[0] *= scalar; + MyBase::mm[1] *= scalar; + MyBase::mm[2] *= scalar; + MyBase::mm[3] *= scalar; + MyBase::mm[4] *= scalar; + MyBase::mm[5] *= scalar; + MyBase::mm[6] *= scalar; + MyBase::mm[7] *= scalar; + MyBase::mm[8] *= scalar; + return *this; + } + + /// @brief Returns m0, where \f$m0_{i,j} += m1_{i,j}\f$ for \f$i, j \in [0, 2]\f$ + template + const Mat3 &operator+=(const Mat3 &m1) + { + const S *s = m1.asPointer(); + + MyBase::mm[0] += s[0]; + MyBase::mm[1] += s[1]; + MyBase::mm[2] += s[2]; + MyBase::mm[3] += s[3]; + MyBase::mm[4] += s[4]; + MyBase::mm[5] += s[5]; + MyBase::mm[6] += s[6]; + MyBase::mm[7] += s[7]; + MyBase::mm[8] += s[8]; + return *this; + } + + /// @brief Returns m0, where \f$m0_{i,j} -= m1_{i,j}\f$ for \f$i, j \in [0, 2]\f$ + template + const Mat3 &operator-=(const Mat3 &m1) + { + const S *s = m1.asPointer(); + + MyBase::mm[0] -= s[0]; + MyBase::mm[1] -= s[1]; + MyBase::mm[2] -= s[2]; + MyBase::mm[3] -= s[3]; + MyBase::mm[4] -= s[4]; + MyBase::mm[5] -= s[5]; + MyBase::mm[6] -= s[6]; + MyBase::mm[7] -= s[7]; + MyBase::mm[8] -= s[8]; + return *this; + } + + /// @brief Returns m0, where \f$m0_{i,j} *= m1_{i,j}\f$ for \f$i, j \in [0, 2]\f$ + template + const Mat3 &operator*=(const Mat3 &m1) + { + Mat3 m0(*this); + const T* s0 = m0.asPointer(); + const S* s1 = m1.asPointer(); + + MyBase::mm[0] = static_cast(s0[0] * s1[0] + + s0[1] * s1[3] + + s0[2] * s1[6]); + MyBase::mm[1] = static_cast(s0[0] * s1[1] + + s0[1] * s1[4] + + s0[2] * s1[7]); + MyBase::mm[2] = static_cast(s0[0] * s1[2] + + s0[1] * s1[5] + + s0[2] * s1[8]); + + MyBase::mm[3] = static_cast(s0[3] * s1[0] + + s0[4] * s1[3] + + s0[5] * s1[6]); + MyBase::mm[4] = static_cast(s0[3] * s1[1] + + s0[4] * s1[4] + + s0[5] * s1[7]); + MyBase::mm[5] = static_cast(s0[3] * s1[2] + + s0[4] * s1[5] + + s0[5] * s1[8]); + + MyBase::mm[6] = static_cast(s0[6] * s1[0] + + s0[7] * s1[3] + + s0[8] * s1[6]); + MyBase::mm[7] = static_cast(s0[6] * s1[1] + + s0[7] * s1[4] + + s0[8] * s1[7]); + MyBase::mm[8] = static_cast(s0[6] * s1[2] + + s0[7] * s1[5] + + s0[8] * s1[8]); + + return *this; + } + + /// returns adjoint of m + Mat3 adjoint() const + { + return Mat3( + MyBase::mm[4] * MyBase::mm[8] - MyBase::mm[5] * MyBase::mm[7], + MyBase::mm[2] * MyBase::mm[7] - MyBase::mm[1] * MyBase::mm[8], + MyBase::mm[1] * MyBase::mm[5] - MyBase::mm[2] * MyBase::mm[4], + MyBase::mm[5] * MyBase::mm[6] - MyBase::mm[3] * MyBase::mm[8], + MyBase::mm[0] * MyBase::mm[8] - MyBase::mm[2] * MyBase::mm[6], + MyBase::mm[2] * MyBase::mm[3] - MyBase::mm[0] * MyBase::mm[5], + MyBase::mm[3] * MyBase::mm[7] - MyBase::mm[4] * MyBase::mm[6], + MyBase::mm[1] * MyBase::mm[6] - MyBase::mm[0] * MyBase::mm[7], + MyBase::mm[0] * MyBase::mm[4] - MyBase::mm[1] * MyBase::mm[3]); + } // adjointTest + + /// returns transpose of this + Mat3 transpose() const + { + return Mat3( + MyBase::mm[0], MyBase::mm[3], MyBase::mm[6], + MyBase::mm[1], MyBase::mm[4], MyBase::mm[7], + MyBase::mm[2], MyBase::mm[5], MyBase::mm[8]); + + } // transposeTest + + /// returns inverse of this + /// throws FailedOperationException if singular + Mat3 inverse(T tolerance = 0) const + { + Mat3 inv(adjoint()); + + T det = inv.mm[0]*MyBase::mm[0] + inv.mm[1]*MyBase::mm[3] + inv.mm[2]*MyBase::mm[6]; + + // If the determinant is 0, m was singular and "this" will contain junk. + if (isApproxEqual(det,0.0,tolerance)) + { + OPENVDB_THROW(ArithmeticError, "Inversion of singular 3x3 matrix"); + } + return inv * (T(1)/det); + } // invertTest + + /// Determinant of matrix + T det() const + { + T co00 = MyBase::mm[4]*MyBase::mm[8] - MyBase::mm[5]*MyBase::mm[7]; + T co10 = MyBase::mm[5]*MyBase::mm[6] - MyBase::mm[3]*MyBase::mm[8]; + T co20 = MyBase::mm[3]*MyBase::mm[7] - MyBase::mm[4]*MyBase::mm[6]; + T d = MyBase::mm[0]*co00 + MyBase::mm[1]*co10 + MyBase::mm[2]*co20; + return d; + } // determinantTest + + /// Trace of matrix + T trace() const + { + return MyBase::mm[0]+MyBase::mm[4]+MyBase::mm[8]; + } + + /// This function snaps a specific axis to a specific direction, + /// preserving scaling. It does this using minimum energy, thus + /// posing a unique solution if basis & direction arent parralel. + /// Direction need not be unit. + Mat3 snapBasis(Axis axis, const Vec3 &direction) + { + return snapBasis(*this, axis, direction); + } + + /// Return the transformed vector by "this" matrix. + /// This function is equivalent to post-multiplying the matrix. + template + Vec3 transform(const Vec3 &v) const + { + return static_cast< Vec3 >(v * *this); + } // xformVectorTest + + /// Return the transformed vector by transpose of "this" matrix. + /// This function is equivalent to pre-multiplying the matrix. + template + Vec3 pretransform(const Vec3 &v) const + { + return static_cast< Vec3 >(*this * v); + } // xformTVectorTest + + /// This function snaps a specific axis to a specific direction, + /// preserving scaling. It does this using minimum energy, thus + /// posing a unique solution if basis & direction arent parralel. + /// Direction need not be unit. + template + Mat3 snappedBasis(Axis axis, const Vec3& direction) const + { + return snapBasis(*this, axis, direction); + } + +private: + static const Mat3 sIdentity; + static const Mat3 sZero; +}; // class Mat3 + + +template +const Mat3 Mat3::sIdentity = Mat3(1, 0, 0, + 0, 1, 0, + 0, 0, 1); + +template +const Mat3 Mat3::sZero = Mat3(0, 0, 0, + 0, 0, 0, + 0, 0, 0); + +/// @relates Mat3 +/// @brief Equality operator, does exact floating point comparisons +template +bool operator==(const Mat3 &m0, const Mat3 &m1) +{ + const T0 *t0 = m0.asPointer(); + const T1 *t1 = m1.asPointer(); + + for (int i=0; i<9; ++i) { + if (!isExactlyEqual(t0[i], t1[i])) return false; + } + return true; +} + +/// @relates Mat3 +/// @brief Inequality operator, does exact floating point comparisons +template +bool operator!=(const Mat3 &m0, const Mat3 &m1) { return !(m0 == m1); } + +/// @relates Mat3 +/// @brief Returns M, where \f$M_{i,j} = m_{i,j} * scalar\f$ for \f$i, j \in [0, 2]\f$ +template +Mat3::type> operator*(S scalar, const Mat3 &m) +{ return m*scalar; } + +/// @relates Mat3 +/// @brief Returns M, where \f$M_{i,j} = m_{i,j} * scalar\f$ for \f$i, j \in [0, 2]\f$ +template +Mat3::type> operator*(const Mat3 &m, S scalar) +{ + Mat3::type> result(m); + result *= scalar; + return result; +} + +/// @relates Mat3 +/// @brief Returns M, where \f$M_{i,j} = m0_{i,j} + m1_{i,j}\f$ for \f$i, j \in [0, 2]\f$ +template +Mat3::type> operator+(const Mat3 &m0, const Mat3 &m1) +{ + Mat3::type> result(m0); + result += m1; + return result; +} + +/// @relates Mat3 +/// @brief Returns M, where \f$M_{i,j} = m0_{i,j} - m1_{i,j}\f$ for \f$i, j \in [0, 2]\f$ +template +Mat3::type> operator-(const Mat3 &m0, const Mat3 &m1) +{ + Mat3::type> result(m0); + result -= m1; + return result; +} + + +/// @brief Matrix multiplication. +/// +/// Returns M, where +/// \f$M_{ij} = \sum_{n=0}^2\left(m0_{nj} + m1_{in}\right)\f$ for \f$i, j \in [0, 2]\f$ +template +Mat3::type>operator*(const Mat3 &m0, const Mat3 &m1) +{ + Mat3::type> result(m0); + result *= m1; + return result; +} + +/// @relates Mat3 +/// @brief Returns v, where \f$v_{i} = \sum_{n=0}^2 m_{i,n} * v_n\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> +operator*(const Mat3 &_m, const Vec3 &_v) +{ + MT const *m = _m.asPointer(); + return Vec3::type>( + _v[0]*m[0] + _v[1]*m[1] + _v[2]*m[2], + _v[0]*m[3] + _v[1]*m[4] + _v[2]*m[5], + _v[0]*m[6] + _v[1]*m[7] + _v[2]*m[8]); +} + +/// @relates Mat3 +/// @brief Returns v, where \f$v_{i} = \sum_{n=0}^2 m_{n,i} * v_n\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> +operator*(const Vec3 &_v, const Mat3 &_m) +{ + MT const *m = _m.asPointer(); + return Vec3::type>( + _v[0]*m[0] + _v[1]*m[3] + _v[2]*m[6], + _v[0]*m[1] + _v[1]*m[4] + _v[2]*m[7], + _v[0]*m[2] + _v[1]*m[5] + _v[2]*m[8]); +} + +/// @relates Mat3 +/// @brief Returns v, where \f$v_{i} = \sum_{n=0}^2 m_{i,n} * v_n\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3 &operator *= (Vec3 &_v, const Mat3 &_m) +{ + Vec3 mult = _v * _m; + _v = mult; + return _v; +} + +/// this = outer product of v1, v2 +/// e.g. M = Mat3f::outerproduct(v1,v2); +template +Mat3 outerProduct(const Vec3& v1, const Vec3& v2) +{ + Mat3 m; + + m.setBasis(Vec3(v1[0]*v2[0], v1[1]*v2[0], v1[2]*v2[0]), + Vec3(v1[0]*v2[1], v1[1]*v2[1], v1[2]*v2[1]), + Vec3(v1[0]*v2[2], v1[1]*v2[2], v1[2]*v2[2])); + + return m; +} // outerproductTest + +typedef Mat3 Mat3s; +typedef Mat3 Mat3d; + +#if DWREAL_IS_DOUBLE == 1 +typedef Mat3d Mat3f; +#else +typedef Mat3s Mat3f; +#endif // DWREAL_IS_DOUBLE + + +/// Interpolate the rotation between m1 and m2 using Mat::powSolve. +/// Unlike slerp, translation is not treated independently. +/// This results in smoother animation results. +template +Mat3 powLerp(const Mat3 &m1, const Mat3 &m2, T t) +{ + Mat3 x = m1.inverse() * m2; + powSolve(x, x, t); + Mat3 m = m1 * x; + return m; +} + + +namespace { + template + void pivot(int i, int j, Mat3& S, Vec3& D, Mat3& Q) + { + const int& n = Mat3::size; // should be 3 + T temp; + /// scratch variables used in pivoting + double cotan_of_2_theta; + double tan_of_theta; + double cosin_of_theta; + double sin_of_theta; + double z; + + double Sij = S(i,j); + + double Sjj_minus_Sii = D[j] - D[i]; + + if (fabs(Sjj_minus_Sii) * (10*math::Tolerance::value()) > fabs(Sij)) { + tan_of_theta = Sij / Sjj_minus_Sii; + } else { + /// pivot on Sij + cotan_of_2_theta = 0.5*Sjj_minus_Sii / Sij ; + + if (cotan_of_2_theta < 0.) { + tan_of_theta = + -1./(sqrt(1. + cotan_of_2_theta*cotan_of_2_theta) - cotan_of_2_theta); + } else { + tan_of_theta = + 1./(sqrt(1. + cotan_of_2_theta*cotan_of_2_theta) + cotan_of_2_theta); + } + } + + cosin_of_theta = 1./sqrt( 1. + tan_of_theta * tan_of_theta); + sin_of_theta = cosin_of_theta * tan_of_theta; + z = tan_of_theta * Sij; + S(i,j) = 0; + D[i] -= z; + D[j] += z; + for (int k = 0; k < i; ++k) { + temp = S(k,i); + S(k,i) = cosin_of_theta * temp - sin_of_theta * S(k,j); + S(k,j)= sin_of_theta * temp + cosin_of_theta * S(k,j); + } + for (int k = i+1; k < j; ++k) { + temp = S(i,k); + S(i,k) = cosin_of_theta * temp - sin_of_theta * S(k,j); + S(k,j) = sin_of_theta * temp + cosin_of_theta * S(k,j); + } + for (int k = j+1; k < n; ++k) { + temp = S(i,k); + S(i,k) = cosin_of_theta * temp - sin_of_theta * S(j,k); + S(j,k) = sin_of_theta * temp + cosin_of_theta * S(j,k); + } + for (int k = 0; k < n; ++k) + { + temp = Q(k,i); + Q(k,i) = cosin_of_theta * temp - sin_of_theta*Q(k,j); + Q(k,j) = sin_of_theta * temp + cosin_of_theta*Q(k,j); + } + } +} + + +/// @brief Use Jacobi iterations to decompose a symmetric 3x3 matrix +/// (diagonalize and compute eigenvectors) +/// @details This is based on the "Efficient numerical diagonalization of Hermitian 3x3 matrices" +/// Joachim Kopp. arXiv.org preprint: physics/0610206 +/// with the addition of largest pivot +template +bool diagonalizeSymmetricMatrix(const Mat3& input, Mat3& Q, Vec3& D, + unsigned int MAX_ITERATIONS=250) +{ + /// use Givens rotation matrix to eliminate off-diagonal entries. + /// initialize the rotation matrix as idenity + Q = Mat3::identity(); + int n = Mat3::size; // should be 3 + + /// temp matrix. Assumed to be symmetric + Mat3 S(input); + + for (int i = 0; i < n; ++i) { + D[i] = S(i,i); + } + + unsigned int iterations(0); + /// Just iterate over all the non-diagonal enteries + /// using the largest as a pivot. + do { + /// check for absolute convergence + /// are symmetric off diagonals all zero + double er = 0; + for (int i = 0; i < n; ++i) { + for (int j = i+1; j < n; ++j) { + er += fabs(S(i,j)); + } + } + if (std::abs(er) < math::Tolerance::value()) { + return true; + } + iterations++; + + T max_element = 0; + int ip = 0; + int jp = 0; + /// loop over all the off-diagonals above the diagonal + for (int i = 0; i < n; ++i) { + for (int j = i+1; j < n; ++j){ + + if ( fabs(D[i]) * (10*math::Tolerance::value()) > fabs(S(i,j))) { + /// value too small to pivot on + S(i,j) = 0; + } + if (fabs(S(i,j)) > max_element) { + max_element = fabs(S(i,j)); + ip = i; + jp = j; + } + } + } + pivot(ip, jp, S, D, Q); + } while (iterations < MAX_ITERATIONS); + + return false; +} + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_MAT3_H_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Mat4.h b/openvdb_2_3_0_library/openvdb/math/Mat4.h new file mode 100755 index 0000000..cf1452c --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Mat4.h @@ -0,0 +1,1367 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_MATH_MAT4_H_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_MAT4_H_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include "Math.h" +#include "Mat3.h" +#include "Vec3.h" +#include "Vec4.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +template class Vec4; + + +/// @class Mat4 Mat4.h +/// @brief 4x4 -matrix class. +template +class Mat4: public Mat<4, T> +{ +public: + /// Data type held by the matrix. + typedef T value_type; + typedef T ValueType; + typedef Mat<4, T> MyBase; + + /// Trivial constructor, the matrix is NOT initialized + Mat4() {} + + /// Constructor given array of elements, the ordering is in row major form: + /** @verbatim + a[ 0] a[1] a[ 2] a[ 3] + a[ 4] a[5] a[ 6] a[ 7] + a[ 8] a[9] a[10] a[11] + a[12] a[13] a[14] a[15] + @endverbatim */ + template + Mat4(Source *a) + { + 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] = a; + MyBase::mm[ 1] = b; + MyBase::mm[ 2] = c; + MyBase::mm[ 3] = d; + + MyBase::mm[ 4] = e; + MyBase::mm[ 5] = f; + MyBase::mm[ 6] = g; + MyBase::mm[ 7] = h; + + MyBase::mm[ 8] = i; + MyBase::mm[ 9] = j; + MyBase::mm[10] = k; + MyBase::mm[11] = l; + + MyBase::mm[12] = m; + MyBase::mm[13] = n; + MyBase::mm[14] = o; + MyBase::mm[15] = p; + } + + /// Construct matrix given basis vectors (columns) + template + Mat4(const Vec4 &v1, const Vec4 &v2, + const Vec4 &v3, const Vec4 &v4) + { + setBasis(v1, v2, v3, v4); + } + + /// Copy constructor + Mat4(const Mat<4, T> &m) + { + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + MyBase::mm[i*4 + j] = m[i][j]; + } + } + } + + /// Conversion constructor + template + explicit Mat4(const Mat4 &m) + { + const Source *src = m.asPointer(); + + for (int i=0; i<16; ++i) { + MyBase::mm[i] = static_cast(src[i]); + } + } + + /// Predefined constant for identity matrix + static const Mat4& identity() { + return sIdentity; + } + + /// Predefined constant for zero matrix + static const Mat4& zero() { + return sZero; + } + + /// Set ith row to vector v + void setRow(int i, const Vec4 &v) + { + // assert(i>=0 && i<4); + int i4 = i * 4; + MyBase::mm[i4+0] = v[0]; + MyBase::mm[i4+1] = v[1]; + MyBase::mm[i4+2] = v[2]; + MyBase::mm[i4+3] = v[3]; + } + + /// Get ith row, e.g. Vec4f v = m.row(1); + Vec4 row(int i) const + { + // assert(i>=0 && i<3); + return Vec4((*this)(i,0), (*this)(i,1), (*this)(i,2), (*this)(i,3)); + } + + /// Set jth column to vector v + void setCol(int j, const Vec4& v) + { + // assert(j>=0 && j<4); + MyBase::mm[ 0+j] = v[0]; + MyBase::mm[ 4+j] = v[1]; + MyBase::mm[ 8+j] = v[2]; + MyBase::mm[12+j] = v[3]; + } + + /// Get jth column, e.g. Vec4f v = m.col(0); + Vec4 col(int j) const + { + // assert(j>=0 && j<4); + return Vec4((*this)(0,j), (*this)(1,j), (*this)(2,j), (*this)(3,j)); + } + + //@{ + /// Array style reference to ith row + /// e.g. m[1][3] = 4; + T* operator[](int i) { return &(MyBase::mm[i<<2]); } + const T* operator[](int i) const { return &(MyBase::mm[i<<2]); } + //@} + + /// Direct access to the internal data + T* asPointer() {return MyBase::mm;} + const T* asPointer() const {return MyBase::mm;} + + /// Alternative indexed reference to the elements + /// Note that the indices are row first and column second. + /// e.g. m(0,0) = 1; + T& operator()(int i, int j) + { + // assert(i>=0 && i<4); + // assert(j>=0 && j<4); + return MyBase::mm[4*i+j]; + } + + /// Alternative indexed constant reference to the elements, + /// Note that the indices are row first and column second. + /// e.g. float f = m(1,0); + T operator()(int i, int j) const + { + // assert(i>=0 && i<4); + // assert(j>=0 && j<4); + return MyBase::mm[4*i+j]; + } + + /// Set the columns of "this" matrix to the vectors v1, v2, v3, v4 + void setBasis(const Vec4 &v1, const Vec4 &v2, + const Vec4 &v3, const Vec4 &v4) + { + MyBase::mm[ 0] = v1[0]; + MyBase::mm[ 1] = v1[1]; + MyBase::mm[ 2] = v1[2]; + MyBase::mm[ 3] = v1[3]; + + MyBase::mm[ 4] = v2[0]; + MyBase::mm[ 5] = v2[1]; + MyBase::mm[ 6] = v2[2]; + MyBase::mm[ 7] = v2[3]; + + MyBase::mm[ 8] = v3[0]; + MyBase::mm[ 9] = v3[1]; + MyBase::mm[10] = v3[2]; + MyBase::mm[11] = v3[3]; + + MyBase::mm[12] = v4[0]; + MyBase::mm[13] = v4[1]; + MyBase::mm[14] = v4[2]; + MyBase::mm[15] = v4[3]; + } + + + // Set "this" matrix to zero + void setZero() + { + MyBase::mm[ 0] = 0; + MyBase::mm[ 1] = 0; + MyBase::mm[ 2] = 0; + MyBase::mm[ 3] = 0; + MyBase::mm[ 4] = 0; + MyBase::mm[ 5] = 0; + MyBase::mm[ 6] = 0; + MyBase::mm[ 7] = 0; + MyBase::mm[ 8] = 0; + MyBase::mm[ 9] = 0; + MyBase::mm[10] = 0; + MyBase::mm[11] = 0; + MyBase::mm[12] = 0; + MyBase::mm[13] = 0; + MyBase::mm[14] = 0; + MyBase::mm[15] = 0; + } + + /// Set "this" matrix to identity + void setIdentity() + { + MyBase::mm[ 0] = 1; + MyBase::mm[ 1] = 0; + MyBase::mm[ 2] = 0; + MyBase::mm[ 3] = 0; + + MyBase::mm[ 4] = 0; + MyBase::mm[ 5] = 1; + MyBase::mm[ 6] = 0; + MyBase::mm[ 7] = 0; + + MyBase::mm[ 8] = 0; + MyBase::mm[ 9] = 0; + MyBase::mm[10] = 1; + MyBase::mm[11] = 0; + + MyBase::mm[12] = 0; + MyBase::mm[13] = 0; + MyBase::mm[14] = 0; + MyBase::mm[15] = 1; + } + + + /// Set upper left to a Mat3 + void setMat3(const Mat3 &m) + { + for (int i = 0; i < 3; i++) + for (int j=0; j < 3; j++) + MyBase::mm[i*4+j] = m[i][j]; + } + + Mat3 getMat3() const + { + Mat3 m; + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + m[i][j] = MyBase::mm[i*4+j]; + + return m; + } + + /// Return the translation component + Vec3 getTranslation() const + { + return Vec3(MyBase::mm[12], MyBase::mm[13], MyBase::mm[14]); + } + + void setTranslation(const Vec3 &t) + { + MyBase::mm[12] = t[0]; + MyBase::mm[13] = t[1]; + MyBase::mm[14] = t[2]; + } + + /// Assignment operator + template + const Mat4& operator=(const Mat4 &m) + { + const Source *src = m.asPointer(); + + // don't suppress warnings when assigning from different numerical types + std::copy(src, (src + this->numElements()), MyBase::mm); + return *this; + } + + /// Test if "this" is equivalent to m with tolerance of eps value + bool eq(const Mat4 &m, T eps=1.0e-8) const + { + for (int i = 0; i < 16; i++) { + if (!isApproxEqual(MyBase::mm[i], m.mm[i], eps)) + return false; + } + return true; + } + + /// Negation operator, for e.g. m1 = -m2; + Mat4 operator-() const + { + return Mat4( + -MyBase::mm[ 0], -MyBase::mm[ 1], -MyBase::mm[ 2], -MyBase::mm[ 3], + -MyBase::mm[ 4], -MyBase::mm[ 5], -MyBase::mm[ 6], -MyBase::mm[ 7], + -MyBase::mm[ 8], -MyBase::mm[ 9], -MyBase::mm[10], -MyBase::mm[11], + -MyBase::mm[12], -MyBase::mm[13], -MyBase::mm[14], -MyBase::mm[15] + ); + } // trivial + + /// Return m, where \f$m_{i,j} *= scalar\f$ for \f$i, j \in [0, 3]\f$ + template + const Mat4& operator*=(S scalar) + { + MyBase::mm[ 0] *= scalar; + MyBase::mm[ 1] *= scalar; + MyBase::mm[ 2] *= scalar; + MyBase::mm[ 3] *= scalar; + + MyBase::mm[ 4] *= scalar; + MyBase::mm[ 5] *= scalar; + MyBase::mm[ 6] *= scalar; + MyBase::mm[ 7] *= scalar; + + MyBase::mm[ 8] *= scalar; + MyBase::mm[ 9] *= scalar; + MyBase::mm[10] *= scalar; + MyBase::mm[11] *= scalar; + + MyBase::mm[12] *= scalar; + MyBase::mm[13] *= scalar; + MyBase::mm[14] *= scalar; + MyBase::mm[15] *= scalar; + return *this; + } + + /// @brief Returns m0, where \f$m0_{i,j} += m1_{i,j}\f$ for \f$i, j \in [0, 3]\f$ + template + const Mat4 &operator+=(const Mat4 &m1) + { + const S* s = m1.asPointer(); + + MyBase::mm[ 0] += s[ 0]; + MyBase::mm[ 1] += s[ 1]; + MyBase::mm[ 2] += s[ 2]; + MyBase::mm[ 3] += s[ 3]; + + MyBase::mm[ 4] += s[ 4]; + MyBase::mm[ 5] += s[ 5]; + MyBase::mm[ 6] += s[ 6]; + MyBase::mm[ 7] += s[ 7]; + + MyBase::mm[ 8] += s[ 8]; + MyBase::mm[ 9] += s[ 9]; + MyBase::mm[10] += s[10]; + MyBase::mm[11] += s[11]; + + MyBase::mm[12] += s[12]; + MyBase::mm[13] += s[13]; + MyBase::mm[14] += s[14]; + MyBase::mm[15] += s[15]; + + return *this; + } + + /// @brief Returns m0, where \f$m0_{i,j} -= m1_{i,j}\f$ for \f$i, j \in [0, 3]\f$ + template + const Mat4 &operator-=(const Mat4 &m1) + { + const S* s = m1.asPointer(); + + MyBase::mm[ 0] -= s[ 0]; + MyBase::mm[ 1] -= s[ 1]; + MyBase::mm[ 2] -= s[ 2]; + MyBase::mm[ 3] -= s[ 3]; + + MyBase::mm[ 4] -= s[ 4]; + MyBase::mm[ 5] -= s[ 5]; + MyBase::mm[ 6] -= s[ 6]; + MyBase::mm[ 7] -= s[ 7]; + + MyBase::mm[ 8] -= s[ 8]; + MyBase::mm[ 9] -= s[ 9]; + MyBase::mm[10] -= s[10]; + MyBase::mm[11] -= s[11]; + + MyBase::mm[12] -= s[12]; + MyBase::mm[13] -= s[13]; + MyBase::mm[14] -= s[14]; + MyBase::mm[15] -= s[15]; + + return *this; + } + + /// Return m, where \f$m_{i,j} = \sum_{k} m0_{i,k}*m1_{k,j}\f$ for \f$i, j \in [0, 3]\f$ + template + const Mat4 &operator*=(const Mat4 &m1) + { + Mat4 m0(*this); + + const T* s0 = m0.asPointer(); + const S* s1 = m1.asPointer(); + + for (int i = 0; i < 4; i++) { + int i4 = 4 * i; + MyBase::mm[i4+0] = static_cast(s0[i4+0] * s1[ 0] + + s0[i4+1] * s1[ 4] + + s0[i4+2] * s1[ 8] + + s0[i4+3] * s1[12]); + + MyBase::mm[i4+1] = static_cast(s0[i4+0] * s1[ 1] + + s0[i4+1] * s1[ 5] + + s0[i4+2] * s1[ 9] + + s0[i4+3] * s1[13]); + + MyBase::mm[i4+2] = static_cast(s0[i4+0] * s1[ 2] + + s0[i4+1] * s1[ 6] + + s0[i4+2] * s1[10] + + s0[i4+3] * s1[14]); + + MyBase::mm[i4+3] = static_cast(s0[i4+0] * s1[ 3] + + s0[i4+1] * s1[ 7] + + s0[i4+2] * s1[11] + + s0[i4+3] * s1[15]); + } + return *this; + } + + /// @return transpose of this + Mat4 transpose() const + { + return Mat4( + MyBase::mm[ 0], MyBase::mm[ 4], MyBase::mm[ 8], MyBase::mm[12], + MyBase::mm[ 1], MyBase::mm[ 5], MyBase::mm[ 9], MyBase::mm[13], + MyBase::mm[ 2], MyBase::mm[ 6], MyBase::mm[10], MyBase::mm[14], + MyBase::mm[ 3], MyBase::mm[ 7], MyBase::mm[11], MyBase::mm[15] + ); + } + + + /// @return inverse of this + /// @throw ArithmeticError if singular + Mat4 inverse(T tolerance = 0) const + { + // + // inv [ A | b ] = [ E | f ] A: 3x3, b: 3x1, c': 1x3 d: 1x1 + // [ c' | d ] [ g' | h ] + // + // If A is invertible use + // + // E = A^-1 + p*h*r + // p = A^-1 * b + // f = -p * h + // g' = -h * c' + // h = 1 / (d - c'*p) + // r' = c'*A^-1 + // + // Otherwise use gauss-jordan elimination + // + + // + // We create this alias to ourself so we can easily use own subscript + // operator. + const Mat4& m(*this); + + T m0011 = m[0][0] * m[1][1]; + T m0012 = m[0][0] * m[1][2]; + T m0110 = m[0][1] * m[1][0]; + T m0210 = m[0][2] * m[1][0]; + T m0120 = m[0][1] * m[2][0]; + T m0220 = m[0][2] * m[2][0]; + + T detA = m0011 * m[2][2] - m0012 * m[2][1] - m0110 * m[2][2] + + m0210 * m[2][1] + m0120 * m[1][2] - m0220 * m[1][1]; + + bool hasPerspective = + (!isExactlyEqual(m[0][3], T(0.0)) || + !isExactlyEqual(m[1][3], T(0.0)) || + !isExactlyEqual(m[2][3], T(0.0)) || + !isExactlyEqual(m[3][3], T(1.0))); + + T det; + if (hasPerspective) { + det = m[0][3] * det3(m, 1,2,3, 0,2,1) + + m[1][3] * det3(m, 2,0,3, 0,2,1) + + m[2][3] * det3(m, 3,0,1, 0,2,1) + + m[3][3] * detA; + } else { + det = detA * m[3][3]; + } + + Mat4 inv; + bool invertible; + + if (isApproxEqual(det,T(0.0),tolerance)) { + invertible = false; + + } else if (isApproxEqual(detA,T(0.0),T(1e-8))) { + // det is too small to rely on inversion by subblocks + invertible = m.invert(inv, tolerance); + + } else { + invertible = true; + detA = 1.0 / detA; + + // + // Calculate A^-1 + // + inv[0][0] = detA * ( m[1][1] * m[2][2] - m[1][2] * m[2][1]); + inv[0][1] = detA * (-m[0][1] * m[2][2] + m[0][2] * m[2][1]); + inv[0][2] = detA * ( m[0][1] * m[1][2] - m[0][2] * m[1][1]); + + inv[1][0] = detA * (-m[1][0] * m[2][2] + m[1][2] * m[2][0]); + inv[1][1] = detA * ( m[0][0] * m[2][2] - m0220); + inv[1][2] = detA * ( m0210 - m0012); + + inv[2][0] = detA * ( m[1][0] * m[2][1] - m[1][1] * m[2][0]); + inv[2][1] = detA * ( m0120 - m[0][0] * m[2][1]); + inv[2][2] = detA * ( m0011 - m0110); + + if (hasPerspective) { + // + // Calculate r, p, and h + // + Vec3 r; + r[0] = m[3][0] * inv[0][0] + m[3][1] * inv[1][0] + + m[3][2] * inv[2][0]; + r[1] = m[3][0] * inv[0][1] + m[3][1] * inv[1][1] + + m[3][2] * inv[2][1]; + r[2] = m[3][0] * inv[0][2] + m[3][1] * inv[1][2] + + m[3][2] * inv[2][2]; + + Vec3 p; + p[0] = inv[0][0] * m[0][3] + inv[0][1] * m[1][3] + + inv[0][2] * m[2][3]; + p[1] = inv[1][0] * m[0][3] + inv[1][1] * m[1][3] + + inv[1][2] * m[2][3]; + p[2] = inv[2][0] * m[0][3] + inv[2][1] * m[1][3] + + inv[2][2] * m[2][3]; + + T h = m[3][3] - p.dot(Vec3(m[3][0],m[3][1],m[3][2])); + if (isApproxEqual(h,T(0.0),tolerance)) { + invertible = false; + + } else { + h = 1.0 / h; + + // + // Calculate h, g, and f + // + inv[3][3] = h; + inv[3][0] = -h * r[0]; + inv[3][1] = -h * r[1]; + inv[3][2] = -h * r[2]; + + inv[0][3] = -h * p[0]; + inv[1][3] = -h * p[1]; + inv[2][3] = -h * p[2]; + + // + // Calculate E + // + p *= h; + inv[0][0] += p[0] * r[0]; + inv[0][1] += p[0] * r[1]; + inv[0][2] += p[0] * r[2]; + inv[1][0] += p[1] * r[0]; + inv[1][1] += p[1] * r[1]; + inv[1][2] += p[1] * r[2]; + inv[2][0] += p[2] * r[0]; + inv[2][1] += p[2] * r[1]; + inv[2][2] += p[2] * r[2]; + } + } else { + // Equations are much simpler in the non-perspective case + inv[3][0] = - (m[3][0] * inv[0][0] + m[3][1] * inv[1][0] + + m[3][2] * inv[2][0]); + inv[3][1] = - (m[3][0] * inv[0][1] + m[3][1] * inv[1][1] + + m[3][2] * inv[2][1]); + inv[3][2] = - (m[3][0] * inv[0][2] + m[3][1] * inv[1][2] + + m[3][2] * inv[2][2]); + inv[0][3] = 0.0; + inv[1][3] = 0.0; + inv[2][3] = 0.0; + inv[3][3] = 1.0; + } + } + + if (!invertible) OPENVDB_THROW(ArithmeticError, "Inversion of singular 4x4 matrix"); + return inv; + } + + + /// Determinant of matrix + T det() const + { + const T *ap; + Mat3 submat; + T det; + T *sp; + int i, j, k, sign; + + det = 0; + sign = 1; + for (i = 0; i < 4; i++) { + ap = &MyBase::mm[ 0]; + sp = submat.asPointer(); + for (j = 0; j < 4; j++) { + for (k = 0; k < 4; k++) { + if ((k != i) && (j != 0)) { + *sp++ = *ap; + } + ap++; + } + } + + det += sign * MyBase::mm[i] * submat.det(); + sign = -sign; + } + + return det; + } + + /// This function snaps a specific axis to a specific direction, + /// preserving scaling. It does this using minimum energy, thus + /// posing a unique solution if basis & direction arent parralel. + /// Direction need not be unit. + Mat4 snapBasis(Axis axis, const Vec3 &direction) + {return snapBasis(*this, axis, direction);} + + /// Sets the matrix to a matrix that translates by v + static Mat4 translation(const Vec3d& v) + { + return Mat4( + T(1), T(0), T(0), T(0), + T(0), T(1), T(0), T(0), + T(0), T(0), T(1), T(0), + T(v.x()), T(v.y()),T(v.z()), T(1)); + } + + /// Sets the matrix to a matrix that translates by v + template + void setToTranslation(const Vec3& v) + { + MyBase::mm[ 0] = 1; + MyBase::mm[ 1] = 0; + MyBase::mm[ 2] = 0; + MyBase::mm[ 3] = 0; + + MyBase::mm[ 4] = 0; + MyBase::mm[ 5] = 1; + MyBase::mm[ 6] = 0; + MyBase::mm[ 7] = 0; + + MyBase::mm[ 8] = 0; + MyBase::mm[ 9] = 0; + MyBase::mm[10] = 1; + MyBase::mm[11] = 0; + + MyBase::mm[12] = v.x(); + MyBase::mm[13] = v.y(); + MyBase::mm[14] = v.z(); + MyBase::mm[15] = 1; + } + + /// Left multiples by the specified translation, i.e. Trans * (*this) + template + void preTranslate(const Vec3& tr) + { + Vec3 tmp(tr.x(), tr.y(), tr.z()); + Mat4 Tr = Mat4::translation(tmp); + + *this = Tr * (*this); + + } + + /// Right multiplies by the specified translation matrix, i.e. (*this) * Trans + template + void postTranslate(const Vec3& tr) + { + Vec3 tmp(tr.x(), tr.y(), tr.z()); + Mat4 Tr = Mat4::translation(tmp); + + *this = (*this) * Tr; + + } + + + /// Sets the matrix to a matrix that scales by v + template + void setToScale(const Vec3& v) + { + this->setIdentity(); + MyBase::mm[ 0] = v.x(); + MyBase::mm[ 5] = v.y(); + MyBase::mm[10] = v.z(); + } + + // Left multiples by the specified scale matrix, i.e. Sc * (*this) + template + void preScale(const Vec3& v) + { + MyBase::mm[ 0] *= v.x(); + MyBase::mm[ 1] *= v.x(); + MyBase::mm[ 2] *= v.x(); + MyBase::mm[ 3] *= v.x(); + + MyBase::mm[ 4] *= v.y(); + MyBase::mm[ 5] *= v.y(); + MyBase::mm[ 6] *= v.y(); + MyBase::mm[ 7] *= v.y(); + + MyBase::mm[ 8] *= v.z(); + MyBase::mm[ 9] *= v.z(); + MyBase::mm[10] *= v.z(); + MyBase::mm[11] *= v.z(); + } + + + + // Right multiples by the specified scale matrix, i.e. (*this) * Sc + template + void postScale(const Vec3& v) + { + + MyBase::mm[ 0] *= v.x(); + MyBase::mm[ 1] *= v.y(); + MyBase::mm[ 2] *= v.z(); + + MyBase::mm[ 4] *= v.x(); + MyBase::mm[ 5] *= v.y(); + MyBase::mm[ 6] *= v.z(); + + MyBase::mm[ 8] *= v.x(); + MyBase::mm[ 9] *= v.y(); + MyBase::mm[10] *= v.z(); + + MyBase::mm[12] *= v.x(); + MyBase::mm[13] *= v.y(); + MyBase::mm[14] *= v.z(); + + } + + + /// @brief Sets the matrix to a rotation about the given axis. + /// @param axis The axis (one of X, Y, Z) to rotate about. + /// @param angle The rotation angle, in radians. + void setToRotation(Axis axis, T angle) {*this = rotation >(axis, angle);} + + /// @brief Sets the matrix to a rotation about an arbitrary axis + /// @param axis The axis of rotation (cannot be zero-length) + /// @param angle The rotation angle, in radians. + void setToRotation(const Vec3& axis, T angle) {*this = rotation >(axis, angle);} + + /// @brief Sets the matrix to a rotation that maps v1 onto v2 about the cross + /// product of v1 and v2. + void setToRotation(const Vec3& v1, const Vec3& v2) {*this = rotation >(v1, v2);} + + + /// @brief Left multiplies by a rotation clock-wiseabout the given axis into this matrix. + /// @param axis The axis (one of X, Y, Z) of rotation. + /// @param angle The clock-wise rotation angle, in radians. + void preRotate(Axis axis, T angle) + { + T c = static_cast(cos(angle)); + T s = -static_cast(sin(angle)); // the "-" makes it clockwise + + switch (axis) { + case X_AXIS: + { + T a4, a5, a6, a7; + + a4 = c * MyBase::mm[ 4] - s * MyBase::mm[ 8]; + a5 = c * MyBase::mm[ 5] - s * MyBase::mm[ 9]; + a6 = c * MyBase::mm[ 6] - s * MyBase::mm[10]; + a7 = c * MyBase::mm[ 7] - s * MyBase::mm[11]; + + + MyBase::mm[ 8] = s * MyBase::mm[ 4] + c * MyBase::mm[ 8]; + MyBase::mm[ 9] = s * MyBase::mm[ 5] + c * MyBase::mm[ 9]; + MyBase::mm[10] = s * MyBase::mm[ 6] + c * MyBase::mm[10]; + MyBase::mm[11] = s * MyBase::mm[ 7] + c * MyBase::mm[11]; + + MyBase::mm[ 4] = a4; + MyBase::mm[ 5] = a5; + MyBase::mm[ 6] = a6; + MyBase::mm[ 7] = a7; + } + break; + + case Y_AXIS: + { + T a0, a1, a2, a3; + + a0 = c * MyBase::mm[ 0] + s * MyBase::mm[ 8]; + a1 = c * MyBase::mm[ 1] + s * MyBase::mm[ 9]; + a2 = c * MyBase::mm[ 2] + s * MyBase::mm[10]; + a3 = c * MyBase::mm[ 3] + s * MyBase::mm[11]; + + MyBase::mm[ 8] = -s * MyBase::mm[ 0] + c * MyBase::mm[ 8]; + MyBase::mm[ 9] = -s * MyBase::mm[ 1] + c * MyBase::mm[ 9]; + MyBase::mm[10] = -s * MyBase::mm[ 2] + c * MyBase::mm[10]; + MyBase::mm[11] = -s * MyBase::mm[ 3] + c * MyBase::mm[11]; + + + MyBase::mm[ 0] = a0; + MyBase::mm[ 1] = a1; + MyBase::mm[ 2] = a2; + MyBase::mm[ 3] = a3; + } + break; + + case Z_AXIS: + { + T a0, a1, a2, a3; + + a0 = c * MyBase::mm[ 0] - s * MyBase::mm[ 4]; + a1 = c * MyBase::mm[ 1] - s * MyBase::mm[ 5]; + a2 = c * MyBase::mm[ 2] - s * MyBase::mm[ 6]; + a3 = c * MyBase::mm[ 3] - s * MyBase::mm[ 7]; + + MyBase::mm[ 4] = s * MyBase::mm[ 0] + c * MyBase::mm[ 4]; + MyBase::mm[ 5] = s * MyBase::mm[ 1] + c * MyBase::mm[ 5]; + MyBase::mm[ 6] = s * MyBase::mm[ 2] + c * MyBase::mm[ 6]; + MyBase::mm[ 7] = s * MyBase::mm[ 3] + c * MyBase::mm[ 7]; + + MyBase::mm[ 0] = a0; + MyBase::mm[ 1] = a1; + MyBase::mm[ 2] = a2; + MyBase::mm[ 3] = a3; + } + break; + + default: + assert(axis==X_AXIS || axis==Y_AXIS || axis==Z_AXIS); + } + } + + + /// @brief Right multiplies by a rotation clock-wiseabout the given axis into this matrix. + /// @param axis The axis (one of X, Y, Z) of rotation. + /// @param angle The clock-wise rotation angle, in radians. + void postRotate(Axis axis, T angle) + { + T c = static_cast(cos(angle)); + T s = -static_cast(sin(angle)); // the "-" makes it clockwise + + + + switch (axis) { + case X_AXIS: + { + T a2, a6, a10, a14; + + a2 = c * MyBase::mm[ 2] - s * MyBase::mm[ 1]; + a6 = c * MyBase::mm[ 6] - s * MyBase::mm[ 5]; + a10 = c * MyBase::mm[10] - s * MyBase::mm[ 9]; + a14 = c * MyBase::mm[14] - s * MyBase::mm[13]; + + + MyBase::mm[ 1] = c * MyBase::mm[ 1] + s * MyBase::mm[ 2]; + MyBase::mm[ 5] = c * MyBase::mm[ 5] + s * MyBase::mm[ 6]; + MyBase::mm[ 9] = c * MyBase::mm[ 9] + s * MyBase::mm[10]; + MyBase::mm[13] = c * MyBase::mm[13] + s * MyBase::mm[14]; + + MyBase::mm[ 2] = a2; + MyBase::mm[ 6] = a6; + MyBase::mm[10] = a10; + MyBase::mm[14] = a14; + } + break; + + case Y_AXIS: + { + T a2, a6, a10, a14; + + a2 = c * MyBase::mm[ 2] + s * MyBase::mm[ 0]; + a6 = c * MyBase::mm[ 6] + s * MyBase::mm[ 4]; + a10 = c * MyBase::mm[10] + s * MyBase::mm[ 8]; + a14 = c * MyBase::mm[14] + s * MyBase::mm[12]; + + MyBase::mm[ 0] = c * MyBase::mm[ 0] - s * MyBase::mm[ 2]; + MyBase::mm[ 4] = c * MyBase::mm[ 4] - s * MyBase::mm[ 6]; + MyBase::mm[ 8] = c * MyBase::mm[ 8] - s * MyBase::mm[10]; + MyBase::mm[12] = c * MyBase::mm[12] - s * MyBase::mm[14]; + + MyBase::mm[ 2] = a2; + MyBase::mm[ 6] = a6; + MyBase::mm[10] = a10; + MyBase::mm[14] = a14; + } + break; + + case Z_AXIS: + { + T a1, a5, a9, a13; + + a1 = c * MyBase::mm[ 1] - s * MyBase::mm[ 0]; + a5 = c * MyBase::mm[ 5] - s * MyBase::mm[ 4]; + a9 = c * MyBase::mm[ 9] - s * MyBase::mm[ 8]; + a13 = c * MyBase::mm[13] - s * MyBase::mm[12]; + + MyBase::mm[ 0] = c * MyBase::mm[ 0] + s * MyBase::mm[ 1]; + MyBase::mm[ 4] = c * MyBase::mm[ 4] + s * MyBase::mm[ 5]; + MyBase::mm[ 8] = c * MyBase::mm[ 8] + s * MyBase::mm[ 9]; + MyBase::mm[12] = c * MyBase::mm[12] + s * MyBase::mm[13]; + + MyBase::mm[ 1] = a1; + MyBase::mm[ 5] = a5; + MyBase::mm[ 9] = a9; + MyBase::mm[13] = a13; + + } + break; + + default: + assert(axis==X_AXIS || axis==Y_AXIS || axis==Z_AXIS); + } + } + + /// @brief Sets the matrix to a shear along axis0 by a fraction of axis1. + /// @param axis0 The fixed axis of the shear. + /// @param axis1 The shear axis. + /// @param shearby The shear factor. + void setToShear(Axis axis0, Axis axis1, T shearby) + { + *this = shear >(axis0, axis1, shearby); + } + + + /// @brief Left multiplies a shearing transformation into the matrix. + /// @see setToShear + void preShear(Axis axis0, Axis axis1, T shear) + { + int index0 = static_cast(axis0); + int index1 = static_cast(axis1); + + // to row "index1" add a multiple of the index0 row + MyBase::mm[index1 * 4 + 0] += shear * MyBase::mm[index0 * 4 + 0]; + MyBase::mm[index1 * 4 + 1] += shear * MyBase::mm[index0 * 4 + 1]; + MyBase::mm[index1 * 4 + 2] += shear * MyBase::mm[index0 * 4 + 2]; + MyBase::mm[index1 * 4 + 3] += shear * MyBase::mm[index0 * 4 + 3]; + } + + + /// @brief Right multiplies a shearing transformation into the matrix. + /// @see setToShear + void postShear(Axis axis0, Axis axis1, T shear) + { + int index0 = static_cast(axis0); + int index1 = static_cast(axis1); + + // to collumn "index0" add a multiple of the index1 row + MyBase::mm[index0 + 0] += shear * MyBase::mm[index1 + 0]; + MyBase::mm[index0 + 4] += shear * MyBase::mm[index1 + 4]; + MyBase::mm[index0 + 8] += shear * MyBase::mm[index1 + 8]; + MyBase::mm[index0 + 12] += shear * MyBase::mm[index1 + 12]; + + } + + /// Transform a Vec4 by post-multiplication. + template + Vec4 transform(const Vec4 &v) const + { + return static_cast< Vec4 >(v * *this); + } + + /// Transform a Vec3 by post-multiplication, without homogenous division. + template + Vec3 transform(const Vec3 &v) const + { + return static_cast< Vec3 >(v * *this); + } + + /// Transform a Vec4 by pre-multiplication. + template + Vec4 pretransform(const Vec4 &v) const + { + return static_cast< Vec4 >(*this * v); + } + + /// Transform a Vec3 by pre-multiplication, without homogenous division. + template + Vec3 pretransform(const Vec3 &v) const + { + return static_cast< Vec3 >(*this * v); + } + + /// Transform a Vec3 by post-multiplication, doing homogenous divison. + template + Vec3 transformH(const Vec3 &p) const + { + T0 w; + + // w = p * (*this).col(3); + w = p[0] * MyBase::mm[ 3] + p[1] * MyBase::mm[ 7] + p[2] * MyBase::mm[11] + MyBase::mm[15]; + + if ( !isExactlyEqual(w , 0.0) ) { + return Vec3(static_cast((p[0] * MyBase::mm[ 0] + p[1] * MyBase::mm[ 4] + + p[2] * MyBase::mm[ 8] + MyBase::mm[12]) / w), + static_cast((p[0] * MyBase::mm[ 1] + p[1] * MyBase::mm[ 5] + + p[2] * MyBase::mm[ 9] + MyBase::mm[13]) / w), + static_cast((p[0] * MyBase::mm[ 2] + p[1] * MyBase::mm[ 6] + + p[2] * MyBase::mm[10] + MyBase::mm[14]) / w)); + } + + return Vec3(0, 0, 0); + } + + /// Transform a Vec3 by pre-multiplication, doing homogenous division. + template + Vec3 pretransformH(const Vec3 &p) const + { + T0 w; + + // w = p * (*this).col(3); + w = p[0] * MyBase::mm[12] + p[1] * MyBase::mm[13] + p[2] * MyBase::mm[14] + MyBase::mm[15]; + + if ( !isExactlyEqual(w , 0.0) ) { + return Vec3(static_cast((p[0] * MyBase::mm[ 0] + p[1] * MyBase::mm[ 1] + + p[2] * MyBase::mm[ 2] + MyBase::mm[ 3]) / w), + static_cast((p[0] * MyBase::mm[ 4] + p[1] * MyBase::mm[ 5] + + p[2] * MyBase::mm[ 6] + MyBase::mm[ 7]) / w), + static_cast((p[0] * MyBase::mm[ 8] + p[1] * MyBase::mm[ 9] + + p[2] * MyBase::mm[10] + MyBase::mm[11]) / w)); + } + + return Vec3(0, 0, 0); + } + + /// Transform a Vec3 by post-multiplication, without translation. + template + Vec3 transform3x3(const Vec3 &v) const + { + return Vec3( + static_cast(v[0] * MyBase::mm[ 0] + v[1] * MyBase::mm[ 4] + v[2] * MyBase::mm[ 8]), + static_cast(v[0] * MyBase::mm[ 1] + v[1] * MyBase::mm[ 5] + v[2] * MyBase::mm[ 9]), + static_cast(v[0] * MyBase::mm[ 2] + v[1] * MyBase::mm[ 6] + v[2] * MyBase::mm[10])); + } + + +private: + bool invert(Mat4 &inverse, T tolerance) const; + + T det2(const Mat4 &a, int i0, int i1, int j0, int j1) const { + int i0row = i0 * 4; + int i1row = i1 * 4; + return a.mm[i0row+j0]*a.mm[i1row+j1] - a.mm[i0row+j1]*a.mm[i1row+j0]; + } + + T det3(const Mat4 &a, int i0, int i1, int i2, + int j0, int j1, int j2) const { + int i0row = i0 * 4; + return a.mm[i0row+j0]*det2(a, i1,i2, j1,j2) + + a.mm[i0row+j1]*det2(a, i1,i2, j2,j0) + + a.mm[i0row+j2]*det2(a, i1,i2, j0,j1); + } + + static const Mat4 sIdentity; + static const Mat4 sZero; +}; // class Mat4 + + +template +const Mat4 Mat4::sIdentity = Mat4(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + +template +const Mat4 Mat4::sZero = Mat4(0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0); + +/// @relates Mat4 +/// @brief Equality operator, does exact floating point comparisons +template +bool operator==(const Mat4 &m0, const Mat4 &m1) +{ + const T0 *t0 = m0.asPointer(); + const T1 *t1 = m1.asPointer(); + + for (int i=0; i<16; ++i) if (!isExactlyEqual(t0[i], t1[i])) return false; + return true; +} + +/// @relates Mat4 +/// @brief Inequality operator, does exact floating point comparisons +template +bool operator!=(const Mat4 &m0, const Mat4 &m1) { return !(m0 == m1); } + +/// @relates Mat4 +/// @brief Returns M, where \f$M_{i,j} = m_{i,j} * scalar\f$ for \f$i, j \in [0, 3]\f$ +template +Mat4::type> operator*(S scalar, const Mat4 &m) +{ + return m*scalar; +} + +/// @relates Mat4 +/// @brief Returns M, where \f$M_{i,j} = m_{i,j} * scalar\f$ for \f$i, j \in [0, 3]\f$ +template +Mat4::type> operator*(const Mat4 &m, S scalar) +{ + Mat4::type> result(m); + result *= scalar; + return result; +} + +/// @relates Mat4 +/// @brief Returns v, where \f$v_{i} = \sum_{n=0}^3 m_{i,n} * v_n \f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> +operator*(const Mat4 &_m, + const Vec4 &_v) +{ + MT const *m = _m.asPointer(); + return Vec4::type>( + _v[0]*m[0] + _v[1]*m[1] + _v[2]*m[2] + _v[3]*m[3], + _v[0]*m[4] + _v[1]*m[5] + _v[2]*m[6] + _v[3]*m[7], + _v[0]*m[8] + _v[1]*m[9] + _v[2]*m[10] + _v[3]*m[11], + _v[0]*m[12] + _v[1]*m[13] + _v[2]*m[14] + _v[3]*m[15]); +} + +/// @relates Mat4 +/// @brief Returns v, where \f$v_{i} = \sum_{n=0}^3 m_{n,i} * v_n \f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> +operator*(const Vec4 &_v, + const Mat4 &_m) +{ + MT const *m = _m.asPointer(); + return Vec4::type>( + _v[0]*m[0] + _v[1]*m[4] + _v[2]*m[8] + _v[3]*m[12], + _v[0]*m[1] + _v[1]*m[5] + _v[2]*m[9] + _v[3]*m[13], + _v[0]*m[2] + _v[1]*m[6] + _v[2]*m[10] + _v[3]*m[14], + _v[0]*m[3] + _v[1]*m[7] + _v[2]*m[11] + _v[3]*m[15]); +} + +/// @relates Mat4 +/// @brief Returns v, where +/// \f$v_{i} = \sum_{n=0}^3\left(m_{i,n} * v_n + m_{i,3}\right)\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> +operator*(const Mat4 &_m, + const Vec3 &_v) +{ + MT const *m = _m.asPointer(); + return Vec3::type>( + _v[0]*m[0] + _v[1]*m[1] + _v[2]*m[2] + m[3], + _v[0]*m[4] + _v[1]*m[5] + _v[2]*m[6] + m[7], + _v[0]*m[8] + _v[1]*m[9] + _v[2]*m[10] + m[11]); +} + +/// @relates Mat4 +/// @brief Returns v, where +/// \f$v_{i} = \sum_{n=0}^3\left(m_{n,i} * v_n + m_{3,i}\right)\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> +operator*(const Vec3 &_v, + const Mat4 &_m) +{ + MT const *m = _m.asPointer(); + return Vec3::type>( + _v[0]*m[0] + _v[1]*m[4] + _v[2]*m[8] + m[12], + _v[0]*m[1] + _v[1]*m[5] + _v[2]*m[9] + m[13], + _v[0]*m[2] + _v[1]*m[6] + _v[2]*m[10] + m[14]); +} + +/// @relates Mat4 +/// @brief Returns M, where \f$M_{i,j} = m0_{i,j} + m1_{i,j}\f$ for \f$i, j \in [0, 3]\f$ +template +Mat4::type> +operator+(const Mat4 &m0, const Mat4 &m1) +{ + Mat4::type> result(m0); + result += m1; + return result; +} + +/// @relates Mat4 +/// @brief Returns M, where \f$M_{i,j} = m0_{i,j} - m1_{i,j}\f$ for \f$i, j \in [0, 3]\f$ +template +Mat4::type> +operator-(const Mat4 &m0, const Mat4 &m1) +{ + Mat4::type> result(m0); + result -= m1; + return result; +} + +/// @relates Mat4 +/// @brief Returns M, where +/// \f$M_{ij} = \sum_{n=0}^3\left(m0_{nj} + m1_{in}\right)\f$ for \f$i, j \in [0, 3]\f$ +template +Mat4::type> +operator*(const Mat4 &m0, const Mat4 &m1) +{ + Mat4::type> result(m0); + result *= m1; + return result; +} + + +/// Transform a Vec3 by pre-multiplication, without translation. +/// Presumes this matrix is inverse of coordinate transform +/// Synonymous to "pretransform3x3" +template +Vec3 transformNormal(const Mat4 &m, const Vec3 &n) +{ + return Vec3( + static_cast(m[0][0]*n[0] + m[0][1]*n[1] + m[0][2]*n[2]), + static_cast(m[1][0]*n[0] + m[1][1]*n[1] + m[1][2]*n[2]), + static_cast(m[2][0]*n[0] + m[2][1]*n[1] + m[2][2]*n[2])); +} + + +/// Invert via gauss-jordan elimination. Modified from dreamworks internal mx library +template +bool Mat4::invert(Mat4 &inverse, T tolerance) const +{ + Mat4 temp(*this); + inverse.setIdentity(); + + // Forward elimination step + double det = 1.0; + for (int i = 0; i < 4; ++i) { + int row = i; + double max = fabs(temp[i][i]); + + for (int k = i+1; k < 4; ++k) { + if (fabs(temp[k][i]) > max) { + row = k; + max = fabs(temp[k][i]); + } + } + + if (isExactlyEqual(max, 0.0)) return false; + + // must move pivot to row i + if (row != i) { + det = -det; + for (int k = 0; k < 4; ++k) { + std::swap(temp[row][k], temp[i][k]); + std::swap(inverse[row][k], inverse[i][k]); + } + } + + double pivot = temp[i][i]; + det *= pivot; + + // scale row i + for (int k = 0; k < 4; ++k) { + temp[i][k] /= pivot; + inverse[i][k] /= pivot; + } + + // eliminate in rows below i + for (int j = i+1; j < 4; ++j) { + double t = temp[j][i]; + if (!isExactlyEqual(t, 0.0)) { + // subtract scaled row i from row j + for (int k = 0; k < 4; ++k) { + temp[j][k] -= temp[i][k] * t; + inverse[j][k] -= inverse[i][k] * t; + } + } + } + } + + // Backward elimination step + for (int i = 3; i > 0; --i) { + for (int j = 0; j < i; ++j) { + double t = temp[j][i]; + + if (!isExactlyEqual(t, 0.0)) { + for (int k = 0; k < 4; ++k) { + inverse[j][k] -= inverse[i][k]*t; + } + } + } + } + return det*det >= tolerance*tolerance; +} + +template +inline bool isAffine(const Mat4& m) { + return (m.col(3) == Vec4(0, 0, 0, 1)); +} + +template +inline bool hasTranslation(const Mat4& m) { + return (m.row(3) != Vec4(0, 0, 0, 1)); +} + + +typedef Mat4 Mat4s; +typedef Mat4 Mat4d; + +#if DWREAL_IS_DOUBLE == 1 +typedef Mat4d Mat4f; +#else +typedef Mat4s Mat4f; +#endif // DWREAL_IS_DOUBLE + +} // namespace math + + +template<> inline math::Mat4s zeroVal() { return math::Mat4s::identity(); } +template<> inline math::Mat4d zeroVal() { return math::Mat4d::identity(); } + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_UTIL_MAT4_H_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Math.h b/openvdb_2_3_0_library/openvdb/math/Math.h new file mode 100755 index 0000000..dd5b5d2 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Math.h @@ -0,0 +1,835 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file Math.h +/// @brief General-purpose arithmetic and comparison routines, most of which +/// accept arbitrary value types (or at least arbitrary numeric value types) + +#ifndef OPENVDB_MATH_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_HAS_BEEN_INCLUDED + +#include +#include // for std::max() +#include // for floor(), ceil() and sqrt() +#include // for pow(), fabs() etc +#include // for srand(), abs(int) +#include // for std::numeric_limits::max() +#include +#include +#include +#include // for boost::random::mt19937 +#include +#include +#include // for BOOST_VERSION +#include +#include + + +// Compile pragmas + +// Intel(r) compiler fires remark #1572: floating-point equality and inequality +// comparisons are unrealiable when == or != is used with floating point operands. +#if defined(__INTEL_COMPILER) + #define OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN \ + _Pragma("warning (push)") \ + _Pragma("warning (disable:1572)") + #define OPENVDB_NO_FP_EQUALITY_WARNING_END \ + _Pragma("warning (pop)") +#else + // For GCC, #pragma GCC diagnostic ignored "-Wfloat-equal" + // isn't working until gcc 4.2+, + // Trying + // #pragma GCC system_header + // creates other problems, most notably "warning: will never be executed" + // in from templates, unsure of how to work around. + // If necessary, could use integer based comparisons for equality + #define OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN + #define OPENVDB_NO_FP_EQUALITY_WARNING_END +#endif + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +/// @brief Return the value of type T that corresponds to zero. +/// @note A zeroVal() specialization must be defined for each @c ValueType T +/// that cannot be constructed using the form @c T(0). For example, @c std::string(0) +/// treats 0 as @c NULL and throws a @c std::logic_error. +template inline T zeroVal() { return T(0); } +/// Return the @c std::string value that corresponds to zero. +template<> inline std::string zeroVal() { return ""; } +/// Return the @c bool value that corresponds to zero. +template<> inline bool zeroVal() { return false; } + +/// @todo These won't be needed if we eliminate StringGrids. +//@{ +/// @brief Needed to support the (zeroVal() + val) idiom +/// when @c ValueType is @c std::string +inline std::string operator+(const std::string& s, bool) { return s; } +inline std::string operator+(const std::string& s, int) { return s; } +inline std::string operator+(const std::string& s, float) { return s; } +inline std::string operator+(const std::string& s, double) { return s; } +//@} + + +namespace math { + +/// @brief Return the unary negation of the given value. +/// @note A negative() specialization must be defined for each ValueType T +/// for which unary negation is not defined. +template inline T negative(const T& val) { return T(-val); } +/// Return the negation of the given boolean. +template<> inline bool negative(const bool& val) { return !val; } +/// Return the "negation" of the given string. +template<> inline std::string negative(const std::string& val) { return val; } + + +//@{ +/// Tolerance for floating-point comparison +template struct Tolerance { static T value() { return zeroVal(); } }; +template<> struct Tolerance { static float value() { return 1e-8f; } }; +template<> struct Tolerance { static double value() { return 1e-15; } }; +//@} + +//@{ +/// Delta for small floating-point offsets +template struct Delta { static T value() { return zeroVal(); } }; +template<> struct Delta { static float value() { return 1e-5f; } }; +template<> struct Delta { static double value() { return 1e-9; } }; +//@} + + +// ==========> Random Values <================== + +/// @brief Simple generator of random numbers over the range [0, 1) +/// @details Thread-safe as long as each thread has its own Rand01 instance +template +class Rand01 +{ +private: + EngineType mEngine; + boost::uniform_01 mRand; + +public: + typedef FloatType ValueType; + + /// @brief Initialize the generator. + /// @param seed seed value for the random number generator + Rand01(unsigned int seed): mEngine(static_cast(seed)) {} + + /// Return a uniformly distributed random number in the range [0, 1). + FloatType operator()() { return mRand(mEngine); } +}; + +typedef Rand01 Random01; + + +/// @brief Simple random integer generator +/// @details Thread-safe as long as each thread has its own RandInt instance +template +class RandInt +{ +private: +#if BOOST_VERSION >= 104700 + typedef boost::random::uniform_int_distribution Distr; +#else + typedef boost::uniform_int Distr; +#endif + EngineType mEngine; + Distr mRand; + +public: + /// @brief Initialize the generator. + /// @param seed seed value for the random number generator + /// @param imin,imax generate integers that are uniformly distributed over [imin, imax] + RandInt(unsigned int seed, int imin, int imax): + mEngine(static_cast(seed)), + mRand(std::min(imin, imax), std::max(imin, imax)) + {} + + /// Change the range over which integers are distributed to [imin, imax]. + void setRange(int imin, int imax) { mRand = Distr(std::min(imin, imax), std::max(imin, imax)); } + + /// Return a randomly-generated integer in the current range. + int operator()() { return mRand(mEngine); } + /// @brief Return a randomly-generated integer in the new range [imin, imax], + /// without changing the current range. + int operator()(int imin, int imax) + { + const int lo = std::min(imin, imax), hi = std::max(imin, imax); +#if BOOST_VERSION >= 104700 + return mRand(mEngine, Distr::param_type(lo, hi)); +#else + return Distr(lo, hi)(mEngine); +#endif + } +}; + +typedef RandInt RandomInt; + + +// ==========> Clamp <================== + +/// Return @a x clamped to [@a min, @a max] +template +inline Type +Clamp(Type x, Type min, Type max) +{ + assert(min min ? x < max ? x : max : min; +} + + +/// Return @a x clamped to [0, 1] +template +inline Type +Clamp01(Type x) { return x > Type(0) ? x < Type(1) ? x : Type(1) : Type(0); } + + +/// Return @c true if @a x is outside [0,1] +template +inline bool +ClampTest01(Type &x) +{ + if (x >= Type(0) && x <= Type(1)) return false; + x = x < Type(0) ? Type(0) : Type(1); + return true; +} + + +/// @brief Return 0 if @a x < @a min, 1 if @a x > @a max or else @f$(3-2t)t^2@f$, +/// where @f$t = (x-min)/(max-min)@f$. +template +inline Type +SmoothUnitStep(Type x, Type min, Type max) +{ + assert(min < max); + const Type t = (x-min)/(max-min); + return t > 0 ? t < 1 ? (3-2*t)*t*t : Type(1) : Type(0); +} + + +// ==========> Absolute Value <================== + + +//@{ +/// Return the absolute value of the given quantity. +inline int32_t Abs(int32_t i) { return abs(i); } +inline int64_t Abs(int64_t i) +{ +#ifdef _MSC_VER + return (i < int64_t(0) ? -i : i); +#else + return labs(i); +#endif +} +inline float Abs(float x) { return fabs(x); } +inline double Abs(double x) { return fabs(x); } +inline long double Abs(long double x) { return fabsl(x); } +inline uint32_t Abs(uint32_t i) { return i; } +inline uint64_t Abs(uint64_t i) { return i; } +// On OSX size_t and uint64_t are different types +#if defined(__APPLE__) || defined(MACOSX) +inline size_t Abs(size_t i) { return i; } +#endif +//@} + + +//////////////////////////////////////// + + +// ==========> Value Comparison <================== + + +/// Return @c true if @a x is exactly equal to zero. +template +inline bool +isZero(const Type& x) +{ + OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN + return x == zeroVal(); + OPENVDB_NO_FP_EQUALITY_WARNING_END +} + + +/// @brief Return @c true if @a x is equal to zero to within +/// the default floating-point comparison tolerance. +template +inline bool +isApproxZero(const Type& x) +{ + const Type tolerance = Type(zeroVal() + Tolerance::value()); + return x < tolerance && x > -tolerance; +} + +/// Return @c true if @a x is equal to zero to within the given tolerance. +template +inline bool +isApproxZero(const Type& x, const Type& tolerance) +{ + return x < tolerance && x > -tolerance; +} + + +/// Return @c true if @a x is less than zero. +template +inline bool +isNegative(const Type& x) { return x < zeroVal(); } + +/// Return @c false, since @c bool values are never less than zero. +template<> inline bool isNegative(const bool&) { return false; } + + +/// @brief Return @c true if @a a is equal to @a b to within +/// the default floating-point comparison tolerance. +template +inline bool +isApproxEqual(const Type& a, const Type& b) +{ + const Type tolerance = Type(zeroVal() + Tolerance::value()); + return !(Abs(a - b) > tolerance); +} + + +/// Return @c true if @a a is equal to @a b to within the given tolerance. +template +inline bool +isApproxEqual(const Type& a, const Type& b, const Type& tolerance) +{ + return !(Abs(a - b) > tolerance); +} + +#define OPENVDB_EXACT_IS_APPROX_EQUAL(T) \ + template<> inline bool isApproxEqual(const T& a, const T& b) { return a == b; } \ + template<> inline bool isApproxEqual(const T& a, const T& b, const T&) { return a == b; } \ + /**/ + +OPENVDB_EXACT_IS_APPROX_EQUAL(bool) +OPENVDB_EXACT_IS_APPROX_EQUAL(std::string) + + +/// @brief Return @c true if @a a is larger than @a b to within +/// the given tolerance, i.e., if @a b - @a a < @a tolerance. +template +inline bool +isApproxLarger(const Type& a, const Type& b, const Type& tolerance) +{ + return (b - a < tolerance); +} + + +/// @brief Return @c true if @a a is exactly equal to @a b. +template +inline bool +isExactlyEqual(const T0& a, const T1& b) +{ + OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN + return a == b; + OPENVDB_NO_FP_EQUALITY_WARNING_END +} + + +template +inline bool +isRelOrApproxEqual(const Type& a, const Type& b, const Type& absTol, const Type& relTol) +{ + // First check to see if we are inside the absolute tolerance + // Necessary for numbers close to 0 + if (!(Abs(a - b) > absTol)) return true; + + // Next check to see if we are inside the relative tolerance + // to handle large numbers that aren't within the abs tolerance + // but could be the closest floating point representation + double relError; + if (Abs(b) > Abs(a)) { + relError = Abs((a - b) / b); + } else { + relError = Abs((a - b) / a); + } + return (relError <= relTol); +} + +template<> +inline bool +isRelOrApproxEqual(const bool& a, const bool& b, const bool&, const bool&) +{ + return (a == b); +} + + +// Avoid strict aliasing issues by using type punning +// http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html +// Using "casting through a union(2)" +inline int32_t +floatToInt32(const float aFloatValue) +{ + union FloatOrInt32 { float floatValue; int32_t int32Value; }; + const FloatOrInt32* foi = reinterpret_cast(&aFloatValue); + return foi->int32Value; +} + + +inline int64_t +doubleToInt64(const double aDoubleValue) +{ + union DoubleOrInt64 { double doubleValue; int64_t int64Value; }; + const DoubleOrInt64* dol = reinterpret_cast(&aDoubleValue); + return dol->int64Value; +} + + +// aUnitsInLastPlace is the allowed difference between the least significant digits +// of the numbers' floating point representation +// Please read refernce paper before trying to use isUlpsEqual +// http://www.cygnus-software.com/papers/comparingFloats/comparingFloats.htm +inline bool +isUlpsEqual(const double aLeft, const double aRight, const int64_t aUnitsInLastPlace) +{ + int64_t longLeft = doubleToInt64(aLeft); + // Because of 2's complement, must restore lexicographical order + if (longLeft < 0) { + longLeft = INT64_C(0x8000000000000000) - longLeft; + } + + int64_t longRight = doubleToInt64(aRight); + // Because of 2's complement, must restore lexicographical order + if (longRight < 0) { + longRight = INT64_C(0x8000000000000000) - longRight; + } + + int64_t difference = labs(longLeft - longRight); + return (difference <= aUnitsInLastPlace); +} + +inline bool +isUlpsEqual(const float aLeft, const float aRight, const int32_t aUnitsInLastPlace) +{ + int32_t intLeft = floatToInt32(aLeft); + // Because of 2's complement, must restore lexicographical order + if (intLeft < 0) { + intLeft = 0x80000000 - intLeft; + } + + int32_t intRight = floatToInt32(aRight); + // Because of 2's complement, must restore lexicographical order + if (intRight < 0) { + intRight = 0x80000000 - intRight; + } + + int32_t difference = abs(intLeft - intRight); + return (difference <= aUnitsInLastPlace); +} + + +//////////////////////////////////////// + + +// ==========> Pow <================== + +/// Return @f$ x^2 @f$. +template +inline Type Pow2(Type x) { return x*x; } + +/// Return @f$ x^3 @f$. +template +inline Type Pow3(Type x) { return x*x*x; } + +/// Return @f$ x^4 @f$. +template +inline Type Pow4(Type x) { return Pow2(Pow2(x)); } + +/// Return @f$ x^n @f$. +template +Type +Pow(Type x, int n) +{ + Type ans = 1; + if (n < 0) { + n = -n; + x = Type(1)/x; + } + while (n--) ans *= x; + return ans; +} + +//@{ +/// Return @f$ b^e @f$. +inline float +Pow(float b, float e) +{ + assert( b >= 0.0f && "Pow(float,float): base is negative" ); + return powf(b,e); +} + +inline double +Pow(double b, double e) +{ + assert( b >= 0.0 && "Pow(double,double): base is negative" ); + return pow(b,e); +} +//@} + + +// ==========> Max <================== + +/// Return the maximum of two values +template +inline const Type& +Max(const Type& a, const Type& b) +{ + return std::max(a,b) ; +} + +/// Return the maximum of three values +template +inline const Type& +Max(const Type& a, const Type& b, const Type& c) +{ + return std::max( std::max(a,b), c ) ; +} + +/// Return the maximum of four values +template +inline const Type& +Max(const Type& a, const Type& b, const Type& c, const Type& d) +{ + return std::max(std::max(a,b), std::max(c,d)); +} + +/// Return the maximum of five values +template +inline const Type& +Max(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e) +{ + return std::max(std::max(a,b), Max(c,d,e)); +} + +/// Return the maximum of six values +template +inline const Type& +Max(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e, const Type& f) +{ + return std::max(Max(a,b,c), Max(d,e,f)); +} + +/// Return the maximum of seven values +template +inline const Type& +Max(const Type& a, const Type& b, const Type& c, const Type& d, + const Type& e, const Type& f, const Type& g) +{ + return std::max(Max(a,b,c,d), Max(e,f,g)); +} + +/// Return the maximum of eight values +template +inline const Type& +Max(const Type& a, const Type& b, const Type& c, const Type& d, + const Type& e, const Type& f, const Type& g, const Type& h) +{ + return std::max(Max(a,b,c,d), Max(e,f,g,h)); +} + + +// ==========> Min <================== + +/// Return the minimum of two values +template +inline const Type& +Min(const Type& a, const Type& b) { return std::min(a, b); } + +/// Return the minimum of three values +template +inline const Type& +Min(const Type& a, const Type& b, const Type& c) { return std::min(std::min(a, b), c); } + +/// Return the minimum of four values +template +inline const Type& +Min(const Type& a, const Type& b, const Type& c, const Type& d) +{ + return std::min(std::min(a, b), std::min(c, d)); +} + +/// Return the minimum of five values +template +inline const Type& +Min(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e) +{ + return std::min(std::min(a,b), Min(c,d,e)); +} + +/// Return the minimum of six values +template +inline const Type& +Min(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e, const Type& f) +{ + return std::min(Min(a,b,c), Min(d,e,f)); +} + +/// Return the minimum of seven values +template +inline const Type& +Min(const Type& a, const Type& b, const Type& c, const Type& d, + const Type& e, const Type& f, const Type& g) +{ + return std::min(Min(a,b,c,d), Min(e,f,g)); +} + +/// Return the minimum of eight values +template +inline const Type& +Min(const Type& a, const Type& b, const Type& c, const Type& d, + const Type& e, const Type& f, const Type& g, const Type& h) +{ + return std::min(Min(a,b,c,d), Min(e,f,g,h)); +} + + +// ============> Exp <================== + +/// Return @f$ e^x @f$. +template +inline Type Exp(const Type& x) { return std::exp(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 sqrtf(x); } +inline double Sqrt(double x) { return sqrt(x); } +inline long double Sqrt(long double x) { return sqrtl(x); } +//@} + + +//@{ +/// Return the cube root of a floating-point value. +inline float Cbrt(float x) { return boost::math::cbrt(x); } +inline double Cbrt(double x) { return boost::math::cbrt(x); } +inline long double Cbrt(long double x) { return boost::math::cbrt(x); } +//@} + + +//@{ +/// Return the remainder of @a x / @a y. +inline int Mod(int x, int y) { return (x % y); } +inline float Mod(float x, float y) { return fmodf(x,y); } +inline double Mod(double x, double y) { return fmod(x,y); } +inline long double Mod(long double x, long double y) { return fmodl(x,y); } +template inline Type Remainder(Type x, Type y) { return Mod(x,y); } +//@} + + +//@{ +/// Return @a x rounded up to the nearest integer. +inline float RoundUp(float x) { return ceilf(x); } +inline double RoundUp(double x) { return ceil(x); } +inline long double RoundUp(long double x) { return ceill(x); } +//@} +/// Return @a x rounded up to the nearest multiple of @a base. +template +inline Type +RoundUp(Type x, Type base) +{ + Type remainder = Remainder(x, base); + return remainder ? x-remainder+base : x; +} + + +//@{ +/// Return @a x rounded down to the nearest integer. +inline float RoundDown(float x) { return floorf(x); } +inline double RoundDown(double x) { return floor(x); } +inline long double RoundDown(long double x) { return floorl(x); } +template inline Type Round(Type x) { return RoundDown(x+0.5); } +//@} +/// Return @a x rounded down to the nearest multiple of @a base. +template +inline Type +RoundDown(Type x, Type base) +{ + Type remainder = Remainder(x, base); + return remainder ? x-remainder : x; +} + + +/// Return the integer part of @a x. +template +inline Type +IntegerPart(Type x) +{ + return (x > 0 ? RoundDown(x) : RoundUp(x)); +} + +/// Return the fractional part of @a x. +template +inline Type FractionalPart(Type x) { return Mod(x,Type(1)); } + + +//@{ +/// Return the floor of @a x. +inline int Floor(float x) { return (int)RoundDown(x); } +inline int Floor(double x) { return (int)RoundDown(x); } +inline int Floor(long double x) { return (int)RoundDown(x); } +//@} + + +//@{ +/// Return the ceiling of @a x. +inline int Ceil(float x) { return (int)RoundUp(x); } +inline int Ceil(double x) { return (int)RoundUp(x); } +inline int Ceil(long double x) { return (int)RoundUp(x); } +//@} + + +/// Return @a x if it is greater in magnitude than @a delta. Otherwise, return zero. +template +inline Type Chop(Type x, Type delta) { return (Abs(x) < delta ? zeroVal() : x); } + + +/// Return @a x truncated to the given number of decimal digits. +template +inline Type +Truncate(Type x, unsigned int digits) +{ + Type tenth = Pow(10,digits); + return RoundDown(x*tenth+0.5)/tenth; +} + + +//////////////////////////////////////// + + +/// Return the inverse of @a x. +template +inline Type +Inv(Type x) +{ + assert(x); + return Type(1)/x; +} + + +enum Axis { + X_AXIS = 0, + Y_AXIS = 1, + Z_AXIS = 2 +}; + +// enum values are consistent with their historical mx analogs. +enum RotationOrder { + XYZ_ROTATION = 0, + XZY_ROTATION, + YXZ_ROTATION, + YZX_ROTATION, + ZXY_ROTATION, + ZYX_ROTATION, + XZX_ROTATION, + ZXZ_ROTATION +}; + + +template +struct promote { + typedef typename boost::numeric::conversion_traits::supertype type; +}; + + +/// @brief Return the index [0,1,2] of the smallest value in a 3D vector. +/// @note This methods assumes operator[] exists and avoids branching. +/// @details If two components of the input vector are equal and smaller then the +/// third component, the largest index of the two is always returned. +/// If all three vector components are equal the largest index, i.e. 2, is +/// returned. In other words the return value corresponds to the largest index +/// of the of the smallest vector components. +template +size_t +MinIndex(const Vec3T& v) +{ + static const size_t hashTable[8] = { 2, 1, 9, 1, 2, 9, 0, 0 };//9 is a dummy value + const size_t hashKey = + ((v[0] < v[1]) << 2) + ((v[0] < v[2]) << 1) + (v[1] < v[2]);// ?*4+?*2+?*1 + return hashTable[hashKey]; +} + + +/// @brief Return the index [0,1,2] of the largest value in a 3D vector. +/// @note This methods assumes operator[] exists and avoids branching. +/// @details If two components of the input vector are equal and larger then the +/// third component, the largest index of the two is always returned. +/// If all three vector components are equal the largest index, i.e. 2, is +/// returned. In other words the return value corresponds to the largest index +/// of the of the largest vector components. +template +size_t +MaxIndex(const Vec3T& v) +{ + static const size_t hashTable[8] = { 2, 1, 9, 1, 2, 9, 0, 0 };//9 is a dummy value + const size_t hashKey = + ((v[0] > v[1]) << 2) + ((v[0] > v[2]) << 1) + (v[1] > v[2]);// ?*4+?*2+?*1 + return hashTable[hashKey]; +} + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_MATH_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Operators.h b/openvdb_2_3_0_library/openvdb/math/Operators.h new file mode 100755 index 0000000..1203a8d --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Operators.h @@ -0,0 +1,2102 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 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" + + +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 { + typedef typename T::ValueType ValueType; + typedef math::Vec3 Vec3Type; +}; + +} // namespace internal + +// ---- Operators defined in index space + + +//@{ +/// @brief Gradient operators defined in index space of various orders +template +struct ISGradient +{ + // random access version + template static Vec3 + result(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + typedef Vec3 Vec3Type; + return Vec3Type( D1::inX(grid, ijk), + D1::inY(grid, ijk), + D1::inZ(grid, ijk) ); + } + + // stencil access version + template static Vec3 + result(const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + typedef Vec3 Vec3Type; + return Vec3Type( D1::inX(stencil), + D1::inY(stencil), + D1::inZ(stencil) ); + } +}; +//@} + +/// struct that relates the BiasedGradientScheme to the +/// forward and backward difference methods used, as well as to +/// the correct stencil type for index space use +template +struct BIAS_SCHEME { + static const DScheme FD = FD_1ST; + static const DScheme BD = BD_1ST; + + template + struct ISStencil { + typedef SevenPointStencil StencilType; + }; +}; + +template<> struct BIAS_SCHEME +{ + static const DScheme FD = FD_1ST; + static const DScheme BD = BD_1ST; + + template + struct ISStencil { + typedef SevenPointStencil StencilType; + }; +}; + +template<> struct BIAS_SCHEME +{ + static const DScheme FD = FD_2ND; + static const DScheme BD = BD_2ND; + + template + struct ISStencil { + typedef ThirteenPointStencil StencilType; + }; +}; +template<> struct BIAS_SCHEME +{ + static const DScheme FD = FD_3RD; + static const DScheme BD = BD_3RD; + + template + struct ISStencil { + typedef NineteenPointStencil StencilType; + }; +}; +template<> struct BIAS_SCHEME +{ + static const DScheme FD = FD_WENO5; + static const DScheme BD = BD_WENO5; + + template + struct ISStencil { + typedef NineteenPointStencil StencilType; + }; +}; +template<> struct BIAS_SCHEME +{ + static const DScheme FD = FD_HJWENO5; + static const DScheme BD = BD_HJWENO5; + + template + struct ISStencil { + typedef NineteenPointStencil StencilType; + }; +}; + + +//@{ +/// @brief Biased Gradient Operators, using upwinding defined by the @c Vec3Bias input + +template +struct ISGradientBiased +{ + static const DScheme FD = BIAS_SCHEME::FD; + static const DScheme BD = BIAS_SCHEME::BD; + + // random access version + template static Vec3 + result(const Accessor& grid, const Coord& ijk, const Vec3Bias& V) + { + typedef typename Accessor::ValueType ValueType; + typedef Vec3 Vec3Type; + + return Vec3Type(V[0]<0 ? D1::inX(grid,ijk) : D1::inX(grid,ijk), + V[1]<0 ? D1::inY(grid,ijk) : D1::inY(grid,ijk), + V[2]<0 ? D1::inZ(grid,ijk) : D1::inZ(grid,ijk) ); + } + + // stencil access version + template static Vec3 + result(const StencilT& stencil, const Vec3Bias& V) + { + typedef typename StencilT::ValueType ValueType; + typedef Vec3 Vec3Type; + + return Vec3Type(V[0]<0 ? D1::inX(stencil) : D1::inX(stencil), + V[1]<0 ? D1::inY(stencil) : D1::inY(stencil), + V[2]<0 ? D1::inZ(stencil) : D1::inZ(stencil) ); + } +}; + + +template +struct ISGradientNormSqrd +{ + static const DScheme FD = BIAS_SCHEME::FD; + static const DScheme BD = BIAS_SCHEME::BD; + + + // random access version + template + static typename Accessor::ValueType + result(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + Vec3Type up = ISGradient::result(grid, ijk); + Vec3Type down = ISGradient::result(grid, ijk); + return math::GudonovsNormSqrd(grid.getValue(ijk)>0, down, up); + } + + // stencil access version + template + static typename StencilT::ValueType + result(const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + Vec3Type up = ISGradient::result(stencil); + Vec3Type down = ISGradient::result(stencil); + return math::GudonovsNormSqrd(stencil.template getValue<0, 0, 0>()>0, down, up); + } +}; + +#ifdef DWA_OPENVDB // for SIMD - note will do the computations in float +template<> +struct ISGradientNormSqrd +{ + // random access version + template + static typename Accessor::ValueType + result(const Accessor& grid, const Coord& ijk) + { + // SSE optimized + const simd::Float4 + v1(grid.getValue(ijk.offsetBy(-2, 0, 0)) - grid.getValue(ijk.offsetBy(-3, 0, 0)), + grid.getValue(ijk.offsetBy( 0,-2, 0)) - grid.getValue(ijk.offsetBy( 0,-3, 0)), + grid.getValue(ijk.offsetBy( 0, 0,-2)) - grid.getValue(ijk.offsetBy( 0, 0,-3)), 0), + v2(grid.getValue(ijk.offsetBy(-1, 0, 0)) - grid.getValue(ijk.offsetBy(-2, 0, 0)), + grid.getValue(ijk.offsetBy( 0,-1, 0)) - grid.getValue(ijk.offsetBy( 0,-2, 0)), + grid.getValue(ijk.offsetBy( 0, 0,-1)) - grid.getValue(ijk.offsetBy( 0, 0,-2)), 0), + v3(grid.getValue(ijk ) - grid.getValue(ijk.offsetBy(-1, 0, 0)), + grid.getValue(ijk ) - grid.getValue(ijk.offsetBy( 0,-1, 0)), + grid.getValue(ijk ) - grid.getValue(ijk.offsetBy( 0, 0,-1)), 0), + v4(grid.getValue(ijk.offsetBy( 1, 0, 0)) - grid.getValue(ijk ), + grid.getValue(ijk.offsetBy( 0, 1, 0)) - grid.getValue(ijk ), + grid.getValue(ijk.offsetBy( 0, 0, 1)) - grid.getValue(ijk ), 0), + v5(grid.getValue(ijk.offsetBy( 2, 0, 0)) - grid.getValue(ijk.offsetBy( 1, 0, 0)), + grid.getValue(ijk.offsetBy( 0, 2, 0)) - grid.getValue(ijk.offsetBy( 0, 1, 0)), + grid.getValue(ijk.offsetBy( 0, 0, 2)) - grid.getValue(ijk.offsetBy( 0, 0, 1)), 0), + v6(grid.getValue(ijk.offsetBy( 3, 0, 0)) - grid.getValue(ijk.offsetBy( 2, 0, 0)), + grid.getValue(ijk.offsetBy( 0, 3, 0)) - grid.getValue(ijk.offsetBy( 0, 2, 0)), + grid.getValue(ijk.offsetBy( 0, 0, 3)) - grid.getValue(ijk.offsetBy( 0, 0, 2)), 0), + down = math::WENO5(v1, v2, v3, v4, v5), + up = math::WENO5(v6, v5, v4, v3, v2); + + return math::GudonovsNormSqrd(grid.getValue(ijk)>0, down, up); + } + + // stencil access version + template + static typename StencilT::ValueType + result(const StencilT& s) + { + // SSE optimized + const simd::Float4 + v1(s.template getValue<-2, 0, 0>() - s.template getValue<-3, 0, 0>(), + s.template getValue< 0,-2, 0>() - s.template getValue< 0,-3, 0>(), + s.template getValue< 0, 0,-2>() - s.template getValue< 0, 0,-3>(), 0), + v2(s.template getValue<-1, 0, 0>() - s.template getValue<-2, 0, 0>(), + s.template getValue< 0,-1, 0>() - s.template getValue< 0,-2, 0>(), + s.template getValue< 0, 0,-1>() - s.template getValue< 0, 0,-2>(), 0), + v3(s.template getValue< 0, 0, 0>() - s.template getValue<-1, 0, 0>(), + s.template getValue< 0, 0, 0>() - s.template getValue< 0,-1, 0>(), + s.template getValue< 0, 0, 0>() - s.template getValue< 0, 0,-1>(), 0), + v4(s.template getValue< 1, 0, 0>() - s.template getValue< 0, 0, 0>(), + s.template getValue< 0, 1, 0>() - s.template getValue< 0, 0, 0>(), + s.template getValue< 0, 0, 1>() - s.template getValue< 0, 0, 0>(), 0), + v5(s.template getValue< 2, 0, 0>() - s.template getValue< 1, 0, 0>(), + s.template getValue< 0, 2, 0>() - s.template getValue< 0, 1, 0>(), + s.template getValue< 0, 0, 2>() - s.template getValue< 0, 0, 1>(), 0), + v6(s.template getValue< 3, 0, 0>() - s.template getValue< 2, 0, 0>(), + s.template getValue< 0, 3, 0>() - s.template getValue< 0, 2, 0>(), + s.template getValue< 0, 0, 3>() - s.template getValue< 0, 0, 2>(), 0), + down = math::WENO5(v1, v2, v3, v4, v5), + up = math::WENO5(v6, v5, v4, v3, v2); + + return math::GudonovsNormSqrd(s.template getValue<0, 0, 0>()>0, down, up); + } +}; +#endif //DWA_OPENVDB // for SIMD - note will do the computations in float +//@} + + +//@{ +/// @brief Laplacian defined in index space, using various center-difference stencils +template +struct ISLaplacian +{ + // random access version + template + static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk); + + // stencil access version + template + static typename StencilT::ValueType result(const StencilT& stencil); +}; + + +template<> +struct ISLaplacian +{ + // random access version + template + static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) + { + return grid.getValue(ijk.offsetBy(1,0,0)) + grid.getValue(ijk.offsetBy(-1, 0, 0)) + + grid.getValue(ijk.offsetBy(0,1,0)) + grid.getValue(ijk.offsetBy(0, -1, 0)) + + grid.getValue(ijk.offsetBy(0,0,1)) + grid.getValue(ijk.offsetBy(0, 0,-1)) + - 6*grid.getValue(ijk); + } + + // stencil access version + template + static typename StencilT::ValueType result(const StencilT& stencil) + { + return stencil.template getValue< 1, 0, 0>() + stencil.template getValue<-1, 0, 0>() + + stencil.template getValue< 0, 1, 0>() + stencil.template getValue< 0,-1, 0>() + + stencil.template getValue< 0, 0, 1>() + stencil.template getValue< 0, 0,-1>() + - 6*stencil.template getValue< 0, 0, 0>(); + } +}; + +template<> +struct ISLaplacian +{ + // random access version + template + static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) + { + return (-1./12.)*( + grid.getValue(ijk.offsetBy(2,0,0)) + grid.getValue(ijk.offsetBy(-2, 0, 0)) + + grid.getValue(ijk.offsetBy(0,2,0)) + grid.getValue(ijk.offsetBy( 0,-2, 0)) + + grid.getValue(ijk.offsetBy(0,0,2)) + grid.getValue(ijk.offsetBy( 0, 0,-2)) ) + + (4./3.)*( + grid.getValue(ijk.offsetBy(1,0,0)) + grid.getValue(ijk.offsetBy(-1, 0, 0)) + + grid.getValue(ijk.offsetBy(0,1,0)) + grid.getValue(ijk.offsetBy( 0,-1, 0)) + + grid.getValue(ijk.offsetBy(0,0,1)) + grid.getValue(ijk.offsetBy( 0, 0,-1)) ) + - 7.5*grid.getValue(ijk); + } + + // stencil access version + template + static typename StencilT::ValueType result(const StencilT& stencil) + { + return (-1./12.)*( + stencil.template getValue< 2, 0, 0>() + stencil.template getValue<-2, 0, 0>() + + stencil.template getValue< 0, 2, 0>() + stencil.template getValue< 0,-2, 0>() + + stencil.template getValue< 0, 0, 2>() + stencil.template getValue< 0, 0,-2>() ) + + (4./3.)*( + stencil.template getValue< 1, 0, 0>() + stencil.template getValue<-1, 0, 0>() + + stencil.template getValue< 0, 1, 0>() + stencil.template getValue< 0,-1, 0>() + + stencil.template getValue< 0, 0, 1>() + stencil.template getValue< 0, 0,-1>() ) + - 7.5*stencil.template getValue< 0, 0, 0>(); + } +}; + +template<> +struct ISLaplacian +{ + // random access version + template + static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) + { + return (1./90.)*( + grid.getValue(ijk.offsetBy(3,0,0)) + grid.getValue(ijk.offsetBy(-3, 0, 0)) + + grid.getValue(ijk.offsetBy(0,3,0)) + grid.getValue(ijk.offsetBy( 0,-3, 0)) + + grid.getValue(ijk.offsetBy(0,0,3)) + grid.getValue(ijk.offsetBy( 0, 0,-3)) ) + - (3./20.)*( + grid.getValue(ijk.offsetBy(2,0,0)) + grid.getValue(ijk.offsetBy(-2, 0, 0)) + + grid.getValue(ijk.offsetBy(0,2,0)) + grid.getValue(ijk.offsetBy( 0,-2, 0)) + + grid.getValue(ijk.offsetBy(0,0,2)) + grid.getValue(ijk.offsetBy( 0, 0,-2)) ) + + 1.5 *( + grid.getValue(ijk.offsetBy(1,0,0)) + grid.getValue(ijk.offsetBy(-1, 0, 0)) + + grid.getValue(ijk.offsetBy(0,1,0)) + grid.getValue(ijk.offsetBy( 0,-1, 0)) + + grid.getValue(ijk.offsetBy(0,0,1)) + grid.getValue(ijk.offsetBy( 0, 0,-1)) ) + - (3*49/18.)*grid.getValue(ijk); + } + + // stencil access version + template + static typename StencilT::ValueType result(const StencilT& stencil) + { + return (1./90.)*( + stencil.template getValue< 3, 0, 0>() + stencil.template getValue<-3, 0, 0>() + + stencil.template getValue< 0, 3, 0>() + stencil.template getValue< 0,-3, 0>() + + stencil.template getValue< 0, 0, 3>() + stencil.template getValue< 0, 0,-3>() ) + - (3./20.)*( + stencil.template getValue< 2, 0, 0>() + stencil.template getValue<-2, 0, 0>() + + stencil.template getValue< 0, 2, 0>() + stencil.template getValue< 0,-2, 0>() + + stencil.template getValue< 0, 0, 2>() + stencil.template getValue< 0, 0,-2>() ) + + 1.5 *( + stencil.template getValue< 1, 0, 0>() + stencil.template getValue<-1, 0, 0>() + + stencil.template getValue< 0, 1, 0>() + stencil.template getValue< 0,-1, 0>() + + stencil.template getValue< 0, 0, 1>() + stencil.template getValue< 0, 0,-1>() ) + - (3*49/18.)*stencil.template getValue< 0, 0, 0>(); + } +}; +//@} + + +//@{ +/// Divergence operator defined in index space using various first derivative schemes +template +struct ISDivergence +{ + // random access version + template static typename Accessor::ValueType::value_type + result(const Accessor& grid, const Coord& ijk) + { + return D1Vec::inX(grid, ijk, 0) + + D1Vec::inY(grid, ijk, 1) + + D1Vec::inZ(grid, ijk, 2); + } + + // stencil access version + template static typename StencilT::ValueType::value_type + result(const StencilT& stencil) + { + return D1Vec::inX(stencil, 0) + + D1Vec::inY(stencil, 1) + + D1Vec::inZ(stencil, 2); + } +}; +//@} + + +//@{ +/// Curl operator defined in index space using various first derivative schemes +template +struct ISCurl +{ + // random access version + template + static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType Vec3Type; + return Vec3Type( D1Vec::inY(grid, ijk, 2) - //dw/dy - dv/dz + D1Vec::inZ(grid, ijk, 1), + D1Vec::inZ(grid, ijk, 0) - //du/dz - dw/dx + D1Vec::inX(grid, ijk, 2), + D1Vec::inX(grid, ijk, 1) - //dv/dx - du/dy + D1Vec::inY(grid, ijk, 0) ); + } + + // stencil access version + template + static typename StencilT::ValueType result(const StencilT& stencil) + { + typedef typename StencilT::ValueType Vec3Type; + return Vec3Type( D1Vec::inY(stencil, 2) - //dw/dy - dv/dz + D1Vec::inZ(stencil, 1), + D1Vec::inZ(stencil, 0) - //du/dz - dw/dx + D1Vec::inX(stencil, 2), + D1Vec::inX(stencil, 1) - //dv/dx - du/dy + D1Vec::inY(stencil, 0) ); + } +}; +//@} + + +//@{ +/// Compute the mean curvature in index space +template +struct ISMeanCurvature +{ + /// @brief random access version + /// @return true if the gradient is none-zero, in which case the + /// mean curvature is computed as two parts: @c alpha is the numerator in + /// @f$\nabla \cdot (\nabla \phi / |\nabla \phi|)@f$, and @c beta is @f$|\nabla \phi|@f$. + template + static bool result(const Accessor& grid, const Coord& ijk, + typename Accessor::ValueType& alpha, + typename Accessor::ValueType& beta) + { + typedef typename Accessor::ValueType 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 true if the gradient is none-zero, in which case the + /// mean curvature is computed as two parts: @c alpha is the numerator in + /// @f$\nabla \cdot (\nabla \phi / |\nabla \phi|)@f$, and @c beta is @f$|\nabla \phi|@f$. + template + static bool result(const StencilT& stencil, + typename StencilT::ValueType& alpha, + typename StencilT::ValueType& beta) + { + typedef typename StencilT::ValueType 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) + { + typedef typename internal::ReturnValue::Vec3Type Vec3Type; + + Vec3d iGradient( ISGradient::result(grid, ijk) ); + return Vec3Type(map.applyIJT(iGradient, ijk.asVec3d())); + } + + // stencil access version + template + static typename internal::ReturnValue::Vec3Type + result(const MapType& map, const StencilT& stencil) + { + typedef typename internal::ReturnValue::Vec3Type Vec3Type; + + Vec3d iGradient( ISGradient::result(stencil) ); + return Vec3Type(map.applyIJT(iGradient, stencil.getCenterCoord().asVec3d())); + } +}; + +// Partial template specialization of Gradient +// translation, any order +template +struct Gradient +{ + // random access version + template + static typename internal::ReturnValue::Vec3Type + result(const TranslationMap&, const Accessor& grid, const Coord& ijk) + { + return ISGradient::result(grid, ijk); + } + + // stencil access version + template + static typename internal::ReturnValue::Vec3Type + result(const TranslationMap&, const StencilT& stencil) + { + return ISGradient::result(stencil); + } +}; + +/// Full template specialization of Gradient +/// uniform scale, 2nd order +template<> +struct Gradient +{ + // random access version + template + static typename internal::ReturnValue::Vec3Type + result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename internal::ReturnValue::ValueType ValueType; + typedef typename internal::ReturnValue::Vec3Type Vec3Type; + + Vec3Type iGradient( ISGradient::result(grid, ijk) ); + ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); + return iGradient * inv2dx; + } + + // stencil access version + template + static typename internal::ReturnValue::Vec3Type + result(const UniformScaleMap& map, const StencilT& stencil) + { + typedef typename internal::ReturnValue::ValueType ValueType; + typedef typename internal::ReturnValue::Vec3Type Vec3Type; + + Vec3Type iGradient( ISGradient::result(stencil) ); + ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); + return iGradient * inv2dx; + } +}; + +/// Full template specialization of Gradient +/// uniform scale translate, 2nd order +template<> +struct Gradient +{ + // random access version + template + static typename internal::ReturnValue::Vec3Type + result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename internal::ReturnValue::ValueType ValueType; + typedef typename internal::ReturnValue::Vec3Type Vec3Type; + + Vec3Type iGradient( ISGradient::result(grid, ijk) ); + ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); + return iGradient * inv2dx; + } + + // stencil access version + template + static typename internal::ReturnValue::Vec3Type + result(const UniformScaleTranslateMap& map, const StencilT& stencil) + { + typedef typename internal::ReturnValue::ValueType ValueType; + typedef typename internal::ReturnValue::Vec3Type Vec3Type; + + Vec3Type iGradient( ISGradient::result(stencil) ); + ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); + return iGradient * inv2dx; + } +}; + +/// Full template specialization of Gradient +/// scale, 2nd order +template<> +struct Gradient +{ + // random access version + template + static typename internal::ReturnValue::Vec3Type + result(const ScaleMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename internal::ReturnValue::ValueType ValueType; + typedef typename internal::ReturnValue::Vec3Type Vec3Type; + + Vec3Type iGradient( ISGradient::result(grid, ijk) ); + return Vec3Type(ValueType(iGradient[0] * map.getInvTwiceScale()[0]), + ValueType(iGradient[1] * map.getInvTwiceScale()[1]), + ValueType(iGradient[2] * map.getInvTwiceScale()[2]) ); + } + + // stencil access version + template + static typename internal::ReturnValue::Vec3Type + result(const ScaleMap& map, const StencilT& stencil) + { + typedef typename internal::ReturnValue::ValueType ValueType; + typedef typename internal::ReturnValue::Vec3Type Vec3Type; + + Vec3Type iGradient( ISGradient::result(stencil) ); + return Vec3Type(ValueType(iGradient[0] * map.getInvTwiceScale()[0]), + ValueType(iGradient[1] * map.getInvTwiceScale()[1]), + ValueType(iGradient[2] * map.getInvTwiceScale()[2]) ); + } +}; + +/// Full template specialization of Gradient +/// scale translate, 2nd order +template<> +struct Gradient +{ + // random access version + template + static typename internal::ReturnValue::Vec3Type + result(const ScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename internal::ReturnValue::ValueType ValueType; + typedef typename internal::ReturnValue::Vec3Type Vec3Type; + + Vec3Type iGradient( ISGradient::result(grid, ijk) ); + return Vec3Type(ValueType(iGradient[0] * map.getInvTwiceScale()[0]), + ValueType(iGradient[1] * map.getInvTwiceScale()[1]), + ValueType(iGradient[2] * map.getInvTwiceScale()[2]) ); + } + + // Stencil access version + template + static typename internal::ReturnValue::Vec3Type + result(const ScaleTranslateMap& map, const StencilT& stencil) + { + typedef typename internal::ReturnValue::ValueType ValueType; + typedef typename internal::ReturnValue::Vec3Type Vec3Type; + + Vec3Type iGradient( ISGradient::result(stencil) ); + return Vec3Type(ValueType(iGradient[0] * map.getInvTwiceScale()[0]), + ValueType(iGradient[1] * map.getInvTwiceScale()[1]), + ValueType(iGradient[2] * map.getInvTwiceScale()[2]) ); + } +}; +//@} + + +//@{ +/// @brief Biased gradient operators, defined with respect to the range-space of the map +/// @note This will need to be divided by two in the case of CD_2NDT +template +struct GradientBiased +{ + // random access version + template static math::Vec3 + result(const MapType& map, const Accessor& grid, const Coord& ijk, + const Vec3& V) + { + typedef typename Accessor::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + Vec3d iGradient( ISGradientBiased::result(grid, ijk, V) ); + return Vec3Type(map.applyIJT(iGradient, ijk.asVec3d())); + } + + // stencil access version + template static math::Vec3 + result(const MapType& map, const StencilT& stencil, + const Vec3& V) + { + typedef typename StencilT::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + Vec3d iGradient( ISGradientBiased::result(stencil, V) ); + return Vec3Type(map.applyIJT(iGradient, stencil.getCenterCoord().asVec3d())); + } +}; +//@} + + +//////////////////////////////////////////////////////// + +// Computes |Grad[Phi]| using upwinding +template +struct GradientNormSqrd +{ + static const DScheme FD = BIAS_SCHEME::FD; + static const DScheme BD = BIAS_SCHEME::BD; + + + // random access version + template + static typename Accessor::ValueType + result(const MapType& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + Vec3Type up = Gradient::result(map, grid, ijk); + Vec3Type down = Gradient::result(map, grid, ijk); + return math::GudonovsNormSqrd(grid.getValue(ijk)>0, down, up); + } + + // stencil access version + template + static typename StencilT::ValueType + result(const MapType& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + Vec3Type up = Gradient::result(map, stencil); + Vec3Type down = Gradient::result(map, stencil); + return math::GudonovsNormSqrd(stencil.template getValue<0, 0, 0>()>0, down, up); + } +}; + +/// Partial template specialization of GradientNormSqrd +template +struct GradientNormSqrd +{ + // random access version + template + static typename Accessor::ValueType + result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + + ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); + return invdxdx * ISGradientNormSqrd::result(grid, ijk); + } + + // stencil access version + template + static typename StencilT::ValueType + result(const UniformScaleMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + + ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); + return invdxdx * ISGradientNormSqrd::result(stencil); + } +}; + +/// Partial template specialization of GradientNormSqrd +template +struct GradientNormSqrd +{ + // random access version + template + static typename Accessor::ValueType + result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + + ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); + return invdxdx * ISGradientNormSqrd::result(grid, ijk); + } + + // stencil access version + template + static typename StencilT::ValueType + result(const UniformScaleTranslateMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + + ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); + return invdxdx * ISGradientNormSqrd::result(stencil); + } +}; + + +//@{ +/// @brief Compute the divergence of a vector-valued grid using differencing +/// of various orders, the result defined with respect to the range-space of the map. +template +struct Divergence +{ + // random access version + template static typename Accessor::ValueType::value_type + result(const MapType& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType::value_type ValueType; + + ValueType div(0); + for (int i=0; i < 3; i++) { + Vec3d vec( D1Vec::inX(grid, ijk, i), + D1Vec::inY(grid, ijk, i), + D1Vec::inZ(grid, ijk, i) ); + div += ValueType(map.applyIJT(vec, ijk.asVec3d())[i]); + } + return div; + } + + // stencil access version + template static typename StencilT::ValueType::value_type + result(const MapType& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType::value_type ValueType; + + ValueType div(0); + for (int i=0; i < 3; i++) { + Vec3d vec( D1Vec::inX(stencil, i), + D1Vec::inY(stencil, i), + D1Vec::inZ(stencil, i) ); + div += ValueType(map.applyIJT(vec, stencil.getCenterCoord().asVec3d())[i]); + } + return div; + } +}; + +/// Partial template specialization of Divergence +/// translation, any scheme +template +struct Divergence +{ + // random access version + template static typename Accessor::ValueType::value_type + result(const TranslationMap&, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType::value_type ValueType; + + ValueType div(0); + div =ISDivergence::result(grid, ijk); + return div; + } + + // stencil access version + template static typename StencilT::ValueType::value_type + result(const TranslationMap&, const StencilT& stencil) + { + typedef typename StencilT::ValueType::value_type ValueType; + + ValueType div(0); + div =ISDivergence::result(stencil); + return div; + } +}; + +/// Partial template specialization of Divergence +/// uniform scale, any scheme +template +struct Divergence +{ + // random access version + template static typename Accessor::ValueType::value_type + result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType::value_type ValueType; + + ValueType div(0); + + div =ISDivergence::result(grid, ijk); + ValueType invdx = ValueType(map.getInvScale()[0]); + return div * invdx; + } + + // stencil access version + template static typename StencilT::ValueType::value_type + result(const UniformScaleMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType::value_type ValueType; + + ValueType div(0); + + div =ISDivergence::result(stencil); + ValueType invdx = ValueType(map.getInvScale()[0]); + return div * invdx; + } +}; + +/// Partial template specialization of Divergence +/// uniform scale and translation, any scheme +template +struct Divergence +{ + // random access version + template static typename Accessor::ValueType::value_type + result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType::value_type ValueType; + + ValueType div(0); + + div =ISDivergence::result(grid, ijk); + ValueType invdx = ValueType(map.getInvScale()[0]); + return div * invdx; + } + + // stencil access version + template static typename StencilT::ValueType::value_type + result(const UniformScaleTranslateMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType::value_type ValueType; + + ValueType div(0); + + div =ISDivergence::result(stencil); + ValueType invdx = ValueType(map.getInvScale()[0]); + return div * invdx; + } +}; + +/// Full template specialization of Divergence +/// uniform scale 2nd order +template<> +struct Divergence +{ + // random access version + template static typename Accessor::ValueType::value_type + result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType::value_type ValueType; + + ValueType div(0); + div =ISDivergence::result(grid, ijk); + ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); + return div * inv2dx; + } + + // stencil access version + template static typename StencilT::ValueType::value_type + result(const UniformScaleMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType::value_type ValueType; + + ValueType div(0); + div =ISDivergence::result(stencil); + ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); + return div * inv2dx; + } +}; + +/// Full template specialization of Divergence +/// uniform scale translate 2nd order +template<> +struct Divergence +{ + // random access version + template static typename Accessor::ValueType::value_type + result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType::value_type ValueType; + + ValueType div(0); + + div =ISDivergence::result(grid, ijk); + ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); + return div * inv2dx; + } + + // stencil access version + template static typename StencilT::ValueType::value_type + result(const UniformScaleTranslateMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType::value_type ValueType; + + ValueType div(0); + + div =ISDivergence::result(stencil); + ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); + return div * inv2dx; + } +}; + +/// Partial template specialization of Divergence +/// scale, any scheme +template +struct Divergence +{ + // random access version + template static typename Accessor::ValueType::value_type + result(const ScaleMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType::value_type ValueType; + + ValueType div = ValueType( + D1Vec::inX(grid, ijk, 0) * (map.getInvScale()[0]) + + D1Vec::inY(grid, ijk, 1) * (map.getInvScale()[1]) + + D1Vec::inZ(grid, ijk, 2) * (map.getInvScale()[2])); + return div; + } + + // stencil access version + template static typename StencilT::ValueType::value_type + result(const ScaleMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType::value_type ValueType; + + ValueType div(0); + div = ValueType( + D1Vec::inX(stencil, 0) * (map.getInvScale()[0]) + + D1Vec::inY(stencil, 1) * (map.getInvScale()[1]) + + D1Vec::inZ(stencil, 2) * (map.getInvScale()[2]) ); + return div; + } +}; + +/// Partial template specialization of Divergence +/// scale translate, any scheme +template +struct Divergence +{ + // random access version + template static typename Accessor::ValueType::value_type + result(const ScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType::value_type ValueType; + + ValueType div = ValueType( + D1Vec::inX(grid, ijk, 0) * (map.getInvScale()[0]) + + D1Vec::inY(grid, ijk, 1) * (map.getInvScale()[1]) + + D1Vec::inZ(grid, ijk, 2) * (map.getInvScale()[2])); + return div; + } + + // stencil access version + template static typename StencilT::ValueType::value_type + result(const ScaleTranslateMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType::value_type ValueType; + + ValueType div(0); + div = ValueType( + D1Vec::inX(stencil, 0) * (map.getInvScale()[0]) + + D1Vec::inY(stencil, 1) * (map.getInvScale()[1]) + + D1Vec::inZ(stencil, 2) * (map.getInvScale()[2]) ); + return div; + } +}; + +/// Full template specialization Divergence +/// scale 2nd order +template<> +struct Divergence +{ + // random access version + template static typename Accessor::ValueType::value_type + result(const ScaleMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType::value_type ValueType; + + ValueType div = ValueType( + D1Vec::inX(grid, ijk, 0) * (map.getInvTwiceScale()[0]) + + D1Vec::inY(grid, ijk, 1) * (map.getInvTwiceScale()[1]) + + D1Vec::inZ(grid, ijk, 2) * (map.getInvTwiceScale()[2]) ); + return div; + } + + // stencil access version + template static typename StencilT::ValueType::value_type + result(const ScaleMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType::value_type ValueType; + + ValueType div = ValueType( + D1Vec::inX(stencil, 0) * (map.getInvTwiceScale()[0]) + + D1Vec::inY(stencil, 1) * (map.getInvTwiceScale()[1]) + + D1Vec::inZ(stencil, 2) * (map.getInvTwiceScale()[2]) ); + return div; + } +}; + +/// Full template specialization of Divergence +/// scale and translate, 2nd order +template<> +struct Divergence +{ + // random access version + template static typename Accessor::ValueType::value_type + result(const ScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType::value_type ValueType; + + ValueType div = ValueType( + D1Vec::inX(grid, ijk, 0) * (map.getInvTwiceScale()[0]) + + D1Vec::inY(grid, ijk, 1) * (map.getInvTwiceScale()[1]) + + D1Vec::inZ(grid, ijk, 2) * (map.getInvTwiceScale()[2]) ); + return div; + } + + // stencil access version + template static typename StencilT::ValueType::value_type + result(const ScaleTranslateMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType::value_type ValueType; + + ValueType div = ValueType( + D1Vec::inX(stencil, 0) * (map.getInvTwiceScale()[0]) + + D1Vec::inY(stencil, 1) * (map.getInvTwiceScale()[1]) + + D1Vec::inZ(stencil, 2) * (map.getInvTwiceScale()[2]) ); + return div; + } +}; +//@} + + +//@{ +/// @brief Compute the curl of a vector-valued grid using differencing +/// of various orders in the space defined by the range of the map. +template +struct Curl +{ + // random access version + template static typename Accessor::ValueType + result(const MapType& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType Vec3Type; + Vec3Type mat[3]; + for (int i = 0; i < 3; i++) { + Vec3d vec( + D1Vec::inX(grid, ijk, i), + D1Vec::inY(grid, ijk, i), + D1Vec::inZ(grid, ijk, i)); + // dF_i/dx_j (x_1 = x, x_2 = y, x_3 = z) + mat[i] = Vec3Type(map.applyIJT(vec, ijk.asVec3d())); + } + return Vec3Type(mat[2][1] - mat[1][2], // dF_3/dx_2 - dF_2/dx_3 + mat[0][2] - mat[2][0], // dF_1/dx_3 - dF_3/dx_1 + mat[1][0] - mat[0][1]); // dF_2/dx_1 - dF_1/dx_2 + } + + // stencil access version + template static typename StencilT::ValueType + result(const MapType& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType Vec3Type; + Vec3Type mat[3]; + for (int i = 0; i < 3; i++) { + Vec3d vec( + D1Vec::inX(stencil, i), + D1Vec::inY(stencil, i), + D1Vec::inZ(stencil, i)); + // dF_i/dx_j (x_1 = x, x_2 = y, x_3 = z) + mat[i] = Vec3Type(map.applyIJT(vec, stencil.getCenterCoord().asVec3d())); + } + return Vec3Type(mat[2][1] - mat[1][2], // dF_3/dx_2 - dF_2/dx_3 + mat[0][2] - mat[2][0], // dF_1/dx_3 - dF_3/dx_1 + mat[1][0] - mat[0][1]); // dF_2/dx_1 - dF_1/dx_2 + } +}; + +/// Partial template specialization of Curl +template +struct Curl +{ + // random access version + template static typename Accessor::ValueType + result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType Vec3Type; + typedef typename Vec3Type::value_type ValueType; + return ISCurl::result(grid, ijk) * ValueType(map.getInvScale()[0]); + } + + // Stencil access version + template static typename StencilT::ValueType + result(const UniformScaleMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType Vec3Type; + typedef typename Vec3Type::value_type ValueType; + return ISCurl::result(stencil) * ValueType(map.getInvScale()[0]); + } +}; + +/// Partial template specialization of Curl +template +struct Curl +{ + // random access version + template static typename Accessor::ValueType + result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType Vec3Type; + typedef typename Vec3Type::value_type ValueType; + + return ISCurl::result(grid, ijk) * ValueType(map.getInvScale()[0]); + } + + // stencil access version + template static typename StencilT::ValueType + result(const UniformScaleTranslateMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType Vec3Type; + typedef typename Vec3Type::value_type ValueType; + + return ISCurl::result(stencil) * ValueType(map.getInvScale()[0]); + } +}; + +/// Full template specialization of Curl +template<> +struct Curl +{ + // random access version + template static typename Accessor::ValueType + result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType Vec3Type; + typedef typename Vec3Type::value_type ValueType; + + return ISCurl::result(grid, ijk) * ValueType(map.getInvTwiceScale()[0]); + } + + // stencil access version + template static typename StencilT::ValueType + result(const UniformScaleMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType Vec3Type; + typedef typename Vec3Type::value_type ValueType; + + return ISCurl::result(stencil) * ValueType(map.getInvTwiceScale()[0]); + } +}; + +/// Full template specialization of Curl +template<> +struct Curl +{ + // random access version + template static typename Accessor::ValueType + result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType Vec3Type; + typedef typename Vec3Type::value_type ValueType; + + return ISCurl::result(grid, ijk) * ValueType(map.getInvTwiceScale()[0]); + } + + // stencil access version + template static typename StencilT::ValueType + result(const UniformScaleTranslateMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType Vec3Type; + typedef typename Vec3Type::value_type ValueType; + + return ISCurl::result(stencil) * ValueType(map.getInvTwiceScale()[0]); + } +}; +//@} + + +//@{ +/// @brief Compute the Laplacian at a given location in a grid using finite differencing +/// of various orders. The result is defined in the range of the map. +template +struct Laplacian +{ + // random access version + template + static typename Accessor::ValueType result(const MapType& map, + const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + // all the second derivatives in index space + ValueType iddx = D2::inX(grid, ijk); + ValueType iddy = D2::inY(grid, ijk); + ValueType iddz = D2::inZ(grid, ijk); + + ValueType iddxy = D2::inXandY(grid, ijk); + ValueType iddyz = D2::inYandZ(grid, ijk); + ValueType iddxz = D2::inXandZ(grid, ijk); + + // second derivatives in index space + Mat3d d2_is(iddx, iddxy, iddxz, + iddxy, iddy, iddyz, + iddxz, iddyz, iddz); + + Mat3d d2_rs; // to hold the second derivative matrix in range space + if (is_linear::value) { + d2_rs = map.applyIJC(d2_is); + } else { + // compute the first derivatives with 2nd order accuracy. + Vec3d d1_is(D1::inX(grid, ijk), + D1::inY(grid, ijk), + D1::inZ(grid, ijk) ); + + d2_rs = map.applyIJC(d2_is, d1_is, ijk.asVec3d()); + } + + // the trace of the second derivative (range space) matrix is laplacian + return ValueType(d2_rs(0,0) + d2_rs(1,1) + d2_rs(2,2)); + } + + // stencil access version + template + static typename StencilT::ValueType result(const MapType& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + // all the second derivatives in index space + ValueType iddx = D2::inX(stencil); + ValueType iddy = D2::inY(stencil); + ValueType iddz = D2::inZ(stencil); + + ValueType iddxy = D2::inXandY(stencil); + ValueType iddyz = D2::inYandZ(stencil); + ValueType iddxz = D2::inXandZ(stencil); + + // second derivatives in index space + Mat3d d2_is(iddx, iddxy, iddxz, + iddxy, iddy, iddyz, + iddxz, iddyz, iddz); + + Mat3d d2_rs; // to hold the second derivative matrix in range space + if (is_linear::value) { + d2_rs = map.applyIJC(d2_is); + } else { + // compute the first derivatives with 2nd order accuracy. + Vec3d d1_is(D1::inX(stencil), + D1::inY(stencil), + D1::inZ(stencil) ); + + d2_rs = map.applyIJC(d2_is, d1_is, stencil.getCenterCoord().asVec3d()); + } + + // the trace of the second derivative (range space) matrix is laplacian + return ValueType(d2_rs(0,0) + d2_rs(1,1) + d2_rs(2,2)); + } +}; + + +template +struct Laplacian +{ + // random access version + template + static typename Accessor::ValueType result(const TranslationMap&, + const Accessor& grid, const Coord& ijk) + { + return ISLaplacian::result(grid, ijk); + } + + // stencil access version + template + static typename StencilT::ValueType result(const TranslationMap&, const StencilT& stencil) + { + return ISLaplacian::result(stencil); + } +}; + + +// The Laplacian is invariant to rotation or reflection. +template +struct Laplacian +{ + // random access version + template + static typename Accessor::ValueType result(const UnitaryMap&, + const Accessor& grid, const Coord& ijk) + { + return ISLaplacian::result(grid, ijk); + } + + // stencil access version + template + static typename StencilT::ValueType result(const UnitaryMap&, const StencilT& stencil) + { + return ISLaplacian::result(stencil); + } +}; + + +template +struct Laplacian +{ + // random access version + template static typename Accessor::ValueType + result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); + return ISLaplacian::result(grid, ijk) * invdxdx; + } + + // stencil access version + template static typename StencilT::ValueType + result(const UniformScaleMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); + return ISLaplacian::result(stencil) * invdxdx; + } +}; + + +template +struct Laplacian +{ + // random access version + template static typename Accessor::ValueType + result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); + return ISLaplacian::result(grid, ijk) * invdxdx; + } + + // stencil access version + template static typename StencilT::ValueType + result(const UniformScaleTranslateMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); + return ISLaplacian::result(stencil) * invdxdx; + } +}; + + +template +struct Laplacian +{ + // random access version + template static typename Accessor::ValueType + result(const ScaleMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + + // compute the second derivatives in index space + ValueType iddx = D2::inX(grid, ijk); + ValueType iddy = D2::inY(grid, ijk); + ValueType iddz = D2::inZ(grid, ijk); + const Vec3d& invScaleSqr = map.getInvScaleSqr(); + // scale them by the appropriate 1/dx^2, 1/dy^2, 1/dz^2 and sum + return ValueType(iddx * invScaleSqr[0] + iddy * invScaleSqr[1] + iddz * invScaleSqr[2]); + } + + // stencil access version + template static typename StencilT::ValueType + result(const ScaleMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + + // compute the second derivatives in index space + ValueType iddx = D2::inX(stencil); + ValueType iddy = D2::inY(stencil); + ValueType iddz = D2::inZ(stencil); + const Vec3d& invScaleSqr = map.getInvScaleSqr(); + // scale them by the appropriate 1/dx^2, 1/dy^2, 1/dz^2 and sum + return ValueType(iddx * invScaleSqr[0] + iddy * invScaleSqr[1] + iddz * invScaleSqr[2]); + } +}; + + +template +struct Laplacian +{ + // random access version + template static typename Accessor::ValueType + result(const ScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + // compute the second derivatives in index space + ValueType iddx = D2::inX(grid, ijk); + ValueType iddy = D2::inY(grid, ijk); + ValueType iddz = D2::inZ(grid, ijk); + const Vec3d& invScaleSqr = map.getInvScaleSqr(); + // scale them by the appropriate 1/dx^2, 1/dy^2, 1/dz^2 and sum + return ValueType(iddx * invScaleSqr[0] + iddy * invScaleSqr[1] + iddz * invScaleSqr[2]); + } + + // stencil access version + template static typename StencilT::ValueType + result(const ScaleTranslateMap& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + // compute the second derivatives in index space + ValueType iddx = D2::inX(stencil); + ValueType iddy = D2::inY(stencil); + ValueType iddz = D2::inZ(stencil); + const Vec3d& invScaleSqr = map.getInvScaleSqr(); + // scale them by the appropriate 1/dx^2, 1/dy^2, 1/dz^2 and sum + return ValueType(iddx * invScaleSqr[0] + iddy * invScaleSqr[1] + iddz * invScaleSqr[2]); + } +}; + + +/// @brief Compute the closest-point transform to a level set. +/// @return the closest point to the surface from which the level set was derived, +/// in the domain space of the map (e.g., voxel space). +template +struct CPT +{ + // random access version + template static math::Vec3 + result(const MapType& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + typedef Vec3 Vec3Type; + + // current distance + ValueType d = grid.getValue(ijk); + // compute gradient in physical space where it is a unit normal + // since the grid holds a distance level set. + Vec3d vectorFromSurface(d*Gradient::result(map, grid, ijk)); + if (is_linear::value) { + Vec3d result = ijk.asVec3d() - map.applyInverseMap(vectorFromSurface); + return Vec3Type(result); + } else { + Vec3d location = map.applyMap(ijk.asVec3d()); + Vec3d result = map.applyInverseMap(location - vectorFromSurface); + return Vec3Type(result); + } + } + + // stencil access version + template static math::Vec3 + result(const MapType& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + typedef Vec3 Vec3Type; + + // current distance + ValueType d = stencil.template getValue<0, 0, 0>(); + // compute gradient in physical space where it is a unit normal + // since the grid holds a distance level set. + Vec3d vectorFromSurface(d*Gradient::result(map, stencil)); + if (is_linear::value) { + Vec3d result = stencil.getCenterCoord().asVec3d() + - map.applyInverseMap(vectorFromSurface); + return Vec3Type(result); + } else { + Vec3d location = map.applyMap(stencil.getCenterCoord().asVec3d()); + Vec3d result = map.applyInverseMap(location - vectorFromSurface); + return Vec3Type(result); + } + } +}; + + +/// @brief Compute the closest-point transform to a level set. +/// @return the closest point to the surface from which the level set was derived, +/// in the range space of the map (e.g., in world space) +template +struct CPT_RANGE +{ + // random access version + template static Vec3 + result(const MapType& map, const Accessor& grid, const Coord& ijk) + { + typedef typename Accessor::ValueType ValueType; + typedef Vec3 Vec3Type; + // current distance + ValueType d = grid.getValue(ijk); + // compute gradient in physical space where it is a unit normal + // since the grid holds a distance level set. + Vec3Type vectorFromSurface = + d*Gradient::result(map, grid, ijk); + Vec3d result = map.applyMap(ijk.asVec3d()) - vectorFromSurface; + + return Vec3Type(result); + } + + // stencil access version + template static Vec3 + result(const MapType& map, const StencilT& stencil) + { + typedef typename StencilT::ValueType ValueType; + typedef Vec3 Vec3Type; + // current distance + ValueType d = stencil.template getValue<0, 0, 0>(); + // compute gradient in physical space where it is a unit normal + // since the grid holds a distance level set. + Vec3Type vectorFromSurface = + d*Gradient::result(map, stencil); + Vec3d result = map.applyMap(stencil.getCenterCoord().asVec3d()) - vectorFromSurface; + + return Vec3Type(result); + } +}; + + +/// @brief Compute the mean curvature. +/// @return the mean curvature in two parts: @c alpha is the numerator in +/// @f$\nabla \cdot (\nabla \phi / |\nabla \phi|)@f$, and @c beta is @f$|\nabla \phi|@f$. +template +struct MeanCurvature +{ + /// @brief random access version + /// @return true if the gradient is none-zero, in which case the + /// mean curvature is computed as two parts: @c alpha is the numerator in + /// @f$\nabla \cdot (\nabla \phi / |\nabla \phi|)@f$, and @c beta is @f$|\nabla \phi|@f$. + template + static bool compute(const MapType& map, const Accessor& grid, const Coord& ijk, + double& alpha, double& beta) + { + typedef typename Accessor::ValueType ValueType; + + // compute the gradient in index and world space + Vec3d d1_is(D1::inX(grid, ijk), + D1::inY(grid, ijk), + 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) + { + typedef typename Accessor::ValueType 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) + { + typedef typename Accessor::ValueType ValueType; + double alpha, beta; + return compute(map, grid, ijk, alpha, beta) ? + ValueType(alpha/(2. *math::Pow2(beta))) : 0; + } + + /// @brief stencil access version + /// @return true if the gradient is none-zero, in which case the + /// mean curvature is computed as two parts: @c alpha is the numerator in + /// @f$\nabla \cdot (\nabla \phi / |\nabla \phi|)@f$, and @c beta is @f$|\nabla \phi|@f$. + template + static bool compute(const MapType& map, const StencilT& stencil, + double& alpha, double& beta) + { + typedef typename StencilT::ValueType 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) + { + typedef typename StencilT::ValueType 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) + { + typedef typename StencilT::ValueType 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) + { + typedef typename Accessor::ValueType 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) + { + typedef typename Accessor::ValueType 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) + { + typedef typename StencilT::ValueType 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) + { + typedef typename StencilT::ValueType 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) + { + typedef typename Accessor::ValueType 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) + { + typedef typename Accessor::ValueType 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) + { + typedef typename StencilT::ValueType 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) + { + typedef typename StencilT::ValueType 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) + { + typedef typename Accessor::ValueType 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) + { + typedef typename Accessor::ValueType 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) + { + typedef typename StencilT::ValueType 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) + { + typedef typename StencilT::ValueType 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(boost::const_pointer_cast(map)) {} + GenericMap(MapBase::ConstPtr map): mMap(map) {} + ~GenericMap() {} + + Vec3d applyMap(const Vec3d& in) const { return mMap->applyMap(in); } + Vec3d applyInverseMap(const Vec3d& in) const { return mMap->applyInverseMap(in); } + + Vec3d applyIJT(const Vec3d& in) const { return mMap->applyIJT(in); } + Vec3d applyIJT(const Vec3d& in, const Vec3d& pos) const { return mMap->applyIJT(in, pos); } + Mat3d applyIJC(const Mat3d& m) const { return mMap->applyIJC(m); } + Mat3d applyIJC(const Mat3d& m, const Vec3d& v, const Vec3d& pos) const + { return mMap->applyIJC(m,v,pos); } + + double determinant() const { return mMap->determinant(); } + double determinant(const Vec3d& in) const { return mMap->determinant(in); } + + Vec3d voxelSize() const { return mMap->voxelSize(); } + Vec3d voxelSize(const Vec3d&v) const { return mMap->voxelSize(v); } + +private: + MapBase::ConstPtr mMap; +}; + +} // end math namespace +} // namespace OPENVDB_VERSION_NAME +} // end openvdb namespace + +#endif // OPENVDB_MATH_OPERATORS_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Proximity.cc b/openvdb_2_3_0_library/openvdb/math/Proximity.cc new file mode 100755 index 0000000..9f327ff --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Proximity.cc @@ -0,0 +1,162 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Proximity.h b/openvdb_2_3_0_library/openvdb/math/Proximity.h new file mode 100755 index 0000000..cc0106c --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Proximity.h @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/QuantizedUnitVec.cc b/openvdb_2_3_0_library/openvdb/math/QuantizedUnitVec.cc new file mode 100755 index 0000000..b51d1cb --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/QuantizedUnitVec.cc @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "QuantizedUnitVec.h" +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + + +//////////////////////////////////////// + + +bool QuantizedUnitVec::sInitialized = false; +float QuantizedUnitVec::sNormalizationWeights[MASK_SLOTS + 1]; + +// Declare this at file scope to ensure thread-safe initialization. +tbb::mutex sInitMutex; + + +//////////////////////////////////////// + + +void +QuantizedUnitVec::init() +{ + tbb::mutex::scoped_lock lock(sInitMutex); + + if (!sInitialized) { + + OPENVDB_START_THREADSAFE_STATIC_WRITE + + sInitialized = true; + + uint16_t xbits, ybits; + double x, y, z, w; + + for (uint16_t b = 0; b < 8192; ++b) { + + xbits = (b & MASK_XSLOT) >> 7; + ybits = b & MASK_YSLOT; + + if ((xbits + ybits) > 126) { + xbits = 127 - xbits; + ybits = 127 - ybits; + } + + x = double(xbits); + y = double(ybits); + z = double(126 - ybits - xbits); + w = 1.0 / std::sqrt(x*x + y*y + z*z); + + sNormalizationWeights[b] = float(w); + } + + OPENVDB_FINISH_THREADSAFE_STATIC_WRITE + } +} + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/QuantizedUnitVec.h b/openvdb_2_3_0_library/openvdb/math/QuantizedUnitVec.h new file mode 100755 index 0000000..7eac3e2 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/QuantizedUnitVec.h @@ -0,0 +1,164 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_MATH_QUANTIZED_UNIT_VEC_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_QUANTIZED_UNIT_VEC_HAS_BEEN_INCLUDED + +#include +#include +#include "Vec3.h" +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + + +// Bit compression method that effciently represents a unit vector using +// 2 bytes i.e. 16 bits of data by only storing two quantized components. +// Based on "Higher Accuracy Quantized Normals" article from GameDev.Net LLC, 2000 + +class OPENVDB_API QuantizedUnitVec +{ +public: + + template + static uint16_t pack(const Vec3& vec); + static Vec3s unpack(const uint16_t data); + + static void flipSignBits(uint16_t&); + +private: + QuantizedUnitVec() {} + + // threadsafe initialization function for the normalization weights. + static void init(); + + // bit masks + static const uint16_t MASK_SLOTS = 0x1FFF; // 0001111111111111 + static const uint16_t MASK_XSLOT = 0x1F80; // 0001111110000000 + static const uint16_t MASK_YSLOT = 0x007F; // 0000000001111111 + static const uint16_t MASK_XSIGN = 0x8000; // 1000000000000000 + static const uint16_t MASK_YSIGN = 0x4000; // 0100000000000000 + static const uint16_t MASK_ZSIGN = 0x2000; // 0010000000000000 + + // initialization flag. + static bool sInitialized; + + // normalization weights, 32 kilobytes. + static float sNormalizationWeights[MASK_SLOTS + 1]; +}; // class QuantizedUnitVec + + +//////////////////////////////////////// + + +template +inline uint16_t +QuantizedUnitVec::pack(const Vec3& vec) +{ + uint16_t data = 0; + T x(vec[0]), y(vec[1]), z(vec[2]); + + // The sign of the three components are first stored using + // 3-bits and can then safely be discarded. + if (x < T(0.0)) { data |= MASK_XSIGN; x = -x; } + if (y < T(0.0)) { data |= MASK_YSIGN; y = -y; } + if (z < T(0.0)) { data |= MASK_ZSIGN; z = -z; } + + // The z component is discarded and x & y are quantized in + // the 0 to 126 range. + T w = T(126.0) / (x + y + z); + uint16_t xbits = uint16_t((x * w) + T(0.5)); + uint16_t ybits = uint16_t((y * w) + T(0.5)); + + // The remaining 13 bits in our 16 bit word are dividied into a + // 6-bit x-slot and a 7-bit y-slot. Both the xbits and the ybits + // can still be represented using (2^7 - 1) quantization levels. + + // If the xbits requre more than 6-bits, store the complement. + // (xbits + ybits < 127, thus if xbits > 63 => ybits <= 63) + if(xbits > 63) { + xbits = 127 - xbits; + ybits = 127 - ybits; + } + + // pack components into their respetive slot + data |= xbits << 7; + data |= ybits; + return data; +} + + +inline Vec3s +QuantizedUnitVec::unpack(const uint16_t data) +{ + if (!sInitialized) init(); + + const float w = sNormalizationWeights[data & MASK_SLOTS]; + + uint16_t xbits = (data & MASK_XSLOT) >> 7; + uint16_t ybits = data & MASK_YSLOT; + + // Check if the complement components where stored and revert. + if ((xbits + ybits) > 126) { + xbits = 127 - xbits; + ybits = 127 - ybits; + } + + Vec3s vec(float(xbits) * w, float(ybits) * w, float(126 - xbits - ybits) * w); + + if(data & MASK_XSIGN) vec[0] = -vec[0]; + if(data & MASK_YSIGN) vec[1] = -vec[1]; + if(data & MASK_ZSIGN) vec[2] = -vec[2]; + return vec; +} + + +//////////////////////////////////////// + + +inline void +QuantizedUnitVec::flipSignBits(uint16_t& v) +{ + v = (v & MASK_SLOTS) | (~v & ~MASK_SLOTS); +} + + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_QUANTIZED_UNIT_VEC_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Quat.h b/openvdb_2_3_0_library/openvdb/math/Quat.h new file mode 100755 index 0000000..5b95082 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Quat.h @@ -0,0 +1,658 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_MATH_QUAT_H_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_QUAT_H_HAS_BEEN_INCLUDED + +#include +#include + +#include "Mat.h" +#include "Mat3.h" +#include "Math.h" +#include "Vec3.h" +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +template class Quat; + +/// Linear interpolation between the two quaternions +template +Quat slerp(const Quat &q1, const Quat &q2, T t, T tolerance=0.00001) +{ + T qdot, angle, sineAngle; + + qdot = q1.dot(q2); + + if (fabs(qdot) >= 1.0) { + angle = 0; // not necessary but suppresses compiler warning + sineAngle = 0; + } else { + angle = acos(qdot); + sineAngle = sin(angle); + } + + // + // Denominator close to 0 corresponds to the case where the + // two quaternions are close to the same rotation. In this + // case linear interpolation is used but we normalize to + // guarantee unit length + // + if (sineAngle <= tolerance) { + T s = 1.0 - t; + + Quat qtemp(s * q1[0] + t * q2[0], s * q1[1] + t * q2[1], + s * q1[2] + t * q2[2], s * q1[3] + t * q2[3]); + // + // Check the case where two close to antipodal quaternions were + // blended resulting in a nearly zero result which can happen, + // for example, if t is close to 0.5. In this case it is not safe + // to project back onto the sphere. + // + double lengthSquared = qtemp.dot(qtemp); + + if (lengthSquared <= tolerance * tolerance) { + qtemp = (t < 0.5) ? q1 : q2; + } else { + qtemp *= 1.0 / sqrt(lengthSquared); + } + return qtemp; + } else { + + T sine = 1.0 / sineAngle; + T a = sin((1.0 - t) * angle) * sine; + T b = sin(t * angle) * sine; + return Quat(a * q1[0] + b * q2[0], a * q1[1] + b * q2[1], + a * q1[2] + b * q2[2], a * q1[3] + b * q2[3]); + } + +} + +template +class Quat +{ +public: + /// Trivial constructor, the quaternion is NOT initialized + Quat() {} + + /// Constructor with four arguments, e.g. Quatf q(1,2,3,4); + Quat(T x, T y, T z, T w) + { + mm[0] = x; + mm[1] = y; + mm[2] = z; + mm[3] = w; + + } + + /// Constructor with array argument, e.g. float a[4]; Quatf q(a); + Quat(T *a) + { + mm[0] = a[0]; + mm[1] = a[1]; + mm[2] = a[2]; + mm[3] = a[3]; + + } + + /// Constructor given rotation as axis and angle, the axis must be + /// unit vector + Quat(const Vec3 &axis, T angle) + { + // assert( REL_EQ(axis.length(), 1.) ); + + T s = sin(angle*T(0.5)); + + mm[0] = axis.x() * s; + mm[1] = axis.y() * s; + mm[2] = axis.z() * s; + + mm[3] = cos(angle*T(0.5)); + + } + + /// Constructor given rotation as axis and angle + Quat(math::Axis axis, T angle) + { + T s = sin(angle*T(0.5)); + + mm[0] = (axis==math::X_AXIS) * s; + mm[1] = (axis==math::Y_AXIS) * s; + mm[2] = (axis==math::Z_AXIS) * s; + + mm[3] = cos(angle*T(0.5)); + } + + /// Constructor given a rotation matrix + template + Quat(const Mat3 &rot) { + + // verify that the matrix is really a rotation + if(!isUnitary(rot)) { // unitary is reflection or rotation + OPENVDB_THROW(ArithmeticError, + "A non-rotation matrix can not be used to construct a quaternion"); + } + if (!isApproxEqual(rot.det(), (T1)1)) { // rule out reflection + OPENVDB_THROW(ArithmeticError, + "A reflection matrix can not be used to construct a quaternion"); + } + + T trace = (T)rot.trace(); + if (trace > 0) { + + T q_w = 0.5 * std::sqrt(trace+1); + T factor = 0.25 / q_w; + + mm[0] = factor * (rot(1,2) - rot(2,1)); + mm[1] = factor * (rot(2,0) - rot(0,2)); + mm[2] = factor * (rot(0,1) - rot(1,0)); + mm[3] = q_w; + } else if (rot(0,0) > rot(1,1) && rot(0,0) > rot(2,2)) { + + T q_x = 0.5 * sqrt(rot(0,0)- rot(1,1)-rot(2,2)+1); + T factor = 0.25 / q_x; + + mm[0] = q_x; + mm[1] = factor * (rot(0,1) + rot(1,0)); + mm[2] = factor * (rot(2,0) + rot(0,2)); + mm[3] = factor * (rot(1,2) - rot(2,1)); + } else if (rot(1,1) > rot(2,2)) { + + T q_y = 0.5 * sqrt(rot(1,1)-rot(0,0)-rot(2,2)+1); + T factor = 0.25 / q_y; + + mm[0] = factor * (rot(0,1) + rot(1,0)); + mm[1] = q_y; + mm[2] = factor * (rot(1,2) + rot(2,1)); + mm[3] = factor * (rot(2,0) - rot(0,2)); + } else { + + T q_z = 0.5 * sqrt(rot(2,2)-rot(0,0)-rot(1,1)+1); + T factor = 0.25 / q_z; + + mm[0] = factor * (rot(2,0) + rot(0,2)); + mm[1] = factor * (rot(1,2) + rot(2,1)); + mm[2] = q_z; + mm[3] = factor * (rot(0,1) - rot(1,0)); + } + } + + /// Copy constructor + Quat(const Quat &q) + { + mm[0] = q.mm[0]; + mm[1] = q.mm[1]; + mm[2] = q.mm[2]; + mm[3] = q.mm[3]; + + } + + /// Reference to the component, e.g. q.x() = 4.5f; + T& x() { return mm[0]; } + T& y() { return mm[1]; } + T& z() { return mm[2]; } + T& w() { return mm[3]; } + + /// Get the component, e.g. float f = q.w(); + T x() const { return mm[0]; } + T y() const { return mm[1]; } + T z() const { return mm[2]; } + T w() const { return mm[3]; } + + // Number of elements + static unsigned numElements() { return 4; } + + /// Array style reference to the components, e.g. q[3] = 1.34f; + T& operator[](int i) { return mm[i]; } + + /// Array style constant reference to the components, e.g. float f = q[1]; + T operator[](int i) const { return mm[i]; } + + /// Cast to T* + operator T*() { return mm; } + operator const T*() const { return mm; } + + /// Alternative indexed reference to the elements + T& operator()(int i) { return mm[i]; } + + /// Alternative indexed constant reference to the elements, + T operator()(int i) const { return mm[i]; } + + /// Return angle of rotation + T angle() const + { + T sqrLength = mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2]; + + if ( sqrLength > 1.0e-8 ) { + + return T(2.0) * acos(mm[3]); + + } else { + + return T(0.0); + } + } + + /// Return axis of rotation + Vec3 axis() const + { + T sqrLength = mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2]; + + if ( sqrLength > 1.0e-8 ) { + + T invLength = T(1)/sqrt(sqrLength); + + return Vec3( mm[0]*invLength, mm[1]*invLength, mm[2]*invLength ); + } else { + + return Vec3(1,0,0); + } + } + + + /// "this" quaternion gets initialized to [x, y, z, w] + Quat& init(T x, T y, T z, T w) + { + mm[0] = x; mm[1] = y; mm[2] = z; mm[3] = w; + return *this; + } + + /// "this" quaternion gets initialized to identity, same as setIdentity() + Quat& init() { return setIdentity(); } + + /// Set "this" quaternion to rotation specified by axis and angle, + /// the axis must be unit vector + Quat& setAxisAngle(const Vec3& axis, T angle) + { + + T s = sin(angle*T(0.5)); + + mm[0] = axis.x() * s; + mm[1] = axis.y() * s; + mm[2] = axis.z() * s; + + mm[3] = cos(angle*T(0.5)); + + return *this; + } // axisAngleTest + + /// Set "this" vector to zero + Quat& setZero() + { + mm[0] = mm[1] = mm[2] = mm[3] = 0; + return *this; + } + + /// Set "this" vector to identity + Quat& setIdentity() + { + mm[0] = mm[1] = mm[2] = 0; + mm[3] = 1; + return *this; + } + + /// Returns vector of x,y,z rotational components + Vec3 eulerAngles(RotationOrder rotationOrder) const + { return math::eulerAngles(Mat3(*this), rotationOrder); } + + /// Assignment operator + Quat& operator=(const Quat &q) + { + mm[0] = q.mm[0]; + mm[1] = q.mm[1]; + mm[2] = q.mm[2]; + mm[3] = q.mm[3]; + + return *this; + } + + /// Equality operator, does exact floating point comparisons + bool operator==(const Quat &q) const + { + return (isExactlyEqual(mm[0],q.mm[0]) && + isExactlyEqual(mm[1],q.mm[1]) && + isExactlyEqual(mm[2],q.mm[2]) && + isExactlyEqual(mm[3],q.mm[3]) ); + } + + /// Test if "this" is equivalent to q with tolerance of eps value + bool eq(const Quat &q, T eps=1.0e-7) const + { + return isApproxEqual(mm[0],q.mm[0],eps) && isApproxEqual(mm[1],q.mm[1],eps) && + isApproxEqual(mm[2],q.mm[2],eps) && isApproxEqual(mm[3],q.mm[3],eps) ; + } // trivial + + /// Add quaternion q to "this" quaternion, e.g. q += q1; + Quat& operator+=(const Quat &q) + { + mm[0] += q.mm[0]; + mm[1] += q.mm[1]; + mm[2] += q.mm[2]; + mm[3] += q.mm[3]; + + return *this; + } + + /// Subtract quaternion q from "this" quaternion, e.g. q -= q1; + Quat& operator-=(const Quat &q) + { + mm[0] -= q.mm[0]; + mm[1] -= q.mm[1]; + mm[2] -= q.mm[2]; + mm[3] -= q.mm[3]; + + return *this; + } + + /// Scale "this" quaternion by scalar, e.g. q *= scalar; + Quat& operator*=(T scalar) + { + mm[0] *= scalar; + mm[1] *= scalar; + mm[2] *= scalar; + mm[3] *= scalar; + + return *this; + } + + /// Return (this+q), e.g. q = q1 + q2; + Quat operator+(const Quat &q) const + { + return Quat(mm[0]+q.mm[0], mm[1]+q.mm[1], mm[2]+q.mm[2], mm[3]+q.mm[3]); + } + + /// Return (this-q), e.g. q = q1 - q2; + Quat operator-(const Quat &q) const + { + return Quat(mm[0]-q.mm[0], mm[1]-q.mm[1], mm[2]-q.mm[2], mm[3]-q.mm[3]); + } + + /// Return (this*q), e.g. q = q1 * q2; + Quat operator*(const Quat &q) const + { + Quat prod; + + prod.mm[0] = mm[3]*q.mm[0] + mm[0]*q.mm[3] + mm[1]*q.mm[2] - mm[2]*q.mm[1]; + prod.mm[1] = mm[3]*q.mm[1] + mm[1]*q.mm[3] + mm[2]*q.mm[0] - mm[0]*q.mm[2]; + prod.mm[2] = mm[3]*q.mm[2] + mm[2]*q.mm[3] + mm[0]*q.mm[1] - mm[1]*q.mm[0]; + prod.mm[3] = mm[3]*q.mm[3] - mm[0]*q.mm[0] - mm[1]*q.mm[1] - mm[2]*q.mm[2]; + + return prod; + + } + + /// Assigns this to (this*q), e.g. q *= q1; + Quat operator*=(const Quat &q) + { + *this = *this * q; + return *this; + } + + /// Return (this*scalar), e.g. q = q1 * scalar; + Quat operator*(T scalar) const + { + return Quat(mm[0]*scalar, mm[1]*scalar, mm[2]*scalar, mm[3]*scalar); + } + + /// Return (this/scalar), e.g. q = q1 / scalar; + Quat operator/(T scalar) const + { + return Quat(mm[0]/scalar, mm[1]/scalar, mm[2]/scalar, mm[3]/scalar); + } + + /// Negation operator, e.g. q = -q; + Quat operator-() const + { return Quat(-mm[0], -mm[1], -mm[2], -mm[3]); } + + /// this = q1 + q2 + /// "this", q1 and q2 need not be distinct objects, e.g. q.add(q1,q); + Quat& add(const Quat &q1, const Quat &q2) + { + mm[0] = q1.mm[0] + q2.mm[0]; + mm[1] = q1.mm[1] + q2.mm[1]; + mm[2] = q1.mm[2] + q2.mm[2]; + mm[3] = q1.mm[3] + q2.mm[3]; + + return *this; + } + + /// this = q1 - q2 + /// "this", q1 and q2 need not be distinct objects, e.g. q.sub(q1,q); + Quat& sub(const Quat &q1, const Quat &q2) + { + mm[0] = q1.mm[0] - q2.mm[0]; + mm[1] = q1.mm[1] - q2.mm[1]; + mm[2] = q1.mm[2] - q2.mm[2]; + mm[3] = q1.mm[3] - q2.mm[3]; + + return *this; + } + + /// this = q1 * q2 + /// q1 and q2 must be distinct objects than "this", e.g. q.mult(q1,q2); + Quat& mult(const Quat &q1, const Quat &q2) + { + mm[0] = q1.mm[3]*q2.mm[0] + q1.mm[0]*q2.mm[3] + + q1.mm[1]*q2.mm[2] - q1.mm[2]*q2.mm[1]; + mm[1] = q1.mm[3]*q2.mm[1] + q1.mm[1]*q2.mm[3] + + q1.mm[2]*q2.mm[0] - q1.mm[0]*q2.mm[2]; + mm[2] = q1.mm[3]*q2.mm[2] + q1.mm[2]*q2.mm[3] + + q1.mm[0]*q2.mm[1] - q1.mm[1]*q2.mm[0]; + mm[3] = q1.mm[3]*q2.mm[3] - q1.mm[0]*q2.mm[0] - + q1.mm[1]*q2.mm[1] - q1.mm[2]*q2.mm[2]; + + return *this; + } + + /// this = scalar*q, q need not be distinct object than "this", + /// e.g. q.scale(1.5,q1); + Quat& scale(T scale, const Quat &q) + { + mm[0] = scale * q.mm[0]; + mm[1] = scale * q.mm[1]; + mm[2] = scale * q.mm[2]; + mm[3] = scale * q.mm[3]; + + return *this; + } + + /// Dot product + T dot(const Quat &q) const + { + return (mm[0]*q.mm[0] + mm[1]*q.mm[1] + mm[2]*q.mm[2] + mm[3]*q.mm[3]); + } + + /// Return the quaternion rate corrsponding to the angular velocity omega + /// and "this" current rotation + Quat derivative(const Vec3& omega) const + { + return Quat( +w()*omega.x() -z()*omega.y() +y()*omega.z() , + +z()*omega.x() +w()*omega.y() -x()*omega.z() , + -y()*omega.x() +x()*omega.y() +w()*omega.z() , + -x()*omega.x() -y()*omega.y() -z()*omega.z() ); + } + + /// this = normalized this + bool normalize(T eps =1.0e-8) + { + T d = sqrt(mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2] + mm[3]*mm[3]); + if( isApproxEqual(d, T(0.0), eps) ) return false; + *this *= ( T(1)/d ); + return true; + } + + /// this = normalized this + Quat unit() const + { + T d = sqrt(mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2] + mm[3]*mm[3]); + if( isExactlyEqual(d , T(0.0) ) ) + OPENVDB_THROW(ArithmeticError, + "Normalizing degenerate quaternion"); + return *this / d; + } + + /// returns inverse of this + Quat inverse(T tolerance = 0) + { + T d = mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2] + mm[3]*mm[3]; + if( isApproxEqual(d, T(0.0), tolerance) ) + OPENVDB_THROW(ArithmeticError, + "Cannot invert degenerate quaternion"); + Quat result = *this/-d; + result.mm[3] = -result.mm[3]; + return result; + } + + + /// Return the conjugate of "this", same as invert without + /// unit quaternion test + Quat conjugate() const + { + return Quat(-mm[0], -mm[1], -mm[2], mm[3]); + } + + /// Return rotated vector by "this" quaternion + Vec3 rotateVector(const Vec3 &v) const + { + Mat3 m(*this); + return m.transform(v); + } + + /// Predefined constants, e.g. Quat q = Quat::identity(); + static Quat zero() { return Quat(0,0,0,0); } + static Quat identity() { return Quat(0,0,0,1); } + + /// @return string representation of Classname + std::string + str() const { + std::ostringstream buffer; + + buffer << "["; + + // For each column + for (unsigned j(0); j < 4; j++) { + if (j) buffer << ", "; + buffer << mm[j]; + } + + buffer << "]"; + + return buffer.str(); + } + + /// Output to the stream, e.g. std::cout << q << std::endl; + friend std::ostream& operator<<(std::ostream &stream, const Quat &q) + { + stream << q.str(); + return stream; + } + + friend Quat slerp<>(const Quat &q1, const Quat &q2, T t, T tolerance); + + + void write(std::ostream& os) const { + os.write((char*)&mm, sizeof(T)*4); + } + void read(std::istream& is) { + is.read((char*)&mm, sizeof(T)*4); + } + +protected: + T mm[4]; +}; + +/// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 3]\f$ +template +Quat operator*(S scalar, const Quat &q) { return q*scalar; } + + +/// @brief Interpolate between m1 and m2. +/// Converts to quaternion form and uses slerp +/// m1 and m2 must be rotation matrices! +template +Mat3 slerp(const Mat3 &m1, const Mat3 &m2, T t) +{ + typedef Mat3 MatType; + + Quat q1(m1); + Quat q2(m2); + + if (q1.dot(q2) < 0) q2 *= -1; + + Quat qslerp = slerp(q1, q2, static_cast(t)); + MatType m = rotation(qslerp); + return m; +} + + + +/// Interpolate between m1 and m4 by converting m1 ... m4 into +/// quaternions and treating them as control points of a Bezier +/// curve using slerp in place of lerp in the De Castlejeau evaluation +/// algorithm. Just like a cubic Bezier curve, this will interpolate +/// m1 at t = 0 and m4 at t = 1 but in general will not pass through +/// m2 and m3. Unlike a standard Bezier curve this curve will not have +/// the convex hull property. +/// m1 ... m4 must be rotation matrices! +template +Mat3 bezLerp(const Mat3 &m1, const Mat3 &m2, + const Mat3 &m3, const Mat3 &m4, + T t) +{ + Mat3 m00, m01, m02, m10, m11; + + m00 = slerp(m1, m2, t); + m01 = slerp(m2, m3, t); + m02 = slerp(m3, m4, t); + + m10 = slerp(m00, m01, t); + m11 = slerp(m01, m02, t); + + return slerp(m10, m11, t); +} + +typedef Quat Quats; +typedef Quat Quatd; + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif //OPENVDB_MATH_QUAT_H_HAS_BEEN_INCLUDED + +// --------------------------------------------------------------------------- +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Ray.h b/openvdb_2_3_0_library/openvdb/math/Ray.h new file mode 100755 index 0000000..b9ed32a --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Ray.h @@ -0,0 +1,342 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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::ostream +#include +#include // for std::numeric_limits::max() + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +template +class Ray +{ +public: + BOOST_STATIC_ASSERT(boost::is_floating_point::value); + typedef RealT RealType; + typedef Vec3 Vec3Type; + typedef Vec3Type Vec3T; + 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 then 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 t0 is strictly less then t1. + OPENVDB_DEPRECATED inline bool test() const { return mTimeSpan.valid(RealT(0)); } + + /// @brief Return @c true if t1 is larger then 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 normalize direction. + /// @details The requirement that the direction is normalized + /// follows from the transformation of t0 and t1 - and that fact that + /// we want applyMap and applyInverseMap to be inverse operations. + template + inline Ray applyMap(const MapType& map) const + { + assert(map.isLinear()); + assert(math::isApproxEqual(mDir.length(), RealT(1))); + const Vec3T eye = map.applyMap(mEye); + const Vec3T dir = map.applyJacobian(mDir); + const RealT length = dir.length(); + return Ray(eye, dir/length, length*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 normalize direction. + /// @details The requirement that the direction is normalized + /// follows from the transformation of t0 and t1 - and that fact that + /// we want applyMap and applyInverseMap to be inverse operations. + template + inline Ray applyInverseMap(const MapType& map) const + { + assert(map.isLinear()); + assert(math::isApproxEqual(mDir.length(), RealT(1))); + const Vec3T eye = map.applyInverseMap(mEye); + const Vec3T dir = map.applyInverseJacobian(mDir); + const RealT length = dir.length(); + return Ray(eye, dir/length, length*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 (size_t i = 0; i < 3; ++i) { + RealT a = (bbox.min()[i] - mEye[i]) * mInvDir[i]; + RealT b = (bbox.max()[i] - mEye[i]) * mInvDir[i]; + if (a > b) std::swap(a, b); + if (a > t0) t0 = a; + if (b < t1) t1 = b; + if (t0 > t1) return false; + } + return true; + } + + /// @brief Return true if this ray intersects the specified bounding box. + /// @param bbox Axis-aligned bounding box in the same space as this ray. + template + inline bool intersects(const BBoxT& bbox) const + { + RealT t0, t1; + return this->intersects(bbox, t0, t1); + } + + /// @brief Return true if this ray intersects the specified bounding box. + /// @note For intersection this ray is clipped to the two intersection points. + /// @param bbox Axis-aligned bounding box in the same space as this ray. + template + inline bool clip(const BBoxT& bbox) + { + RealT t0, t1; + const bool hit = this->intersects(bbox, t0, t1); + if (hit) 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 "Math.h" + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +/// @brief This class computes statistics (minimum value, maximum +/// value, mean, variance and standard deviation) of a population +/// of floating-point values. +/// +/// @details variance = Mean[ (X-Mean[X])^2 ] = Mean[X^2] - Mean[X]^2, +/// standard deviation = sqrt(variance) +/// +/// @note This class employs incremental computation and double precision. +class Stats +{ +public: + Stats(): mSize(0), mAvg(0.0), mAux(0.0), + mMin(std::numeric_limits::max()), mMax(-mMin) {} + + /// Add a single sample. + void add(double val) + { + mSize++; + mMin = std::min(val, mMin); + mMax = std::max(val, mMax); + const double delta = val - mAvg; + mAvg += delta/double(mSize); + mAux += delta*(val - mAvg); + } + + /// Add @a n samples with constant value @a val. + void add(double val, uint64_t n) + { + mMin = std::min(val, mMin); + mMax = std::max(val, mMax); + const double denom = 1.0/double(mSize + n); + const double delta = val - mAvg; + mAvg += denom*delta*n; + mAux += denom*delta*delta*mSize*n; + mSize += n; + } + + /// Add the samples from the other Stats instance. + void add(const Stats& other) + { + if (other.mSize > 0) { + mMin = std::min(mMin, other.mMin); + mMax = std::max(mMax, other.mMax); + const double denom = 1.0/double(mSize + other.mSize); + const double delta = other.mAvg - mAvg; + mAvg += denom*delta*other.mSize; + mAux += other.mAux + denom*delta*delta*mSize*other.mSize; + mSize += other.mSize; + } + } + + /// Return the size of the population, i.e., the total number of samples. + inline uint64_t size() const { return mSize; } + + /// Return the minimum value. + inline double min() const { return mMin; } + + /// Return the maximum value. + inline double max() const { return mMax; } + + //@{ + /// Return the arithmetic mean, i.e. average, value. + inline double avg() const { return mAvg; } + inline double mean() const { return mAvg; } + //@} + + //@{ + /// @brief Return the population variance. + /// @note The unbiased sample variance = population variance * + //num/(num-1) + inline double var() const { return mSize<2 ? 0.0 : mAux/double(mSize); } + inline double variance() const { return this->var(); } + //@} + + //@{ + /// @brief Return the standard deviation (=Sqrt(variance)) as + /// defined from the (biased) population variance. + inline double std() const { return sqrt(this->var()); } + inline double stdDev() const { return this->std(); } + //@} + + /// @brief Print statistics to the specified output stream. + void print(const std::string &name= "", std::ostream &strm=std::cout, int precision=3) const + { + // Write to a temporary string stream so as not to affect the state + // (precision, field width, etc.) of the output stream. + std::ostringstream os; + os << std::setprecision(precision) << std::setiosflags(std::ios::fixed); + os << "Statistics "; + if (!name.empty()) os << "for \"" << name << "\" "; + if (mSize>0) { + os << "with " << mSize << " samples:\n" + << " Min=" << mMin + << ", Max=" << mMax + << ", Ave=" << mAvg + << ", Std=" << this->stdDev() + << ", Var=" << this->variance() << std::endl; + } else { + os << ": no samples were added." << std::endl; + } + strm << os.str(); + } + +private: + uint64_t mSize; + double mAvg, mAux, mMin, mMax; +}; // end Stats + + +//////////////////////////////////////// + + +/// @brief This class computes a histogram, with a fixed interval width, +/// of a population of floating-point values. +class Histogram +{ +public: + /// Construct with given minimum and maximum values and the given bin count. + Histogram(double min, double max, size_t numBins = 10) + : mSize(0), mMin(min), mMax(max+1e-10), + mDelta(double(numBins)/(max-min)), mBins(numBins) + { + assert(numBins > 1); + assert(mMax-mMin > 1e-10); + for (size_t i=0; i 1); + assert(mMax-mMin > 1e-10); + for (size_t i=0; imMax) return false; + mBins[size_t(mDelta*(val-mMin))] += n; + mSize += n; + return true; + } + + /// @brief Add all the contributions from the other histogram, provided that + /// it has the same configuration as this histogram. + bool add(const Histogram& other) + { + if (!isApproxEqual(mMin, other.mMin) || !isApproxEqual(mMax, other.mMax) || + mBins.size() != other.mBins.size()) return false; + for (size_t i=0, e=mBins.size(); i!=e; ++i) mBins[i] += other.mBins[i]; + mSize += other.mSize; + return true; + } + + /// Return the number of bins in this histogram. + inline size_t numBins() const { return mBins.size(); } + /// Return the lower bound of this histogram's value range. + inline double min() const { return mMin; } + /// Return the upper bound of this histogram's value range. + inline double max() const { return mMax; } + /// Return the minimum value in the nth bin. + inline double min(int n) const { return mMin+n/mDelta; } + /// Return the maximum value in the nth bin. + inline double max(int n) const { return mMin+(n+1)/mDelta; } + /// Return the number of samples in the nth bin. + inline uint64_t count(int n) const { return mBins[n]; } + /// Return the population size, i.e., the total number of samples. + inline uint64_t size() const { return mSize; } + + /// Print the histogram to the specified output stream. + void print(const std::string& name = "", std::ostream& strm = std::cout) const + { + // Write to a temporary string stream so as not to affect the state + // (precision, field width, etc.) of the output stream. + std::ostringstream os; + os << std::setprecision(6) << std::setiosflags(std::ios::fixed) << std::endl; + os << "Histogram "; + if (!name.empty()) os << "for \"" << name << "\" "; + if (mSize > 0) { + os << "with " << mSize << " samples:\n"; + os << "==============================================================\n"; + os << "|| # | Min | Max | Frequency | % ||\n"; + os << "==============================================================\n"; + for (size_t i=0, e=mBins.size(); i!=e; ++i) { + os << "|| " << std::setw(4) << i << " | " << std::setw(14) << this->min(i) << " | " + << std::setw(14) << this->max(i) << " | " << std::setw(9) << mBins[i] << " | " + << std::setw(3) << (100*mBins[i]/mSize) << " ||\n"; + } + os << "==============================================================\n"; + } else { + os << ": no samples were added." << std::endl; + } + strm << os.str(); + } + +private: + uint64_t mSize; + double mMin, mMax, mDelta; + std::vector mBins; +}; + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_STATS_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Stencils.h b/openvdb_2_3_0_library/openvdb/math/Stencils.h new file mode 100755 index 0000000..6aa678e --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Stencils.h @@ -0,0 +1,1648 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @author Ken Museth +/// @file Stencils.h + +#ifndef OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED + +#include +#include +#include // for Pow2, needed by WENO and Gudonov +#include // for Real +#include // for Coord +#include // for WENO5 and GudonovsNormSqrd + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + + +//////////////////////////////////////// + + +template +class BaseStencil +{ +public: + typedef _GridType GridType; + typedef typename GridType::TreeType TreeType; + typedef typename GridType::ValueType ValueType; + typedef std::vector BufferType; + typedef typename BufferType::iterator IterType; + typedef typename GridType::ConstAccessor AccessorType; + + /// @brief Initialize the stencil buffer with the values of voxel (i, j, k) + /// and its neighbors. + /// @param ijk Index coordinates of stencil center + inline void moveTo(const Coord& ijk) + { + mCenter = ijk; + mStencil[0] = mCache.getValue(ijk); + static_cast(*this).init(mCenter); + } + + /// @brief Initialize the stencil buffer with the values of voxel (i, j, k) + /// and its neighbors. The method also takes a value of the center + /// element of the stencil, assuming it is already known. + /// @param ijk Index coordinates of stnecil center + /// @param centerValue Value of the center element of the stencil + inline void moveTo(const Coord& ijk, const ValueType& centerValue) + { + mCenter = ijk; + mStencil[0] = centerValue; + static_cast(*this).init(mCenter); + } + + /// @brief Initialize the stencil buffer with the values of voxel + /// (x, y, z) and its neighbors. + /// + /// @note This version is slightly faster than the one above, since + /// the center voxel's value is read directly from the iterator. + template + inline void moveTo(const IterType& iter) + { + mCenter = iter.getCoord(); + mStencil[0] = *iter; + static_cast(*this).init(mCenter); + } + + /// @brief Initialize the stencil buffer with the values of voxel (x, y, z) + /// and its neighbors. + /// @param xyz Floating point voxel coordinates of stencil center + /// @details This method will check to see if it is necessary to + /// update the stencil based on the cached index coordinates of + /// the center point. + inline void moveTo(const Vec3R& xyz) + { + Coord ijk = openvdb::Coord::floor(xyz); + if (ijk != mCenter) this->moveTo(ijk); + } + + /// @brief Return the value from the stencil buffer with linear + /// offset pos. + /// + /// @note The default (@a pos = 0) corresponds to the first element + /// which is typically the center point of the stencil. + inline const ValueType& getValue(unsigned int pos = 0) const + { + assert(pos < mStencil.size()); + return mStencil[pos]; + } + + /// @brief Return the value at the specified location relative to the center of the stencil + template + inline const ValueType& getValue() const + { + return mStencil[static_cast(*this).template pos()]; + } + + /// @brief Set the value at the specified location relative to the center of the stencil + template + inline void setValue(const ValueType& value) + { + mStencil[static_cast(*this).template pos()] = value; + } + + /// @brief Return the size of the stencil buffer. + inline int size() { return mStencil.size(); } + + /// @brief Return the median value of the current stencil. + inline ValueType median() const + { + std::vector tmp(mStencil);//local copy + assert(!tmp.empty()); + size_t midpoint = (tmp.size() - 1) >> 1; + // Partially sort the vector until the median value is at the midpoint. + std::nth_element(tmp.begin(), tmp.begin() + midpoint, tmp.end()); + return tmp[midpoint]; + } + + /// @brief Return the mean value of the current stencil. + inline ValueType mean() const + { + ValueType sum = 0.0; + for (int n=0, s=mStencil.size(); n()) const + { + const bool less = this->getValue< 0, 0, 0>() < isoValue; + return (less ^ (this->getValue<-1, 0, 0>() < isoValue)) || + (less ^ (this->getValue< 1, 0, 0>() < isoValue)) || + (less ^ (this->getValue< 0,-1, 0>() < isoValue)) || + (less ^ (this->getValue< 0, 1, 0>() < isoValue)) || + (less ^ (this->getValue< 0, 0,-1>() < isoValue)) || + (less ^ (this->getValue< 0, 0, 1>() < isoValue)) ; + } + + /// @brief Return a const reference to the grid from which this + /// stencil was constructed. + inline const GridType& grid() const { return *mGrid; } + + /// @brief Return a const reference to the ValueAccessor + /// associated with this Stencil. + inline const AccessorType& accessor() const { return mCache; } + +protected: + // Constructor is protected to prevent direct instantiation. + BaseStencil(const GridType& grid, int size): + mGrid(&grid), mCache(grid.getConstAccessor()), + mStencil(size), mCenter(Coord::max()) + { + } + + const GridType* mGrid; + AccessorType mCache; + BufferType mStencil; + Coord mCenter; + +}; // class BaseStencil + + +//////////////////////////////////////// + + +namespace { // anonymous namespace for stencil-layout map + + // the seven point stencil + template struct SevenPt {}; + template<> struct SevenPt< 0, 0, 0> { enum { idx = 0 }; }; + template<> struct SevenPt< 1, 0, 0> { enum { idx = 1 }; }; + template<> struct SevenPt< 0, 1, 0> { enum { idx = 2 }; }; + template<> struct SevenPt< 0, 0, 1> { enum { idx = 3 }; }; + template<> struct SevenPt<-1, 0, 0> { enum { idx = 4 }; }; + template<> struct SevenPt< 0,-1, 0> { enum { idx = 5 }; }; + template<> struct SevenPt< 0, 0,-1> { enum { idx = 6 }; }; + +} + + +template +class SevenPointStencil: public BaseStencil > +{ +public: + typedef BaseStencil > BaseType; + typedef typename BaseType::BufferType BufferType; + typedef typename GridType::ValueType ValueType; + typedef math::Vec3 Vec3Type; + static const int SIZE = 7; + + SevenPointStencil(const GridType& grid): BaseType(grid, SIZE) {} + + /// Return linear offset for the specified stencil point relative to its center + template + unsigned int pos() const { return SevenPt::idx; } + +private: + inline void init(const Coord& ijk) + { + BaseType::template setValue<-1, 0, 0>(mCache.getValue(ijk.offsetBy(-1, 0, 0))); + BaseType::template setValue< 1, 0, 0>(mCache.getValue(ijk.offsetBy( 1, 0, 0))); + + BaseType::template setValue< 0,-1, 0>(mCache.getValue(ijk.offsetBy( 0,-1, 0))); + BaseType::template setValue< 0, 1, 0>(mCache.getValue(ijk.offsetBy( 0, 1, 0))); + + BaseType::template setValue< 0, 0,-1>(mCache.getValue(ijk.offsetBy( 0, 0,-1))); + BaseType::template setValue< 0, 0, 1>(mCache.getValue(ijk.offsetBy( 0, 0, 1))); + } + + template friend class BaseStencil; // allow base class to call init() + using BaseType::mCache; + using BaseType::mStencil; +}; + + +//////////////////////////////////////// + + +namespace { // anonymous namespace for stencil-layout map + + // the eight point box stencil + template struct BoxPt {}; + template<> struct BoxPt< 0, 0, 0> { enum { idx = 0 }; }; + template<> struct BoxPt< 0, 0, 1> { enum { idx = 1 }; }; + template<> struct BoxPt< 0, 1, 1> { enum { idx = 2 }; }; + template<> struct BoxPt< 0, 1, 0> { enum { idx = 3 }; }; + template<> struct BoxPt< 1, 0, 0> { enum { idx = 4 }; }; + template<> struct BoxPt< 1, 0, 1> { enum { idx = 5 }; }; + template<> struct BoxPt< 1, 1, 1> { enum { idx = 6 }; }; + template<> struct BoxPt< 1, 1, 0> { enum { idx = 7 }; }; +} + +template +class BoxStencil: public BaseStencil > +{ +public: + typedef BaseStencil > BaseType; + typedef typename BaseType::BufferType BufferType; + typedef typename GridType::ValueType ValueType; + typedef math::Vec3 Vec3Type; + static const int SIZE = 8; + + BoxStencil(const GridType& grid): BaseType(grid, SIZE) {} + + /// Return linear offset for the specified stencil point relative to its center + template + unsigned int pos() const { return BoxPt::idx; } + + /// @brief Return true if the center of the stencil intersects the + /// iso-contour specified by the isoValue + inline bool intersects(const ValueType &isoValue = zeroVal()) const + { + const bool less = mStencil[0] < isoValue; + return (less ^ (mStencil[1] < isoValue)) || + (less ^ (mStencil[2] < isoValue)) || + (less ^ (mStencil[3] < isoValue)) || + (less ^ (mStencil[4] < isoValue)) || + (less ^ (mStencil[5] < isoValue)) || + (less ^ (mStencil[6] < isoValue)) || + (less ^ (mStencil[7] < isoValue)) ; + } + + /// @brief Return the trilinear interpolation at the normalized position. + /// @param xyz Floating point coordinate position. + /// @warning It is assumed that the stencil has already been moved + /// to the relevant voxel position, e.g. using moveTo(xyz). + /// @note Trilinear interpolation kernal reads as: + /// v000 (1-u)(1-v)(1-w) + v001 (1-u)(1-v)w + v010 (1-u)v(1-w) + v011 (1-u)vw + /// + v100 u(1-v)(1-w) + v101 u(1-v)w + v110 uv(1-w) + v111 uvw + inline ValueType interpolation(const Vec3Type& xyz) const + { + const Real u = xyz[0] - BaseType::mCenter[0]; assert(u>=0 && u<=1); + const Real v = xyz[1] - BaseType::mCenter[1]; assert(v>=0 && v<=1); + const Real w = xyz[2] - BaseType::mCenter[2]; assert(w>=0 && w<=1); + + ValueType V = BaseType::template getValue<0,0,0>(); + ValueType A = V + (BaseType::template getValue<0,0,1>() - V) * w; + V = BaseType::template getValue< 0, 1, 0>(); + ValueType B = V + (BaseType::template getValue<0,1,1>() - V) * w; + ValueType C = A + (B - A) * v; + + V = BaseType::template getValue<1,0,0>(); + A = V + (BaseType::template getValue<1,0,1>() - V) * w; + V = BaseType::template getValue<1,1,0>(); + B = V + (BaseType::template getValue<1,1,1>() - V) * w; + ValueType D = A + (B - A) * v; + + return C + (D - C) * u; + } + + /// @brief Return the gradient in world space of the trilinear interpolation kernel. + /// @param xyz Floating point coordinate position. + /// @warning It is assumed that the stencil has already been moved + /// to the relevant voxel position, e.g. using moveTo(xyz). + /// @note Computed as partial derivatives of the trilinear interpolation kernel: + /// v000 (1-u)(1-v)(1-w) + v001 (1-u)(1-v)w + v010 (1-u)v(1-w) + v011 (1-u)vw + /// + v100 u(1-v)(1-w) + v101 u(1-v)w + v110 uv(1-w) + v111 uvw + inline Vec3Type gradient(const Vec3Type& xyz) const + { + const Real u = xyz[0] - BaseType::mCenter[0]; assert(u>=0 && u<=1); + const Real v = xyz[1] - BaseType::mCenter[1]; assert(v>=0 && v<=1); + const Real w = xyz[2] - BaseType::mCenter[2]; assert(w>=0 && w<=1); + + ValueType D[4]={BaseType::template getValue<0,0,1>()-BaseType::template getValue<0,0,0>(), + BaseType::template getValue<0,1,1>()-BaseType::template getValue<0,1,0>(), + BaseType::template getValue<1,0,1>()-BaseType::template getValue<1,0,0>(), + BaseType::template getValue<1,1,1>()-BaseType::template getValue<1,1,0>()}; + + // Z component + ValueType A = D[0] + (D[1]- D[0]) * v; + ValueType B = D[2] + (D[3]- D[2]) * v; + Vec3Type grad(zeroVal(), zeroVal(), A + (B - A) * u); + + D[0] = BaseType::template getValue<0,0,0>() + D[0] * w; + D[1] = BaseType::template getValue<0,1,0>() + D[1] * w; + D[2] = BaseType::template getValue<1,0,0>() + D[2] * w; + D[3] = BaseType::template getValue<1,1,0>() + D[3] * w; + + // X component + A = D[0] + (D[1] - D[0]) * v; + B = D[2] + (D[3] - D[2]) * v; + + grad[0] = B - A; + + // Y component + A = D[1] - D[0]; + B = D[3] - D[2]; + + grad[1] = A + (B - A) * u; + + return BaseType::mGrid->transform().baseMap()->applyIJT(grad, xyz); + } + +private: + inline void init(const Coord& ijk) + { + BaseType::template setValue< 0, 0, 1>(mCache.getValue(ijk.offsetBy( 0, 0, 1))); + BaseType::template setValue< 0, 1, 1>(mCache.getValue(ijk.offsetBy( 0, 1, 1))); + BaseType::template setValue< 0, 1, 0>(mCache.getValue(ijk.offsetBy( 0, 1, 0))); + BaseType::template setValue< 1, 0, 0>(mCache.getValue(ijk.offsetBy( 1, 0, 0))); + BaseType::template setValue< 1, 0, 1>(mCache.getValue(ijk.offsetBy( 1, 0, 1))); + BaseType::template setValue< 1, 1, 1>(mCache.getValue(ijk.offsetBy( 1, 1, 1))); + BaseType::template setValue< 1, 1, 0>(mCache.getValue(ijk.offsetBy( 1, 1, 0))); + } + + template friend class BaseStencil; // allow base class to call init() + using BaseType::mCache; + using BaseType::mStencil; +}; + + +//////////////////////////////////////// + + +namespace { // anonymous namespace for stencil-layout map + + // the dense point stencil + template struct DensePt {}; + template<> struct DensePt< 0, 0, 0> { enum { idx = 0 }; }; + + template<> struct DensePt< 1, 0, 0> { enum { idx = 1 }; }; + template<> struct DensePt< 0, 1, 0> { enum { idx = 2 }; }; + template<> struct DensePt< 0, 0, 1> { enum { idx = 3 }; }; + + template<> struct DensePt<-1, 0, 0> { enum { idx = 4 }; }; + template<> struct DensePt< 0,-1, 0> { enum { idx = 5 }; }; + template<> struct DensePt< 0, 0,-1> { enum { idx = 6 }; }; + + template<> struct DensePt<-1,-1, 0> { enum { idx = 7 }; }; + template<> struct DensePt< 0,-1,-1> { enum { idx = 8 }; }; + template<> struct DensePt<-1, 0,-1> { enum { idx = 9 }; }; + + template<> struct DensePt< 1,-1, 0> { enum { idx = 10 }; }; + template<> struct DensePt< 0, 1,-1> { enum { idx = 11 }; }; + template<> struct DensePt<-1, 0, 1> { enum { idx = 12 }; }; + + template<> struct DensePt<-1, 1, 0> { enum { idx = 13 }; }; + template<> struct DensePt< 0,-1, 1> { enum { idx = 14 }; }; + template<> struct DensePt< 1, 0,-1> { enum { idx = 15 }; }; + + template<> struct DensePt< 1, 1, 0> { enum { idx = 16 }; }; + template<> struct DensePt< 0, 1, 1> { enum { idx = 17 }; }; + template<> struct DensePt< 1, 0, 1> { enum { idx = 18 }; }; + +} + + +template +class SecondOrderDenseStencil: public BaseStencil > +{ +public: + typedef BaseStencil > BaseType; + typedef typename BaseType::BufferType BufferType; + typedef typename GridType::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + static const int SIZE = 19; + + SecondOrderDenseStencil(const GridType& grid): BaseType(grid, SIZE) {} + + /// Return linear offset for the specified stencil point relative to its center + template + unsigned int pos() const { return DensePt::idx; } + +private: + inline void init(const Coord& ijk) + { + mStencil[DensePt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); + mStencil[DensePt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); + mStencil[DensePt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); + + mStencil[DensePt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); + mStencil[DensePt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); + mStencil[DensePt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); + + mStencil[DensePt<-1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, -1, 0)); + mStencil[DensePt< 1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, -1, 0)); + mStencil[DensePt<-1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 1, 0)); + mStencil[DensePt< 1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 1, 0)); + + mStencil[DensePt<-1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, -1)); + mStencil[DensePt< 1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, -1)); + mStencil[DensePt<-1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 1)); + mStencil[DensePt< 1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 1)); + + mStencil[DensePt< 0,-1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, -1)); + mStencil[DensePt< 0, 1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, -1)); + mStencil[DensePt< 0,-1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 1)); + mStencil[DensePt< 0, 1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 1)); + } + + template friend class BaseStencil; // allow base class to call init() + using BaseType::mCache; + using BaseType::mStencil; +}; + + +//////////////////////////////////////// + + +namespace { // anonymous namespace for stencil-layout map + + // the dense point stencil + template struct ThirteenPt {}; + template<> struct ThirteenPt< 0, 0, 0> { enum { idx = 0 }; }; + + template<> struct ThirteenPt< 1, 0, 0> { enum { idx = 1 }; }; + template<> struct ThirteenPt< 0, 1, 0> { enum { idx = 2 }; }; + template<> struct ThirteenPt< 0, 0, 1> { enum { idx = 3 }; }; + + template<> struct ThirteenPt<-1, 0, 0> { enum { idx = 4 }; }; + template<> struct ThirteenPt< 0,-1, 0> { enum { idx = 5 }; }; + template<> struct ThirteenPt< 0, 0,-1> { enum { idx = 6 }; }; + + template<> struct ThirteenPt< 2, 0, 0> { enum { idx = 7 }; }; + template<> struct ThirteenPt< 0, 2, 0> { enum { idx = 8 }; }; + template<> struct ThirteenPt< 0, 0, 2> { enum { idx = 9 }; }; + + template<> struct ThirteenPt<-2, 0, 0> { enum { idx = 10 }; }; + template<> struct ThirteenPt< 0,-2, 0> { enum { idx = 11 }; }; + template<> struct ThirteenPt< 0, 0,-2> { enum { idx = 12 }; }; + +} + + +template +class ThirteenPointStencil: public BaseStencil > +{ +public: + typedef BaseStencil > BaseType; + typedef typename BaseType::BufferType BufferType; + typedef typename GridType::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + static const int SIZE = 13; + + ThirteenPointStencil(const GridType& grid): BaseType(grid, SIZE) {} + + /// Return linear offset for the specified stencil point relative to its center + template + unsigned int pos() const { return ThirteenPt::idx; } + +private: + inline void init(const Coord& ijk) + { + mStencil[ThirteenPt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); + mStencil[ThirteenPt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); + mStencil[ThirteenPt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); + mStencil[ThirteenPt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); + + mStencil[ThirteenPt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); + mStencil[ThirteenPt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); + mStencil[ThirteenPt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); + mStencil[ThirteenPt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -2, 0)); + + mStencil[ThirteenPt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); + mStencil[ThirteenPt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); + mStencil[ThirteenPt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); + mStencil[ThirteenPt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -2)); + } + + template friend class BaseStencil; // allow base class to call init() + using BaseType::mCache; + using BaseType::mStencil; +}; + + +//////////////////////////////////////// + + +namespace { // anonymous namespace for stencil-layout map + + // the 4th-order dense point stencil + template struct FourthDensePt {}; + template<> struct FourthDensePt< 0, 0, 0> { enum { idx = 0 }; }; + + template<> struct FourthDensePt<-2, 2, 0> { enum { idx = 1 }; }; + template<> struct FourthDensePt<-1, 2, 0> { enum { idx = 2 }; }; + template<> struct FourthDensePt< 0, 2, 0> { enum { idx = 3 }; }; + template<> struct FourthDensePt< 1, 2, 0> { enum { idx = 4 }; }; + template<> struct FourthDensePt< 2, 2, 0> { enum { idx = 5 }; }; + + template<> struct FourthDensePt<-2, 1, 0> { enum { idx = 6 }; }; + template<> struct FourthDensePt<-1, 1, 0> { enum { idx = 7 }; }; + template<> struct FourthDensePt< 0, 1, 0> { enum { idx = 8 }; }; + template<> struct FourthDensePt< 1, 1, 0> { enum { idx = 9 }; }; + template<> struct FourthDensePt< 2, 1, 0> { enum { idx = 10 }; }; + + template<> struct FourthDensePt<-2, 0, 0> { enum { idx = 11 }; }; + template<> struct FourthDensePt<-1, 0, 0> { enum { idx = 12 }; }; + template<> struct FourthDensePt< 1, 0, 0> { enum { idx = 13 }; }; + template<> struct FourthDensePt< 2, 0, 0> { enum { idx = 14 }; }; + + template<> struct FourthDensePt<-2,-1, 0> { enum { idx = 15 }; }; + template<> struct FourthDensePt<-1,-1, 0> { enum { idx = 16 }; }; + template<> struct FourthDensePt< 0,-1, 0> { enum { idx = 17 }; }; + template<> struct FourthDensePt< 1,-1, 0> { enum { idx = 18 }; }; + template<> struct FourthDensePt< 2,-1, 0> { enum { idx = 19 }; }; + + template<> struct FourthDensePt<-2,-2, 0> { enum { idx = 20 }; }; + template<> struct FourthDensePt<-1,-2, 0> { enum { idx = 21 }; }; + template<> struct FourthDensePt< 0,-2, 0> { enum { idx = 22 }; }; + template<> struct FourthDensePt< 1,-2, 0> { enum { idx = 23 }; }; + template<> struct FourthDensePt< 2,-2, 0> { enum { idx = 24 }; }; + + + template<> struct FourthDensePt<-2, 0, 2> { enum { idx = 25 }; }; + template<> struct FourthDensePt<-1, 0, 2> { enum { idx = 26 }; }; + template<> struct FourthDensePt< 0, 0, 2> { enum { idx = 27 }; }; + template<> struct FourthDensePt< 1, 0, 2> { enum { idx = 28 }; }; + template<> struct FourthDensePt< 2, 0, 2> { enum { idx = 29 }; }; + + template<> struct FourthDensePt<-2, 0, 1> { enum { idx = 30 }; }; + template<> struct FourthDensePt<-1, 0, 1> { enum { idx = 31 }; }; + template<> struct FourthDensePt< 0, 0, 1> { enum { idx = 32 }; }; + template<> struct FourthDensePt< 1, 0, 1> { enum { idx = 33 }; }; + template<> struct FourthDensePt< 2, 0, 1> { enum { idx = 34 }; }; + + template<> struct FourthDensePt<-2, 0,-1> { enum { idx = 35 }; }; + template<> struct FourthDensePt<-1, 0,-1> { enum { idx = 36 }; }; + template<> struct FourthDensePt< 0, 0,-1> { enum { idx = 37 }; }; + template<> struct FourthDensePt< 1, 0,-1> { enum { idx = 38 }; }; + template<> struct FourthDensePt< 2, 0,-1> { enum { idx = 39 }; }; + + template<> struct FourthDensePt<-2, 0,-2> { enum { idx = 40 }; }; + template<> struct FourthDensePt<-1, 0,-2> { enum { idx = 41 }; }; + template<> struct FourthDensePt< 0, 0,-2> { enum { idx = 42 }; }; + template<> struct FourthDensePt< 1, 0,-2> { enum { idx = 43 }; }; + template<> struct FourthDensePt< 2, 0,-2> { enum { idx = 44 }; }; + + + template<> struct FourthDensePt< 0,-2, 2> { enum { idx = 45 }; }; + template<> struct FourthDensePt< 0,-1, 2> { enum { idx = 46 }; }; + template<> struct FourthDensePt< 0, 1, 2> { enum { idx = 47 }; }; + template<> struct FourthDensePt< 0, 2, 2> { enum { idx = 48 }; }; + + template<> struct FourthDensePt< 0,-2, 1> { enum { idx = 49 }; }; + template<> struct FourthDensePt< 0,-1, 1> { enum { idx = 50 }; }; + template<> struct FourthDensePt< 0, 1, 1> { enum { idx = 51 }; }; + template<> struct FourthDensePt< 0, 2, 1> { enum { idx = 52 }; }; + + template<> struct FourthDensePt< 0,-2,-1> { enum { idx = 53 }; }; + template<> struct FourthDensePt< 0,-1,-1> { enum { idx = 54 }; }; + template<> struct FourthDensePt< 0, 1,-1> { enum { idx = 55 }; }; + template<> struct FourthDensePt< 0, 2,-1> { enum { idx = 56 }; }; + + template<> struct FourthDensePt< 0,-2,-2> { enum { idx = 57 }; }; + template<> struct FourthDensePt< 0,-1,-2> { enum { idx = 58 }; }; + template<> struct FourthDensePt< 0, 1,-2> { enum { idx = 59 }; }; + template<> struct FourthDensePt< 0, 2,-2> { enum { idx = 60 }; }; + +} + + +template +class FourthOrderDenseStencil: public BaseStencil > +{ +public: + typedef BaseStencil > BaseType; + typedef typename BaseType::BufferType BufferType; + typedef typename GridType::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + static const int SIZE = 61; + + FourthOrderDenseStencil(const GridType& grid): BaseType(grid, SIZE) {} + + /// Return linear offset for the specified stencil point relative to its center + template + unsigned int pos() const { return FourthDensePt::idx; } + +private: + inline void init(const Coord& ijk) + { + mStencil[FourthDensePt<-2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 2, 0)); + mStencil[FourthDensePt<-1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 2, 0)); + mStencil[FourthDensePt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); + mStencil[FourthDensePt< 1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 2, 0)); + mStencil[FourthDensePt< 2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 2, 0)); + + mStencil[FourthDensePt<-2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 1, 0)); + mStencil[FourthDensePt<-1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 1, 0)); + mStencil[FourthDensePt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); + mStencil[FourthDensePt< 1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 1, 0)); + mStencil[FourthDensePt< 2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 1, 0)); + + mStencil[FourthDensePt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); + mStencil[FourthDensePt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); + mStencil[FourthDensePt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); + mStencil[FourthDensePt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); + + mStencil[FourthDensePt<-2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-1, 0)); + mStencil[FourthDensePt<-1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-1, 0)); + mStencil[FourthDensePt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 0)); + mStencil[FourthDensePt< 1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-1, 0)); + mStencil[FourthDensePt< 2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-1, 0)); + + mStencil[FourthDensePt<-2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-2, 0)); + mStencil[FourthDensePt<-1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-2, 0)); + mStencil[FourthDensePt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 0)); + mStencil[FourthDensePt< 1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-2, 0)); + mStencil[FourthDensePt< 2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-2, 0)); + + mStencil[FourthDensePt<-2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 2)); + mStencil[FourthDensePt<-1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 2)); + mStencil[FourthDensePt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); + mStencil[FourthDensePt< 1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 2)); + mStencil[FourthDensePt< 2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 2)); + + mStencil[FourthDensePt<-2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 1)); + mStencil[FourthDensePt<-1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 1)); + mStencil[FourthDensePt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); + mStencil[FourthDensePt< 1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 1)); + mStencil[FourthDensePt< 2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 1)); + + mStencil[FourthDensePt<-2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-1)); + mStencil[FourthDensePt<-1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-1)); + mStencil[FourthDensePt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-1)); + mStencil[FourthDensePt< 1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-1)); + mStencil[FourthDensePt< 2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-1)); + + mStencil[FourthDensePt<-2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-2)); + mStencil[FourthDensePt<-1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-2)); + mStencil[FourthDensePt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-2)); + mStencil[FourthDensePt< 1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-2)); + mStencil[FourthDensePt< 2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-2)); + + + mStencil[FourthDensePt< 0,-2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 2)); + mStencil[FourthDensePt< 0,-1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 2)); + mStencil[FourthDensePt< 0, 1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 2)); + mStencil[FourthDensePt< 0, 2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 2)); + + mStencil[FourthDensePt< 0,-2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 1)); + mStencil[FourthDensePt< 0,-1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 1)); + mStencil[FourthDensePt< 0, 1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 1)); + mStencil[FourthDensePt< 0, 2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 1)); + + mStencil[FourthDensePt< 0,-2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-1)); + mStencil[FourthDensePt< 0,-1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-1)); + mStencil[FourthDensePt< 0, 1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-1)); + mStencil[FourthDensePt< 0, 2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-1)); + + mStencil[FourthDensePt< 0,-2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-2)); + mStencil[FourthDensePt< 0,-1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-2)); + mStencil[FourthDensePt< 0, 1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-2)); + mStencil[FourthDensePt< 0, 2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-2)); + } + + template friend class BaseStencil; // allow base class to call init() + using BaseType::mCache; + using BaseType::mStencil; +}; + + +//////////////////////////////////////// + + +namespace { // anonymous namespace for stencil-layout map + + // the dense point stencil + template struct NineteenPt {}; + template<> struct NineteenPt< 0, 0, 0> { enum { idx = 0 }; }; + + template<> struct NineteenPt< 1, 0, 0> { enum { idx = 1 }; }; + template<> struct NineteenPt< 0, 1, 0> { enum { idx = 2 }; }; + template<> struct NineteenPt< 0, 0, 1> { enum { idx = 3 }; }; + + template<> struct NineteenPt<-1, 0, 0> { enum { idx = 4 }; }; + template<> struct NineteenPt< 0,-1, 0> { enum { idx = 5 }; }; + template<> struct NineteenPt< 0, 0,-1> { enum { idx = 6 }; }; + + template<> struct NineteenPt< 2, 0, 0> { enum { idx = 7 }; }; + template<> struct NineteenPt< 0, 2, 0> { enum { idx = 8 }; }; + template<> struct NineteenPt< 0, 0, 2> { enum { idx = 9 }; }; + + template<> struct NineteenPt<-2, 0, 0> { enum { idx = 10 }; }; + template<> struct NineteenPt< 0,-2, 0> { enum { idx = 11 }; }; + template<> struct NineteenPt< 0, 0,-2> { enum { idx = 12 }; }; + + template<> struct NineteenPt< 3, 0, 0> { enum { idx = 13 }; }; + template<> struct NineteenPt< 0, 3, 0> { enum { idx = 14 }; }; + template<> struct NineteenPt< 0, 0, 3> { enum { idx = 15 }; }; + + template<> struct NineteenPt<-3, 0, 0> { enum { idx = 16 }; }; + template<> struct NineteenPt< 0,-3, 0> { enum { idx = 17 }; }; + template<> struct NineteenPt< 0, 0,-3> { enum { idx = 18 }; }; + +} + + +template +class NineteenPointStencil: public BaseStencil > +{ +public: + typedef BaseStencil > BaseType; + typedef typename BaseType::BufferType BufferType; + typedef typename GridType::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + static const int SIZE = 19; + + NineteenPointStencil(const GridType& grid): BaseType(grid, SIZE) {} + + /// Return linear offset for the specified stencil point relative to its center + template + unsigned int pos() const { return NineteenPt::idx; } + +private: + inline void init(const Coord& ijk) + { + mStencil[NineteenPt< 3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 0)); + mStencil[NineteenPt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); + mStencil[NineteenPt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); + mStencil[NineteenPt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); + mStencil[NineteenPt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); + mStencil[NineteenPt<-3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 0)); + + mStencil[NineteenPt< 0, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 0)); + mStencil[NineteenPt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); + mStencil[NineteenPt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); + mStencil[NineteenPt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); + mStencil[NineteenPt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -2, 0)); + mStencil[NineteenPt< 0,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -3, 0)); + + mStencil[NineteenPt< 0, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 3)); + mStencil[NineteenPt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); + mStencil[NineteenPt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); + mStencil[NineteenPt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); + mStencil[NineteenPt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -2)); + mStencil[NineteenPt< 0, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -3)); + } + + template friend class BaseStencil; // allow base class to call init() + using BaseType::mCache; + using BaseType::mStencil; +}; + + +//////////////////////////////////////// + + +namespace { // anonymous namespace for stencil-layout map + + // the 4th-order dense point stencil + template struct SixthDensePt { }; + template<> struct SixthDensePt< 0, 0, 0> { enum { idx = 0 }; }; + + template<> struct SixthDensePt<-3, 3, 0> { enum { idx = 1 }; }; + template<> struct SixthDensePt<-2, 3, 0> { enum { idx = 2 }; }; + template<> struct SixthDensePt<-1, 3, 0> { enum { idx = 3 }; }; + template<> struct SixthDensePt< 0, 3, 0> { enum { idx = 4 }; }; + template<> struct SixthDensePt< 1, 3, 0> { enum { idx = 5 }; }; + template<> struct SixthDensePt< 2, 3, 0> { enum { idx = 6 }; }; + template<> struct SixthDensePt< 3, 3, 0> { enum { idx = 7 }; }; + + template<> struct SixthDensePt<-3, 2, 0> { enum { idx = 8 }; }; + template<> struct SixthDensePt<-2, 2, 0> { enum { idx = 9 }; }; + template<> struct SixthDensePt<-1, 2, 0> { enum { idx = 10 }; }; + template<> struct SixthDensePt< 0, 2, 0> { enum { idx = 11 }; }; + template<> struct SixthDensePt< 1, 2, 0> { enum { idx = 12 }; }; + template<> struct SixthDensePt< 2, 2, 0> { enum { idx = 13 }; }; + template<> struct SixthDensePt< 3, 2, 0> { enum { idx = 14 }; }; + + template<> struct SixthDensePt<-3, 1, 0> { enum { idx = 15 }; }; + template<> struct SixthDensePt<-2, 1, 0> { enum { idx = 16 }; }; + template<> struct SixthDensePt<-1, 1, 0> { enum { idx = 17 }; }; + template<> struct SixthDensePt< 0, 1, 0> { enum { idx = 18 }; }; + template<> struct SixthDensePt< 1, 1, 0> { enum { idx = 19 }; }; + template<> struct SixthDensePt< 2, 1, 0> { enum { idx = 20 }; }; + template<> struct SixthDensePt< 3, 1, 0> { enum { idx = 21 }; }; + + template<> struct SixthDensePt<-3, 0, 0> { enum { idx = 22 }; }; + template<> struct SixthDensePt<-2, 0, 0> { enum { idx = 23 }; }; + template<> struct SixthDensePt<-1, 0, 0> { enum { idx = 24 }; }; + template<> struct SixthDensePt< 1, 0, 0> { enum { idx = 25 }; }; + template<> struct SixthDensePt< 2, 0, 0> { enum { idx = 26 }; }; + template<> struct SixthDensePt< 3, 0, 0> { enum { idx = 27 }; }; + + + template<> struct SixthDensePt<-3,-1, 0> { enum { idx = 28 }; }; + template<> struct SixthDensePt<-2,-1, 0> { enum { idx = 29 }; }; + template<> struct SixthDensePt<-1,-1, 0> { enum { idx = 30 }; }; + template<> struct SixthDensePt< 0,-1, 0> { enum { idx = 31 }; }; + template<> struct SixthDensePt< 1,-1, 0> { enum { idx = 32 }; }; + template<> struct SixthDensePt< 2,-1, 0> { enum { idx = 33 }; }; + template<> struct SixthDensePt< 3,-1, 0> { enum { idx = 34 }; }; + + + template<> struct SixthDensePt<-3,-2, 0> { enum { idx = 35 }; }; + template<> struct SixthDensePt<-2,-2, 0> { enum { idx = 36 }; }; + template<> struct SixthDensePt<-1,-2, 0> { enum { idx = 37 }; }; + template<> struct SixthDensePt< 0,-2, 0> { enum { idx = 38 }; }; + template<> struct SixthDensePt< 1,-2, 0> { enum { idx = 39 }; }; + template<> struct SixthDensePt< 2,-2, 0> { enum { idx = 40 }; }; + template<> struct SixthDensePt< 3,-2, 0> { enum { idx = 41 }; }; + + + template<> struct SixthDensePt<-3,-3, 0> { enum { idx = 42 }; }; + template<> struct SixthDensePt<-2,-3, 0> { enum { idx = 43 }; }; + template<> struct SixthDensePt<-1,-3, 0> { enum { idx = 44 }; }; + template<> struct SixthDensePt< 0,-3, 0> { enum { idx = 45 }; }; + template<> struct SixthDensePt< 1,-3, 0> { enum { idx = 46 }; }; + template<> struct SixthDensePt< 2,-3, 0> { enum { idx = 47 }; }; + template<> struct SixthDensePt< 3,-3, 0> { enum { idx = 48 }; }; + + + template<> struct SixthDensePt<-3, 0, 3> { enum { idx = 49 }; }; + template<> struct SixthDensePt<-2, 0, 3> { enum { idx = 50 }; }; + template<> struct SixthDensePt<-1, 0, 3> { enum { idx = 51 }; }; + template<> struct SixthDensePt< 0, 0, 3> { enum { idx = 52 }; }; + template<> struct SixthDensePt< 1, 0, 3> { enum { idx = 53 }; }; + template<> struct SixthDensePt< 2, 0, 3> { enum { idx = 54 }; }; + template<> struct SixthDensePt< 3, 0, 3> { enum { idx = 55 }; }; + + + template<> struct SixthDensePt<-3, 0, 2> { enum { idx = 56 }; }; + template<> struct SixthDensePt<-2, 0, 2> { enum { idx = 57 }; }; + template<> struct SixthDensePt<-1, 0, 2> { enum { idx = 58 }; }; + template<> struct SixthDensePt< 0, 0, 2> { enum { idx = 59 }; }; + template<> struct SixthDensePt< 1, 0, 2> { enum { idx = 60 }; }; + template<> struct SixthDensePt< 2, 0, 2> { enum { idx = 61 }; }; + template<> struct SixthDensePt< 3, 0, 2> { enum { idx = 62 }; }; + + template<> struct SixthDensePt<-3, 0, 1> { enum { idx = 63 }; }; + template<> struct SixthDensePt<-2, 0, 1> { enum { idx = 64 }; }; + template<> struct SixthDensePt<-1, 0, 1> { enum { idx = 65 }; }; + template<> struct SixthDensePt< 0, 0, 1> { enum { idx = 66 }; }; + template<> struct SixthDensePt< 1, 0, 1> { enum { idx = 67 }; }; + template<> struct SixthDensePt< 2, 0, 1> { enum { idx = 68 }; }; + template<> struct SixthDensePt< 3, 0, 1> { enum { idx = 69 }; }; + + + template<> struct SixthDensePt<-3, 0,-1> { enum { idx = 70 }; }; + template<> struct SixthDensePt<-2, 0,-1> { enum { idx = 71 }; }; + template<> struct SixthDensePt<-1, 0,-1> { enum { idx = 72 }; }; + template<> struct SixthDensePt< 0, 0,-1> { enum { idx = 73 }; }; + template<> struct SixthDensePt< 1, 0,-1> { enum { idx = 74 }; }; + template<> struct SixthDensePt< 2, 0,-1> { enum { idx = 75 }; }; + template<> struct SixthDensePt< 3, 0,-1> { enum { idx = 76 }; }; + + + template<> struct SixthDensePt<-3, 0,-2> { enum { idx = 77 }; }; + template<> struct SixthDensePt<-2, 0,-2> { enum { idx = 78 }; }; + template<> struct SixthDensePt<-1, 0,-2> { enum { idx = 79 }; }; + template<> struct SixthDensePt< 0, 0,-2> { enum { idx = 80 }; }; + template<> struct SixthDensePt< 1, 0,-2> { enum { idx = 81 }; }; + template<> struct SixthDensePt< 2, 0,-2> { enum { idx = 82 }; }; + template<> struct SixthDensePt< 3, 0,-2> { enum { idx = 83 }; }; + + + template<> struct SixthDensePt<-3, 0,-3> { enum { idx = 84 }; }; + template<> struct SixthDensePt<-2, 0,-3> { enum { idx = 85 }; }; + template<> struct SixthDensePt<-1, 0,-3> { enum { idx = 86 }; }; + template<> struct SixthDensePt< 0, 0,-3> { enum { idx = 87 }; }; + template<> struct SixthDensePt< 1, 0,-3> { enum { idx = 88 }; }; + template<> struct SixthDensePt< 2, 0,-3> { enum { idx = 89 }; }; + template<> struct SixthDensePt< 3, 0,-3> { enum { idx = 90 }; }; + + + template<> struct SixthDensePt< 0,-3, 3> { enum { idx = 91 }; }; + template<> struct SixthDensePt< 0,-2, 3> { enum { idx = 92 }; }; + template<> struct SixthDensePt< 0,-1, 3> { enum { idx = 93 }; }; + template<> struct SixthDensePt< 0, 1, 3> { enum { idx = 94 }; }; + template<> struct SixthDensePt< 0, 2, 3> { enum { idx = 95 }; }; + template<> struct SixthDensePt< 0, 3, 3> { enum { idx = 96 }; }; + + template<> struct SixthDensePt< 0,-3, 2> { enum { idx = 97 }; }; + template<> struct SixthDensePt< 0,-2, 2> { enum { idx = 98 }; }; + template<> struct SixthDensePt< 0,-1, 2> { enum { idx = 99 }; }; + template<> struct SixthDensePt< 0, 1, 2> { enum { idx = 100 }; }; + template<> struct SixthDensePt< 0, 2, 2> { enum { idx = 101 }; }; + template<> struct SixthDensePt< 0, 3, 2> { enum { idx = 102 }; }; + + template<> struct SixthDensePt< 0,-3, 1> { enum { idx = 103 }; }; + template<> struct SixthDensePt< 0,-2, 1> { enum { idx = 104 }; }; + template<> struct SixthDensePt< 0,-1, 1> { enum { idx = 105 }; }; + template<> struct SixthDensePt< 0, 1, 1> { enum { idx = 106 }; }; + template<> struct SixthDensePt< 0, 2, 1> { enum { idx = 107 }; }; + template<> struct SixthDensePt< 0, 3, 1> { enum { idx = 108 }; }; + + template<> struct SixthDensePt< 0,-3,-1> { enum { idx = 109 }; }; + template<> struct SixthDensePt< 0,-2,-1> { enum { idx = 110 }; }; + template<> struct SixthDensePt< 0,-1,-1> { enum { idx = 111 }; }; + template<> struct SixthDensePt< 0, 1,-1> { enum { idx = 112 }; }; + template<> struct SixthDensePt< 0, 2,-1> { enum { idx = 113 }; }; + template<> struct SixthDensePt< 0, 3,-1> { enum { idx = 114 }; }; + + template<> struct SixthDensePt< 0,-3,-2> { enum { idx = 115 }; }; + template<> struct SixthDensePt< 0,-2,-2> { enum { idx = 116 }; }; + template<> struct SixthDensePt< 0,-1,-2> { enum { idx = 117 }; }; + template<> struct SixthDensePt< 0, 1,-2> { enum { idx = 118 }; }; + template<> struct SixthDensePt< 0, 2,-2> { enum { idx = 119 }; }; + template<> struct SixthDensePt< 0, 3,-2> { enum { idx = 120 }; }; + + template<> struct SixthDensePt< 0,-3,-3> { enum { idx = 121 }; }; + template<> struct SixthDensePt< 0,-2,-3> { enum { idx = 122 }; }; + template<> struct SixthDensePt< 0,-1,-3> { enum { idx = 123 }; }; + template<> struct SixthDensePt< 0, 1,-3> { enum { idx = 124 }; }; + template<> struct SixthDensePt< 0, 2,-3> { enum { idx = 125 }; }; + template<> struct SixthDensePt< 0, 3,-3> { enum { idx = 126 }; }; + +} + + +template +class SixthOrderDenseStencil: public BaseStencil > +{ +public: + typedef BaseStencil > BaseType; + typedef typename BaseType::BufferType BufferType; + typedef typename GridType::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + static const int SIZE = 127; + + SixthOrderDenseStencil(const GridType& grid): BaseType(grid, SIZE) {} + + /// Return linear offset for the specified stencil point relative to its center + template + unsigned int pos() const { return SixthDensePt::idx; } + +private: + inline void init(const Coord& ijk) + { + mStencil[SixthDensePt<-3, 3, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 3, 0)); + mStencil[SixthDensePt<-2, 3, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 3, 0)); + mStencil[SixthDensePt<-1, 3, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 3, 0)); + mStencil[SixthDensePt< 0, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 0)); + mStencil[SixthDensePt< 1, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 3, 0)); + mStencil[SixthDensePt< 2, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 3, 0)); + mStencil[SixthDensePt< 3, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 3, 0)); + + mStencil[SixthDensePt<-3, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 2, 0)); + mStencil[SixthDensePt<-2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 2, 0)); + mStencil[SixthDensePt<-1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 2, 0)); + mStencil[SixthDensePt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); + mStencil[SixthDensePt< 1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 2, 0)); + mStencil[SixthDensePt< 2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 2, 0)); + mStencil[SixthDensePt< 3, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 2, 0)); + + mStencil[SixthDensePt<-3, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 1, 0)); + mStencil[SixthDensePt<-2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 1, 0)); + mStencil[SixthDensePt<-1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 1, 0)); + mStencil[SixthDensePt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); + mStencil[SixthDensePt< 1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 1, 0)); + mStencil[SixthDensePt< 2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 1, 0)); + mStencil[SixthDensePt< 3, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 1, 0)); + + mStencil[SixthDensePt<-3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 0)); + mStencil[SixthDensePt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); + mStencil[SixthDensePt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); + mStencil[SixthDensePt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); + mStencil[SixthDensePt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); + mStencil[SixthDensePt< 3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 0)); + + mStencil[SixthDensePt<-3,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-3,-1, 0)); + mStencil[SixthDensePt<-2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-1, 0)); + mStencil[SixthDensePt<-1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-1, 0)); + mStencil[SixthDensePt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 0)); + mStencil[SixthDensePt< 1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-1, 0)); + mStencil[SixthDensePt< 2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-1, 0)); + mStencil[SixthDensePt< 3,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 3,-1, 0)); + + mStencil[SixthDensePt<-3,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-3,-2, 0)); + mStencil[SixthDensePt<-2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-2, 0)); + mStencil[SixthDensePt<-1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-2, 0)); + mStencil[SixthDensePt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 0)); + mStencil[SixthDensePt< 1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-2, 0)); + mStencil[SixthDensePt< 2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-2, 0)); + mStencil[SixthDensePt< 3,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 3,-2, 0)); + + mStencil[SixthDensePt<-3,-3, 0>::idx] = mCache.getValue(ijk.offsetBy(-3,-3, 0)); + mStencil[SixthDensePt<-2,-3, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-3, 0)); + mStencil[SixthDensePt<-1,-3, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-3, 0)); + mStencil[SixthDensePt< 0,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 0)); + mStencil[SixthDensePt< 1,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-3, 0)); + mStencil[SixthDensePt< 2,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-3, 0)); + mStencil[SixthDensePt< 3,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 3,-3, 0)); + + mStencil[SixthDensePt<-3, 0, 3>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 3)); + mStencil[SixthDensePt<-2, 0, 3>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 3)); + mStencil[SixthDensePt<-1, 0, 3>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 3)); + mStencil[SixthDensePt< 0, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 3)); + mStencil[SixthDensePt< 1, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 3)); + mStencil[SixthDensePt< 2, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 3)); + mStencil[SixthDensePt< 3, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 3)); + + mStencil[SixthDensePt<-3, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 2)); + mStencil[SixthDensePt<-2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 2)); + mStencil[SixthDensePt<-1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 2)); + mStencil[SixthDensePt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); + mStencil[SixthDensePt< 1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 2)); + mStencil[SixthDensePt< 2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 2)); + mStencil[SixthDensePt< 3, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 2)); + + mStencil[SixthDensePt<-3, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 1)); + mStencil[SixthDensePt<-2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 1)); + mStencil[SixthDensePt<-1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 1)); + mStencil[SixthDensePt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); + mStencil[SixthDensePt< 1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 1)); + mStencil[SixthDensePt< 2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 1)); + mStencil[SixthDensePt< 3, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 1)); + + mStencil[SixthDensePt<-3, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-3, 0,-1)); + mStencil[SixthDensePt<-2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-1)); + mStencil[SixthDensePt<-1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-1)); + mStencil[SixthDensePt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-1)); + mStencil[SixthDensePt< 1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-1)); + mStencil[SixthDensePt< 2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-1)); + mStencil[SixthDensePt< 3, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 3, 0,-1)); + + mStencil[SixthDensePt<-3, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-3, 0,-2)); + mStencil[SixthDensePt<-2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-2)); + mStencil[SixthDensePt<-1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-2)); + mStencil[SixthDensePt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-2)); + mStencil[SixthDensePt< 1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-2)); + mStencil[SixthDensePt< 2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-2)); + mStencil[SixthDensePt< 3, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 3, 0,-2)); + + mStencil[SixthDensePt<-3, 0,-3>::idx] = mCache.getValue(ijk.offsetBy(-3, 0,-3)); + mStencil[SixthDensePt<-2, 0,-3>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-3)); + mStencil[SixthDensePt<-1, 0,-3>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-3)); + mStencil[SixthDensePt< 0, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-3)); + mStencil[SixthDensePt< 1, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-3)); + mStencil[SixthDensePt< 2, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-3)); + mStencil[SixthDensePt< 3, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 3, 0,-3)); + + mStencil[SixthDensePt< 0,-3, 3>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 3)); + mStencil[SixthDensePt< 0,-2, 3>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 3)); + mStencil[SixthDensePt< 0,-1, 3>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 3)); + mStencil[SixthDensePt< 0, 1, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 3)); + mStencil[SixthDensePt< 0, 2, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 3)); + mStencil[SixthDensePt< 0, 3, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 3)); + + mStencil[SixthDensePt< 0,-3, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 2)); + mStencil[SixthDensePt< 0,-2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 2)); + mStencil[SixthDensePt< 0,-1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 2)); + mStencil[SixthDensePt< 0, 1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 2)); + mStencil[SixthDensePt< 0, 2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 2)); + mStencil[SixthDensePt< 0, 3, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 2)); + + mStencil[SixthDensePt< 0,-3, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 1)); + mStencil[SixthDensePt< 0,-2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 1)); + mStencil[SixthDensePt< 0,-1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 1)); + mStencil[SixthDensePt< 0, 1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 1)); + mStencil[SixthDensePt< 0, 2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 1)); + mStencil[SixthDensePt< 0, 3, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 1)); + + mStencil[SixthDensePt< 0,-3,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-3,-1)); + mStencil[SixthDensePt< 0,-2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-1)); + mStencil[SixthDensePt< 0,-1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-1)); + mStencil[SixthDensePt< 0, 1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-1)); + mStencil[SixthDensePt< 0, 2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-1)); + mStencil[SixthDensePt< 0, 3,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 3,-1)); + + mStencil[SixthDensePt< 0,-3,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-3,-2)); + mStencil[SixthDensePt< 0,-2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-2)); + mStencil[SixthDensePt< 0,-1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-2)); + mStencil[SixthDensePt< 0, 1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-2)); + mStencil[SixthDensePt< 0, 2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-2)); + mStencil[SixthDensePt< 0, 3,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 3,-2)); + + mStencil[SixthDensePt< 0,-3,-3>::idx] = mCache.getValue(ijk.offsetBy( 0,-3,-3)); + mStencil[SixthDensePt< 0,-2,-3>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-3)); + mStencil[SixthDensePt< 0,-1,-3>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-3)); + mStencil[SixthDensePt< 0, 1,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-3)); + mStencil[SixthDensePt< 0, 2,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-3)); + mStencil[SixthDensePt< 0, 3,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 3,-3)); + } + + template friend class BaseStencil; // allow base class to call init() + using BaseType::mCache; + using BaseType::mStencil; +}; + + +////////////////////////////////////////////////////////////////////// + + +/// This is a simple 7-point nearest neighbor stencil that supports +/// gradient by second-order central differencing, first-order upwinding, +/// Laplacian, closest-point transform and zero-crossing test. +/// +/// @note For optimal random access performance this class +/// includes its own grid accessor. +template +class GradStencil: public BaseStencil > +{ +public: + typedef BaseStencil > BaseType; + typedef typename BaseType::BufferType BufferType; + typedef typename GridType::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + static const int SIZE = 7; + + GradStencil(const GridType& grid): + BaseType(grid, SIZE), + mInv2Dx(ValueType(0.5 / grid.voxelSize()[0])), + mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) + { + } + + GradStencil(const GridType& grid, Real dx): + BaseType(grid, SIZE), + mInv2Dx(ValueType(0.5 / dx)), + mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) + { + } + + /// @brief Return the norm square of the single-sided upwind gradient + /// (computed via Gudonov's scheme) at the previously buffered location. + /// + /// @note This method should not be called until the stencil + /// buffer has been populated via a call to moveTo(ijk). + inline ValueType normSqGrad() const + { + return mInvDx2 * math::GudonovsNormSqrd(mStencil[0] > 0, + mStencil[0] - mStencil[1], + mStencil[2] - mStencil[0], + mStencil[0] - mStencil[3], + mStencil[4] - mStencil[0], + mStencil[0] - mStencil[5], + mStencil[6] - mStencil[0]); + } + + /// @brief Return the gradient computed at the previously buffered + /// location by second order central differencing. + /// + /// @note This method should not be called until the stencil + /// buffer has been populated via a call to moveTo(ijk). + inline Vec3Type gradient() const + { + return Vec3Type(mStencil[2] - mStencil[1], + mStencil[4] - mStencil[3], + mStencil[6] - mStencil[5])*mInv2Dx; + } + /// @brief Return the first-order upwind gradient corresponding to the direction V. + /// + /// @note This method should not be called until the stencil + /// buffer has been populated via a call to moveTo(ijk). + inline Vec3Type gradient(const Vec3Type& V) const + { + return Vec3Type(V[0]>0 ? mStencil[0] - mStencil[1] : mStencil[2] - mStencil[0], + V[1]>0 ? mStencil[0] - mStencil[3] : mStencil[4] - mStencil[0], + V[2]>0 ? mStencil[0] - mStencil[5] : mStencil[6] - mStencil[0])*2*mInv2Dx; + } + + /// Return the Laplacian computed at the previously buffered + /// location by second-order central differencing. + inline ValueType laplacian() const + { + return mInvDx2 * (mStencil[1] + mStencil[2] + + mStencil[3] + mStencil[4] + + mStencil[5] + mStencil[6] - 6*mStencil[0]); + } + + /// Return @c true if the sign of the value at the center point of the stencil + /// is different from the signs of any of its six nearest neighbors. + inline bool zeroCrossing() const + { + const BufferType& v = mStencil; + return (v[0]>0 ? (v[1]<0 || v[2]<0 || v[3]<0 || v[4]<0 || v[5]<0 || v[6]<0) + : (v[1]>0 || v[2]>0 || v[3]>0 || v[4]>0 || v[5]>0 || v[6]>0)); + } + + /// @brief Compute the closest-point transform to a level set. + /// @return the closest point in index space to the surface + /// from which the level set was derived. + /// + /// @note This method assumes that the grid represents a level set + /// with distances in world units and a simple affine transfrom + /// with uniform scaling. + inline Vec3Type cpt() + { + const Coord& ijk = BaseType::getCenterCoord(); + const ValueType d = ValueType(mStencil[0] * 0.5 * mInvDx2); // distance in voxels / (2dx^2) + return Vec3Type(ijk[0] - d*(mStencil[2] - mStencil[1]), + ijk[1] - d*(mStencil[4] - mStencil[3]), + ijk[2] - d*(mStencil[6] - mStencil[5])); + } + +private: + + inline void init(const Coord& ijk) + { + mStencil[1] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); + mStencil[2] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); + + mStencil[3] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); + mStencil[4] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); + + mStencil[5] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); + mStencil[6] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); + } + + template friend class BaseStencil; // allow base class to call init() + using BaseType::mCache; + using BaseType::mStencil; + const ValueType mInv2Dx, mInvDx2; +}; // class GradStencil + + +//////////////////////////////////////// + + +/// @brief This is a special 19-point stencil that supports optimal fifth-order WENO +/// upwinding, second-order central differencing, Laplacian, and zero-crossing test. +/// +/// @note For optimal random access performance this class +/// includes its own grid accessor. +template +class WenoStencil: public BaseStencil > +{ +public: + typedef BaseStencil > BaseType; + typedef typename BaseType::BufferType BufferType; + typedef typename GridType::ValueType ValueType; + typedef math::Vec3 Vec3Type; + + static const int SIZE = 19; + + WenoStencil(const GridType& grid): + BaseType(grid, SIZE), + mDx2(ValueType(math::Pow2(grid.voxelSize()[0]))), + mInv2Dx(ValueType(0.5 / grid.voxelSize()[0])), + mInvDx2(ValueType(1.0 / mDx2)) + { + } + + WenoStencil(const GridType& grid, Real dx): + BaseType(grid, SIZE), + mDx2(ValueType(dx * dx)), + mInv2Dx(ValueType(0.5 / dx)), + mInvDx2(ValueType(1.0 / mDx2)) + { + } + + /// @brief Return the norm-square of the WENO upwind gradient (computed via + /// WENO upwinding and Gudonov's scheme) at the previously buffered location. + /// + /// @note This method should not be called until the stencil + /// buffer has been populated via a call to moveTo(ijk). + inline ValueType normSqGrad() const + { + const BufferType& v = mStencil; +#ifdef DWA_OPENVDB + // SSE optimized + const simd::Float4 + v1(v[2]-v[1], v[ 8]-v[ 7], v[14]-v[13], 0), + v2(v[3]-v[2], v[ 9]-v[ 8], v[15]-v[14], 0), + v3(v[0]-v[3], v[ 0]-v[ 9], v[ 0]-v[15], 0), + v4(v[4]-v[0], v[10]-v[ 0], v[16]-v[ 0], 0), + v5(v[5]-v[4], v[11]-v[10], v[17]-v[16], 0), + v6(v[6]-v[5], v[12]-v[11], v[18]-v[17], 0), + dP_m = math::WENO5(v1, v2, v3, v4, v5, mDx2), + dP_p = math::WENO5(v6, v5, v4, v3, v2, mDx2); + + return mInvDx2 * math::GudonovsNormSqrd(mStencil[0] > 0, dP_m, dP_p); +#else + const Real + dP_xm = math::WENO5(v[ 2]-v[ 1],v[ 3]-v[ 2],v[ 0]-v[ 3],v[ 4]-v[ 0],v[ 5]-v[ 4],mDx2), + dP_xp = math::WENO5(v[ 6]-v[ 5],v[ 5]-v[ 4],v[ 4]-v[ 0],v[ 0]-v[ 3],v[ 3]-v[ 2],mDx2), + dP_ym = math::WENO5(v[ 8]-v[ 7],v[ 9]-v[ 8],v[ 0]-v[ 9],v[10]-v[ 0],v[11]-v[10],mDx2), + dP_yp = math::WENO5(v[12]-v[11],v[11]-v[10],v[10]-v[ 0],v[ 0]-v[ 9],v[ 9]-v[ 8],mDx2), + dP_zm = math::WENO5(v[14]-v[13],v[15]-v[14],v[ 0]-v[15],v[16]-v[ 0],v[17]-v[16],mDx2), + dP_zp = math::WENO5(v[18]-v[17],v[17]-v[16],v[16]-v[ 0],v[ 0]-v[15],v[15]-v[14],mDx2); + return mInvDx2*math::GudonovsNormSqrd(v[0]>0,dP_xm,dP_xp,dP_ym,dP_yp,dP_zm,dP_zp); +#endif + } + + /// Return the optimal fifth-order upwind gradient corresponding to the + /// direction V. + /// + /// @note This method should not be called until the stencil + /// buffer has been populated via a call to moveTo(ijk). + inline Vec3Type gradient(const Vec3Type& V) const + { + const BufferType& v = mStencil; + return 2*mInv2Dx * Vec3Type( + V[0]>0 ? math::WENO5(v[ 2]-v[ 1],v[ 3]-v[ 2],v[ 0]-v[ 3], v[ 4]-v[ 0],v[ 5]-v[ 4],mDx2) + : math::WENO5(v[ 6]-v[ 5],v[ 5]-v[ 4],v[ 4]-v[ 0], v[ 0]-v[ 3],v[ 3]-v[ 2],mDx2), + V[1]>0 ? math::WENO5(v[ 8]-v[ 7],v[ 9]-v[ 8],v[ 0]-v[ 9], v[10]-v[ 0],v[11]-v[10],mDx2) + : math::WENO5(v[12]-v[11],v[11]-v[10],v[10]-v[ 0], v[ 0]-v[ 9],v[ 9]-v[ 8],mDx2), + V[2]>0 ? math::WENO5(v[14]-v[13],v[15]-v[14],v[ 0]-v[15], v[16]-v[ 0],v[17]-v[16],mDx2) + : math::WENO5(v[18]-v[17],v[17]-v[16],v[16]-v[ 0], v[ 0]-v[15],v[15]-v[14],mDx2)); + } + /// Return the gradient computed at the previously buffered + /// location by second-order central differencing. + /// + /// @note This method should not be called until the stencil + /// buffer has been populated via a call to moveTo(ijk). + inline Vec3Type gradient() const + { + return mInv2Dx * Vec3Type( + mStencil[ 4] - mStencil[ 3], + mStencil[10] - mStencil[ 9], + mStencil[16] - mStencil[15]); + } + + /// Return the Laplacian computed at the previously buffered + /// location by second-order central differencing. + /// + /// @note This method should not be called until the stencil + /// buffer has been populated via a call to moveTo(ijk). + inline ValueType laplacian() const + { + return mInvDx2 * ( + mStencil[ 3] + mStencil[ 4] + + mStencil[ 9] + mStencil[10] + + mStencil[15] + mStencil[16] - 6*mStencil[0]); + } + + /// Return @c true if the sign of the value at the center point of the stencil + /// differs from the sign of any of its six nearest neighbors + inline bool zeroCrossing() const + { + const BufferType& v = mStencil; + return (v[ 0]>0 ? (v[ 3]<0 || v[ 4]<0 || v[ 9]<0 || v[10]<0 || v[15]<0 || v[16]<0) + : (v[ 3]>0 || v[ 4]>0 || v[ 9]>0 || v[10]>0 || v[15]>0 || v[16]>0)); + } + +private: + inline void init(const Coord& ijk) + { + mStencil[ 1] = mCache.getValue(ijk.offsetBy(-3, 0, 0)); + mStencil[ 2] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); + mStencil[ 3] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); + mStencil[ 4] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); + mStencil[ 5] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); + mStencil[ 6] = mCache.getValue(ijk.offsetBy( 3, 0, 0)); + + mStencil[ 7] = mCache.getValue(ijk.offsetBy( 0, -3, 0)); + mStencil[ 8] = mCache.getValue(ijk.offsetBy( 0, -2, 0)); + mStencil[ 9] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); + mStencil[10] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); + mStencil[11] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); + mStencil[12] = mCache.getValue(ijk.offsetBy( 0, 3, 0)); + + mStencil[13] = mCache.getValue(ijk.offsetBy( 0, 0, -3)); + mStencil[14] = mCache.getValue(ijk.offsetBy( 0, 0, -2)); + mStencil[15] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); + mStencil[16] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); + mStencil[17] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); + mStencil[18] = mCache.getValue(ijk.offsetBy( 0, 0, 3)); + } + + template friend class BaseStencil; // allow base class to call init() + using BaseType::mCache; + using BaseType::mStencil; + const ValueType mDx2, mInv2Dx, mInvDx2; +}; // class WenoStencil + + +////////////////////////////////////////////////////////////////////// + + +template +class CurvatureStencil: public BaseStencil > +{ +public: + typedef BaseStencil > BaseType; + typedef typename GridType::ValueType ValueType; + + static const int SIZE = 19; + + CurvatureStencil(const GridType& grid): + BaseType(grid, SIZE), + mInv2Dx(ValueType(0.5 / grid.voxelSize()[0])), + mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) + { + } + + CurvatureStencil(const GridType& grid, Real dx): + BaseType(grid, SIZE), + mInv2Dx(ValueType(0.5 / dx)), + mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) + { + } + + /// @brief Return the mean curvature at the previously buffered location. + /// + /// @note This method should not be called until the stencil + /// buffer has been populated via a call to moveTo(ijk). + inline ValueType meanCurvature() + { + Real alpha, beta; + 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; +}; // class CurvatureStencil + + +////////////////////////////////////////////////////////////////////// + + +/// @brief Dense stencil of a given width +template +class DenseStencil: public BaseStencil > +{ +public: + typedef BaseStencil > BaseType; + typedef typename GridType::ValueType ValueType; + + DenseStencil(const GridType& grid, int halfWidth) : + BaseType(grid, /*size=*/math::Pow3(2 * halfWidth + 1)), + mHalfWidth(halfWidth) + { + assert(halfWidth>0); + } + + inline const ValueType& getCenterValue() const { return mStencil[(mStencil.size()-1)>>1]; } + + /// @brief Initialize the stencil buffer with the values of voxel (x, y, z) + /// and its neighbors. + inline void moveTo(const Coord& ijk) + { + BaseType::mCenter = ijk; + this->init(ijk); + } + /// @brief Initialize the stencil buffer with the values of voxel + /// (x, y, z) and its neighbors. + template + inline void moveTo(const IterType& iter) + { + BaseType::mCenter = iter.getCoord(); + this->init(BaseType::mCenter); + } + +private: + /// Initialize the stencil buffer centered at (i, j, k). + /// @warning The center point is NOT at mStencil[0] for this DenseStencil! + inline void init(const Coord& ijk) + { + int n = 0; + for (Coord p=ijk.offsetBy(-mHalfWidth), q=ijk.offsetBy(mHalfWidth); p[0] <= q[0]; ++p[0]) { + for (p[1] = ijk[1]-mHalfWidth; p[1] <= q[1]; ++p[1]) { + for (p[2] = ijk[2]-mHalfWidth; p[2] <= q[2]; ++p[2]) { + mStencil[n++] = mCache.getValue(p); + } + } + } + } + + template friend class BaseStencil; // allow base class to call init() + using BaseType::mCache; + using BaseType::mStencil; + const int mHalfWidth; +}; + + +} // end math namespace +} // namespace OPENVDB_VERSION_NAME +} // end openvdb namespace + +#endif // OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Transform.cc b/openvdb_2_3_0_library/openvdb/math/Transform.cc new file mode 100755 index 0000000..cda3602 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Transform.cc @@ -0,0 +1,508 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Transform.h" +#include "LegacyFrustum.h" + +#include +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + + +//////////////////////////////////////// + + +Transform::Transform(const MapBase::Ptr& map): + mMap(boost::const_pointer_cast(map)) +{ + // auto-convert to simplest type + if (!mMap->isType() && mMap->isLinear()) { + AffineMap::Ptr affine = mMap->getAffineMap(); + mMap = simplify(affine); + } +} + +Transform::Transform(const Transform& other): + mMap(boost::const_pointer_cast(other.baseMap())) +{ +} + + +//////////////////////////////////////// + + +// Factory methods + +Transform::Ptr +Transform::createLinearTransform(double voxelDim) +{ + return Transform::Ptr(new Transform( + MapBase::Ptr(new UniformScaleMap(voxelDim)))); +} + +Transform::Ptr +Transform::createLinearTransform(const Mat4R& m) +{ + return Transform::Ptr(new Transform(MapBase::Ptr(new AffineMap(m)))); +} + +Transform::Ptr +Transform::createFrustumTransform(const BBoxd& bbox, double taper, + double depth, double voxelDim) +{ + return Transform::Ptr(new Transform( + NonlinearFrustumMap(bbox, taper, depth).preScale(Vec3d(voxelDim, voxelDim, voxelDim)))); +} + + +//////////////////////////////////////// + + +void +Transform::read(std::istream& is) +{ + // Read the type name. + Name type = readString(is); + + if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_NEW_TRANSFORM) { + // Handle old-style transforms. + + if (type == "LinearTransform") { + // First read in the old transform's base class. + Coord tmpMin, tmpMax; + is.read(reinterpret_cast(&tmpMin), sizeof(Coord::ValueType) * 3); + is.read(reinterpret_cast(&tmpMax), sizeof(Coord::ValueType) * 3); + + // Second read in the old linear transform + Mat4d tmpLocalToWorld, tmpWorldToLocal, tmpVoxelToLocal, tmpLocalToVoxel; + + tmpLocalToWorld.read(is); + tmpWorldToLocal.read(is); + tmpVoxelToLocal.read(is); + tmpLocalToVoxel.read(is); + + // Convert and simplify + AffineMap::Ptr affineMap(new AffineMap(tmpVoxelToLocal*tmpLocalToWorld)); + mMap = simplify(affineMap); + + } else if (type == "FrustumTransform") { + + internal::LegacyFrustum legacyFrustum(is); + + CoordBBox bb = legacyFrustum.getBBox(); + BBoxd bbox(bb.min().asVec3d(), bb.max().asVec3d() + /* -Vec3d(1,1,1) */ + ); + double taper = legacyFrustum.getTaper(); + double depth = legacyFrustum.getDepth(); + + double nearPlaneWidth = legacyFrustum.getNearPlaneWidth(); + double nearPlaneDist = legacyFrustum.getNearPlaneDist(); + const Mat4d& camxform = legacyFrustum.getCamXForm(); + + // create the new frustum with these parameters + Mat4d xform(Mat4d::identity()); + xform.setToTranslation(Vec3d(0,0, -nearPlaneDist)); + xform.preScale(Vec3d(nearPlaneWidth, nearPlaneWidth, -nearPlaneWidth)); + + // create the linear part of the frustum (the second map) + Mat4d second = xform * camxform; + + // we might have precision problems, the constructor for the + // affine map is not forgiving (so we fix here). + const Vec4d col3 = second.col(3); + const Vec4d ref(0, 0, 0, 1); + + if (ref.eq(col3) ) { + second.setCol(3, ref); + } + + MapBase::Ptr linearMap(simplify(AffineMap(second).getAffineMap())); + + // note that the depth is scaled on the nearPlaneSize. + // the linearMap will uniformly scale the frustum to the correct size + // and rotate to align with the camera + mMap = MapBase::Ptr(new NonlinearFrustumMap( + bbox, taper, depth/nearPlaneWidth, linearMap)); + + } else { + OPENVDB_THROW(IoError, "Transforms of type " + type + " are no longer supported"); + } + } else { + // Check if the map has been registered. + if (!MapRegistry::isRegistered(type)) { + OPENVDB_THROW(KeyError, "Map " << type << " is not registered"); + } + + // Create the map of the type and then read it in. + mMap = math::MapRegistry::createMap(type); + mMap->read(is); + } +} + + +void +Transform::write(std::ostream& os) const +{ + if (!mMap) OPENVDB_THROW(IoError, "Transform does not have a map"); + + // Write the type-name of the map. + writeString(os, mMap->type()); + + mMap->write(os); +} + + +//////////////////////////////////////// + + +bool +Transform::isIdentity() const +{ + if (mMap->isLinear()) { + return mMap->getAffineMap()->isIdentity(); + } else if ( mMap->isType() ) { + NonlinearFrustumMap::Ptr frustum + = boost::static_pointer_cast(mMap); + return frustum->isIdentity(); + } + // unknown nonlinear map type + return false; +} + + +//////////////////////////////////////// + + +void +Transform::preRotate(double radians, const Axis axis) +{ + mMap = mMap->preRotate(radians, axis); +} + +void +Transform::preTranslate(const Vec3d& t) +{ + mMap = mMap->preTranslate(t); +} + +void +Transform::preScale(const Vec3d& s) +{ + mMap = mMap->preScale(s); +} + +void +Transform::preScale(double s) +{ + const Vec3d vec(s,s,s); + mMap = mMap->preScale(vec); +} + +void +Transform::preShear(double shear, Axis axis0, Axis axis1) +{ + mMap = mMap->preShear(shear, axis0, axis1); +} + +void +Transform::preMult(const Mat4d& m) +{ + if (mMap->isLinear()) { + + const Mat4d currentMat4 = mMap->getAffineMap()->getMat4(); + const Mat4d newMat4 = m * currentMat4; + + AffineMap::Ptr affineMap( new AffineMap( newMat4) ); + mMap = simplify(affineMap); + + } else if (mMap->isType() ) { + + NonlinearFrustumMap::Ptr currentFrustum = + boost::static_pointer_cast(mMap); + + const Mat4d currentMat4 = currentFrustum->secondMap().getMat4(); + const Mat4d newMat4 = m * currentMat4; + + AffineMap affine(newMat4); + + NonlinearFrustumMap::Ptr frustum( new NonlinearFrustumMap( currentFrustum->getBBox(), + currentFrustum->getTaper(), + currentFrustum->getDepth(), + affine.copy() ) ); + mMap = boost::static_pointer_cast( frustum ); + } + +} + +void +Transform::preMult(const Mat3d& m) +{ + Mat4d mat4 = Mat4d::identity(); + mat4.setMat3(m); + preMult(mat4); +} + +void +Transform::postRotate(double radians, const Axis axis) +{ + mMap = mMap->postRotate(radians, axis); +} + +void +Transform::postTranslate(const Vec3d& t) +{ + mMap = mMap->postTranslate(t); +} + +void +Transform::postScale(const Vec3d& s) +{ + mMap = mMap->postScale(s); +} + +void +Transform::postScale(double s) +{ + const Vec3d vec(s,s,s); + mMap = mMap->postScale(vec); +} + +void +Transform::postShear(double shear, Axis axis0, Axis axis1) +{ + mMap = mMap->postShear(shear, axis0, axis1); +} + + +void +Transform::postMult(const Mat4d& m) +{ + if (mMap->isLinear()) { + + const Mat4d currentMat4 = mMap->getAffineMap()->getMat4(); + const Mat4d newMat4 = currentMat4 * m; + + AffineMap::Ptr affineMap( new AffineMap( newMat4) ); + mMap = simplify(affineMap); + + } else if (mMap->isType() ) { + + NonlinearFrustumMap::Ptr currentFrustum = + boost::static_pointer_cast(mMap); + + const Mat4d currentMat4 = currentFrustum->secondMap().getMat4(); + const Mat4d newMat4 = currentMat4 * m; + + AffineMap affine(newMat4); + + NonlinearFrustumMap::Ptr frustum( new NonlinearFrustumMap( currentFrustum->getBBox(), + currentFrustum->getTaper(), + currentFrustum->getDepth(), + affine.copy() ) ); + mMap = boost::static_pointer_cast( frustum ); + } + +} + +void +Transform::postMult(const Mat3d& m) +{ + Mat4d mat4 = Mat4d::identity(); + mat4.setMat3(m); + postMult(mat4); +} + + +//////////////////////////////////////// + +// Utility methods + +void +calculateBounds(const Transform& t, + const Vec3d& minWS, + const Vec3d& maxWS, + Vec3d& minIS, + Vec3d& maxIS) +{ + /// the pre-image of the 8 corners of the box + Vec3d corners[8]; + corners[0] = minWS; + corners[1] = Vec3d(maxWS(0), minWS(1), minWS(2)); + corners[2] = Vec3d(maxWS(0), maxWS(1), minWS(2)); + corners[3] = Vec3d(minWS(0), maxWS(1), minWS(2)); + corners[4] = Vec3d(minWS(0), minWS(1), maxWS(2)); + corners[5] = Vec3d(maxWS(0), minWS(1), maxWS(2)); + corners[6] = maxWS; + corners[7] = Vec3d(minWS(0), maxWS(1), maxWS(2)); + + Vec3d pre_image; + minIS = t.worldToIndex(corners[0]); + maxIS = minIS; + for (int i = 1; i < 8; ++i) { + pre_image = t.worldToIndex(corners[i]); + for (int j = 0; j < 3; ++j) { + minIS(j) = std::min(minIS(j), pre_image(j)); + maxIS(j) = std::max(maxIS(j), pre_image(j)); + } + } +} + + +//////////////////////////////////////// + + +bool +Transform::operator==(const Transform& other) const +{ + if (!this->voxelSize().eq(other.voxelSize())) return false; + + if (this->mapType() == other.mapType()) { + return this->baseMap()->isEqual(*other.baseMap()); + } + + if (this->isLinear() && other.isLinear()) { + // promote both maps to mat4 form and compare + return ( *(this->baseMap()->getAffineMap()) == + *(other.baseMap()->getAffineMap()) ); + } + + return this->baseMap()->isEqual(*other.baseMap()); +} + + +//////////////////////////////////////// + + +void +Transform::print(std::ostream& os, const std::string& indent) const +{ + struct Local { + // Print a Vec4d more compactly than Vec4d::str() does. + static std::string rowAsString(const Vec4d& row) + { + std::ostringstream ostr; + ostr << "[" << std::setprecision(3) << row[0] << ", " + << row[1] << ", " << row[2] << ", " << row[3] << "] "; + return ostr.str(); + } + }; + + // Write to a string stream so that I/O manipulators don't affect the output stream. + std::ostringstream ostr; + + { + Vec3d dim = this->voxelSize(); + if (dim.eq(Vec3d(dim[0]))) { + ostr << indent << std::left << "voxel size: " << std::setprecision(3) << dim[0]; + } else { + ostr << indent << std::left << "voxel dimensions: [" << std::setprecision(3) + << dim[0] << ", " << dim[1] << ", " << dim[2] << "]"; + } + ostr << "\n"; + } + + if (this->isLinear()) { + openvdb::Mat4R v2w = this->baseMap()->getAffineMap()->getMat4(); + + ostr << indent << std::left << "index to world:\n"; + for (int row = 0; row < 4; ++row) { + ostr << indent << " " << std::left << Local::rowAsString(v2w[row]) << "\n"; + } + + } else if (this->mapType() == NonlinearFrustumMap::mapType()) { + const NonlinearFrustumMap& frustum = + static_cast(*this->baseMap()); + const openvdb::Mat4R linear = this->baseMap()->getAffineMap()->getMat4(); + + std::vector linearRow; + size_t w = 0; + for (int row = 0; row < 4; ++row) { + std::string str = Local::rowAsString(linear[row]); + w = std::max(w, str.size()); + linearRow.push_back(str); + } + w = std::max(w, 30); + + // Print rows of the linear component matrix side-by-side with frustum parameters. + ostr << indent << std::left << std::setw(w) << "linear:" + << " frustum:\n"; + ostr << indent << " " << std::left << std::setw(w) << linearRow[0] + << " taper: " << frustum.getTaper() << "\n"; + ostr << indent << " " << std::left << std::setw(w) << linearRow[1] + << " depth: " << frustum.getDepth() << "\n"; + + std::ostringstream ostmp; + ostmp << indent << " " << std::left << std::setw(w) << linearRow[2] + << " bounds: " << frustum.getBBox(); + if (ostmp.str().size() < 79) { + ostr << ostmp.str() << "\n"; + ostr << indent << " " << std::left << std::setw(w) << linearRow[3] << "\n"; + } else { + // If the frustum bounding box doesn't fit on one line, split it into two lines. + ostr << indent << " " << std::left << std::setw(w) << linearRow[2] + << " bounds: " << frustum.getBBox().min() << " ->\n"; + ostr << indent << " " << std::left << std::setw(w) << linearRow[3] + << " " << frustum.getBBox().max() << "\n"; + } + + } else { + /// @todo Handle other map types. + } + + os << ostr.str(); +} + + +//////////////////////////////////////// + + +std::ostream& +operator<<(std::ostream& os, const Transform& t) +{ + os << "Transform type: " << t.baseMap()->type() << std::endl; + os << t.baseMap()->str() << std::endl; + return os; +} + + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Transform.h b/openvdb_2_3_0_library/openvdb/math/Transform.h new file mode 100755 index 0000000..7159bfb --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Transform.h @@ -0,0 +1,295 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_MATH_TRANSFORM_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_TRANSFORM_HAS_BEEN_INCLUDED + +#include "Maps.h" +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +// Forward declaration +class Transform; + + +// Utility methods + +/// @brief Calculate an axis-aligned bounding box in index space from an +/// axis-aligned bounding box in world space. +OPENVDB_API void +calculateBounds(const Transform& t, const Vec3d& minWS, const Vec3d& maxWS, + Vec3d& minIS, Vec3d& maxIS); + +/// @brief Calculate an axis-aligned bounding box in index space from a +/// bounding sphere in world space. +/// @todo void calculateBounds(const Transform& t, const Vec3d& center, const Real radius, +/// Vec3d& minIS, Vec3d& maxIS); + + +//////////////////////////////////////// + + +/// @class Transform +class OPENVDB_API Transform +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + Transform(): mMap(MapBase::Ptr(new ScaleMap())) {} + Transform(const MapBase::Ptr&); + Transform(const Transform&); + ~Transform() {} + + Ptr copy() const { return Ptr(new Transform(mMap->copy())); } + + //@{ + /// @brief Create and return a shared pointer to a new transform. + static Transform::Ptr createLinearTransform(double voxelSize = 1.0); + static Transform::Ptr createLinearTransform(const Mat4R&); + static Transform::Ptr createFrustumTransform(const BBoxd&, double taper, + double depth, double voxelSize = 1.0); + //@} + + /// Return @c true if the transformation map is exclusively linear/affine. + bool isLinear() const { return mMap->isLinear(); } + + /// Return @c true if the transform is equivalent to an idenity. + bool isIdentity() const ; + /// Return the transformation map's type-name + Name mapType() const { return mMap->type(); } + + + //@{ + /// @brief Update the linear (affine) map by prepending or + /// postfixing the appropriate operation. In the case of + /// a frustum, the pre-operations apply to the linear part + /// of the transform and not the entire transform, while the + /// post-operations are allways applied last. + void preRotate(double radians, const Axis axis = X_AXIS); + void preTranslate(const Vec3d&); + void preScale(const Vec3d&); + void preScale(double); + void preShear(double shear, Axis axis0, Axis axis1); + void preMult(const Mat4d&); + void preMult(const Mat3d&); + + void postRotate(double radians, const Axis axis = X_AXIS); + void postTranslate(const Vec3d&); + void postScale(const Vec3d&); + void postScale(double); + void postShear(double shear, Axis axis0, Axis axis1); + void postMult(const Mat4d&); + void postMult(const Mat3d&); + //@} + + /// Return the size of a voxel using the linear component of the map. + Vec3d voxelSize() const { return mMap->voxelSize(); } + /// @brief Return the size of a voxel at position (x, y, z). + /// @note Maps that have a nonlinear component (e.g., perspective and frustum maps) + /// have position-dependent voxel sizes. + Vec3d voxelSize(const Vec3d& xyz) const { return mMap->voxelSize(xyz); } + + /// Return the voxel volume of the linear component of the map. + double voxelVolume() const { return mMap->determinant(); } + /// Return the voxel volume at position (x, y, z). + double voxelVolume(const Vec3d& xyz) const { return mMap->determinant(xyz); } + /// Return true if the voxels in world space are uniformly sized cubes + bool hasUniformScale() const { return mMap->hasUniformScale(); } + + //@{ + /// @brief Apply this transformation to the given coordinates. + Vec3d indexToWorld(const Vec3d& xyz) const { return mMap->applyMap(xyz); } + Vec3d indexToWorld(const Coord& ijk) const { return mMap->applyMap(ijk.asVec3d()); } + Vec3d worldToIndex(const Vec3d& xyz) const { return mMap->applyInverseMap(xyz); } + Coord worldToIndexCellCentered(const Vec3d& xyz) const {return Coord::round(worldToIndex(xyz));} + Coord worldToIndexNodeCentered(const Vec3d& xyz) const {return Coord::floor(worldToIndex(xyz));} + //@} + + //@{ + /// Return a base pointer to the transformation map. + MapBase::ConstPtr baseMap() const { return mMap; } + MapBase::Ptr baseMap() { return mMap; } + //@} + + //@{ + /// @brief Return the result of downcasting the base map pointer to a + /// @c MapType pointer, or return a null pointer if the types are incompatible. + template typename MapType::Ptr map(); + template typename MapType::ConstPtr map() const; + template typename MapType::ConstPtr constMap() const; + //@} + + /// Unserialize this transform from the given stream. + void read(std::istream&); + /// Serialize this transform to the given stream. + void write(std::ostream&) const; + + /// @brief Print a description of this transform. + /// @param os a stream to which to write textual information + /// @param indent a string with which to prefix each line of text + void print(std::ostream& os = std::cout, const std::string& indent = "") const; + + bool operator==(const Transform& other) const; + inline bool operator!=(const Transform& other) const { return !(*this == other); } + +private: + MapBase::Ptr mMap; +}; // class Transform + + +OPENVDB_API std::ostream& operator<<(std::ostream&, const Transform&); + + +//////////////////////////////////////// + + +template +inline typename MapType::Ptr +Transform::map() +{ + if (mMap->type() == MapType::mapType()) { + return boost::static_pointer_cast(mMap); + } + return typename MapType::Ptr(); +} + + +template +inline typename MapType::ConstPtr +Transform::map() const +{ + return boost::const_pointer_cast( + const_cast(this)->map()); +} + + +template +inline typename MapType::ConstPtr +Transform::constMap() const +{ + return map(); +} + + +//////////////////////////////////////// + + +/// Helper function used internally by processTypedMap() +template +inline void +doProcessTypedMap(Transform& transform, OpType& op) +{ + ResolvedMapType& resolvedMap = *transform.map(); +#ifdef _MSC_VER + op.operator()(resolvedMap); +#else + op.template operator()(resolvedMap); +#endif +} + +/// Helper function used internally by processTypedMap() +template +inline void +doProcessTypedMap(const Transform& transform, OpType& op) +{ + const ResolvedMapType& resolvedMap = *transform.map(); +#ifdef _MSC_VER + op.operator()(resolvedMap); +#else + op.template operator()(resolvedMap); +#endif +} + + +/// @brief Utility function that, given a generic map pointer, +/// calls a functor on the fully-resoved map +/// +/// Usage: +/// @code +/// struct Foo { +/// template +/// void operator()(const MapT& map) const { blah } +/// }; +/// +/// processTypedMap(myMap, Foo()); +/// @endcode +/// +/// @return @c false if the grid type is unknown or unhandled. +template +bool +processTypedMap(TransformType& transform, OpType& op) +{ + using namespace openvdb; + + const Name mapType = transform.mapType(); + if (mapType == UniformScaleMap::mapType()) { + doProcessTypedMap(transform, op); + + } else if (mapType == UniformScaleTranslateMap::mapType()) { + doProcessTypedMap(transform, op); + + } else if (mapType == ScaleMap::mapType()) { + doProcessTypedMap(transform, op); + + } else if (mapType == ScaleTranslateMap::mapType()) { + doProcessTypedMap(transform, op); + + } else if (mapType == UnitaryMap::mapType()) { + doProcessTypedMap(transform, op); + + } else if (mapType == AffineMap::mapType()) { + doProcessTypedMap(transform, op); + + } else if (mapType == TranslationMap::mapType()) { + doProcessTypedMap(transform, op); + + } else if (mapType == NonlinearFrustumMap::mapType()) { + doProcessTypedMap(transform, op); + } else { + return false; + } + return true; +} + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_TRANSFORM_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Tuple.h b/openvdb_2_3_0_library/openvdb/math/Tuple.h new file mode 100755 index 0000000..a4aec55 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Tuple.h @@ -0,0 +1,233 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file Tuple.h +/// @author Ben Kwa + +#ifndef OPENVDB_MATH_TUPLE_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_TUPLE_HAS_BEEN_INCLUDED + +#include +#include +#include "Math.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +/// @class Tuple "Tuple.h" +/// A base class for homogenous tuple types +template +class Tuple { +public: + typedef T value_type; + typedef T ValueType; + + static const int size = SIZE; + + /// Default ctor. Does nothing. Required because declaring a copy (or + /// other) constructor means the default constructor gets left out. + Tuple() {} + + /// Copy constructor. Used when the class signature matches exactly. + inline Tuple(Tuple const &src) { + for (int i = 0; i < SIZE; ++i) { + mm[i] = src.mm[i]; + } + } + + /// Conversion constructor. Tuples with different value types and + /// different sizes can be interconverted using this member. Converting + /// from a larger tuple results in truncation; converting from a smaller + /// tuple results in the extra data members being zeroed out. This + /// function assumes that the integer 0 is convertible to the tuple's + /// value type. + template + explicit Tuple(Tuple const &src) { + static const int copyEnd = SIZE < src_size ? SIZE : src_size; + + for (int i = 0; i < copyEnd; ++i) { + mm[i] = src[i]; + } + for (int i = copyEnd; i < SIZE; ++i) { + mm[i] = 0; + } + } + + T operator[](int i) const { + // we'd prefer to use size_t, but can't because gcc3.2 doesn't like + // it - it conflicts with child class conversion operators to + // pointer types. +// assert(i >= 0 && i < SIZE); + return mm[i]; + } + + T& operator[](int i) { + // see above for size_t vs int +// assert(i >= 0 && i < SIZE); + return mm[i]; + } + + /// @name Compatibility + /// These are mostly for backwards compability with functions that take + /// old-style Vs (which are just arrays). + //@{ + /// Copies this tuple into an array of a compatible type + template + void toV(S *v) const { + for (int i = 0; i < SIZE; ++i) { + v[i] = mm[i]; + } + } + + /// Exposes the internal array. Be careful when using this function. + value_type *asV() { + return mm; + } + /// Exposes the internal array. Be careful when using this function. + value_type const *asV() const { + return mm; + } + //@} Compatibility + + /// @return string representation of Classname + std::string + str() const { + std::ostringstream buffer; + + buffer << "["; + + // For each column + for (unsigned j(0); j < SIZE; j++) { + if (j) buffer << ", "; + buffer << mm[j]; + } + + buffer << "]"; + + return buffer.str(); + } + + void write(std::ostream& os) const { + os.write(reinterpret_cast(&mm), sizeof(T)*SIZE); + } + void read(std::istream& is) { + is.read(reinterpret_cast(&mm), sizeof(T)*SIZE); + } + +protected: + T mm[SIZE]; +}; + + +//////////////////////////////////////// + + +/// @return true if t0 < t1, comparing components in order of significance. +template +bool +operator<(const Tuple& t0, const Tuple& t1) +{ + for (size_t i = 0; i < SIZE-1; ++i) { + if (!isExactlyEqual(t0[i], t1[i])) return t0[i] < t1[i]; + } + return t0[SIZE-1] < t1[SIZE-1]; +} + + +/// @return true if t0 > t1, comparing components in order of significance. +template +bool +operator>(const Tuple& t0, const Tuple& t1) +{ + for (size_t i = 0; i < SIZE-1; ++i) { + if (!isExactlyEqual(t0[i], t1[i])) return t0[i] > t1[i]; + } + return t0[SIZE-1] > t1[SIZE-1]; +} + + +//////////////////////////////////////// + + +/// Helper class to compute the absolute value of a Tuple +template +struct TupleAbs { + static inline Tuple absVal(const Tuple& t) + { + Tuple result; + for (size_t i = 0; i < SIZE; ++i) result[i] = ::fabs(t[i]); + return result; + } +}; + +// Partial specialization for integer types, using abs() instead of fabs() +template +struct TupleAbs { + static inline Tuple absVal(const Tuple& t) + { + Tuple result; + for (size_t i = 0; i < SIZE; ++i) result[i] = ::abs(t[i]); + return result; + } +}; + + +/// @return the absolute value of the given Tuple. +template +Tuple +Abs(const Tuple& t) +{ + return TupleAbs::value>::absVal(t); +} + + +//////////////////////////////////////// + + +/// Write a Tuple to an output stream +template +std::ostream& operator<<(std::ostream& ostr, const Tuple& classname) +{ + ostr << classname.str(); + return ostr; +} + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_TUPLE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Vec2.h b/openvdb_2_3_0_library/openvdb/math/Vec2.h new file mode 100755 index 0000000..3a63e74 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Vec2.h @@ -0,0 +1,531 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_MATH_VEC2_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_VEC2_HAS_BEEN_INCLUDED + +#include +#include +#include "Math.h" +#include "Tuple.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +template class Mat2; + +template +class Vec2: public Tuple<2, T> +{ +public: + typedef T value_type; + typedef T ValueType; + + /// Trivial constructor, the vector is NOT initialized + Vec2() {} + + /// Constructor with one argument, e.g. Vec2f v(0); + explicit Vec2(T val) { this->mm[0] = this->mm[1] = val; } + + /// Constructor with three arguments, e.g. Vec2f v(1,2,3); + Vec2(T x, T y) + { + this->mm[0] = x; + this->mm[1] = y; + } + + /// Constructor with array argument, e.g. float a[2]; Vec2f v(a); + template + Vec2(Source *a) + { + this->mm[0] = a[0]; + this->mm[1] = a[1]; + } // trivial + + /// Conversion constructor + template + explicit Vec2(const Tuple<2, Source> &t) + { + this->mm[0] = static_cast(t[0]); + this->mm[1] = static_cast(t[1]); + } + + /// Reference to the component, e.g. v.x() = 4.5f; + T& x() {return this->mm[0];} + T& y() {return this->mm[1];} + + /// Get the component, e.g. float f = v.y(); + T x() const {return this->mm[0];} + T y() const {return this->mm[1];} + + /// Alternative indexed reference to the elements + T& operator()(int i) {return this->mm[i];} + + /// Alternative indexed constant reference to the elements, + T operator()(int i) const {return this->mm[i];} + + T* asPointer() {return this->mm;} + const T* asPointer() const {return this->mm;} + + /// "this" vector gets initialized to [x, y, z], + /// calling v.init(); has same effect as calling v = Vec2::zero(); + const Vec2& init(T x=0, T y=0) + { + this->mm[0] = x; this->mm[1] = y; + return *this; + } + + /// Set "this" vector to zero + const Vec2& setZero() + { + this->mm[0] = 0; this->mm[1] = 0; + return *this; + } + + /// Assignment operator + template + const Vec2& operator=(const Vec2 &v) + { + // note: don't static_cast because that suppresses warnings + this->mm[0] = v[0]; + this->mm[1] = v[1]; + + return *this; + } + + /// Equality operator, does exact floating point comparisons + bool operator==(const Vec2 &v) const + { + return (isExactlyEqual(this->mm[0], v.mm[0]) && isExactlyEqual(this->mm[1], v.mm[1])); + } + + /// Inequality operator, does exact floating point comparisons + bool operator!=(const Vec2 &v) const { return !(*this==v); } + + /// Test if "this" vector is equivalent to vector v with tolerance of eps + bool eq(const Vec2 &v, T eps = static_cast(1.0e-7)) const + { + return isApproxEqual(this->mm[0], v.mm[0], eps) && + isApproxEqual(this->mm[1], v.mm[1], eps); + } // trivial + + /// Negation operator, for e.g. v1 = -v2; + Vec2 operator-() const {return Vec2(-this->mm[0], -this->mm[1]);} + + /// this = v1 + v2 + /// "this", v1 and v2 need not be distinct objects, e.g. v.add(v1,v); + template + const Vec2& add(const Vec2 &v1, const Vec2 &v2) + { + this->mm[0] = v1[0] + v2[0]; + this->mm[1] = v1[1] + v2[1]; + + return *this; + } + + /// this = v1 - v2 + /// "this", v1 and v2 need not be distinct objects, e.g. v.sub(v1,v); + template + const Vec2& sub(const Vec2 &v1, const Vec2 &v2) + { + this->mm[0] = v1[0] - v2[0]; + this->mm[1] = v1[1] - v2[1]; + + return *this; + } + + /// this = scalar*v, v need not be a distinct object from "this", + /// e.g. v.scale(1.5,v1); + template + const Vec2& scale(T0 scalar, const Vec2 &v) + { + this->mm[0] = scalar * v[0]; + this->mm[1] = scalar * v[1]; + + return *this; + } + + template + const Vec2 &div(T0 scalar, const Vec2 &v) + { + this->mm[0] = v[0] / scalar; + this->mm[1] = v[1] / scalar; + + return *this; + } + + /// Dot product + T dot(const Vec2 &v) const { return this->mm[0]*v[0] + this->mm[1]*v[1]; } // trivial + + /// Length of the vector + T length() const + { + return static_cast(sqrt(double(this->mm[0]*this->mm[0] + this->mm[1]*this->mm[1]))); + } + + /// Squared length of the vector, much faster than length() as it + /// does not involve square root + T lengthSqr() const { return (this->mm[0]*this->mm[0] + this->mm[1]*this->mm[1]); } + + /// 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 the sum of all the vector components. + inline T sum() 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; + } + + /// Returns v, where \f$v_i *= scalar\f$ for \f$i \in [0, 1]\f$ + template + const Vec2 &operator*=(S scalar) + { + this->mm[0] *= scalar; + this->mm[1] *= scalar; + return *this; + } + + /// Returns v0, where \f$v0_i *= v1_i\f$ for \f$i \in [0, 1]\f$ + template + const Vec2 &operator*=(const Vec2 &v1) + { + this->mm[0] *= v1[0]; + this->mm[1] *= v1[1]; + return *this; + } + + /// Returns v, where \f$v_i /= scalar\f$ for \f$i \in [0, 1]\f$ + template + const Vec2 &operator/=(S scalar) + { + this->mm[0] /= scalar; + this->mm[1] /= scalar; + return *this; + } + + /// Returns v0, where \f$v0_i /= v1_i\f$ for \f$i \in [0, 1]\f$ + template + const Vec2 &operator/=(const Vec2 &v1) + { + this->mm[0] /= v1[0]; + this->mm[1] /= v1[1]; + return *this; + } + + /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 1]\f$ + template + const Vec2 &operator+=(S scalar) + { + this->mm[0] += scalar; + this->mm[1] += scalar; + return *this; + } + + /// Returns v0, where \f$v0_i += v1_i\f$ for \f$i \in [0, 1]\f$ + template + const Vec2 &operator+=(const Vec2 &v1) + { + this->mm[0] += v1[0]; + this->mm[1] += v1[1]; + return *this; + } + + /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 1]\f$ + template + const Vec2 &operator-=(S scalar) + { + this->mm[0] -= scalar; + this->mm[1] -= scalar; + return *this; + } + + /// Returns v0, where \f$v0_i -= v1_i\f$ for \f$i \in [0, 1]\f$ + template + const Vec2 &operator-=(const Vec2 &v1) + { + this->mm[0] -= v1[0]; + this->mm[1] -= v1[1]; + return *this; + } + + // Number of cols, rows, elements + static unsigned numRows() { return 1; } + static unsigned numColumns() { return 2; } + static unsigned numElements() { return 2; } + + /// Returns the scalar component of v in the direction of onto, onto need + /// not be unit. e.g float c = Vec2f::component(v1,v2); + T component(const Vec2 &onto, T eps=1.0e-8) const + { + T l = onto.length(); + if (isApproxEqual(l, T(0), eps)) return 0; + + return dot(onto)*(T(1)/l); + } + + /// Return the projection of v onto the vector, onto need not be unit + /// e.g. Vec2f v = Vec2f::projection(v,n); + Vec2 projection(const Vec2 &onto, T eps=1.0e-8) const + { + T l = onto.lengthSqr(); + if (isApproxEqual(l, T(0), eps)) return Vec2::zero(); + + return onto*(dot(onto)*(T(1)/l)); + } + + /// Return an arbitrary unit vector perpendicular to v + /// Vector v must be a unit vector + /// e.g. v.normalize(); Vec2f n = Vec2f::getArbPerpendicular(v); + Vec2 getArbPerpendicular() const { return Vec2(-this->mm[1], this->mm[0]); } + + /// True if a Nan is present in vector + bool isNan() const { return isnan(this->mm[0]) || isnan(this->mm[1]); } + + /// True if an Inf is present in vector + bool isInfinite() const { return isinf(this->mm[0]) || isinf(this->mm[1]); } + + /// True if all no Nan or Inf values present + bool isFinite() const { return finite(this->mm[0]) && finite(this->mm[1]); } + + /// Predefined constants, e.g. Vec2f v = Vec2f::xNegAxis(); + static Vec2 zero() { return Vec2(0, 0); } +}; + + +/// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 1]\f$ +template +inline Vec2::type> operator*(S scalar, const Vec2 &v) +{ + return v * scalar; +} + +/// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 1]\f$ +template +inline Vec2::type> operator*(const Vec2 &v, S scalar) +{ + Vec2::type> result(v); + result *= scalar; + return result; +} + +/// Returns V, where \f$V_i = v0_i * v1_i\f$ for \f$i \in [0, 1]\f$ +template +inline Vec2::type> operator*(const Vec2 &v0, const Vec2 &v1) +{ + Vec2::type> result(v0[0] * v1[0], v0[1] * v1[1]); + return result; +} + +/// Returns V, where \f$V_i = scalar / v_i\f$ for \f$i \in [0, 1]\f$ +template +inline Vec2::type> operator/(S scalar, const Vec2 &v) +{ + return Vec2::type>(scalar/v[0], scalar/v[1]); +} + +/// Returns V, where \f$V_i = v_i / scalar\f$ for \f$i \in [0, 1]\f$ +template +inline Vec2::type> operator/(const Vec2 &v, S scalar) +{ + Vec2::type> result(v); + result /= scalar; + return result; +} + +/// Returns V, where \f$V_i = v0_i / v1_i\f$ for \f$i \in [0, 1]\f$ +template +inline Vec2::type> operator/(const Vec2 &v0, const Vec2 &v1) +{ + Vec2::type> result(v0[0] / v1[0], v0[1] / v1[1]); + return result; +} + +/// Returns V, where \f$V_i = v0_i + v1_i\f$ for \f$i \in [0, 1]\f$ +template +inline Vec2::type> operator+(const Vec2 &v0, const Vec2 &v1) +{ + Vec2::type> result(v0); + result += v1; + return result; +} + +/// Returns V, where \f$V_i = v_i + scalar\f$ for \f$i \in [0, 1]\f$ +template +inline Vec2::type> operator+(const Vec2 &v, S scalar) +{ + Vec2::type> result(v); + result += scalar; + return result; +} + +/// Returns V, where \f$V_i = v0_i - v1_i\f$ for \f$i \in [0, 1]\f$ +template +inline Vec2::type> operator-(const Vec2 &v0, const Vec2 &v1) +{ + Vec2::type> result(v0); + result -= v1; + return result; +} + +/// Returns V, where \f$V_i = v_i - scalar\f$ for \f$i \in [0, 1]\f$ +template +inline Vec2::type> operator-(const Vec2 &v, S scalar) +{ + Vec2::type> result(v); + result -= scalar; + return result; +} + +/// Angle between two vectors, the result is between [0, pi], +/// e.g. float a = Vec2f::angle(v1,v2); +template +inline T angle(const Vec2 &v1, const Vec2 &v2) +{ + T c = v1.dot(v2); + return acos(c); +} + +template +inline bool +isApproxEqual(const Vec2& a, const Vec2& b) +{ + return a.eq(b); +} +template +inline bool +isApproxEqual(const Vec2& a, const Vec2& b, const Vec2& eps) +{ + return isApproxEqual(a.x(), b.x(), eps.x()) && + isApproxEqual(a.y(), b.y(), eps.y()); +} + +/// Orthonormalize vectors v1 and v2 and store back the resulting basis +/// e.g. Vec2f::orthonormalize(v1,v2); +template +inline void orthonormalize(Vec2 &v1, Vec2 &v2) +{ + // If the input vectors are v0, v1, and v2, then the Gram-Schmidt + // orthonormalization produces vectors u0, u1, and u2 as follows, + // + // u0 = v0/|v0| + // u1 = (v1-(u0*v1)u0)/|v1-(u0*v1)u0| + // + // where |A| indicates length of vector A and A*B indicates dot + // product of vectors A and B. + + // compute u0 + v1.normalize(); + + // compute u1 + T d0 = v1.dot(v2); + v2 -= v1*d0; + v2.normalize(); +} + + +/// \remark We are switching to a more explicit name because the semantics +/// are different from std::min/max. In that case, the function returns a +/// reference to one of the objects based on a comparator. Here, we must +/// fabricate a new object which might not match either of the inputs. + +/// Return component-wise minimum of the two vectors. +template +inline Vec2 minComponent(const Vec2 &v1, const Vec2 &v2) +{ + return Vec2( + std::min(v1.x(), v2.x()), + std::min(v1.y(), v2.y())); +} + +/// Return component-wise maximum of the two vectors. +template +inline Vec2 maxComponent(const Vec2 &v1, const Vec2 &v2) +{ + return Vec2( + std::max(v1.x(), v2.x()), + std::max(v1.y(), v2.y())); +} + +/// @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(); } + +typedef Vec2 Vec2i; +typedef Vec2 Vec2ui; +typedef Vec2 Vec2s; +typedef Vec2 Vec2d; + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_VEC2_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Vec3.h b/openvdb_2_3_0_library/openvdb/math/Vec3.h new file mode 100755 index 0000000..fac7319 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Vec3.h @@ -0,0 +1,635 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_MATH_VEC3_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_VEC3_HAS_BEEN_INCLUDED + +#include +#include +#include "Math.h" +#include "Tuple.h" + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +template class Mat3; + +template +class Vec3: public Tuple<3, T> +{ +public: + typedef T value_type; + typedef T ValueType; + + /// Trivial constructor, the vector is NOT initialized + Vec3() {} + + /// Constructor with one argument, e.g. Vec3f v(0); + explicit Vec3(T val) { this->mm[0] = this->mm[1] = this->mm[2] = val; } + + /// Constructor with three arguments, e.g. Vec3d v(1,2,3); + Vec3(T x, T y, T z) + { + this->mm[0] = x; + this->mm[1] = y; + this->mm[2] = z; + } + + /// Constructor with array argument, e.g. double a[3]; Vec3d v(a); + template + Vec3(Source *a) + { + this->mm[0] = a[0]; + this->mm[1] = a[1]; + this->mm[2] = a[2]; + } + + /// Conversion constructor + template + explicit Vec3(const Tuple<3, Source> &v) + { + this->mm[0] = static_cast(v[0]); + this->mm[1] = static_cast(v[1]); + this->mm[2] = static_cast(v[2]); + } + + template + Vec3(const Vec3& v) + { + this->mm[0] = static_cast(v[0]); + this->mm[1] = static_cast(v[1]); + this->mm[2] = static_cast(v[2]); + } + + /// Reference to the component, e.g. v.x() = 4.5f; + T& x() { return this->mm[0]; } + T& y() { return this->mm[1]; } + T& z() { return this->mm[2]; } + + /// Get the component, e.g. float f = v.y(); + T x() const { return this->mm[0]; } + T y() const { return this->mm[1]; } + T z() const { return this->mm[2]; } + + T* asPointer() { return this->mm; } + const T* asPointer() const { return this->mm; } + + /// Alternative indexed reference to the elements + T& operator()(int i) { return this->mm[i]; } + + /// Alternative indexed constant reference to the elements, + T operator()(int i) const { return this->mm[i]; } + + /// "this" vector gets initialized to [x, y, z], + /// calling v.init(); has same effect as calling v = Vec3::zero(); + const Vec3& init(T x=0, T y=0, T z=0) + { + this->mm[0] = x; this->mm[1] = y; this->mm[2] = z; + return *this; + } + + + /// Set "this" vector to zero + const Vec3& setZero() + { + this->mm[0] = 0; this->mm[1] = 0; this->mm[2] = 0; + return *this; + } + + /// Assignment operator + template + const Vec3& operator=(const Vec3 &v) + { + // note: don't static_cast because that suppresses warnings + this->mm[0] = ValueType(v[0]); + this->mm[1] = ValueType(v[1]); + this->mm[2] = ValueType(v[2]); + + return *this; + } + + /// Test if "this" vector is equivalent to vector v with tolerance of eps + bool eq(const Vec3 &v, T eps = static_cast(1.0e-7)) const + { + return isRelOrApproxEqual(this->mm[0], v.mm[0], eps, eps) && + isRelOrApproxEqual(this->mm[1], v.mm[1], eps, eps) && + isRelOrApproxEqual(this->mm[2], v.mm[2], eps, eps); + } + + + /// Negation operator, for e.g. v1 = -v2; + Vec3 operator-() const { return Vec3(-this->mm[0], -this->mm[1], -this->mm[2]); } + + /// this = v1 + v2 + /// "this", v1 and v2 need not be distinct objects, e.g. v.add(v1,v); + template + const Vec3& add(const Vec3 &v1, const Vec3 &v2) + { + this->mm[0] = v1[0] + v2[0]; + this->mm[1] = v1[1] + v2[1]; + this->mm[2] = v1[2] + v2[2]; + + return *this; + } + + /// this = v1 - v2 + /// "this", v1 and v2 need not be distinct objects, e.g. v.sub(v1,v); + template + const Vec3& sub(const Vec3 &v1, const Vec3 &v2) + { + this->mm[0] = v1[0] - v2[0]; + this->mm[1] = v1[1] - v2[1]; + this->mm[2] = v1[2] - v2[2]; + + return *this; + } + + /// this = scalar*v, v need not be a distinct object from "this", + /// e.g. v.scale(1.5,v1); + template + const Vec3& scale(T0 scale, const Vec3 &v) + { + this->mm[0] = scale * v[0]; + this->mm[1] = scale * v[1]; + this->mm[2] = scale * v[2]; + + return *this; + } + + template + const Vec3 &div(T0 scale, const Vec3 &v) + { + this->mm[0] = v[0] / scale; + this->mm[1] = v[1] / scale; + this->mm[2] = v[2] / scale; + + return *this; + } + + /// Dot product + T dot(const Vec3 &v) const + { + return + this->mm[0]*v.mm[0] + + this->mm[1]*v.mm[1] + + this->mm[2]*v.mm[2]; + } + + /// Length of the vector + T length() const + { + return static_cast(sqrt(double( + this->mm[0]*this->mm[0] + + this->mm[1]*this->mm[1] + + this->mm[2]*this->mm[2]))); + } + + + /// Squared length of the vector, much faster than length() as it + /// does not involve square root + T lengthSqr() const + { + return + this->mm[0]*this->mm[0] + + this->mm[1]*this->mm[1] + + this->mm[2]*this->mm[2]; + } + + /// Return the cross product of "this" vector and v; + Vec3 cross(const Vec3 &v) const + { + return Vec3(this->mm[1]*v.mm[2] - this->mm[2]*v.mm[1], + this->mm[2]*v.mm[0] - this->mm[0]*v.mm[2], + this->mm[0]*v.mm[1] - this->mm[1]*v.mm[0]); + } + + + /// this = v1 cross v2, v1 and v2 must be distinct objects than "this" + const Vec3& cross(const Vec3 &v1, const Vec3 &v2) + { + // assert(this!=&v1); + // assert(this!=&v2); + this->mm[0] = v1.mm[1]*v2.mm[2] - v1.mm[2]*v2.mm[1]; + this->mm[1] = v1.mm[2]*v2.mm[0] - v1.mm[0]*v2.mm[2]; + this->mm[2] = v1.mm[0]*v2.mm[1] - v1.mm[1]*v2.mm[0]; + return *this; + } + + /// Returns v, where \f$v_i *= scalar\f$ for \f$i \in [0, 2]\f$ + template + const Vec3 &operator*=(S scalar) + { + this->mm[0] *= scalar; + this->mm[1] *= scalar; + this->mm[2] *= scalar; + return *this; + } + + /// Returns v0, where \f$v0_i *= v1_i\f$ for \f$i \in [0, 2]\f$ + template + const Vec3 &operator*=(const Vec3 &v1) + { + this->mm[0] *= v1[0]; + this->mm[1] *= v1[1]; + this->mm[2] *= v1[2]; + return *this; + } + + /// Returns v, where \f$v_i /= scalar\f$ for \f$i \in [0, 2]\f$ + template + const Vec3 &operator/=(S scalar) + { + this->mm[0] /= scalar; + this->mm[1] /= scalar; + this->mm[2] /= scalar; + return *this; + } + + /// Returns v0, where \f$v0_i /= v1_i\f$ for \f$i \in [0, 2]\f$ + template + const Vec3 &operator/=(const Vec3 &v1) + { + this->mm[0] /= v1[0]; + this->mm[1] /= v1[1]; + this->mm[2] /= v1[2]; + return *this; + } + + /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 2]\f$ + template + const Vec3 &operator+=(S scalar) + { + this->mm[0] += scalar; + this->mm[1] += scalar; + this->mm[2] += scalar; + return *this; + } + + /// Returns v0, where \f$v0_i += v1_i\f$ for \f$i \in [0, 2]\f$ + template + const Vec3 &operator+=(const Vec3 &v1) + { + this->mm[0] += v1[0]; + this->mm[1] += v1[1]; + this->mm[2] += v1[2]; + return *this; + } + + /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 2]\f$ + template + const Vec3 &operator-=(S scalar) + { + this->mm[0] -= scalar; + this->mm[1] -= scalar; + this->mm[2] -= scalar; + return *this; + } + + /// Returns v0, where \f$v0_i -= v1_i\f$ for \f$i \in [0, 2]\f$ + template + const Vec3 &operator-=(const Vec3 &v1) + { + this->mm[0] -= v1[0]; + this->mm[1] -= v1[1]; + this->mm[2] -= v1[2]; + return *this; + } + + /// Return a reference to itsef 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 the sum of all the vector components. + inline T sum() 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; + } + + // Number of cols, rows, elements + static unsigned numRows() { return 1; } + static unsigned numColumns() { return 3; } + static unsigned numElements() { return 3; } + + /// Returns the scalar component of v in the direction of onto, onto need + /// not be unit. e.g double c = Vec3d::component(v1,v2); + T component(const Vec3 &onto, T eps=1.0e-7) const + { + T l = onto.length(); + if (isApproxEqual(l, T(0), eps)) return 0; + + return dot(onto)*(T(1)/l); + } + + /// Return the projection of v onto the vector, onto need not be unit + /// e.g. Vec3d a = vprojection(n); + Vec3 projection(const Vec3 &onto, T eps=1.0e-7) const + { + T l = onto.lengthSqr(); + if (isApproxEqual(l, T(0), eps)) return Vec3::zero(); + + return onto*(dot(onto)*(T(1)/l)); + } + + /// Return an arbitrary unit vector perpendicular to v + /// Vector this must be a unit vector + /// e.g. v = v.normalize(); Vec3d n = v.getArbPerpendicular(); + Vec3 getArbPerpendicular() const + { + Vec3 u; + T l; + + if ( fabs(this->mm[0]) >= fabs(this->mm[1]) ) { + // v.x or v.z is the largest magnitude component, swap them + l = this->mm[0]*this->mm[0] + this->mm[2]*this->mm[2]; + l = static_cast(T(1)/sqrt(double(l))); + u.mm[0] = -this->mm[2]*l; + u.mm[1] = (T)0.0; + u.mm[2] = +this->mm[0]*l; + } else { + // W.y or W.z is the largest magnitude component, swap them + l = this->mm[1]*this->mm[1] + this->mm[2]*this->mm[2]; + l = static_cast(T(1)/sqrt(double(l))); + u.mm[0] = (T)0.0; + u.mm[1] = +this->mm[2]*l; + u.mm[2] = -this->mm[1]*l; + } + + return u; + } + + /// True if a Nan is present in vector + bool isNan() const { return isnan(this->mm[0]) || isnan(this->mm[1]) || isnan(this->mm[2]); } + + /// True if an Inf is present in vector + bool isInfinite() const + { + return isinf(this->mm[0]) || isinf(this->mm[1]) || isinf(this->mm[2]); + } + + /// True if all no Nan or Inf values present + bool isFinite() const + { + return finite(this->mm[0]) && finite(this->mm[1]) && finite(this->mm[2]); + } + + /// Predefined constants, e.g. Vec3d v = Vec3d::xNegAxis(); + static Vec3 zero() { return Vec3(0, 0, 0); } +}; + + +/// Equality operator, does exact floating point comparisons +template +inline bool operator==(const Vec3 &v0, const Vec3 &v1) +{ + return isExactlyEqual(v0[0], v1[0]) && isExactlyEqual(v0[1], v1[1]) + && isExactlyEqual(v0[2], v1[2]); +} + +/// Inequality operator, does exact floating point comparisons +template +inline bool operator!=(const Vec3 &v0, const Vec3 &v1) { return !(v0==v1); } + +/// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> operator*(S scalar, const Vec3 &v) { return v*scalar; } + +/// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> operator*(const Vec3 &v, S scalar) +{ + Vec3::type> result(v); + result *= scalar; + return result; +} + +/// Returns V, where \f$V_i = v0_i * v1_i\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> operator*(const Vec3 &v0, const Vec3 &v1) +{ + Vec3::type> result(v0[0] * v1[0], v0[1] * v1[1], v0[2] * v1[2]); + return result; +} + + +/// Returns V, where \f$V_i = scalar / v_i\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> operator/(S scalar, const Vec3 &v) +{ + return Vec3::type>(scalar/v[0], scalar/v[1], scalar/v[2]); +} + +/// Returns V, where \f$V_i = v_i / scalar\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> operator/(const Vec3 &v, S scalar) +{ + Vec3::type> result(v); + result /= scalar; + return result; +} + +/// Returns V, where \f$V_i = v0_i / v1_i\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> operator/(const Vec3 &v0, const Vec3 &v1) +{ + Vec3::type> result(v0[0] / v1[0], v0[1] / v1[1], v0[2] / v1[2]); + return result; +} + +/// Returns V, where \f$V_i = v0_i + v1_i\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> operator+(const Vec3 &v0, const Vec3 &v1) +{ + Vec3::type> result(v0); + result += v1; + return result; +} + +/// Returns V, where \f$V_i = v_i + scalar\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> operator+(const Vec3 &v, S scalar) +{ + Vec3::type> result(v); + result += scalar; + return result; +} + +/// Returns V, where \f$V_i = v0_i - v1_i\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> operator-(const Vec3 &v0, const Vec3 &v1) +{ + Vec3::type> result(v0); + result -= v1; + return result; +} + +/// Returns V, where \f$V_i = v_i - scalar\f$ for \f$i \in [0, 2]\f$ +template +inline Vec3::type> operator-(const Vec3 &v, S scalar) +{ + Vec3::type> result(v); + result -= scalar; + return result; +} + +/// Angle between two vectors, the result is between [0, pi], +/// e.g. double a = Vec3d::angle(v1,v2); +template +inline T angle(const Vec3 &v1, const Vec3 &v2) +{ + Vec3 c = v1.cross(v2); + return static_cast(atan2(c.length(), v1.dot(v2))); +} + +template +inline bool +isApproxEqual(const Vec3& a, const Vec3& b) +{ + return a.eq(b); +} +template +inline bool +isApproxEqual(const Vec3& a, const Vec3& b, const Vec3& eps) +{ + return isApproxEqual(a.x(), b.x(), eps.x()) && + isApproxEqual(a.y(), b.y(), eps.y()) && + isApproxEqual(a.z(), b.z(), eps.z()); +} + +/// Orthonormalize vectors v1, v2 and v3 and store back the resulting +/// basis e.g. Vec3d::orthonormalize(v1,v2,v3); +template +inline void orthonormalize(Vec3 &v1, Vec3 &v2, Vec3 &v3) +{ + // If the input vectors are v0, v1, and v2, then the Gram-Schmidt + // orthonormalization produces vectors u0, u1, and u2 as follows, + // + // u0 = v0/|v0| + // u1 = (v1-(u0*v1)u0)/|v1-(u0*v1)u0| + // u2 = (v2-(u0*v2)u0-(u1*v2)u1)/|v2-(u0*v2)u0-(u1*v2)u1| + // + // where |A| indicates length of vector A and A*B indicates dot + // product of vectors A and B. + + // compute u0 + v1.normalize(); + + // compute u1 + T d0 = v1.dot(v2); + v2 -= v1*d0; + v2.normalize(); + + // compute u2 + T d1 = v2.dot(v3); + d0 = v1.dot(v3); + v3 -= v1*d0 + v2*d1; + v3.normalize(); +} + +/// @remark We are switching to a more explicit name because the semantics +/// are different from std::min/max. In that case, the function returns a +/// reference to one of the objects based on a comparator. Here, we must +/// fabricate a new object which might not match either of the inputs. + +/// Return component-wise minimum of the two vectors. +template +inline Vec3 minComponent(const Vec3 &v1, const Vec3 &v2) +{ + return Vec3( + std::min(v1.x(), v2.x()), + std::min(v1.y(), v2.y()), + std::min(v1.z(), v2.z())); +} + +/// Return component-wise maximum of the two vectors. +template +inline Vec3 maxComponent(const Vec3 &v1, const Vec3 &v2) +{ + return Vec3( + std::max(v1.x(), v2.x()), + std::max(v1.y(), v2.y()), + std::max(v1.z(), v2.z())); +} + +/// @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(); } + +typedef Vec3 Vec3i; +typedef Vec3 Vec3ui; +typedef Vec3 Vec3s; +typedef Vec3 Vec3d; + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_VEC3_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/math/Vec4.h b/openvdb_2_3_0_library/openvdb/math/Vec4.h new file mode 100755 index 0000000..a8ddf50 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/math/Vec4.h @@ -0,0 +1,556 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_MATH_VEC4_HAS_BEEN_INCLUDED +#define OPENVDB_MATH_VEC4_HAS_BEEN_INCLUDED + +#include +#include +#include "Math.h" +#include "Tuple.h" +#include "Vec3.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace math { + +template class Mat3; + +template +class Vec4: public Tuple<4, T> +{ +public: + typedef T value_type; + typedef T ValueType; + + /// Trivial constructor, the vector is NOT initialized + Vec4() {} + + /// Constructor with one argument, e.g. Vec4f v(0); + explicit Vec4(T val) { this->mm[0] = this->mm[1] = this->mm[2] = this->mm[3] = val; } + + /// Constructor with three arguments, e.g. Vec4f v(1,2,3); + Vec4(T x, T y, T z, T w) + { + this->mm[0] = x; + this->mm[1] = y; + this->mm[2] = z; + this->mm[3] = w; + } + + /// Constructor with array argument, e.g. float a[4]; Vec4f v(a); + template + Vec4(Source *a) + { + this->mm[0] = a[0]; + this->mm[1] = a[1]; + this->mm[2] = a[2]; + this->mm[3] = a[3]; + } + + /// Conversion constructor + template + explicit Vec4(const Tuple<4, Source> &v) + { + this->mm[0] = static_cast(v[0]); + this->mm[1] = static_cast(v[1]); + this->mm[2] = static_cast(v[2]); + this->mm[3] = static_cast(v[3]); + } + + /// Reference to the component, e.g. v.x() = 4.5f; + T& x() { return this->mm[0]; } + T& y() { return this->mm[1]; } + T& z() { return this->mm[2]; } + T& w() { return this->mm[3]; } + + /// Get the component, e.g. float f = v.y(); + T x() const { return this->mm[0]; } + T y() const { return this->mm[1]; } + T z() const { return this->mm[2]; } + T w() const { return this->mm[3]; } + + T* asPointer() { return this->mm; } + const T* asPointer() const { return this->mm; } + + /// Alternative indexed reference to the elements + T& operator()(int i) { return this->mm[i]; } + + /// Alternative indexed constant reference to the elements, + T operator()(int i) const { return this->mm[i]; } + + /// Returns a Vec3 with the first three elements of the Vec4. + Vec3 getVec3() const { return Vec3(this->mm[0], this->mm[1], this->mm[2]); } + + /// "this" vector gets initialized to [x, y, z, w], + /// calling v.init(); has same effect as calling v = Vec4::zero(); + const Vec4& init(T x=0, T y=0, T z=0, T w=0) + { + this->mm[0] = x; this->mm[1] = y; this->mm[2] = z; this->mm[3] = w; + return *this; + } + + /// Set "this" vector to zero + const Vec4& setZero() + { + this->mm[0] = 0; this->mm[1] = 0; this->mm[2] = 0; this->mm[3] = 0; + return *this; + } + + /// Assignment operator + template + const Vec4& operator=(const Vec4 &v) + { + // note: don't static_cast because that suppresses warnings + this->mm[0] = v[0]; + this->mm[1] = v[1]; + this->mm[2] = v[2]; + this->mm[3] = v[3]; + + return *this; + } + + /// Test if "this" vector is equivalent to vector v with tolerance + /// of eps + bool eq(const Vec4 &v, T eps=1.0e-8) const + { + return isApproxEqual(this->mm[0], v.mm[0], eps) && + isApproxEqual(this->mm[1], v.mm[1], eps) && + isApproxEqual(this->mm[2], v.mm[2], eps) && + isApproxEqual(this->mm[3], v.mm[3], eps); + } + + /// Negation operator, for e.g. v1 = -v2; + Vec4 operator-() const + { + return Vec4( + -this->mm[0], + -this->mm[1], + -this->mm[2], + -this->mm[3]); + } + + /// this = v1 + v2 + /// "this", v1 and v2 need not be distinct objects, e.g. v.add(v1,v); + template + const Vec4& add(const Vec4 &v1, const Vec4 &v2) + { + this->mm[0] = v1[0] + v2[0]; + this->mm[1] = v1[1] + v2[1]; + this->mm[2] = v1[2] + v2[2]; + this->mm[3] = v1[3] + v2[3]; + + return *this; + } + + + /// this = v1 - v2 + /// "this", v1 and v2 need not be distinct objects, e.g. v.sub(v1,v); + template + const Vec4& sub(const Vec4 &v1, const Vec4 &v2) + { + this->mm[0] = v1[0] - v2[0]; + this->mm[1] = v1[1] - v2[1]; + this->mm[2] = v1[2] - v2[2]; + this->mm[3] = v1[3] - v2[3]; + + return *this; + } + + /// this = scalar*v, v need not be a distinct object from "this", + /// e.g. v.scale(1.5,v1); + template + const Vec4& scale(T0 scale, const Vec4 &v) + { + this->mm[0] = scale * v[0]; + this->mm[1] = scale * v[1]; + this->mm[2] = scale * v[2]; + this->mm[3] = scale * v[3]; + + return *this; + } + + template + const Vec4 &div(T0 scalar, const Vec4 &v) + { + this->mm[0] = v[0] / scalar; + this->mm[1] = v[1] / scalar; + this->mm[2] = v[2] / scalar; + this->mm[3] = v[3] / scalar; + + return *this; + } + + /// Dot product + T dot(const Vec4 &v) const + { + return (this->mm[0]*v.mm[0] + this->mm[1]*v.mm[1] + + this->mm[2]*v.mm[2] + this->mm[3]*v.mm[3]); + } + + /// Length of the vector + T length() const + { + return sqrt( + this->mm[0]*this->mm[0] + + this->mm[1]*this->mm[1] + + this->mm[2]*this->mm[2] + + this->mm[3]*this->mm[3]); + } + + + /// Squared length of the vector, much faster than length() as it + /// does not involve square root + T lengthSqr() const + { + return (this->mm[0]*this->mm[0] + this->mm[1]*this->mm[1] + + this->mm[2]*this->mm[2] + this->mm[3]*this->mm[3]); + } + + /// Return a reference to itsef 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 the sum of all the vector components. + inline T sum() 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; + } + + /// Returns v, where \f$v_i *= scalar\f$ for \f$i \in [0, 3]\f$ + template + const Vec4 &operator*=(S scalar) + { + this->mm[0] *= scalar; + this->mm[1] *= scalar; + this->mm[2] *= scalar; + this->mm[3] *= scalar; + return *this; + } + + /// Returns v0, where \f$v0_i *= v1_i\f$ for \f$i \in [0, 3]\f$ + template + const Vec4 &operator*=(const Vec4 &v1) + { + this->mm[0] *= v1[0]; + this->mm[1] *= v1[1]; + this->mm[2] *= v1[2]; + this->mm[3] *= v1[3]; + + return *this; + } + + /// Returns v, where \f$v_i /= scalar\f$ for \f$i \in [0, 3]\f$ + template + const Vec4 &operator/=(S scalar) + { + this->mm[0] /= scalar; + this->mm[1] /= scalar; + this->mm[2] /= scalar; + this->mm[3] /= scalar; + return *this; + } + + /// Returns v0, where \f$v0_i /= v1_i\f$ for \f$i \in [0, 3]\f$ + template + const Vec4 &operator/=(const Vec4 &v1) + { + this->mm[0] /= v1[0]; + this->mm[1] /= v1[1]; + this->mm[2] /= v1[2]; + this->mm[3] /= v1[3]; + return *this; + } + + /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 3]\f$ + template + const Vec4 &operator+=(S scalar) + { + this->mm[0] += scalar; + this->mm[1] += scalar; + this->mm[2] += scalar; + this->mm[3] += scalar; + return *this; + } + + /// Returns v0, where \f$v0_i += v1_i\f$ for \f$i \in [0, 3]\f$ + template + const Vec4 &operator+=(const Vec4 &v1) + { + this->mm[0] += v1[0]; + this->mm[1] += v1[1]; + this->mm[2] += v1[2]; + this->mm[3] += v1[3]; + return *this; + } + + /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 3]\f$ + template + const Vec4 &operator-=(S scalar) + { + this->mm[0] -= scalar; + this->mm[1] -= scalar; + this->mm[2] -= scalar; + this->mm[3] -= scalar; + return *this; + } + + /// Returns v0, where \f$v0_i -= v1_i\f$ for \f$i \in [0, 3]\f$ + template + const Vec4 &operator-=(const Vec4 &v1) + { + this->mm[0] -= v1[0]; + this->mm[1] -= v1[1]; + this->mm[2] -= v1[2]; + this->mm[3] -= v1[3]; + return *this; + } + + // Number of cols, rows, elements + static unsigned numRows() { return 1; } + static unsigned numColumns() { return 4; } + static unsigned numElements() { return 4; } + + /// True if a Nan is present in vector + bool isNan() const + { + return isnan(this->mm[0]) || isnan(this->mm[1]) + || isnan(this->mm[2]) || isnan(this->mm[3]); + } + + /// True if an Inf is present in vector + bool isInfinite() const + { + return isinf(this->mm[0]) || isinf(this->mm[1]) + || isinf(this->mm[2]) || isinf(this->mm[3]); + } + + /// True if all no Nan or Inf values present + bool isFinite() const + { + return finite(this->mm[0]) && finite(this->mm[1]) + && finite(this->mm[2]) && finite(this->mm[3]); + } + + /// Predefined constants, e.g. Vec4f v = Vec4f::xNegAxis(); + static Vec4 zero() { return Vec4(0, 0, 0, 0); } + static Vec4 origin() { return Vec4(0, 0, 0, 1); } +}; + +/// Equality operator, does exact floating point comparisons +template +inline bool operator==(const Vec4 &v0, const Vec4 &v1) +{ + return + isExactlyEqual(v0[0], v1[0]) && + isExactlyEqual(v0[1], v1[1]) && + isExactlyEqual(v0[2], v1[2]) && + isExactlyEqual(v0[3], v1[3]); +} + +/// Inequality operator, does exact floating point comparisons +template +inline bool operator!=(const Vec4 &v0, const Vec4 &v1) { return !(v0==v1); } + +/// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> operator*(S scalar, const Vec4 &v) +{ return v*scalar; } + +/// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> operator*(const Vec4 &v, S scalar) +{ + Vec4::type> result(v); + result *= scalar; + return result; +} + +/// Returns V, where \f$V_i = v0_i * v1_i\f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> operator*(const Vec4 &v0, + const Vec4 &v1) +{ + Vec4::type> result(v0[0]*v1[0], + v0[1]*v1[1], + v0[2]*v1[2], + v0[3]*v1[3]); + return result; +} + +/// Returns V, where \f$V_i = scalar / v_i\f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> operator/(S scalar, const Vec4 &v) +{ + return Vec4::type>(scalar/v[0], + scalar/v[1], + scalar/v[2], + scalar/v[3]); +} + +/// Returns V, where \f$V_i = v_i / scalar\f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> operator/(const Vec4 &v, S scalar) +{ + Vec4::type> result(v); + result /= scalar; + return result; +} + +/// Returns V, where \f$V_i = v0_i / v1_i\f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> operator/(const Vec4 &v0, + const Vec4 &v1) +{ + Vec4::type> + result(v0[0]/v1[0], v0[1]/v1[1], v0[2]/v1[2], v0[3]/v1[3]); + return result; +} + +/// Returns V, where \f$V_i = v0_i + v1_i\f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> operator+(const Vec4 &v0, const Vec4 &v1) +{ + Vec4::type> result(v0); + result += v1; + return result; +} + +/// Returns V, where \f$V_i = v_i + scalar\f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> operator+(const Vec4 &v, S scalar) +{ + Vec4::type> result(v); + result += scalar; + return result; +} + +/// Returns V, where \f$V_i = v0_i - v1_i\f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> operator-(const Vec4 &v0, const Vec4 &v1) +{ + Vec4::type> result(v0); + result -= v1; + return result; +} + +/// Returns V, where \f$V_i = v_i - scalar\f$ for \f$i \in [0, 3]\f$ +template +inline Vec4::type> operator-(const Vec4 &v, S scalar) +{ + Vec4::type> result(v); + result -= scalar; + return result; +} + +/// @remark We are switching to a more explicit name because the semantics +/// are different from std::min/max. In that case, the function returns a +/// reference to one of the objects based on a comparator. Here, we must +/// fabricate a new object which might not match either of the inputs. + +/// Return component-wise minimum of the two vectors. +template +inline Vec4 minComponent(const Vec4 &v1, const Vec4 &v2) +{ + return Vec4( + std::min(v1.x(), v2.x()), + std::min(v1.y(), v2.y()), + std::min(v1.z(), v2.z()), + std::min(v1.w(), v2.w())); +} + +/// Return component-wise maximum of the two vectors. +template +inline Vec4 maxComponent(const Vec4 &v1, const Vec4 &v2) +{ + return Vec4( + std::max(v1.x(), v2.x()), + std::max(v1.y(), v2.y()), + std::max(v1.z(), v2.z()), + std::max(v1.w(), v2.w())); +} + +/// @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(); } + +typedef Vec4 Vec4i; +typedef Vec4 Vec4ui; +typedef Vec4 Vec4s; +typedef Vec4 Vec4d; + +} // namespace math +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_MATH_VEC4_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/metadata/MetaMap.cc b/openvdb_2_3_0_library/openvdb/metadata/MetaMap.cc new file mode 100755 index 0000000..c0ef6e3 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/metadata/MetaMap.cc @@ -0,0 +1,204 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +MetaMap::MetaMap(const MetaMap &other) +{ + // Insert all metadata into this map. + ConstMetaIterator iter = other.beginMeta(); + for( ; iter != other.endMeta(); ++iter) { + this->insertMeta(iter->first, *(iter->second)); + } +} + + +MetaMap::Ptr +MetaMap::copyMeta() const +{ + MetaMap::Ptr ret(new MetaMap); + ret->mMeta = this->mMeta; + return ret; +} + + +MetaMap::Ptr +MetaMap::deepCopyMeta() const +{ + return MetaMap::Ptr(new MetaMap(*this)); +} + + +MetaMap& +MetaMap::operator=(const MetaMap& other) +{ + if (&other != this) { + this->clearMetadata(); + // Insert all metadata into this map. + ConstMetaIterator iter = other.beginMeta(); + for ( ; iter != other.endMeta(); ++iter) { + this->insertMeta(iter->first, *(iter->second)); + } + } + return *this; +} + + +void +MetaMap::readMeta(std::istream &is) +{ + // Clear out the current metamap if need be. + this->clearMetadata(); + + // Read in the number of metadata items. + Index32 count = 0; + is.read(reinterpret_cast(&count), sizeof(Index32)); + + // Read in each metadata. + for (Index32 i = 0; i < count; ++i) { + // Read in the name. + Name name = readString(is); + + // Read in the metadata typename. + Name typeName = readString(is); + + // Create a metadata type from the typename. Make sure that the type is + // registered. + if (!Metadata::isRegisteredType(typeName)) { + OPENVDB_LOG_WARN("cannot read metadata \"" << name + << "\" of unregistered type \"" << typeName << "\""); + UnknownMetadata metadata; + metadata.read(is); + } else { + Metadata::Ptr metadata = Metadata::createMetadata(typeName); + + // Read the value from the stream. + metadata->read(is); + + // Add the name and metadata to the map. + insertMeta(name, *metadata); + } + } +} + +void +MetaMap::writeMeta(std::ostream &os) const +{ + // Write out the number of metadata items we have in the map. Note that we + // save as Index32 to save a 32-bit number. Using size_t would be platform + // dependent. + Index32 count = (Index32)metaCount(); + os.write(reinterpret_cast(&count), sizeof(Index32)); + + // Iterate through each metadata and write it out. + for (ConstMetaIterator iter = beginMeta(); iter != endMeta(); ++iter) { + // Write the name of the metadata. + writeString(os, iter->first); + + // Write the type name of the metadata. + writeString(os, iter->second->typeName()); + + // Write out the metadata value. + iter->second->write(os); + } +} + +void +MetaMap::insertMeta(const Name &name, const Metadata &m) +{ + if(name.size() == 0) + OPENVDB_THROW(ValueError, "Metadata name cannot be an empty string"); + + // See if the value already exists, if so then replace the existing one. + MetaIterator iter = mMeta.find(name); + + if(iter == mMeta.end()) { + // Create a copy of hte metadata and store it in the map + Metadata::Ptr tmp = m.copy(); + mMeta[name] = tmp; + } else { + if(iter->second->typeName() != m.typeName()) { + std::ostringstream ostr; + ostr << "Cannot assign value of type " + << m.typeName() << " to metadata attribute " << name + << " of " << "type " << iter->second->typeName(); + OPENVDB_THROW(TypeError, ostr.str()); + } + // else + Metadata::Ptr tmp = m.copy(); + iter->second = tmp; + } +} + +void +MetaMap::removeMeta(const Name &name) +{ + // Find the required metadata + MetaIterator iter = mMeta.find(name); + + if(iter == mMeta.end()) + return; + // else, delete the metadata and remove from the map + mMeta.erase(iter); +} + + +std::string +MetaMap::str() const +{ + std::ostringstream buffer; + buffer << "MetaMap:\n"; + for(ConstMetaIterator iter = beginMeta(); iter != endMeta(); ++iter) { + buffer << " " << iter->first << " = " << iter->second->str(); + buffer << std::endl; + } + return buffer.str(); +} + +std::ostream& +operator<<(std::ostream& ostr, const MetaMap& metamap) +{ + ostr << metamap.str(); + return ostr; +} + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/metadata/MetaMap.h b/openvdb_2_3_0_library/openvdb/metadata/MetaMap.h new file mode 100755 index 0000000..71734a6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/metadata/MetaMap.h @@ -0,0 +1,252 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_METADATA_METAMAP_HAS_BEEN_INCLUDED +#define OPENVDB_METADATA_METAMAP_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +/// @brief Provides functionality storing type agnostic metadata information. +/// Grids and other structures can inherit from this to attain metadata +/// functionality. +class OPENVDB_API MetaMap +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + typedef std::map MetadataMap; + typedef MetadataMap::iterator MetaIterator; + typedef MetadataMap::const_iterator ConstMetaIterator; + ///< @todo this should really iterate over a map of Metadata::ConstPtrs + + /// Constructor + MetaMap() {} + MetaMap(const MetaMap& other); + + /// Destructor + virtual ~MetaMap() {} + + /// Return a copy of this map whose fields are shared with this map. + MetaMap::Ptr copyMeta() const; + /// Return a deep copy of this map that shares no data with this map. + MetaMap::Ptr deepCopyMeta() const; + + /// Assign to this map a deep copy of another map. + MetaMap& operator=(const MetaMap&); + + /// Read in all the Meta information the given stream. + void readMeta(std::istream&); + + /// Write out all the Meta information to the given stream. + void writeMeta(std::ostream&) const; + + /// Insert a new metadata or overwrite existing. If Metadata with given name + /// doesn't exist, a new Metadata field is added. If it does exist and given + /// metadata is of the same type, then overwrite existing with new value. If + /// it does exist and not of the same type, then throw an exception. + /// + /// @param name the name of the metadata. + /// @param metadata the actual metadata to store. + void insertMeta(const Name& name, const Metadata& metadata); + + /// Removes an existing metadata field from the grid. If the metadata with + /// the given name doesn't exist, do nothing. + /// + /// @param name the name of the metadata field to remove. + void removeMeta(const Name &name); + + //@{ + /// @return a pointer to the metadata with the given name, NULL if no such + /// field exists. + Metadata::Ptr operator[](const Name&); + Metadata::ConstPtr operator[](const Name&) const; + //@} + + //@{ + /// @return pointer to TypedMetadata, NULL if type and name mismatch. + template typename T::Ptr getMetadata(const Name &name); + template typename T::ConstPtr getMetadata(const Name &name) const; + //@} + + /// @return direct access to the underlying value stored by the given + /// metadata name. Here T is the type of the value stored. If there is a + /// mismatch, then throws an exception. + template T& metaValue(const Name &name); + template const T& metaValue(const Name &name) const; + + /// Functions for iterating over the Metadata. + MetaIterator beginMeta() { return mMeta.begin(); } + MetaIterator endMeta() { return mMeta.end(); } + ConstMetaIterator beginMeta() const { return mMeta.begin(); } + ConstMetaIterator endMeta() const { return mMeta.end(); } + + void clearMetadata() { mMeta.clear(); } + + size_t metaCount() const { return mMeta.size(); } + + bool empty() const { return mMeta.empty(); } + + /// @return string representation of MetaMap + std::string str() const; + +private: + /// @return a pointer to TypedMetadata with the given template parameter. + /// @throw LookupError if no field with the given name is found. + /// @throw TypeError if the given field is not of type T. + template + typename TypedMetadata::Ptr getValidTypedMetadata(const Name&) const; + + MetadataMap mMeta; +}; + +/// Write a MetaMap to an output stream +std::ostream& operator<<(std::ostream&, const MetaMap&); + + +//////////////////////////////////////// + + +inline Metadata::Ptr +MetaMap::operator[](const Name& name) +{ + MetaIterator iter = mMeta.find(name); + return (iter == mMeta.end() ? Metadata::Ptr() : iter->second); +} + +inline Metadata::ConstPtr +MetaMap::operator[](const Name &name) const +{ + ConstMetaIterator iter = mMeta.find(name); + return (iter == mMeta.end() ? Metadata::Ptr() : iter->second); +} + + +//////////////////////////////////////// + + +template +inline typename T::Ptr +MetaMap::getMetadata(const Name &name) +{ + ConstMetaIterator iter = mMeta.find(name); + if(iter == mMeta.end()) { + return typename T::Ptr(); + } + + // To ensure that we get valid conversion if the metadata pointers cross dso + // boundaries, we have to check the qualified typename and then do a static + // cast. This is slower than doing a dynamic_pointer_cast, but is safer when + // pointers cross dso boundaries. + if (iter->second->typeName() == T::staticTypeName()) { + return boost::static_pointer_cast(iter->second); + } // else + return typename T::Ptr(); +} + +template +inline typename T::ConstPtr +MetaMap::getMetadata(const Name &name) const +{ + ConstMetaIterator iter = mMeta.find(name); + if(iter == mMeta.end()) { + return typename T::ConstPtr(); + } + // To ensure that we get valid conversion if the metadata pointers cross dso + // boundaries, we have to check the qualified typename and then do a static + // cast. This is slower than doing a dynamic_pointer_cast, but is safer when + // pointers cross dso boundaries. + if (iter->second->typeName() == T::staticTypeName()) { + return boost::static_pointer_cast(iter->second); + } // else + return typename T::ConstPtr(); +} + + +//////////////////////////////////////// + + +template +inline typename TypedMetadata::Ptr +MetaMap::getValidTypedMetadata(const Name &name) const +{ + ConstMetaIterator iter = mMeta.find(name); + if (iter == mMeta.end()) OPENVDB_THROW(LookupError, "Cannot find metadata " << name); + + // To ensure that we get valid conversion if the metadata pointers cross dso + // boundaries, we have to check the qualified typename and then do a static + // cast. This is slower than doing a dynamic_pointer_cast, but is safer when + // pointers cross dso boundaries. + typename TypedMetadata::Ptr m; + if (iter->second->typeName() == TypedMetadata::staticTypeName()) { + m = boost::static_pointer_cast, Metadata>(iter->second); + } + if (!m) OPENVDB_THROW(TypeError, "Invalid type for metadata " << name); + return m; +} + + +//////////////////////////////////////// + + +template +inline T& +MetaMap::metaValue(const Name &name) +{ + typename TypedMetadata::Ptr m = getValidTypedMetadata(name); + return m->value(); +} + + +template +inline const T& +MetaMap::metaValue(const Name &name) const +{ + typename TypedMetadata::Ptr m = getValidTypedMetadata(name); + return m->value(); +} + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_METADATA_METAMAP_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/metadata/Metadata.cc b/openvdb_2_3_0_library/openvdb/metadata/Metadata.cc new file mode 100755 index 0000000..3901d44 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/metadata/Metadata.cc @@ -0,0 +1,168 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Metadata.h" + +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +typedef tbb::mutex Mutex; +typedef Mutex::scoped_lock Lock; + +typedef Metadata::Ptr (*createMetadata)(); +typedef std::map MetadataFactoryMap; +typedef MetadataFactoryMap::const_iterator MetadataFactoryMapCIter; + +struct LockedMetadataTypeRegistry { + LockedMetadataTypeRegistry() {} + ~LockedMetadataTypeRegistry() {} + Mutex mMutex; + MetadataFactoryMap mMap; +}; + +// Declare this at file scope to ensure thread-safe initialization +static Mutex theInitMetadataTypeRegistryMutex; + +// Global function for accessing the regsitry +static LockedMetadataTypeRegistry* +getMetadataTypeRegistry() +{ + Lock lock(theInitMetadataTypeRegistryMutex); + + static LockedMetadataTypeRegistry *registry = NULL; + + if(registry == NULL) { +#if defined(__ICC) +__pragma(warning(disable:1711)) // disable ICC "assignment to static variable" warnings +#endif + registry = new LockedMetadataTypeRegistry(); +#if defined(__ICC) +__pragma(warning(default:1711)) +#endif + } + + return registry; +} + +bool +Metadata::isRegisteredType(const Name &typeName) +{ + LockedMetadataTypeRegistry *registry = getMetadataTypeRegistry(); + Lock lock(registry->mMutex); + + return (registry->mMap.find(typeName) != registry->mMap.end()); +} + +void +Metadata::registerType(const Name &typeName, Metadata::Ptr (*createMetadata)()) +{ + LockedMetadataTypeRegistry *registry = getMetadataTypeRegistry(); + Lock lock(registry->mMutex); + + if (registry->mMap.find(typeName) != registry->mMap.end()) { + OPENVDB_THROW(KeyError, + "Cannot register " << typeName << ". Type is already registered"); + } + + registry->mMap[typeName] = createMetadata; +} + +void +Metadata::unregisterType(const Name &typeName) +{ + LockedMetadataTypeRegistry *registry = getMetadataTypeRegistry(); + Lock lock(registry->mMutex); + + registry->mMap.erase(typeName); +} + +Metadata::Ptr +Metadata::createMetadata(const Name &typeName) +{ + LockedMetadataTypeRegistry *registry = getMetadataTypeRegistry(); + Lock lock(registry->mMutex); + + MetadataFactoryMapCIter iter = registry->mMap.find(typeName); + + if (iter == registry->mMap.end()) { + OPENVDB_THROW(LookupError, + "Cannot create metadata for unregistered type " << typeName); + } + + return (iter->second)(); +} + +void +Metadata::clearRegistry() +{ + LockedMetadataTypeRegistry *registry = getMetadataTypeRegistry(); + Lock lock(registry->mMutex); + + registry->mMap.clear(); +} + + +//////////////////////////////////////// + + +void +UnknownMetadata::readValue(std::istream& is, Index32 numBytes) +{ + // Read and discard the metadata (without seeking, because + // the stream might not be seekable). + const size_t BUFFER_SIZE = 1024; + std::vector buffer(BUFFER_SIZE); + for (Index32 bytesRemaining = numBytes; bytesRemaining > 0; ) { + const Index32 bytesToSkip = std::min(bytesRemaining, BUFFER_SIZE); + is.read(&buffer[0], bytesToSkip); + bytesRemaining -= bytesToSkip; + } +} + + +void +UnknownMetadata::writeValue(std::ostream&) const +{ + OPENVDB_THROW(TypeError, "Metadata has unknown type"); +} + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/metadata/Metadata.h b/openvdb_2_3_0_library/openvdb/metadata/Metadata.h new file mode 100755 index 0000000..d579bb6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/metadata/Metadata.h @@ -0,0 +1,414 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_METADATA_METADATA_HAS_BEEN_INCLUDED +#define OPENVDB_METADATA_METADATA_HAS_BEEN_INCLUDED + +#include +#include +#include +#include // for math::isZero() +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +/// @brief Base class for storing metadata information in a grid. +class OPENVDB_API Metadata +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + /// Constructor + Metadata() {} + + /// Destructor + virtual ~Metadata() {} + + /// @return the type name of the metadata. + virtual Name typeName() const = 0; + + /// @return a copy of the metadata. + virtual Metadata::Ptr copy() const = 0; + + /// Copy value from the given metadata into the curent metadata + virtual void copy(const Metadata &other) = 0; + + /// @return string representation of Metadata + virtual std::string str() const = 0; + + /// Return the boolean representation of this metadata (empty strings + /// and zeroVals evaluate to false; most other values evaluate to true). + virtual bool asBool() const = 0; + + /// @return the size of the attribute in bytes. + virtual Index32 size() const = 0; + + /// Read the attribute from a stream. + void read(std::istream&); + /// Write the attribute to a stream. + void write(std::ostream&) const; + + /// Creates a new Metadata from the metadata type registry. + static Metadata::Ptr createMetadata(const Name &typeName); + + /// @return true if the given type is known by the metadata type registry. + static bool isRegisteredType(const Name &typeName); + + /// Clears out the metadata registry. + static void clearRegistry(); + +protected: + /// Read the size of the attribute from a stream. + static Index32 readSize(std::istream&); + /// Write the size of the attribute to a stream. + void writeSize(std::ostream&) const; + + /// Read the attribute from a stream. + virtual void readValue(std::istream&, Index32 numBytes) = 0; + /// Write the attribute to a stream. + virtual void writeValue(std::ostream&) const = 0; + + /// Register the given metadata type along with a factory function. + static void registerType(const Name& typeName, Metadata::Ptr (*createMetadata)()); + static void unregisterType(const Name& typeName); + +private: + // Disallow copying of instances of this class. + Metadata(const Metadata&); + Metadata& operator=(const Metadata&); +}; + + +/// @brief Subclass to read (and ignore) data of an unregistered type +class OPENVDB_API UnknownMetadata: public Metadata +{ +public: + UnknownMetadata() {} + virtual ~UnknownMetadata() {} + virtual Name typeName() const { return ""; } + virtual Metadata::Ptr copy() const { OPENVDB_THROW(TypeError, "Metadata has unknown type"); } + virtual void copy(const Metadata&) { OPENVDB_THROW(TypeError, "Destination has unknown type"); } + virtual std::string str() const { return ""; } + virtual bool asBool() const { return false; } + virtual Index32 size() const { return 0; } + +protected: + virtual void readValue(std::istream&s, Index32 numBytes); + virtual void writeValue(std::ostream&) const; +}; + + +/// @brief Templated metadata class to hold specific types. +template +class TypedMetadata: public Metadata +{ +public: + typedef boost::shared_ptr > Ptr; + typedef boost::shared_ptr > ConstPtr; + + // Constructors & destructors + TypedMetadata(); + TypedMetadata(const T &value); + TypedMetadata(const TypedMetadata &other); + virtual ~TypedMetadata(); + + /// @return the type name of the metadata. + virtual Name typeName() const; + + /// @return a copy of the metadata + virtual Metadata::Ptr copy() const; + + /// Copy value from the given metadata into the curent metadata + virtual void copy(const Metadata &other); + + /// @return string representation of value + virtual std::string str() const; + + /// Return the boolean representation of this metadata (empty strings + /// and zeroVals evaluate to false; most other values evaluate to true). + virtual bool asBool() const; + + /// @return the size of the attribute in bytes. + virtual Index32 size() const { return static_cast(sizeof(T)); } + + /// Set this metadata's value. + void setValue(const T&); + /// @return this metadata's value. + T& value(); + const T& value() const; + + /// Static specialized function for the type name. This function must be + /// template specialized for each type T. + static Name staticTypeName() { return typeNameAsString(); } + + /// Creates a new metadata of this type. + static Metadata::Ptr createMetadata(); + + /// Register the given metadata type and a function that knows how to create + /// the metadata type. This way the registry will know how to create certain + /// metadata types. + static void registerType(); + static void unregisterType(); + + static bool isRegisteredType(); + +protected: + /// Read the attribute from a stream. + virtual void readValue(std::istream&, Index32 numBytes); + /// Write the attribute to a stream. + virtual void writeValue(std::ostream&) const; + +private: + T mValue; +}; + +/// Write a Metadata to an output stream +std::ostream& operator<<(std::ostream& ostr, const Metadata& metadata); + + +//////////////////////////////////////// + + +inline void +Metadata::writeSize(std::ostream& os) const +{ + const Index32 n = this->size(); + os.write(reinterpret_cast(&n), sizeof(Index32)); +} + + +inline Index32 +Metadata::readSize(std::istream& is) +{ + Index32 n = 0; + is.read(reinterpret_cast(&n), sizeof(Index32)); + return n; +} + + +inline void +Metadata::read(std::istream& is) +{ + const Index32 numBytes = this->readSize(is); + this->readValue(is, numBytes); +} + + +inline void +Metadata::write(std::ostream& os) const +{ + this->writeSize(os); + this->writeValue(os); +} + + +//////////////////////////////////////// + + +template +inline +TypedMetadata::TypedMetadata() : mValue(T()) +{ +} + +template +inline +TypedMetadata::TypedMetadata(const T &value) : mValue(value) +{ +} + +template +inline +TypedMetadata::TypedMetadata(const TypedMetadata &other) : + Metadata(), + mValue(other.mValue) +{ +} + +template +inline +TypedMetadata::~TypedMetadata() +{ +} + +template +inline Name +TypedMetadata::typeName() const +{ + return TypedMetadata::staticTypeName(); +} + +template +inline void +TypedMetadata::setValue(const T& val) +{ + mValue = val; +} + +template +inline T& +TypedMetadata::value() +{ + return mValue; +} + +template +inline const T& +TypedMetadata::value() const +{ + return mValue; +} + +template +inline Metadata::Ptr +TypedMetadata::copy() const +{ + Metadata::Ptr metadata(new TypedMetadata()); + metadata->copy(*this); + return metadata; +} + +template +inline void +TypedMetadata::copy(const Metadata &other) +{ + const TypedMetadata* t = dynamic_cast*>(&other); + if (t == NULL) OPENVDB_THROW(TypeError, "Incompatible type during copy"); + mValue = t->mValue; +} + + +template +inline void +TypedMetadata::readValue(std::istream& is, Index32 /*numBytes*/) +{ + //assert(this->size() == numBytes); + is.read(reinterpret_cast(&mValue), this->size()); +} + +template +inline void +TypedMetadata::writeValue(std::ostream& os) const +{ + os.write(reinterpret_cast(&mValue), this->size()); +} + +template +inline std::string +TypedMetadata::str() const +{ + std::ostringstream ostr; + ostr << mValue; + return ostr.str(); +} + +template +inline bool +TypedMetadata::asBool() const +{ + return !math::isZero(mValue); +} + +template +inline Metadata::Ptr +TypedMetadata::createMetadata() +{ + Metadata::Ptr ret(new TypedMetadata()); + return ret; +} + +template +inline void +TypedMetadata::registerType() +{ + Metadata::registerType(TypedMetadata::staticTypeName(), + TypedMetadata::createMetadata); +} + +template +inline void +TypedMetadata::unregisterType() +{ + Metadata::unregisterType(TypedMetadata::staticTypeName()); +} + +template +inline bool +TypedMetadata::isRegisteredType() +{ + return Metadata::isRegisteredType(TypedMetadata::staticTypeName()); +} + + +template<> +inline std::string +TypedMetadata::str() const +{ + return (mValue ? "true" : "false"); +} + + +inline std::ostream& +operator<<(std::ostream& ostr, const Metadata& metadata) +{ + ostr << metadata.str(); + return ostr; +} + + +typedef TypedMetadata BoolMetadata; +typedef TypedMetadata DoubleMetadata; +typedef TypedMetadata FloatMetadata; +typedef TypedMetadata Int32Metadata; +typedef TypedMetadata Int64Metadata; +typedef TypedMetadata Vec2DMetadata; +typedef TypedMetadata Vec2IMetadata; +typedef TypedMetadata Vec2SMetadata; +typedef TypedMetadata Vec3DMetadata; +typedef TypedMetadata Vec3IMetadata; +typedef TypedMetadata Vec3SMetadata; +typedef TypedMetadata Mat4SMetadata; +typedef TypedMetadata Mat4DMetadata; + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_METADATA_METADATA_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/metadata/StringMetadata.h b/openvdb_2_3_0_library/openvdb/metadata/StringMetadata.h new file mode 100755 index 0000000..2b0a2bf --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/metadata/StringMetadata.h @@ -0,0 +1,75 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_METADATA_STRINGMETADATA_HAS_BEEN_INCLUDED +#define OPENVDB_METADATA_STRINGMETADATA_HAS_BEEN_INCLUDED + +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +typedef TypedMetadata StringMetadata; + + +template <> +inline Index32 +StringMetadata::size() const +{ + return mValue.size(); +} + + +template<> +inline void +StringMetadata::readValue(std::istream& is, Index32 size) +{ + mValue.resize(size, '\0'); + is.read(&mValue[0], size); +} + +template<> +inline void +StringMetadata::writeValue(std::ostream &os) const +{ + os.write(reinterpret_cast(&mValue[0]), this->size()); +} + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_METADATA_STRINGMETADATA_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/openvdb.cc b/openvdb_2_3_0_library/openvdb/openvdb.cc new file mode 100755 index 0000000..6a42756 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/openvdb.cc @@ -0,0 +1,134 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "openvdb.h" +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +typedef tbb::mutex Mutex; +typedef Mutex::scoped_lock Lock; + +// Declare this at file scope to ensure thread-safe initialization. +Mutex sInitMutex; +bool sIsInitialized = false; + +void +initialize() +{ + Lock lock(sInitMutex); + if (sIsInitialized) return; + + // Register metadata. + Metadata::clearRegistry(); + BoolMetadata::registerType(); + DoubleMetadata::registerType(); + FloatMetadata::registerType(); + Int32Metadata::registerType(); + Int64Metadata::registerType(); + StringMetadata::registerType(); + Vec2IMetadata::registerType(); + Vec2SMetadata::registerType(); + Vec2DMetadata::registerType(); + Vec3IMetadata::registerType(); + Vec3SMetadata::registerType(); + Vec3DMetadata::registerType(); + Mat4SMetadata::registerType(); + Mat4DMetadata::registerType(); + + // Register maps + math::MapRegistry::clear(); + math::AffineMap::registerMap(); + math::UnitaryMap::registerMap(); + math::ScaleMap::registerMap(); + math::UniformScaleMap::registerMap(); + math::TranslationMap::registerMap(); + math::ScaleTranslateMap::registerMap(); + math::UniformScaleTranslateMap::registerMap(); + math::NonlinearFrustumMap::registerMap(); + + // Register common grid types. + GridBase::clearRegistry(); + BoolGrid::registerGrid(); + FloatGrid::registerGrid(); + DoubleGrid::registerGrid(); + Int32Grid::registerGrid(); + Int64Grid::registerGrid(); + HermiteGrid::registerGrid(); + StringGrid::registerGrid(); + Vec3IGrid::registerGrid(); + Vec3SGrid::registerGrid(); + Vec3DGrid::registerGrid(); + +#ifdef __ICC +// Disable ICC "assignment to statically allocated variable" warning. +// This assignment is mutex-protected and therefore thread-safe. +__pragma(warning(disable:1711)) +#endif + + sIsInitialized = true; + +#ifdef __ICC +__pragma(warning(default:1711)) +#endif +} + + +void +uninitialize() +{ + Lock lock(sInitMutex); + +#ifdef __ICC +// Disable ICC "assignment to statically allocated variable" warning. +// This assignment is mutex-protected and therefore thread-safe. +__pragma(warning(disable:1711)) +#endif + + sIsInitialized = false; + +#ifdef __ICC +__pragma(warning(default:1711)) +#endif + + Metadata::clearRegistry(); + GridBase::clearRegistry(); + math::MapRegistry::clear(); +} + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/openvdb.h b/openvdb_2_3_0_library/openvdb/openvdb.h new file mode 100755 index 0000000..d5357c6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/openvdb.h @@ -0,0 +1,98 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_INIT_HAS_BEEN_INCLUDED +#define OPENVDB_INIT_HAS_BEEN_INCLUDED + +#include "Platform.h" +#include "Types.h" +#include "Metadata.h" +#include "math/Maps.h" +#include "math/Transform.h" +#include "Grid.h" +#include "tree/Tree.h" +#include "io/File.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +/// Common tree types +typedef tree::Tree4::Type BoolTree; +typedef tree::Tree4::Type FloatTree; +typedef tree::Tree4::Type DoubleTree; +typedef tree::Tree4::Type Int32Tree; +typedef tree::Tree4::Type UInt32Tree; +typedef tree::Tree4::Type Int64Tree; +typedef tree::Tree4::Type HermiteTree; +typedef tree::Tree4::Type Vec2ITree; +typedef tree::Tree4::Type Vec2STree; +typedef tree::Tree4::Type Vec2DTree; +typedef tree::Tree4::Type Vec3ITree; +typedef tree::Tree4::Type Vec3STree; +typedef tree::Tree4::Type Vec3DTree; +typedef tree::Tree4::Type StringTree; +typedef Vec3STree Vec3fTree; +typedef Vec3DTree Vec3dTree; +typedef FloatTree ScalarTree; +typedef Vec3fTree VectorTree; + +/// Common grid types +typedef Grid BoolGrid; +typedef Grid FloatGrid; +typedef Grid DoubleGrid; +typedef Grid Int32Grid; +typedef Grid Int64Grid; +typedef Grid HermiteGrid; +typedef Grid Vec3IGrid; +typedef Grid Vec3SGrid; +typedef Grid Vec3DGrid; +typedef Grid StringGrid; +typedef Vec3SGrid Vec3fGrid; +typedef Vec3DGrid Vec3dGrid; +typedef FloatGrid ScalarGrid; +typedef Vec3fGrid VectorGrid; + + +/// Global registration of basic types +OPENVDB_API void initialize(); + +/// Global deregistration of basic types +OPENVDB_API void uninitialize(); + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_INIT_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/python/pyAccessor.h b/openvdb_2_3_0_library/openvdb/python/pyAccessor.h new file mode 100755 index 0000000..aa4b8b3 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/python/pyAccessor.h @@ -0,0 +1,343 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_PYACCESSOR_HAS_BEEN_INCLUDED +#define OPENVDB_PYACCESSOR_HAS_BEEN_INCLUDED + +#include +#include "openvdb/openvdb.h" +#include "pyutil.h" + +namespace pyAccessor { + +namespace py = boost::python; +using namespace openvdb::OPENVDB_VERSION_NAME; + + +//@{ +/// Type traits for grid accessors +template +struct AccessorTraits +{ + typedef _GridT GridT; + typedef GridT NonConstGridT; + typedef typename NonConstGridT::Ptr GridPtrT; + typedef typename NonConstGridT::Accessor AccessorT; + typedef typename AccessorT::ValueType ValueT; + + static const bool IsConst = false; + + static const char* typeName() { return "Accessor"; } + + static void setActiveState(AccessorT& acc, const Coord& ijk, bool on) { + acc.setActiveState(ijk, on); + } + static void setValueOnly(AccessorT& acc, const Coord& ijk, const ValueT& val) { + acc.setValueOnly(ijk, val); + } + static void setValueOn(AccessorT& acc, const Coord& ijk) { acc.setValueOn(ijk); } + static void setValueOn(AccessorT& acc, const Coord& ijk, const ValueT& val) { + acc.setValueOn(ijk, val); + } + static void setValueOff(AccessorT& acc, const Coord& ijk) { acc.setValueOff(ijk); } + static void setValueOff(AccessorT& acc, const Coord& ijk, const ValueT& val) { + acc.setValueOff(ijk, val); + } +}; + +// Partial specialization for const accessors +template +struct AccessorTraits +{ + typedef const _GridT GridT; + typedef _GridT NonConstGridT; + typedef typename NonConstGridT::ConstPtr GridPtrT; + typedef typename NonConstGridT::ConstAccessor AccessorT; + typedef typename AccessorT::ValueType ValueT; + + static const bool IsConst = true; + + static const char* typeName() { return "ConstAccessor"; } + + static void setActiveState(AccessorT&, const Coord&, bool) { notWritable(); } + static void setValueOnly(AccessorT&, const Coord&, const ValueT&) { notWritable(); } + static void setValueOn(AccessorT&, const Coord&) { notWritable(); } + static void setValueOn(AccessorT&, const Coord&, const ValueT&) { notWritable(); } + static void setValueOff(AccessorT&, const Coord&) { notWritable(); } + static void setValueOff(AccessorT&, const Coord&, const ValueT&) { notWritable(); } + + static void notWritable() + { + PyErr_SetString(PyExc_TypeError, "accessor is read-only"); + py::throw_error_already_set(); + } +}; +//@} + + +//////////////////////////////////////// + + +/// Variant of pyutil::extractArg() that extracts a Coord from a py::object +/// argument to a given ValueAccessor method +template +inline Coord +extractCoordArg(py::object obj, const char* functionName, int argIdx = 0) +{ + return pyutil::extractArg(obj, functionName, + AccessorTraits::typeName(), argIdx, "tuple(int, int, int)"); +} + + +/// Variant of pyutil::extractArg() that extracts a value of type +/// ValueAccessor::ValueType from an argument to a ValueAccessor method +template +inline typename GridT::ValueType +extractValueArg( + py::object obj, + const char* functionName, + int argIdx = 0, // args are numbered starting from 1 + const char* expectedType = NULL) +{ + return pyutil::extractArg( + obj, functionName, AccessorTraits::typeName(), argIdx, expectedType); +} + + +//////////////////////////////////////// + + +/// @brief ValueAccessor wrapper class that also stores a grid pointer, +/// so that the grid doesn't get deleted as long as the accessor is live +/// +/// @internal This class could have just been made to inherit from ValueAccessor, +/// but the method wrappers allow for more Pythonic error messages. For example, +/// if we constructed the Python getValue() method directly from the corresponding +/// ValueAccessor method, as follows, +/// +/// .def("getValue", &Accessor::getValue, ...) +/// +/// then the conversion from a Python type to a Coord& would be done +/// automatically. But if the Python method were called with an object of +/// a type that is not convertible to a Coord, then the TypeError message +/// would say something like "TypeError: No registered converter was able to +/// produce a C++ rvalue of type openvdb::math::Coord...". +/// Handling the type conversion manually is more work, but it allows us to +/// instead generate messages like "TypeError: expected tuple(int, int, int), +/// found str as argument to FloatGridAccessor.getValue()". +template +class AccessorWrap +{ +public: + typedef AccessorTraits<_GridType> Traits; + typedef typename Traits::AccessorT Accessor; + typedef typename Traits::ValueT ValueType; + typedef typename Traits::NonConstGridT GridType; + typedef typename Traits::GridPtrT GridPtrType; + + AccessorWrap(GridPtrType grid): mGrid(grid), mAccessor(grid->getAccessor()) {} + + AccessorWrap copy() const { return *this; } + + void clear() { mAccessor.clear(); } + + GridPtrType parent() const { return mGrid; } + + ValueType getValue(py::object coordObj) + { + const Coord ijk = extractCoordArg(coordObj, "getValue"); + return mAccessor.getValue(ijk); + } + + int getValueDepth(py::object coordObj) + { + const Coord ijk = extractCoordArg(coordObj, "getValueDepth"); + return mAccessor.getValueDepth(ijk); + } + + int isVoxel(py::object coordObj) + { + const Coord ijk = extractCoordArg(coordObj, "isVoxel"); + return mAccessor.isVoxel(ijk); + } + + py::tuple probeValue(py::object coordObj) + { + const Coord ijk = extractCoordArg(coordObj, "probeValue"); + ValueType value; + bool on = mAccessor.probeValue(ijk, value); + return py::make_tuple(value, on); + } + + bool isValueOn(py::object coordObj) + { + const Coord ijk = extractCoordArg(coordObj, "isValueOn"); + return mAccessor.isValueOn(ijk); + } + + void setActiveState(py::object coordObj, bool on) + { + const Coord ijk = extractCoordArg(coordObj, "setActiveState", /*argIdx=*/1); + Traits::setActiveState(mAccessor, ijk, on); + } + + void setValueOnly(py::object coordObj, py::object valObj) + { + Coord ijk = extractCoordArg(coordObj, "setValueOnly", 1); + ValueType val = extractValueArg(valObj, "setValueOnly", 2); + Traits::setValueOnly(mAccessor, ijk, val); + } + + void setValueOn(py::object coordObj, py::object valObj) + { + Coord ijk = extractCoordArg(coordObj, "setValueOn", 1); + if (valObj.is_none()) { + Traits::setValueOn(mAccessor, ijk); + } else { + ValueType val = extractValueArg(valObj, "setValueOn", 2); + Traits::setValueOn(mAccessor, ijk, val); + } + } + + void setValueOff(py::object coordObj, py::object valObj) + { + Coord ijk = extractCoordArg(coordObj, "setValueOff", 1); + if (valObj.is_none()) { + Traits::setValueOff(mAccessor, ijk); + } else { + ValueType val = extractValueArg(valObj, "setValueOff", 2); + Traits::setValueOff(mAccessor, ijk, val); + } + } + + int isCached(py::object coordObj) + { + const Coord ijk = extractCoordArg(coordObj, "isCached"); + return mAccessor.isCached(ijk); + } + + /// @brief Define a Python wrapper class for this C++ class. + static void wrap() + { + const std::string + pyGridTypeName = pyutil::GridTraits::name(), + pyValueTypeName = openvdb::typeNameAsString(), + pyAccessorTypeName = Traits::typeName(); + + py::class_ clss( + pyAccessorTypeName.c_str(), + (std::string(Traits::IsConst ? "Read-only" : "Read/write") + + " access by (i, j, k) index coordinates to the voxels\nof a " + + pyGridTypeName).c_str(), + py::no_init); + + clss.def("copy", &AccessorWrap::copy, + ("copy() -> " + pyAccessorTypeName + "\n\n" + "Return a copy of this accessor.").c_str()) + + .def("clear", &AccessorWrap::clear, + "clear()\n\n" + "Clear this accessor of all cached data.") + + .add_property("parent", &AccessorWrap::parent, + ("this accessor's parent " + pyGridTypeName).c_str()) + + // + // Voxel access + // + .def("getValue", &AccessorWrap::getValue, + py::arg("ijk"), + ("getValue(ijk) -> " + pyValueTypeName + "\n\n" + "Return the value of the voxel at coordinates (i, j, k).").c_str()) + + .def("getValueDepth", &AccessorWrap::getValueDepth, + py::arg("ijk"), + "getValueDepth(ijk) -> int\n\n" + "Return the tree depth (0 = root) at which the value of voxel\n" + "(i, j, k) resides. If (i, j, k) isn't explicitly represented in\n" + "the tree (i.e., it is implicitly a background voxel), return -1.") + + .def("isVoxel", &AccessorWrap::isVoxel, + py::arg("ijk"), + "isVoxel(ijk) -> bool\n\n" + "Return True if voxel (i, j, k) resides at the leaf level of the tree.") + + .def("probeValue", &AccessorWrap::probeValue, + py::arg("ijk"), + "probeValue(ijk) -> value, bool\n\n" + "Return the value of the voxel at coordinates (i, j, k)\n" + "together with the voxel's active state.") + + .def("isValueOn", &AccessorWrap::isValueOn, + py::arg("ijk"), + "isValueOn(ijk) -> bool\n\n" + "Return the active state of the voxel at coordinates (i, j, k).") + .def("setActiveState", &AccessorWrap::setActiveState, + (py::arg("ijk"), py::arg("on")), + "setActiveState(ijk, on)\n\n" + "Mark voxel (i, j, k) as either active or inactive (True or False),\n" + "but don't change its value.") + + .def("setValueOnly", &AccessorWrap::setValueOnly, + (py::arg("ijk"), py::arg("value")), + "setValueOnly(ijk, value)\n\n" + "Set the value of voxel (i, j, k), but don't change its active state.") + + .def("setValueOn", &AccessorWrap::setValueOn, + (py::arg("ijk"), py::arg("value") = py::object()), + "setValueOn(ijk, value=None)\n\n" + "Mark voxel (i, j, k) as active and, if the given value\n" + "is not None, set the voxel's value.\n") + .def("setValueOff", &AccessorWrap::setValueOff, + (py::arg("ijk"), py::arg("value") = py::object()), + "setValueOff(ijk, value=None)\n\n" + "Mark voxel (i, j, k) as inactive and, if the given value\n" + "is not None, set the voxel's value.") + + .def("isCached", &AccessorWrap::isCached, + py::arg("ijk"), + "isCached(ijk) -> bool\n\n" + "Return True if this accessor has cached the path to voxel (i, j, k).") + + ; // py::class_ + } + +private: + const GridPtrType mGrid; + Accessor mAccessor; +}; // class AccessorWrap + +} // namespace pyAccessor + +#endif // OPENVDB_PYACCESSOR_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/python/pyFloatGrid.cc b/openvdb_2_3_0_library/openvdb/python/pyFloatGrid.cc new file mode 100755 index 0000000..dac2400 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/python/pyFloatGrid.cc @@ -0,0 +1,65 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file pyFloatGrid.cc +/// @author Peter Cucka +/// @brief Boost.Python wrappers for scalar, floating-point openvdb::Grid types + +#include "pyGrid.h" + + +/// Create a Python wrapper for each supported Grid type. +void +exportFloatGrid() +{ + // Add a module-level list that gives the types of all supported Grid classes. + py::scope().attr("GridTypes") = py::list(); + + // Specify that py::numeric::array should refer to the Python type numpy.ndarray + // (rather than the older Numeric.array). + py::numeric::array::set_module_and_type("numpy", "ndarray"); + + pyGrid::exportGrid(); +#ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES + pyGrid::exportGrid(); +#endif + + py::def("createLevelSetSphere", + &pyGrid::createLevelSetSphere, + (py::arg("radius"), py::arg("center")=openvdb::Coord(), py::arg("voxelSize")=1.0, + py::arg("halfWidth")=openvdb::LEVEL_SET_HALF_WIDTH), + "createLevelSetSphere(radius, center, voxelSize, halfWidth) -> FloatGrid\n\n" + "Return a grid containing a narrow-band level set representation\n" + "of a sphere."); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/python/pyGrid.h b/openvdb_2_3_0_library/openvdb/python/pyGrid.h new file mode 100755 index 0000000..bb19557 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/python/pyGrid.h @@ -0,0 +1,1934 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file pyGrid.h +/// @author Peter Cucka +/// @brief Boost.Python wrapper for openvdb::Grid + +#ifndef OPENVDB_PYGRID_HAS_BEEN_INCLUDED +#define OPENVDB_PYGRID_HAS_BEEN_INCLUDED + +#include +#include +#ifdef PY_OPENVDB_USE_NUMPY +#include // for PyArray_DATA() +#endif +#include "openvdb/openvdb.h" +#include "openvdb/io/Stream.h" +#include "openvdb/tools/LevelSetSphere.h" +#include "openvdb/tools/Dense.h" +#include "pyutil.h" +#include "pyAccessor.h" // for pyAccessor::AccessorWrap +#include "pyopenvdb.h" +#include + +namespace py = boost::python; +using namespace openvdb::OPENVDB_VERSION_NAME; + + +namespace pyopenvdb { + +inline py::object +getPyObjectFromGrid(const GridBase::Ptr& grid) +{ + if (!grid) return py::object(); + +#define CONVERT_BASE_TO_GRID(GridType, grid) \ + if (grid->isType()) { \ + return py::object(gridPtrCast(grid)); \ + } + + CONVERT_BASE_TO_GRID(FloatGrid, grid); + CONVERT_BASE_TO_GRID(Vec3SGrid, grid); + CONVERT_BASE_TO_GRID(BoolGrid, grid); +#ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES + CONVERT_BASE_TO_GRID(DoubleGrid, grid); + CONVERT_BASE_TO_GRID(Int32Grid, grid); + CONVERT_BASE_TO_GRID(Int64Grid, grid); + CONVERT_BASE_TO_GRID(Vec3IGrid, grid); + CONVERT_BASE_TO_GRID(Vec3DGrid, grid); +#endif +#undef CONVERT_BASE_TO_GRID + + OPENVDB_THROW(TypeError, grid->type() + " is not a supported OpenVDB grid type"); +} + + +inline openvdb::GridBase::Ptr +getGridFromPyObject(const boost::python::object& gridObj) +{ + if (!gridObj) return GridBase::Ptr(); + +#define CONVERT_GRID_TO_BASE(GridPtrType) \ + { \ + py::extract x(gridObj); \ + if (x.check()) return x(); \ + } + + // Extract a grid pointer of one of the supported types + // from the input object, then cast it to a base pointer. + CONVERT_GRID_TO_BASE(FloatGrid::Ptr); + CONVERT_GRID_TO_BASE(Vec3SGrid::Ptr); + CONVERT_GRID_TO_BASE(BoolGrid::Ptr); +#ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES + CONVERT_GRID_TO_BASE(DoubleGrid::Ptr); + CONVERT_GRID_TO_BASE(Int32Grid::Ptr); + CONVERT_GRID_TO_BASE(Int64Grid::Ptr); + CONVERT_GRID_TO_BASE(Vec3IGrid::Ptr); + CONVERT_GRID_TO_BASE(Vec3DGrid::Ptr); +#endif +#undef CONVERT_GRID_TO_BASE + + OPENVDB_THROW(TypeError, + pyutil::className(gridObj) + " is not a supported OpenVDB grid type"); +} + + +inline openvdb::GridBase::Ptr +getGridFromPyObject(PyObject* gridObj) +{ + return getGridFromPyObject(pyutil::pyBorrow(gridObj)); +} + +} // namespace pyopenvdb + + +//////////////////////////////////////// + + +namespace pyGrid { + +inline py::object +getGridFromGridBase(GridBase::Ptr grid) +{ + py::object obj; + try { + obj = pyopenvdb::getPyObjectFromGrid(grid); + } catch (openvdb::TypeError& e) { + PyErr_SetString(PyExc_TypeError, e.what()); + py::throw_error_already_set(); + return py::object(); + } + return obj; +} + + +/// GridBase is not exposed in Python because it isn't really needed +/// (and because exposing it would be complicated, requiring wrapping +/// pure virtual functions like GridBase::baseTree()), but there are +/// a few cases where, internally, we need to extract a GridBase::Ptr +/// from a py::object. Hence this converter. +inline GridBase::Ptr +getGridBaseFromGrid(py::object gridObj) +{ + GridBase::Ptr grid; + try { + grid = pyopenvdb::getGridFromPyObject(gridObj); + } catch (openvdb::TypeError& e) { + PyErr_SetString(PyExc_TypeError, e.what()); + py::throw_error_already_set(); + return GridBase::Ptr(); + } + return grid; +} + + +//////////////////////////////////////// + + +/// Variant of pyutil::extractArg() that uses the class name of a given grid type +template +inline T +extractValueArg( + py::object obj, + const char* functionName, + int argIdx = 0, // args are numbered starting from 1 + const char* expectedType = NULL) +{ + return pyutil::extractArg(obj, + functionName, pyutil::GridTraits::name(), argIdx, expectedType); +} + + +/// @brief Variant of pyutil::extractArg() that uses the class name +/// and @c ValueType of a given grid type +template +inline typename GridType::ValueType +extractValueArg( + py::object obj, + const char* functionName, + int argIdx = 0, // args are numbered starting from 1 + const char* expectedType = NULL) +{ + return extractValueArg( + obj, functionName, argIdx, expectedType); +} + + +//////////////////////////////////////// + + +template +inline typename GridType::Ptr +copyGrid(const GridType& grid) +{ + return grid.copy(); +} + + +template +inline bool +sharesWith(const GridType& grid, py::object other) +{ + py::extract x(other); + if (x.check()) { + typename GridType::ConstPtr otherGrid = x(); + return (&otherGrid->tree() == &grid.tree()); + } + return false; +} + + +//////////////////////////////////////// + + +template +inline std::string +getValueType() +{ + return pyutil::GridTraits::valueTypeName(); +} + + +template +inline typename GridType::ValueType +getZeroValue() +{ + return openvdb::zeroVal(); +} + + +template +inline typename GridType::ValueType +getOneValue() +{ + typedef typename GridType::ValueType ValueT; + return ValueT(openvdb::zeroVal() + 1); +} + + +template +inline bool +notEmpty(const GridType& grid) +{ + return !grid.empty(); +} + + +template +inline typename GridType::ValueType +getGridBackground(const GridType& grid) +{ + return grid.background(); +} + + +template +inline void +setGridBackground(GridType& grid, py::object obj) +{ + grid.setBackground(extractValueArg(obj, "setBackground")); +} + + +inline void +setGridName(GridBase::Ptr grid, py::object strObj) +{ + if (grid) { + if (!strObj) { // if name is None + grid->removeMeta(GridBase::META_GRID_NAME); + } else { + const std::string name = pyutil::extractArg( + strObj, "setName", /*className=*/NULL, /*argIdx=*/1, "str"); + grid->setName(name); + } + } +} + + +inline void +setGridCreator(GridBase::Ptr grid, py::object strObj) +{ + if (grid) { + if (!strObj) { // if name is None + grid->removeMeta(GridBase::META_GRID_CREATOR); + } else { + const std::string name = pyutil::extractArg( + strObj, "setCreator", /*className=*/NULL, /*argIdx=*/1, "str"); + grid->setCreator(name); + } + } +} + + +inline std::string +getGridClass(GridBase::ConstPtr grid) +{ + return GridBase::gridClassToString(grid->getGridClass()); +} + + +inline void +setGridClass(GridBase::Ptr grid, py::object strObj) +{ + if (!strObj) { + grid->clearGridClass(); + } else { + const std::string name = pyutil::extractArg( + strObj, "setGridClass", /*className=*/NULL, /*argIdx=*/1, "str"); + grid->setGridClass(GridBase::stringToGridClass(name)); + } +} + + +inline std::string +getVecType(GridBase::ConstPtr grid) +{ + return GridBase::vecTypeToString(grid->getVectorType()); +} + + +inline void +setVecType(GridBase::Ptr grid, py::object strObj) +{ + if (!strObj) { + grid->clearVectorType(); + } else { + const std::string name = pyutil::extractArg( + strObj, "setVectorType", /*className=*/NULL, /*argIdx=*/1, "str"); + grid->setVectorType(GridBase::stringToVecType(name)); + } +} + + +inline std::string +gridInfo(GridBase::ConstPtr grid, int verbosity) +{ + std::ostringstream ostr; + grid->print(ostr, std::max(1, verbosity)); + return ostr.str(); +} + + +//////////////////////////////////////// + + +inline void +setGridTransform(GridBase::Ptr grid, py::object xformObj) +{ + if (grid) { + if (math::Transform::Ptr xform = pyutil::extractArg( + xformObj, "setTransform", /*className=*/NULL, /*argIdx=*/1, "Transform")) + { + grid->setTransform(xform); + } else { + PyErr_SetString(PyExc_ValueError, "null transform"); + py::throw_error_already_set(); + } + } +} + + +//////////////////////////////////////// + + +// Helper class to construct a pyAccessor::AccessorWrap for a given grid, +// permitting partial specialization for const vs. non-const grids +template +struct AccessorHelper +{ + typedef typename pyAccessor::AccessorWrap Wrapper; + static Wrapper wrap(typename GridType::Ptr grid) + { + if (!grid) { + PyErr_SetString(PyExc_ValueError, "null grid"); + py::throw_error_already_set(); + } + return Wrapper(grid); + } +}; + +// Specialization for const grids +template +struct AccessorHelper +{ + typedef typename pyAccessor::AccessorWrap Wrapper; + static Wrapper wrap(typename GridType::ConstPtr grid) + { + if (!grid) { + PyErr_SetString(PyExc_ValueError, "null grid"); + py::throw_error_already_set(); + } + return Wrapper(grid); + } +}; + + +/// Return a non-const accessor (wrapped in a pyAccessor::AccessorWrap) for the given grid. +template +inline typename AccessorHelper::Wrapper +getAccessor(typename GridType::Ptr grid) +{ + return AccessorHelper::wrap(grid); +} + +/// @brief Return a const accessor (wrapped in a pyAccessor::AccessorWrap) for the given grid. +/// @internal Note that the grid pointer is non-const, even though the grid is +/// treated as const. This is because we don't expose a const grid type in Python. +template +inline typename AccessorHelper::Wrapper +getConstAccessor(typename GridType::Ptr grid) +{ + return AccessorHelper::wrap(grid); +} + + +//////////////////////////////////////// + + +template +inline py::tuple +evalLeafBoundingBox(const GridType& grid) +{ + CoordBBox bbox; + grid.tree().evalLeafBoundingBox(bbox); + return py::make_tuple(bbox.min(), bbox.max()); +} + + +template +inline Coord +evalLeafDim(const GridType& grid) +{ + Coord dim; + grid.tree().evalLeafDim(dim); + return dim; +} + + +template +inline py::tuple +evalActiveVoxelBoundingBox(const GridType& grid) +{ + CoordBBox bbox = grid.evalActiveVoxelBoundingBox(); + return py::make_tuple(bbox.min(), bbox.max()); +} + + +template +inline py::tuple +getNodeLog2Dims(const GridType& grid) +{ + std::vector dims; + grid.tree().getNodeLog2Dims(dims); + py::list lst; + for (size_t i = 0, N = dims.size(); i < N; ++i) { + lst.append(dims[i]); + } + return py::tuple(lst); +} + + +template +inline Index +treeDepth(const GridType& grid) +{ + return grid.tree().treeDepth(); +} + + +template +inline Index32 +leafCount(const GridType& grid) +{ + return grid.tree().leafCount(); +} + + +template +inline Index32 +nonLeafCount(const GridType& grid) +{ + return grid.tree().nonLeafCount(); +} + + +template +inline Index64 +activeLeafVoxelCount(const GridType& grid) +{ + return grid.tree().activeLeafVoxelCount(); +} + + +template +inline py::tuple +evalMinMax(const GridType& grid) +{ + typename GridType::ValueType vmin, vmax; + grid.tree().evalMinMax(vmin, vmax); + return py::make_tuple(vmin, vmax); +} + + +template +inline py::tuple +getIndexRange(const GridType& grid) +{ + CoordBBox bbox; + grid.tree().getIndexRange(bbox); + return py::make_tuple(bbox.min(), bbox.max()); +} + + +//template +//inline void +//expandIndexRange(GridType& grid, py::object coordObj) +//{ +// Coord xyz = extractValueArg( +// coordObj, "expand", 0, "tuple(int, int, int)"); +// grid.tree().expand(xyz); +//} + + +//////////////////////////////////////// + + +inline py::dict +getAllMetadata(GridBase::ConstPtr grid) +{ + if (grid) return py::dict(static_cast(*grid)); + return py::dict(); +} + + +inline void +replaceAllMetadata(GridBase::Ptr grid, const MetaMap& metadata) +{ + if (grid) { + grid->clearMetadata(); + for (MetaMap::ConstMetaIterator it = metadata.beginMeta(); + it != metadata.endMeta(); ++it) + { + if (it->second) grid->insertMeta(it->first, *it->second); + } + } +} + + +inline void +updateMetadata(GridBase::Ptr grid, const MetaMap& metadata) +{ + if (grid) { + for (MetaMap::ConstMetaIterator it = metadata.beginMeta(); + it != metadata.endMeta(); ++it) + { + if (it->second) { + grid->removeMeta(it->first); + grid->insertMeta(it->first, *it->second); + } + } + } +} + + +inline py::dict +getStatsMetadata(GridBase::ConstPtr grid) +{ + MetaMap::ConstPtr metadata; + if (grid) metadata = grid->getStatsMetadata(); + if (metadata) return py::dict(*metadata); + return py::dict(); +} + + +inline py::object +getMetadataKeys(GridBase::ConstPtr grid) +{ + if (grid) return py::dict(static_cast(*grid)).iterkeys(); + return py::object(); +} + + +inline py::object +getMetadata(GridBase::ConstPtr grid, py::object nameObj) +{ + if (!grid) return py::object(); + + const std::string name = pyutil::extractArg( + nameObj, "__getitem__", NULL, /*argIdx=*/1, "str"); + + Metadata::ConstPtr metadata = (*grid)[name]; + if (!metadata) { + PyErr_SetString(PyExc_KeyError, name.c_str()); + py::throw_error_already_set(); + } + + // Use the MetaMap-to-dict converter (see pyOpenVDBModule.cc) to convert + // the Metadata value to a Python object of the appropriate type. + /// @todo Would be more efficient to convert the Metadata object + /// directly to a Python object. + MetaMap metamap; + metamap.insertMeta(name, *metadata); + return py::dict(metamap)[name]; +} + + +inline void +setMetadata(GridBase::Ptr grid, py::object nameObj, py::object valueObj) +{ + if (!grid) return; + + const std::string name = pyutil::extractArg( + nameObj, "__setitem__", NULL, /*argIdx=*/1, "str"); + + // Insert the Python object into a Python dict, then use the dict-to-MetaMap + // converter (see pyOpenVDBModule.cc) to convert the dict to a MetaMap + // containing a Metadata object of the appropriate type. + /// @todo Would be more efficient to convert the Python object + /// directly to a Metadata object. + py::dict dictObj; + dictObj[name] = valueObj; + MetaMap metamap = py::extract(dictObj); + + if (Metadata::Ptr metadata = metamap[name]) { + grid->removeMeta(name); + grid->insertMeta(name, *metadata); + } +} + + +inline void +removeMetadata(GridBase::Ptr grid, const std::string& name) +{ + if (grid) { + Metadata::Ptr metadata = (*grid)[name]; + if (!metadata) { + PyErr_SetString(PyExc_KeyError, name.c_str()); + py::throw_error_already_set(); + } + grid->removeMeta(name); + } +} + + +inline bool +hasMetadata(GridBase::ConstPtr grid, const std::string& name) +{ + if (grid) return ((*grid)[name].get() != NULL); + return false; +} + + +//////////////////////////////////////// + + +template +inline void +prune(GridType& grid, py::object tolerance) +{ + grid.tree().prune(extractValueArg(tolerance, "prune")); +} + + +template +inline void +pruneInactive(GridType& grid, py::object valObj) +{ + if (valObj.is_none()) { + grid.tree().pruneInactive(); + } else { + grid.tree().pruneInactive(extractValueArg(valObj, "pruneInactive")); + } +} + + +template +inline void +fill(GridType& grid, py::object minObj, py::object maxObj, + py::object valObj, bool active) +{ + const Coord + bmin = extractValueArg(minObj, "fill", 1, "tuple(int, int, int)"), + bmax = extractValueArg(maxObj, "fill", 2, "tuple(int, int, int)"); + grid.fill(CoordBBox(bmin, bmax), extractValueArg(valObj, "fill", 3), active); +} + + +template +inline void +signedFloodFill(GridType& grid) +{ + grid.signedFloodFill(); +} + + +//////////////////////////////////////// + + +#ifndef PY_OPENVDB_USE_NUMPY + +template +inline void +copyFromArray(GridType&, const py::object&, py::object, py::object) +{ + PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); + boost::python::throw_error_already_set(); +} + +template +inline void +copyToArray(GridType&, const py::object&, py::object) +{ + PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); + boost::python::throw_error_already_set(); +} + +#else // if defined(PY_OPENVDB_USE_NUMPY) + +template struct NumPyToCpp {}; +//template<> struct NumPyToCpp { typedef half type; }; +template<> struct NumPyToCpp { typedef float type; }; +template<> struct NumPyToCpp { typedef double type; }; +template<> struct NumPyToCpp { typedef bool type; }; +template<> struct NumPyToCpp { typedef Int16 type; }; +template<> struct NumPyToCpp { typedef Int32 type; }; +template<> struct NumPyToCpp { typedef Int64 type; }; +template<> struct NumPyToCpp { typedef Index32 type; }; +template<> struct NumPyToCpp { typedef Index64 type; }; + + +// Abstract base class for helper classes that copy data between +// NumPy arrays of various types and grids of various types +template +class CopyOpBase +{ +public: + typedef typename GridType::ValueType ValueT; + + CopyOpBase(bool toGrid, GridType& grid, py::object arrObj, + py::object coordObj, py::object tolObj) + : mToGrid(toGrid) + , mGrid(&grid) + { + const char* const opName[2] = { "copyToArray", "copyFromArray" }; + + // Extract the coordinates (i, j, k) of the voxel at which to start populating data. + // Voxel (i, j, k) will correspond to array element (0, 0, 0). + const Coord origin = extractValueArg( + coordObj, opName[toGrid], 1, "tuple(int, int, int)"); + + // Extract a reference to (not a copy of) the NumPy array. + const py::numeric::array arrayObj = pyutil::extractArg( + arrObj, opName[toGrid], pyutil::GridTraits::name(), + /*argIdx=*/1, "numpy.ndarray"); + + const PyArray_Descr* dtype = PyArray_DESCR(arrayObj.ptr()); + const py::object shape = arrayObj.attr("shape"); + + if (PyObject_HasAttrString(arrayObj.ptr(), "dtype")) { + mArrayTypeName = pyutil::str(arrayObj.attr("dtype")); + } else { + mArrayTypeName = "'_'"; + mArrayTypeName[1] = dtype->kind; + } + + mArray = PyArray_DATA(arrayObj.ptr()); + mArrayTypeNum = dtype->type_num; + mTolerance = extractValueArg(tolObj, opName[toGrid], 2); + for (long i = 0, N = py::len(shape); i < N; ++i) { + mArrayDims.push_back(py::extract(shape[i])); + } + // Compute the bounding box of the region of the grid that is to be copied from or to. + mBBox.reset(origin, origin.offsetBy(mArrayDims[0]-1, mArrayDims[1]-1, mArrayDims[2]-1)); + } + virtual ~CopyOpBase() {} + + void operator()() const + { + try { + if (mToGrid) { + copyFromArray(); // copy data from the array to the grid + } else { + copyToArray(); // copy data from the grid to the array + } + } catch (openvdb::TypeError&) { + PyErr_Format(PyExc_TypeError, + "unsupported NumPy data type %s", mArrayTypeName.c_str()); + boost::python::throw_error_already_set(); + } + } + +protected: + virtual void validate() const = 0; + virtual void copyFromArray() const = 0; + virtual void copyToArray() const = 0; + + template + void fromArray() const + { + validate(); + tools::Dense valArray(mBBox, static_cast(mArray)); + tools::copyFromDense(valArray, *mGrid, mTolerance); + } + + template + void toArray() const + { + validate(); + tools::Dense valArray(mBBox, static_cast(mArray)); + tools::copyToDense(*mGrid, valArray); + } + + + bool mToGrid; // if true, copy from the array to the grid, else vice-versa + void* mArray; + GridType* mGrid; + int mArrayTypeNum; + std::vector mArrayDims; + std::string mArrayTypeName; + CoordBBox mBBox; + ValueT mTolerance; +}; // class CopyOpBase + + +// Helper subclass that can be specialized for various grid and NumPy array types +template class CopyOp: public CopyOpBase {}; + +// Specialization for scalar grids +template +class CopyOp: public CopyOpBase +{ +public: + CopyOp(bool toGrid, GridType& grid, py::object arrObj, py::object coordObj, + py::object tolObj = py::object(zeroVal())): + CopyOpBase(toGrid, grid, arrObj, coordObj, tolObj) + { + } + +protected: + virtual void validate() const + { + if (this->mArrayDims.size() != 3) { + std::ostringstream os; + os << "expected 3-dimensional array, found " + << this->mArrayDims.size() << "-dimensional array"; + PyErr_SetString(PyExc_ValueError, os.str().c_str()); + boost::python::throw_error_already_set(); + } + } + + virtual void copyFromArray() const + { + switch (this->mArrayTypeNum) { + case NPY_FLOAT: this->template fromArray::type>(); break; + case NPY_DOUBLE: this->template fromArray::type>(); break; + case NPY_BOOL: this->template fromArray::type>(); break; + case NPY_INT16: this->template fromArray::type>(); break; + case NPY_INT32: this->template fromArray::type>(); break; + case NPY_INT64: this->template fromArray::type>(); break; + case NPY_UINT32: this->template fromArray::type>(); break; + case NPY_UINT64: this->template fromArray::type>(); break; + default: throw openvdb::TypeError(); break; + } + } + + virtual void copyToArray() const + { + switch (this->mArrayTypeNum) { + case NPY_FLOAT: this->template toArray::type>(); break; + case NPY_DOUBLE: this->template toArray::type>(); break; + case NPY_BOOL: this->template toArray::type>(); break; + case NPY_INT16: this->template toArray::type>(); break; + case NPY_INT32: this->template toArray::type>(); break; + case NPY_INT64: this->template toArray::type>(); break; + case NPY_UINT32: this->template toArray::type>(); break; + case NPY_UINT64: this->template toArray::type>(); break; + default: throw openvdb::TypeError(); break; + } + } +}; // class CopyOp + +// Specialization for Vec3 grids +template +class CopyOp: public CopyOpBase +{ +public: + CopyOp(bool toGrid, GridType& grid, py::object arrObj, py::object coordObj, + py::object tolObj = py::object(zeroVal())): + CopyOpBase(toGrid, grid, arrObj, coordObj, tolObj) + { + } + +protected: + virtual void validate() const + { + if (this->mArrayDims.size() != 4) { + std::ostringstream os; + os << "expected 4-dimensional array, found " + << this->mArrayDims.size() << "-dimensional array"; + PyErr_SetString(PyExc_ValueError, os.str().c_str()); + boost::python::throw_error_already_set(); + } + if (this->mArrayDims[3] != 3) { + std::ostringstream os; + os << "expected " << this->mArrayDims[0] << "x" << this->mArrayDims[1] + << "x" << this->mArrayDims[2] << "x3 array, found " << this->mArrayDims[0] + << "x" << this->mArrayDims[1] << "x" << this->mArrayDims[2] + << "x" << this->mArrayDims[3] << " array"; + PyErr_SetString(PyExc_ValueError, os.str().c_str()); + boost::python::throw_error_already_set(); + } + } + + virtual void copyFromArray() const + { + switch (this->mArrayTypeNum) { + case NPY_FLOAT: + this->template fromArray::type> >(); break; + case NPY_DOUBLE: + this->template fromArray::type> >(); break; + case NPY_BOOL: + this->template fromArray::type> >(); break; + case NPY_INT16: + this->template fromArray::type> >(); break; + case NPY_INT32: + this->template fromArray::type> >(); break; + case NPY_INT64: + this->template fromArray::type> >(); break; + case NPY_UINT32: + this->template fromArray::type> >(); break; + case NPY_UINT64: + this->template fromArray::type> >(); break; + default: throw openvdb::TypeError(); break; + } + } + + virtual void copyToArray() const + { + switch (this->mArrayTypeNum) { + case NPY_FLOAT: + this->template toArray::type> >(); break; + case NPY_DOUBLE: + this->template toArray::type> >(); break; + case NPY_BOOL: + this->template toArray::type> >(); break; + case NPY_INT16: + this->template toArray::type> >(); break; + case NPY_INT32: + this->template toArray::type> >(); break; + case NPY_INT64: + this->template toArray::type> >(); break; + case NPY_UINT32: + this->template toArray::type> >(); break; + case NPY_UINT64: + this->template toArray::type> >(); break; + default: throw openvdb::TypeError(); break; + } + } +}; // class CopyOp + + +template +inline void +copyFromArray(GridType& grid, py::object arrayObj, py::object coordObj, py::object toleranceObj) +{ + typedef typename GridType::ValueType ValueT; + CopyOp::Size> + op(/*toGrid=*/true, grid, arrayObj, coordObj, toleranceObj); + op(); +} + + +template +inline void +copyToArray(GridType& grid, py::object arrayObj, py::object coordObj) +{ + typedef typename GridType::ValueType ValueT; + CopyOp::Size> + op(/*toGrid=*/false, grid, arrayObj, coordObj); + op(); +} + +#endif // defined(PY_OPENVDB_USE_NUMPY) + + +//////////////////////////////////////// + + +template +inline void +applyMap(const char* methodName, GridType& grid, py::object funcObj) +{ + typedef typename GridType::ValueType ValueT; + + for (IterType it = grid.tree().template begin(); it; ++it) { + // Evaluate the functor. + py::object result = funcObj(*it); + + // Verify that the result is of type GridType::ValueType. + py::extract val(result); + if (!val.check()) { + PyErr_Format(PyExc_TypeError, + "expected callable argument to %s.%s() to return %s, found %s", + pyutil::GridTraits::name(), + methodName, + openvdb::typeNameAsString(), + pyutil::className(result).c_str()); + py::throw_error_already_set(); + } + + it.setValue(val()); + } +} + + +template +inline void +mapOn(GridType& grid, py::object funcObj) +{ + applyMap("mapOn", grid, funcObj); +} + + +template +inline void +mapOff(GridType& grid, py::object funcObj) +{ + applyMap("mapOff", grid, funcObj); +} + + +template +inline void +mapAll(GridType& grid, py::object funcObj) +{ + applyMap("mapAll", grid, funcObj); +} + + +//////////////////////////////////////// + + +template +struct TreeCombineOp +{ + typedef typename GridType::TreeType TreeT; + typedef typename GridType::ValueType ValueT; + + TreeCombineOp(py::object _op): op(_op) {} + void operator()(const ValueT& a, const ValueT& b, ValueT& result) + { + py::object resultObj = op(a, b); + + py::extract val(resultObj); + if (!val.check()) { + PyErr_Format(PyExc_TypeError, + "expected callable argument to %s.combine() to return %s, found %s", + pyutil::GridTraits::name(), + openvdb::typeNameAsString(), + pyutil::className(resultObj).c_str()); + py::throw_error_already_set(); + } + + result = val(); + } + py::object op; +}; + + +template +inline void +combine(GridType& grid, py::object otherGridObj, py::object funcObj) +{ + typedef typename GridType::Ptr GridPtr; + GridPtr otherGrid = extractValueArg(otherGridObj, + "combine", 1, pyutil::GridTraits::name()); + TreeCombineOp op(funcObj); + grid.tree().combine(otherGrid->tree(), op, /*prune=*/true); +} + + +//////////////////////////////////////// + + +template +inline typename GridType::Ptr +createLevelSetSphere(float radius, const openvdb::Vec3f& center, float voxelSize, float halfWidth) +{ + return tools::createLevelSetSphere(radius, center, voxelSize, halfWidth); +} + + +//////////////////////////////////////// + + +template class IterWrap; // forward declaration + +// +// Type traits for various iterators +// +template struct IterTraits +{ + // IterT the type of the iterator + // name() function returning the base name of the iterator type (e.g., "ValueOffIter") + // descr() function returning a string describing the iterator + // begin() function returning a begin iterator for a given grid +}; + +template struct IterTraits +{ + typedef typename GridT::ValueOnCIter IterT; + static std::string name() { return "ValueOnCIter"; } + static std::string descr() + { + return std::string("Read-only iterator over the active values (tile and voxel)\nof a ") + + pyutil::GridTraits::type>::name(); + } + static IterWrap begin(typename GridT::Ptr g) + { + return IterWrap(g, g->cbeginValueOn()); + } +}; // IterTraits + +template struct IterTraits +{ + typedef typename GridT::ValueOffCIter IterT; + static std::string name() { return "ValueOffCIter"; } + static std::string descr() + { + return std::string("Read-only iterator over the inactive values (tile and voxel)\nof a ") + + pyutil::GridTraits::type>::name(); + } + static IterWrap begin(typename GridT::Ptr g) + { + return IterWrap(g, g->cbeginValueOff()); + } +}; // IterTraits + +template struct IterTraits +{ + typedef typename GridT::ValueAllCIter IterT; + static std::string name() { return "ValueAllCIter"; } + static std::string descr() + { + return std::string("Read-only iterator over all tile and voxel values of a ") + + pyutil::GridTraits::type>::name(); + } + static IterWrap begin(typename GridT::Ptr g) + { + return IterWrap(g, g->cbeginValueAll()); + } +}; // IterTraits + +template struct IterTraits +{ + typedef typename GridT::ValueOnIter IterT; + static std::string name() { return "ValueOnIter"; } + static std::string descr() + { + return std::string("Read/write iterator over the active values (tile and voxel)\nof a ") + + pyutil::GridTraits::type>::name(); + } + static IterWrap begin(typename GridT::Ptr g) + { + return IterWrap(g, g->beginValueOn()); + } +}; // IterTraits + +template struct IterTraits +{ + typedef typename GridT::ValueOffIter IterT; + static std::string name() { return "ValueOffIter"; } + static std::string descr() + { + return std::string("Read/write iterator over the inactive values (tile and voxel)\nof a ") + + pyutil::GridTraits::type>::name(); + } + static IterWrap begin(typename GridT::Ptr g) + { + return IterWrap(g, g->beginValueOff()); + } +}; // IterTraits + +template struct IterTraits +{ + typedef typename GridT::ValueAllIter IterT; + static std::string name() { return "ValueAllIter"; } + static std::string descr() + { + return std::string("Read/write iterator over all tile and voxel values of a ") + + pyutil::GridTraits::type>::name(); + } + static IterWrap begin(typename GridT::Ptr g) + { + return IterWrap(g, g->beginValueAll()); + } +}; // IterTraits + + +//////////////////////////////////////// + + +// Helper class to modify a grid through a non-const iterator +template +struct IterItemSetter +{ + typedef typename GridT::ValueType ValueT; + static void setValue(const IterT& iter, const ValueT& val) { iter.setValue(val); } + static void setActive(const IterT& iter, bool on) { iter.setActiveState(on); } +}; + +// Partial specialization for const iterators +template +struct IterItemSetter +{ + typedef typename GridT::ValueType ValueT; + static void setValue(const IterT& iter, const ValueT& val) + { + PyErr_SetString(PyExc_AttributeError, "can't set attribute 'value'"); + py::throw_error_already_set(); + } + static void setActive(const IterT& iter, bool on) + { + PyErr_SetString(PyExc_AttributeError, "can't set attribute 'active'"); + py::throw_error_already_set(); + } +}; + + +/// @brief Value returned by the next() method of a grid's value iterator +/// @details This class allows both dictionary-style (e.g., items["depth"]) and +/// attribute access (e.g., items.depth) to the items returned by an iterator. +/// @todo Create a reusable base class for "named dicts" like this? +template +class IterValueProxy +{ +public: + typedef _GridT GridT; + typedef _IterT IterT; + typedef typename GridT::ValueType ValueT; + typedef IterItemSetter SetterT; + + IterValueProxy(typename GridT::ConstPtr grid, const IterT& iter): mGrid(grid), mIter(iter) {} + + IterValueProxy copy() const { return *this; } + + typename GridT::ConstPtr parent() const { return mGrid; } + + ValueT getValue() const { return *mIter; } + bool getActive() const { return mIter.isValueOn(); } + Index getDepth() const { return mIter.getDepth(); } + Coord getBBoxMin() const { return mIter.getBoundingBox().min(); } + Coord getBBoxMax() const { return mIter.getBoundingBox().max(); } + Index64 getVoxelCount() const { return mIter.getVoxelCount(); } + + void setValue(const ValueT& val) { SetterT::setValue(mIter, val); } + void setActive(bool on) { SetterT::setActive(mIter, on); } + + /// Return this dictionary's keys as a list of C strings. + static const char* const * keys() + { + static const char* const sKeys[] = { + "value", "active", "depth", "min", "max", "count", NULL + }; + return sKeys; + } + + /// Return @c true if the given string is a valid key. + static bool hasKey(const std::string& key) + { + for (int i = 0; keys()[i] != NULL; ++i) { + if (key == keys()[i]) return true; + } + return false; + } + + /// Return this dictionary's keys as a Python list of Python strings. + static py::list getKeys() + { + py::list keyList; + for (int i = 0; keys()[i] != NULL; ++i) keyList.append(keys()[i]); + return keyList; + } + + /// @brief Return the value for the given key. + /// @throw KeyError if the key is invalid + py::object getItem(py::object keyObj) const + { + py::extract x(keyObj); + if (x.check()) { + const std::string key = x(); + if (key == "value") return py::object(this->getValue()); + else if (key == "active") return py::object(this->getActive()); + else if (key == "depth") return py::object(this->getDepth()); + else if (key == "min") return py::object(this->getBBoxMin()); + else if (key == "max") return py::object(this->getBBoxMax()); + else if (key == "count") return py::object(this->getVoxelCount()); + } + PyErr_SetObject(PyExc_KeyError, ("%s" % keyObj.attr("__repr__")()).ptr()); + py::throw_error_already_set(); + return py::object(); + } + + /// @brief Set the value for the given key. + /// @throw KeyError if the key is invalid + /// @throw AttributeError if the key refers to a read-only item + void setItem(py::object keyObj, py::object valObj) + { + py::extract x(keyObj); + if (x.check()) { + const std::string key = x(); + if (key == "value") { + this->setValue(py::extract(valObj)); return; + } else if (key == "active") { + this->setActive(py::extract(valObj)); return; + } else if (this->hasKey(key)) { + PyErr_SetObject(PyExc_AttributeError, + ("can't set attribute '%s'" % keyObj.attr("__repr__")()).ptr()); + py::throw_error_already_set(); + } + } + PyErr_SetObject(PyExc_KeyError, + ("'%s'" % keyObj.attr("__repr__")()).ptr()); + py::throw_error_already_set(); + } + + bool operator==(const IterValueProxy& other) const + { + return (other.getActive() == this->getActive() + && other.getDepth() == this->getDepth() + && other.getValue() == this->getValue() + && other.getBBoxMin() == this->getBBoxMin() + && other.getBBoxMax() == this->getBBoxMax() + && other.getVoxelCount() == this->getVoxelCount()); + } + bool operator!=(const IterValueProxy& other) const { return !(*this == other); } + + /// Print this dictionary to a stream. + std::ostream& put(std::ostream& os) const + { + // valuesAsStrings = ["%s: %s" % key, repr(this[key]) for key in this.keys()] + py::list valuesAsStrings; + for (int i = 0; this->keys()[i] != NULL; ++i) { + py::str + key(this->keys()[i]), + val(this->getItem(key).attr("__repr__")()); + valuesAsStrings.append("'%s': %s" % py::make_tuple(key, val)); + } + // print ", ".join(valuesAsStrings) + py::object joined = py::str(", ").attr("join")(valuesAsStrings); + std::string s = py::extract(joined); + os << "{" << s << "}"; + return os; + } + /// Return a string describing this dictionary. + std::string info() const { std::ostringstream os; os << *this; return os.str(); } + +private: + // To keep the iterator's grid from being deleted (leaving the iterator dangling), + // store a shared pointer to the grid. + const typename GridT::ConstPtr mGrid; + const IterT mIter; // the iterator may not be incremented +}; // class IterValueProxy + + +template +inline std::ostream& +operator<<(std::ostream& os, const IterValueProxy& iv) { return iv.put(os); } + + +//////////////////////////////////////// + + +/// Wrapper for a grid's value iterator classes +template +class IterWrap +{ +public: + typedef _GridT GridT; + typedef _IterT IterT; + typedef typename GridT::ValueType ValueT; + typedef IterValueProxy IterValueProxyT; + typedef IterTraits Traits; + + IterWrap(typename GridT::ConstPtr grid, const IterT& iter): mGrid(grid), mIter(iter) {} + + typename GridT::ConstPtr parent() const { return mGrid; } + + /// Return an IterValueProxy for the current iterator position. + IterValueProxyT next() + { + if (!mIter) { + PyErr_SetString(PyExc_StopIteration, "no more values"); + py::throw_error_already_set(); + } + IterValueProxyT result(mGrid, mIter); + ++mIter; + return result; + } + + static py::object returnSelf(const py::object& obj) { return obj; } + + /// @brief Define a Python wrapper class for this C++ class and another for + /// the IterValueProxy class returned by iterators of this type. + static void wrap() + { + const std::string + gridClassName = pyutil::GridTraits::type>::name(), + iterClassName = /*gridClassName +*/ Traits::name(), + valueClassName = /*gridClassName +*/ "Value"; + + py::class_( + iterClassName.c_str(), + /*docstring=*/Traits::descr().c_str(), + /*ctor=*/py::no_init) // can only be instantiated from C++, not from Python + + .add_property("parent", &IterWrap::parent, + ("the " + gridClassName + " over which to iterate").c_str()) + + .def("next", &IterWrap::next, ("next() -> " + valueClassName).c_str()) + .def("__iter__", &returnSelf); + + py::class_( + valueClassName.c_str(), + /*docstring=*/("Proxy for a tile or voxel value in a " + gridClassName).c_str(), + /*ctor=*/py::no_init) // can only be instantiated from C++, not from Python + + .def("copy", &IterValueProxyT::copy, + ("copy() -> " + valueClassName + "\n\n" + "Return a shallow copy of this value, i.e., one that shares\n" + "its data with the original.").c_str()) + + .add_property("parent", &IterValueProxyT::parent, + ("the " + gridClassName + " to which this value belongs").c_str()) + + .def("__str__", &IterValueProxyT::info) + .def("__repr__", &IterValueProxyT::info) + + .def("__eq__", &IterValueProxyT::operator==) + .def("__ne__", &IterValueProxyT::operator!=) + + .add_property("value", &IterValueProxyT::getValue, &IterValueProxyT::setValue, + "value of this tile or voxel") + .add_property("active", &IterValueProxyT::getActive, &IterValueProxyT::setActive, + "active state of this tile or voxel") + .add_property("depth", &IterValueProxyT::getDepth, + "tree depth at which this value is stored") + .add_property("min", &IterValueProxyT::getBBoxMin, + "lower bound of the axis-aligned bounding box of this tile or voxel") + .add_property("max", &IterValueProxyT::getBBoxMax, + "upper bound of the axis-aligned bounding box of this tile or voxel") + .add_property("count", &IterValueProxyT::getVoxelCount, + "number of voxels spanned by this value") + + .def("keys", &IterValueProxyT::getKeys, + "keys() -> list\n\n" + "Return a list of keys for this tile or voxel.") + .staticmethod("keys") + .def("__contains__", &IterValueProxyT::hasKey, + "__contains__(key) -> bool\n\n" + "Return True if the given key exists.") + .staticmethod("__contains__") + .def("__getitem__", &IterValueProxyT::getItem, + "__getitem__(key) -> value\n\n" + "Return the value of the item with the given key.") + .def("__setitem__", &IterValueProxyT::getItem, + "__setitem__(key, value)\n\n" + "Set the value of the item with the given key."); + } + +private: + // To keep this iterator's grid from being deleted, leaving the iterator dangling, + // store a shared pointer to the grid. + const typename GridT::ConstPtr mGrid; + IterT mIter; +}; // class IterWrap + + +//////////////////////////////////////// + + +template +struct PickleSuite: py::pickle_suite +{ + typedef typename GridT::Ptr GridPtrT; + + /// Return @c true, indicating that this pickler preserves a Grid's __dict__. + static bool getstate_manages_dict() { return true; } + + /// Return a tuple representing the state of the given Grid. + static py::tuple getstate(py::object gridObj) + { + py::tuple state; + + // Extract a Grid from the Python object. + GridPtrT grid; + py::extract x(gridObj); + if (x.check()) grid = x(); + + if (grid) { + // Serialize the Grid to a string. + std::ostringstream ostr(std::ios_base::binary); + { + openvdb::io::Stream strm(ostr); + strm.setGridStatsMetadataEnabled(false); + strm.write(openvdb::GridPtrVec(1, grid)); + } + // Construct a state tuple comprising the Python object's __dict__ + // and the serialized Grid. + state = py::make_tuple(gridObj.attr("__dict__"), ostr.str()); + } + return state; + } + + /// Restore the given Grid to a saved state. + static void setstate(py::object gridObj, py::object stateObj) + { + GridPtrT grid; + { + py::extract x(gridObj); + if (x.check()) grid = x(); + } + if (!grid) return; + + py::tuple state; + { + py::extract x(stateObj); + if (x.check()) state = x(); + } + bool badState = (py::len(state) != 2); + + if (!badState) { + // Restore the object's __dict__. + py::extract x(state[0]); + if (x.check()) { + py::dict d = py::extract(gridObj.attr("__dict__"))(); + d.update(x()); + } else { + badState = true; + } + } + + std::string serialized; + if (!badState) { + // Extract the string containing the serialized Grid. + py::extract x(state[1]); + if (x.check()) serialized = x(); + else badState = true; + } + + if (badState) { + PyErr_SetObject(PyExc_ValueError, + ("expected (dict, str) tuple in call to __setstate__; found %s" + % stateObj.attr("__repr__")()).ptr()); + py::throw_error_already_set(); + } + + // Restore the internal state of the C++ object. + GridPtrVecPtr grids; + { + std::istringstream istr(serialized, std::ios_base::binary); + io::Stream strm(istr); + grids = strm.getGrids(); // (note: file-level metadata is ignored) + } + if (grids && !grids->empty()) { + if (GridPtrT savedGrid = gridPtrCast((*grids)[0])) { + grid->MetaMap::operator=(*savedGrid); ///< @todo add a Grid::setMetadata() method? + grid->setTransform(savedGrid->transformPtr()); + grid->setTree(savedGrid->treePtr()); + } + } + } +}; // struct PickleSuite + + +//////////////////////////////////////// + + +/// Create a Python wrapper for a particular template instantiation of Grid. +template +inline void +exportGrid() +{ + typedef typename GridType::ValueType ValueT; + typedef typename GridType::Ptr GridPtr; + typedef pyutil::GridTraits Traits; + + typedef typename GridType::ValueOnCIter ValueOnCIterT; + typedef typename GridType::ValueOffCIter ValueOffCIterT; + typedef typename GridType::ValueAllCIter ValueAllCIterT; + typedef typename GridType::ValueOnIter ValueOnIterT; + typedef typename GridType::ValueOffIter ValueOffIterT; + typedef typename GridType::ValueAllIter ValueAllIterT; + + math::Transform::Ptr (GridType::*getTransform)() = &GridType::transformPtr; + + const std::string pyGridTypeName = Traits::name(); + const std::string defaultCtorDescr = "Initialize with a background value of " + + pyutil::str(pyGrid::getZeroValue()) + "."; + + // Define the Grid wrapper class and make it the current scope. + { + py::class_ clss( + /*classname=*/pyGridTypeName.c_str(), + /*docstring=*/(Traits::descr()).c_str(), + /*ctor=*/py::init<>(defaultCtorDescr.c_str()) + ); + + py::scope gridClassScope = clss; + + clss.def(py::init(py::args("background"), + "Initialize with the given background value.")) + + .def("copy", &pyGrid::copyGrid, + ("copy() -> " + pyGridTypeName + "\n\n" + "Return a shallow copy of this grid, i.e., a grid\n" + "that shares its voxel data with this grid.").c_str()) + .def("deepCopy", &GridType::deepCopy, + ("deepCopy() -> " + pyGridTypeName + "\n\n" + "Return a deep copy of this grid.\n").c_str()) + + .def_pickle(pyGrid::PickleSuite()) + + .def("sharesWith", &pyGrid::sharesWith, + ("sharesWith(" + pyGridTypeName + ") -> bool\n\n" + "Return True if this grid shares its voxel data with the given grid.").c_str()) + + /// @todo Any way to set a docstring for a class property? + .add_static_property("valueTypeName", &pyGrid::getValueType) + /// @todo docstring = "name of this grid's value type" + .add_static_property("zeroValue", &pyGrid::getZeroValue) + /// @todo docstring = "zero, as expressed in this grid's value type" + .add_static_property("oneValue", &pyGrid::getOneValue) + /// @todo docstring = "one, as expressed in this grid's value type" + /// @todo Is Grid.typeName ever needed? + //.add_static_property("typeName", &GridType::gridType) + /// @todo docstring = to "name of this grid's type" + + .add_property("background", + &pyGrid::getGridBackground, &pyGrid::setGridBackground, + "value of this grid's background voxels") + .add_property("name", &GridType::getName, &pyGrid::setGridName, + "this grid's name") + .add_property("creator", &GridType::getCreator, &pyGrid::setGridCreator, + "description of this grid's creator") + + .add_property("transform", getTransform, &pyGrid::setGridTransform, + "transform associated with this grid") + + .add_property("gridClass", &pyGrid::getGridClass, &pyGrid::setGridClass, + "the class of volumetric data (level set, fog volume, etc.)\nstored in this grid") + + .add_property("vectorType", &pyGrid::getVecType, &pyGrid::setVecType, + "how transforms are applied to values stored in this grid") + + .def("getAccessor", &pyGrid::getAccessor, + ("getAccessor() -> " + pyGridTypeName + "Accessor\n\n" + "Return an accessor that provides random read and write access\n" + "to this grid's voxels.").c_str()) + .def("getConstAccessor", &pyGrid::getConstAccessor, + ("getConstAccessor() -> " + pyGridTypeName + "Accessor\n\n" + "Return an accessor that provides random read-only access\n" + "to this grid's voxels.").c_str()) + + // + // Metadata + // + .add_property("metadata", &pyGrid::getAllMetadata, &pyGrid::replaceAllMetadata, + "dict of this grid's metadata\n\n" + "Setting this attribute replaces all of this grid's metadata,\n" + "but mutating it in place has no effect on the grid, since\n" + "the value of this attribute is a only a copy of the metadata.\n" + "Use either indexing or updateMetadata() to mutate metadata in place.") + .def("updateMetadata", &pyGrid::updateMetadata, + "updateMetadata(dict)\n\n" + "Add metadata to this grid, replacing any existing items\n" + "having the same names as the new items.") + + .def("addStatsMetadata", &GridType::addStatsMetadata, + "addStatsMetadata()\n\n" + "Add metadata to this grid comprising the current values\n" + "of statistics like the active voxel count and bounding box.\n" + "(This metadata is not automatically kept up-to-date with\n" + "changes to this grid.)") + .def("getStatsMetadata", &pyGrid::getStatsMetadata, + "getStatsMetadata() -> dict\n\n" + "Return a (possibly empty) dict containing just the metadata\n" + "that was added to this grid with addStatsMetadata().") + + .def("__getitem__", &pyGrid::getMetadata, + "__getitem__(name) -> value\n\n" + "Return the metadata value associated with the given name.") + .def("__setitem__", &pyGrid::setMetadata, + "__setitem__(name, value)\n\n" + "Add metadata to this grid, replacing any existing item having\n" + "the same name as the new item.") + .def("__delitem__", &pyGrid::removeMetadata, + "__delitem__(name)\n\n" + "Remove the metadata with the given name.") + .def("__contains__", &pyGrid::hasMetadata, + "__contains__(name) -> bool\n\n" + "Return True if this grid contains metadata with the given name.") + .def("__iter__", &pyGrid::getMetadataKeys, + "__iter__() -> iterator\n\n" + "Return an iterator over this grid's metadata keys.") + .def("iterkeys", &pyGrid::getMetadataKeys, + "iterkeys() -> iterator\n\n" + "Return an iterator over this grid's metadata keys.") + + .add_property("saveFloatAsHalf", + &GridType::saveFloatAsHalf, &GridType::setSaveFloatAsHalf, + "if True, write floating-point voxel values as 16-bit half floats") + + // + // Statistics + // + .def("memUsage", &GridType::memUsage, + "memUsage() -> int\n\n" + "Return the memory usage of this grid in bytes.") + + .def("evalLeafBoundingBox", &pyGrid::evalLeafBoundingBox, + "evalLeafBoundingBox() -> xyzMin, xyzMax\n\n" + "Return the coordinates of opposite corners of the axis-aligned\n" + "bounding box of all leaf nodes.") + .def("evalLeafDim", &pyGrid::evalLeafDim, + "evalLeafDim() -> x, y, z\n\n" + "Return the dimensions of the axis-aligned bounding box\n" + "of all leaf nodes.") + + .def("evalActiveVoxelBoundingBox", &pyGrid::evalActiveVoxelBoundingBox, + "evalActiveVoxelBoundingBox() -> xyzMin, xyzMax\n\n" + "Return the coordinates of opposite corners of the axis-aligned\n" + "bounding box of all active voxels.") + .def("evalActiveVoxelDim", &GridType::evalActiveVoxelDim, + "evalActiveVoxelDim() -> x, y, z\n\n" + "Return the dimensions of the axis-aligned bounding box of all\n" + "active voxels.") + + .add_property("treeDepth", &pyGrid::treeDepth, + "depth of this grid's tree from root node to leaf node") + .def("nodeLog2Dims", &pyGrid::getNodeLog2Dims, + "list of Log2Dims of the nodes of this grid's tree\n" + "in order from root to leaf") + + .def("leafCount", &pyGrid::leafCount, + "leafCount() -> int\n\n" + "Return the number of leaf nodes in this grid's tree.") + .def("nonLeafCount", &pyGrid::nonLeafCount, + "nonLeafCount() -> int\n\n" + "Return the number of non-leaf nodes in this grid's tree.") + + .def("activeVoxelCount", &GridType::activeVoxelCount, + "activeVoxelCount() -> int\n\n" + "Return the number of active voxels in this grid.") + .def("activeLeafVoxelCount", &pyGrid::activeLeafVoxelCount, + "activeLeafVoxelCount() -> int\n\n" + "Return the number of active voxels that are stored\n" + "in the leaf nodes of this grid's tree.") + + .def("evalMinMax", &pyGrid::evalMinMax, + "evalMinMax() -> min, max\n\n" + "Return the minimum and maximum active values in this grid.") + + .def("getIndexRange", &pyGrid::getIndexRange, + "getIndexRange() -> min, max\n\n" + "Return the minimum and maximum coordinates that are represented\n" + "in this grid. These might include background voxels.") + //.def("expand", &pyGrid::expandIndexRange, + // py::arg("xyz"), + // "expand(xyz)\n\n" + // "Expand this grid's index range to include the given coordinates.") + + .def("info", &pyGrid::gridInfo, + py::arg("verbosity")=1, + "info(verbosity=1) -> str\n\n" + "Return a string containing information about this grid\n" + "with a specified level of verbosity.\n") + + // + // Tools + // + .def("fill", &pyGrid::fill, + (py::arg("min"), py::arg("max"), py::arg("value"), py::arg("active")=true), + "fill(min, max, value, active=True)\n\n" + "Set all voxels within a given axis-aligned box to\n" + "a constant value (either active or inactive).") + .def("signedFloodFill", &pyGrid::signedFloodFill, + "signedFloodFill()\n\n" + "Propagate the sign from a narrow-band level set into inactive\n" + "voxels and tiles.") + + .def("copyFromArray", &pyGrid::copyFromArray, + (py::arg("array"), py::arg("ijk")=Coord(0), + py::arg("tolerance")=pyGrid::getZeroValue()), + ("copyFromArray(array, ijk=(0, 0, 0), tolerance=0)\n\n" + "Populate this grid, starting at voxel (i, j, k), with values\nfrom a " + + std::string(openvdb::VecTraits::IsVec ? "four" : "three") + + "-dimensional array. Mark voxels as inactive\n" + "if and only if their values are equal to this grid's\n" + "background value within the given tolerance.").c_str()) + .def("copyToArray", &pyGrid::copyToArray, + (py::arg("array"), py::arg("ijk")=Coord(0)), + ("copyToArray(array, ijk=(0, 0, 0))\n\nPopulate a " + + std::string(openvdb::VecTraits::IsVec ? "four" : "three") + + "-dimensional array with values\n" + "from this grid, starting at voxel (i, j, k).").c_str()) + + .def("prune", &pyGrid::prune, + (py::arg("tolerance")=0), + "prune(tolerance=0)\n\n" + "Remove nodes whose values all have the same active state\n" + "and are equal to within a given tolerance.") + .def("pruneInactive", &pyGrid::pruneInactive, + (py::arg("value")=py::object()), + "pruneInactive(value=None)\n\n" + "Remove nodes whose values are all inactive and replace them\n" + "with either background tiles or tiles of the given value\n" + "(if the value is not None).") + + .def("empty", &GridType::empty, + "empty() -> bool\n\n" + "Return True if this grid contains only background voxels.") + .def("__nonzero__", &pyGrid::notEmpty) + + .def("clear", &GridType::clear, + "clear()\n\n" + "Remove all tiles from this grid and all nodes other than the root node.") + + .def("merge", &GridType::merge, + ("merge(" + pyGridTypeName + ")\n\n" + "Move child nodes from the other grid into this grid wherever\n" + "those nodes correspond to constant-value tiles in this grid,\n" + "and replace leaf-level inactive voxels in this grid with\n" + "corresponding voxels in the other grid that are active.\n\n" + "Note: this operation always empties the other grid.").c_str()) + + .def("mapOn", &pyGrid::mapOn, + py::arg("function"), + "mapOn(function)\n\n" + "Iterate over all the active (\"on\") values (tile and voxel)\n" + "of this grid and replace each value with function(value).\n\n" + "Example: grid.mapOn(lambda x: x * 2 if x < 0.5 else x)") + + .def("mapOff", &pyGrid::mapOff, + py::arg("function"), + "mapOff(function)\n\n" + "Iterate over all the inactive (\"off\") values (tile and voxel)\n" + "of this grid and replace each value with function(value).\n\n" + "Example: grid.mapOff(lambda x: x * 2 if x < 0.5 else x)") + + .def("mapAll", &pyGrid::mapAll, + py::arg("function"), + "mapAll(function)\n\n" + "Iterate over all values (tile and voxel) of this grid\n" + "and replace each value with function(value).\n\n" + "Example: grid.mapAll(lambda x: x * 2 if x < 0.5 else x)") + + .def("combine", &pyGrid::combine, + (py::arg("grid"), py::arg("function")), + "combine(grid, function)\n\n" + "Compute function(self, other) over all corresponding pairs\n" + "of values (tile or voxel) of this grid and the other grid\n" + "and store the result in this grid.\n\n" + "Note: this operation always empties the other grid.\n\n" + "Example: grid.combine(otherGrid, lambda a, b: min(a, b))") + + // + // Iterators + // + .def("citerOnValues", &pyGrid::IterTraits::begin, + "citerOnValues() -> iterator\n\n" + "Return a read-only iterator over this grid's active\ntile and voxel values.") + .def("citerOffValues", &pyGrid::IterTraits::begin, + "iterOffValues() -> iterator\n\n" + "Return a read-only iterator over this grid's inactive\ntile and voxel values.") + .def("citerAllValues", &pyGrid::IterTraits::begin, + "iterAllValues() -> iterator\n\n" + "Return a read-only iterator over all of this grid's\ntile and voxel values.") + + .def("iterOnValues", &pyGrid::IterTraits::begin, + "iterOnValues() -> iterator\n\n" + "Return a read/write iterator over this grid's active\ntile and voxel values.") + .def("iterOffValues", &pyGrid::IterTraits::begin, + "iterOffValues() -> iterator\n\n" + "Return a read/write iterator over this grid's inactive\ntile and voxel values.") + .def("iterAllValues", &pyGrid::IterTraits::begin, + "iterAllValues() -> iterator\n\n" + "Return a read/write iterator over all of this grid's\ntile and voxel values.") + + ; // py::class_ + + py::implicitly_convertible(); + py::implicitly_convertible(); + /// @todo Is there a way to implicitly convert GridType references to GridBase + /// references without wrapping the GridBase class? The following doesn't compile, + /// because GridBase has pure virtual functions: + /// @code + /// py::implicitly_convertible(); + /// @endcode + + // Wrap const and non-const value accessors and expose them + // as nested classes of the Grid class. + pyAccessor::AccessorWrap::wrap(); + pyAccessor::AccessorWrap::wrap(); + + // Wrap tree value iterators and expose them as nested classes of the Grid class. + IterWrap::wrap(); + IterWrap::wrap(); + IterWrap::wrap(); + IterWrap::wrap(); + IterWrap::wrap(); + IterWrap::wrap(); + + } // gridClassScope + + // Add the Python type object for this grid type to the module-level list. + py::extract(py::scope().attr("GridTypes"))().append( + py::scope().attr(pyGridTypeName.c_str())); +} + +} // namespace pyGrid + +#endif // OPENVDB_PYGRID_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/python/pyIntGrid.cc b/openvdb_2_3_0_library/openvdb/python/pyIntGrid.cc new file mode 100755 index 0000000..43f68d2 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/python/pyIntGrid.cc @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file pyIntGrid.cc +/// @brief Boost.Python wrappers for scalar, integer-valued openvdb::Grid types + +#include "pyGrid.h" + + +void +exportIntGrid() +{ + pyGrid::exportGrid(); +#ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES + pyGrid::exportGrid(); + pyGrid::exportGrid(); +#endif +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/python/pyMetadata.cc b/openvdb_2_3_0_library/openvdb/python/pyMetadata.cc new file mode 100755 index 0000000..121b2ef --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/python/pyMetadata.cc @@ -0,0 +1,90 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include "openvdb/openvdb.h" + +namespace py = boost::python; +using namespace openvdb::OPENVDB_VERSION_NAME; + +namespace { + +class MetadataWrap: public Metadata, public py::wrapper +{ +public: + Name typeName() const { return this->get_override("typeName")(); } + Metadata::Ptr copy() const { return this->get_override("copy")(); } + void copy(const Metadata& other) { this->get_override("copy")(other); } + std::string str() const { return this->get_override("str")(); } + bool asBool() const { return this->get_override("asBool")(); } + Index32 size() const { return this->get_override("size")(); } + +protected: + void readValue(std::istream& is, Index32 numBytes) { + this->get_override("readValue")(is, numBytes); + } + void writeValue(std::ostream& os) const { + this->get_override("writeValue")(os); + } +}; + +// aliases disambiguate the different versions of copy +Metadata::Ptr (MetadataWrap::*copy0)() const = &MetadataWrap::copy; +void (MetadataWrap::*copy1)(const Metadata&) = &MetadataWrap::copy; + +} // end anonymous namespace + + +void exportMetadata() +{ + py::class_ clss( + /*classname=*/"Metadata", + /*docstring=*/ + "Class that holds the value of a single item of metadata of a type\n" + "for which no Python equivalent exists (typically a custom type)", + /*ctor=*/py::no_init // can only be instantiated from C++, not from Python + ); + clss.def("copy", py::pure_virtual(copy0), + "copy() -> Metadata\n\nReturn a copy of this value.") + .def("copy", py::pure_virtual(copy1), + "copy() -> Metadata\n\nReturn a copy of this value.") + .def("type", py::pure_virtual(&Metadata::typeName), + "type() -> str\n\nReturn the name of this value's type.") + .def("size", py::pure_virtual(&Metadata::size), + "size() -> int\n\nReturn the size of this value in bytes.") + .def("__nonzero__", py::pure_virtual(&Metadata::asBool)) + .def("__str__", py::pure_virtual(&Metadata::str)) + ; + py::register_ptr_to_python(); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/python/pyOpenVDBModule.cc b/openvdb_2_3_0_library/openvdb/python/pyOpenVDBModule.cc new file mode 100755 index 0000000..0e1ced2 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/python/pyOpenVDBModule.cc @@ -0,0 +1,693 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include // for strncmp(), strrchr(), etc. +#include +#include +#include +#include +#include "openvdb/openvdb.h" +#include "pyopenvdb.h" +#include "pyGrid.h" +#include "pyutil.h" + +namespace py = boost::python; + + +// Forward declarations +void exportTransform(); +void exportMetadata(); +void exportFloatGrid(); +void exportIntGrid(); +void exportVec3Grid(); + + +namespace _openvdbmodule { + +using namespace openvdb; + + +/// Helper class to convert between a Python numeric sequence +/// (tuple, list, etc.) and an openvdb::Coord +struct CoordConverter +{ + /// @return a Python tuple object equivalent to the given Coord. + static PyObject* convert(const openvdb::Coord& xyz) + { + py::object obj = py::make_tuple(xyz[0], xyz[1], xyz[2]); + Py_INCREF(obj.ptr()); + ///< @todo is this the right way to ensure that the object + ///< doesn't get freed on exit? + return obj.ptr(); + } + + /// @return NULL if the given Python object is not convertible to a Coord. + static void* convertible(PyObject* obj) + { + if (!PySequence_Check(obj)) return NULL; // not a Python sequence + + Py_ssize_t len = PySequence_Length(obj); + if (len != 3 && len != 1) return NULL; // not the right length + + return obj; + } + + /// Convert from a Python object to a Coord. + static void construct(PyObject* obj, + py::converter::rvalue_from_python_stage1_data* data) + { + // Construct a Coord in the provided memory location. + typedef py::converter::rvalue_from_python_storage + StorageT; + void* storage = reinterpret_cast(data)->storage.bytes; + new (storage) openvdb::Coord; // placement new + data->convertible = storage; + + openvdb::Coord* xyz = static_cast(storage); + + // Populate the Coord. + switch (PySequence_Length(obj)) { + case 1: + xyz->reset(pyutil::getSequenceItem(obj, 0)); + break; + case 3: + xyz->reset( + pyutil::getSequenceItem(obj, 0), + pyutil::getSequenceItem(obj, 1), + pyutil::getSequenceItem(obj, 2)); + break; + default: + PyErr_Format(PyExc_ValueError, + "expected a sequence of three integers"); + py::throw_error_already_set(); + break; + } + } + + /// Register both the Coord-to-tuple and the sequence-to-Coord converters. + static void registerConverter() + { + py::to_python_converter(); + py::converter::registry::push_back( + &CoordConverter::convertible, + &CoordConverter::construct, + py::type_id()); + } +}; // struct CoordConverter + +/// @todo CoordBBoxConverter? + + +//////////////////////////////////////// + + +/// Helper class to convert between a Python numeric sequence +/// (tuple, list, etc.) and an openvdb::Vec +template +struct VecConverter +{ + static PyObject* convert(const VecT& v) + { + py::object obj; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + switch (VecT::size) { // compile-time constant + case 2: obj = py::make_tuple(v[0], v[1]); break; + case 3: obj = py::make_tuple(v[0], v[1], v[2]); break; + case 4: obj = py::make_tuple(v[0], v[1], v[2], v[3]); break; + default: + { + py::list lst; + for (int n = 0; n < VecT::size; ++n) lst.append(v[n]); + obj = lst; + } + } + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + Py_INCREF(obj.ptr()); + return obj.ptr(); + } + + static void* convertible(PyObject* obj) + { + if (!PySequence_Check(obj)) return NULL; // not a Python sequence + + Py_ssize_t len = PySequence_Length(obj); + if (len != VecT::size) return NULL; + + // Check that all elements of the Python sequence are convertible + // to the Vec's value type. + py::object seq = pyutil::pyBorrow(obj); + for (int i = 0; i < VecT::size; ++i) { + if (!py::extract(seq[i]).check()) { + return NULL; + } + } + return obj; + } + + static void construct(PyObject* obj, + py::converter::rvalue_from_python_stage1_data* data) + { + // Construct a Vec in the provided memory location. + typedef py::converter::rvalue_from_python_storage StorageT; + void* storage = reinterpret_cast(data)->storage.bytes; + new (storage) VecT; // placement new + data->convertible = storage; + VecT* v = static_cast(storage); + + // Populate the vector. + for (int n = 0; n < VecT::size; ++n) { + (*v)[n] = pyutil::getSequenceItem(obj, n); + } + } + + static void registerConverter() + { + py::to_python_converter >(); + py::converter::registry::push_back( + &VecConverter::convertible, + &VecConverter::construct, + py::type_id()); + } +}; // struct VecConverter + + +//////////////////////////////////////// + + +/// Helper class to convert between a Python dict and an openvdb::MetaMap +/// @todo Consider implementing a separate, templated converter for +/// the various Metadata types. +struct MetaMapConverter +{ + static PyObject* convert(const MetaMap& metaMap) + { + py::dict ret; + for (MetaMap::ConstMetaIterator it = metaMap.beginMeta(); + it != metaMap.endMeta(); ++it) + { + if (Metadata::Ptr meta = it->second) { + py::object obj(meta); + const std::string typeName = meta->typeName(); + if (typeName == StringMetadata::staticTypeName()) { + obj = py::str(static_cast(*meta).value()); + } else if (typeName == DoubleMetadata::staticTypeName()) { + obj = py::object(static_cast(*meta).value()); + } else if (typeName == FloatMetadata::staticTypeName()) { + obj = py::object(static_cast(*meta).value()); + } else if (typeName == Int32Metadata::staticTypeName()) { + obj = py::object(static_cast(*meta).value()); + } else if (typeName == Int64Metadata::staticTypeName()) { + obj = py::object(static_cast(*meta).value()); + } else if (typeName == BoolMetadata::staticTypeName()) { + obj = py::object(static_cast(*meta).value()); + } else if (typeName == Vec2DMetadata::staticTypeName()) { + const Vec2d v = static_cast(*meta).value(); + obj = py::make_tuple(v[0], v[1]); + } else if (typeName == Vec2IMetadata::staticTypeName()) { + const Vec2i v = static_cast(*meta).value(); + obj = py::make_tuple(v[0], v[1]); + } else if (typeName == Vec2SMetadata::staticTypeName()) { + const Vec2s v = static_cast(*meta).value(); + obj = py::make_tuple(v[0], v[1]); + } else if (typeName == Vec3DMetadata::staticTypeName()) { + const Vec3d v = static_cast(*meta).value(); + obj = py::make_tuple(v[0], v[1], v[2]); + } else if (typeName == Vec3IMetadata::staticTypeName()) { + const Vec3i v = static_cast(*meta).value(); + obj = py::make_tuple(v[0], v[1], v[2]); + } else if (typeName == Vec3SMetadata::staticTypeName()) { + const Vec3s v = static_cast(*meta).value(); + obj = py::make_tuple(v[0], v[1], v[2]); + } + ret[it->first] = obj; + } + } + Py_INCREF(ret.ptr()); + return ret.ptr(); + } + + static void* convertible(PyObject* obj) + { + return (PyMapping_Check(obj) ? obj : NULL); + } + + static void construct(PyObject* obj, + py::converter::rvalue_from_python_stage1_data* data) + { + // Construct a MetaMap in the provided memory location. + typedef py::converter::rvalue_from_python_storage StorageT; + void* storage = reinterpret_cast(data)->storage.bytes; + new (storage) MetaMap; // placement new + data->convertible = storage; + MetaMap* metaMap = static_cast(storage); + + // Populate the map. + py::dict pyDict(pyutil::pyBorrow(obj)); + py::list keys = pyDict.keys(); + for (size_t i = 0, N = py::len(keys); i < N; ++i) { + std::string name; + py::object key = keys[i]; + if (py::extract(key).check()) { + name = py::extract(key); + } else { + const std::string + keyAsStr = py::extract(key.attr("__str__")()), + keyType = pyutil::className(key); + PyErr_Format(PyExc_TypeError, + "expected string as metadata name, found object" + " \"%s\" of type %s", keyAsStr.c_str(), keyType.c_str()); + py::throw_error_already_set(); + } + + // Note: the order of the following tests is significant, as it + // avoids unnecessary type promotion (e.g., of ints to floats). + py::object val = pyDict[keys[i]]; + Metadata::Ptr value; + if (py::extract(val).check()) { + value.reset( + new StringMetadata(py::extract(val))); + } else if (PyInt_Check(val.ptr()) + && PyInt_AsLong(val.ptr()) <= std::numeric_limits::max() + && PyInt_AsLong(val.ptr()) >= std::numeric_limits::min()) + { + value.reset(new Int32Metadata(py::extract(val))); + } else if (PyInt_Check(val.ptr()) || PyLong_Check(val.ptr())) { + value.reset(new Int64Metadata(py::extract(val))); + //} else if (py::extract(val).check()) { + // value.reset(new FloatMetadata(py::extract(val))); + } else if (py::extract(val).check()) { + value.reset(new DoubleMetadata(py::extract(val))); + } else if (py::extract(val).check()) { + value.reset(new Vec2IMetadata(py::extract(val))); + } else if (py::extract(val).check()) { + value.reset(new Vec2DMetadata(py::extract(val))); + } else if (py::extract(val).check()) { + value.reset(new Vec2SMetadata(py::extract(val))); + } else if (py::extract(val).check()) { + value.reset(new Vec3IMetadata(py::extract(val))); + } else if (py::extract(val).check()) { + value.reset(new Vec3DMetadata(py::extract(val))); + } else if (py::extract(val).check()) { + value.reset(new Vec3SMetadata(py::extract(val))); + } else if (py::extract(val).check()) { + value = py::extract(val); + } else { + const std::string + valAsStr = py::extract(val.attr("__str__")()), + valType = pyutil::className(val); + PyErr_Format(PyExc_TypeError, + "metadata value \"%s\" of type %s is not allowed", + valAsStr.c_str(), valType.c_str()); + py::throw_error_already_set(); + } + if (value) metaMap->insertMeta(name, *value); + } + } + + static void registerConverter() + { + py::to_python_converter(); + py::converter::registry::push_back( + &MetaMapConverter::convertible, + &MetaMapConverter::construct, + py::type_id()); + } +}; // struct MetaMapConverter + + +//////////////////////////////////////// + + +template void translateException(const T&) {} + +/// @brief Define a function that translates an OpenVDB exception into +/// the equivalent Python exception. +/// @details openvdb::Exception::what() typically returns a string of the form +/// ": ". To avoid duplication of the exception name in Python +/// stack traces, the function strips off the ": " prefix. To do that, +/// it needs the class name in the form of a string, hence the preprocessor macro. +#define PYOPENVDB_CATCH(_openvdbname, _pyname) \ + template<> \ + void translateException<_openvdbname>(const _openvdbname& e) \ + { \ + const char* name = #_openvdbname; \ + if (const char* c = std::strrchr(name, ':')) name = c + 1; \ + const int namelen = std::strlen(name); \ + const char* msg = e.what(); \ + if (0 == std::strncmp(msg, name, namelen)) msg += namelen; \ + if (0 == std::strncmp(msg, ": ", 2)) msg += 2; \ + PyErr_SetString(_pyname, msg); \ + } + + +/// Define an overloaded function that translate all OpenVDB exceptions into +/// their Python equivalents. +/// @todo IllegalValueException and LookupError are redundant and should someday be removed. +PYOPENVDB_CATCH(openvdb::ArithmeticError, PyExc_ArithmeticError) +PYOPENVDB_CATCH(openvdb::IllegalValueException, PyExc_ValueError) +PYOPENVDB_CATCH(openvdb::IndexError, PyExc_IndexError) +PYOPENVDB_CATCH(openvdb::IoError, PyExc_IOError) +PYOPENVDB_CATCH(openvdb::KeyError, PyExc_KeyError) +PYOPENVDB_CATCH(openvdb::LookupError, PyExc_LookupError) +PYOPENVDB_CATCH(openvdb::NotImplementedError, PyExc_NotImplementedError) +PYOPENVDB_CATCH(openvdb::ReferenceError, PyExc_ReferenceError) +PYOPENVDB_CATCH(openvdb::RuntimeError, PyExc_RuntimeError) +PYOPENVDB_CATCH(openvdb::TypeError, PyExc_TypeError) +PYOPENVDB_CATCH(openvdb::ValueError, PyExc_ValueError) + +#undef PYOPENVDB_CATCH + + +//////////////////////////////////////// + + +py::object +readFromFile(const std::string& filename, const std::string& gridName) +{ + io::File vdbFile(filename); + vdbFile.open(); + + if (!vdbFile.hasGrid(gridName)) { + PyErr_Format(PyExc_KeyError, + "file %s has no grid named \"%s\"", + filename.c_str(), gridName.c_str()); + py::throw_error_already_set(); + } + + return pyGrid::getGridFromGridBase(vdbFile.readGrid(gridName)); +} + + +py::tuple +readAllFromFile(const std::string& filename) +{ + io::File vdbFile(filename); + vdbFile.open(); + + GridPtrVecPtr grids = vdbFile.getGrids(); + MetaMap::Ptr metadata = vdbFile.getMetadata(); + vdbFile.close(); + + py::list gridList; + for (GridPtrVec::const_iterator it = grids->begin(); it != grids->end(); ++it) { + gridList.append(pyGrid::getGridFromGridBase(*it)); + } + + return py::make_tuple(gridList, py::dict(*metadata)); +} + + +py::dict +readFileMetadata(const std::string& filename) +{ + io::File vdbFile(filename); + vdbFile.open(); + + MetaMap::Ptr metadata = vdbFile.getMetadata(); + vdbFile.close(); + + return py::dict(*metadata); +} + + +py::object +readGridMetadataFromFile(const std::string& filename, const std::string& gridName) +{ + io::File vdbFile(filename); + vdbFile.open(); + + if (!vdbFile.hasGrid(gridName)) { + PyErr_Format(PyExc_KeyError, + "file %s has no grid named \"%s\"", + filename.c_str(), gridName.c_str()); + py::throw_error_already_set(); + } + + return pyGrid::getGridFromGridBase(vdbFile.readGridMetadata(gridName)); +} + + +py::list +readAllGridMetadataFromFile(const std::string& filename) +{ + io::File vdbFile(filename); + vdbFile.open(); + GridPtrVecPtr grids = vdbFile.readAllGridMetadata(); + vdbFile.close(); + + py::list gridList; + for (GridPtrVec::const_iterator it = grids->begin(); it != grids->end(); ++it) { + gridList.append(pyGrid::getGridFromGridBase(*it)); + } + return gridList; +} + + +void +writeToFile(const std::string& filename, py::object gridOrSeqObj, py::object dictObj) +{ + GridPtrVec gridVec; + try { + GridBase::Ptr base = pyopenvdb::getGridFromPyObject(gridOrSeqObj); + gridVec.push_back(base); + } catch (openvdb::TypeError&) { + for (py::stl_input_iterator it(gridOrSeqObj), end; it != end; ++it) { + if (GridBase::Ptr base = pyGrid::getGridBaseFromGrid(*it)) { + gridVec.push_back(base); + } + } + } + + io::File vdbFile(filename); + if (dictObj.is_none()) { + vdbFile.write(gridVec); + } else { + MetaMap metadata = py::extract(dictObj); + vdbFile.write(gridVec, metadata); + } + vdbFile.close(); +} + + +//////////////////////////////////////// + + +// Descriptor for the openvdb::GridClass enum (for use with pyutil::StringEnum) +struct GridClassDescr +{ + static const char* name() { return "GridClass"; } + static const char* doc() + { + return "Classes of volumetric data (level set, fog volume, etc.)"; + } + static pyutil::CStringPair item(int i) + { + static const int sCount = 4; + static const char* const sStrings[sCount][2] = { + { "UNKNOWN", strdup(GridBase::gridClassToString(GRID_UNKNOWN).c_str()) }, + { "LEVEL_SET", strdup(GridBase::gridClassToString(GRID_LEVEL_SET).c_str()) }, + { "FOG_VOLUME", strdup(GridBase::gridClassToString(GRID_FOG_VOLUME).c_str()) }, + { "STAGGERED", strdup(GridBase::gridClassToString(GRID_STAGGERED).c_str()) } + }; + if (i >= 0 && i < sCount) return pyutil::CStringPair(&sStrings[i][0], &sStrings[i][1]); + return pyutil::CStringPair(static_cast(NULL), static_cast(NULL)); + } +}; + + +// Descriptor for the openvdb::VecType enum (for use with pyutil::StringEnum) +struct VecTypeDescr +{ + static const char* name() { return "VectorType"; } + static const char* doc() + { + return + "The type of a vector determines how transforms are applied to it.\n" + "- INVARIANT:\n" + " does not transform (e.g., tuple, uvw, color)\n" + "- COVARIANT:\n" + " apply inverse-transpose transformation with w = 0\n" + " and ignore translation (e.g., gradient/normal)\n" + "- COVARIANT_NORMALIZE:\n" + " apply inverse-transpose transformation with w = 0\n" + " and ignore translation, vectors are renormalized\n" + " (e.g., unit normal)\n" + "- CONTRAVARIANT_RELATIVE:\n" + " apply \"regular\" transformation with w = 0 and ignore\n" + " translation (e.g., displacement, velocity, acceleration)\n" + "- CONTRAVARIANT_ABSOLUTE:\n" + " apply \"regular\" transformation with w = 1 so that\n" + " vector translates (e.g., position)"; + } + static pyutil::CStringPair item(int i) + { + static const int sCount = 5; + static const char* const sStrings[sCount][2] = { + { "INVARIANT", strdup(GridBase::vecTypeToString(openvdb::VEC_INVARIANT).c_str()) }, + { "COVARIANT", strdup(GridBase::vecTypeToString(openvdb::VEC_COVARIANT).c_str()) }, + { "COVARIANT_NORMALIZE", + strdup(GridBase::vecTypeToString(openvdb::VEC_COVARIANT_NORMALIZE).c_str()) }, + { "CONTRAVARIANT_RELATIVE", + strdup(GridBase::vecTypeToString(openvdb::VEC_CONTRAVARIANT_RELATIVE).c_str()) }, + { "CONTRAVARIANT_ABSOLUTE", + strdup(GridBase::vecTypeToString(openvdb::VEC_CONTRAVARIANT_ABSOLUTE).c_str()) } + }; + if (i >= 0 && i < sCount) return std::make_pair(&sStrings[i][0], &sStrings[i][1]); + return pyutil::CStringPair(static_cast(NULL), static_cast(NULL)); + } +}; + +} // namespace _openvdbmodule + + +//////////////////////////////////////// + + +#ifdef DWA_OPENVDB +#define PY_OPENVDB_MODULE_NAME _openvdb +#else +#define PY_OPENVDB_MODULE_NAME pyopenvdb +#endif + +BOOST_PYTHON_MODULE(PY_OPENVDB_MODULE_NAME) +{ + // Don't auto-generate ugly, C++-style function signatures. + py::docstring_options docOptions; + docOptions.disable_signatures(); + docOptions.enable_user_defined(); + + using namespace openvdb::OPENVDB_VERSION_NAME; + + // Initialize OpenVDB. + initialize(); + + _openvdbmodule::CoordConverter::registerConverter(); + + _openvdbmodule::VecConverter::registerConverter(); + _openvdbmodule::VecConverter::registerConverter(); + _openvdbmodule::VecConverter::registerConverter(); + _openvdbmodule::VecConverter::registerConverter(); + + _openvdbmodule::VecConverter::registerConverter(); + _openvdbmodule::VecConverter::registerConverter(); + _openvdbmodule::VecConverter::registerConverter(); + _openvdbmodule::VecConverter::registerConverter(); + + _openvdbmodule::VecConverter::registerConverter(); + _openvdbmodule::VecConverter::registerConverter(); + _openvdbmodule::VecConverter::registerConverter(); + _openvdbmodule::VecConverter::registerConverter(); + + _openvdbmodule::MetaMapConverter::registerConverter(); + +#define PYOPENVDB_TRANSLATE_EXCEPTION(_classname) \ + py::register_exception_translator<_classname>(&_openvdbmodule::translateException<_classname>) + + PYOPENVDB_TRANSLATE_EXCEPTION(ArithmeticError); + PYOPENVDB_TRANSLATE_EXCEPTION(IllegalValueException); + PYOPENVDB_TRANSLATE_EXCEPTION(IndexError); + PYOPENVDB_TRANSLATE_EXCEPTION(IoError); + PYOPENVDB_TRANSLATE_EXCEPTION(KeyError); + PYOPENVDB_TRANSLATE_EXCEPTION(LookupError); + PYOPENVDB_TRANSLATE_EXCEPTION(NotImplementedError); + PYOPENVDB_TRANSLATE_EXCEPTION(ReferenceError); + PYOPENVDB_TRANSLATE_EXCEPTION(RuntimeError); + PYOPENVDB_TRANSLATE_EXCEPTION(TypeError); + PYOPENVDB_TRANSLATE_EXCEPTION(ValueError); + +#undef PYOPENVDB_TRANSLATE_EXCEPTION + + + // Export the python bindings. + exportTransform(); + exportMetadata(); + exportFloatGrid(); + exportIntGrid(); + exportVec3Grid(); + + + py::def("read", + &_openvdbmodule::readFromFile, + (py::arg("filename"), py::arg("gridname")), + "read(filename, gridname) -> Grid\n\n" + "Read a single grid from a .vdb file."); + + py::def("readAll", + &_openvdbmodule::readAllFromFile, + py::arg("filename"), + "readAll(filename) -> list, dict\n\n" + "Read a .vdb file and return a list of grids and\n" + "a dict of file-level metadata."); + + py::def("readMetadata", + &_openvdbmodule::readFileMetadata, + py::arg("filename"), + "readMetadata(filename) -> dict\n\n" + "Read file-level metadata from a .vdb file."); + + py::def("readGridMetadata", + &_openvdbmodule::readGridMetadataFromFile, + (py::arg("filename"), py::arg("gridname")), + "readGridMetadata(filename, gridname) -> Grid\n\n" + "Read a single grid's metadata and transform (but not its tree)\n" + "from a .vdb file."); + + py::def("readAllGridMetadata", + &_openvdbmodule::readAllGridMetadataFromFile, + py::arg("filename"), + "readAllGridMetadata(filename) -> list\n\n" + "Read a .vdb file and return a list of grids populated with\n" + "their metadata and transforms, but not their trees."); + + py::def("write", + &_openvdbmodule::writeToFile, + (py::arg("filename"), py::arg("grids"), py::arg("metadata") = py::object()), + "write(filename, grids, metadata=None)\n\n" + "Write a grid or a sequence of grids and, optionally, a dict\n" + "of (name, value) metadata pairs to a .vdb file."); + + // Add some useful module-level constants. + py::scope().attr("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-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/python/pyTransform.cc b/openvdb_2_3_0_library/openvdb/python/pyTransform.cc new file mode 100755 index 0000000..b5ffcf5 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/python/pyTransform.cc @@ -0,0 +1,321 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include "openvdb/openvdb.h" +#include "pyutil.h" + +namespace py = boost::python; +using namespace openvdb::OPENVDB_VERSION_NAME; + +namespace pyTransform { + +inline void scale1(math::Transform& t, double s) { t.preScale(s); } +inline void scale3(math::Transform& t, const Vec3d& xyz) { t.preScale(xyz); } + +inline Vec3d voxelDim0(math::Transform& t) { return t.voxelSize(); } +inline Vec3d voxelDim1(math::Transform& t, const Vec3d& p) { return t.voxelSize(p); } + +inline double voxelVolume0(math::Transform& t) { return t.voxelVolume(); } +inline double voxelVolume1(math::Transform& t, const Vec3d& p) { return t.voxelVolume(p); } + +inline Vec3d indexToWorld(math::Transform& t, const Vec3d& p) { return t.indexToWorld(p); } +inline Vec3d worldToIndex(math::Transform& t, const Vec3d& p) { return t.worldToIndex(p); } + + +inline std::string +info(math::Transform& t) +{ + std::ostringstream ostr; + t.print(ostr); + return ostr.str(); +} + + +inline math::Transform::Ptr +createLinearFromDim(double dim) +{ + return math::Transform::createLinearTransform(dim); +} + + +inline math::Transform::Ptr +createLinearFromMat(py::object obj) +{ + Mat4R m; + + // Verify that obj is a four-element sequence. + bool is4x4Seq = (PySequence_Check(obj.ptr()) && PySequence_Length(obj.ptr()) == 4); + if (is4x4Seq) { + for (size_t row = 0; is4x4Seq && row < 4; ++row) { + // Verify that each element of obj is itself a four-element sequence. + py::object rowObj = obj[row]; + if (PySequence_Check(rowObj.ptr()) && PySequence_Length(rowObj.ptr()) == 4) { + // Extract four numeric values from this row of the sequence. + for (size_t col = 0; is4x4Seq && col < 4; ++col) { + if (py::extract(rowObj[col]).check()) { + m[row][col] = py::extract(rowObj[col]); + } else { + is4x4Seq = false; + } + } + } else { + is4x4Seq = false; + } + } + } + if (!is4x4Seq) { + PyErr_Format(PyExc_ValueError, "expected a 4 x 4 sequence of numeric values"); + py::throw_error_already_set(); + } + + return math::Transform::createLinearTransform(m); +} + + +inline math::Transform::Ptr +createFrustum(const Coord& xyzMin, const Coord& xyzMax, + double taper, double depth, double voxelDim = 1.0) +{ + return math::Transform::createFrustumTransform( + BBoxd(xyzMin.asVec3d(), xyzMax.asVec3d()), taper, depth, voxelDim); +} + + +//////////////////////////////////////// + + +struct PickleSuite: py::pickle_suite +{ + enum { STATE_DICT = 0, STATE_MAJOR, STATE_MINOR, STATE_FORMAT, STATE_XFORM }; + + /// Return @c true, indicating that this pickler preserves a Transform's __dict__. + static bool getstate_manages_dict() { return true; } + + /// Return a tuple representing the state of the given Transform. + static py::tuple getstate(py::object xformObj) + { + py::tuple state; + py::extract x(xformObj); + if (x.check()) { + // Extract a Transform from the Python object. + math::Transform xform = x(); + std::ostringstream ostr(std::ios_base::binary); + // Serialize the Transform to a string. + xform.write(ostr); + + // Construct a state tuple comprising the Python object's __dict__, + // the version numbers of the serialization format, + // and the serialized Transform. + state = py::make_tuple( + xformObj.attr("__dict__"), + uint32_t(OPENVDB_LIBRARY_MAJOR_VERSION), + uint32_t(OPENVDB_LIBRARY_MINOR_VERSION), + uint32_t(OPENVDB_FILE_VERSION), + ostr.str()); + } + return state; + } + + /// Restore the given Transform to a saved state. + static void setstate(py::object xformObj, py::object stateObj) + { + math::Transform::Ptr xform; + { + py::extract x(xformObj); + if (x.check()) xform = x(); + else return; + } + + py::tuple state; + { + py::extract x(stateObj); + if (x.check()) state = x(); + } + bool badState = (py::len(state) != 5); + + if (!badState) { + // Restore the object's __dict__. + py::extract x(state[int(STATE_DICT)]); + if (x.check()) { + py::dict d = py::extract(xformObj.attr("__dict__"))(); + d.update(x()); + } else { + badState = true; + } + } + + openvdb::VersionId libVersion; + uint32_t formatVersion = 0; + if (!badState) { + // Extract the serialization format version numbers. + const int idx[3] = { STATE_MAJOR, STATE_MINOR, STATE_FORMAT }; + uint32_t version[3] = { 0, 0, 0 }; + for (int i = 0; i < 3 && !badState; ++i) { + py::extract x(state[idx[i]]); + if (x.check()) version[i] = x(); + else badState = true; + } + libVersion.first = version[0]; + libVersion.second = version[1]; + formatVersion = version[2]; + } + + std::string serialized; + if (!badState) { + // Extract the string containing the serialized Transform. + py::extract x(state[int(STATE_XFORM)]); + if (x.check()) serialized = x(); + else badState = true; + } + + if (badState) { + PyErr_SetObject(PyExc_ValueError, + ("expected (dict, int, int, int, str) tuple in call to __setstate__; found %s" + % stateObj.attr("__repr__")()).ptr()); + py::throw_error_already_set(); + } + + // Restore the internal state of the C++ object. + std::istringstream istr(serialized, std::ios_base::binary); + io::setVersion(istr, libVersion, formatVersion); + xform->read(istr); + } +}; // struct PickleSuite + +} // namespace pyTransform + + +void +exportTransform() +{ + py::enum_("Axis") + .value("X", math::X_AXIS) + .value("Y", math::Y_AXIS) + .value("Z", math::Z_AXIS); + + py::class_("Transform", py::init<>()) + + .def("deepCopy", &math::Transform::copy, + "deepCopy() -> Transform\n\n" + "Return a copy of this transform.") + + /// @todo Should this also be __str__()? + .def("info", &pyTransform::info, + "info() -> str\n\n" + "Return a string containing a description of this transform.\n") + + .def_pickle(pyTransform::PickleSuite()) + + .add_property("typeName", &math::Transform::mapType, + "name of this transform's type") + .add_property("isLinear", &math::Transform::isLinear, + "True if this transform is linear") + + .def("rotate", &math::Transform::preRotate, + (py::arg("radians"), py::arg("axis") = math::X_AXIS), + "rotate(radians, axis)\n\n" + "Accumulate a rotation about either Axis.X, Axis.Y or Axis.Z.") + .def("translate", &math::Transform::postTranslate, py::arg("xyz"), + "translate((x, y, z))\n\n" + "Accumulate a translation.") + .def("scale", &pyTransform::scale1, py::arg("s"), + "scale(s)\n\n" + "Accumulate a uniform scale.") + .def("scale", &pyTransform::scale3, py::arg("sxyz"), + "scale((sx, sy, sz))\n\n" + "Accumulate a nonuniform scale.") + .def("shear", &math::Transform::preShear, + (py::arg("s"), py::arg("axis0"), py::arg("axis1")), + "shear(s, axis0, axis1)\n\n" + "Accumulate a shear (axis0 and axis1 are either\n" + "Axis.X, Axis.Y or Axis.Z).") + + .def("voxelSize", &pyTransform::voxelDim0, + "voxelSize() -> (dx, dy, dz)\n\n" + "Return the size of voxels of the linear component of this transform.") + .def("voxelSize", &pyTransform::voxelDim1, py::arg("xyz"), + "voxelSize((x, y, z)) -> (dx, dy, dz)\n\n" + "Return the size of the voxel at position (x, y, z).") + + .def("voxelVolume", &pyTransform::voxelVolume0, + "voxelVolume() -> float\n\n" + "Return the voxel volume of the linear component of this transform.") + .def("voxelVolume", &pyTransform::voxelVolume1, py::arg("xyz"), + "voxelVolume((x, y, z)) -> float\n\n" + "Return the voxel volume at position (x, y, z).") + + .def("indexToWorld", &pyTransform::indexToWorld, py::arg("xyz"), + "indexToWorld((x, y, z)) -> (x', y', z')\n\n" + "Apply this transformation to the given coordinates.") + .def("worldToIndex", &pyTransform::worldToIndex, py::arg("xyz"), + "worldToIndex((x, y, z)) -> (x', y', z')\n\n" + "Apply the inverse of this transformation to the given coordinates.") + .def("worldToIndexCellCentered", &math::Transform::worldToIndexCellCentered, + py::arg("xyz"), + "worldToIndexCellCentered((x, y, z)) -> (i, j, k)\n\n" + "Apply the inverse of this transformation to the given coordinates\n" + "and round the result to the nearest integer coordinates.") + .def("worldToIndexNodeCentered", &math::Transform::worldToIndexNodeCentered, + py::arg("xyz"), + "worldToIndexNodeCentered((x, y, z)) -> (i, j, k)\n\n" + "Apply the inverse of this transformation to the given coordinates\n" + "and round the result down to the nearest integer coordinates.") + + // Allow Transforms to be compared for equality and inequality. + .def(py::self == py::other()) + .def(py::self != py::other()) + ; + + py::def("createLinearTransform", &pyTransform::createLinearFromMat, py::arg("matrix"), + "createLinearTransform(matrix) -> Transform\n\n" + "Create a new linear transform from a 4 x 4 matrix given as a sequence\n" + "of the form [[a, b, c, d], [e, f, g, h], [i, j, k, l], [m, n, o, p]],\n" + "where [m, n, o, p] is the translation component."); + + py::def("createLinearTransform", &pyTransform::createLinearFromDim, + (py::arg("voxelSize") = 1.0), + "createLinearTransform(voxelSize) -> Transform\n\n" + "Create a new linear transform with the given uniform voxel size."); + + py::def("createFrustumTransform", &pyTransform::createFrustum, + (py::arg("xyzMin"), py::arg("xyzMax"), + py::arg("taper"), py::arg("depth"), py::arg("voxelSize") = 1.0), + "createFrustumTransform(xyzMin, xyzMax, taper, depth, voxelSize) -> Transform\n\n" + "Create a new frustum transform with unit bounding box (xyzMin, xyzMax)\n" + "and the given taper, depth and uniform voxel size."); + + // allows Transform::Ptr Grid::getTransform() to work + py::register_ptr_to_python(); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/python/pyVec3Grid.cc b/openvdb_2_3_0_library/openvdb/python/pyVec3Grid.cc new file mode 100755 index 0000000..c73104e --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/python/pyVec3Grid.cc @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file pyVec3Grid.cc +/// @brief Boost.Python wrappers for vector-valued openvdb::Grid types + +#include "pyGrid.h" + + +void +exportVec3Grid() +{ + pyGrid::exportGrid(); +#ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES + pyGrid::exportGrid(); + pyGrid::exportGrid(); +#endif +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/python/pyopenvdb.h b/openvdb_2_3_0_library/openvdb/python/pyopenvdb.h new file mode 100755 index 0000000..6857ec1 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/python/pyopenvdb.h @@ -0,0 +1,113 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file pyopenvdb.h +/// +/// @brief Glue functions for access to pyOpenVDB objects from C++ code +/// @details Use these functions in your own Python function implementations +/// to extract an OpenVDB grid from or wrap a grid in a @c PyObject. +/// For example (using Boost.Python), +/// @code +/// #include +/// #include +/// #include +/// +/// // Implementation of a Python function that processes pyOpenVDB grids +/// boost::python::object +/// processGrid(boost::python::object inObj) +/// { +/// boost::python::object outObj; +/// try { +/// // Extract an OpenVDB grid from the input argument. +/// if (openvdb::GridBase::Ptr grid = +/// pyopenvdb::getGridFromPyObject(inObj)) +/// { +/// grid = grid->deepCopyGrid(); +/// +/// // Process the grid... +/// +/// // Wrap the processed grid in a PyObject. +/// outObj = pyopenvdb::getPyObjectFromGrid(grid); +/// } +/// } catch (openvdb::TypeError& e) { +/// PyErr_Format(PyExc_TypeError, e.what()); +/// boost::python::throw_error_already_set(); +/// } +/// return outObj; +/// } +/// +/// BOOST_PYTHON_MODULE(mymodule) +/// { +/// openvdb::initialize(); +/// +/// // Definition of a Python function that processes pyOpenVDB grids +/// boost::python::def(/*name=*/"processGrid", &processGrid, /*argname=*/"grid"); +/// } +/// @endcode +/// Then, from Python, +/// @code +/// import openvdb +/// import mymodule +/// +/// grid = openvdb.read('myGrid.vdb', 'MyGrid') +/// grid = mymodule.processGrid(grid) +/// openvdb.write('myProcessedGrid.vdb', [grid]) +/// @endcode + +#ifndef PYOPENVDB_HAS_BEEN_INCLUDED +#define PYOPENVDB_HAS_BEEN_INCLUDED + +#include +#include "openvdb/Grid.h" + + +namespace pyopenvdb { + +//@{ +/// @brief Return a pointer to the OpenVDB grid held by the given Python object. +/// @throw openvdb::TypeError if the Python object is not one of the pyOpenVDB grid types. +/// (See the Python module's GridTypes global variable for the list of supported grid types.) +openvdb::GridBase::Ptr getGridFromPyObject(PyObject*); +openvdb::GridBase::Ptr getGridFromPyObject(const boost::python::object&); +//@} + +/// @brief Return a new Python object that holds the given OpenVDB grid. +/// @return @c None if the given grid pointer is null. +/// @throw openvdb::TypeError if the grid is not of a supported type. +/// (See the Python module's GridTypes global variable for the list of supported grid types.) +boost::python::object getPyObjectFromGrid(const openvdb::GridBase::Ptr&); + +} // namespace pyopenvdb + +#endif // PYOPENVDB_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/python/pyutil.h b/openvdb_2_3_0_library/openvdb/python/pyutil.h new file mode 100755 index 0000000..32e3764 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/python/pyutil.h @@ -0,0 +1,284 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_PYUTIL_HAS_BEEN_INCLUDED +#define OPENVDB_PYUTIL_HAS_BEEN_INCLUDED + +#include "openvdb/openvdb.h" +#include +#include +#include // for std::pair +#include +#include + + +namespace pyutil { + +/// Return a new @c boost::python::object that borrows (i.e., doesn't +/// take over ownership of) the given @c PyObject's reference. +inline boost::python::object +pyBorrow(PyObject* obj) +{ + return boost::python::object(boost::python::handle<>(boost::python::borrowed(obj))); +} + + +/// @brief Given a @c PyObject that implements the sequence protocol +/// (e.g., a @c PyListObject), return the value of type @c ValueT +/// at index @a idx in the sequence. +/// @details Raise a Python @c TypeError exception if the value +/// at index @a idx is not convertible to type @c ValueT. +template +inline ValueT +getSequenceItem(PyObject* obj, int idx) +{ + return boost::python::extract(pyBorrow(obj)[idx]); +} + + +//////////////////////////////////////// + + +template +struct GridTraitsBase +{ + /// @brief Return the name of the Python class that wraps this grid type + /// (e.g., "FloatGrid" for openvdb::FloatGrid). + /// + /// @note This name is not the same as GridType::type(). + /// The latter returns a name like "Tree_float_5_4_3". + static const char* name(); + + /// Return the name of this grid type's value type ("bool", "float", "vec3s", etc.). + static const char* valueTypeName() + { + return openvdb::typeNameAsString(); + } + + /// @brief Return a description of this grid type. + /// + /// @note This name is generated at runtime for each call to descr(). + static const std::string descr() + { + return std::string("OpenVDB grid with voxels of type ") + valueTypeName(); + } +}; // struct GridTraitsBase + + +template +struct GridTraits: public GridTraitsBase +{ +}; + +/// Map a grid type to a traits class that derives from GridTraitsBase +/// and that defines a name() method. +#define GRID_TRAITS(_typ, _name) \ + template<> struct GridTraits<_typ>: public GridTraitsBase<_typ> { \ + static const char* name() { return _name; } \ + } + +GRID_TRAITS(openvdb::FloatGrid, "FloatGrid"); +GRID_TRAITS(openvdb::Vec3SGrid, "Vec3SGrid"); +GRID_TRAITS(openvdb::BoolGrid, "BoolGrid"); +#ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES +GRID_TRAITS(openvdb::DoubleGrid, "DoubleGrid"); +GRID_TRAITS(openvdb::Int32Grid, "Int32Grid"); +GRID_TRAITS(openvdb::Int64Grid, "Int64Grid"); +GRID_TRAITS(openvdb::Vec3IGrid, "Vec3IGrid"); +GRID_TRAITS(openvdb::Vec3DGrid, "Vec3DGrid"); +#endif + +#undef GRID_TRAITS + + +//////////////////////////////////////// + + +// Note that the elements are pointers to C strings (char**), because +// boost::python::class_::def_readonly() requires a pointer to a static member. +typedef std::pair CStringPair; + + +/// @brief Enum-like mapping from string keys to string values, with characteristics +/// of both (Python) classes and class instances (as well as NamedTuples) +/// @details +/// - (@e key, @e value) pairs can be accessed as class attributes (\"MyClass.MY_KEY\") +/// - (@e key, @e value) pairs can be accessed via dict lookup on instances +/// (\"MyClass()['MY_KEY']\") +/// - (@e key, @e value) pairs can't be modified or reassigned +/// - instances are iterable (\"for key in MyClass(): ...\") +/// +/// A @c Descr class must implement the following interface: +/// @code +/// struct MyDescr +/// { +/// // Return the Python name for the enum class. +/// static const char* name(); +/// // Return the docstring for the enum class. +/// static const char* doc(); +/// // Return the ith (key, value) pair, in the form of +/// // a pair of *pointers* to C strings +/// static CStringPair item(int i); +/// }; +/// @endcode +template +struct StringEnum +{ + /// Return the (key, value) map as a Python dict. + static boost::python::dict items() + { + static tbb::mutex sMutex; + static boost::python::dict itemDict; + if (!itemDict) { + // The first time this function is called, populate + // the static dict with (key, value) pairs. + tbb::mutex::scoped_lock lock(sMutex); + if (!itemDict) { + for (int i = 0; ; ++i) { + const CStringPair item = Descr::item(i); + OPENVDB_START_THREADSAFE_STATIC_WRITE + if (item.first) { + itemDict[boost::python::str(*item.first)] = + boost::python::str(*item.second); + } + OPENVDB_FINISH_THREADSAFE_STATIC_WRITE + else break; + } + } + } + return itemDict; + } + + /// Return the keys as a Python list of strings. + static boost::python::object keys() { return items().attr("keys")(); } + /// Return the number of keys as a Python int. + boost::python::object numItems() const + { + return boost::python::object(boost::python::len(items())); + } + /// Return the value (as a Python string) for the given key. + boost::python::object getItem(boost::python::object keyObj) const { return items()[keyObj]; } + /// Return a Python iterator over the keys. + boost::python::object iter() const { return items().attr("__iter__")(); } + + /// Register this enum. + static void wrap() + { + boost::python::class_ cls( + /*classname=*/Descr::name(), + /*docstring=*/Descr::doc()); + cls.def("keys", &StringEnum::keys, "keys() -> list") + .staticmethod("keys") + .def("__len__", &StringEnum::numItems, "__len__() -> int") + .def("__iter__", &StringEnum::iter, "__iter__() -> iterator") + .def("__getitem__", &StringEnum::getItem, "__getitem__(str) -> str") + /*end*/; + // Add a read-only, class-level attribute for each (key, value) pair. + for (int i = 0; ; ++i) { + const CStringPair item = Descr::item(i); + if (item.first) cls.def_readonly(*item.first, item.second); + else break; + } + } +}; + + +//////////////////////////////////////// + + +/// @brief From the given Python object, extract a value of type @c T. +/// +/// If the object cannot be converted to type @c T, raise a @c TypeError with a more +/// Pythonic error message (incorporating the provided class and function names, etc.) +/// than the one that would be generated by boost::python::extract(), e.g., +/// "TypeError: expected float, found str as argument 2 to FloatGrid.prune()" instead of +/// "TypeError: No registered converter was able to produce a C++ rvalue of type +/// boost::shared_ptr +inline T +extractArg( + boost::python::object obj, + const char* functionName, + const char* className = NULL, + int argIdx = 0, // args are numbered starting from 1 + const char* expectedType = NULL) +{ + boost::python::extract val(obj); + if (!val.check()) { + // Generate an error string of the form + // "expected , found as argument + // to .()", where and + // are optional. + std::ostringstream os; + os << "expected "; + if (expectedType) os << expectedType; else os << openvdb::typeNameAsString(); + const std::string actualType = + boost::python::extract(obj.attr("__class__").attr("__name__")); + os << ", found " << actualType << " as argument"; + if (argIdx > 0) os << " " << argIdx; + os << " to "; + if (className) os << className << "."; + os << functionName << "()"; + + PyErr_SetString(PyExc_TypeError, os.str().c_str()); + boost::python::throw_error_already_set(); + } + return val(); +} + + +//////////////////////////////////////// + + +/// Return str(val) for the given value. +template +inline std::string +str(const T& val) +{ + return boost::python::extract(boost::python::str(val)); +} + + +/// Return the name of the given Python object's class. +inline std::string +className(boost::python::object obj) +{ + std::string s = boost::python::extract( + obj.attr("__class__").attr("__name__")); + return s; +} + +} // namespace pyutil + +#endif // OPENVDB_PYUTIL_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/python/test/TestOpenVDB.py b/openvdb_2_3_0_library/openvdb/python/test/TestOpenVDB.py new file mode 100755 index 0000000..5f633bc --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/python/test/TestOpenVDB.py @@ -0,0 +1,697 @@ +#!/usr/local/bin/python +# Copyright (c) 2012-2013 DreamWorks Animation LLC +# +# All rights reserved. This software is distributed under the +# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +# +# Redistributions of source code must retain the above copyright +# and license notice and the following restrictions and disclaimer. +# +# * Neither the name of DreamWorks Animation nor the names of +# its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. + +""" +Unit tests for the OpenVDB Python module + +These are intended primarily to test the Python-to-C++ and +C++-to-Python bindings, not the OpenVDB library itself. +""" + +import os, os.path +import sys +import unittest +try: + import pyopenvdb as openvdb +except ImportError: + import studioenv + from studio.ani import Ani + from studio import logging + from studio import openvdb + + +def valueFactory(zeroValue, elemValue): + """ + Return elemValue converted to a value of the same type as zeroValue. + If zeroValue is a sequence, return a sequence of the same type and length, + with each element set to elemValue. + """ + val = zeroValue + typ = type(val) + try: + # If the type is a sequence type, return a sequence of the appropriate length. + size = len(val) + val = typ([elemValue]) * size + except TypeError: + # Return a scalar value of the appropriate type. + val = typ(elemValue) + return val + + +class TestOpenVDB(unittest.TestCase): + + def run(self, result=None, *args, **kwargs): + super(TestOpenVDB, self).run(result, *args, **kwargs) + + def setUp(self): + # Make output files and directories world-writable. + self.umask = os.umask(0) + + def tearDown(self): + os.umask(self.umask) + + + def testModule(self): + # At a minimum, BoolGrid, FloatGrid and Vec3SGrid should exist. + self.assert_(openvdb.BoolGrid in openvdb.GridTypes) + self.assert_(openvdb.FloatGrid in openvdb.GridTypes) + self.assert_(openvdb.Vec3SGrid in openvdb.GridTypes) + + # Verify that it is possible to construct a grid of each supported type. + for cls in openvdb.GridTypes: + grid = cls() + acc = grid.getAccessor() + acc.setValueOn((-1, -2, 3)) + self.assertEqual(grid.activeVoxelCount(), 1) + + + def testTransform(self): + xform1 = openvdb.createLinearTransform( + [[.5, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 2, 0], + [1, 2, 3, 1]]) + self.assert_(xform1.typeName != '') + self.assertEqual(xform1.indexToWorld((1, 1, 1)), (1.5, 3, 5)) + xform2 = xform1 + self.assertEqual(xform2, xform1) + xform2 = xform1.deepCopy() + self.assertEqual(xform2, xform1) + xform2 = openvdb.createFrustumTransform(taper=0.5, depth=100, + xyzMin=(0, 0, 0), xyzMax=(100, 100, 100), voxelSize=0.25) + self.assertNotEqual(xform2, xform1) + worldp = xform2.indexToWorld((10, 10, 10)) + worldp = map(lambda x: int(round(x * 1000000)), worldp) + self.assertEqual(worldp, [-110000, -110000, 2500000]) + + grid = openvdb.FloatGrid() + self.assertEqual(grid.transform, openvdb.createLinearTransform()) + grid.transform = openvdb.createLinearTransform(2.0) + self.assertEqual(grid.transform, openvdb.createLinearTransform(2.0)) + + + def testGridCopy(self): + grid = openvdb.FloatGrid() + self.assert_(grid.sharesWith(grid)) + self.assertFalse(grid.sharesWith([])) # wrong type; Grid expected + + copyOfGrid = grid.copy() + self.assert_(copyOfGrid.sharesWith(grid)) + + deepCopyOfGrid = grid.deepCopy() + self.assertFalse(deepCopyOfGrid.sharesWith(grid)) + self.assertFalse(deepCopyOfGrid.sharesWith(copyOfGrid)) + + + def testGridProperties(self): + expected = { + openvdb.BoolGrid: ('bool', False, True), + openvdb.FloatGrid: ('float', 0.0, 1.0), + openvdb.Vec3SGrid: ('vec3s', (0, 0, 0), (-1, 0, 1)), + } + + for factory in expected: + valType, bg, newbg = expected[factory] + + grid = factory() + + self.assertEqual(grid.valueTypeName, valType) + def setValueType(obj): + obj.valueTypeName = 'double' + # Grid.valueTypeName is read-only, so setting it raises an exception. + self.assertRaises(AttributeError, lambda obj=grid: setValueType(obj)) + + self.assertEqual(grid.background, bg) + grid.background = newbg + self.assertEqual(grid.background, newbg) + + self.assertEqual(grid.name, '') + grid.name = 'test' + self.assertEqual(grid.name, 'test') + + self.assertFalse(grid.saveFloatAsHalf) + grid.saveFloatAsHalf = True + self.assert_(grid.saveFloatAsHalf) + + self.assert_(grid.treeDepth > 2) + + + def testGridMetadata(self): + grid = openvdb.BoolGrid() + + self.assertEqual(grid.metadata, {}) + + meta = { 'name': 'test', 'saveFloatAsHalf': True, 'xyz': (-1, 0, 1) } + grid.metadata = meta + self.assertEqual(grid.metadata, meta) + + meta['xyz'] = (-100, 100, 0) + grid.updateMetadata(meta) + self.assertEqual(grid.metadata, meta) + + self.assertEqual(set(grid.iterkeys()), set(meta.iterkeys())) + + for name in meta: + self.assert_(name in grid) + self.assertEqual(grid[name], meta[name]) + + for name in grid: + self.assert_(name in grid) + self.assertEqual(grid[name], meta[name]) + + self.assert_('xyz' in grid) + del grid['xyz'] + self.assertFalse('xyz' in grid) + grid['xyz'] = meta['xyz'] + self.assert_('xyz' in grid) + + grid.addStatsMetadata() + meta = grid.getStatsMetadata() + self.assertEqual(0, meta["file_voxel_count"]) + + + def testGridFill(self): + grid = openvdb.FloatGrid() + acc = grid.getAccessor() + ijk = (1, 1, 1) + + self.assertRaises(TypeError, lambda: grid.fill("", (7, 7, 7), 1, False)) + self.assertRaises(TypeError, lambda: grid.fill((0, 0, 0), "", 1, False)) + self.assertRaises(TypeError, lambda: grid.fill((0, 0, 0), (7, 7, 7), "", False)) + + self.assertFalse(acc.isValueOn(ijk)) + grid.fill((0, 0, 0), (7, 7, 7), 1, active=False) + self.assertEqual(acc.getValue(ijk), 1) + self.assertFalse(acc.isValueOn(ijk)) + + grid.fill((0, 0, 0), (7, 7, 7), 2, active=True) + self.assertEqual(acc.getValue(ijk), 2) + self.assert_(acc.isValueOn(ijk)) + + activeCount = grid.activeVoxelCount() + acc.setValueOn(ijk, 2.125) + self.assertEqual(grid.activeVoxelCount(), activeCount) + + grid.fill(ijk, ijk, 2.125, active=True) + self.assertEqual(acc.getValue(ijk), 2.125) + self.assert_(acc.isValueOn(ijk)) + self.assertEqual(grid.activeVoxelCount(), activeCount) + leafCount = grid.leafCount() + + grid.prune() + self.assertAlmostEqual(acc.getValue(ijk), 2.125) + self.assert_(acc.isValueOn(ijk)) + self.assertEqual(grid.leafCount(), leafCount) + self.assertEqual(grid.activeVoxelCount(), activeCount) + + grid.prune(tolerance=0.2) + self.assertEqual(grid.activeVoxelCount(), activeCount) + self.assertEqual(acc.getValue(ijk), 2) + self.assert_(acc.isValueOn(ijk)) + self.assert_(grid.leafCount() < leafCount) + + + def testGridIterators(self): + onCoords = set([(-10, -10, -10), (0, 0, 0), (1, 1, 1)]) + + for factory in openvdb.GridTypes: + grid = factory() + acc = grid.getAccessor() + for c in onCoords: + acc.setValueOn(c) + + coords = set(value.min for value in grid.iterOnValues()) + self.assertEqual(coords, onCoords) + + n = 0 + for _ in grid.iterAllValues(): + n += 1 + for _ in grid.iterOffValues(): + n -= 1 + self.assertEqual(n, len(onCoords)) + + grid = factory() + grid.fill((0, 0, 1), (18, 18, 18), grid.oneValue) # make active + activeCount = grid.activeVoxelCount() + + # Iterate over active values (via a const iterator) and verify + # that the cumulative active voxel count matches the grid's. + count = 0 + for value in grid.citerOnValues(): + count += value.count + self.assertEqual(count, activeCount) + + # Via a non-const iterator, turn off every other active value. + # Then verify that the cumulative active voxel count is half the original count. + state = True + for value in grid.iterOnValues(): + count -= value.count + value.active = state + state = not state + self.assertEqual(grid.activeVoxelCount(), activeCount / 2) + + # Verify that writing through a const iterator is not allowed. + value = grid.citerOnValues().next() + self.assertRaises(AttributeError, lambda: setattr(value, 'active', 0)) + self.assertRaises(AttributeError, lambda: setattr(value, 'depth', 0)) + # Verify that some value attributes are immutable, even given a non-const iterator. + value = grid.iterOnValues().next() + self.assertRaises(AttributeError, lambda: setattr(value, 'min', (0, 0, 0))) + self.assertRaises(AttributeError, lambda: setattr(value, 'max', (0, 0, 0))) + self.assertRaises(AttributeError, lambda: setattr(value, 'count', 1)) + + + def testMap(self): + grid = openvdb.BoolGrid() + grid.fill((-4, -4, -4), (5, 5, 5), grid.zeroValue) # make active + grid.mapOn(lambda x: not x) # replace active False values with True + n = sum(item.value for item in grid.iterOnValues()) + self.assertEqual(n, 10 * 10 * 10) + + grid = openvdb.FloatGrid() + grid.fill((-4, -4, -4), (5, 5, 5), grid.oneValue) + grid.mapOn(lambda x: x * 2) + n = sum(item.value for item in grid.iterOnValues()) + self.assertEqual(n, 10 * 10 * 10 * 2) + + grid = openvdb.Vec3SGrid() + grid.fill((-4, -4, -4), (5, 5, 5), grid.zeroValue) + grid.mapOn(lambda x: (0, 1, 0)) + n = sum(item.value[1] for item in grid.iterOnValues()) + self.assertEqual(n, 10 * 10 * 10) + + + def testValueAccessor(self): + coords = set([(-10, -10, -10), (0, 0, 0), (1, 1, 1)]) + + for factory in openvdb.GridTypes: + grid = factory() + zero, one = grid.zeroValue, grid.oneValue + acc = grid.getAccessor() + cacc = grid.getConstAccessor() + leafDepth = grid.treeDepth - 1 + + self.assertRaises(TypeError, lambda: cacc.setValueOn((5, 5, 5), zero)) + self.assertRaises(TypeError, lambda: cacc.setValueOff((5, 5, 5), zero)) + self.assertRaises(TypeError, lambda: cacc.setActiveState((5, 5, 5), True)) + self.assertRaises(TypeError, lambda: acc.setValueOn("", zero)) + self.assertRaises(TypeError, lambda: acc.setValueOff("", zero)) + if grid.valueTypeName != "bool": + self.assertRaises(TypeError, lambda: acc.setValueOn((5, 5, 5), object())) + self.assertRaises(TypeError, lambda: acc.setValueOff((5, 5, 5), object())) + + for c in coords: + grid.clear() + + # All voxels are inactive, background (0), and stored at the root. + self.assertEqual(acc.getValue(c), zero) + self.assertEqual(cacc.getValue(c), zero) + self.assertFalse(acc.isValueOn(c)) + self.assertFalse(cacc.isValueOn(c)) + self.assertEqual(acc.getValueDepth(c), -1) + self.assertEqual(cacc.getValueDepth(c), -1) + + acc.setValueOn(c) # active / 0 / leaf + self.assertEqual(acc.getValue(c), zero) + self.assertEqual(cacc.getValue(c), zero) + self.assert_(acc.isValueOn(c)) + self.assert_(cacc.isValueOn(c)) + self.assertEqual(acc.getValueDepth(c), leafDepth) + self.assertEqual(cacc.getValueDepth(c), leafDepth) + + acc.setValueOff(c, grid.oneValue) # inactive / 1 / leaf + self.assertEqual(acc.getValue(c), one) + self.assertEqual(cacc.getValue(c), one) + self.assertFalse(acc.isValueOn(c)) + self.assertFalse(cacc.isValueOn(c)) + self.assertEqual(acc.getValueDepth(c), leafDepth) + self.assertEqual(cacc.getValueDepth(c), leafDepth) + + # Verify that an accessor remains valid even after its grid is deleted + # (because the C++ wrapper retains a reference to the C++ grid). + def scoped(): + grid = factory() + acc = grid.getAccessor() + cacc = grid.getConstAccessor() + one = grid.oneValue + acc.setValueOn((0, 0, 0), one) + del grid + self.assertEqual(acc.getValue((0, 0, 0)), one) + self.assertEqual(cacc.getValue((0, 0, 0)), one) + scoped() + + + def testValueAccessorCopy(self): + xyz = (0, 0, 0) + grid = openvdb.BoolGrid() + + acc = grid.getAccessor() + self.assertEqual(acc.getValue(xyz), False) + self.assertFalse(acc.isValueOn(xyz)) + + copyOfAcc = acc.copy() + self.assertEqual(copyOfAcc.getValue(xyz), False) + self.assertFalse(copyOfAcc.isValueOn(xyz)) + + # Verify that changes made to the grid through one accessor are reflected in the other. + acc.setValueOn(xyz, True) + self.assertEqual(acc.getValue(xyz), True) + self.assert_(acc.isValueOn(xyz)) + self.assertEqual(copyOfAcc.getValue(xyz), True) + self.assert_(copyOfAcc.isValueOn(xyz)) + + copyOfAcc.setValueOff(xyz) + self.assertEqual(acc.getValue(xyz), True) + self.assertFalse(acc.isValueOn(xyz)) + self.assertEqual(copyOfAcc.getValue(xyz), True) + self.assertFalse(copyOfAcc.isValueOn(xyz)) + + # Verify that the two accessors are distinct, by checking that they + # have cached different sets of nodes. + xyz2 = (-1, -1, -1) + copyOfAcc.setValueOn(xyz2) + self.assert_(copyOfAcc.isCached(xyz2)) + self.assertFalse(copyOfAcc.isCached(xyz)) + self.assert_(acc.isCached(xyz)) + self.assertFalse(acc.isCached(xyz2)) + + + def testPickle(self): + import pickle + + # Test pickling of transforms of various types. + testXforms = [ + openvdb.createLinearTransform(voxelSize=0.1), + openvdb.createLinearTransform(matrix=[[1,0,0,0],[0,2,0,0],[0,0,3,0],[4,3,2,1]]), + openvdb.createFrustumTransform((0,0,0), (10,10,10), taper=0.8, depth=10.0), + ] + for xform in testXforms: + s = pickle.dumps(xform) + restoredXform = pickle.loads(s) + self.assertEqual(restoredXform, xform) + + # Test pickling of grids of various types. + for factory in openvdb.GridTypes: + + # Construct a grid. + grid = factory() + # Add some metadata to the grid. + meta = { 'name': 'test', 'saveFloatAsHalf': True, 'xyz': (-1, 0, 1) } + grid.metadata = meta + # Add some voxel data to the grid. + active = True + for width in range(63, 0, -10): + val = valueFactory(grid.zeroValue, width) + grid.fill((0, 0, 0), (width,)*3, val, active) + active = not active + + # Pickle the grid to a string, then unpickle the string. + s = pickle.dumps(grid) + restoredGrid = pickle.loads(s) + + # Verify that the original and unpickled grids' metadata are equal. + self.assertEqual(restoredGrid.metadata, meta) + + # Verify that the original and unpickled grids have the same active values. + for restored, original in zip(restoredGrid.iterOnValues(), grid.iterOnValues()): + self.assertEqual(restored, original) + # Verify that the original and unpickled grids have the same inactive values. + for restored, original in zip(restoredGrid.iterOffValues(), grid.iterOffValues()): + self.assertEqual(restored, original) + + + def testGridCombine(self): + # Construct two grids and add some voxel data to each. + aGrid, bGrid = openvdb.FloatGrid(), openvdb.FloatGrid(background=1.0) + for width in range(63, 1, -10): + aGrid.fill((0, 0, 0), (width,)*3, width) + bGrid.fill((0, 0, 0), (width,)*3, 2 * width) + + # Save a copy of grid A. + copyOfAGrid = aGrid.deepCopy() + + # Combine corresponding values of the two grids, storing the result in grid A. + # (Since the grids have the same topology and B's active values are twice A's, + # the function computes 2*min(a, 2*a) + 3*max(a, 2*a) = 2*a + 3*(2*a) = 8*a + # for active values, and 2*min(0, 1) + 3*max(0, 1) = 2*0 + 3*1 = 3 + # for inactive values.) + aGrid.combine(bGrid, lambda a, b: 2 * min(a, b) + 3 * max(a, b)) + + self.assert_(bGrid.empty()) + + # Verify that the resulting grid's values are as expected. + for original, combined in zip(copyOfAGrid.iterOnValues(), aGrid.iterOnValues()): + self.assertEqual(combined.min, original.min) + self.assertEqual(combined.max, original.max) + self.assertEqual(combined.depth, original.depth) + self.assertEqual(combined.value, 8 * original.value) + for original, combined in zip(copyOfAGrid.iterOffValues(), aGrid.iterOffValues()): + self.assertEqual(combined.min, original.min) + self.assertEqual(combined.max, original.max) + self.assertEqual(combined.depth, original.depth) + self.assertEqual(combined.value, 3) + + + def testLevelSetSphere(self): + HALF_WIDTH = 4 + sphere = openvdb.createLevelSetSphere(halfWidth=HALF_WIDTH, voxelSize=1.0, radius=100.0) + lo, hi = sphere.evalMinMax() + self.assert_(lo >= -HALF_WIDTH) + self.assert_(hi <= HALF_WIDTH) + + + def testCopyFromArray(self): + import random + import time + + # Skip this test if NumPy is not available. + try: + import numpy as np + except ImportError: + return + + # Skip this test if the OpenVDB module was built without NumPy support. + arr = np.ndarray((1, 2, 1)) + grid = openvdb.FloatGrid() + try: + grid.copyFromArray(arr) + except NotImplementedError: + return + + # Verify that a non-three-dimensional array can't be copied into a grid. + grid = openvdb.FloatGrid() + self.assertRaises(TypeError, lambda: grid.copyFromArray('abc')) + arr = np.ndarray((1, 2)) + self.assertRaises(ValueError, lambda: grid.copyFromArray(arr)) + + # Verify that complex-valued arrays are not supported. + arr = np.ndarray((1, 2, 1), dtype = complex) + grid = openvdb.FloatGrid() + self.assertRaises(TypeError, lambda: grid.copyFromArray(arr)) + + ARRAY_DIM = 201 + BG, FG = 0, 1 + + # Generate some random voxel coordinates. + random.seed(0) + def randCoord(): + return tuple(random.randint(0, ARRAY_DIM-1) for i in range(3)) + coords = set(randCoord() for i in range(200)) + + def createArrays(): + # Test both scalar- and vec3-valued (i.e., four-dimensional) arrays. + for shape in ( + (ARRAY_DIM, ARRAY_DIM, ARRAY_DIM), # scalar array + (ARRAY_DIM, ARRAY_DIM, ARRAY_DIM, 3) # vec3 array + ): + for dtype in (np.float32, np.int32, np.float64, np.int64, np.uint32, np.bool): + # Create a NumPy array, fill it with the background value, + # then set some elements to the foreground value. + arr = np.ndarray(shape, dtype) + arr.fill(BG) + bg = arr[0, 0, 0] + for c in coords: + arr[c] = FG + + yield arr + + # Test copying from arrays of various types to grids of various types. + for cls in openvdb.GridTypes: + for arr in createArrays(): + isScalarArray = (len(arr.shape) == 3) + isScalarGrid = False + try: + len(cls.zeroValue) # values of vector grids are sequences, which have a length + except TypeError: + isScalarGrid = True # values of scalar grids have no length + + gridBG = valueFactory(cls.zeroValue, BG) + gridFG = valueFactory(cls.zeroValue, FG) + + # Create an empty grid. + grid = cls(gridBG) + acc = grid.getAccessor() + + # Verify that scalar arrays can't be copied into vector grids + # and vector arrays can't be copied into scalar grids. + if isScalarGrid != isScalarArray: + self.assertRaises(ValueError, lambda: grid.copyFromArray(arr)) + continue + + # Copy values from the NumPy array to the grid, marking + # background values as inactive and all other values as active. + now = time.clock() + grid.copyFromArray(arr) + elapsed = time.clock() - now + #print 'copied %d voxels from %s array to %s in %f sec' % ( + # arr.shape[0] * arr.shape[1] * arr.shape[2], + # str(arr.dtype) + ('' if isScalarArray else '[]'), + # grid.__class__.__name__, elapsed) + + # Verify that the grid's active voxels match the array's foreground elements. + self.assertEqual(grid.activeVoxelCount(), len(coords)) + for c in coords: + self.assertEqual(acc.getValue(c), gridFG) + for value in grid.iterOnValues(): + self.assert_(value.min in coords) + + + def testCopyToArray(self): + import random + import time + + # Skip this test if NumPy is not available. + try: + import numpy as np + except ImportError: + return + + # Skip this test if the OpenVDB module was built without NumPy support. + arr = np.ndarray((1, 2, 1)) + grid = openvdb.FloatGrid() + try: + grid.copyFromArray(arr) + except NotImplementedError: + return + + # Verify that a grid can't be copied into a non-three-dimensional array. + grid = openvdb.FloatGrid() + self.assertRaises(TypeError, lambda: grid.copyToArray('abc')) + arr = np.ndarray((1, 2)) + self.assertRaises(ValueError, lambda: grid.copyToArray(arr)) + + # Verify that complex-valued arrays are not supported. + arr = np.ndarray((1, 2, 1), dtype = complex) + grid = openvdb.FloatGrid() + self.assertRaises(TypeError, lambda: grid.copyToArray(arr)) + + ARRAY_DIM = 201 + BG, FG = 0, 1 + + # Generate some random voxel coordinates. + random.seed(0) + def randCoord(): + return tuple(random.randint(0, ARRAY_DIM-1) for i in range(3)) + coords = set(randCoord() for i in range(200)) + + def createArrays(): + # Test both scalar- and vec3-valued (i.e., four-dimensional) arrays. + for shape in ( + (ARRAY_DIM, ARRAY_DIM, ARRAY_DIM), # scalar array + (ARRAY_DIM, ARRAY_DIM, ARRAY_DIM, 3) # vec3 array + ): + for dtype in (np.float32, np.int32, np.float64, np.int64, np.uint32, np.bool): + # Return a new NumPy array. + arr = np.ndarray(shape, dtype) + arr.fill(-100) + yield arr + + # Test copying from arrays of various types to grids of various types. + for cls in openvdb.GridTypes: + for arr in createArrays(): + isScalarArray = (len(arr.shape) == 3) + isScalarGrid = False + try: + len(cls.zeroValue) # values of vector grids are sequences, which have a length + except TypeError: + isScalarGrid = True # values of scalar grids have no length + + gridBG = valueFactory(cls.zeroValue, BG) + gridFG = valueFactory(cls.zeroValue, FG) + + # Create an empty grid, fill it with the background value, + # then set some elements to the foreground value. + grid = cls(gridBG) + acc = grid.getAccessor() + for c in coords: + acc.setValueOn(c, gridFG) + + # Verify that scalar grids can't be copied into vector arrays + # and vector grids can't be copied into scalar arrays. + if isScalarGrid != isScalarArray: + self.assertRaises(ValueError, lambda: grid.copyToArray(arr)) + continue + + # Copy values from the grid to the NumPy array. + now = time.clock() + grid.copyToArray(arr) + elapsed = time.clock() - now + #print 'copied %d voxels from %s to %s array in %f sec' % ( + # arr.shape[0] * arr.shape[1] * arr.shape[2], grid.__class__.__name__, + # str(arr.dtype) + ('' if isScalarArray else '[]'), elapsed) + + # Verify that the grid's active voxels match the array's foreground elements. + for c in coords: + self.assertEqual(arr[c] if isScalarArray else tuple(arr[c]), gridFG) + arr[c] = gridBG + self.assertEqual(np.amin(arr), BG) + self.assertEqual(np.amax(arr), BG) + + + +if __name__ == '__main__': + print 'Testing', os.path.dirname(openvdb.__file__) + sys.stdout.flush() + + try: + logging.configure(sys.argv) + args = Ani(sys.argv).userArgs() # strip out ANI-related arguments + except NameError: + args = sys.argv + + # Unlike CppUnit, PyUnit doesn't use the "-t" flag to identify + # test names, so for consistency, strip out any "-t" arguments, + # so that, e.g., "TestOpenVDB.py -t TestOpenVDB.testTransform" + # is equivalent to "TestOpenVDB.py TestOpenVDB.testTransform". + args = [a for a in args if a != '-t'] + + unittest.main(argv=args) + + +# Copyright (c) 2012-2013 DreamWorks Animation LLC +# All rights reserved. This software is distributed under the +# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/Composite.h b/openvdb_2_3_0_library/openvdb/tools/Composite.h new file mode 100755 index 0000000..b6c5e8e --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/Composite.h @@ -0,0 +1,574 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file Composite.h +/// +/// @brief Functions to efficiently perform various compositing operations on grids +/// +/// @author Peter Cucka + +#ifndef OPENVDB_TOOLS_COMPOSITE_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_COMPOSITE_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include // for isExactlyEqual() +#include "ValueTransformer.h" // for transformValues() +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Given two level set grids, replace the A grid with the union of A and B. +/// @throw ValueError if the background value of either grid is not greater than zero. +/// @note This operation always leaves the B grid empty. +template OPENVDB_STATIC_SPECIALIZATION +inline void csgUnion(GridOrTreeT& a, GridOrTreeT& b, bool prune = true); +/// @brief Given two level set grids, replace the A grid with the intersection of A and B. +/// @throw ValueError if the background value of either grid is not greater than zero. +/// @note This operation always leaves the B grid empty. +template OPENVDB_STATIC_SPECIALIZATION +inline void csgIntersection(GridOrTreeT& a, GridOrTreeT& b, bool prune = true); +/// @brief Given two level set grids, replace the A grid with the difference A / B. +/// @throw ValueError if the background value of either grid is not greater than zero. +/// @note This operation always leaves the B grid empty. +template OPENVDB_STATIC_SPECIALIZATION +inline void csgDifference(GridOrTreeT& a, GridOrTreeT& b, bool prune = true); + +/// @brief Given grids A and B, compute max(a, b) per voxel (using sparse traversal). +/// Store the result in the A grid and leave the B grid empty. +template OPENVDB_STATIC_SPECIALIZATION +inline void compMax(GridOrTreeT& a, GridOrTreeT& b); +/// @brief Given grids A and B, compute min(a, b) per voxel (using sparse traversal). +/// Store the result in the A grid and leave the B grid empty. +template OPENVDB_STATIC_SPECIALIZATION +inline void compMin(GridOrTreeT& a, GridOrTreeT& b); +/// @brief Given grids A and B, compute a + b per voxel (using sparse traversal). +/// Store the result in the A grid and leave the B grid empty. +template OPENVDB_STATIC_SPECIALIZATION +inline void compSum(GridOrTreeT& a, GridOrTreeT& b); +/// @brief Given grids A and B, compute a * b per voxel (using sparse traversal). +/// Store the result in the A grid and leave the B grid empty. +template OPENVDB_STATIC_SPECIALIZATION +inline void compMul(GridOrTreeT& a, GridOrTreeT& b); +/// @brief Given grids A and B, compute a / b per voxel (using sparse traversal). +/// Store the result in the A grid and leave the B grid empty. +template OPENVDB_STATIC_SPECIALIZATION +inline void compDiv(GridOrTreeT& a, GridOrTreeT& b); + +/// Copy the active voxels of B into A. +template OPENVDB_STATIC_SPECIALIZATION +inline void compReplace(GridOrTreeT& a, const GridOrTreeT& b); + + +//////////////////////////////////////// + + +namespace composite { + +// composite::min() and composite::max() for non-vector types compare with operator<(). +template inline +const typename boost::disable_if_c::IsVec, T>::type& // = T if T is not a vector type +min(const T& a, const T& b) { return std::min(a, b); } + +template inline +const typename boost::disable_if_c::IsVec, T>::type& +max(const T& a, const T& b) { return std::max(a, b); } + + +// composite::min() and composite::max() for OpenVDB vector types compare by magnitude. +template inline +const typename boost::enable_if_c::IsVec, T>::type& // = T if T is a vector type +min(const T& a, const T& b) +{ + const typename T::ValueType aMag = a.lengthSqr(), bMag = b.lengthSqr(); + return (aMag < bMag ? a : (bMag < aMag ? b : std::min(a, b))); +} + +template inline +const typename boost::enable_if_c::IsVec, T>::type& +max(const T& a, const T& b) +{ + const typename T::ValueType aMag = a.lengthSqr(), bMag = b.lengthSqr(); + return (aMag < bMag ? b : (bMag < aMag ? a : std::max(a, b))); +} + +} // namespace composite + + +template +OPENVDB_STATIC_SPECIALIZATION inline void +compMax(GridOrTreeT& aTree, GridOrTreeT& bTree) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeT; + typedef typename TreeT::ValueType ValueT; + struct Local { + static inline void op(CombineArgs& args) { + args.setResult(composite::max(args.a(), args.b())); + } + }; + Adapter::tree(aTree).combineExtended(Adapter::tree(bTree), Local::op, /*prune=*/false); +} + + +template +OPENVDB_STATIC_SPECIALIZATION inline void +compMin(GridOrTreeT& aTree, GridOrTreeT& bTree) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeT; + typedef typename TreeT::ValueType ValueT; + struct Local { + static inline void op(CombineArgs& args) { + args.setResult(composite::min(args.a(), args.b())); + } + }; + Adapter::tree(aTree).combineExtended(Adapter::tree(bTree), Local::op, /*prune=*/false); +} + + +template +OPENVDB_STATIC_SPECIALIZATION inline void +compSum(GridOrTreeT& aTree, GridOrTreeT& bTree) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeT; + struct Local { + static inline void op(CombineArgs& args) { + args.setResult(args.a() + args.b()); + } + }; + Adapter::tree(aTree).combineExtended(Adapter::tree(bTree), Local::op, /*prune=*/false); +} + + +template +OPENVDB_STATIC_SPECIALIZATION inline void +compMul(GridOrTreeT& aTree, GridOrTreeT& bTree) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeT; + struct Local { + static inline void op(CombineArgs& args) { + args.setResult(args.a() * args.b()); + } + }; + Adapter::tree(aTree).combineExtended(Adapter::tree(bTree), Local::op, /*prune=*/false); +} + + +template +OPENVDB_STATIC_SPECIALIZATION inline void +compDiv(GridOrTreeT& aTree, GridOrTreeT& bTree) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeT; + struct Local { + static inline void op(CombineArgs& args) { + args.setResult(args.a() / args.b()); + } + }; + Adapter::tree(aTree).combineExtended(Adapter::tree(bTree), Local::op, /*prune=*/false); +} + + +//////////////////////////////////////// + + +template +struct CompReplaceOp +{ + TreeT* const aTree; + + CompReplaceOp(TreeT& _aTree): aTree(&_aTree) {} + + void operator()(const typename TreeT::ValueOnCIter& iter) const + { + CoordBBox bbox; + iter.getBoundingBox(bbox); + aTree->fill(bbox, *iter); + } + + void operator()(const typename TreeT::LeafCIter& leafIter) const + { + tree::ValueAccessor acc(*aTree); + for (typename TreeT::LeafCIter::LeafNodeT::ValueOnCIter iter = + leafIter->cbeginValueOn(); iter; ++iter) + { + acc.setValue(iter.getCoord(), *iter); + } + } +}; + + +template +OPENVDB_STATIC_SPECIALIZATION inline void +compReplace(GridOrTreeT& aTree, const GridOrTreeT& bTree) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeT; + typedef typename TreeT::ValueOnCIter ValueOnCIterT; + + // Copy active states (but not values) from B to A. + Adapter::tree(aTree).topologyUnion(Adapter::tree(bTree)); + + CompReplaceOp op(Adapter::tree(aTree)); + + // Copy all active tile values from B to A. + ValueOnCIterT iter = bTree.cbeginValueOn(); + iter.setMaxDepth(iter.getLeafDepth() - 1); // don't descend into leaf nodes + foreach(iter, op); + + // Copy all active voxel values from B to A. + foreach(Adapter::tree(bTree).cbeginLeaf(), op); +} + + +//////////////////////////////////////// + + +/// Base visitor class for CSG operations +/// (not intended to be used polymorphically, so no virtual functions) +template +class CsgVisitorBase +{ +public: + typedef TreeType TreeT; + typedef typename TreeT::ValueType ValueT; + typedef typename TreeT::LeafNodeType::ChildAllIter ChildIterT; + + enum { STOP = 3 }; + + CsgVisitorBase(const TreeT& aTree, const TreeT& bTree): + mAOutside(aTree.background()), + mAInside(math::negative(mAOutside)), + mBOutside(bTree.background()), + mBInside(math::negative(mBOutside)) + { + const ValueT zero = zeroVal(); + if (!(mAOutside > zero)) { + OPENVDB_THROW(ValueError, + "expected grid A outside value > 0, got " << mAOutside); + } + if (!(mAInside < zero)) { + OPENVDB_THROW(ValueError, + "expected grid A inside value < 0, got " << mAInside); + } + if (!(mBOutside > zero)) { + OPENVDB_THROW(ValueError, + "expected grid B outside value > 0, got " << mBOutside); + } + if (!(mBInside < zero)) { + OPENVDB_THROW(ValueError, + "expected grid B outside value < 0, got " << mBOutside); + } + } + +protected: + ValueT mAOutside, mAInside, mBOutside, mBInside; +}; + + +//////////////////////////////////////// + + +template +struct CsgUnionVisitor: public CsgVisitorBase +{ + typedef TreeType TreeT; + typedef typename TreeT::ValueType ValueT; + typedef typename TreeT::LeafNodeType::ChildAllIter ChildIterT; + + enum { STOP = CsgVisitorBase::STOP }; + + CsgUnionVisitor(const TreeT& a, const TreeT& b): CsgVisitorBase(a, b) {} + + /// Don't process nodes that are at different tree levels. + template + inline int operator()(AIterT&, BIterT&) { return 0; } + + /// Process root and internal nodes. + template + inline int operator()(IterT& aIter, IterT& bIter) + { + ValueT aValue = zeroVal(); + typename IterT::ChildNodeType* aChild = aIter.probeChild(aValue); + if (!aChild && aValue < zeroVal()) { + // A is an inside tile. Leave it alone and stop traversing this branch. + return STOP; + } + + ValueT bValue = zeroVal(); + typename IterT::ChildNodeType* bChild = bIter.probeChild(bValue); + if (!bChild && bValue < zeroVal()) { + // B is an inside tile. Make A an inside tile and stop traversing this branch. + aIter.setValue(this->mAInside); + aIter.setValueOn(bIter.isValueOn()); + delete aChild; + return STOP; + } + + if (!aChild && aValue > zeroVal()) { + // A is an outside tile. If B has a child, transfer it to A, + // otherwise leave A alone. + if (bChild) { + bIter.setValue(this->mBOutside); + bIter.setValueOff(); + bChild->resetBackground(this->mBOutside, this->mAOutside); + aIter.setChild(bChild); // transfer child + delete aChild; + } + return STOP; + } + + // If A has a child and B is an outside tile, stop traversing this branch. + // Continue traversal only if A and B both have children. + return (aChild && bChild) ? 0 : STOP; + } + + /// Process leaf node values. + inline int operator()(ChildIterT& aIter, ChildIterT& bIter) + { + ValueT aValue, bValue; + aIter.probeValue(aValue); + bIter.probeValue(bValue); + if (aValue > bValue) { // a = min(a, b) + aIter.setValue(bValue); + aIter.setValueOn(bIter.isValueOn()); + } + return 0; + } +}; + + + +//////////////////////////////////////// + + +template +struct CsgIntersectVisitor: public CsgVisitorBase +{ + typedef TreeType TreeT; + typedef typename TreeT::ValueType ValueT; + typedef typename TreeT::LeafNodeType::ChildAllIter ChildIterT; + + enum { STOP = CsgVisitorBase::STOP }; + + CsgIntersectVisitor(const TreeT& a, const TreeT& b): CsgVisitorBase(a, b) {} + + /// Don't process nodes that are at different tree levels. + template + inline int operator()(AIterT&, BIterT&) { return 0; } + + /// Process root and internal nodes. + template + inline int operator()(IterT& aIter, IterT& bIter) + { + ValueT aValue = zeroVal(); + typename IterT::ChildNodeType* aChild = aIter.probeChild(aValue); + if (!aChild && !(aValue < zeroVal())) { + // A is an outside tile. Leave it alone and stop traversing this branch. + return STOP; + } + + ValueT bValue = zeroVal(); + typename IterT::ChildNodeType* bChild = bIter.probeChild(bValue); + if (!bChild && !(bValue < zeroVal())) { + // B is an outside tile. Make A an outside tile and stop traversing this branch. + aIter.setValue(this->mAOutside); + aIter.setValueOn(bIter.isValueOn()); + delete aChild; + return STOP; + } + + if (!aChild && aValue < zeroVal()) { + // A is an inside tile. If B has a child, transfer it to A, + // otherwise leave A alone. + if (bChild) { + bIter.setValue(this->mBOutside); + bIter.setValueOff(); + bChild->resetBackground(this->mBOutside, this->mAOutside); + aIter.setChild(bChild); // transfer child + delete aChild; + } + return STOP; + } + + // If A has a child and B is an outside tile, stop traversing this branch. + // Continue traversal only if A and B both have children. + return (aChild && bChild) ? 0 : STOP; + } + + /// Process leaf node values. + inline int operator()(ChildIterT& aIter, ChildIterT& bIter) + { + ValueT aValue, bValue; + aIter.probeValue(aValue); + bIter.probeValue(bValue); + if (aValue < bValue) { // a = max(a, b) + aIter.setValue(bValue); + aIter.setValueOn(bIter.isValueOn()); + } + return 0; + } +}; + + +//////////////////////////////////////// + + +template +struct CsgDiffVisitor: public CsgVisitorBase +{ + typedef TreeType TreeT; + typedef typename TreeT::ValueType ValueT; + typedef typename TreeT::LeafNodeType::ChildAllIter ChildIterT; + + enum { STOP = CsgVisitorBase::STOP }; + + CsgDiffVisitor(const TreeT& a, const TreeT& b): CsgVisitorBase(a, b) {} + + /// Don't process nodes that are at different tree levels. + template + inline int operator()(AIterT&, BIterT&) { return 0; } + + /// Process root and internal nodes. + template + inline int operator()(IterT& aIter, IterT& bIter) + { + ValueT aValue = zeroVal(); + typename IterT::ChildNodeType* aChild = aIter.probeChild(aValue); + if (!aChild && !(aValue < zeroVal())) { + // A is an outside tile. Leave it alone and stop traversing this branch. + return STOP; + } + + ValueT bValue = zeroVal(); + typename IterT::ChildNodeType* bChild = bIter.probeChild(bValue); + if (!bChild && bValue < zeroVal()) { + // B is an inside tile. Make A an inside tile and stop traversing this branch. + aIter.setValue(this->mAOutside); + aIter.setValueOn(bIter.isValueOn()); + delete aChild; + return STOP; + } + + if (!aChild && aValue < zeroVal()) { + // A is an inside tile. If B has a child, transfer it to A, + // otherwise leave A alone. + if (bChild) { + bIter.setValue(this->mBOutside); + bIter.setValueOff(); + bChild->resetBackground(this->mBOutside, this->mAOutside); + aIter.setChild(bChild); // transfer child + bChild->negate(); + delete aChild; + } + return STOP; + } + + // If A has a child and B is an outside tile, stop traversing this branch. + // Continue traversal only if A and B both have children. + return (aChild && bChild) ? 0 : STOP; + } + + /// Process leaf node values. + inline int operator()(ChildIterT& aIter, ChildIterT& bIter) + { + ValueT aValue, bValue; + aIter.probeValue(aValue); + bIter.probeValue(bValue); + bValue = math::negative(bValue); + if (aValue < bValue) { // a = max(a, -b) + aIter.setValue(bValue); + aIter.setValueOn(bIter.isValueOn()); + } + return 0; + } +}; + + +//////////////////////////////////////// + + +template +OPENVDB_STATIC_SPECIALIZATION inline void +csgUnion(GridOrTreeT& a, GridOrTreeT& b, bool prune) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeT; + TreeT &aTree = Adapter::tree(a), &bTree = Adapter::tree(b); + CsgUnionVisitor visitor(aTree, bTree); + aTree.visit2(bTree, visitor); + if (prune) aTree.pruneLevelSet(); + //if (prune) aTree.prune(); +} + +template +OPENVDB_STATIC_SPECIALIZATION inline void +csgIntersection(GridOrTreeT& a, GridOrTreeT& b, bool prune) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeT; + TreeT &aTree = Adapter::tree(a), &bTree = Adapter::tree(b); + CsgIntersectVisitor visitor(aTree, bTree); + aTree.visit2(bTree, visitor); + if (prune) aTree.pruneLevelSet(); + //if (prune) aTree.prune(); +} + +template +OPENVDB_STATIC_SPECIALIZATION inline void +csgDifference(GridOrTreeT& a, GridOrTreeT& b, bool prune) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeT; + TreeT &aTree = Adapter::tree(a), &bTree = Adapter::tree(b); + CsgDiffVisitor visitor(aTree, bTree); + aTree.visit2(bTree, visitor); + if (prune) aTree.pruneLevelSet(); + //if (prune) aTree.prune(); +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_COMPOSITE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/Dense.h b/openvdb_2_3_0_library/openvdb/tools/Dense.h new file mode 100755 index 0000000..45ab9a9 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/Dense.h @@ -0,0 +1,533 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file Dense.h +/// +/// @brief This file defines a simple dense grid and efficient +/// converters to and from VDB grids. + +#ifndef OPENVDB_TOOLS_DENSE_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_DENSE_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Populate a dense grid with the values of voxels from a sparse grid, +/// where the sparse grid intersects the dense grid. +/// @param sparse an OpenVDB grid or tree from which to copy values +/// @param dense the dense grid into which to copy values +/// @param serial if false, process voxels in parallel +template +void +copyToDense( + const GridOrTreeT& sparse, + DenseT& dense, + bool serial = false); + + +/// @brief Populate a sparse grid with the values of all of the voxels of a dense grid. +/// @param dense the dense grid from which to copy values +/// @param sparse an OpenVDB grid or tree into which to copy values +/// @param tolerance values in the dense grid that are within this tolerance of the sparse +/// grid's background value become inactive background voxels or tiles in the sparse grid +/// @param serial if false, process voxels in parallel +template +void +copyFromDense( + const DenseT& dense, + GridOrTreeT& sparse, + const typename GridOrTreeT::ValueType& tolerance, + bool serial = false); + + +//////////////////////////////////////// + +/// We currently support the following two 3D memory layouts for dense +/// volumes: XYZ, i.e. x is the fastest moving index, and ZYX, i.e. z +/// is the fastest moving index. The ZYX memory layout leads to nested +/// for-loops of the order x, y, z, which we find to be the most +/// intuitive. Hence, ZYX is the layout used throughout VDB. However, +/// other data structures, e.g. Houdini and Maya, employ the XYZ +/// layout. Clearly a dense volume with the ZYX layout converts more +/// efficiently to a VDB, but we support both for convenience. +enum MemoryLayout { LayoutXYZ, LayoutZYX }; + +/// @brief Base class for Dense which is defined below. +/// @note The constructor of this class is protected to prevent direct +/// instantiation. +template class DenseBase; + +/// @brief Partial template specialization of DenseBase. +/// @note ZYX is the memory-layout in VDB. It leads to nested +/// for-loops of the order x, y, z which we find to be the most intuitive. +template +class DenseBase +{ +public: + /// @brief Return the linear offset into this grid's value array given by + /// unsigned coordinates (i, j, k), i.e., coordinates relative to + /// the origin of this grid's bounding box. + inline size_t coordToOffset(size_t i, size_t j, size_t k) const { return i*mX + j*mY + k; } + + /// @brief Return the stride of the array in the x direction ( = dimY*dimZ). + /// @note This method is required by both CopyToDense and CopyFromDense. + inline size_t xStride() const { return mX; } + + /// @brief Return the stride of the array in the y direction ( = dimZ). + /// @note This method is required by both CopyToDense and CopyFromDense. + inline size_t yStride() const { return mY; } + + /// @brief Return the stride of the array in the z direction ( = 1). + /// @note This method is required by both CopyToDense and CopyFromDense. + static size_t zStride() { return 1; } + +protected: + /// Protected constructor so as to prevent direct instantiation + DenseBase(const CoordBBox& bbox) : mBBox(bbox), mY(bbox.dim()[2]), mX(mY*bbox.dim()[1]) {} + + const CoordBBox mBBox;//signed coordinates of the domain represented by the grid + const size_t mY, mX;//strides in the y and x direction +};// end of DenseBase + +/// @brief Partial template specialization of DenseBase. +/// @note This is the memory-layout emplayed in Houdini and Maya. It leads +/// to nested for-loops of the order z, y, x. +template +class DenseBase +{ +public: + /// @brief Return the linear offset into this grid's value array given by + /// unsigned coordinates (i, j, k), i.e., coordinates relative to + /// the origin of this grid's bounding box. + inline size_t coordToOffset(size_t i, size_t j, size_t k) const { return i + j*mY + k*mZ; } + + /// @brief Return the stride of the array in the x direction ( = 1). + /// @note This method is required by both CopyToDense and CopyFromDense. + static size_t xStride() { return 1; } + + /// @brief Return the stride of the array in the y direction ( = dimX). + /// @note This method is required by both CopyToDense and CopyFromDense. + inline size_t yStride() const { return mY; } + + /// @brief Return the stride of the array in the y direction ( = dimX*dimY). + /// @note This method is required by both CopyToDense and CopyFromDense. + inline size_t zStride() const { return mZ; } + +protected: + /// Protected constructor so as to prevent direct instantiation + DenseBase(const CoordBBox& bbox) : mBBox(bbox), mY(bbox.dim()[0]), mZ(mY*bbox.dim()[1]) {} + + const CoordBBox mBBox;//signed coordinates of the domain represented by the grid + const size_t mY, mZ;//strides in the y and z direction +};// end of DenseBase + +/// @brief Dense is a simple dense grid API used by the CopyToDense and +/// CopyFromDense classes defined below. +/// @details Use the Dense class to efficiently produce a dense in-memory +/// representation of an OpenVDB grid. However, be aware that a dense grid +/// could have a memory footprint that is orders of magnitude larger than +/// the sparse grid from which it originates. +/// +/// @note This class can be used as a simple wrapper for existing dense grid +/// classes if they provide access to the raw data array. +/// @note This implementation allows for the 3D memory layout to be +/// defined by the MemoryLayout template parameter (see above for definition). +/// The default memory layout is ZYX since that's the layout used by OpenVDB grids. +template +class Dense : public DenseBase +{ +public: + typedef ValueT ValueType; + typedef DenseBase BaseT; + + /// @brief Construct a dense grid with a given range of coordinates. + /// + /// @param bbox the bounding box of the (signed) coordinate range of this grid + /// @throw ValueError if the bounding box is empty. + /// @note The min and max coordinates of the bounding box are inclusive. + Dense(const CoordBBox& bbox) : BaseT(bbox) { this->init(); } + + /// @brief Construct a dense grid with a given range of coordinates and initial value + /// + /// @param bbox the bounding box of the (signed) coordinate range of this grid + /// @param value the initial value of the grid. + /// @throw ValueError if the bounding box is empty. + /// @note The min and max coordinates of the bounding box are inclusive. + Dense(const CoordBBox& bbox, const ValueT& value) : BaseT(bbox) + { + this->init(); + this->fill(value); + } + + /// @brief Construct a dense grid that wraps an external array. + /// + /// @param bbox the bounding box of the (signed) coordinate range of this grid + /// @param data a raw C-style array whose size is commensurate with + /// the coordinate domain of @a bbox + /// + /// @note The data array is assumed to have a stride of one in the @e z direction. + /// @throw ValueError if the bounding box is empty. + /// @note The min and max coordinates of the bounding box are inclusive. + Dense(const CoordBBox& bbox, ValueT* data) : BaseT(bbox), mData(data) + { + if (BaseT::mBBox.empty()) { + OPENVDB_THROW(ValueError, "can't construct a dense grid with an empty bounding box"); + } + } + + /// @brief Construct a dense grid with a given origin and dimensions. + /// + /// @param dim the desired dimensions of the grid + /// @param min the signed coordinates of the first voxel in the dense grid + /// @throw ValueError if any of the dimensions are zero. + /// @note The @a min coordinate is inclusive, and the max coordinate will be + /// @a min + @a dim - 1. + Dense(const Coord& dim, const Coord& min = Coord(0)) + : BaseT(CoordBBox(min, min+dim.offsetBy(-1))) + { + this->init(); + } + + /// @brief Return the memory layout for this grid (see above for definitions). + static MemoryLayout memoryLayout() { return Layout; } + + /// @brief Return a raw pointer to this grid's value array. + /// @note This method is required by CopyToDense. + inline ValueT* data() { return mData; } + + /// @brief Return a raw pointer to this grid's value array. + /// @note This method is required by CopyFromDense. + inline const ValueT* data() const { return mData; } + + /// @brief Return the bounding box of the signed index domain of this grid. + /// @note This method is required by both CopyToDense and CopyFromDense. + inline const CoordBBox& bbox() const { return BaseT::mBBox; } + + /// @brief Return the number of voxels contained in this grid. + inline Index64 valueCount() const { return BaseT::mBBox.volume(); } + + /// @brief Set the value of the voxel at the given array offset. + inline void setValue(size_t offset, const ValueT& value) { mData[offset] = value; } + + /// @brief Return the value of the voxel at the given array offset. + const ValueT& getValue(size_t offset) const { return mData[offset]; } + + /// @brief Set the value of the voxel at unsigned index coordinates (i, j, k). + /// @note This is somewhat slower than using an array offset. + inline void setValue(size_t i, size_t j, size_t k, const ValueT& value) + { + mData[BaseT::coordToOffset(i,j,k)] = value; + } + + /// @brief Return the value of the voxel at unsigned index coordinates (i, j, k). + /// @note This is somewhat slower than using an array offset. + inline const ValueT& getValue(size_t i, size_t j, size_t k) const + { + return mData[BaseT::coordToOffset(i,j,k)]; + } + + /// @brief Set the value of the voxel at the given signed coordinates. + /// @note This is slower than using either an array offset or unsigned index coordinates. + inline void setValue(const Coord& xyz, const ValueT& value) + { + mData[this->coordToOffset(xyz)] = value; + } + + /// @brief Return the value of the voxel at the given signed coordinates. + /// @note This is slower than using either an array offset or unsigned index coordinates. + inline const ValueT& getValue(const Coord& xyz) const + { + return mData[this->coordToOffset(xyz)]; + } + + /// @brief Fill this grid with a constant value. + inline void fill(const ValueT& value) + { + size_t size = this->valueCount(); + ValueT* a = mData; + while(size--) *a++ = value; + } + + /// @brief Return the linear offset into this grid's value array given by + /// the specified signed coordinates, i.e., coordinates in the space of + /// this grid's bounding box. + /// + /// @note This method reflects the fact that we assume the same + /// layout of values as an OpenVDB grid, i.e., the fastest coordinate is @e z. + inline size_t coordToOffset(Coord xyz) const + { + assert(BaseT::mBBox.isInside(xyz)); + return BaseT::coordToOffset(size_t(xyz[0]-BaseT::mBBox.min()[0]), + size_t(xyz[1]-BaseT::mBBox.min()[1]), + size_t(xyz[2]-BaseT::mBBox.min()[2])); + } + + /// @brief Return the memory footprint of this Dense grid in bytes. + inline Index64 memUsage() const + { + return sizeof(*this) + BaseT::mBBox.volume() * sizeof(ValueType); + } + +private: + + /// @brief Private method to initialize the dense value array. + void init() + { + if (BaseT::mBBox.empty()) { + OPENVDB_THROW(ValueError, "can't construct a dense grid with an empty bounding box"); + } + mArray.reset(new ValueT[BaseT::mBBox.volume()]); + mData = mArray.get(); + } + + boost::scoped_array mArray; + ValueT* mData;//raw c-style pointer to values +};// end of Dense + +//////////////////////////////////////// + + +/// @brief Copy an OpenVDB tree into an existing dense grid. +/// +/// @note Only voxels that intersect the dense grid's bounding box are copied +/// from the OpenVDB tree. But both active and inactive voxels are copied, +/// so all existing values in the dense grid are overwritten, regardless of +/// the OpenVDB tree's tolopogy. +template > +class CopyToDense +{ +public: + typedef _DenseT DenseT; + typedef _TreeT TreeT; + typedef typename TreeT::ValueType ValueT; + + CopyToDense(const TreeT& tree, DenseT& dense) + : mRoot(&(tree.root())), mDense(&dense) {} + + void copy(bool serial = false) const + { + if (serial) { + mRoot->copyToDense(mDense->bbox(), *mDense); + } else { + tbb::parallel_for(mDense->bbox(), *this); + } + } + + /// @brief Public method called by tbb::parallel_for + void operator()(const CoordBBox& bbox) const + { + mRoot->copyToDense(bbox, *mDense); + } + +private: + const typename TreeT::RootNodeType* mRoot; + DenseT* mDense; +};// CopyToDense + + +// Convenient wrapper function for the CopyToDense class +template +void +copyToDense(const GridOrTreeT& sparse, DenseT& dense, bool serial) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeT; + + CopyToDense op(Adapter::constTree(sparse), dense); + op.copy(serial); +} + + +//////////////////////////////////////// + + +/// @brief Copy the values from a dense grid into an OpenVDB tree. +/// +/// @details Values in the dense grid that are within a tolerance of +/// the background value are truncated to inactive background voxels or tiles. +/// This allows the tree to form a sparse representation of the dense grid. +/// +/// @note Since this class allocates leaf nodes concurrently it is recommended +/// to use a scalable implementation of @c new like the one provided by TBB, +/// rather than the mutex-protected standard library @c new. +template > +class CopyFromDense +{ +public: + typedef _DenseT DenseT; + typedef _TreeT TreeT; + typedef typename TreeT::ValueType ValueT; + typedef typename TreeT::LeafNodeType LeafT; + typedef tree::ValueAccessor AccessorT; + + CopyFromDense(const DenseT& dense, TreeT& tree, const ValueT& tolerance) + : mDense(&dense), + mTree(&tree), + mBlocks(NULL), + mTolerance(tolerance), + mAccessor(tree.empty() ? NULL : new AccessorT(tree)) + { + } + CopyFromDense(const CopyFromDense& other) + : mDense(other.mDense), + mTree(other.mTree), + mBlocks(other.mBlocks), + mTolerance(other.mTolerance), + mAccessor(other.mAccessor.get() == NULL ? NULL : new AccessorT(*mTree)) + { + } + + /// @brief Copy values from the dense grid to the sparse tree. + void copy(bool serial = false) + { + mBlocks = new std::vector(); + const CoordBBox& bbox = mDense->bbox(); + // Pre-process: Construct a list of blocks alligned with (potential) leaf nodes + for (CoordBBox sub=bbox; sub.min()[0] <= bbox.max()[0]; sub.min()[0] = sub.max()[0] + 1) { + for (sub.min()[1] = bbox.min()[1]; sub.min()[1] <= bbox.max()[1]; + sub.min()[1] = sub.max()[1] + 1) + { + for (sub.min()[2] = bbox.min()[2]; sub.min()[2] <= bbox.max()[2]; + sub.min()[2] = sub.max()[2] + 1) + { + sub.max() = Coord::minComponent(bbox.max(), + (sub.min()&(~(LeafT::DIM-1u))).offsetBy(LeafT::DIM-1u)); + mBlocks->push_back(Block(sub)); + } + } + } + + // Multi-threaded process: Convert dense grid into leaf nodes and tiles + if (serial) { + (*this)(tbb::blocked_range(0, mBlocks->size())); + } else { + tbb::parallel_for(tbb::blocked_range(0, mBlocks->size()), *this); + } + + // Post-process: Insert leaf nodes and tiles into the tree, and prune the tiles only! + tree::ValueAccessor acc(*mTree); + for (size_t m=0, size = mBlocks->size(); mroot().pruneTiles(mTolerance); + } + + /// @brief Public method called by tbb::parallel_for + /// @warning Never call this method directly! + void operator()(const tbb::blocked_range &r) const + { + assert(mBlocks); + LeafT* leaf = new LeafT(); + + for (size_t m=r.begin(), n=0, end = r.end(); m != end; ++m, ++n) { + + Block& block = (*mBlocks)[m]; + const CoordBBox &bbox = block.bbox; + + if (mAccessor.get() == NULL) {//i.e. empty target tree + leaf->fill(mTree->background(), false); + } else {//account for existing leaf nodes in the target tree + if (const LeafT* target = mAccessor->probeConstLeaf(bbox.min())) { + (*leaf) = (*target); + } else { + ValueT value = zeroVal(); + bool state = mAccessor->probeValue(bbox.min(), value); + leaf->fill(value, state); + } + } + + leaf->copyFromDense(bbox, *mDense, mTree->background(), mTolerance); + + if (!leaf->isConstant(block.tile.first, block.tile.second, mTolerance)) { + leaf->setOrigin(bbox.min() & (~(LeafT::DIM - 1))); + block.leaf = leaf; + leaf = new LeafT(); + } + }// loop over blocks + + delete leaf; + } + +private: + struct Block { + CoordBBox bbox; + LeafT* leaf; + std::pair tile; + Block(const CoordBBox& b) : bbox(b), leaf(NULL) {} + }; + + const DenseT* mDense; + TreeT* mTree; + std::vector* mBlocks; + ValueT mTolerance; + boost::scoped_ptr mAccessor; +};// CopyFromDense + + +// Convenient wrapper function for the CopyFromDense class +template +void +copyFromDense(const DenseT& dense, GridOrTreeT& sparse, + const typename GridOrTreeT::ValueType& tolerance, bool serial) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeT; + + CopyFromDense op(dense, Adapter::tree(sparse), tolerance); + op.copy(serial); +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_DENSE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/DenseSparseTools.h b/openvdb_2_3_0_library/openvdb/tools/DenseSparseTools.h new file mode 100755 index 0000000..3c0f9c8 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/DenseSparseTools.h @@ -0,0 +1,1259 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_TOOLS_DENSESPARSETOOLS_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_DENSESPARSETOOLS_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include "Dense.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Selectively extract and transform data from a dense grid, producing a +/// sparse tree with leaf nodes only (e.g. create a tree from the square +/// of values greater than a cutoff.) +/// @param dense A dense grid that acts as a data source +/// @param functor A functor that selects and transforms data for output +/// @param background The background value of the resulting sparse grid +/// @param threaded Option to use threaded or serial code path +/// @return @c Ptr to tree with the valuetype and configuration defined +/// by typedefs in the @c functor. +/// @note To achieve optimal sparsity consider calling the prune() +/// method on the result. +/// @note To simply copy the all the data from a Dense grid to a +/// OpenVDB Grid, use tools::copyFromDense() for better performance. +/// +/// The type of the sparse tree is determined by the specified OtpType +/// functor by means of the typedef OptType::ResultTreeType +/// +/// The OptType function is responsible for the the transformation of +/// dense grid data to sparse grid data on a per-voxel basis. +/// +/// Only leaf nodes with active values will be added to the sparse grid. +/// +/// The OpType must struct that defines a the minimal form +/// @code +/// struct ExampleOp +/// { +/// typedef DesiredTreeType ResultTreeType; +/// +/// template +/// void OpType::operator() (const DenseValueType a, const IndexOrCoord& ijk, +/// ResultTreeType::LeafNodeType* leaf); +/// }; +/// @endcode +/// +/// For example, to generate a tree with valuesOn +/// at locations greater than a given maskvalue +/// @code +/// template +/// class Rule +/// { +/// public: +/// // Standard tree type (e.g. BoolTree or FloatTree in openvdb.h) +/// typedef typename openvdb::tree::Tree4::Type ResultTreeType; +/// +/// typedef typename ResultTreeType::LeafNodeType ResultLeafNodeType; +/// typedef typename ResultTreeType::ValueType ResultValueType; +/// +/// typedef float DenseValueType; +/// +/// typedef vdbmath::Coord::ValueType Index; +/// +/// Rule(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; +/// }; +/// @endcode +template +typename OpType::ResultTreeType::Ptr +extractSparseTree(const DenseType& dense, const OpType& functor, + const typename OpType::ResultValueType& background, + bool threaded = true); + +/// This struct that aids template resoluion of a new tree type +/// has the same configuration at TreeType, but the ValueType from +/// DenseType. +template struct DSConverter { + typedef typename DenseType::ValueType ValueType; + + typedef typename TreeType::template ValueConverter::Type Type; +}; + + +/// @brief Copy data from the intersection of a sparse tree and a dense input grid. +/// The resulting tree has the same configuration as the sparse tree, but holds +/// the data type specified by the dense input. +/// @param dense A dense grid that acts as a data source +/// @param mask The active voxels and tiles intersected with dense define iteration mask +/// @param background The background value of the resulting sparse grid +/// @param threaded Option to use threaded or serial code path +/// @return @c Ptr to tree with the same configuration as @c mask but of value type +/// defined by @c dense. +template +typename DSConverter::Type::Ptr +extractSparseTreeWithMask(const DenseType& dense, + const MaskTreeType& mask, + const typename DenseType::ValueType& background, + bool threaded = true); + + +/// Apply a point-wise functor to the intersection of a dense grid and a given bounding box +/// @param dense A dense grid to be transformed +/// @param bbox Index space bounding box, define region where the transformation is applied +/// @param op A functor that acts on the dense grid value type +/// @param parallel Used to select multithreaded or single threaded +/// Minimally, the @c op class has to support a @c operator() method, +/// @code +/// // Square values in a grid +/// struct Op +/// { +/// ValueT operator()(const ValueT& in) const +/// { +/// // do work +/// ValueT result = in * in; +/// +/// return result; +/// } +/// }; +/// @endcode +/// NB: only Dense grids with memory layout zxy are supported +template +void transformDense(Dense& dense, + const openvdb::CoordBBox& bbox, const OpType& op, bool parallel=true); + +/// We currrently support the following operations when compositing sparse +/// data into a dense grid. +enum DSCompositeOp { + DS_OVER, DS_ADD, DS_SUB, DS_MIN, DS_MAX, DS_MULT, DS_SET +}; + +/// @brief Composite data from a sparse tree into a dense array of the same value type. +/// @param dense Dense grid to be altered by the operation +/// @param source Sparse data to composite into @c dense +/// @param alpha Sparse Alpha mask used in compositing operations. +/// @param beta Constant multiplier on src +/// @param strength Constant multiplier on alpha +/// @param threaded Enable threading for this operation. +template +void compositeToDense(Dense& dense, + const TreeT& source, + const TreeT& alpha, + const typename TreeT::ValueType beta, + const typename TreeT::ValueType strength, + bool threaded = true); + + +/// @brief Functor-based class used to extract data that satisfies some +/// criteria defined by the embedded @c OpType functor. The @c extractSparseTree +/// function wraps this class. +template +class SparseExtractor +{ + +public: + + typedef openvdb::math::Coord::ValueType Index; + + typedef typename DenseType::ValueType DenseValueType; + typedef typename OpType::ResultTreeType ResultTreeType; + typedef typename ResultTreeType::ValueType ResultValueType; + typedef typename ResultTreeType::LeafNodeType ResultLeafNodeType; + typedef typename ResultTreeType::template ValueConverter::Type BoolTree; + + typedef tbb::blocked_range3d Range3d; + + +private: + + const DenseType& mDense; + const OpType& mFunctor; + const ResultValueType mBackground; + const openvdb::math::CoordBBox mBBox; + const Index mWidth; + typename ResultTreeType::Ptr mMask; + openvdb::math::Coord mMin; + + +public: + + SparseExtractor(const DenseType& dense, const OpType& functor, + const ResultValueType background) : + mDense(dense), mFunctor(functor), + mBackground(background), + mBBox(dense.bbox()), + mWidth(ResultLeafNodeType::DIM), + mMask( new ResultTreeType(mBackground)) + {} + + + SparseExtractor(const DenseType& dense, + const openvdb::math::CoordBBox& bbox, + const OpType& functor, + const ResultValueType background) : + mDense(dense), mFunctor(functor), + mBackground(background), + mBBox(bbox), + mWidth(ResultLeafNodeType::DIM), + mMask( new ResultTreeType(mBackground)) + { + // mBBox must be inside the coordinate rage of the dense grid + if (!dense.bbox().isInside(mBBox)) { + OPENVDB_THROW(ValueError, "Data extraction window out of bound"); + } + } + + + SparseExtractor(SparseExtractor& other, tbb::split): + mDense(other.mDense), mFunctor(other.mFunctor), + mBackground(other.mBackground), mBBox(other.mBBox), + mWidth(other.mWidth), + mMask(new ResultTreeType(mBackground)), + mMin(other.mMin) + {} + + typename ResultTreeType::Ptr extract(bool threaded = true) { + + + // Construct 3D range of leaf nodes that + // intersect mBBox. + + // Snap the bbox to nearest leaf nodes min and max + + openvdb::math::Coord padded_min = mBBox.min(); + openvdb::math::Coord padded_max = mBBox.max(); + + + padded_min &= ~(mWidth - 1); + padded_max &= ~(mWidth - 1); + + padded_max[0] += mWidth - 1; + padded_max[1] += mWidth - 1; + padded_max[2] += mWidth - 1; + + + // number of leaf nodes in each direction + // division by leaf width, e.g. 8 in most cases + + const Index xleafCount = ( padded_max.x() - padded_min.x() + 1 ) / mWidth; + const Index yleafCount = ( padded_max.y() - padded_min.y() + 1 ) / mWidth; + const Index zleafCount = ( padded_max.z() - padded_min.z() + 1 ) / mWidth; + + mMin = padded_min; + + + Range3d leafRange(0, xleafCount, 1, + 0, yleafCount, 1, + 0, zleafCount, 1); + + + // Iterate over the leafnodes applying *this as a functor. + if (threaded) { + tbb::parallel_reduce(leafRange, *this); + } else { + (*this)(leafRange); + } + + return mMask; + } + + + void operator()(const Range3d& range) { + + ResultLeafNodeType* leaf = NULL; + + // Unpack the range3d item. + const Index imin = range.pages().begin(); + const Index imax = range.pages().end(); + + const Index jmin = range.rows().begin(); + const Index jmax = range.rows().end(); + + const Index kmin = range.cols().begin(); + const Index kmax = range.cols().end(); + + + // loop over all the canidate leafs. Adding only those with 'true' values + // to the tree + + for (Index i = imin; i < imax; ++i) { + for (Index j = jmin; j < jmax; ++j) { + for (Index k = kmin; k < kmax; ++k) { + + // Calculate the origin of canidate leaf + const openvdb::math::Coord origin = + mMin + openvdb::math::Coord(mWidth * i, + mWidth * j, + mWidth * k ); + + if (leaf == NULL) { + leaf = new ResultLeafNodeType(origin, mBackground); + } else { + leaf->setOrigin(origin); + leaf->fill(mBackground); + leaf->setValuesOff(); + } + + // The bouding box for this leaf + + openvdb::math::CoordBBox localBBox = leaf->getNodeBoundingBox(); + + // Shrink to the intersection with mBBox (i.e. the dense + // volume) + + localBBox.intersect(mBBox); + + // Early out for non-intersecting leafs + + if (localBBox.empty()) continue; + + + const openvdb::math::Coord start = localBBox.getStart(); + const openvdb::math::Coord end = localBBox.getEnd(); + + // Order the looping to respect the memory layout in + // the Dense source + + if (mDense.memoryLayout() == openvdb::tools::LayoutZYX) { + + openvdb::math::Coord ijk; + Index offset; + const DenseValueType* dp; + for (ijk[0] = start.x(); ijk[0] < end.x(); ++ijk[0] ) { + for (ijk[1] = start.y(); ijk[1] < end.y(); ++ijk[1] ) { + for (ijk[2] = start.z(), + offset = ResultLeafNodeType::coordToOffset(ijk), + dp = &mDense.getValue(ijk); + ijk[2] < end.z(); ++ijk[2], ++offset, ++dp) { + + mFunctor(*dp, offset, leaf); + } + } + } + + } else { + + openvdb::math::Coord ijk; + const DenseValueType* dp; + for (ijk[2] = start.z(); ijk[2] < end.z(); ++ijk[2]) { + for (ijk[1] = start.y(); ijk[1] < end.y(); ++ijk[1]) { + for (ijk[0] = start.x(), + dp = &mDense.getValue(ijk); + ijk[0] < end.x(); ++ijk[0], ++dp) { + + mFunctor(*dp, ijk, leaf); + + } + } + } + } + + // Only add non-empty leafs (empty is defined as all inactive) + + if (!leaf->isEmpty()) { + mMask->addLeaf(*leaf); + leaf = NULL; + } + + } + } + } + + // Clean up an unused leaf. + + if (leaf != NULL) delete leaf; + }; + + void join(SparseExtractor& rhs) { + mMask->merge(*rhs.mMask); + } +}; // class SparseExtractor + + +template +typename OpType::ResultTreeType::Ptr +extractSparseTree(const DenseType& dense, const OpType& functor, + const typename OpType::ResultValueType& background, + bool threaded) +{ + + // Construct the mask using a parallel reduce patern. + // Each thread computes disjoint mask-trees. The join merges + // into a single tree. + + SparseExtractor extractor(dense, functor, background); + + return extractor.extract(threaded); +} + + +/// @brief Functor-based class used to extract data from a dense grid, at +/// the index-space intersection with a suppiled maks in the form of a sparse tree. +/// The @c extractSparseTreeWithMask function wraps this class. +template +class SparseMaskedExtractor +{ +public: + + typedef typename DSConverter::Type _ResultTreeType; + typedef _ResultTreeType ResultTreeType; + typedef typename ResultTreeType::LeafNodeType ResultLeafNodeType; + typedef typename ResultTreeType::ValueType ResultValueType; + typedef ResultValueType DenseValueType; + + typedef typename ResultTreeType::template ValueConverter::Type BoolTree; + typedef typename BoolTree::LeafCIter BoolLeafCIter; + typedef std::vector BoolLeafVec; + + + SparseMaskedExtractor(const DenseType& dense, + const ResultValueType& background, + const BoolLeafVec& leafVec + ): + mDense(dense), mBackground(background), mBBox(dense.bbox()), + mLeafVec(leafVec), + mResult(new ResultTreeType(mBackground)) + {} + + + + SparseMaskedExtractor(const SparseMaskedExtractor& other, tbb::split): + mDense(other.mDense), mBackground(other.mBackground), mBBox(other.mBBox), + mLeafVec(other.mLeafVec), mResult( new ResultTreeType(mBackground)) + {} + + typename ResultTreeType::Ptr extract(bool threaded = true) { + + tbb::blocked_range range(0, mLeafVec.size()); + + if (threaded) { + tbb::parallel_reduce(range, *this); + } else { + (*this)(range); + } + + return mResult; + } + + + // Used in looping over leaf nodes in the masked grid + // and using the active mask to select data to + void operator()(const tbb::blocked_range& range) { + + ResultLeafNodeType* leaf = NULL; + + + // loop over all the canidate leafs. Adding only those with 'true' values + // to the tree + + for (size_t idx = range.begin(); idx < range.end(); ++ idx) { + + const typename BoolTree::LeafNodeType* boolLeaf = mLeafVec[idx]; + + // The bouding box for this leaf + + openvdb::math::CoordBBox localBBox = boolLeaf->getNodeBoundingBox(); + + // Shrink to the intersection with the dense volume + + localBBox.intersect(mBBox); + + // Early out if there was no intersection + + if (localBBox.empty()) continue; + + // Reset or allocate the target leaf + + if (leaf == NULL) { + leaf = new ResultLeafNodeType(boolLeaf->origin(), mBackground); + } else { + leaf->setOrigin(boolLeaf->origin()); + leaf->fill(mBackground); + leaf->setValuesOff(); + } + + + // Iterate over the intersecting bounding box + // copying active values to the result tree + + const openvdb::math::Coord start = localBBox.getStart(); + const openvdb::math::Coord end = localBBox.getEnd(); + + + openvdb::math::Coord ijk; + + if (mDense.memoryLayout() == openvdb::tools::LayoutZYX + && boolLeaf->isDense()) { + + Index offset; + const DenseValueType* src; + for (ijk[0] = start.x(); ijk[0] < end.x(); ++ijk[0] ) { + for (ijk[1] = start.y(); ijk[1] < end.y(); ++ijk[1] ) { + for (ijk[2] = start.z(), + offset = ResultLeafNodeType::coordToOffset(ijk), + src = &mDense.getValue(ijk); + ijk[2] < end.z(); ++ijk[2], ++offset, ++src) { + + // copy into leaf + leaf->setValueOn(offset, *src); + } + + } + } + + } else { + + Index offset; + for (ijk[0] = start.x(); ijk[0] < end.x(); ++ijk[0] ) { + for (ijk[1] = start.y(); ijk[1] < end.y(); ++ijk[1] ) { + for (ijk[2] = start.z(), + offset = ResultLeafNodeType::coordToOffset(ijk); + ijk[2] < end.z(); ++ijk[2], ++offset) { + + if (boolLeaf->isValueOn(offset)) { + const ResultValueType denseValue = mDense.getValue(ijk); + leaf->setValueOn(offset, denseValue); + } + } + } + } + } + // Only add non-empty leafs (empty is defined as all inactive) + + if (!leaf->isEmpty()) { + mResult->addLeaf(*leaf); + leaf = NULL; + } + } + + // Clean up an unused leaf. + + if (leaf != NULL) delete leaf; + }; + + void join(SparseMaskedExtractor& rhs) { + mResult->merge(*rhs.mResult); + } + + +private: + const DenseType& mDense; + const ResultValueType mBackground; + const openvdb::math::CoordBBox& mBBox; + const BoolLeafVec& mLeafVec; + + typename ResultTreeType::Ptr mResult; + +}; // class SparseMaskedExtractor + + +/// @brief a simple utility class used by @c extractSparseTreeWithMask +template +struct ExtractAll +{ + typedef _ResultTreeType ResultTreeType; + typedef typename ResultTreeType::LeafNodeType ResultLeafNodeType; + + template inline void + operator()(const DenseValueType& a, const CoordOrIndex& offset, ResultLeafNodeType* leaf) const + { + leaf->setValueOn(offset, a); + } +}; + + +template +typename DSConverter::Type::Ptr +extractSparseTreeWithMask(const DenseType& dense, + const MaskTreeType& mask, + const typename DenseType::ValueType& background, + bool threaded) +{ + typedef SparseMaskedExtractor LeafExtractor; + typedef typename LeafExtractor::DenseValueType DenseValueType; + typedef typename LeafExtractor::ResultTreeType ResultTreeType; + typedef typename LeafExtractor::BoolLeafVec BoolLeafVec; + typedef typename LeafExtractor::BoolTree BoolTree; + typedef typename LeafExtractor::BoolLeafCIter BoolLeafCIter; + typedef ExtractAll ExtractionRule; + + // Use Bool tree to hold the topology + + BoolTree boolTree(mask, false, TopologyCopy()); + + // Construct an array of pointers to the mask leafs. + + const size_t leafCount = boolTree.leafCount(); + BoolLeafVec leafarray(leafCount); + BoolLeafCIter leafiter = boolTree.cbeginLeaf(); + for (size_t n = 0; n != leafCount; ++n, ++leafiter) { + leafarray[n] = leafiter.getLeaf(); + } + + + // Extract the data that is masked leaf nodes in the mask. + + LeafExtractor leafextractor(dense, background, leafarray); + typename ResultTreeType::Ptr resultTree = leafextractor.extract(threaded); + + + // Extract data that is masked by tiles in the mask. + + + // Loop over the mask tiles, extracting the data into new trees. + // These trees will be leaf-orthogonal to the leafTree (i.e. no leaf + // nodes will overlap). Merge these trees into the result. + + typename MaskTreeType::ValueOnCIter tileIter(mask); + tileIter.setMaxDepth(MaskTreeType::ValueOnCIter::LEAF_DEPTH - 1); + + // Return the leaf tree if the mask had no tiles + + if (!tileIter) return resultTree; + + ExtractionRule allrule; + + // Loop over the tiles in series, but the actual data extraction + // is in parallel. + + CoordBBox bbox; + for ( ; tileIter; ++tileIter) { + + // Find the intersection of the tile with the dense grid. + + tileIter.getBoundingBox(bbox); + bbox.intersect(dense.bbox()); + + if (bbox.empty()) continue; + + SparseExtractor copyData(dense, bbox, allrule, background); + typename ResultTreeType::Ptr fromTileTree = copyData.extract(threaded); + resultTree->merge(*fromTileTree); + } + + return resultTree; +} + + +/// @brief Class that applies a functor to the index space intersection +/// of a prescribed bounding box and the dense grid. +/// NB: This class only supports DenseGrids with ZYX memory layout. +template +class DenseTransformer +{ +public: + + typedef _ValueT ValueT; + typedef Dense DenseT; + typedef openvdb::math::Coord::ValueType IntType; + typedef tbb::blocked_range2d RangeType; + + +private: + + DenseT& mDense; + const OpType& mOp; + openvdb::math::CoordBBox mBBox; + +public: + DenseTransformer(DenseT& dense, + const openvdb::math::CoordBBox& bbox, + const OpType& functor): + mDense(dense), mOp(functor), mBBox(dense.bbox()) + { + // The interation space is the intersection of the + // input bbox and the index-space covered by the dense grid + mBBox.intersect(bbox); + } + + DenseTransformer(const DenseTransformer& other) : + mDense(other.mDense), mOp(other.mOp), mBBox(other.mBBox) {} + + void apply(bool threaded = true) { + + // Early out if the interation space is empty + + if (mBBox.empty()) return; + + + const openvdb::math::Coord start = mBBox.getStart(); + const openvdb::math::Coord end = mBBox.getEnd(); + + // The interation range only the slower two directions. + const RangeType range(start.x(), end.x(), 1, + start.y(), end.y(), 1); + + if (threaded) { + tbb::parallel_for(range, *this); + } else { + (*this)(range); + } + } + + void operator()(const RangeType& range) const { + + // The stride in the z-direction. + // Note: the bbox is [inclusive, inclusive] + + const size_t zlength = size_t(mBBox.max().z() - mBBox.min().z() + 1); + + const IntType imin = range.rows().begin(); + const IntType imax = range.rows().end(); + const IntType jmin = range.cols().begin(); + const IntType jmax = range.cols().end(); + + + openvdb::math::Coord xyz(imin, jmin, mBBox.min().z()); + for (xyz[0] = imin; xyz[0] != imax; ++xyz[0]) { + for (xyz[1] = jmin; xyz[1] != jmax; ++xyz[1]) { + + mOp.transform(mDense, xyz, zlength); + } + } + } +}; // class DenseTransformer + + +/// @brief a wrapper struct used to avoid unnecessary computation of +/// memory access from @c Coord when all offsets are guaranteed to be +/// within the dense grid. +template +struct ContiguousOp +{ + ContiguousOp(const PointWiseOp& op) : mOp(op){}; + + typedef Dense DenseT; + inline void transform(DenseT& dense, openvdb::math::Coord& ijk, size_t size) const + { + ValueT* dp = const_cast(&dense.getValue(ijk)); + + for (size_t offset = 0; offset < size; ++offset) { + dp[offset] = mOp(dp[offset]); + } + } + + const PointWiseOp mOp; +}; + + +/// Apply a point-wise functor to the intersection of a dense grid and a given bounding box +template +void +transformDense(Dense& dense, + const openvdb::CoordBBox& bbox, + const PointwiseOpT& functor, bool parallel) +{ + typedef ContiguousOp OpT; + + // Convert the Op so it operates on an contiguous line in memory + + OpT op(functor); + + // Apply to the index space intersection in the dense grid + DenseTransformer transformer(dense, bbox, op); + transformer.apply(parallel); +} + + +template +class SparseToDenseCompositor +{ + +public: + typedef _TreeT TreeT; + typedef typename TreeT::ValueType ValueT; + typedef typename TreeT::LeafNodeType LeafT; + typedef typename TreeT::template ValueConverter::Type BoolTreeT; + typedef typename BoolTreeT::LeafNodeType BoolLeafT; + typedef Dense DenseT; + typedef openvdb::math::Coord::ValueType Index; + typedef tbb::blocked_range3d Range3d; + + SparseToDenseCompositor(DenseT& dense, const TreeT& source, const TreeT& alpha, + const ValueT beta, const ValueT strength) : + mDense(dense), mSource(source), mAlpha(alpha), mBeta(beta), mStrength(strength) + {} + + SparseToDenseCompositor(const SparseToDenseCompositor& other): + mDense(other.mDense), mSource(other.mSource), mAlpha(other.mAlpha), + mBeta(other.mBeta), mStrength(other.mStrength) {}; + + + + void sparseComposite(bool threaded) { + + const ValueT beta = mBeta; + const ValueT strenght = mStrength; + + // construct a tree that defines the iteration space + + BoolTreeT boolTree(mSource, false /*background*/, openvdb::TopologyCopy()); + boolTree.topologyUnion(mAlpha); + + // Coposite regions that are represented by leafnodes in either mAlpha or mSource + // Parallelize over bool-leafs + + openvdb::tree::LeafManager boolLeafs(boolTree); + boolLeafs.foreach(*this, threaded); + + // Composite tregions that are represnted by tiles + // Parallelize within each tile. + + typename BoolTreeT::ValueOnCIter citer = boolTree.cbeginValueOn(); + citer.setMaxDepth(BoolTree::ValueOnCIter::LEAF_DEPTH - 1); + + if (!citer) return; + + typename tree::ValueAccessor alphaAccessor(mAlpha); + typename tree::ValueAccessor sourceAccessor(mSource); + + for (; citer; ++citer) { + + const openvdb::math::Coord org = citer.getCoord(); + + // Early out if both alpha and source are zero in this tile. + + const ValueT alphaValue = alphaAccessor.getValue(org); + const ValueT sourceValue = sourceAccessor.getValue(org); + + if (openvdb::math::isZero(alphaValue) && + openvdb::math::isZero(sourceValue) ) continue; + + // Compute overlap of tile with the dense grid + + openvdb::math::CoordBBox localBBox = citer.getBoundingBox(); + localBBox.intersect(mDense.bbox()); + + // Early out if there is no intersection + + if (localBBox.empty()) continue; + + // Composite the tile-uniform values into the dense grid. + compositeFromTile(mDense, localBBox, sourceValue, + alphaValue, beta, strenght, threaded); + } + } + + // Composites leaf values where the alpha values are active. + // Used in sparseComposite + void inline operator()(const BoolLeafT& boolLeaf, size_t /*i*/) const + { + + typedef UniformLeaf ULeaf; + openvdb::math::CoordBBox localBBox = boolLeaf.getNodeBoundingBox(); + localBBox.intersect(mDense.bbox()); + + // Early out for non-overlapping leafs + + if (localBBox.empty()) return; + + const openvdb::math::Coord org = boolLeaf.origin(); + const LeafT* alphaLeaf = mAlpha.probeLeaf(org); + const LeafT* sourceLeaf = mSource.probeLeaf(org); + + if (!sourceLeaf) { + + // Create a source leaf proxy with the correct value + ULeaf uniformSource(mSource.getValue(org)); + + if (!alphaLeaf) { + + // Create an alpha leaf proxy with the correct value + ULeaf uniformAlpha(mAlpha.getValue(org)); + + compositeFromLeaf(mDense, localBBox, uniformSource, uniformAlpha, + mBeta, mStrength); + } else { + + compositeFromLeaf(mDense, localBBox, uniformSource, *alphaLeaf, + mBeta, mStrength); + } + } else { + if (!alphaLeaf) { + + // Create an alpha leaf proxy with the correct value + ULeaf uniformAlpha(mAlpha.getValue(org)); + + compositeFromLeaf(mDense, localBBox, *sourceLeaf, uniformAlpha, + mBeta, mStrength); + } else { + + compositeFromLeaf(mDense, localBBox, *sourceLeaf, *alphaLeaf, + mBeta, mStrength); + } + } + } + // i.e. it assumes that all valueOff Alpha voxels have value 0. + + template + inline static void compositeFromLeaf(DenseT& dense, const openvdb::math::CoordBBox& bbox, + const LeafT1& source, const LeafT2& alpha, + const ValueT beta, const ValueT strength) + { + typedef openvdb::math::Coord::ValueType IntType; + + const ValueT sbeta = strength * beta; + openvdb::math::Coord ijk = bbox.min(); + + + if (alpha.isDense() /*all active values*/) { + + // Optial path for dense alphaLeaf + const IntType size = bbox.max().z() + 1 - bbox.min().z(); + + for (ijk[0] = bbox.min().x(); ijk[0] < bbox.max().x() + 1; ++ijk[0]) { + for (ijk[1] = bbox.min().y(); ijk[1] < bbox.max().y() + 1; ++ijk[1]) { + + ValueT* d = const_cast(&dense.getValue(ijk)); + const ValueT* a = &alpha.getValue(ijk); + const ValueT* s = &source.getValue(ijk); + + for (IntType idx = 0; idx < size; ++idx) { + d[idx] = CompositeMethod::apply(d[idx], a[idx], s[idx], + strength, beta, sbeta); + } + } + } + } else { + + // AlphaLeaf has non-active cells. + + for (ijk[0] = bbox.min().x(); ijk[0] < bbox.max().x() + 1; ++ijk[0]) { + for (ijk[1] = bbox.min().y(); ijk[1] < bbox.max().y() + 1; ++ijk[1]) { + for (ijk[2] = bbox.min().z(); ijk[2] < bbox.max().z() + 1; ++ijk[2]) { + + if (alpha.isValueOn(ijk)) { + + dense.setValue(ijk, + CompositeMethod::apply(dense.getValue(ijk), + alpha.getValue(ijk), source.getValue(ijk), + strength, beta, sbeta) + ); + } + } + } + } + } + } + + inline static void compositeFromTile(DenseT& dense, openvdb::math::CoordBBox& bbox, + const ValueT& sourceValue, const ValueT& alphaValue, + const ValueT& beta, const ValueT& strength, + bool threaded) + { + + typedef UniformTransformer TileTransformer; + TileTransformer functor(sourceValue, alphaValue, beta, strength); + + // Transform the data inside the bbox according to the TileTranformer. + + transformDense(dense, bbox, functor, threaded); + + } + + + void denseComposite(bool threaded) + { + /// Construct a range that corresponds to the + /// bounding box of the dense volume + const openvdb::math::CoordBBox& bbox = mDense.bbox(); + + Range3d range(bbox.min().x(), bbox.max().x(), LeafT::DIM, + bbox.min().y(), bbox.max().y(), LeafT::DIM, + bbox.min().z(), bbox.max().z(), LeafT::DIM); + + // Interate over the range, compositing into + // the dense grid using value accessors for + // sparse the grids. + if (threaded) { + tbb::parallel_for(range, *this); + } else { + (*this)(range); + } + + } + + // Composites a dense region using value accessors + // into a dense grid + void inline operator()(const Range3d& range) const + { + // Use value accessors to alpha and source + + typename tree::ValueAccessor alphaAccessor(mAlpha); + typename tree::ValueAccessor sourceAccessor(mSource); + + const ValueT strength = mStrength; + const ValueT beta = mBeta; + const ValueT sbeta = strength * beta; + + // Unpack the range3d item. + const Index imin = range.pages().begin(); + const Index imax = range.pages().end(); + + const Index jmin = range.rows().begin(); + const Index jmax = range.rows().end(); + + const Index kmin = range.cols().begin(); + const Index kmax = range.cols().end(); + + openvdb::Coord ijk; + for (ijk[0] = imin; ijk[0] < imax; ++ijk[0]) { + for (ijk[1] = jmin; ijk[1] < jmax; ++ijk[1]) { + for (ijk[2] = kmin; ijk[2] < kmax; ++ijk[2]) { + const ValueT d_old = mDense.getValue(ijk); + const ValueT& alpha = alphaAccessor.getValue(ijk); + const ValueT& src = sourceAccessor.getValue(ijk); + + mDense.setValue(ijk, CompositeMethod::apply(d_old, alpha, src, + strength, beta, sbeta)); + } + } + } + + } + + +private: + + // Internal class that wraps the templated composite method + // for use when both alpha and source are uniform over + // a prescribed bbox (e.g. a tile). + class UniformTransformer + { + public: + UniformTransformer(const ValueT& source, const ValueT& alpha, const ValueT& _beta, + const ValueT& _strength) : + mSource(source), mAlpha(alpha), mBeta(_beta), + mStrength(_strength), mSBeta(_strength * _beta) + {} + + ValueT operator()(const ValueT& input) const + { + return CompositeMethod::apply(input, mAlpha, mSource, + mStrength, mBeta, mSBeta); + } + + private: + const ValueT mSource; const ValueT mAlpha; const ValueT mBeta; + const ValueT mStrength; const ValueT mSBeta; + }; + + + // Simple Class structure that mimics a leaf + // with uniform values. Holds LeafT::DIM copies + // of a value in an array. + struct Line { ValueT mValues[LeafT::DIM]; }; + class UniformLeaf : private Line + { + public: + typedef typename LeafT::ValueType ValueT; + + typedef Line BaseT; + UniformLeaf(const ValueT& value) : BaseT(init(value)){}; + + static const BaseT init(const ValueT& value) { + BaseT tmp; + for (openvdb::Index i = 0; i < LeafT::DIM; ++i) { + tmp.mValues[i] = value; + } + return tmp; + } + + bool isDense() const { return true; } + bool isValueOn(openvdb::math::Coord&) const { return true; } + + inline const ValueT& getValue(const openvdb::math::Coord& ) const + {return BaseT::mValues[0];} + }; + +private: + DenseT& mDense; + const TreeT& mSource; + const TreeT& mAlpha; + ValueT mBeta; + ValueT mStrength; +}; // class SparseToDenseCompositor + + +namespace ds +{ + //@{ + /// @brief Point wise methods used to apply various compositing operations. + template + struct OpOver + { + static inline ValueT apply(const ValueT u, const ValueT alpha, + const ValueT v, + const ValueT strength, + const ValueT beta, + const ValueT /*sbeta*/) + { return (u + strength * alpha * (beta * v - u)); } + }; + + + template + struct OpAdd + { + static inline ValueT apply(const ValueT u, const ValueT alpha, + const ValueT v, + const ValueT /*strength*/, + const ValueT /*beta*/, + const ValueT sbeta) + { return (u + sbeta * alpha * v); } + }; + + template + struct OpSub + { + static inline ValueT apply(const ValueT u, const ValueT alpha, + const ValueT v, + const ValueT /*strength*/, + const ValueT /*beta*/, + const ValueT sbeta) + { return (u - sbeta * alpha * v); } + }; + + template + struct OpMin + { + static inline ValueT apply(const ValueT u, const ValueT alpha, + const ValueT v, + const ValueT s /*trength*/, + const ValueT beta, + const ValueT /*sbeta*/) + { return ( ( 1 - s * alpha) * u + s * alpha * std::min(u, beta * v) ); } + }; + + + template + struct OpMax + { + static inline ValueT apply(const ValueT u, const ValueT alpha, + const ValueT v, + const ValueT s/*trength*/, + const ValueT beta, + const ValueT /*sbeta*/) + { return ( ( 1 - s * alpha ) * u + s * alpha * std::min(u, beta * v) ); } + }; + + template + struct OpMult + { + static inline ValueT apply(const ValueT u, const ValueT alpha, + const ValueT v, + const ValueT s/*trength*/, + const ValueT /*beta*/, + const ValueT sbeta) + { return ( ( 1 + alpha * (sbeta * v - s)) * u ); } + }; + //@} + + //@{ + /// Translator that converts an enum to compositing functor types + template + struct CompositeFunctorTranslator{}; + + template + struct CompositeFunctorTranslator{ typedef OpOver OpT; }; + + template + struct CompositeFunctorTranslator{ typedef OpAdd OpT; }; + + template + struct CompositeFunctorTranslator{ typedef OpSub OpT; }; + + template + struct CompositeFunctorTranslator{ typedef OpMin OpT; }; + + template + struct CompositeFunctorTranslator{ typedef OpMax OpT; }; + + template + struct CompositeFunctorTranslator{ typedef OpMult OpT; }; + //@} + +} // namespace ds + + +template +void compositeToDense( + Dense& dense, + const TreeT& source, const TreeT& alpha, + const typename TreeT::ValueType beta, + const typename TreeT::ValueType strength, + bool threaded) +{ + typedef typename TreeT::ValueType ValueT; + typedef ds::CompositeFunctorTranslator Translator; + typedef typename Translator::OpT Method; + + if (openvdb::math::isZero(strength)) return; + + SparseToDenseCompositor tool(dense, source, alpha, beta, strength); + + if (openvdb::math::isZero(alpha.background()) && + openvdb::math::isZero(source.background())) + { + // Use the sparsity of (alpha U source) as the iteration space. + tool.sparseComposite(threaded); + } else { + // Use the bounding box of dense as the iteration space. + tool.denseComposite(threaded); + } +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif //OPENVDB_TOOLS_DENSESPARSETOOLS_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/Filter.h b/openvdb_2_3_0_library/openvdb/tools/Filter.h new file mode 100755 index 0000000..268148f --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/Filter.h @@ -0,0 +1,459 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @author Ken Museth +/// +/// @file Filter.h +/// +/// @brief Filtering of VDB volumes. Note that only the values in the +/// grid are changed, not its topology! All operations can optionally +/// be masked with another grid that acts as an alpha-mask. + +#ifndef OPENVDB_TOOLS_FILTER_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_FILTER_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Interpolation.h" + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Volume filtering (e.g., diffusion) with optional alpha masking +/// +/// @note Only the values in the grid are changed, not its topology! +template::Type, + typename InterruptT = util::NullInterrupter> +class Filter +{ +public: + typedef GridT GridType; + typedef MaskT MaskType; + typedef typename GridType::TreeType TreeType; + typedef typename TreeType::LeafNodeType LeafType; + typedef typename GridType::ValueType ValueType; + typedef typename MaskType::ValueType AlphaType; + typedef typename tree::LeafManager LeafManagerType; + typedef typename LeafManagerType::LeafRange RangeType; + typedef typename LeafManagerType::BufferType BufferType; + BOOST_STATIC_ASSERT(boost::is_floating_point::value); + + /// Constructor + /// @param grid Grid to be filtered. + /// @param interrupt Optional interrupter. + Filter(GridT& grid, InterruptT* interrupt = NULL) + : mGrid(&grid) + , mTask(0) + , mInterrupter(interrupt) + , mMask(NULL) + , mGrainSize(1) + , mMinMask(0) + , mMaxMask(1) + , mInvertMask(false) + { + } + + /// @brief Shallow copy constructor called by tbb::parallel_for() + /// threads during filtering. + /// @param other The other Filter from which to copy. + Filter(const Filter& other) + : mGrid(other.mGrid) + , mTask(other.mTask) + , mInterrupter(other.mInterrupter) + , mMask(other.mMask) + , mGrainSize(other.mGrainSize) + , mMinMask(other.mMinMask) + , mMaxMask(other.mMaxMask) + , mInvertMask(other.mInvertMask) + { + } + + /// @return the grain-size used for multi-threading + int getGrainSize() const { return mGrainSize; } + /// @brief Set the grain-size used for multi-threading. + /// @note A grainsize of 0 or less disables multi-threading! + void setGrainSize(int grainsize) { mGrainSize = grainsize; } + + /// @brief Return the minimum value of the mask to be used for the + /// derivation of a smooth alpha value. + AlphaType minMask() const { return mMinMask; } + /// @brief Return the maximum value of the mask to be used for the + /// derivation of a smooth alpha value. + AlphaType maxMask() const { return mMaxMask; } + /// @brief Define the range for the (optional) scalar mask. + /// @param min Minimum value of the range. + /// @param max Maximum value of the range. + /// @details Mask values outside the range are clamped to zero or one, and + /// values inside the range map smoothly to 0->1 (unless the mask is inverted). + /// @throw ValueError if @a min is not smaller then @a max. + void setMaskRange(AlphaType min, AlphaType max) + { + if (!(min < max)) OPENVDB_THROW(ValueError, "Invalid mask range (expects min < max)"); + mMinMask = min; + mMaxMask = max; + } + + /// @brief Return true if the mask is inverted, i.e. min->max in the + /// original mask maps to 1->0 in the inverted alpha mask. + bool isMaskInverted() const { return mInvertMask; } + /// @brief Invert the optional mask, i.e. min->max in the original + /// mask maps to 1->0 in the inverted alpha mask. + void invertMask(bool invert=true) { mInvertMask = invert; } + + /// @brief One iteration of a fast separable mean-value (i.e. box) filter. + /// @param width The width of the mean-value filter is 2*width+1 voxels. + /// @param iterations Number of times the mean-value filter is applied. + /// @param mask Optional alpha mask. + void mean(int width = 1, int iterations = 1, const MaskType* mask = NULL); + + /// @brief One iteration of a fast separable gaussian filter. + /// + /// @note This is approximated as 4 iterations of a separable mean filter + /// which typically leads an approximation that's better than 95%! + /// @param width The width of the mean-value filter is 2*width+1 voxels. + /// @param iterations Numer of times the mean-value filter is applied. + /// @param mask Optional alpha mask. + void gaussian(int width = 1, int iterations = 1, const MaskType* mask = NULL); + + /// @brief One iteration of a median-value filter + /// + /// @note This filter is not separable and is hence relatively slow! + /// @param width The width of the mean-value filter is 2*width+1 voxels. + /// @param iterations Numer of times the mean-value filter is applied. + /// @param mask Optional alpha mask. + void median(int width = 1, int iterations = 1, const MaskType* mask = NULL); + + /// Offsets (i.e. adds) a constant value to all active voxels. + /// @param offset Offset in the same units as the grid. + /// @param mask Optional alpha mask. + void offset(ValueType offset, const MaskType* mask = NULL); + + /// @brief Used internally by tbb::parallel_for() + /// @param range Range of LeafNodes over which to multi-thread. + /// + /// @warning Never call this method directly! + void operator()(const RangeType& range) const + { + if (mTask) mTask(const_cast(this), range); + else OPENVDB_THROW(ValueError, "task is undefined - call median(), mean(), etc."); + } + +private: + typedef typename TreeType::LeafNodeType LeafT; + typedef typename LeafT::ValueOnIter VoxelIterT; + typedef typename LeafT::ValueOnCIter VoxelCIterT; + typedef typename tree::LeafManager::BufferType BufferT; + typedef typename RangeType::Iterator LeafIterT; + + void cook(LeafManagerType& leafs); + + // Private class to derive the normalized alpha mask + struct AlphaMask + { + AlphaMask(const GridType& grid, const MaskType& mask, + AlphaType min, AlphaType max, bool invert) + : mAcc(mask.tree()), mSampler(mAcc, mask.transform(), grid.transform()), + mMin(min), mInvNorm(1/(max-min)), mInvert(invert) + { + assert(min < max); + } + inline bool operator()(const Coord& xyz, AlphaType& a, AlphaType& b) const + { + a = mSampler(xyz); + const AlphaType t = (a-mMin)*mInvNorm; + a = t > 0 ? t < 1 ? (3-2*t)*t*t : 1 : 0;//smooth mapping to 0->1 + b = 1 - a; + if (mInvert) std::swap(a,b); + return a>0; + } + typedef typename MaskType::ConstAccessor AccType; + AccType mAcc; + tools::DualGridSampler mSampler; + const AlphaType mMin, mInvNorm; + const bool mInvert; + }; + + template + struct Avg { + Avg(const GridT* grid, Int32 w) : + acc(grid->tree()), width(w), frac(1/ValueType(2*w+1)) {} + ValueType operator()(Coord xyz) { + ValueType sum = zeroVal(); + Int32& i = xyz[Axis], j = i + width; + for (i -= width; i <= j; ++i) sum += acc.getValue(xyz); + return sum*frac; + } + typename GridT::ConstAccessor acc; + const Int32 width; + const ValueType frac; + }; + + // Private filter methods called by tbb::parallel_for threads + template + void doBox( const RangeType& r, Int32 w); + void doBoxX(const RangeType& r, Int32 w) { this->doBox >(r,w); } + void doBoxZ(const RangeType& r, Int32 w) { this->doBox >(r,w); } + void doBoxY(const RangeType& r, Int32 w) { this->doBox >(r,w); } + void doMedian(const RangeType&, int); + void doOffset(const RangeType&, ValueType); + /// @return true if the process was interrupted + bool wasInterrupted(); + + GridType* mGrid; + typename boost::function mTask; + InterruptT* mInterrupter; + const MaskType* mMask; + int mGrainSize; + AlphaType mMinMask, mMaxMask; + bool mInvertMask; +}; // end of Filter class + +//////////////////////////////////////// + +template +inline void +Filter::mean(int width, int iterations, const MaskType* mask) +{ + mMask = mask; + + if (mInterrupter) mInterrupter->start("Applying mean filter"); + + const int w = std::max(1, width); + + LeafManagerType leafs(mGrid->tree(), 1, mGrainSize==0); + + for (int i=0; iwasInterrupted(); ++i) { + mTask = boost::bind(&Filter::doBoxX, _1, _2, w); + this->cook(leafs); + + mTask = boost::bind(&Filter::doBoxY, _1, _2, w); + this->cook(leafs); + + mTask = boost::bind(&Filter::doBoxZ, _1, _2, w); + this->cook(leafs); + } + + if (mInterrupter) mInterrupter->end(); +} + +template +inline void +Filter::gaussian(int width, int iterations, const MaskType* mask) +{ + mMask = mask; + + if (mInterrupter) mInterrupter->start("Applying gaussian filter"); + + const int w = std::max(1, width); + + LeafManagerType leafs(mGrid->tree(), 1, mGrainSize==0); + + for (int i=0; iwasInterrupted(); ++n) { + mTask = boost::bind(&Filter::doBoxX, _1, _2, w); + this->cook(leafs); + + mTask = boost::bind(&Filter::doBoxY, _1, _2, w); + this->cook(leafs); + + mTask = boost::bind(&Filter::doBoxZ, _1, _2, w); + this->cook(leafs); + } + } + + if (mInterrupter) mInterrupter->end(); +} + + +template +inline void +Filter::median(int width, int iterations, const MaskType* mask) +{ + mMask = mask; + + if (mInterrupter) mInterrupter->start("Applying median filter"); + + LeafManagerType leafs(mGrid->tree(), 1, mGrainSize==0); + + mTask = boost::bind(&Filter::doMedian, _1, _2, std::max(1, width)); + for (int i=0; iwasInterrupted(); ++i) this->cook(leafs); + + if (mInterrupter) mInterrupter->end(); +} + +template +inline void +Filter::offset(ValueType value, const MaskType* mask) +{ + mMask = mask; + + if (mInterrupter) mInterrupter->start("Applying offset"); + + LeafManagerType leafs(mGrid->tree(), 0, mGrainSize==0); + + mTask = boost::bind(&Filter::doOffset, _1, _2, value); + this->cook(leafs); + + if (mInterrupter) mInterrupter->end(); +} + +//////////////////////////////////////// + + +/// Private method to perform the task (serial or threaded) and +/// subsequently swap the leaf buffers. +template +inline void +Filter::cook(LeafManagerType& leafs) +{ + if (mGrainSize>0) { + tbb::parallel_for(leafs.leafRange(mGrainSize), *this); + } else { + (*this)(leafs.leafRange()); + } + leafs.swapLeafBuffer(1, mGrainSize==0); +} + +/// One dimensional convolution of a separable box filter +template +template +inline void +Filter::doBox(const RangeType& range, Int32 w) +{ + this->wasInterrupted(); + AvgT avg(mGrid, w); + if (mMask) { + AlphaType a, b; + AlphaMask alpha(*mGrid, *mMask, mMinMask, mMaxMask, mInvertMask); + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + const Coord xyz = iter.getCoord(); + if (alpha(xyz, a, b)) { + buffer.setValue(iter.pos(), ValueType(b*(*iter) + a*avg(xyz))); + } + } + } + } else { + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + buffer.setValue(iter.pos(), avg(iter.getCoord())); + } + } + } +} + +/// Performs simple but slow median-value diffusion +template +inline void +Filter::doMedian(const RangeType& range, int width) +{ + this->wasInterrupted(); + typename math::DenseStencil stencil(*mGrid, width);//creates local cache! + if (mMask) { + AlphaType a, b; + AlphaMask alpha(*mGrid, *mMask, mMinMask, mMaxMask, mInvertMask); + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + if (alpha(iter.getCoord(), a, b)) { + stencil.moveTo(iter); + buffer.setValue(iter.pos(), ValueType(b*(*iter) + a*stencil.median())); + } + } + } + } else { + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + stencil.moveTo(iter); + buffer.setValue(iter.pos(), stencil.median()); + } + } + } +} + +/// Offsets the values by a constant +template +inline void +Filter::doOffset(const RangeType& range, ValueType offset) +{ + this->wasInterrupted(); + if (mMask) { + AlphaType a, b; + AlphaMask alpha(*mGrid, *mMask, mMinMask, mMaxMask, mInvertMask); + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) { + if (alpha(iter.getCoord(), a, b)) iter.setValue(ValueType(*iter + a*offset)); + } + } + } else { + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) { + iter.setValue(*iter + offset); + } + } + } +} + +template +inline bool +Filter::wasInterrupted() +{ + if (util::wasInterrupted(mInterrupter)) { + tbb::task::self().cancel_group_execution(); + return true; + } + return false; +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_FILTER_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/GridOperators.h b/openvdb_2_3_0_library/openvdb/tools/GridOperators.h new file mode 100755 index 0000000..8fa838c --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/GridOperators.h @@ -0,0 +1,1090 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file GridOperators.h +/// +/// @brief Applies an operator on an input grid to produce an output +/// grid with the same topology but potentially different value type. + +#ifndef OPENVDB_TOOLS_GRID_OPERATORS_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_GRID_OPERATORS_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief VectorToScalarConverter::Type is the type of a grid +/// having the same tree configuration as VectorGridType but a scalar value type, T, +/// where T is the type of the original vector components. +/// @details For example, VectorToScalarConverter::Type is equivalent to DoubleGrid. +template struct VectorToScalarConverter { + typedef typename VectorGridType::ValueType::value_type VecComponentValueT; + typedef typename VectorGridType::template ValueConverter::Type Type; +}; + +/// @brief ScalarToVectorConverter::Type is the type of a grid +/// having the same tree configuration as ScalarGridType but value type Vec3 +/// where T is ScalarGridType::ValueType. +/// @details For example, ScalarToVectorConverter::Type is equivalent to Vec3DGrid. +template struct ScalarToVectorConverter { + typedef math::Vec3 VectorValueT; + typedef typename ScalarGridType::template ValueConverter::Type Type; +}; + + +/// @brief Compute the Closest-Point Transform (CPT) from a distance field. +/// @return a new vector-valued grid with the same numerical precision as the input grid +/// (for example, if the input grid is a DoubleGrid, the output grid will be a Vec3DGrid) +/// @details When a mask grid is specified, the solution is calculated only in +/// the intersection of the mask active topology and the input active topology +/// independent of the transforms associated with either grid. +/// @note The current implementation assumes all the input distance values +/// are represented by leaf voxels and not tiles. This is true for all +/// narrow-band level sets, which this class was originally developed for. +/// In the future we will expand this class to also handle tile values. +template inline +typename ScalarToVectorConverter::Type::Ptr +cpt(const GridType& grid, bool threaded, InterruptT* interrupt); + +template inline +typename ScalarToVectorConverter::Type::Ptr +cpt(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); + +template inline +typename ScalarToVectorConverter::Type::Ptr +cpt(const GridType& grid, bool threaded = true) +{ + return cpt(grid, threaded, NULL); +} + +template inline +typename ScalarToVectorConverter::Type::Ptr +cpt(const GridType& grid, const MaskT& mask, bool threaded = true) +{ + return cpt(grid, mask, threaded, NULL); +} + + +/// @brief Compute the curl of the given vector-valued grid. +/// @return a new vector-valued grid +/// @details When a mask grid is specified, the solution is calculated only in +/// the intersection of the mask active topology and the input active topology +/// independent of the transforms associated with either grid. +template inline +typename GridType::Ptr +curl(const GridType& grid, bool threaded, InterruptT* interrupt); + +template inline +typename GridType::Ptr +curl(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); + +template inline +typename GridType::Ptr +curl(const GridType& grid, bool threaded = true) +{ + return curl(grid, threaded, NULL); +} + +template inline +typename GridType::Ptr +curl(const GridType& grid, const MaskT& mask, bool threaded = true) +{ + return curl(grid, mask, threaded, NULL); +} + + +/// @brief Compute the divergence of the given vector-valued grid. +/// @return a new scalar-valued grid with the same numerical precision as the input grid +/// (for example, if the input grid is a Vec3DGrid, the output grid will be a DoubleGrid) +/// @details When a mask grid is specified, the solution is calculated only in +/// the intersection of the mask active topology and the input active topology +/// independent of the transforms associated with either grid. +template inline +typename VectorToScalarConverter::Type::Ptr +divergence(const GridType& grid, bool threaded, InterruptT* interrupt); + +template inline +typename VectorToScalarConverter::Type::Ptr +divergence(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); + +template inline +typename VectorToScalarConverter::Type::Ptr +divergence(const GridType& grid, bool threaded = true) +{ + return divergence(grid, threaded, NULL); +} + +template inline +typename VectorToScalarConverter::Type::Ptr +divergence(const GridType& grid, const MaskT& mask, bool threaded = true) +{ + return divergence(grid, mask, threaded, NULL); +} + + +/// @brief Compute the gradient of the given scalar grid. +/// @return a new vector-valued grid with the same numerical precision as the input grid +/// (for example, if the input grid is a DoubleGrid, the output grid will be a Vec3DGrid) +/// @details When a mask grid is specified, the solution is calculated only in +/// the intersection of the mask active topology and the input active topology +/// independent of the transforms associated with either grid. +template inline +typename ScalarToVectorConverter::Type::Ptr +gradient(const GridType& grid, bool threaded, InterruptT* interrupt); + +template inline +typename ScalarToVectorConverter::Type::Ptr +gradient(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); + +template inline +typename ScalarToVectorConverter::Type::Ptr +gradient(const GridType& grid, bool threaded = true) +{ + return gradient(grid, threaded, NULL); +} + +template inline +typename ScalarToVectorConverter::Type::Ptr +gradient(const GridType& grid, const MaskT& mask, bool threaded = true) +{ + return gradient(grid, mask, threaded, NULL); +} + + +/// @brief Compute the Laplacian of the given scalar grid. +/// @return a new scalar grid +/// @details When a mask grid is specified, the solution is calculated only in +/// the intersection of the mask active topology and the input active topology +/// independent of the transforms associated with either grid. +template inline +typename GridType::Ptr +laplacian(const GridType& grid, bool threaded, InterruptT* interrupt); + +template inline +typename GridType::Ptr +laplacian(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); + +template inline +typename GridType::Ptr +laplacian(const GridType& grid, bool threaded = true) +{ + return laplacian(grid, threaded, NULL); +} + +template inline +typename GridType::Ptr +laplacian(const GridType& grid, const MaskT mask, bool threaded = true) +{ + return laplacian(grid, mask, threaded, NULL); +} + + +/// @brief Compute the mean curvature of the given grid. +/// @return a new grid +/// @details When a mask grid is specified, the solution is calculated only in +/// the intersection of the mask active topology and the input active topology +/// independent of the transforms associated with either grid. +template inline +typename GridType::Ptr +meanCurvature(const GridType& grid, bool threaded, InterruptT* interrupt); + +template inline +typename GridType::Ptr +meanCurvature(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); + +template inline +typename GridType::Ptr +meanCurvature(const GridType& grid, bool threaded = true) +{ + return meanCurvature(grid, threaded, NULL); +} + +template inline +typename GridType::Ptr +meanCurvature(const GridType& grid, const MaskT& mask, bool threaded = true) +{ + return meanCurvature(grid, mask, threaded, NULL); +} + + +/// @brief Compute the magnitudes of the vectors of the given vector-valued grid. +/// @return a new scalar-valued grid with the same numerical precision as the input grid +/// (for example, if the input grid is a Vec3DGrid, the output grid will be a DoubleGrid) +/// @details When a mask grid is specified, the solution is calculated only in +/// the intersection of the mask active topology and the input active topology +/// independent of the transforms associated with either grid. +template inline +typename VectorToScalarConverter::Type::Ptr +magnitude(const GridType& grid, bool threaded, InterruptT* interrupt); + +template inline +typename VectorToScalarConverter::Type::Ptr +magnitude(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); + +template inline +typename VectorToScalarConverter::Type::Ptr +magnitude(const GridType& grid, bool threaded = true) +{ + return magnitude(grid, threaded, NULL); +} + +template inline +typename VectorToScalarConverter::Type::Ptr +magnitude(const GridType& grid, const MaskT& mask, bool threaded = true) +{ + return magnitude(grid, mask, threaded, NULL); +} + + +/// @brief Normalize the vectors of the given vector-valued grid. +/// @return a new vector-valued grid +/// @details When a mask grid is specified, the solution is calculated only in +/// the intersection of the mask active topology and the input active topology +/// independent of the transforms associated with either grid. +template inline +typename GridType::Ptr +normalize(const GridType& grid, bool threaded, InterruptT* interrupt); + +template inline +typename GridType::Ptr +normalize(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); + +template inline +typename GridType::Ptr +normalize(const GridType& grid, bool threaded = true) +{ + return normalize(grid, threaded, NULL); +} + +template inline +typename GridType::Ptr +normalize(const GridType& grid, const MaskT& mask, bool threaded = true) +{ + return normalize(grid, mask, threaded, NULL); +} + + +//////////////////////////////////////// + + +namespace gridop { + +/// @brief ToBoolGrid::Type is the type of a grid having the same +/// tree hierarchy as grid type T but a value type of bool. +/// @details For example, ToBoolGrid::Type is equivalent to BoolGrid. +template +struct ToBoolGrid { + typedef Grid::Type> Type; +}; + + +/// @brief Apply an operator on an input grid to produce an output grid +/// with the same topology but a possibly different value type. +/// @details To facilitate inlining, this class is also templated on a Map type. +/// +/// @note This is a helper class and should never be used directly. +/// +/// @note The current implementation assumes all the input +/// values are represented by leaf voxels and not tiles. In the +/// future we will expand this class to also handle tile values. +template< + typename InGridT, + typename MaskGridType, + typename OutGridT, + typename MapT, + typename OperatorT, + typename InterruptT = util::NullInterrupter> +class GridOperator +{ +public: + typedef typename OutGridT::TreeType OutTreeT; + typedef typename OutTreeT::LeafNodeType OutLeafT; + typedef typename tree::LeafManager LeafManagerT; + + GridOperator(const InGridT& grid, const MaskGridType* mask, const MapT& map, + InterruptT* interrupt = NULL): + mAcc(grid.getConstAccessor()), mMap(map), mInterrupt(interrupt), mMask(mask) + { + } + + virtual ~GridOperator() {} + typename OutGridT::Ptr process(bool threaded = true) + { + if (mInterrupt) mInterrupt->start("Processing grid"); + + // Derive background value of the output grid + typename InGridT::TreeType tmp(mAcc.tree().background()); + typename OutGridT::ValueType backg = OperatorT::result(mMap, tmp, math::Coord(0)); + + // output tree = topology copy of input tree! + typename OutTreeT::Ptr tree(new OutTreeT(mAcc.tree(), backg, TopologyCopy())); + + + // create grid with output tree and unit transform + typename OutGridT::Ptr result(new OutGridT(tree)); + + // Modify the solution area if a mask was supplied. + if (mMask) { + result->topologyIntersection(*mMask); + } + + // transform of output grid = transform of input grid + result->setTransform(math::Transform::Ptr(new math::Transform( mMap.copy() ))); + + LeafManagerT leafManager(*tree); + + if (threaded) { + tbb::parallel_for(leafManager.leafRange(), *this); + } else { + (*this)(leafManager.leafRange()); + } + + if (mInterrupt) mInterrupt->end(); + return result; + } + + /// @brief Iterate sequentially over LeafNodes and voxels in the output + /// grid and compute the laplacian using a valueAccessor for the + /// input grid. + /// + /// @note Never call this public method directly - it is called by + /// TBB threads only! + void operator()(const typename LeafManagerT::LeafRange& range) const + { + if (util::wasInterrupted(mInterrupt)) tbb::task::self().cancel_group_execution(); + + for (typename LeafManagerT::LeafRange::Iterator leaf=range.begin(); leaf; ++leaf) { + for (typename OutLeafT::ValueOnIter value=leaf->beginValueOn(); value; ++value) { + value.setValue(OperatorT::result(mMap, mAcc, value.getCoord())); + } + } + } + +protected: + typedef typename InGridT::ConstAccessor AccessorT; + mutable AccessorT mAcc; + const MapT& mMap; + InterruptT* mInterrupt; + const MaskGridType* mMask; +}; // end of GridOperator class + +} // namespace gridop + + +//////////////////////////////////////// + + +/// @brief Compute the closest-point transform of a scalar grid. +template< + typename InGridT, + typename MaskGridType = typename gridop::ToBoolGrid::Type, + typename InterruptT = util::NullInterrupter> +class Cpt +{ +public: + typedef InGridT InGridType; + typedef typename ScalarToVectorConverter::Type OutGridType; + + Cpt(const InGridType& grid, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) + { + } + + Cpt(const InGridType& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) + { + } + + typename OutGridType::Ptr process(bool threaded = true, bool useWorldTransform = true) + { + Functor functor(mInputGrid, mMask, threaded, useWorldTransform, mInterrupt); + processTypedMap(mInputGrid.transform(), functor); + if (functor.mOutputGrid) functor.mOutputGrid->setVectorType(VEC_CONTRAVARIANT_ABSOLUTE); + return functor.mOutputGrid; + } + +private: + struct IsOpT + { + template + static typename OutGridType::ValueType + result(const MapT& map, const AccT& acc, const Coord& xyz) + { + return math::CPT::result(map, acc, xyz); + } + }; + struct WsOpT + { + template + static typename OutGridType::ValueType + result(const MapT& map, const AccT& acc, const Coord& xyz) + { + return math::CPT_RANGE::result(map, acc, xyz); + } + }; + struct Functor + { + Functor(const InGridType& grid, const MaskGridType* mask, + bool threaded, bool worldspace, InterruptT* interrupt) + : mThreaded(threaded) + , mWorldSpace(worldspace) + , mInputGrid(grid) + , mInterrupt(interrupt) + , mMask(mask) + {} + + template + void operator()(const MapT& map) + { + if (mWorldSpace) { + gridop::GridOperator + op(mInputGrid, mMask, map, mInterrupt); + mOutputGrid = op.process(mThreaded); // cache the result + } else { + gridop::GridOperator + op(mInputGrid, mMask, map, mInterrupt); + mOutputGrid = op.process(mThreaded); // cache the result + } + } + const bool mThreaded; + const bool mWorldSpace; + const InGridType& mInputGrid; + typename OutGridType::Ptr mOutputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; + }; + const InGridType& mInputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; +}; // end of Cpt class + + +//////////////////////////////////////// + + +/// @brief Compute the curl of a vector grid. +template< + typename GridT, + typename MaskGridType = typename gridop::ToBoolGrid::Type, + typename InterruptT = util::NullInterrupter> +class Curl +{ +public: + typedef GridT InGridType; + typedef GridT OutGridType; + + Curl(const GridT& grid, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) + { + } + + Curl(const GridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) + { + } + + typename GridT::Ptr process(bool threaded = true) + { + Functor functor(mInputGrid, mMask, threaded, mInterrupt); + processTypedMap(mInputGrid.transform(), functor); + if (functor.mOutputGrid) functor.mOutputGrid->setVectorType(VEC_COVARIANT); + return functor.mOutputGrid; + } + +private: + struct Functor + { + Functor(const GridT& grid, const MaskGridType* mask, + bool threaded, InterruptT* interrupt): + mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} + + template + void operator()(const MapT& map) + { + typedef math::Curl OpT; + gridop::GridOperator + op(mInputGrid, mMask, map, mInterrupt); + mOutputGrid = op.process(mThreaded); // cache the result + } + + const bool mThreaded; + const GridT& mInputGrid; + typename GridT::Ptr mOutputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; + }; // Private Functor + + const GridT& mInputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; +}; // end of Curl class + + +//////////////////////////////////////// + + +/// @brief Compute the divergence of a vector grid. +template< + typename InGridT, + typename MaskGridType = typename gridop::ToBoolGrid::Type, + typename InterruptT = util::NullInterrupter> +class Divergence +{ +public: + typedef InGridT InGridType; + typedef typename VectorToScalarConverter::Type OutGridType; + + Divergence(const InGridT& grid, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) + { + } + + Divergence(const InGridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) + { + } + + typename OutGridType::Ptr process(bool threaded = true) + { + if (mInputGrid.getGridClass() == GRID_STAGGERED) { + Functor functor(mInputGrid, mMask, threaded, mInterrupt); + processTypedMap(mInputGrid.transform(), functor); + return functor.mOutputGrid; + } else { + Functor functor(mInputGrid, mMask, threaded, mInterrupt); + processTypedMap(mInputGrid.transform(), functor); + return functor.mOutputGrid; + } + } + +protected: + template + struct Functor + { + Functor(const InGridT& grid, const MaskGridType* mask, + bool threaded, InterruptT* interrupt): + mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} + + template + void operator()(const MapT& map) + { + typedef math::Divergence OpT; + gridop::GridOperator + op(mInputGrid, mMask, map, mInterrupt); + mOutputGrid = op.process(mThreaded); // cache the result + } + + const bool mThreaded; + const InGridType& mInputGrid; + typename OutGridType::Ptr mOutputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; + }; // Private Functor + + const InGridType& mInputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; +}; // end of Divergence class + + +//////////////////////////////////////// + + +/// @brief Compute the gradient of a scalar grid. +template< + typename InGridT, + typename MaskGridType = typename gridop::ToBoolGrid::Type, + typename InterruptT = util::NullInterrupter> +class Gradient +{ +public: + typedef InGridT InGridType; + typedef typename ScalarToVectorConverter::Type OutGridType; + + Gradient(const InGridT& grid, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) + { + } + + Gradient(const InGridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) + { + } + + typename OutGridType::Ptr process(bool threaded = true) + { + Functor functor(mInputGrid, mMask, threaded, mInterrupt); + processTypedMap(mInputGrid.transform(), functor); + if (functor.mOutputGrid) functor.mOutputGrid->setVectorType(VEC_COVARIANT); + return functor.mOutputGrid; + } + +protected: + struct Functor + { + Functor(const InGridT& grid, const MaskGridType* mask, + bool threaded, InterruptT* interrupt): + mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} + + template + void operator()(const MapT& map) + { + typedef math::Gradient OpT; + gridop::GridOperator + op(mInputGrid, mMask, map, mInterrupt); + mOutputGrid = op.process(mThreaded); // cache the result + } + + const bool mThreaded; + const InGridT& mInputGrid; + typename OutGridType::Ptr mOutputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; + }; // Private Functor + + const InGridT& mInputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; +}; // end of Gradient class + + +//////////////////////////////////////// + + +template< + typename GridT, + typename MaskGridType = typename gridop::ToBoolGrid::Type, + typename InterruptT = util::NullInterrupter> +class Laplacian +{ +public: + typedef GridT InGridType; + typedef GridT OutGridType; + + Laplacian(const GridT& grid, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) + { + } + + Laplacian(const GridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) + { + } + + typename GridT::Ptr process(bool threaded = true) + { + Functor functor(mInputGrid, mMask, threaded, mInterrupt); + processTypedMap(mInputGrid.transform(), functor); + if (functor.mOutputGrid) functor.mOutputGrid->setVectorType(VEC_COVARIANT); + return functor.mOutputGrid; + } + +protected: + struct Functor + { + Functor(const GridT& grid, const MaskGridType* mask, bool threaded, InterruptT* interrupt): + mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} + + template + void operator()(const MapT& map) + { + typedef math::Laplacian OpT; + gridop::GridOperator + op(mInputGrid, mMask, map); + mOutputGrid = op.process(mThreaded); // cache the result + } + + const bool mThreaded; + const GridT& mInputGrid; + typename GridT::Ptr mOutputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; + }; // Private Functor + + const GridT& mInputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; +}; // end of Laplacian class + + +//////////////////////////////////////// + + +template< + typename GridT, + typename MaskGridType = typename gridop::ToBoolGrid::Type, + typename InterruptT = util::NullInterrupter> +class MeanCurvature +{ +public: + typedef GridT InGridType; + typedef GridT OutGridType; + + MeanCurvature(const GridT& grid, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) + { + } + + MeanCurvature(const GridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) + { + } + + typename GridT::Ptr process(bool threaded = true) + { + Functor functor(mInputGrid, mMask, threaded, mInterrupt); + processTypedMap(mInputGrid.transform(), functor); + if (functor.mOutputGrid) functor.mOutputGrid->setVectorType(VEC_COVARIANT); + return functor.mOutputGrid; + } + +protected: + struct Functor + { + Functor(const GridT& grid, const MaskGridType* mask, bool threaded, InterruptT* interrupt): + mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} + + template + void operator()(const MapT& map) + { + typedef math::MeanCurvature OpT; + gridop::GridOperator + op(mInputGrid, mMask, map); + mOutputGrid = op.process(mThreaded); // cache the result + } + + const bool mThreaded; + const GridT& mInputGrid; + typename GridT::Ptr mOutputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; + }; // Private Functor + + const GridT& mInputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; +}; // end of MeanCurvature class + + +//////////////////////////////////////// + + +template< + typename InGridT, + typename MaskGridType = typename gridop::ToBoolGrid::Type, + typename InterruptT = util::NullInterrupter> +class Magnitude +{ +public: + typedef InGridT InGridType; + typedef typename VectorToScalarConverter::Type OutGridType; + + Magnitude(const InGridType& grid, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) + { + } + + Magnitude(const InGridType& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) + { + } + + typename OutGridType::Ptr process(bool threaded = true) + { + Functor functor(mInputGrid, mMask, threaded, mInterrupt); + processTypedMap(mInputGrid.transform(), functor); + return functor.mOutputGrid; + } + +protected: + struct OpT + { + template + static typename OutGridType::ValueType + result(const MapT&, const AccT& acc, const Coord& xyz) { return acc.getValue(xyz).length();} + }; + struct Functor + { + Functor(const InGridT& grid, const MaskGridType* mask, + bool threaded, InterruptT* interrupt): + mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} + + template + void operator()(const MapT& map) + { + gridop::GridOperator + op(mInputGrid, mMask, map); + mOutputGrid = op.process(mThreaded); // cache the result + } + + const bool mThreaded; + const InGridType& mInputGrid; + typename OutGridType::Ptr mOutputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; + }; // Private Functor + + const InGridType& mInputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; +}; // end of Magnitude class + + +//////////////////////////////////////// + + +template< + typename GridT, + typename MaskGridType = typename gridop::ToBoolGrid::Type, + typename InterruptT = util::NullInterrupter> +class Normalize +{ +public: + typedef GridT InGridType; + typedef GridT OutGridType; + + Normalize(const GridT& grid, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) + { + } + + Normalize(const GridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): + mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) + { + } + + typename GridT::Ptr process(bool threaded = true) + { + Functor functor(mInputGrid, mMask, threaded, mInterrupt); + processTypedMap(mInputGrid.transform(), functor); + if (typename GridT::Ptr outGrid = functor.mOutputGrid) { + const VecType vecType = mInputGrid.getVectorType(); + if (vecType == VEC_COVARIANT) { + outGrid->setVectorType(VEC_COVARIANT_NORMALIZE); + } else { + outGrid->setVectorType(vecType); + } + } + return functor.mOutputGrid; + } + +protected: + struct OpT + { + template + static typename OutGridType::ValueType + result(const MapT&, const AccT& acc, const Coord& xyz) + { + typename OutGridType::ValueType vec = acc.getValue(xyz); + if ( !vec.normalize() ) vec.setZero(); + return vec; + } + }; + struct Functor + { + Functor(const GridT& grid, const MaskGridType* mask, bool threaded, InterruptT* interrupt): + mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} + + template + void operator()(const MapT& map) + { + gridop::GridOperator + op(mInputGrid, mMask,map); + mOutputGrid = op.process(mThreaded); // cache the result + } + + const bool mThreaded; + const GridT& mInputGrid; + typename GridT::Ptr mOutputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; + }; // Private Functor + + const GridT& mInputGrid; + InterruptT* mInterrupt; + const MaskGridType* mMask; +}; // end of Normalize class + + +//////////////////////////////////////// + + +template inline +typename ScalarToVectorConverter::Type::Ptr +cpt(const GridType& grid, bool threaded, InterruptT* interrupt) +{ + Cpt::Type, InterruptT> op(grid, interrupt); + return op.process(threaded); +} + +template inline +typename ScalarToVectorConverter::Type::Ptr +cpt(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) +{ + Cpt op(grid, mask, interrupt); + return op.process(threaded); +} + +template inline +typename GridType::Ptr +curl(const GridType& grid, bool threaded, InterruptT* interrupt) +{ + Curl::Type, InterruptT> op(grid, interrupt); + return op.process(threaded); +} + +template inline +typename GridType::Ptr +curl(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) +{ + Curl op(grid, mask, interrupt); + return op.process(threaded); +} + +template inline +typename VectorToScalarConverter::Type::Ptr +divergence(const GridType& grid, bool threaded, InterruptT* interrupt) +{ + Divergence::Type, InterruptT> + op(grid, interrupt); + return op.process(threaded); +} + +template inline +typename VectorToScalarConverter::Type::Ptr +divergence(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) +{ + Divergence op(grid, mask, interrupt); + return op.process(threaded); +} + +template inline +typename ScalarToVectorConverter::Type::Ptr +gradient(const GridType& grid, bool threaded, InterruptT* interrupt) +{ + Gradient::Type, InterruptT> + op(grid, interrupt); + return op.process(threaded); +} + +template inline +typename ScalarToVectorConverter::Type::Ptr +gradient(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) +{ + Gradient op(grid, mask, interrupt); + return op.process(threaded); +} + +template inline +typename GridType::Ptr +laplacian(const GridType& grid, bool threaded, InterruptT* interrupt) +{ + Laplacian::Type, InterruptT> + op(grid, interrupt); + return op.process(threaded); +} + +template inline +typename GridType::Ptr +laplacian(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) +{ + Laplacian op(grid, mask, interrupt); + return op.process(threaded); +} + +template inline +typename GridType::Ptr +meanCurvature(const GridType& grid, bool threaded, InterruptT* interrupt) +{ + MeanCurvature::Type, InterruptT> + op(grid, interrupt); + return op.process(threaded); +} + +template inline +typename GridType::Ptr +meanCurvature(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) +{ + MeanCurvature op(grid, mask, interrupt); + return op.process(threaded); +} + +template inline +typename VectorToScalarConverter::Type::Ptr +magnitude(const GridType& grid, bool threaded, InterruptT* interrupt) +{ + Magnitude::Type, InterruptT> + op(grid, interrupt); + return op.process(threaded); +} + +template inline +typename VectorToScalarConverter::Type::Ptr +magnitude(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) +{ + Magnitude op(grid, mask, interrupt); + return op.process(threaded); +} + +template inline +typename GridType::Ptr +normalize(const GridType& grid, bool threaded, InterruptT* interrupt) +{ + Normalize::Type, InterruptT> + op(grid, interrupt); + return op.process(threaded); +} + +template inline +typename GridType::Ptr +normalize(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) +{ + Normalize op(grid, mask, interrupt); + return op.process(threaded); +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_GRID_OPERATORS_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/GridTransformer.h b/openvdb_2_3_0_library/openvdb/tools/GridTransformer.h new file mode 100755 index 0000000..9756ed8 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/GridTransformer.h @@ -0,0 +1,986 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file GridTransformer.h +/// @author Peter Cucka + +#ifndef OPENVDB_TOOLS_GRIDTRANSFORMER_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_GRIDTRANSFORMER_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include // for isApproxEqual() +#include +#include "Interpolation.h" +#include "LevelSetRebuild.h" // for doLevelSetRebuild() + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Resample an input grid into an output grid of the same type such that, +/// after resampling, the input and output grids coincide (apart from sampling +/// artifacts), but the output grid's transform is unchanged. +/// @details Specifically, this function resamples the input grid into the output +/// grid's index space, using a sampling kernel like PointSampler, BoxSampler, +/// or QuadraticSampler. +/// @param inGrid the grid to be resampled +/// @param outGrid the grid into which to write the resampled voxel data +/// @param interrupter an object adhering to the util::NullInterrupter interface +/// @par Example: +/// @code +/// // Create an input grid with the default identity transform +/// // and populate it with a level-set sphere. +/// FloatGrid::ConstPtr src = tools::makeSphere(...); +/// // Create an output grid and give it a uniform-scale transform. +/// FloatGrid::Ptr dest = FloatGrid::create(); +/// const float voxelSize = 0.5; +/// dest->setTransform(math::Transform::createLinearTransform(voxelSize)); +/// // Resample the input grid into the output grid, reproducing +/// // the level-set sphere at a smaller voxel size. +/// MyInterrupter interrupter = ...; +/// tools::resampleToMatch(*src, *dest, interrupter); +/// @endcode +template +inline void +resampleToMatch(const GridType& inGrid, GridType& outGrid, Interrupter& interrupter); + +/// @brief Resample an input grid into an output grid of the same type such that, +/// after resampling, the input and output grids coincide (apart from sampling +/// artifacts), but the output grid's transform is unchanged. +/// @details Specifically, this function resamples the input grid into the output +/// grid's index space, using a sampling kernel like PointSampler, BoxSampler, +/// or QuadraticSampler. +/// @param inGrid the grid to be resampled +/// @param outGrid the grid into which to write the resampled voxel data +/// @par Example: +/// @code +/// // Create an input grid with the default identity transform +/// // and populate it with a level-set sphere. +/// FloatGrid::ConstPtr src = tools::makeSphere(...); +/// // Create an output grid and give it a uniform-scale transform. +/// FloatGrid::Ptr dest = FloatGrid::create(); +/// const float voxelSize = 0.5; +/// dest->setTransform(math::Transform::createLinearTransform(voxelSize)); +/// // Resample the input grid into the output grid, reproducing +/// // the level-set sphere at a smaller voxel size. +/// tools::resampleToMatch(*src, *dest); +/// @endcode +template +inline void +resampleToMatch(const GridType& inGrid, GridType& outGrid); + + +//////////////////////////////////////// + + +namespace internal { + +/// @brief A TileSampler wraps a grid sampler of another type (BoxSampler, +/// QuadraticSampler, etc.), and for samples that fall within a given tile +/// of the grid, it returns a cached tile value instead of accessing the grid. +template +class TileSampler: public Sampler +{ +public: + typedef typename TreeT::ValueType ValueT; + + /// @param b the index-space bounding box of a particular grid tile + /// @param tileVal the tile's value + /// @param on the tile's active state + TileSampler(const CoordBBox& b, const ValueT& tileVal, bool on): + mBBox(b.min().asVec3d(), b.max().asVec3d()), mVal(tileVal), mActive(on), mEmpty(false) + { + mBBox.expand(-this->radius()); // shrink the bounding box by the sample radius + mEmpty = mBBox.empty(); + } + + bool sample(const TreeT& inTree, const Vec3R& inCoord, ValueT& result) const + { + if (!mEmpty && mBBox.isInside(inCoord)) { result = mVal; return mActive; } + return Sampler::sample(inTree, inCoord, result); + } + +protected: + BBoxd mBBox; + ValueT mVal; + bool mActive, mEmpty; +}; + + +/// @brief For point sampling, tree traversal is less expensive than testing +/// bounding box membership. +template +struct TileSampler: public PointSampler { + TileSampler(const CoordBBox&, const typename TreeT::ValueType&, bool) {} +}; + +/// @brief For point sampling, tree traversal is less expensive than testing +/// bounding box membership. +template +struct TileSampler: public StaggeredPointSampler { + TileSampler(const CoordBBox&, const typename TreeT::ValueType&, bool) {} +}; + +} // namespace internal + + +//////////////////////////////////////// + + +/// A GridResampler applies a geometric transformation to an +/// input grid using one of several sampling schemes, and stores +/// the result in an output grid. +/// +/// Usage: +/// @code +/// GridResampler resampler(); +/// resampler.transformGrid(xform, inGrid, outGrid); +/// @endcode +/// where @c xform is a functor that implements the following methods: +/// @code +/// bool isAffine() const +/// openvdb::Vec3d transform(const openvdb::Vec3d&) const +/// openvdb::Vec3d invTransform(const openvdb::Vec3d&) const +/// @endcode +/// @note When the transform is affine and can be expressed as a 4 x 4 matrix, +/// a GridTransformer is much more efficient than a GridResampler. +class GridResampler +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::function InterruptFunc; + + GridResampler(): mThreaded(true), mTransformTiles(true) {} + virtual ~GridResampler() {} + + /// Enable or disable threading. (Threading is enabled by default.) + void setThreaded(bool b) { mThreaded = b; } + /// Return @c true if threading is enabled. + bool threaded() const { return mThreaded; } + /// Enable or disable processing of tiles. (Enabled by default, except for level set grids.) + void setTransformTiles(bool b) { mTransformTiles = b; } + /// Return @c true if tile processing is enabled. + bool transformTiles() const { return mTransformTiles; } + + /// @brief Allow processing to be aborted by providing an interrupter object. + /// The interrupter will be queried periodically during processing. + /// @see util/NullInterrupter.h for interrupter interface requirements. + template void setInterrupter(InterrupterType&); + + template + void transformGrid(const Transformer&, + const GridT& inGrid, GridT& outGrid) const; + +protected: + template + void applyTransform(const Transformer&, const GridT& inGrid, GridT& outGrid) const; + + bool interrupt() const { return mInterrupt && mInterrupt(); } + +private: + template + static void transformBBox(const Transformer&, const CoordBBox& inBBox, + const InTreeT& inTree, OutTreeT& outTree, const InterruptFunc&, + const Sampler& = Sampler()); + + template + class RangeProcessor; + + bool mThreaded, mTransformTiles; + InterruptFunc mInterrupt; +}; + + +//////////////////////////////////////// + + +/// @brief A GridTransformer applies a geometric transformation to an +/// input grid using one of several sampling schemes, and stores +/// the result in an output grid. +/// +/// @note GridTransformer is optimized for affine transformations. +/// +/// Usage: +/// @code +/// Mat4R xform = ...; +/// GridTransformer transformer(xform); +/// transformer.transformGrid(inGrid, outGrid); +/// @endcode +/// or +/// @code +/// Vec3R pivot = ..., scale = ..., rotate = ..., translate = ...; +/// GridTransformer transformer(pivot, scale, rotate, translate); +/// transformer.transformGrid(inGrid, outGrid); +/// @endcode +class GridTransformer: public GridResampler +{ +public: + typedef boost::shared_ptr Ptr; + + GridTransformer(const Mat4R& xform); + GridTransformer( + const Vec3R& pivot, + const Vec3R& scale, + const Vec3R& rotate, + const Vec3R& translate, + const std::string& xformOrder = "tsr", + const std::string& rotationOrder = "zyx"); + virtual ~GridTransformer() {} + + const Mat4R& getTransform() const { return mTransform; } + + template + void transformGrid(const GridT& inGrid, GridT& outGrid) const; + +private: + struct MatrixTransform; + + inline void init(const Vec3R& pivot, const Vec3R& scale, + const Vec3R& rotate, const Vec3R& translate, + const std::string& xformOrder, const std::string& rotOrder); + + Vec3R mPivot; + Vec3i mMipLevels; + Mat4R mTransform, mPreScaleTransform, mPostScaleTransform; +}; + + +//////////////////////////////////////// + + +namespace local_util { + +/// @brief Decompose an affine transform into scale, rotation and translation components. +/// @return @c false if the given matrix is not affine or cannot otherwise be decomposed. +/// @todo This is not safe for matrices with shear. +template +inline bool +decompose(const math::Mat4& m, math::Vec3& scale, + math::Vec3& rotate, math::Vec3& translate) +{ + if (!math::isAffine(m)) return false; + + // this is the translation in world space + translate = m.getTranslation(); + // Extract translation. + math::Mat3 temp = m.getMat3(); + + scale.init( + (math::Vec3(1, 0, 0) * temp).length(), + (math::Vec3(0, 1, 0) * temp).length(), + (math::Vec3(0, 0, 1) * temp).length()); + // Extract scale. + temp *= math::scale >(scale).inverse(); + + rotate = math::eulerAngles(temp, math::XYZ_ROTATION); + + if (!rotate.eq(math::Vec3::zero()) && !scale.eq(math::Vec3(scale[0]))) { + // No unique decomposition if scale is nonuniform and rotation is nonzero. + return false; + } + return true; +} + +} // namespace local_util + + +//////////////////////////////////////// + + +/// This class implements the Transformer functor interface (specifically, +/// the isAffine(), transform() and invTransform() methods) for a transform +/// that is expressed as a 4 x 4 matrix. +struct GridTransformer::MatrixTransform +{ + MatrixTransform(): mat(Mat4R::identity()), invMat(Mat4R::identity()) {} + MatrixTransform(const Mat4R& xform): mat(xform), invMat(xform.inverse()) {} + + bool isAffine() const { return math::isAffine(mat); } + + Vec3R transform(const Vec3R& pos) const { return mat.transformH(pos); } + + Vec3R invTransform(const Vec3R& pos) const { return invMat.transformH(pos); } + + Mat4R mat, invMat; +}; + + +//////////////////////////////////////// + + +/// @brief This class implements the Transformer functor interface (specifically, +/// the isAffine(), transform() and invTransform() methods) for a transform +/// that maps an A grid into a B grid's index space such that, after resampling, +/// A's index space and transform match B's index space and transform. +class ABTransform +{ +public: + /// @param aXform the A grid's transform + /// @param bXform the B grid's transform + ABTransform(const math::Transform& aXform, const math::Transform& bXform): + mAXform(aXform), + mBXform(bXform), + mIsAffine(mAXform.isLinear() && mBXform.isLinear()), + mIsIdentity(mIsAffine && mAXform == mBXform) + {} + + bool isAffine() const { return mIsAffine; } + + bool isIdentity() const { return mIsIdentity; } + + openvdb::Vec3R transform(const openvdb::Vec3R& pos) const + { + return mBXform.worldToIndex(mAXform.indexToWorld(pos)); + } + + openvdb::Vec3R invTransform(const openvdb::Vec3R& pos) const + { + return mAXform.worldToIndex(mBXform.indexToWorld(pos)); + } + + const math::Transform& getA() const { return mAXform; } + const math::Transform& getB() const { return mBXform; } + +private: + const math::Transform &mAXform, &mBXform; + const bool mIsAffine; + const bool mIsIdentity; +}; + + +/// The normal entry points for resampling are the resampleToMatch() functions, +/// which correctly handle level set grids under scaling and shearing. +/// doResampleToMatch() is mainly for internal use but is typically faster +/// for level sets, and correct provided that no scaling or shearing is needed. +/// +/// @warning Do not use this function to scale or shear a level set grid. +template +inline void +doResampleToMatch(const GridType& inGrid, GridType& outGrid, Interrupter& interrupter) +{ + ABTransform xform(inGrid.transform(), outGrid.transform()); + + if (Sampler::consistent() && xform.isIdentity()) { + // If the transforms of the input and output are identical, the + // output tree is simply a deep copy of the input tree. + outGrid.setTree(inGrid.tree().copy()); + } else if (xform.isAffine()) { + // If the input and output transforms are both affine, create an + // input to output transform (in:index-to-world * out:world-to-index) + // and use the fast GridTransformer API. + Mat4R mat = xform.getA().baseMap()->getAffineMap()->getMat4() * + ( xform.getB().baseMap()->getAffineMap()->getMat4().inverse() ); + + GridTransformer transformer(mat); + transformer.setInterrupter(interrupter); + + // Transform the input grid and store the result in the output grid. + transformer.transformGrid(inGrid, outGrid); + } else { + // If either the input or the output transform is non-affine, + // use the slower GridResampler API. + GridResampler resampler; + resampler.setInterrupter(interrupter); + + resampler.transformGrid(xform, inGrid, outGrid); + } +} + + +template +inline void +resampleToMatch(const GridType& inGrid, GridType& outGrid, Interrupter& interrupter) +{ + if (inGrid.getGridClass() == GRID_LEVEL_SET) { + // If the input grid is a level set, resample it using the level set rebuild tool. + + if (inGrid.constTransform() == outGrid.constTransform()) { + // If the transforms of the input and output grids are identical, + // the output tree is simply a deep copy of the input tree. + outGrid.setTree(inGrid.tree().copy()); + return; + } + + // If the output grid is a level set, resample the input grid to have the output grid's + // background value. Otherwise, preserve the input grid's background value. + typedef typename GridType::ValueType ValueT; + const ValueT halfWidth = ((outGrid.getGridClass() == openvdb::GRID_LEVEL_SET) + ? ValueT(outGrid.background() * (1.0 / outGrid.voxelSize()[0])) + : ValueT(inGrid.background() * (1.0 / inGrid.voxelSize()[0]))); + + typename GridType::Ptr tempGrid; + try { + tempGrid = doLevelSetRebuild(inGrid, /*iso=*/zeroVal(), + /*exWidth=*/halfWidth, /*inWidth=*/halfWidth, + &outGrid.constTransform(), &interrupter); + } catch (TypeError&) { + // The input grid is classified as a level set, but it has a value type + // that is not supported by the level set rebuild tool. Fall back to + // using the generic resampler. + tempGrid.reset(); + } + if (tempGrid) { + outGrid.setTree(tempGrid->treePtr()); + return; + } + } + + // If the input grid is not a level set, use the generic resampler. + doResampleToMatch(inGrid, outGrid, interrupter); +} + + +template +inline void +resampleToMatch(const GridType& inGrid, GridType& outGrid) +{ + util::NullInterrupter interrupter; + resampleToMatch(inGrid, outGrid, interrupter); +} + + +//////////////////////////////////////// + + +inline +GridTransformer::GridTransformer(const Mat4R& xform): + mPivot(0, 0, 0), + mMipLevels(0, 0, 0), + mTransform(xform), + mPreScaleTransform(Mat4R::identity()), + mPostScaleTransform(Mat4R::identity()) +{ + Vec3R scale, rotate, translate; + if (local_util::decompose(mTransform, scale, rotate, translate)) { + // If the transform can be decomposed into affine components, + // use them to set up a mipmapping-like scheme for downsampling. + init(mPivot, scale, rotate, translate, "srt", "zyx"); + } +} + + +inline +GridTransformer::GridTransformer( + const Vec3R& pivot, const Vec3R& scale, + const Vec3R& rotate, const Vec3R& translate, + const std::string& xformOrder, const std::string& rotOrder): + mPivot(0, 0, 0), + mMipLevels(0, 0, 0), + mPreScaleTransform(Mat4R::identity()), + mPostScaleTransform(Mat4R::identity()) +{ + init(pivot, scale, rotate, translate, xformOrder, rotOrder); +} + + +//////////////////////////////////////// + + +inline void +GridTransformer::init( + const Vec3R& pivot, const Vec3R& scale, + const Vec3R& rotate, const Vec3R& translate, + const std::string& xformOrder, const std::string& rotOrder) +{ + if (xformOrder.size() != 3) { + OPENVDB_THROW(ValueError, "invalid transform order (" + xformOrder + ")"); + } + if (rotOrder.size() != 3) { + OPENVDB_THROW(ValueError, "invalid rotation order (" + rotOrder + ")"); + } + + mPivot = pivot; + + // Scaling is handled via a mipmapping-like scheme of successive + // halvings of the tree resolution, until the remaining scale + // factor is greater than or equal to 1/2. + Vec3R scaleRemainder = scale; + for (int i = 0; i < 3; ++i) { + double s = std::fabs(scale(i)); + if (s < 0.5) { + mMipLevels(i) = int(std::floor(-std::log(s)/std::log(2.0))); + scaleRemainder(i) = scale(i) * (1 << mMipLevels(i)); + } + } + + // Build pre-scale and post-scale transform matrices based on + // the user-specified order of operations. + // Note that we iterate over the transform order string in reverse order + // (e.g., "t", "r", "s", given "srt"). This is because math::Mat matrices + // postmultiply row vectors rather than premultiplying column vectors. + mTransform = mPreScaleTransform = mPostScaleTransform = Mat4R::identity(); + Mat4R* remainder = &mPostScaleTransform; + int rpos, spos, tpos; + rpos = spos = tpos = 3; + for (int ix = 2; ix >= 0; --ix) { // reverse iteration + switch (xformOrder[ix]) { + + case 'r': + rpos = ix; + mTransform.preTranslate(pivot); + remainder->preTranslate(pivot); + + int xpos, ypos, zpos; + xpos = ypos = zpos = 3; + for (int ir = 2; ir >= 0; --ir) { + switch (rotOrder[ir]) { + case 'x': + xpos = ir; + mTransform.preRotate(math::X_AXIS, rotate.x()); + remainder->preRotate(math::X_AXIS, rotate.x()); + break; + case 'y': + ypos = ir; + mTransform.preRotate(math::Y_AXIS, rotate.y()); + remainder->preRotate(math::Y_AXIS, rotate.y()); + break; + case 'z': + zpos = ir; + mTransform.preRotate(math::Z_AXIS, rotate.z()); + remainder->preRotate(math::Z_AXIS, rotate.z()); + break; + } + } + // Reject rotation order strings that don't contain exactly one + // instance of "x", "y" and "z". + if (xpos > 2 || ypos > 2 || zpos > 2) { + OPENVDB_THROW(ValueError, "invalid rotation order (" + rotOrder + ")"); + } + + mTransform.preTranslate(-pivot); + remainder->preTranslate(-pivot); + break; + + case 's': + spos = ix; + mTransform.preTranslate(pivot); + mTransform.preScale(scale); + mTransform.preTranslate(-pivot); + + remainder->preTranslate(pivot); + remainder->preScale(scaleRemainder); + remainder->preTranslate(-pivot); + remainder = &mPreScaleTransform; + break; + + case 't': + tpos = ix; + mTransform.preTranslate(translate); + remainder->preTranslate(translate); + break; + } + } + // Reject transform order strings that don't contain exactly one + // instance of "t", "r" and "s". + if (tpos > 2 || rpos > 2 || spos > 2) { + OPENVDB_THROW(ValueError, "invalid transform order (" + xformOrder + ")"); + } +} + + +//////////////////////////////////////// + + +template +void +GridResampler::setInterrupter(InterrupterType& interrupter) +{ + mInterrupt = boost::bind(&InterrupterType::wasInterrupted, + /*this=*/&interrupter, /*percent=*/-1); +} + + +template +void +GridResampler::transformGrid(const Transformer& xform, + const GridT& inGrid, GridT& outGrid) const +{ + outGrid.setBackground(inGrid.background()); + applyTransform(xform, inGrid, outGrid); +} + + +template +void +GridTransformer::transformGrid(const GridT& inGrid, GridT& outGrid) const +{ + outGrid.setBackground(inGrid.background()); + + if (!Sampler::mipmap() || mMipLevels == Vec3i::zero()) { + // Skip the mipmapping step. + const MatrixTransform xform(mTransform); + applyTransform(xform, inGrid, outGrid); + + } else { + bool firstPass = true; + const typename GridT::ValueType background = inGrid.background(); + typename GridT::Ptr tempGrid = GridT::create(background); + + if (!mPreScaleTransform.eq(Mat4R::identity())) { + firstPass = false; + // Apply the pre-scale transform to the input grid + // and store the result in a temporary grid. + const MatrixTransform xform(mPreScaleTransform); + applyTransform(xform, inGrid, *tempGrid); + } + + // While the scale factor along one or more axes is less than 1/2, + // scale the grid by half along those axes. + Vec3i count = mMipLevels; // # of halvings remaining per axis + while (count != Vec3i::zero()) { + MatrixTransform xform; + xform.mat.setTranslation(mPivot); + xform.mat.preScale(Vec3R( + count.x() ? .5 : 1, count.y() ? .5 : 1, count.z() ? .5 : 1)); + xform.mat.preTranslate(-mPivot); + xform.invMat = xform.mat.inverse(); + + if (firstPass) { + firstPass = false; + // Scale the input grid and store the result in a temporary grid. + applyTransform(xform, inGrid, *tempGrid); + } else { + // Scale the temporary grid and store the result in a transient grid, + // then swap the two and discard the transient grid. + typename GridT::Ptr destGrid = GridT::create(background); + applyTransform(xform, *tempGrid, *destGrid); + tempGrid.swap(destGrid); + } + // (3, 2, 1) -> (2, 1, 0) -> (1, 0, 0) -> (0, 0, 0), etc. + count = math::maxComponent(count - 1, Vec3i::zero()); + } + + // Apply the post-scale transform and store the result in the output grid. + if (!mPostScaleTransform.eq(Mat4R::identity())) { + const MatrixTransform xform(mPostScaleTransform); + applyTransform(xform, *tempGrid, outGrid); + } else { + outGrid.setTree(tempGrid->treePtr()); + } + } +} + + +//////////////////////////////////////// + + +template +class GridResampler::RangeProcessor +{ +public: + typedef typename TreeT::LeafCIter LeafIterT; + typedef typename TreeT::ValueAllCIter TileIterT; + typedef typename tree::IteratorRange LeafRange; + typedef typename tree::IteratorRange TileRange; + typedef typename tree::ValueAccessor InTreeAccessor; + typedef typename tree::ValueAccessor OutTreeAccessor; + + RangeProcessor(const Transformer& xform, const CoordBBox& b, const TreeT& inT, TreeT& outT): + mIsRoot(true), mXform(xform), mBBox(b), + mInTree(inT), mOutTree(&outT), mInAcc(mInTree), mOutAcc(*mOutTree) + {} + + RangeProcessor(const Transformer& xform, const CoordBBox& b, const TreeT& inTree): + mIsRoot(false), mXform(xform), mBBox(b), + mInTree(inTree), mOutTree(new TreeT(inTree.background())), + mInAcc(mInTree), mOutAcc(*mOutTree) + {} + + ~RangeProcessor() { if (!mIsRoot) delete mOutTree; } + + /// Splitting constructor: don't copy the original processor's output tree + RangeProcessor(RangeProcessor& other, tbb::split): + mIsRoot(false), + mXform(other.mXform), + mBBox(other.mBBox), + mInTree(other.mInTree), + mOutTree(new TreeT(mInTree.background())), + mInAcc(mInTree), + mOutAcc(*mOutTree), + mInterrupt(other.mInterrupt) + {} + + void setInterrupt(const InterruptFunc& f) { mInterrupt = f; } + + /// Transform each leaf node in the given range. + void operator()(LeafRange& r) + { + for ( ; r; ++r) { + if (interrupt()) break; + LeafIterT i = r.iterator(); + CoordBBox bbox(i->origin(), i->origin() + Coord(i->dim())); + if (!mBBox.empty()) { + // Intersect the leaf node's bounding box with mBBox. + bbox = CoordBBox( + Coord::maxComponent(bbox.min(), mBBox.min()), + Coord::minComponent(bbox.max(), mBBox.max())); + } + if (!bbox.empty()) { + transformBBox(mXform, bbox, mInAcc, mOutAcc, mInterrupt); + } + } + } + + /// Transform each non-background tile in the given range. + void operator()(TileRange& r) + { + for ( ; r; ++r) { + if (interrupt()) break; + + TileIterT i = r.iterator(); + // Skip voxels and background tiles. + if (!i.isTileValue()) continue; + if (!i.isValueOn() && math::isApproxEqual(*i, mOutTree->background())) continue; + + CoordBBox bbox; + i.getBoundingBox(bbox); + if (!mBBox.empty()) { + // Intersect the tile's bounding box with mBBox. + bbox = CoordBBox( + Coord::maxComponent(bbox.min(), mBBox.min()), + Coord::minComponent(bbox.max(), mBBox.max())); + } + if (!bbox.empty()) { + /// @todo This samples the tile voxel-by-voxel, which is much too slow. + /// Instead, compute the largest axis-aligned bounding box that is + /// contained in the transformed tile (adjusted for the sampler radius) + /// and fill it with the tile value. Then transform the remaining voxels. + internal::TileSampler + sampler(bbox, i.getValue(), i.isValueOn()); + transformBBox(mXform, bbox, mInAcc, mOutAcc, mInterrupt, sampler); + } + } + } + + /// Merge another processor's output tree into this processor's tree. + void join(RangeProcessor& other) + { + if (!interrupt()) mOutTree->merge(*other.mOutTree); + } + +private: + bool interrupt() const { return mInterrupt && mInterrupt(); } + + const bool mIsRoot; // true if mOutTree is the top-level tree + Transformer mXform; + CoordBBox mBBox; + const TreeT& mInTree; + TreeT* mOutTree; + InTreeAccessor mInAcc; + OutTreeAccessor mOutAcc; + InterruptFunc mInterrupt; +}; + + +//////////////////////////////////////// + + +template +void +GridResampler::applyTransform(const Transformer& xform, + const GridT& inGrid, GridT& outGrid) const +{ + typedef typename GridT::TreeType TreeT; + const TreeT& inTree = inGrid.tree(); + TreeT& outTree = outGrid.tree(); + + typedef RangeProcessor RangeProc; + + const GridClass gridClass = inGrid.getGridClass(); + + if (gridClass != GRID_LEVEL_SET && mTransformTiles) { + // Independently transform the tiles of the input grid. + // Note: Tiles in level sets can only be background tiles, and they + // are handled more efficiently with a signed flood fill (see below). + + RangeProc proc(xform, CoordBBox(), inTree, outTree); + proc.setInterrupt(mInterrupt); + + typename RangeProc::TileIterT tileIter = inTree.cbeginValueAll(); + tileIter.setMaxDepth(tileIter.getLeafDepth() - 1); // skip leaf nodes + typename RangeProc::TileRange tileRange(tileIter); + + if (mThreaded) { + tbb::parallel_reduce(tileRange, proc); + } else { + proc(tileRange); + } + } + + CoordBBox clipBBox; + if (gridClass == GRID_LEVEL_SET) { + // Inactive voxels in level sets can only be background voxels, and they + // are handled more efficiently with a signed flood fill (see below). + clipBBox = inGrid.evalActiveVoxelBoundingBox(); + } + + // Independently transform the leaf nodes of the input grid. + + RangeProc proc(xform, clipBBox, inTree, outTree); + proc.setInterrupt(mInterrupt); + + typename RangeProc::LeafRange leafRange(inTree.cbeginLeaf()); + + if (mThreaded) { + tbb::parallel_reduce(leafRange, proc); + } else { + proc(leafRange); + } + + // If the grid is a level set, mark inactive voxels as inside or outside. + if (gridClass == GRID_LEVEL_SET) { + outTree.pruneInactive(); + outTree.signedFloodFill(); + } +} + + +//////////////////////////////////////// + + +//static +template +void +GridResampler::transformBBox( + const Transformer& xform, + const CoordBBox& bbox, + const InTreeT& inTree, + OutTreeT& outTree, + const InterruptFunc& interrupt, + const Sampler& sampler) +{ + typedef typename OutTreeT::ValueType ValueT; + + // Transform the corners of the input tree's bounding box + // and compute the enclosing bounding box in the output tree. + Vec3R + inRMin(bbox.min().x(), bbox.min().y(), bbox.min().z()), + inRMax(bbox.max().x(), bbox.max().y(), bbox.max().z()), + outRMin = math::minComponent(xform.transform(inRMin), xform.transform(inRMax)), + outRMax = math::maxComponent(xform.transform(inRMin), xform.transform(inRMax)); + for (int i = 0; i < 8; ++i) { + Vec3R corner( + i & 1 ? inRMax.x() : inRMin.x(), + i & 2 ? inRMax.y() : inRMin.y(), + i & 4 ? inRMax.z() : inRMin.z()); + outRMin = math::minComponent(outRMin, xform.transform(corner)); + outRMax = math::maxComponent(outRMax, xform.transform(corner)); + } + Vec3i + outMin = local_util::floorVec3(outRMin) - Sampler::radius(), + outMax = local_util::ceilVec3(outRMax) + Sampler::radius(); + + if (!xform.isAffine()) { + // If the transform is not affine, back-project each output voxel + // into the input tree. + Vec3R xyz, inXYZ; + Coord outXYZ; + int &x = outXYZ.x(), &y = outXYZ.y(), &z = outXYZ.z(); + for (x = outMin.x(); x <= outMax.x(); ++x) { + if (interrupt && interrupt()) break; + xyz.x() = x; + for (y = outMin.y(); y <= outMax.y(); ++y) { + if (interrupt && interrupt()) break; + xyz.y() = y; + for (z = outMin.z(); z <= outMax.z(); ++z) { + xyz.z() = z; + inXYZ = xform.invTransform(xyz); + ValueT result; + if (sampler.sample(inTree, inXYZ, result)) { + outTree.setValueOn(outXYZ, result); + } else { + // Note: Don't overwrite existing active values with inactive values. + if (!outTree.isValueOn(outXYZ)) { + outTree.setValueOff(outXYZ, result); + } + } + } + } + } + } else { // affine + // Compute step sizes in the input tree that correspond to + // unit steps in x, y and z in the output tree. + const Vec3R + translation = xform.invTransform(Vec3R(0, 0, 0)), + deltaX = xform.invTransform(Vec3R(1, 0, 0)) - translation, + deltaY = xform.invTransform(Vec3R(0, 1, 0)) - translation, + deltaZ = xform.invTransform(Vec3R(0, 0, 1)) - translation; + +#if defined(__ICC) + /// @todo The following line is a workaround for bad code generation + /// in opt-icc11.1_64 (but not debug or gcc) builds. It should be + /// removed once the problem has been addressed at its source. + const Vec3R dummy = deltaX; +#endif + + // Step by whole voxels through the output tree, sampling the + // corresponding fractional voxels of the input tree. + Vec3R inStartX = xform.invTransform(Vec3R(outMin)); + Coord outXYZ; + int &x = outXYZ.x(), &y = outXYZ.y(), &z = outXYZ.z(); + for (x = outMin.x(); x <= outMax.x(); ++x, inStartX += deltaX) { + if (interrupt && interrupt()) break; + Vec3R inStartY = inStartX; + for (y = outMin.y(); y <= outMax.y(); ++y, inStartY += deltaY) { + if (interrupt && interrupt()) break; + Vec3R inXYZ = inStartY; + for (z = outMin.z(); z <= outMax.z(); ++z, inXYZ += deltaZ) { + ValueT result; + if (sampler.sample(inTree, inXYZ, result)) { + outTree.setValueOn(outXYZ, result); + } else { + // Note: Don't overwrite existing active values with inactive values. + if (!outTree.isValueOn(outXYZ)) { + outTree.setValueOff(outXYZ, result); + } + } + } + } + } + } +} // GridResampler::transformBBox() + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_GRIDTRANSFORMER_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/Interpolation.h b/openvdb_2_3_0_library/openvdb/tools/Interpolation.h new file mode 100755 index 0000000..1eed214 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/Interpolation.h @@ -0,0 +1,768 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file Interpolation.h +/// +/// Sampler classes such as PointSampler and BoxSampler that are intended for use +/// with tools::GridTransformer should operate in voxel space and must adhere to +/// the interface described in the example below: +/// @code +/// struct MySampler +/// { +/// // Return a short name that can be used to identify this sampler +/// // in error messages and elsewhere. +/// const char* name() { return "mysampler"; } +/// +/// // Return the radius of the sampling kernel in voxels, not including +/// // the center voxel. This is the number of voxels of padding that +/// // are added to all sides of a volume as a result of resampling. +/// int radius() { return 2; } +/// +/// // Return true if scaling by a factor smaller than 0.5 (along any axis) +/// // should be handled via a mipmapping-like scheme of successive halvings +/// // of a grid's resolution, until the remaining scale factor is +/// // greater than or equal to 1/2. Set this to false only when high-quality +/// // scaling is not required. +/// bool mipmap() { return true; } +/// +/// // Specify if sampling at a location that is collocated with a grid point +/// // is guaranteed to return the exact value at that grid point. +/// // For most sampling kernels, this should be false. +/// bool consistent() { return false; } +/// +/// // Sample the tree at the given coordinates and return the result in val. +/// // Return true if the sampled value is active. +/// template +/// bool sample(const TreeT& tree, const Vec3R& coord, typename TreeT::ValueType& val); +/// }; +/// @endcode + +#ifndef OPENVDB_TOOLS_INTERPOLATION_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_INTERPOLATION_HAS_BEEN_INCLUDED + +#include +#include +#include // for OPENVDB_VERSION_NAME +#include // for round() +#include // for Transform +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +// The following samplers operate in voxel space. +// When the samplers are applied to grids holding vector or other non-scalar data, +// the data is assumed to be collocated. For example, using the BoxSampler on a grid +// with ValueType Vec3f assumes that all three elements in a vector can be assigned +// the same physical location. Consider using the GridSampler below instead. + +struct PointSampler +{ + static const char* name() { return "point"; } + static int radius() { return 0; } + static bool mipmap() { return false; } + static bool consistent() { return true; } + + /// @brief Sample @a inTree at the nearest neighbor to @a inCoord + /// and store the result in @a result. + /// @return @c true if the sampled value is active. + template + static bool sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result); +}; + + +struct BoxSampler +{ + static const char* name() { return "box"; } + static int radius() { return 1; } + static bool mipmap() { return true; } + static bool consistent() { return true; } + + /// @brief Trilinearly reconstruct @a inTree at @a inCoord + /// and store the result in @a result. + /// @return @c true if any one of the sampled values is active. + template + static bool sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result); + + /// @brief Trilinearly reconstruct @a inTree at @a inCoord. + /// @return the reconstructed value + template + static typename TreeT::ValueType sample(const TreeT& inTree, const Vec3R& inCoord); + +private: + template + static inline ValueT trilinearInterpolation(ValueT (& data)[N][N][N], const Vec3R& uvw); +}; + + +struct QuadraticSampler +{ + static const char* name() { return "quadratic"; } + static int radius() { return 1; } + static bool mipmap() { return true; } + static bool consistent() { return false; } + + /// @brief Triquadratically reconstruct @a inTree at @a inCoord + /// and store the result in @a result. + /// @return @c true if any one of the sampled values is active. + template + static bool sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result); +}; + + +//////////////////////////////////////// + + +// The following samplers operate in voxel space and are designed for Vec3 +// staggered grid data (e.g., fluid simulations using the Marker-and-Cell approach +// associate elements of the velocity vector with different physical locations: +// the faces of a cube). + +struct StaggeredPointSampler +{ + static const char* name() { return "point"; } + static int radius() { return 0; } + static bool mipmap() { return false; } + static bool consistent() { return false; } + + /// @brief Sample @a inTree at the nearest neighbor to @a inCoord + /// and store the result in @a result. + /// @return true if the sampled value is active. + template + static bool sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result); +}; + + +struct StaggeredBoxSampler +{ + static const char* name() { return "box"; } + static int radius() { return 1; } + static bool mipmap() { return true; } + static bool consistent() { return false; } + + /// @brief Trilinearly reconstruct @a inTree at @a inCoord + /// and store the result in @a result. + /// @return true if any one of the sampled value is active. + template + static bool sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result); +}; + + +struct StaggeredQuadraticSampler +{ + static const char* name() { return "quadratic"; } + static int radius() { return 1; } + static bool mipmap() { return true; } + static bool consistent() { return false; } + + /// @brief Triquadratically reconstruct @a inTree at @a inCoord + /// and store the result in @a result. + /// @return true if any one of the sampled values is active. + template + static bool sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result); +}; + + +//////////////////////////////////////// + + +/// @brief Class that provides the interface for continuous sampling +/// of values in a tree. +/// +/// @details Since trees support only discrete voxel sampling, TreeSampler +/// must be used to sample arbitrary continuous points in (world or +/// index) space. +/// +/// @warning This implementation of the GridSampler stores a pointer +/// to a Tree for value access. While this is thread-safe it is +/// uncached and hence slow compared to using a +/// ValueAccessor. Consequently it is normally advisable to use the +/// template specialization below that employs a +/// ValueAccessor. However, care must be taken when dealing with +/// multi-threading (see warning below). +template +class GridSampler +{ +public: + typedef boost::shared_ptr Ptr; + typedef typename GridOrTreeType::ValueType ValueType; + typedef typename TreeAdapter::GridType GridType; + typedef typename TreeAdapter::TreeType TreeType; + typedef typename TreeAdapter::AccessorType AccessorType; + + /// @param grid a grid to be sampled + explicit GridSampler(const GridType& grid) + : mTree(&(grid.tree())), mTransform(&(grid.transform())) {} + + /// @param tree a tree to be sampled, or a ValueAccessor for the tree + /// @param transform is used when sampling world space locations. + GridSampler(const TreeType& tree, const math::Transform& transform) + : mTree(&tree), mTransform(&transform) {} + + const math::Transform& transform() const { return *mTransform; } + + /// @brief Sample a point in index space in the grid. + /// @param x Fractional x-coordinate of point in index-coordinates of grid + /// @param y Fractional y-coordinate of point in index-coordinates of grid + /// @param z Fractional z-coordinate of point in index-coordinates of grid + template + ValueType sampleVoxel(const RealType& x, const RealType& y, const RealType& z) const + { + return this->isSample(Vec3d(x,y,z)); + } + + /// @brief Sample value in integer index space + /// @param i Integer x-coordinate in index space + /// @param j Integer y-coordinate in index space + /// @param k Integer x-coordinate in index space + ValueType sampleVoxel(typename Coord::ValueType i, + typename Coord::ValueType j, + typename Coord::ValueType k) const + { + return this->isSample(Coord(i,j,k)); + } + + /// @brief Sample value in integer index space + /// @param ijk the location in index space + ValueType isSample(const Coord& ijk) const { return mTree->getValue(ijk); } + + /// @brief Sample in fractional index space + /// @param ispoint the location in index space + ValueType isSample(const Vec3d& ispoint) const + { + ValueType result = zeroVal(); + SamplerType::sample(*mTree, ispoint, result); + return result; + } + + /// @brief Sample in world space + /// @param wspoint the location in world space + ValueType wsSample(const Vec3d& wspoint) const + { + ValueType result = zeroVal(); + SamplerType::sample(*mTree, mTransform->worldToIndex(wspoint), result); + return result; + } + +private: + const TreeType* mTree; + const math::Transform* mTransform; +}; // class GridSampler + + +/// @brief Specialization of GridSampler for construction from a ValueAccessor type +/// +/// @note This version should normally be favoured over the one above +/// that takes a Grid or Tree. The reason is this version uses a +/// ValueAccessor that performs fast (cached) access where the +/// tree-based flavour performs slower (uncached) access. +/// +/// @warning Since this version stores a pointer to an (externally +/// allocated) value accessor it is not threadsafe. Hence each thread +/// should have it own instance of a GridSampler constructed from a +/// local ValueAccessor. Alternatively the Grid/Tree-based GridSampler +/// is threadsafe, but also slower. +template +class GridSampler, SamplerType> +{ +public: + typedef boost::shared_ptr Ptr; + typedef typename TreeT::ValueType ValueType; + typedef TreeT TreeType; + typedef Grid GridType; + typedef typename tree::ValueAccessor AccessorType; + + /// @param acc a ValueAccessor to be sampled + /// @param transform is used when sampling world space locations. + GridSampler(const AccessorType& acc, + const math::Transform& transform) + : mAccessor(&acc), mTransform(&transform) {} + + const math::Transform& transform() const { return *mTransform; } + + /// @brief Sample a point in index space in the grid. + /// @param x Fractional x-coordinate of point in index-coordinates of grid + /// @param y Fractional y-coordinate of point in index-coordinates of grid + /// @param z Fractional z-coordinate of point in index-coordinates of grid + template + ValueType sampleVoxel(const RealType& x, const RealType& y, const RealType& z) const + { + return this->isSample(Vec3d(x,y,z)); + } + + /// @brief Sample value in integer index space + /// @param i Integer x-coordinate in index space + /// @param j Integer y-coordinate in index space + /// @param k Integer x-coordinate in index space + ValueType sampleVoxel(typename Coord::ValueType i, + typename Coord::ValueType j, + typename Coord::ValueType k) const + { + return this->isSample(Coord(i,j,k)); + } + + /// @brief Sample value in integer index space + /// @param ijk the location in index space + ValueType isSample(const Coord& ijk) const { return mAccessor->getValue(ijk); } + + /// @brief Sample in fractional index space + /// @param ispoint the location in index space + ValueType isSample(const Vec3d& ispoint) const + { + ValueType result = zeroVal(); + SamplerType::sample(*mAccessor, ispoint, result); + return result; + } + + /// @brief Sample in world space + /// @param wspoint the location in world space + ValueType wsSample(const Vec3d& wspoint) const + { + ValueType result = zeroVal(); + SamplerType::sample(*mAccessor, mTransform->worldToIndex(wspoint), result); + return result; + } + +private: + const AccessorType* mAccessor;//not thread-safe! + const math::Transform* mTransform; +};//Specialization of GridSampler + + +//////////////////////////////////////// + + +/// @brief This is a simple convenience class that allows for sampling +/// from a source grid into the index space of a target grid. At +/// construction the source and target grids are checked for alignment +/// which potentially renders interpolation unnecessary. Else +/// interpolation is performed according to the templated Sampler +/// type. +/// +/// @warning For performance reasons the check for alignment of the +/// two grids is only performed at construction time! +template +class DualGridSampler +{ +public: + typedef typename GridOrTreeT::ValueType ValueType; + typedef typename TreeAdapter::GridType GridType; + typedef typename TreeAdapter::TreeType TreeType; + typedef typename TreeAdapter::AccessorType AccessorType; + + /// @brief Grid and transform constructor. + /// @param sourceGrid Source grid. + /// @param targetXform Transform of the target grid. + DualGridSampler(const GridType& sourceGrid, + const math::Transform& targetXform) + : mSourceTree(&(sourceGrid.tree())) + , mSourceXform(&(sourceGrid.transform())) + , mTargetXform(&targetXform) + , mAligned(targetXform == *mSourceXform) + { + } + /// @brief Tree and transform constructor. + /// @param sourceTree Source tree. + /// @param sourceXform Transform of the source grid. + /// @param targetXform Transform of the target grid. + DualGridSampler(const TreeType& sourceTree, + const math::Transform& sourceXform, + const math::Transform& targetXform) + : mSourceTree(&sourceTree) + , mSourceXform(&sourceXform) + , mTargetXform(&targetXform) + , mAligned(targetXform == sourceXform) + { + } + /// @brief Return the value of the source grid at the index + /// coordinates, ijk, relative to the target grid (or its tranform). + inline ValueType operator()(const Coord& ijk) const + { + if (mAligned) return mSourceTree->getValue(ijk); + const Vec3R world = mTargetXform->indexToWorld(ijk); + return SamplerT::sample(*mSourceTree, mSourceXform->worldToIndex(world)); + } + /// @brief Return true if the two grids are aligned. + inline bool isAligned() const { return mAligned; } +private: + const TreeType* mSourceTree; + const math::Transform* mSourceXform; + const math::Transform* mTargetXform; + const bool mAligned; +};// DualGridSampler + +/// @brief Specialization of DualGridSampler for construction from a ValueAccessor type. +template +class DualGridSampler, SamplerT> +{ + public: + typedef typename TreeT::ValueType ValueType; + typedef TreeT TreeType; + typedef Grid GridType; + typedef typename tree::ValueAccessor AccessorType; + + /// @brief ValueAccessor and transform constructor. + /// @param sourceAccessor ValueAccessor into the source grid. + /// @param sourceXform Transform for the source grid. + /// @param targetXform Transform for the target grid. + DualGridSampler(const AccessorType& sourceAccessor, + const math::Transform& sourceXform, + const math::Transform& targetXform) + : mSourceAcc(&sourceAccessor) + , mSourceXform(&sourceXform) + , mTargetXform(&targetXform) + , mAligned(targetXform == sourceXform) + { + } + /// @brief Return the value of the source grid at the index + /// coordinates, ijk, relative to the target grid. + inline ValueType operator()(const Coord& ijk) const + { + if (mAligned) return mSourceAcc->getValue(ijk); + const Vec3R world = mTargetXform->indexToWorld(ijk); + return SamplerT::sample(*mSourceAcc, mSourceXform->worldToIndex(world)); + } + /// @brief Return true if the two grids are aligned. + inline bool isAligned() const { return mAligned; } +private: + const AccessorType* mSourceAcc; + const math::Transform* mSourceXform; + const math::Transform* mTargetXform; + const bool mAligned; +};//Specialization of DualGridSampler + +//////////////////////////////////////// + + +namespace local_util { + +inline Vec3i +floorVec3(const Vec3R& v) +{ + return Vec3i(int(std::floor(v(0))), int(std::floor(v(1))), int(std::floor(v(2)))); +} + + +inline Vec3i +ceilVec3(const Vec3R& v) +{ + return Vec3i(int(std::ceil(v(0))), int(std::ceil(v(1))), int(std::ceil(v(2)))); +} + + +inline Vec3i +roundVec3(const Vec3R& v) +{ + return Vec3i(int(::round(v(0))), int(::round(v(1))), int(::round(v(2)))); +} + +} // namespace local_util + + +//////////////////////////////////////// + + +template +inline bool +PointSampler::sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result) +{ + Vec3i inIdx = local_util::roundVec3(inCoord); + return inTree.probeValue(Coord(inIdx), result); +} + + +//////////////////////////////////////// + + +template +inline ValueT +BoxSampler::trilinearInterpolation(ValueT (& data)[N][N][N], const Vec3R& uvw) +{ + // Trilinear interpolation: + // The eight surrounding latice values are used to construct the result. \n + // result(x,y,z) = + // v000 (1-x)(1-y)(1-z) + v001 (1-x)(1-y)z + v010 (1-x)y(1-z) + v011 (1-x)yz + // + v100 x(1-y)(1-z) + v101 x(1-y)z + v110 xy(1-z) + v111 xyz + + ValueT resultA, resultB; + + resultA = data[0][0][0] + ValueT((data[0][0][1] - data[0][0][0]) * uvw[2]); + resultB = data[0][1][0] + ValueT((data[0][1][1] - data[0][1][0]) * uvw[2]); + ValueT result1 = resultA + ValueT((resultB-resultA) * uvw[1]); + + resultA = data[1][0][0] + ValueT((data[1][0][1] - data[1][0][0]) * uvw[2]); + resultB = data[1][1][0] + ValueT((data[1][1][1] - data[1][1][0]) * uvw[2]); + ValueT result2 = resultA + ValueT((resultB - resultA) * uvw[1]); + + return result1 + ValueT(uvw[0] * (result2 - result1)); +} + + +template +inline bool +BoxSampler::sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result) +{ + typedef typename TreeT::ValueType ValueT; + + Vec3i inIdx = local_util::floorVec3(inCoord); + Vec3R uvw = inCoord - inIdx; + + // Retrieve the values of the eight voxels surrounding the + // fractional source coordinates. + ValueT data[2][2][2]; + + bool hasActiveValues = false; + Coord ijk(inIdx); + hasActiveValues |= inTree.probeValue(ijk, data[0][0][0]); // i, j, k + ijk[2] += 1; + hasActiveValues |= inTree.probeValue(ijk, data[0][0][1]); // i, j, k + 1 + ijk[1] += 1; + hasActiveValues |= inTree.probeValue(ijk, data[0][1][1]); // i, j+1, k + 1 + ijk[2] = inIdx[2]; + hasActiveValues |= inTree.probeValue(ijk, data[0][1][0]); // i, j+1, k + ijk[0] += 1; + ijk[1] = inIdx[1]; + hasActiveValues |= inTree.probeValue(ijk, data[1][0][0]); // i+1, j, k + ijk[2] += 1; + hasActiveValues |= inTree.probeValue(ijk, data[1][0][1]); // i+1, j, k + 1 + ijk[1] += 1; + hasActiveValues |= inTree.probeValue(ijk, data[1][1][1]); // i+1, j+1, k + 1 + ijk[2] = inIdx[2]; + hasActiveValues |= inTree.probeValue(ijk, data[1][1][0]); // i+1, j+1, k + + result = trilinearInterpolation(data, uvw); + return hasActiveValues; +} + + +template +inline typename TreeT::ValueType +BoxSampler::sample(const TreeT& inTree, const Vec3R& inCoord) +{ + typedef typename TreeT::ValueType ValueT; + + Vec3i inIdx = local_util::floorVec3(inCoord); + Vec3R uvw = inCoord - inIdx; + + // Retrieve the values of the eight voxels surrounding the + // fractional source coordinates. + ValueT data[2][2][2]; + + Coord ijk(inIdx); + data[0][0][0] = inTree.getValue(ijk); // i, j, k + ijk[2] += 1; + data[0][0][1] = inTree.getValue(ijk); // i, j, k + 1 + ijk[1] += 1; + data[0][1][1] = inTree.getValue(ijk); // i, j+1, k + 1 + ijk[2] = inIdx[2]; + data[0][1][0] = inTree.getValue(ijk); // i, j+1, k + ijk[0] += 1; + ijk[1] = inIdx[1]; + data[1][0][0] = inTree.getValue(ijk); // i+1, j, k + ijk[2] += 1; + data[1][0][1] = inTree.getValue(ijk); // i+1, j, k + 1 + ijk[1] += 1; + data[1][1][1] = inTree.getValue(ijk); // i+1, j+1, k + 1 + ijk[2] = inIdx[2]; + data[1][1][0] = inTree.getValue(ijk); // i+1, j+1, k + + return trilinearInterpolation(data, uvw); +} + + +//////////////////////////////////////// + + +template +inline bool +QuadraticSampler::sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result) +{ + typedef typename TreeT::ValueType ValueT; + + Vec3i + inIdx = local_util::floorVec3(inCoord), + inLoIdx = inIdx - Vec3i(1, 1, 1); + Vec3R frac = inCoord - inIdx; + + // Retrieve the values of the 27 voxels surrounding the + // fractional source coordinates. + bool active = false; + ValueT v[3][3][3]; + for (int dx = 0, ix = inLoIdx.x(); dx < 3; ++dx, ++ix) { + for (int dy = 0, iy = inLoIdx.y(); dy < 3; ++dy, ++iy) { + for (int dz = 0, iz = inLoIdx.z(); dz < 3; ++dz, ++iz) { + if (inTree.probeValue(Coord(ix, iy, iz), v[dx][dy][dz])) { + active = true; + } + } + } + } + + /// @todo For vector types, interpolate over each component independently. + ValueT vx[3]; + for (int dx = 0; dx < 3; ++dx) { + ValueT vy[3]; + for (int dy = 0; dy < 3; ++dy) { + // Fit a parabola to three contiguous samples in z + // (at z=-1, z=0 and z=1), then evaluate the parabola at z', + // where z' is the fractional part of inCoord.z, i.e., + // inCoord.z - inIdx.z. The coefficients come from solving + // + // | (-1)^2 -1 1 || a | | v0 | + // | 0 0 1 || b | = | v1 | + // | 1^2 1 1 || c | | v2 | + // + // for a, b and c. + const ValueT* vz = &v[dx][dy][0]; + const ValueT + az = static_cast(0.5 * (vz[0] + vz[2]) - vz[1]), + bz = static_cast(0.5 * (vz[2] - vz[0])), + cz = static_cast(vz[1]); + vy[dy] = static_cast(frac.z() * (frac.z() * az + bz) + cz); + } + // Fit a parabola to three interpolated samples in y, then + // evaluate the parabola at y', where y' is the fractional + // part of inCoord.y. + const ValueT + ay = static_cast(0.5 * (vy[0] + vy[2]) - vy[1]), + by = static_cast(0.5 * (vy[2] - vy[0])), + cy = static_cast(vy[1]); + vx[dx] = static_cast(frac.y() * (frac.y() * ay + by) + cy); + } + // Fit a parabola to three interpolated samples in x, then + // evaluate the parabola at the fractional part of inCoord.x. + const ValueT + ax = static_cast(0.5 * (vx[0] + vx[2]) - vx[1]), + bx = static_cast(0.5 * (vx[2] - vx[0])), + cx = static_cast(vx[1]); + result = static_cast(frac.x() * (frac.x() * ax + bx) + cx); + + return active; +} + + +//////////////////////////////////////// + + +template +inline bool +StaggeredPointSampler::sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result) +{ + typedef typename TreeT::ValueType ValueType; + + ValueType tempX, tempY, tempZ; + bool active = false; + + active = PointSampler::sample(inTree, inCoord + Vec3R(0.5, 0, 0), tempX) || active; + active = PointSampler::sample(inTree, inCoord + Vec3R(0, 0.5, 0), tempY) || active; + active = PointSampler::sample(inTree, inCoord + Vec3R(0, 0, 0.5), tempZ) || active; + + result.x() = tempX.x(); + result.y() = tempY.y(); + result.z() = tempZ.z(); + + return active; +} + + +//////////////////////////////////////// + + +template +inline bool +StaggeredBoxSampler::sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result) +{ + typedef typename TreeT::ValueType ValueType; + + ValueType tempX, tempY, tempZ; + tempX = tempY = tempZ = zeroVal(); + bool active = false; + + active = BoxSampler::sample(inTree, inCoord + Vec3R(0.5, 0, 0), tempX) || active; + active = BoxSampler::sample(inTree, inCoord + Vec3R(0, 0.5, 0), tempY) || active; + active = BoxSampler::sample(inTree, inCoord + Vec3R(0, 0, 0.5), tempZ) || active; + + result.x() = tempX.x(); + result.y() = tempY.y(); + result.z() = tempZ.z(); + + return active; +} + + +//////////////////////////////////////// + + +template +inline bool +StaggeredQuadraticSampler::sample(const TreeT& inTree, const Vec3R& inCoord, + typename TreeT::ValueType& result) +{ + typedef typename TreeT::ValueType ValueType; + + ValueType tempX, tempY, tempZ; + bool active = false; + + active = QuadraticSampler::sample(inTree, inCoord + Vec3R(0.5, 0, 0), tempX) || active; + active = QuadraticSampler::sample(inTree, inCoord + Vec3R(0, 0.5, 0), tempY) || active; + active = QuadraticSampler::sample(inTree, inCoord + Vec3R(0, 0, 0.5), tempZ) || active; + + result.x() = tempX.x(); + result.y() = tempY.y(); + result.z() = tempZ.z(); + + return active; +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_INTERPOLATION_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/LevelSetAdvect.h b/openvdb_2_3_0_library/openvdb/tools/LevelSetAdvect.h new file mode 100755 index 0000000..2119720 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/LevelSetAdvect.h @@ -0,0 +1,711 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/////////////////////////////////////////////////////////////////////////// +// +/// @author Ken Museth +/// +/// @file LevelSetAdvect.h +/// +/// @brief Hyperbolic advection of narrow-band level sets + +#ifndef OPENVDB_TOOLS_LEVEL_SET_ADVECT_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVEL_SET_ADVECT_HAS_BEEN_INCLUDED + +#include "LevelSetTracker.h" +#include "Interpolation.h" // for BoxSampler, etc. +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// Below are two simple wrapper classes for advection velocity fields +/// DiscreteField wraps a velocity grid and EnrightField is mostly +/// intended for debugging (it's an analytical divergence free and +/// periodic field). They both share the same API required by the +/// LevelSetAdvection class defined below. Thus, any class with this +/// API should work with LevelSetAdvection. + +/// Note the Field wrapper classes below always assume the velocity +/// is represented in the world-frame of reference. For DiscreteField +/// this implies the input grid must contain velocities in world +/// coordinates. + +/// @brief Thin wrapper class for a velocity grid +/// @note Consider replacing BoxSampler with StaggeredBoxSampler +template +class DiscreteField +{ +public: + typedef typename VelGridT::ValueType VectorType; + typedef typename VectorType::ValueType ScalarType; + + DiscreteField(const VelGridT &vel): mAccessor(vel.tree()), mTransform(&vel.transform()) {} + + /// @return const reference to the transfrom between world and index space + /// @note Use this method to determine if a client grid is + /// aligned with the coordinate space of the velocity grid. + const math::Transform& transform() const { return *mTransform; } + + /// @return the interpolated velocity at the world space position xyz + inline VectorType operator() (const Vec3d& xyz, ScalarType) const + { + VectorType result = zeroVal(); + Interpolator::sample(mAccessor, mTransform->worldToIndex(xyz), result); + return result; + } + + /// @return the velocity at the coordinate space position ijk + inline VectorType operator() (const Coord& ijk, ScalarType) const + { + return mAccessor.getValue(ijk); + } + +private: + const typename VelGridT::ConstAccessor mAccessor;//Not thread-safe + const math::Transform* mTransform; + +}; // end of DiscreteField + +/// @brief Analytical, divergence-free and periodic vecloity field +/// @note Primarily intended for debugging! +/// @warning This analytical velocity only produce meaningfull values +/// in the unitbox in world space. In other words make sure any level +/// set surface in fully enclodes in the axis aligned bounding box +/// spanning 0->1 in world units. +template +class EnrightField +{ +public: + typedef ScalarT ScalarType; + typedef math::Vec3 VectorType; + + EnrightField() {} + + /// @return const reference to the identity transfrom between world and index space + /// @note Use this method to determine if a client grid is + /// aligned with the coordinate space of this velocity field + math::Transform transform() const { return math::Transform(); } + + /// @return the velocity in world units, evaluated at the world + /// position xyz and at the specified time + inline VectorType operator() (const Vec3d& xyz, ScalarType time) const; + + /// @return the velocity at the coordinate space position ijk + inline VectorType operator() (const Coord& ijk, ScalarType time) const + { + return (*this)(ijk.asVec3d(), time); + } +}; // end of EnrightField + +/// @brief Hyperbolic advection of narrow-band level sets in an +/// external velocity field +/// +/// The @c FieldType template argument below refers to any functor +/// with the following interface (see tools/VelocityFields.h +/// for examples): +/// +/// @code +/// class VelocityField { +/// ... +/// public: +/// openvdb::VectorType operator() (const openvdb::Coord& xyz, ScalarType time) const; +/// ... +/// }; +/// @endcode +/// +/// @note The functor method returns the velocity field at coordinate +/// position xyz of the advection grid, and for the specified +/// time. Note that since the velocity is returned in the local +/// coordinate space of the grid that is being advected, the functor +/// typically depends on the transformation of that grid. This design +/// is chosen for performance reasons. +/// +/// The @c InterruptType template argument below refers to any class +/// with the following interface: +/// @code +/// class Interrupter { +/// ... +/// public: +/// void start(const char* name = NULL)// called when computations begin +/// void end() // called when computations end +/// bool wasInterrupted(int percent=-1)// return true to break computation +///}; +/// @endcode +/// +/// @note If no template argument is provided for this InterruptType +/// the util::NullInterrupter is used which implies that all +/// interrupter calls are no-ops (i.e. incurs no computational overhead). +/// + +template, + typename InterruptT = util::NullInterrupter> +class LevelSetAdvection +{ +public: + typedef GridT GridType; + typedef LevelSetTracker TrackerT; + typedef typename TrackerT::RangeType RangeType; + typedef typename TrackerT::LeafType LeafType; + typedef typename TrackerT::BufferType BufferType; + typedef typename TrackerT::ValueType ScalarType; + typedef typename FieldT::VectorType VectorType; + + /// Main constructor + LevelSetAdvection(GridT& grid, const FieldT& field, InterruptT* interrupt = NULL): + mTracker(grid, interrupt), mField(field), + mSpatialScheme(math::HJWENO5_BIAS), + mTemporalScheme(math::TVD_RK2) {} + + virtual ~LevelSetAdvection() {}; + + /// @return the spatial finite difference scheme + math::BiasedGradientScheme getSpatialScheme() const { return mSpatialScheme; } + /// @brief Set the spatial finite difference scheme + void setSpatialScheme(math::BiasedGradientScheme scheme) { mSpatialScheme = scheme; } + + /// @return the temporal integration scheme + math::TemporalIntegrationScheme getTemporalScheme() const { return mTemporalScheme; } + /// @brief Set the spatial finite difference scheme + void setTemporalScheme(math::TemporalIntegrationScheme scheme) { mTemporalScheme = scheme; } + + /// @return the spatial finite difference scheme + math::BiasedGradientScheme getTrackerSpatialScheme() const { return mTracker.getSpatialScheme(); } + /// @brief Set the spatial finite difference scheme + void setTrackerSpatialScheme(math::BiasedGradientScheme scheme) { mTracker.setSpatialScheme(scheme); } + + /// @return the temporal integration scheme + math::TemporalIntegrationScheme getTrackerTemporalScheme() const { return mTracker.getTemporalScheme(); } + /// @brief Set the spatial finite difference scheme + void setTrackerTemporalScheme(math::TemporalIntegrationScheme scheme) { mTracker.setTemporalScheme(scheme); } + + /// @return The number of normalizations performed per track or + /// normalize call. + int getNormCount() const { return mTracker.getNormCount(); } + /// @brief Set the number of normalizations performed per track or + /// normalize call. + void setNormCount(int n) { mTracker.setNormCount(n); } + + /// @return the grain-size used for multi-threading + int getGrainSize() const { return mTracker.getGrainSize(); } + /// @brief Set the grain-size used for multi-threading. + /// @note A grainsize of 0 or less disables multi-threading! + void setGrainSize(int grainsize) { mTracker.setGrainSize(grainsize); } + + /// Advect the level set from it's current time, time0, to it's + /// final time, time1. If time0>time1 backward advection is performed. + /// + /// @return number of CFL iterations used to advect from time0 to time1 + size_t advect(ScalarType time0, ScalarType time1); + +private: + + // This templated private class implements all the level set magic. + template + class LevelSetAdvect + { + public: + /// Main constructor + LevelSetAdvect(LevelSetAdvection& parent); + /// Shallow copy constructor called by tbb::parallel_for() threads + LevelSetAdvect(const LevelSetAdvect& other); + /// Shallow copy constructor called by tbb::parallel_reduce() threads + LevelSetAdvect(LevelSetAdvect& other, tbb::split); + /// destructor + virtual ~LevelSetAdvect() {if (mIsMaster) this->clearField();}; + /// Advect the level set from it's current time, time0, to it's final time, time1. + /// @return number of CFL iterations + size_t advect(ScalarType time0, ScalarType time1); + /// Used internally by tbb::parallel_for() + void operator()(const RangeType& r) const + { + if (mTask) mTask(const_cast(this), r); + else OPENVDB_THROW(ValueError, "task is undefined - don\'t call this method directly"); + } + /// Used internally by tbb::parallel_reduce() + void operator()(const RangeType& r) + { + if (mTask) mTask(this, r); + else OPENVDB_THROW(ValueError, "task is undefined - don\'t call this method directly"); + } + /// This is only called by tbb::parallel_reduce() threads + void join(const LevelSetAdvect& other) { mMaxAbsV = math::Max(mMaxAbsV, other.mMaxAbsV); } + private: + typedef typename boost::function FuncType; + LevelSetAdvection& mParent; + VectorType** mVec; + const ScalarType mMinAbsV; + ScalarType mMaxAbsV; + const MapT* mMap; + FuncType mTask; + const bool mIsMaster; + /// Enum to defeing the type of multi-threading + enum ThreadingMode { PARALLEL_FOR, PARALLEL_REDUCE }; // for internal use + // method calling tbb + void cook(ThreadingMode mode, size_t swapBuffer = 0); + /// Sample field and return the CFT time step + typename GridT::ValueType sampleField(ScalarType time0, ScalarType time1); + void clearField(); + void sampleXformedField(const RangeType& r, ScalarType time0, ScalarType time1); + void sampleAlignedField(const RangeType& r, ScalarType time0, ScalarType time1); + // Forward Euler advection steps: Phi(result) = Phi(0) - dt * V.Grad(0); + void euler1(const RangeType& r, ScalarType dt, Index resultBuffer); + // Convex combination of Phi and a forward Euler advection steps: + // Phi(result) = alpha * Phi(phi) + (1-alpha) * (Phi(0) - dt * V.Grad(0)); + void euler2(const RangeType& r, ScalarType dt, ScalarType alpha, Index phiBuffer, Index resultBuffer); + }; // end of private LevelSetAdvect class + + template + size_t advect1(ScalarType time0, ScalarType time1); + + template + size_t advect2(ScalarType time0, ScalarType time1); + + template + size_t advect3(ScalarType time0, ScalarType time1); + + TrackerT mTracker; + //each thread needs a deep copy of the field since it might contain a ValueAccessor + const FieldT mField; + math::BiasedGradientScheme mSpatialScheme; + math::TemporalIntegrationScheme mTemporalScheme; + + // disallow copy by assignment + void operator=(const LevelSetAdvection& other) {} + +};//end of LevelSetAdvection + +template +inline size_t +LevelSetAdvection::advect(ScalarType time0, ScalarType time1) +{ + switch (mSpatialScheme) { + case math::FIRST_BIAS: + return this->advect1(time0, time1); + case math::SECOND_BIAS: + return this->advect1(time0, time1); + case math::THIRD_BIAS: + return this->advect1(time0, time1); + case math::WENO5_BIAS: + return this->advect1(time0, time1); + case math::HJWENO5_BIAS: + return this->advect1(time0, time1); + default: + OPENVDB_THROW(ValueError, "Spatial difference scheme not supported!"); + } + return 0; +} + +template +template +inline size_t +LevelSetAdvection::advect1(ScalarType time0, ScalarType time1) +{ + switch (mTemporalScheme) { + case math::TVD_RK1: + return this->advect2(time0, time1); + case math::TVD_RK2: + return this->advect2(time0, time1); + case math::TVD_RK3: + return this->advect2(time0, time1); + default: + OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); + } + return 0; +} + +template +template +inline size_t +LevelSetAdvection::advect2(ScalarType time0, ScalarType time1) +{ + const math::Transform& trans = mTracker.grid().transform(); + if (trans.mapType() == math::UniformScaleMap::mapType()) { + return this->advect3(time0, time1); + } else if (trans.mapType() == math::UniformScaleTranslateMap::mapType()) { + return this->advect3(time0, time1); + } else if (trans.mapType() == math::UnitaryMap::mapType()) { + return this->advect3(time0, time1); + } else if (trans.mapType() == math::TranslationMap::mapType()) { + return this->advect3(time0, time1); + } else { + OPENVDB_THROW(ValueError, "MapType not supported!"); + } + return 0; +} + +template +template +inline size_t +LevelSetAdvection::advect3(ScalarType time0, ScalarType time1) +{ + LevelSetAdvect tmp(*this); + return tmp.advect(time0, time1); +} + +/////////////////////////////////////////////////////////////////////// + +template +inline math::Vec3 +EnrightField::operator() (const Vec3d& xyz, ScalarType time) const +{ + static const ScalarT pi = ScalarT(3.1415926535897931), phase = pi / ScalarT(3.0); + const ScalarT Px = pi * ScalarT(xyz[0]), Py = pi * ScalarT(xyz[1]), Pz = pi * ScalarT(xyz[2]); + const ScalarT tr = cos(ScalarT(time) * phase); + const ScalarT a = sin(ScalarT(2.0)*Py); + const ScalarT b = -sin(ScalarT(2.0)*Px); + const ScalarT c = sin(ScalarT(2.0)*Pz); + return math::Vec3( + tr * ( ScalarT(2) * math::Pow2(sin(Px)) * a * c ), + tr * ( b * math::Pow2(sin(Py)) * c ), + tr * ( b * a * math::Pow2(sin(Pz)) )); +} + + +/////////////////////////////////////////////////////////////////////// + + +template +template +inline +LevelSetAdvection:: +LevelSetAdvect:: +LevelSetAdvect(LevelSetAdvection& parent): + mParent(parent), + mVec(NULL), + mMinAbsV(1e-6), + mMap(parent.mTracker.grid().transform().template constMap().get()), + mTask(0), + mIsMaster(true) +{ +} + +template +template +inline +LevelSetAdvection:: +LevelSetAdvect:: +LevelSetAdvect(const LevelSetAdvect& other): + mParent(other.mParent), + mVec(other.mVec), + mMinAbsV(other.mMinAbsV), + mMaxAbsV(other.mMaxAbsV), + mMap(other.mMap), + mTask(other.mTask), + mIsMaster(false) +{ +} + +template +template +inline +LevelSetAdvection:: +LevelSetAdvect:: +LevelSetAdvect(LevelSetAdvect& other, tbb::split): + mParent(other.mParent), + mVec(other.mVec), + mMinAbsV(other.mMinAbsV), + mMaxAbsV(other.mMaxAbsV), + mMap(other.mMap), + mTask(other.mTask), + mIsMaster(false) +{ +} + +template +template +inline size_t +LevelSetAdvection:: +LevelSetAdvect:: +advect(ScalarType time0, ScalarType time1) +{ + size_t countCFL = 0; + if ( math::isZero(time0 - time1) ) return countCFL; + const bool isForward = time0 < time1; + while ((isForward ? time0time1) && mParent.mTracker.checkInterrupter()) { + /// Make sure we have enough temporal auxiliary buffers + mParent.mTracker.leafs().rebuildAuxBuffers(TemporalScheme == math::TVD_RK3 ? 2 : 1); + + const ScalarType dt = this->sampleField(time0, time1); + if ( math::isZero(dt) ) break;//V is essentially zero so terminate + + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN //switch is resolved at compile-time + switch(TemporalScheme) { + case math::TVD_RK1: + // Perform one explicit Euler step: t1 = t0 + dt + // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(0) + mTask = boost::bind(&LevelSetAdvect::euler1, _1, _2, dt, /*result=*/1); + // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) + this->cook(PARALLEL_FOR, 1); + break; + case math::TVD_RK2: + // Perform one explicit Euler step: t1 = t0 + dt + // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(0) + mTask = boost::bind(&LevelSetAdvect::euler1, _1, _2, dt, /*result=*/1); + // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) + this->cook(PARALLEL_FOR, 1); + + // Convex combine explict Euler step: t2 = t0 + dt + // Phi_t2(1) = 1/2 * Phi_t0(1) + 1/2 * (Phi_t1(0) - dt * V.Grad_t1(0)) + mTask = boost::bind(&LevelSetAdvect::euler2, _1, _2, dt, ScalarType(0.5), /*phi=*/1, /*result=*/1); + // Cook and swap buffer 0 and 1 such that Phi_t2(0) and Phi_t1(1) + this->cook(PARALLEL_FOR, 1); + break; + case math::TVD_RK3: + // Perform one explicit Euler step: t1 = t0 + dt + // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(0) + mTask = boost::bind(&LevelSetAdvect::euler1, _1, _2, dt, /*result=*/1); + // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) + this->cook(PARALLEL_FOR, 1); + + // Convex combine explict Euler step: t2 = t0 + dt/2 + // Phi_t2(2) = 3/4 * Phi_t0(1) + 1/4 * (Phi_t1(0) - dt * V.Grad_t1(0)) + mTask = boost::bind(&LevelSetAdvect::euler2, _1, _2, dt, ScalarType(0.75), /*phi=*/1, /*result=*/2); + // Cook and swap buffer 0 and 2 such that Phi_t2(0) and Phi_t1(2) + this->cook(PARALLEL_FOR, 2); + + // Convex combine explict Euler step: t3 = t0 + dt + // Phi_t3(2) = 1/3 * Phi_t0(1) + 2/3 * (Phi_t2(0) - dt * V.Grad_t2(0) + mTask = boost::bind(&LevelSetAdvect::euler2, _1, _2, dt, ScalarType(1.0/3.0), /*phi=*/1, /*result=*/2); + // Cook and swap buffer 0 and 2 such that Phi_t3(0) and Phi_t2(2) + this->cook(PARALLEL_FOR, 2); + break; + default: + OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); + }//end of compile-time resolved switch + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + + time0 += isForward ? dt : -dt; + ++countCFL; + mParent.mTracker.leafs().removeAuxBuffers(); + this->clearField(); + /// Track the narrow band + mParent.mTracker.track(); + }//end wile-loop over time + return countCFL;//number of CLF propagation steps +} + +template +template +inline typename GridT::ValueType +LevelSetAdvection:: +LevelSetAdvect:: +sampleField(ScalarType time0, ScalarType time1) +{ + mMaxAbsV = mMinAbsV; + const size_t leafCount = mParent.mTracker.leafs().leafCount(); + if (leafCount==0) return ScalarType(0.0); + mVec = new VectorType*[leafCount]; + if (mParent.mField.transform() == mParent.mTracker.grid().transform()) { + mTask = boost::bind(&LevelSetAdvect::sampleAlignedField, _1, _2, time0, time1); + } else { + mTask = boost::bind(&LevelSetAdvect::sampleXformedField, _1, _2, time0, time1); + } + this->cook(PARALLEL_REDUCE); + if (math::isExactlyEqual(mMinAbsV, mMaxAbsV)) return ScalarType(0.0);//V is essentially zero + static const ScalarType CFL = (TemporalScheme == math::TVD_RK1 ? ScalarType(0.3) : + TemporalScheme == math::TVD_RK2 ? ScalarType(0.9) : + ScalarType(1.0))/math::Sqrt(ScalarType(3.0)); + const ScalarType dt = math::Abs(time1 - time0), dx = mParent.mTracker.voxelSize(); + return math::Min(dt, ScalarType(CFL*dx/math::Sqrt(mMaxAbsV))); +} + +template +template +inline void +LevelSetAdvection:: +LevelSetAdvect:: +sampleXformedField(const RangeType& range, ScalarType time0, ScalarType time1) +{ + const bool isForward = time0 < time1; + typedef typename LeafType::ValueOnCIter VoxelIterT; + const MapT& map = *mMap; + mParent.mTracker.checkInterrupter(); + for (size_t n=range.begin(), e=range.end(); n != e; ++n) { + const LeafType& leaf = mParent.mTracker.leafs().leaf(n); + VectorType* vec = new VectorType[leaf.onVoxelCount()]; + int m = 0; + for (VoxelIterT iter = leaf.cbeginValueOn(); iter; ++iter, ++m) { + const VectorType V = mParent.mField(map.applyMap(iter.getCoord().asVec3d()), time0); + mMaxAbsV = math::Max(mMaxAbsV, ScalarType(math::Pow2(V[0])+math::Pow2(V[1])+math::Pow2(V[2]))); + vec[m] = isForward ? V : -V; + } + mVec[n] = vec; + } +} + +template +template +inline void +LevelSetAdvection:: +LevelSetAdvect:: +sampleAlignedField(const RangeType& range, ScalarType time0, ScalarType time1) +{ + const bool isForward = time0 < time1; + typedef typename LeafType::ValueOnCIter VoxelIterT; + mParent.mTracker.checkInterrupter(); + for (size_t n=range.begin(), e=range.end(); n != e; ++n) { + const LeafType& leaf = mParent.mTracker.leafs().leaf(n); + VectorType* vec = new VectorType[leaf.onVoxelCount()]; + int m = 0; + for (VoxelIterT iter = leaf.cbeginValueOn(); iter; ++iter, ++m) { + const VectorType V = mParent.mField(iter.getCoord(), time0); + mMaxAbsV = math::Max(mMaxAbsV, ScalarType(math::Pow2(V[0])+math::Pow2(V[1])+math::Pow2(V[2]))); + vec[m] = isForward ? V : -V; + } + mVec[n] = vec; + } +} + +template +template +inline void +LevelSetAdvection:: +LevelSetAdvect:: +clearField() +{ + if (mVec == NULL) return; + for (size_t n=0, e=mParent.mTracker.leafs().leafCount(); n +template +inline void +LevelSetAdvection:: +LevelSetAdvect:: +cook(ThreadingMode mode, size_t swapBuffer) +{ + mParent.mTracker.startInterrupter("Advecting level set"); + + if (mParent.mTracker.getGrainSize()==0) { + (*this)(mParent.mTracker.leafs().getRange()); + } else if (mode == PARALLEL_FOR) { + tbb::parallel_for(mParent.mTracker.leafs().getRange(mParent.mTracker.getGrainSize()), *this); + } else if (mode == PARALLEL_REDUCE) { + tbb::parallel_reduce(mParent.mTracker.leafs().getRange(mParent.mTracker.getGrainSize()), *this); + } else { + throw std::runtime_error("Undefined threading mode"); + } + + mParent.mTracker.leafs().swapLeafBuffer(swapBuffer, mParent.mTracker.getGrainSize()==0); + + mParent.mTracker.endInterrupter(); +} + +// Forward Euler advection steps: +// Phi(result) = Phi(0) - dt * V.Grad(0); +template +template +inline void +LevelSetAdvection:: +LevelSetAdvect:: +euler1(const RangeType& range, ScalarType dt, Index resultBuffer) +{ + typedef math::BIAS_SCHEME Scheme; + typedef typename Scheme::template ISStencil::StencilType Stencil; + typedef typename LeafType::ValueOnCIter VoxelIterT; + mParent.mTracker.checkInterrupter(); + const MapT& map = *mMap; + typename TrackerT::LeafManagerType& leafs = mParent.mTracker.leafs(); + Stencil stencil(mParent.mTracker.grid()); + for (size_t n=range.begin(), e=range.end(); n != e; ++n) { + BufferType& result = leafs.getBuffer(n, resultBuffer); + const VectorType* vec = mVec[n]; + int m=0; + for (VoxelIterT iter = leafs.leaf(n).cbeginValueOn(); iter; ++iter, ++m) { + stencil.moveTo(iter); + const VectorType V = vec[m], G = math::GradientBiased::result(map, stencil, V); + result.setValue(iter.pos(), *iter - dt * V.dot(G)); + } + } +} + +// Convex combination of Phi and a forward Euler advection steps: +// Phi(result) = alpha * Phi(phi) + (1-alpha) * (Phi(0) - dt * V.Grad(0)); +template +template +inline void +LevelSetAdvection:: +LevelSetAdvect:: +euler2(const RangeType& range, ScalarType dt, ScalarType alpha, Index phiBuffer, Index resultBuffer) +{ + typedef math::BIAS_SCHEME Scheme; + typedef typename Scheme::template ISStencil::StencilType Stencil; + typedef typename LeafType::ValueOnCIter VoxelIterT; + mParent.mTracker.checkInterrupter(); + const MapT& map = *mMap; + typename TrackerT::LeafManagerType& leafs = mParent.mTracker.leafs(); + const ScalarType beta = ScalarType(1.0) - alpha; + Stencil stencil(mParent.mTracker.grid()); + for (size_t n=range.begin(), e=range.end(); n != e; ++n) { + const BufferType& phi = leafs.getBuffer(n, phiBuffer); + BufferType& result = leafs.getBuffer(n, resultBuffer); + const VectorType* vec = mVec[n]; + int m=0; + for (VoxelIterT iter = leafs.leaf(n).cbeginValueOn(); iter; ++iter, ++m) { + stencil.moveTo(iter); + const VectorType V = vec[m], G = math::GradientBiased::result(map, stencil, V); + result.setValue(iter.pos(), alpha*phi[iter.pos()] + beta*(*iter - dt * V.dot(G))); + } + } +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_LEVEL_SET_ADVECT_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/LevelSetFilter.h b/openvdb_2_3_0_library/openvdb/tools/LevelSetFilter.h new file mode 100755 index 0000000..ea3c478 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/LevelSetFilter.h @@ -0,0 +1,549 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @author Ken Museth +/// +/// @file LevelSetFilter.h +/// +/// @brief Performs various types of level set deformations with +/// interface tracking. These unrestricted deformations include +/// surface smoothing (e.g., Laplacian flow), filtering (e.g., mean +/// value) and morphological operations (e.g., morphological opening). +/// All these operations can optionally be masked with another grid that +/// acts as an alpha-mask. + +#ifndef OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED + +#include +#include +#include "LevelSetTracker.h" +#include "Interpolation.h" + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Filtering (e.g. diffusion) of narrow-band level sets. An +/// optional scalar field can be used to produce a (smooth) alpha mask +/// for the filtering. +/// +/// @note This class performs propper interface tracking which allows +/// for unrestricted surface deformations +template::Type, + typename InterruptT = util::NullInterrupter> +class LevelSetFilter : public LevelSetTracker +{ +public: + typedef LevelSetTracker BaseType; + typedef GridT GridType; + typedef MaskT MaskType; + typedef typename GridType::TreeType TreeType; + typedef typename TreeType::ValueType ValueType; + typedef typename MaskType::ValueType AlphaType; + typedef typename tree::LeafManager::LeafRange RangeType; + BOOST_STATIC_ASSERT(boost::is_floating_point::value); + + /// @brief Main constructor from a grid + /// @param grid The level set to be filtered. + /// @param interrupt Optional interrupter. + LevelSetFilter(GridType& grid, InterruptT* interrupt = NULL) + : BaseType(grid, interrupt) + , mTask(0) + , mMask(NULL) + , mMinMask(0) + , mMaxMask(1) + , mInvertMask(false) + { + } + /// @brief Shallow copy constructor called by tbb::parallel_for() + /// threads during filtering. + /// @param other The other LevelSetFilter from which to copy. + LevelSetFilter(const LevelSetFilter& other) + : BaseType(other) + , mTask(other.mTask) + , mMask(other.mMask) + , mMinMask(other.mMinMask) + , mMaxMask(other.mMaxMask) + , mInvertMask(other.mInvertMask) + { + } + /// @brief Destructor + virtual ~LevelSetFilter() {}; + + /// @brief Used internally by tbb::parallel_for(). + /// @param range The range over which to perform multi-threading. + /// @warning Never call this method directly! + void operator()(const RangeType& range) const + { + if (mTask) mTask(const_cast(this), range); + else OPENVDB_THROW(ValueError, "task is undefined - call offset(), etc"); + } + + /// @brief Return the minimum value of the mask to be used for the + /// derivation of a smooth alpha value. + AlphaType minMask() const { return mMinMask; } + /// @brief Return the maximum value of the mask to be used for the + /// derivation of a smooth alpha value. + AlphaType maxMask() const { return mMaxMask; } + /// @brief Define the range for the (optional) scalar mask. + /// @param min Minimum value of the range. + /// @param max Maximum value of the range. + /// @details Mask values outside the range maps to alpha values of + /// respectfully zero and one, and values inside the range maps + /// smoothly to 0->1 (unless of course the mask is inverted). + /// @throw ValueError if @a min is not smaller then @a max. + void setMaskRange(AlphaType min, AlphaType max) + { + if (!(min < max)) OPENVDB_THROW(ValueError, "Invalid mask range (expects min < max)"); + mMinMask = min; + mMaxMask = max; + } + + /// @brief Return true if the mask is inverted, i.e. min->max in the + /// original mask maps to 1->0 in the inverted alpha mask. + bool isMaskInverted() const { return mInvertMask; } + /// @brief Invert the optional mask, i.e. min->max in the original + /// mask maps to 1->0 in the inverted alpha mask. + void invertMask(bool invert=true) { mInvertMask = invert; } + + /// @brief One iteration of mean-curvature flow of the level set. + /// @param mask Optional alpha mask. + void meanCurvature(const MaskType* mask = NULL); + + /// @brief One iteration of laplacian flow of the level set. + /// @param mask Optional alpha mask. + void laplacian(const MaskType* mask = NULL); + + /// @brief One iteration of a fast separable gaussian filter. + /// @param width Width of the gaussian kernel in voxel units. + /// @param mask Optional alpha mask. + /// + /// @note This is approximated as 4 iterations of a separable mean filter + /// which typically leads an approximation that's better than 95%! + void gaussian(int width = 1, const MaskType* mask = NULL); + + /// @brief Offset the level set by the specified (world) distance. + /// @param offset Value of the offset. + /// @param mask Optional alpha mask. + void offset(ValueType offset, const MaskType* mask = NULL); + + /// @brief One iteration of median-value flow of the level set. + /// @param width Width of the median-value kernel in voxel units. + /// @param mask Optional alpha mask. + /// + /// @warning This filter is not separable and is hence relatively + /// slow! + void median(int width = 1, const MaskType* mask = NULL); + + /// @brief One iteration of mean-value flow of the level set. + /// @param width Width of the mean-value kernel in voxel units. + /// @param mask Optional alpha mask. + /// + /// @note This filter is separable so it's fast! + void mean(int width = 1, const MaskType* mask = NULL); + +private: + typedef typename TreeType::LeafNodeType LeafT; + typedef typename LeafT::ValueOnIter VoxelIterT; + typedef typename LeafT::ValueOnCIter VoxelCIterT; + typedef typename tree::LeafManager::BufferType BufferT; + typedef typename RangeType::Iterator LeafIterT; + + // Only two private member data + typename boost::function mTask; + const MaskType* mMask; + AlphaType mMinMask, mMaxMask; + bool mInvertMask; + + // Private cook method calling tbb::parallel_for + void cook(bool swap) + { + const int n = BaseType::getGrainSize(); + if (n>0) { + tbb::parallel_for(BaseType::leafs().leafRange(n), *this); + } else { + (*this)(BaseType::leafs().leafRange()); + } + if (swap) BaseType::leafs().swapLeafBuffer(1, n==0); + } + + // Private class to derive the normalized alpha mask + struct AlphaMask + { + AlphaMask(const GridType& grid, const MaskType& mask, + AlphaType min, AlphaType max, bool invert) + : mAcc(mask.tree()), mSampler(mAcc, mask.transform(), grid.transform()), + mMin(min), mInvNorm(1/(max-min)), mInvert(invert) + { + assert(min < max); + } + inline bool operator()(const Coord& xyz, AlphaType& a, AlphaType& b) const + { + a = mSampler(xyz); + const AlphaType t = (a-mMin)*mInvNorm; + a = t > 0 ? t < 1 ? (3-2*t)*t*t : 1 : 0;//smooth mapping to 0->1 + b = 1 - a; + if (mInvert) std::swap(a,b); + return a>0; + } + typedef typename MaskType::ConstAccessor AccType; + AccType mAcc; + tools::DualGridSampler mSampler; + const AlphaType mMin, mInvNorm; + const bool mInvert; + }; + + // Private driver method for mean and gaussian filtering + void box(int width); + + template + struct Avg { + Avg(const GridT& grid, Int32 w) : + acc(grid.tree()), width(w), frac(1/ValueType(2*w+1)) {} + ValueType operator()(Coord xyz) { + ValueType sum = zeroVal(); + Int32& i = xyz[Axis], j = i + width; + for (i -= width; i <= j; ++i) sum += acc.getValue(xyz); + return sum*frac; + } + typename GridT::ConstAccessor acc; + const Int32 width; + const ValueType frac; + }; + + // Private methods called by tbb::parallel_for threads + template + void doBox( const RangeType& r, Int32 w); + void doBoxX(const RangeType& r, Int32 w) { this->doBox >(r,w); } + void doBoxZ(const RangeType& r, Int32 w) { this->doBox >(r,w); } + void doBoxY(const RangeType& r, Int32 w) { this->doBox >(r,w); } + void doMedian(const RangeType&, int); + void doMeanCurvature(const RangeType&); + void doLaplacian(const RangeType&); + void doOffset(const RangeType&, ValueType); + +}; // end of LevelSetFilter class + + +//////////////////////////////////////// + +template +inline void +LevelSetFilter::median(int width, const MaskType* mask) +{ + mMask = mask; + + BaseType::startInterrupter("Median-value flow of level set"); + + BaseType::leafs().rebuildAuxBuffers(1, BaseType::getGrainSize()==0); + + mTask = boost::bind(&LevelSetFilter::doMedian, _1, _2, std::max(1, width)); + this->cook(true); + + BaseType::track(); + + BaseType::endInterrupter(); +} + +template +inline void +LevelSetFilter::mean(int width, const MaskType* mask) +{ + mMask = mask; + + BaseType::startInterrupter("Mean-value flow of level set"); + + this->box(width); + + BaseType::endInterrupter(); +} + +template +inline void +LevelSetFilter::gaussian(int width, const MaskType* mask) +{ + mMask = mask; + + BaseType::startInterrupter("Gaussian flow of level set"); + + for (int n=0; n<4; ++n) this->box(width); + + BaseType::endInterrupter(); +} + +template +inline void +LevelSetFilter::box(int width) +{ + BaseType::leafs().rebuildAuxBuffers(1, BaseType::getGrainSize()==0); + + width = std::max(1, width); + + mTask = boost::bind(&LevelSetFilter::doBoxX, _1, _2, width); + this->cook(true); + + mTask = boost::bind(&LevelSetFilter::doBoxY, _1, _2, width); + this->cook(true); + + mTask = boost::bind(&LevelSetFilter::doBoxZ, _1, _2, width); + this->cook(true); + + BaseType::track(); +} + +template +inline void +LevelSetFilter::meanCurvature(const MaskType* mask) +{ + mMask = mask; + + BaseType::startInterrupter("Mean-curvature flow of level set"); + + BaseType::leafs().rebuildAuxBuffers(1, BaseType::getGrainSize()==0); + + mTask = boost::bind(&LevelSetFilter::doMeanCurvature, _1, _2); + this->cook(true); + + BaseType::track(); + + BaseType::endInterrupter(); +} + +template +inline void +LevelSetFilter::laplacian(const MaskType* mask) +{ + mMask = mask; + + BaseType::startInterrupter("Laplacian flow of level set"); + + BaseType::leafs().rebuildAuxBuffers(1, BaseType::getGrainSize()==0); + + mTask = boost::bind(&LevelSetFilter::doLaplacian, _1, _2); + this->cook(true); + + BaseType::track(); + + BaseType::endInterrupter(); +} + +template +inline void +LevelSetFilter::offset(ValueType value, const MaskType* mask) +{ + mMask = mask; + + BaseType::startInterrupter("Offsetting level set"); + + BaseType::leafs().removeAuxBuffers();// no auxiliary buffers required + + const ValueType CFL = ValueType(0.5) * BaseType::voxelSize(), offset = openvdb::math::Abs(value); + ValueType dist = 0.0; + while (offset-dist > ValueType(0.001)*CFL && BaseType::checkInterrupter()) { + const ValueType delta = openvdb::math::Min(offset-dist, CFL); + dist += delta; + + mTask = boost::bind(&LevelSetFilter::doOffset, _1, _2, copysign(delta, value)); + this->cook(false); + + BaseType::track(); + } + + BaseType::endInterrupter(); +} + + +///////////////////////// PRIVATE METHODS ////////////////////// + +/// Performs parabolic mean-curvature diffusion +template +inline void +LevelSetFilter::doMeanCurvature(const RangeType& range) +{ + BaseType::checkInterrupter(); + //const float CFL = 0.9f, dt = CFL * mDx * mDx / 6.0f; + const ValueType dx = BaseType::voxelSize(), dt = math::Pow2(dx) / ValueType(3.0); + math::CurvatureStencil stencil(BaseType::grid(), dx); + if (mMask) { + AlphaType a, b; + AlphaMask alpha(BaseType::grid(), *mMask, mMinMask, mMaxMask, mInvertMask); + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + if (alpha(iter.getCoord(), a, b)) { + stencil.moveTo(iter); + const ValueType phi0 = *iter, phi1 = phi0 + dt*stencil.meanCurvatureNormGrad(); + buffer.setValue(iter.pos(), b*phi0 + a*phi1); + } + } + } + } else { + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + stencil.moveTo(iter); + buffer.setValue(iter.pos(), *iter + dt*stencil.meanCurvatureNormGrad()); + } + } + } +} + +/// Performs laplacian diffusion. Note if the grids contains a true +/// signed distance field (e.g. a solution to the Eikonal equation) +/// Laplacian diffusions (e.g. geometric heat equation) is actually +/// identical to mean curvature diffusion, yet less computationally +/// expensive! In other words if you're performing renormalization +/// anyway (e.g. rebuilding the narrow-band) you should consider +/// performing laplacian diffusion over mean curvature flow! +template +inline void +LevelSetFilter::doLaplacian(const RangeType& range) +{ + BaseType::checkInterrupter(); + //const float CFL = 0.9f, half_dt = CFL * mDx * mDx / 12.0f; + const ValueType dx = BaseType::voxelSize(), dt = math::Pow2(dx) / ValueType(6.0); + math::GradStencil stencil(BaseType::grid(), dx); + if (mMask) { + AlphaType a, b; + AlphaMask alpha(BaseType::grid(), *mMask, mMinMask, mMaxMask, mInvertMask); + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + if (alpha(iter.getCoord(), a, b)) { + stencil.moveTo(iter); + const ValueType phi0 = *iter, phi1 = phi0 + dt*stencil.laplacian(); + buffer.setValue(iter.pos(), b*phi0 + a*phi1); + } + } + } + } else { + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + stencil.moveTo(iter); + buffer.setValue(iter.pos(), *iter + dt*stencil.laplacian()); + } + } + } +} + +/// Offsets the values by a constant +template +inline void +LevelSetFilter::doOffset(const RangeType& range, ValueType offset) +{ + BaseType::checkInterrupter(); + if (mMask) { + AlphaType a, b; + AlphaMask alpha(BaseType::grid(), *mMask, mMinMask, mMaxMask, mInvertMask); + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) { + if (alpha(iter.getCoord(), a, b)) iter.setValue(*iter + a*offset); + } + } + } else { + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) { + iter.setValue(*iter + offset); + } + } + } +} + +/// Performs simple but slow median-value diffusion +template +inline void +LevelSetFilter::doMedian(const RangeType& range, int width) +{ + BaseType::checkInterrupter(); + typename math::DenseStencil stencil(BaseType::grid(), width);//creates local cache! + if (mMask) { + AlphaType a, b; + AlphaMask alpha(BaseType::grid(), *mMask, mMinMask, mMaxMask, mInvertMask); + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + if (alpha(iter.getCoord(), a, b)) { + stencil.moveTo(iter); + buffer.setValue(iter.pos(), b*(*iter) + a*stencil.median()); + } + } + } + } else { + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + stencil.moveTo(iter); + buffer.setValue(iter.pos(), stencil.median()); + } + } + } +} + +/// One dimensional convolution of a separable box filter +template +template +inline void +LevelSetFilter::doBox(const RangeType& range, Int32 w) +{ + BaseType::checkInterrupter(); + AvgT avg(BaseType::grid(), w); + if (mMask) { + AlphaType a, b; + AlphaMask alpha(BaseType::grid(), *mMask, mMinMask, mMaxMask, mInvertMask); + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + const Coord xyz = iter.getCoord(); + if (alpha(xyz, a, b)) buffer.setValue(iter.pos(), b*(*iter)+ a*avg(xyz)); + } + } + } else { + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + BufferT& buffer = leafIter.buffer(1); + for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { + buffer.setValue(iter.pos(), avg(iter.getCoord())); + } + } + } +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/LevelSetFracture.h b/openvdb_2_3_0_library/openvdb/tools/LevelSetFracture.h new file mode 100755 index 0000000..586dbe7 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/LevelSetFracture.h @@ -0,0 +1,360 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file tools/LevelSetFracture.h +/// +/// @brief Divide volumes represented by level set grids into multiple, +/// disjoint pieces by intersecting them with one or more "cutter" volumes, +/// also represented by level sets. + +#ifndef OPENVDB_TOOLS_LEVELSETFRACTURE_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVELSETFRACTURE_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include "Composite.h" // for csgIntersection() and csgDifference() +#include "GridTransformer.h" // for resampleToMatch() +#include "LevelSetUtil.h" // for MinMaxVoxel() +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Level set fracturing +template +class LevelSetFracture +{ +public: + typedef std::vector Vec3sList; + typedef std::vector QuatsList; + typedef std::list GridPtrList; + typedef typename GridPtrList::iterator GridPtrListIter; + + + /// @brief Default constructor + /// + /// @param interrupter optional interrupter object + explicit LevelSetFracture(InterruptType* interrupter = NULL); + + /// @brief Divide volumes represented by level set grids into multiple, + /// disjoint pieces by intersecting them with one or more "cutter" volumes, + /// also represented by level sets. + /// @details If desired, the process can be applied iteratively, so that + /// fragments created with one cutter are subdivided by other cutters. + /// + /// @note The incoming @a grids and the @a cutter are required to have matching + /// transforms and narrow band widths! + /// + /// @param grids list of grids to fracture. The residuals of the + /// fractured grids will remain in this list + /// @param cutter a level set grid to use as the cutter object + /// @param segment toggle to split disjoint fragments into their own grids + /// @param points optional list of world space points at which to instance the + /// cutter object (if null, use the cutter's current position only) + /// @param rotations optional list of custom rotations for each cutter instance + /// @param cutterOverlap toggle to allow consecutive cutter instances to fracture + /// previously generated fragments + void fracture(GridPtrList& grids, const GridType& cutter, bool segment = false, + const Vec3sList* points = NULL, const QuatsList* rotations = NULL, + bool cutterOverlap = true); + + /// Return a list of new fragments, not including the residuals from the input grids. + GridPtrList& fragments() { return mFragments; } + + /// Remove all elements from the fragment list. + void clear() { mFragments.clear(); } + +private: + // disallow copy by assignment + void operator=(const LevelSetFracture&) {} + + bool wasInterrupted(int percent = -1) const { + return mInterrupter && mInterrupter->wasInterrupted(percent); + } + + bool isValidFragment(GridType&) const; + void segmentFragments(GridPtrList&) const; + void process(GridPtrList&, const GridType& cutter); + + InterruptType* mInterrupter; + GridPtrList mFragments; +}; + + +//////////////////////////////////////// + + +// Internal utility objects and implementation details + +namespace internal { + +/// @brief Segmentation scheme, splits disjoint fragments into separate grids. +/// @note This is a temporary solution and it will be replaced soon. +template +inline std::vector +segment(GridType& grid, InterruptType* interrupter = NULL) +{ + typedef typename GridType::Ptr GridPtr; + typedef typename GridType::TreeType TreeType; + typedef typename TreeType::Ptr TreePtr; + typedef typename TreeType::ValueType ValueType; + + std::vector segments; + + while (grid.activeVoxelCount() > 0) { + + if (interrupter && interrupter->wasInterrupted()) break; + + // Deep copy the grid's metadata (tree and transform are shared) + GridPtr segment(new GridType(grid, ShallowCopy())); + // Make the transform unique and insert an empty tree + segment->setTransform(grid.transform().copy()); + TreePtr tree(new TreeType(grid.background())); + segment->setTree(tree); + + std::deque coordList; + coordList.push_back(grid.tree().beginLeaf()->beginValueOn().getCoord()); + + Coord ijk, n_ijk; + ValueType value; + + typename tree::ValueAccessor sourceAcc(grid.tree()); + typename tree::ValueAccessor targetAcc(segment->tree()); + + while (!coordList.empty()) { + + if (interrupter && interrupter->wasInterrupted()) break; + + ijk = coordList.back(); + coordList.pop_back(); + + if (!sourceAcc.probeValue(ijk, value)) continue; + if (targetAcc.isValueOn(ijk)) continue; + + targetAcc.setValue(ijk, value); + sourceAcc.setValueOff(ijk); + + for (int n = 0; n < 6; n++) { + n_ijk = ijk + util::COORD_OFFSETS[n]; + if (!targetAcc.isValueOn(n_ijk) && sourceAcc.isValueOn(n_ijk)) { + coordList.push_back(n_ijk); + } + } + } + + grid.tree().pruneInactive(); + segment->tree().signedFloodFill(); + segments.push_back(segment); + } + return segments; +} + +} // namespace internal + + +//////////////////////////////////////// + + +template +LevelSetFracture::LevelSetFracture(InterruptType* interrupter) + : mInterrupter(interrupter) + , mFragments() +{ +} + + +template +void +LevelSetFracture::fracture(GridPtrList& grids, const GridType& cutter, + bool segmentation, const Vec3sList* points, const QuatsList* rotations, bool cutterOverlap) +{ + // We can process all incoming grids with the same cutter instance, + // this optimization is enabled by the requirement of having matching + // transforms between all incoming grids and the cutter object. + if (points && points->size() != 0) { + + math::Transform::Ptr originalCutterTransform = cutter.transform().copy(); + GridType cutterGrid(cutter, ShallowCopy()); + + const bool hasInstanceRotations = + points && rotations && points->size() == rotations->size(); + + // for each instance point.. + for (size_t p = 0, P = points->size(); p < P; ++p) { + + int percent = int((float(p) / float(P)) * 100.0); + if (wasInterrupted(percent)) break; + + GridType instCutterGrid; + instCutterGrid.setTransform(originalCutterTransform->copy()); + math::Transform::Ptr xform = originalCutterTransform->copy(); + + if (hasInstanceRotations) { + const Vec3s& rot = (*rotations)[p].eulerAngles(math::XYZ_ROTATION); + xform->preRotate(rot[0], math::X_AXIS); + xform->preRotate(rot[1], math::Y_AXIS); + xform->preRotate(rot[2], math::Z_AXIS); + xform->postTranslate((*points)[p]); + } else { + xform->postTranslate((*points)[p]); + } + + cutterGrid.setTransform(xform); + + if (wasInterrupted()) break; + + // Since there is no scaling, use the generic resampler instead of + // the more expensive level set rebuild tool. + if (mInterrupter != NULL) { + doResampleToMatch(cutterGrid, instCutterGrid, *mInterrupter); + } else { + util::NullInterrupter interrupter; + doResampleToMatch(cutterGrid, instCutterGrid, interrupter); + } + + if (cutterOverlap && !mFragments.empty()) process(mFragments, instCutterGrid); + process(grids, instCutterGrid); + } + + } else { + // use cutter in place + if (cutterOverlap && !mFragments.empty()) process(mFragments, cutter); + process(grids, cutter); + } + + if (segmentation) { + segmentFragments(mFragments); + segmentFragments(grids); + } +} + + +template +bool +LevelSetFracture::isValidFragment(GridType& grid) const +{ + typedef typename GridType::TreeType TreeType; + if (grid.activeVoxelCount() < 27) return false; + + // Check if valid level-set + { + tree::LeafManager leafs(grid.tree()); + MinMaxVoxel minmax(leafs); + minmax.runParallel(); + + if ((minmax.minVoxel() < 0) == (minmax.maxVoxel() < 0)) return false; + } + + return true; +} + + +template +void +LevelSetFracture::segmentFragments(GridPtrList& grids) const +{ + GridPtrList newFragments; + + for (GridPtrListIter it = grids.begin(); it != grids.end(); ++it) { + + if (wasInterrupted()) break; + + std::vector segments = internal::segment(*(*it), mInterrupter); + for (size_t n = 0, N = segments.size(); n < N; ++n) { + + if (wasInterrupted()) break; + + if (isValidFragment(*segments[n])) { + newFragments.push_back(segments[n]); + } + } + } + + grids.swap(newFragments); +} + + +template +void +LevelSetFracture::process( + GridPtrList& grids, const GridType& cutter) +{ + typedef typename GridType::Ptr GridPtr; + + GridPtrList newFragments; + + for (GridPtrListIter it = grids.begin(); it != grids.end(); ++it) { + + if (wasInterrupted()) break; + + GridPtr grid = *it; + + // gen new fragment + GridPtr fragment = grid->deepCopy(); + csgIntersection(*fragment, *cutter.deepCopy()); + + if (wasInterrupted()) break; + + if (!isValidFragment(*fragment)) continue; + + // update residual + GridPtr residual = grid->deepCopy(); + csgDifference(*residual, *cutter.deepCopy()); + + if (wasInterrupted()) break; + + if (!isValidFragment(*residual)) continue; + + newFragments.push_back(fragment); + + grid->tree().clear(); + grid->tree().merge(residual->tree()); + } + + if (!newFragments.empty()) { + mFragments.splice(mFragments.end(), newFragments); + } +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_LEVELSETFRACTURE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/LevelSetMeasure.h b/openvdb_2_3_0_library/openvdb/tools/LevelSetMeasure.h new file mode 100755 index 0000000..600900e --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/LevelSetMeasure.h @@ -0,0 +1,560 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @author Ken Museth +/// +/// @file LevelSetMeasure.h + +#ifndef OPENVDB_TOOLS_LEVELSETMEASURE_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVELSETMEASURE_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include //for Pi +#include +#include +#include +#include +#include +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Return the surface area of a narrow-band level set. +/// +/// @param grid a scalar, floating-point grid with one or more disjoint, +/// closed isosurfaces at the given @a isovalue +/// @param useWorldSpace if true the area is computed in +/// world space units, else in voxel units. +/// +/// @throw TypeError if @a grid is not scalar or not floating-point or not a level set. +template +inline Real +levelSetArea(const GridType& grid, bool useWorldSpace = true); + +/// @brief Return the volume of a narrow-band level set surface. +/// +/// @param grid a scalar, floating-point grid with one or more disjoint, +/// closed isosurfaces at the given @a isovalue +/// @param useWorldSpace if true the volume is computed in +/// world space units, else in voxel units. +/// +/// @throw TypeError if @a grid is not scalar or not floating-point or not a level set. +template +inline Real +levelSetVolume(const GridType& grid, bool useWorldSpace = true); + +/// @brief Compute the surface area and volume of a narrow-band level set. +/// +/// @param grid a scalar, floating-point grid with one or more disjoint, +/// closed isosurfaces at the given @a isovalue +/// @param area surface area of the level set +/// @param volume volume of the level set surface +/// @param useWorldSpace if true the area and volume are computed in +/// world space units, else in voxel units. +/// +/// @throw TypeError if @a grid is not scalar or not floating-point or not a level set. +template +inline void +levelSetMeasure(const GridType& grid, Real& area, Real& volume, bool useWorldSpace = true); + +/// @brief Compute the surface area and volume of a narrow-band level set. +/// +/// @param grid a scalar, floating-point grid with one or more disjoint, +/// closed isosurfaces at the given @a isovalue +/// @param area surface area of the level set +/// @param volume volume of the level set surface +/// @param avgCurvature average mean curvature of the level set surface +/// @param useWorldSpace if true the area, volume and curvature are computed in +/// world space units, else in voxel units. +/// +/// @throw TypeError if @a grid is not scalar or not floating-point or not a level set. +template +inline void +levelSetMeasure(const GridType& grid, Real& area, Real& volume, Real& avgCurvature, + bool useWorldSpace = true); + +/// @brief Smeared-out and continuous Dirac Delta function. +template +class DiracDelta +{ +public: + DiracDelta(RealT eps) : mC(0.5/eps), mD(2*boost::math::constants::pi()*mC), mE(eps) {} + inline RealT operator()(RealT phi) const { return math::Abs(phi) > mE ? 0 : mC*(1+cos(mD*phi)); } +private: + const RealT mC, mD, mE; +}; + + +/// @brief Multi-threaded computation of surface area, volume and +/// average mean-curvature for narrow band level sets. +/// +/// @details To reduce the risk of round-off errors (primarily due to +/// catastrophic cancellation) and guarantee determinism during +/// multi-threading this class is implemented using parallel_for, and +/// delayed reduction of a sorted list. +template +class LevelSetMeasure +{ +public: + typedef GridT GridType; + typedef typename GridType::TreeType TreeType; + typedef typename TreeType::ValueType ValueType; + typedef typename tree::LeafManager ManagerType; + typedef typename ManagerType::LeafRange RangeType; + BOOST_STATIC_ASSERT(boost::is_floating_point::value); + + /// @brief Main constructor from a grid + /// @param grid The level set to be measured. + /// @param interrupt Optional interrupter. + /// @throw RuntimeError if the grid is not a level set. + LevelSetMeasure(const GridType& grid, InterruptT* interrupt = NULL); + + LevelSetMeasure(ManagerType& leafs, Real Dx, InterruptT* interrupt); + + /// @brief Re-initialize using the specified grid. + void reinit(const GridType& grid); + + /// @brief Re-initialize using the specified LeafManager and voxelSize. + void reinit(ManagerType& leafs, Real dx); + + /// @brief Destructor + ~LevelSetMeasure() {} + + /// @return the grain-size used for multi-threading + int getGrainSize() const { return mGrainSize; } + + /// @brief Set the grain-size used for multi-threading. + /// @note A grainsize of 0 or less disables multi-threading! + void setGrainSize(int grainsize) { mGrainSize = grainsize; } + + /// @brief Compute the surface area and volume of the level + /// set. Use the last argument to specify the result in world or + /// voxel units. + /// @note This method is faster (about 3x) then the measure method + /// below that also computes the average mean-curvature. + void measure(Real& area, Real& volume, bool useWorldUnits = true); + + /// @brief Compute the surface area, volume, and average + /// mean-curvatue of the level set. Use the last argument to + /// specify the result in world or voxel units. + /// @note This method is slower (about 3x) then the measure method + /// above that only computes the area and volume. + void measure(Real& area, Real& volume, Real& avgMeanCurvature, bool useWorldUnits = true); + + /// @brief Used internally by tbb::parallel_reduce(). + /// @param range The range over which to perform multi-threading. + /// @warning Never call this method directly! + void operator()(const RangeType& range) const + { + if (mTask) mTask(const_cast(this), range); + else OPENVDB_THROW(ValueError, "task is undefined"); + } + +private: + typedef typename GridT::ConstAccessor AccT; + typedef typename TreeType::LeafNodeType LeafT; + typedef typename LeafT::ValueOnCIter VoxelCIterT; + typedef typename ManagerType::BufferType BufferT; + typedef typename RangeType::Iterator LeafIterT; + + AccT mAcc; + ManagerType* mLeafs; + InterruptT* mInterrupter; + double mDx; + double* mArray; + typename boost::function mTask; + int mGrainSize; + + // @brief Return false if the process was interrupted + bool checkInterrupter(); + + // Private methods called by tbb::parallel_reduce threads + void measure2( const RangeType& ); + + // Private methods called by tbb::parallel_reduce threads + void measure3( const RangeType& ); + + inline double reduce(double* first, double scale) + { + double* last = first + mLeafs->leafCount(); + tbb::parallel_sort(first, last);//reduces catastrophic cancellation + Real sum = 0.0; + while(first != last) sum += *first++; + return scale * sum; + } + +}; // end of LevelSetMeasure class + + +template +inline +LevelSetMeasure::LevelSetMeasure(const GridType& grid, InterruptT* interrupt) + : mAcc(grid.tree()) + , mLeafs(NULL) + , mInterrupter(interrupt) + , mDx(grid.voxelSize()[0]) + , mArray(NULL) + , mTask(0) + , mGrainSize(1) +{ + if (!grid.hasUniformVoxels()) { + OPENVDB_THROW(RuntimeError, + "The transform must have uniform scale for the LevelSetMeasure to function"); + } + if (grid.getGridClass() != GRID_LEVEL_SET) { + OPENVDB_THROW(RuntimeError, + "LevelSetMeasure only supports level sets;" + " try setting the grid class to \"level set\""); + } +} + + +template +inline +LevelSetMeasure::LevelSetMeasure( + ManagerType& leafs, Real dx, InterruptT* interrupt) + : mAcc(leafs.tree()) + , mLeafs(&leafs) + , mInterrupter(interrupt) + , mDx(dx) + , mArray(NULL) + , mTask(0) + , mGrainSize(1) +{ +} + +template +inline void +LevelSetMeasure::reinit(const GridType& grid) +{ + if (!grid.hasUniformVoxels()) { + OPENVDB_THROW(RuntimeError, + "The transform must have uniform scale for the LevelSetMeasure to function"); + } + if (grid.getGridClass() != GRID_LEVEL_SET) { + OPENVDB_THROW(RuntimeError, + "LevelSetMeasure only supports level sets;" + " try setting the grid class to \"level set\""); + } + mLeafs = NULL; + mAcc = grid.getConstAccessor(); + mDx = grid.voxelSize()[0]; +} + + +template +inline void +LevelSetMeasure::reinit(ManagerType& leafs, Real dx) +{ + mLeafs = &leafs; + mAcc = AccT(leafs.tree()); + mDx = dx; +} + +//////////////////////////////////////// + + +template +inline void +LevelSetMeasure::measure(Real& area, Real& volume, bool useWorldUnits) +{ + if (mInterrupter) mInterrupter->start("Measuring level set"); + mTask = boost::bind(&LevelSetMeasure::measure2, _1, _2); + + const bool newLeafs = mLeafs == NULL; + if (newLeafs) mLeafs = new ManagerType(mAcc.tree()); + const size_t leafCount = mLeafs->leafCount(); + if (leafCount == 0) { + area = volume = 0; + return; + } + mArray = new double[2*leafCount]; + + if (mGrainSize>0) { + tbb::parallel_for(mLeafs->leafRange(mGrainSize), *this); + } else { + (*this)(mLeafs->leafRange()); + } + + const double dx = useWorldUnits ? mDx : 1.0; + area = this->reduce(mArray, math::Pow2(dx)); + volume = this->reduce(mArray + leafCount, math::Pow3(dx) / 3.0); + + if (newLeafs) { + delete mLeafs; + mLeafs = NULL; + } + delete [] mArray; + + if (mInterrupter) mInterrupter->end(); +} + + +template +inline void +LevelSetMeasure::measure(Real& area, Real& volume, Real& avgMeanCurvature, + bool useWorldUnits) +{ + if (mInterrupter) mInterrupter->start("Measuring level set"); + mTask = boost::bind(&LevelSetMeasure::measure3, _1, _2); + + const bool newLeafs = mLeafs == NULL; + if (newLeafs) mLeafs = new ManagerType(mAcc.tree()); + const size_t leafCount = mLeafs->leafCount(); + if (leafCount == 0) { + area = volume = avgMeanCurvature = 0; + return; + } + mArray = new double[3*leafCount]; + + if (mGrainSize>0) { + tbb::parallel_for(mLeafs->leafRange(mGrainSize), *this); + } else { + (*this)(mLeafs->leafRange()); + } + + const double dx = useWorldUnits ? mDx : 1.0; + area = this->reduce(mArray, math::Pow2(dx)); + volume = this->reduce(mArray + leafCount, math::Pow3(dx) / 3.0); + avgMeanCurvature = this->reduce(mArray + 2*leafCount, dx/area); + + if (newLeafs) { + delete mLeafs; + mLeafs = NULL; + } + delete [] mArray; + + if (mInterrupter) mInterrupter->end(); +} + + +///////////////////////// PRIVATE METHODS ////////////////////// + + +template +inline bool +LevelSetMeasure::checkInterrupter() +{ + if (util::wasInterrupted(mInterrupter)) { + tbb::task::self().cancel_group_execution(); + return false; + } + return true; +} + +template +inline void +LevelSetMeasure::measure2(const RangeType& range) +{ + typedef math::Vec3 Vec3T; + typedef math::ISGradient Grad; + this->checkInterrupter(); + const Real invDx = 1.0/mDx; + const DiracDelta DD(1.5); + const size_t leafCount = mLeafs->leafCount(); + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + Real sumA = 0, sumV = 0;//reduce risk of catastrophic cancellation + for (VoxelCIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { + const Real dd = DD(invDx * (*voxelIter)); + if (dd > 0.0) { + const Coord p = voxelIter.getCoord(); + const Vec3T g = invDx*Grad::result(mAcc, p);//voxel units + sumA += dd * g.dot(g); + sumV += dd * (g[0]*p[0]+g[1]*p[1]+g[2]*p[2]); + } + } + double* v = mArray + leafIter.pos(); + *v = sumA; + v += leafCount; + *v = sumV; + } +} + + +template +inline void +LevelSetMeasure::measure3(const RangeType& range) +{ + typedef math::Vec3 Vec3T; + typedef math::ISGradient Grad; + typedef math::ISMeanCurvature Curv; + this->checkInterrupter(); + const Real invDx = 1.0/mDx; + const DiracDelta DD(1.5); + ValueType alpha, beta; + const size_t leafCount = mLeafs->leafCount(); + for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { + Real sumA = 0, sumV = 0, sumC = 0;//reduce risk of catastrophic cancellation + for (VoxelCIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { + const Real dd = DD(invDx * (*voxelIter)); + if (dd > 0.0) { + const Coord p = voxelIter.getCoord(); + const Vec3T g = invDx*Grad::result(mAcc, p);//voxel units + const Real dA = dd * g.dot(g); + sumA += dA; + sumV += dd * (g[0]*p[0]+g[1]*p[1]+g[2]*p[2]); + Curv::result(mAcc, p, alpha, beta); + sumC += dA * alpha/(2*math::Pow2(beta))*invDx; + } + } + double* v = mArray + leafIter.pos(); + *v = sumA; + v += leafCount; + *v = sumV; + v += leafCount; + *v = sumC; + } +} + +//////////////////////////////////////// + +template +inline typename boost::enable_if, Real>::type +doLevelSetArea(const GridT& grid, bool useWorldSpace) +{ + Real area, volume; + LevelSetMeasure m(grid); + m.measure(area, volume, useWorldSpace); + return area; +} + +template +inline typename boost::disable_if, Real>::type +doLevelSetArea(const GridT&, bool) +{ + OPENVDB_THROW(TypeError, + "level set area is supported only for scalar, floating-point grids"); +} + +template +inline Real +levelSetArea(const GridT& grid, bool useWorldSpace) +{ + return doLevelSetArea(grid, useWorldSpace); +} + +//////////////////////////////////////// + +template +inline typename boost::enable_if, Real>::type +doLevelSetVolume(const GridT& grid, bool useWorldSpace) +{ + Real area, volume; + LevelSetMeasure m(grid); + m.measure(area, volume, useWorldSpace); + return volume; +} + +template +inline typename boost::disable_if, Real>::type +doLevelSetVolume(const GridT&, bool) +{ + OPENVDB_THROW(TypeError, + "level set volume is supported only for scalar, floating-point grids"); +} + +template +inline Real +levelSetVolume(const GridT& grid, bool useWorldSpace) +{ + return doLevelSetVolume(grid, useWorldSpace); +} + +//////////////////////////////////////// + +template +inline typename boost::enable_if >::type +doLevelSetMeasure(const GridT& grid, Real& area, Real& volume, bool useWorldSpace) +{ + LevelSetMeasure m(grid); + m.measure(area, volume, useWorldSpace); +} + +template +inline typename boost::disable_if >::type +doLevelSetMeasure(const GridT&, Real&, Real&, bool) +{ + OPENVDB_THROW(TypeError, + "level set measure is supported only for scalar, floating-point grids"); +} + +template +inline void +levelSetMeasure(const GridT& grid, Real& area, Real& volume, bool useWorldSpace) +{ + doLevelSetMeasure(grid, area, volume, useWorldSpace); +} + +//////////////////////////////////////// + +template +inline typename boost::enable_if >::type +doLevelSetMeasure(const GridT& grid, Real& area, Real& volume, Real& avgCurvature, + bool useWorldSpace) +{ + LevelSetMeasure m(grid); + m.measure(area, volume, avgCurvature, useWorldSpace); +} + +template +inline typename boost::disable_if >::type +doLevelSetMeasure(const GridT&, Real&, Real&, Real&, bool) +{ + OPENVDB_THROW(TypeError, + "level set measure is supported only for scalar, floating-point grids"); +} + +template +inline void +levelSetMeasure(const GridT& grid, Real& area, Real& volume, Real& avgCurvature, bool useWorldSpace) +{ + doLevelSetMeasure(grid, area, volume, avgCurvature, useWorldSpace); +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_LEVELSETMEASURE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/LevelSetMorph.h b/openvdb_2_3_0_library/openvdb/tools/LevelSetMorph.h new file mode 100755 index 0000000..ac8a352 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/LevelSetMorph.h @@ -0,0 +1,601 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/////////////////////////////////////////////////////////////////////////// +// +/// @author Ken Museth +/// +/// @file LevelSetMorph.h +/// +/// @brief Shape morphology of level sets. Morphing from a source +/// narrow-band level sets to a target narrow-band level set. + +#ifndef OPENVDB_TOOLS_LEVEL_SET_MORPH_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVEL_SET_MORPH_HAS_BEEN_INCLUDED + +#include "LevelSetTracker.h" +#include "Interpolation.h" // for BoxSampler, etc. +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + + +/// @brief Shape morphology of level sets. Morphing from a source +/// narrow-band level sets to a target narrow-band level set. +/// +/// @details +/// The @c InterruptType template argument below refers to any class +/// with the following interface: +/// @code +/// class Interrupter { +/// ... +/// public: +/// void start(const char* name = NULL)// called when computations begin +/// void end() // called when computations end +/// bool wasInterrupted(int percent=-1)// return true to break computation +/// }; +/// @endcode +/// +/// @note If no template argument is provided for this InterruptType, +/// the util::NullInterrupter is used, which implies that all interrupter +/// calls are no-ops (i.e., they incur no computational overhead). +template +class LevelSetMorphing +{ +public: + typedef GridT GridType; + typedef typename GridT::TreeType TreeType; + typedef LevelSetTracker TrackerT; + typedef typename TrackerT::LeafRange LeafRange; + typedef typename TrackerT::LeafType LeafType; + typedef typename TrackerT::BufferType BufferType; + typedef typename TrackerT::ValueType ScalarType; + + /// Main constructor + LevelSetMorphing(GridT& sourceGrid, const GridT& targetGrid, InterruptT* interrupt = NULL): + mTracker(sourceGrid, interrupt), mTarget(&targetGrid), + mSpatialScheme(math::HJWENO5_BIAS), + mTemporalScheme(math::TVD_RK2) {} + + virtual ~LevelSetMorphing() {}; + + /// Redefine the target level set + void setTarget(const GridT& targetGrid) { mTarget = &targetGrid; } + + /// Return the spatial finite-difference scheme + math::BiasedGradientScheme getSpatialScheme() const { return mSpatialScheme; } + /// Set the spatial finite-difference scheme + void setSpatialScheme(math::BiasedGradientScheme scheme) { mSpatialScheme = scheme; } + + /// Return the temporal integration scheme + math::TemporalIntegrationScheme getTemporalScheme() const { return mTemporalScheme; } + /// Set the temporal integration scheme + void setTemporalScheme(math::TemporalIntegrationScheme scheme) { mTemporalScheme = scheme; } + + /// Return the spatial finite-difference scheme + math::BiasedGradientScheme getTrackerSpatialScheme() const + { + return mTracker.getSpatialScheme(); + } + /// Set the spatial finite-difference scheme + void setTrackerSpatialScheme(math::BiasedGradientScheme scheme) + { + mTracker.setSpatialScheme(scheme); + } + /// Return the temporal integration scheme + math::TemporalIntegrationScheme getTrackerTemporalScheme() const + { + return mTracker.getTemporalScheme(); + } + /// Set the temporal integration scheme + void setTrackerTemporalScheme(math::TemporalIntegrationScheme scheme) + { + mTracker.setTemporalScheme(scheme); + } + /// Return the number of normalizations performed per track or normalize call. + int getNormCount() const { return mTracker.getNormCount(); } + /// Set the number of normalizations performed per track or normalize call. + void setNormCount(int n) { mTracker.setNormCount(n); } + + /// Return the grain size used for multithreading + int getGrainSize() const { return mTracker.getGrainSize(); } + /// @brief Set the grain size used for multithreading. + /// @note A grain size of 0 or less disables multithreading! + void setGrainSize(int grainsize) { mTracker.setGrainSize(grainsize); } + + /// @brief Advect the level set from its current time, @a time0, to its + /// final time, @a time1. If @a time0 > @a time1, perform backward advection. + /// + /// @return the number of CFL iterations used to advect from @a time0 to @a time1 + size_t advect(ScalarType time0, ScalarType time1); + +private: + + // This templated private class implements all the level set magic. + template + class LevelSetMorph + { + public: + /// Main constructor + LevelSetMorph(TrackerT& tracker, const GridT* target); + /// Shallow copy constructor called by tbb::parallel_for() threads + LevelSetMorph(const LevelSetMorph& other); + /// Shallow copy constructor called by tbb::parallel_reduce() threads + LevelSetMorph(LevelSetMorph& other, tbb::split); + /// destructor + virtual ~LevelSetMorph() {} + /// Advect the level set from it's current time, time0, to it's final time, time1. + /// @return number of CFL iterations + size_t advect(ScalarType time0, ScalarType time1); + /// Used internally by tbb::parallel_for() + void operator()(const LeafRange& r) const + { + if (mTask) mTask(const_cast(this), r); + else OPENVDB_THROW(ValueError, "task is undefined - don\'t call this method directly"); + } + /// Used internally by tbb::parallel_reduce() + void operator()(const LeafRange& r) + { + if (mTask) mTask(this, r); + else OPENVDB_THROW(ValueError, "task is undefined - don\'t call this method directly"); + } + /// This is only called by tbb::parallel_reduce() threads + void join(const LevelSetMorph& other) { mMaxAbsS = math::Max(mMaxAbsS, other.mMaxAbsS); } + private: + typedef typename boost::function FuncType; + TrackerT* mTracker; + const GridT* mTarget; + ScalarType mMinAbsS, mMaxAbsS; + const MapT* mMap; + FuncType mTask; + + /// Enum to define the type of multithreading + enum ThreadingMode { PARALLEL_FOR, PARALLEL_REDUCE }; // for internal use + // method calling tbb + void cook(ThreadingMode mode, size_t swapBuffer = 0); + + /// Sample field and return the CFT time step + typename GridT::ValueType sampleSpeed( + ScalarType time0, ScalarType time1, Index speedBuffer); + void sampleXformedSpeed(const LeafRange& r, Index speedBuffer); + void sampleAlignedSpeed(const LeafRange& r, Index speedBuffer); + + // Forward Euler advection steps: Phi(result) = Phi(0) - dt * Speed(speed)*|Grad[Phi(0)]|; + void euler1(const LeafRange& r, ScalarType dt, Index resultBuffer, Index speedBuffer); + + // Convex combination of Phi and a forward Euler advection steps: + // Phi(result) = alpha * Phi(phi) + (1-alpha) * (Phi(0) - dt * Speed(speed)*|Grad[Phi(0)]|); + void euler2(const LeafRange& r, ScalarType dt, ScalarType alpha, + Index phiBuffer, Index resultBuffer, Index speedBuffer); + + }; // end of private LevelSetMorph class + + template + size_t advect1(ScalarType time0, ScalarType time1); + + template + size_t advect2(ScalarType time0, ScalarType time1); + + template + size_t advect3(ScalarType time0, ScalarType time1); + + TrackerT mTracker; + const GridT* mTarget; + math::BiasedGradientScheme mSpatialScheme; + math::TemporalIntegrationScheme mTemporalScheme; + + // disallow copy by assignment + void operator=(const LevelSetMorphing& other) {} + +};//end of LevelSetMorphing + +template +inline size_t +LevelSetMorphing::advect(ScalarType time0, ScalarType time1) +{ + switch (mSpatialScheme) { + case math::FIRST_BIAS: + return this->advect1(time0, time1); + //case math::SECOND_BIAS: + //return this->advect1(time0, time1); + //case math::THIRD_BIAS: + //return this->advect1(time0, time1); + //case math::WENO5_BIAS: + //return this->advect1(time0, time1); + case math::HJWENO5_BIAS: + return this->advect1(time0, time1); + default: + OPENVDB_THROW(ValueError, "Spatial difference scheme not supported!"); + } + return 0; +} + +template +template +inline size_t +LevelSetMorphing::advect1(ScalarType time0, ScalarType time1) +{ + switch (mTemporalScheme) { + case math::TVD_RK1: + return this->advect2(time0, time1); + case math::TVD_RK2: + return this->advect2(time0, time1); + case math::TVD_RK3: + return this->advect2(time0, time1); + default: + OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); + } + return 0; +} + +template +template +inline size_t +LevelSetMorphing::advect2(ScalarType time0, ScalarType time1) +{ + const math::Transform& trans = mTracker.grid().transform(); + if (trans.mapType() == math::UniformScaleMap::mapType()) { + return this->advect3(time0, time1); + } else if (trans.mapType() == math::UniformScaleTranslateMap::mapType()) { + return this->advect3( + time0, time1); + } else if (trans.mapType() == math::UnitaryMap::mapType()) { + return this->advect3(time0, time1); + } else if (trans.mapType() == math::TranslationMap::mapType()) { + return this->advect3(time0, time1); + } else { + OPENVDB_THROW(ValueError, "MapType not supported!"); + } + return 0; +} + +template +template +inline size_t +LevelSetMorphing::advect3(ScalarType time0, ScalarType time1) +{ + LevelSetMorph tmp(mTracker, mTarget); + return tmp.advect(time0, time1); +} + + +/////////////////////////////////////////////////////////////////////// + + +template +template +inline +LevelSetMorphing:: +LevelSetMorph:: +LevelSetMorph(TrackerT& tracker, const GridT* target): + mTracker(&tracker), + mTarget(target), + mMinAbsS(1e-6), + mMap(tracker.grid().transform().template constMap().get()), + mTask(0) +{ +} + +template +template +inline +LevelSetMorphing:: +LevelSetMorph:: +LevelSetMorph(const LevelSetMorph& other): + mTracker(other.mTracker), + mTarget(other.mTarget), + mMinAbsS(other.mMinAbsS), + mMaxAbsS(other.mMaxAbsS), + mMap(other.mMap), + mTask(other.mTask) +{ +} + +template +template +inline +LevelSetMorphing:: +LevelSetMorph:: +LevelSetMorph(LevelSetMorph& other, tbb::split): + mTracker(other.mTracker), + mTarget(other.mTarget), + mMinAbsS(other.mMinAbsS), + mMaxAbsS(other.mMaxAbsS), + mMap(other.mMap), + mTask(other.mTask) +{ +} + +template +template +inline size_t +LevelSetMorphing:: +LevelSetMorph:: +advect(ScalarType time0, ScalarType time1) +{ + // Make sure we have enough temporal auxiliary buffers for the time + // integration AS WELL AS an extra buffer with the speed function! + static const Index auxBuffers = 1 + (TemporalScheme == math::TVD_RK3 ? 2 : 1); + size_t countCFL = 0; + while (time0 < time1 && mTracker->checkInterrupter()) { + mTracker->leafs().rebuildAuxBuffers(auxBuffers); + + const ScalarType dt = this->sampleSpeed(time0, time1, auxBuffers); + if ( math::isZero(dt) ) break;//V is essentially zero so terminate + + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN //switch is resolved at compile-time + switch(TemporalScheme) { + case math::TVD_RK1: + // Perform one explicit Euler step: t1 = t0 + dt + // Phi_t1(1) = Phi_t0(0) - dt * Speed(2) * |Grad[Phi(0)]| + mTask = boost::bind(&LevelSetMorph::euler1, _1, _2, dt, /*result=*/1, /*speed*/2); + // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) + this->cook(PARALLEL_FOR, 1); + break; + case math::TVD_RK2: + // Perform one explicit Euler step: t1 = t0 + dt + // Phi_t1(1) = Phi_t0(0) - dt * Speed(2) * |Grad[Phi(0)]| + mTask = boost::bind(&LevelSetMorph::euler1, _1, _2, dt, /*result=*/1, /*speed*/2); + // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) + this->cook(PARALLEL_FOR, 1); + + // Convex combine explict Euler step: t2 = t0 + dt + // Phi_t2(1) = 1/2 * Phi_t0(1) + 1/2 * (Phi_t1(0) - dt * Speed(2) * |Grad[Phi(0)]|) + mTask = boost::bind(&LevelSetMorph::euler2, _1, _2, dt, ScalarType(0.5), + /*phi=*/1, /*result=*/1, /*speed*/2); + // Cook and swap buffer 0 and 1 such that Phi_t2(0) and Phi_t1(1) + this->cook(PARALLEL_FOR, 1); + break; + case math::TVD_RK3: + // Perform one explicit Euler step: t1 = t0 + dt + // Phi_t1(1) = Phi_t0(0) - dt * Speed(3) * |Grad[Phi(0)]| + mTask = boost::bind(&LevelSetMorph::euler1, _1, _2, dt, /*result=*/1, /*speed*/3); + // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) + this->cook(PARALLEL_FOR, 1); + + // Convex combine explict Euler step: t2 = t0 + dt/2 + // Phi_t2(2) = 3/4 * Phi_t0(1) + 1/4 * (Phi_t1(0) - dt * Speed(3) * |Grad[Phi(0)]|) + mTask = boost::bind(&LevelSetMorph::euler2, _1, _2, dt, ScalarType(0.75), + /*phi=*/1, /*result=*/2, /*speed*/3); + // Cook and swap buffer 0 and 2 such that Phi_t2(0) and Phi_t1(2) + this->cook(PARALLEL_FOR, 2); + + // Convex combine explict Euler step: t3 = t0 + dt + // Phi_t3(2) = 1/3 * Phi_t0(1) + 2/3 * (Phi_t2(0) - dt * Speed(3) * |Grad[Phi(0)]|) + mTask = boost::bind(&LevelSetMorph::euler2, _1, _2, dt, ScalarType(1.0/3.0), + /*phi=*/1, /*result=*/2, /*speed*/3); + // Cook and swap buffer 0 and 2 such that Phi_t3(0) and Phi_t2(2) + this->cook(PARALLEL_FOR, 2); + break; + default: + OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); + }//end of compile-time resolved switch + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + + time0 += dt; + ++countCFL; + mTracker->leafs().removeAuxBuffers(); + + // Track the narrow band + mTracker->track(); + }//end wile-loop over time + + return countCFL;//number of CLF propagation steps +} + +template +template +inline typename GridT::ValueType +LevelSetMorphing:: +LevelSetMorph:: +sampleSpeed(ScalarType time0, ScalarType time1, Index speedBuffer) +{ + mMaxAbsS = mMinAbsS; + const size_t leafCount = mTracker->leafs().leafCount(); + if (leafCount==0 || time0 >= time1) return ScalarType(0); + + if (mTarget->transform() == mTracker->grid().transform()) { + mTask = boost::bind(&LevelSetMorph::sampleAlignedSpeed, _1, _2, speedBuffer); + } else { + mTask = boost::bind(&LevelSetMorph::sampleXformedSpeed, _1, _2, speedBuffer); + } + this->cook(PARALLEL_REDUCE); + if (math::isApproxEqual(mMinAbsS, mMaxAbsS)) return ScalarType(0);//speed is essentially zero + static const ScalarType CFL = (TemporalScheme == math::TVD_RK1 ? ScalarType(0.3) : + TemporalScheme == math::TVD_RK2 ? ScalarType(0.9) : + ScalarType(1.0))/math::Sqrt(ScalarType(3.0)); + const ScalarType dt = math::Abs(time1 - time0), dx = mTracker->voxelSize(); + return math::Min(dt, ScalarType(CFL*dx/mMaxAbsS)); +} + +template +template +inline void +LevelSetMorphing:: +LevelSetMorph:: +sampleXformedSpeed(const LeafRange& range, Index speedBuffer) +{ + typedef typename LeafType::ValueOnCIter VoxelIterT; + typedef tools::GridSampler SamplerT; + const MapT& map = *mMap; + mTracker->checkInterrupter(); + + SamplerT sampler(mTarget->getAccessor(), mTarget->transform()); + for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { + BufferType& speed = leafIter.buffer(speedBuffer); + for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { + ScalarType& s = const_cast(speed.getValue(voxelIter.pos())); + s -= sampler.wsSample(map.applyMap(voxelIter.getCoord().asVec3d())); + mMaxAbsS = math::Max(mMaxAbsS, math::Abs(s)); + } + } +} + +template +template +inline void +LevelSetMorphing:: +LevelSetMorph:: +sampleAlignedSpeed(const LeafRange& range, Index speedBuffer) +{ + typedef typename LeafType::ValueOnCIter VoxelIterT; + mTracker->checkInterrupter(); + + typename GridT::ConstAccessor target = mTarget->getAccessor(); + for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { + BufferType& speed = leafIter.buffer(speedBuffer); + for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { + ScalarType& s = const_cast(speed.getValue(voxelIter.pos())); + s -= target.getValue(voxelIter.getCoord()); + mMaxAbsS = math::Max(mMaxAbsS, math::Abs(s)); + } + } +} + +template +template +inline void +LevelSetMorphing:: +LevelSetMorph:: +cook(ThreadingMode mode, size_t swapBuffer) +{ + mTracker->startInterrupter("Morphing level set"); + + const int grainSize = mTracker->getGrainSize(); + const LeafRange range = mTracker->leafs().leafRange(grainSize); + + if (mTracker->getGrainSize()==0) { + (*this)(range); + } else if (mode == PARALLEL_FOR) { + tbb::parallel_for(range, *this); + } else if (mode == PARALLEL_REDUCE) { + tbb::parallel_reduce(range, *this); + } else { + throw std::runtime_error("Undefined threading mode"); + } + + mTracker->leafs().swapLeafBuffer(swapBuffer, grainSize == 0); + + mTracker->endInterrupter(); +} + +// Forward Euler advection steps: +// Phi(result) = Phi(0) - dt * Phi(speed) * |Grad[Phi(0)]| +template +template +inline void +LevelSetMorphing:: +LevelSetMorph:: +euler1(const LeafRange& range, ScalarType dt, Index resultBuffer, Index speedBuffer) +{ + typedef math::BIAS_SCHEME Scheme; + typedef typename Scheme::template ISStencil::StencilType Stencil; + typedef typename LeafType::ValueOnCIter VoxelIterT; + + mTracker->checkInterrupter(); + const MapT& map = *mMap; + Stencil stencil(mTracker->grid()); + + for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { + BufferType& speed = leafIter.buffer(speedBuffer); + BufferType& result = leafIter.buffer(resultBuffer); + for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { + const Index n = voxelIter.pos(); + stencil.moveTo(voxelIter); + const ScalarType G = math::GradientNormSqrd::result(map, stencil); + result.setValue(n, *voxelIter - dt * speed.getValue(n) * G); + } + } +} + +// Convex combination of Phi and a forward Euler advection steps: +// Phi(result) = alpha * Phi(phi) + (1-alpha) * (Phi(0) - dt * Phi(speed) * |Grad[Phi(0)]|) +template +template +inline void +LevelSetMorphing:: +LevelSetMorph:: +euler2(const LeafRange& range, ScalarType dt, ScalarType alpha, + Index phiBuffer, Index resultBuffer, Index speedBuffer) +{ + typedef math::BIAS_SCHEME Scheme; + typedef typename Scheme::template ISStencil::StencilType Stencil; + typedef typename LeafType::ValueOnCIter VoxelIterT; + + mTracker->checkInterrupter(); + const MapT& map = *mMap; + const ScalarType beta = ScalarType(1.0) - alpha; + Stencil stencil(mTracker->grid()); + + for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { + BufferType& speed = leafIter.buffer(speedBuffer); + BufferType& result = leafIter.buffer(resultBuffer); + BufferType& phi = leafIter.buffer(phiBuffer); + for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { + const Index n = voxelIter.pos(); + stencil.moveTo(voxelIter); + const ScalarType G = math::GradientNormSqrd::result(map, stencil); + result.setValue(n, + alpha * phi.getValue(n) + beta * (*voxelIter - dt * speed.getValue(n) * G)); + } + } +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_LEVEL_SET_MORPH_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/LevelSetRebuild.h b/openvdb_2_3_0_library/openvdb/tools/LevelSetRebuild.h new file mode 100755 index 0000000..1b70843 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/LevelSetRebuild.h @@ -0,0 +1,348 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_TOOLS_LEVELSETREBUILD_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVELSETREBUILD_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + + +/// @brief Return a new grid of type @c GridType that contains a narrow-band level set +/// representation of an isosurface of a given grid. +/// +/// @param grid a scalar, floating-point grid with one or more disjoint, +/// closed isosurfaces at the given @a isovalue +/// @param isovalue the isovalue that defines the implicit surface (defaults to zero, +/// which is typical if the input grid is already a level set or a SDF). +/// @param halfWidth half the width of the narrow band, in voxel units +/// (defaults to 3 voxels, which is required for some level set operations) +/// @param xform optional transform for the output grid +/// (if not provided, the transform of the input @a grid will be matched) +/// +/// @throw TypeError if @a grid is not scalar or not floating-point +/// +/// @note If the input grid contains overlapping isosurfaces, interior edges will be lost. +template +inline typename GridType::Ptr +levelSetRebuild(const GridType& grid, float isovalue = 0, + float halfWidth = float(LEVEL_SET_HALF_WIDTH), const math::Transform* xform = NULL); + + +/// @brief Return a new grid of type @c GridType that contains a narrow-band level set +/// representation of an isosurface of a given grid. +/// +/// @param grid a scalar, floating-point grid with one or more disjoint, +/// closed isosurfaces at the given @a isovalue +/// @param isovalue the isovalue that defines the implicit surface +/// @param exBandWidth the exterior narrow-band width in voxel units +/// @param inBandWidth the interior narrow-band width in voxel units +/// @param xform optional transform for the output grid +/// (if not provided, the transform of the input @a grid will be matched) +/// +/// @throw TypeError if @a grid is not scalar or not floating-point +/// +/// @note If the input grid contains overlapping isosurfaces, interior edges will be lost. +template +inline typename GridType::Ptr +levelSetRebuild(const GridType& grid, float isovalue, float exBandWidth, float inBandWidth, + const math::Transform* xform = NULL); + + +/// @brief Return a new grid of type @c GridType that contains a narrow-band level set +/// representation of an isosurface of a given grid. +/// +/// @param grid a scalar, floating-point grid with one or more disjoint, +/// closed isosurfaces at the given @a isovalue +/// @param isovalue the isovalue that defines the implicit surface +/// @param exBandWidth the exterior narrow-band width in voxel units +/// @param inBandWidth the interior narrow-band width in voxel units +/// @param xform optional transform for the output grid +/// (if not provided, the transform of the input @a grid will be matched) +/// @param interrupter optional interrupter object +/// +/// @throw TypeError if @a grid is not scalar or not floating-point +/// +/// @note If the input grid contains overlapping isosurfaces, interior edges will be lost. +template +inline typename GridType::Ptr +levelSetRebuild(const GridType& grid, float isovalue, float exBandWidth, float inBandWidth, + const math::Transform* xform = NULL, InterruptT* interrupter = NULL); + + +//////////////////////////////////////// + + +// Internal utility objects and implementation details + +namespace internal { + +class PointListTransform +{ +public: + PointListTransform(const PointList& pointsIn, std::vector& pointsOut, + const math::Transform& xform) + : mPointsIn(pointsIn) + , mPointsOut(&pointsOut) + , mXform(xform) + { + } + + void runParallel() + { + tbb::parallel_for(tbb::blocked_range(0, mPointsOut->size()), *this); + } + + void runSerial() + { + (*this)(tbb::blocked_range(0, mPointsOut->size())); + } + + inline void operator()(const tbb::blocked_range& range) const + { + for (size_t n = range.begin(); n < range.end(); ++n) { + (*mPointsOut)[n] = mXform.worldToIndex(mPointsIn[n]); + } + } + +private: + const PointList& mPointsIn; + std::vector * const mPointsOut; + const math::Transform& mXform; +}; + + +class PrimCpy +{ +public: + PrimCpy(const PolygonPoolList& primsIn, const std::vector& indexList, + std::vector& primsOut) + : mPrimsIn(primsIn) + , mIndexList(indexList) + , mPrimsOut(&primsOut) + { + } + + void runParallel() + { + tbb::parallel_for(tbb::blocked_range(0, mIndexList.size()), *this); + } + + void runSerial() + { + (*this)(tbb::blocked_range(0, mIndexList.size())); + } + + inline void operator()(const tbb::blocked_range& range) const + { + openvdb::Vec4I quad; + quad[3] = openvdb::util::INVALID_IDX; + std::vector& primsOut = *mPrimsOut; + + for (size_t n = range.begin(); n < range.end(); ++n) { + size_t index = mIndexList[n]; + PolygonPool& polygons = mPrimsIn[n]; + + // Copy quads + for (size_t i = 0, I = polygons.numQuads(); i < I; ++i) { + primsOut[index++] = polygons.quad(i); + } + polygons.clearQuads(); + + // Copy triangles (adaptive mesh) + for (size_t i = 0, I = polygons.numTriangles(); i < I; ++i) { + const openvdb::Vec3I& triangle = polygons.triangle(i); + quad[0] = triangle[0]; + quad[1] = triangle[1]; + quad[2] = triangle[2]; + primsOut[index++] = quad; + } + + polygons.clearTriangles(); + } + } + +private: + const PolygonPoolList& mPrimsIn; + const std::vector& mIndexList; + std::vector * const mPrimsOut; +}; + +} // namespace internal + + +//////////////////////////////////////// + + +/// The normal entry points for level set rebuild are the levelSetRebuild() functions. +/// doLevelSetRebuild() is mainly for internal use, but when the isovalue and half band +/// widths are given in ValueType units (for example, if they are queried from +/// a grid), it might be more convenient to call this function directly. +/// +/// @internal This overload is enabled only for grids with a scalar, floating-point ValueType. +template +inline typename boost::enable_if, +typename GridType::Ptr>::type +doLevelSetRebuild(const GridType& grid, typename GridType::ValueType iso, + typename GridType::ValueType exWidth, typename GridType::ValueType inWidth, + const math::Transform* xform, InterruptT* interrupter) +{ + const float + isovalue = float(iso), + exBandWidth = float(exWidth), + inBandWidth = float(inWidth); + + tools::VolumeToMesh mesher(isovalue); + mesher(grid); + + math::Transform::Ptr transform = (xform != NULL) ? xform->copy() : grid.transform().copy(); + + std::vector points(mesher.pointListSize()); + + { // Copy and transform (required for MeshToVolume) points to grid space. + internal::PointListTransform ptnXForm(mesher.pointList(), points, *transform); + ptnXForm.runParallel(); + mesher.pointList().reset(NULL); + } + + std::vector primitives; + + { // Copy primitives. + PolygonPoolList& polygonPoolList = mesher.polygonPoolList(); + + size_t numPrimitives = 0; + std::vector indexlist(mesher.polygonPoolListSize()); + + for (size_t n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { + const openvdb::tools::PolygonPool& polygons = polygonPoolList[n]; + indexlist[n] = numPrimitives; + numPrimitives += polygons.numQuads(); + numPrimitives += polygons.numTriangles(); + } + + primitives.resize(numPrimitives); + internal::PrimCpy primCpy(polygonPoolList, indexlist, primitives); + primCpy.runParallel(); + } + + MeshToVolume vol(transform, OUTPUT_RAW_DATA, interrupter); + vol.convertToLevelSet(points, primitives, exBandWidth, inBandWidth); + + return vol.distGridPtr(); +} + + +/// @internal This overload is enabled only for grids that do not have a scalar, +/// floating-point ValueType. +template +inline typename boost::disable_if, +typename GridType::Ptr>::type +doLevelSetRebuild(const GridType&, typename GridType::ValueType /*isovalue*/, + typename GridType::ValueType /*exWidth*/, typename GridType::ValueType /*inWidth*/, + const math::Transform*, InterruptT*) +{ + OPENVDB_THROW(TypeError, + "level set rebuild is supported only for scalar, floating-point grids"); +} + + +//////////////////////////////////////// + + +template +inline typename GridType::Ptr +levelSetRebuild(const GridType& grid, float iso, float exWidth, float inWidth, + const math::Transform* xform, InterruptT* interrupter) +{ + typedef typename GridType::ValueType ValueT; + ValueT + isovalue(zeroVal() + iso), + exBandWidth(zeroVal() + exWidth), + inBandWidth(zeroVal() + inWidth); + + return doLevelSetRebuild(grid, isovalue, exBandWidth, inBandWidth, xform, interrupter); +} + + +template +inline typename GridType::Ptr +levelSetRebuild(const GridType& grid, float iso, float exWidth, float inWidth, + const math::Transform* xform) +{ + typedef typename GridType::ValueType ValueT; + ValueT + isovalue(zeroVal() + iso), + exBandWidth(zeroVal() + exWidth), + inBandWidth(zeroVal() + inWidth); + + return doLevelSetRebuild( + grid, isovalue, exBandWidth, inBandWidth, xform, NULL); +} + + +template +inline typename GridType::Ptr +levelSetRebuild(const GridType& grid, float iso, float halfVal, const math::Transform* xform) +{ + typedef typename GridType::ValueType ValueT; + ValueT + isovalue(zeroVal() + iso), + halfWidth(zeroVal() + halfVal); + + return doLevelSetRebuild( + grid, isovalue, halfWidth, halfWidth, xform, NULL); +} + + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_LEVELSETREBUILD_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/LevelSetSphere.h b/openvdb_2_3_0_library/openvdb/tools/LevelSetSphere.h new file mode 100755 index 0000000..b26ef91 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/LevelSetSphere.h @@ -0,0 +1,221 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +/// +/// @file LevelSetSphere.h +/// +/// @brief Generate a narrow-band level set of sphere. +/// +/// @note By definition a level set has a fixed narrow band width +/// (the half width is defined by LEVEL_SET_HALF_WIDTH in Types.h), +/// whereas an SDF can have a variable narrow band width. + +#ifndef OPENVDB_TOOLS_LEVELSETSPHERE_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVELSETSPHERE_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Return a grid of type @c GridType containing a narrow-band level set +/// representation of a sphere. +/// +/// @param radius radius of the sphere in world units +/// @param center center of the sphere in world units +/// @param voxelSize voxel size in world units +/// @param halfWidth half the width of the narrow band, in voxel units +/// @param interrupt a pointer adhering to the util::NullInterrupter interface +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +/// @note The leapfrog algorithm employed in this method is best suited +/// for a single large sphere. For multiple small spheres consider +/// using the faster algorithm in ParticlesToLevelSet.h +template +typename GridType::Ptr +createLevelSetSphere(float radius, const openvdb::Vec3f& center, float voxelSize, + float halfWidth = float(LEVEL_SET_HALF_WIDTH), InterruptT* interrupt = NULL); + +/// @brief Return a grid of type @c GridType containing a narrow-band level set +/// representation of a sphere. +/// +/// @param radius radius of the sphere in world units +/// @param center center of the sphere in world units +/// @param voxelSize voxel size in world units +/// @param halfWidth half the width of the narrow band, in voxel units +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +/// @note The leapfrog algorithm employed in this method is best suited +/// for a single large sphere. For multiple small spheres consider +/// using the faster algorithm in ParticlesToLevelSet.h +template +typename GridType::Ptr +createLevelSetSphere(float radius, const openvdb::Vec3f& center, float voxelSize, + float halfWidth = float(LEVEL_SET_HALF_WIDTH)) +{ + return createLevelSetSphere(radius,center,voxelSize,halfWidth); +} + + +//////////////////////////////////////// + + +/// @brief Generates a signed distance field (or narrow band level +/// set) to a single sphere. +/// +/// @note The leapfrog algorithm employed in this class is best +/// suited for a single large sphere. For multiple small spheres consider +/// using the faster algorithm in tools/ParticlesToLevelSet.h +template +class LevelSetSphere +{ +public: + typedef typename GridT::ValueType ValueT; + typedef typename math::Vec3 Vec3T; + BOOST_STATIC_ASSERT(boost::is_floating_point::value); + + /// @brief Constructor + /// + /// @param radius radius of the sphere in world units + /// @param center center of the sphere in world units + /// @param interrupt pointer to optional interrupter. Use template + /// argument util::NullInterrupter if no interruption is desired. + /// + /// @note If the radius of the sphere is smaller then + /// 1.5*voxelSize, i.e. the sphere is smaller then the Nyquist + /// frequency of the grid, it is ignored! + LevelSetSphere(ValueT radius, const Vec3T ¢er, InterruptT* interrupt = NULL) + : mRadius(radius), mCenter(center), mInterrupt(interrupt) + { + if (mRadius<=0) OPENVDB_THROW(ValueError, "radius must be positive"); + } + + /// @return a narrow-band level set of the sphere + /// + /// @param voxelSize Size of voxels in world units + /// @param halfWidth Half-width of narrow-band in voxel units + typename GridT::Ptr getLevelSet(ValueT voxelSize, ValueT halfWidth) + { + mGrid = createLevelSet(voxelSize, halfWidth); + this->rasterSphere(voxelSize, halfWidth); + mGrid->setGridClass(GRID_LEVEL_SET); + return mGrid; + } + +private: + void rasterSphere(ValueT dx, ValueT w) + { + if (!(dx>0.0f)) OPENVDB_THROW(ValueError, "voxel size must be positive"); + if (!(w>1)) OPENVDB_THROW(ValueError, "half-width must be larger than one"); + + // Define radius of sphere and narrow-band in voxel units + const ValueT r0 = mRadius/dx, rmax = r0 + w; + + // Radius below the Nyquist frequency + if (r0 < 1.5f) return; + + // Define center of sphere in voxel units + const Vec3T c(mCenter[0]/dx, mCenter[1]/dx, mCenter[2]/dx); + + // Define index coordinates and their respective bounds + openvdb::Coord ijk; + int &i = ijk[0], &j = ijk[1], &k = ijk[2], m=1; + const int imin=math::Floor(c[0]-rmax), imax=math::Ceil(c[0]+rmax); + const int jmin=math::Floor(c[1]-rmax), jmax=math::Ceil(c[1]+rmax); + const int kmin=math::Floor(c[2]-rmax), kmax=math::Ceil(c[2]+rmax); + + // Allocate an ValueAccessor for accelerated random access + typename GridT::Accessor accessor = mGrid->getAccessor(); + + if (mInterrupt) mInterrupt->start("Generating level set of sphere"); + // Compute signed distances to sphere using leapfrogging in k + for ( i = imin; i <= imax; ++i ) { + if (util::wasInterrupted(mInterrupt)) return; + const float x2 = math::Pow2(i - c[0]); + for ( j = jmin; j <= jmax; ++j ) { + const float x2y2 = math::Pow2(j - c[1]) + x2; + for (k=kmin; k<=kmax; k += m) { + m = 1; + /// Distance in voxel units to sphere + const float v = math::Sqrt(x2y2 + math::Pow2(k-c[2]))-r0, + d = math::Abs(v); + if ( d < w ){ // inside narrow band + accessor.setValue(ijk, dx*v);// distance in world units + } else {// outside narrow band + m += math::Floor(d-w);// leapfrog + } + }//end leapfrog over k + }//end loop over j + }//end loop over i + + // Define consistant signed distances outside the narrow-band + mGrid->signedFloodFill(); + if (mInterrupt) mInterrupt->end(); + } + + const ValueT mRadius; + const Vec3T mCenter; + InterruptT* mInterrupt; + typename GridT::Ptr mGrid; +};// LevelSetSphere + + +//////////////////////////////////////// + + +template +typename GridType::Ptr +createLevelSetSphere(float radius, const openvdb::Vec3f& center, float voxelSize, + float halfWidth, InterruptT* interrupt) +{ + // GridType::ValueType is required to be a floating-point scalar. + BOOST_STATIC_ASSERT(boost::is_floating_point::value); + + typedef typename GridType::ValueType ValueT; + LevelSetSphere factory(ValueT(radius), center, interrupt); + return factory.getLevelSet(ValueT(voxelSize), ValueT(halfWidth)); +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_LEVELSETSPHERE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/LevelSetTracker.h b/openvdb_2_3_0_library/openvdb/tools/LevelSetTracker.h new file mode 100755 index 0000000..daf28dc --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/LevelSetTracker.h @@ -0,0 +1,519 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @author Ken Museth +/// +/// @file LevelSetTracker.h +/// +/// @brief Performs multi-threaded interface tracking of narrow band +/// level sets. This is the building-block for most level set +/// computations that involve dynamic topology, e.g. advection. + +#ifndef OPENVDB_TOOLS_LEVEL_SET_TRACKER_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVEL_SET_TRACKER_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Morphology.h"//for tools::dilateVoxels + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Performs multi-threaded interface tracking of narrow band level sets +template +class LevelSetTracker +{ +public: + typedef GridT GridType; + typedef typename GridT::TreeType TreeType; + typedef typename TreeType::LeafNodeType LeafType; + typedef typename TreeType::ValueType ValueType; + typedef typename tree::LeafManager LeafManagerType; // leafs + buffers + typedef typename LeafManagerType::RangeType RangeType; + typedef typename LeafManagerType::LeafRange LeafRange; + typedef typename LeafManagerType::BufferType BufferType; + BOOST_STATIC_ASSERT(boost::is_floating_point::value); + + /// Main constructor + /// @throw RuntimeError if the grid is not a level set + LevelSetTracker(GridT& grid, InterruptT* interrupt = NULL); + + /// Shallow copy constructor called by tbb::parallel_for() threads during filtering + LevelSetTracker(const LevelSetTracker& other); + + virtual ~LevelSetTracker() { if (mIsMaster) delete mLeafs; } + + /// Iterative normalization, i.e. solving the Eikonal equation + void normalize(); + + /// Track the level set interface, i.e. rebuild and normalize the + /// narrow band of the level set. + void track(); + + /// Remove voxels that are outside the narrow band. (substep of track) + void prune(); + + /// @return the spatial finite difference scheme + math::BiasedGradientScheme getSpatialScheme() const { return mSpatialScheme; } + /// @brief Set the spatial finite difference scheme + void setSpatialScheme(math::BiasedGradientScheme scheme) { mSpatialScheme = scheme; } + + /// @return the temporal integration scheme + math::TemporalIntegrationScheme getTemporalScheme() const { return mTemporalScheme; } + /// @brief Set the spatial finite difference scheme + void setTemporalScheme(math::TemporalIntegrationScheme scheme) { mTemporalScheme = scheme; } + + /// @return The number of normalizations performed per track or + /// normalize call. + int getNormCount() const { return mNormCount; } + /// @brief Set the number of normalizations performed per track or + /// normalize call. + void setNormCount(int n) { mNormCount = n; } + + /// @return the grain-size used for multi-threading + int getGrainSize() const { return mGrainSize; } + /// @brief Set the grain-size used for multi-threading. + /// @note A grainsize of 0 or less disables multi-threading! + void setGrainSize(int grainsize) { mGrainSize = grainsize; } + + ValueType voxelSize() const { return mDx; } + + void startInterrupter(const char* msg); + void endInterrupter(); + /// @return false if the process was interrupted + bool checkInterrupter(); + + const GridType& grid() const { return *mGrid; } + + LeafManagerType& leafs() { return *mLeafs; } + const LeafManagerType& leafs() const { return *mLeafs; } + + /// @brief Public functor called by tbb::parallel_for() + /// @note Never call this method directly + void operator()(const RangeType& r) const + { + if (mTask) mTask(const_cast(this), r); + else OPENVDB_THROW(ValueError, "task is undefined - call track(), etc"); + } + +private: + + template + struct Normalizer + { + Normalizer(LevelSetTracker& tracker): mTracker(tracker), mTask(0) {} + void normalize(); + void operator()(const RangeType& r) const {mTask(const_cast(this), r);} + typedef typename boost::function FuncType; + LevelSetTracker& mTracker; + FuncType mTask; + void cook(int swapBuffer=0); + void euler1(const RangeType& range, ValueType dt, Index resultBuffer); + void euler2(const RangeType& range, ValueType dt, ValueType alpha, + Index phiBuffer, Index resultBuffer); + }; // end of protected Normalizer class + + typedef typename boost::function FuncType; + + void trim(const RangeType& r); + + template + void normalize1(); + + template + void normalize2(); + + // Throughout the methods below mLeafs is always assumed to contain + // a list of the current LeafNodes! The auxiliary buffers on the + // other hand always have to be allocated locally, since some + // methods need them and others don't! + GridType* mGrid; + LeafManagerType* mLeafs; + InterruptT* mInterrupter; + const ValueType mDx; + math::BiasedGradientScheme mSpatialScheme; + math::TemporalIntegrationScheme mTemporalScheme; + int mNormCount;// Number of iteratations of normalization + int mGrainSize; + FuncType mTask; + const bool mIsMaster; + + // disallow copy by assignment + void operator=(const LevelSetTracker& other) {} + +}; // end of LevelSetTracker class + +template +LevelSetTracker::LevelSetTracker(GridT& grid, InterruptT* interrupt): + mGrid(&grid), + mLeafs(new LeafManagerType(grid.tree())), + mInterrupter(interrupt), + mDx(grid.voxelSize()[0]), + mSpatialScheme(math::HJWENO5_BIAS), + mTemporalScheme(math::TVD_RK1), + mNormCount(static_cast(LEVEL_SET_HALF_WIDTH)), + mGrainSize(1), + mTask(0), + mIsMaster(true)// N.B. +{ + if ( !grid.hasUniformVoxels() ) { + OPENVDB_THROW(RuntimeError, + "The transform must have uniform scale for the LevelSetTracker to function"); + } + if ( grid.getGridClass() != GRID_LEVEL_SET) { + OPENVDB_THROW(RuntimeError, + "LevelSetTracker only supports level sets!\n" + "However, only level sets are guaranteed to work!\n" + "Hint: Grid::setGridClass(openvdb::GRID_LEVEL_SET)"); + } +} + +template +LevelSetTracker::LevelSetTracker(const LevelSetTracker& other): + mGrid(other.mGrid), + mLeafs(other.mLeafs), + mInterrupter(other.mInterrupter), + mDx(other.mDx), + mSpatialScheme(other.mSpatialScheme), + mTemporalScheme(other.mTemporalScheme), + mNormCount(other.mNormCount), + mGrainSize(other.mGrainSize), + mTask(other.mTask), + mIsMaster(false)// N.B. +{ +} + +template +inline void +LevelSetTracker::prune() +{ + this->startInterrupter("Pruning Level Set"); + // Prune voxels that are too far away from the zero-crossing + mTask = boost::bind(&LevelSetTracker::trim, _1, _2); + if (mGrainSize>0) { + tbb::parallel_for(mLeafs->getRange(mGrainSize), *this); + } else { + (*this)(mLeafs->getRange()); + } + + // Remove inactive nodes from tree + mGrid->tree().pruneLevelSet(); + + // The tree topology has changes so rebuild the list of leafs + mLeafs->rebuildLeafArray(); + this->endInterrupter(); +} + +template +inline void +LevelSetTracker::track() +{ + // Dilate narrow-band (this also rebuilds the leaf array!) + tools::dilateVoxels(*mLeafs); + + // Compute signed distances in dilated narrow-band + this->normalize(); + + // Remove voxels that are outside the narrow band + this->prune(); +} + +template +inline void +LevelSetTracker::startInterrupter(const char* msg) +{ + if (mInterrupter) mInterrupter->start(msg); +} + +template +inline void +LevelSetTracker::endInterrupter() +{ + if (mInterrupter) mInterrupter->end(); +} + +template +inline bool +LevelSetTracker::checkInterrupter() +{ + if (util::wasInterrupted(mInterrupter)) { + tbb::task::self().cancel_group_execution(); + return false; + } + return true; +} + +/// Prunes away voxels that have moved outside the narrow band +template +inline void +LevelSetTracker::trim(const RangeType& range) +{ + typedef typename LeafType::ValueOnIter VoxelIterT; + const_cast(this)->checkInterrupter(); + const ValueType gamma = mGrid->background(); + for (size_t n=range.begin(), e=range.end(); n != e; ++n) { + LeafType &leaf = mLeafs->leaf(n); + for (VoxelIterT iter = leaf.beginValueOn(); iter; ++iter) { + const ValueType val = *iter; + if (val < -gamma) + leaf.setValueOff(iter.pos(), -gamma); + else if (val > gamma) + leaf.setValueOff(iter.pos(), gamma); + } + } +} + +template +inline void +LevelSetTracker::normalize() +{ + switch (mSpatialScheme) { + case math::FIRST_BIAS: + this->normalize1(); break; + case math::SECOND_BIAS: + this->normalize1(); break; + case math::THIRD_BIAS: + this->normalize1(); break; + case math::WENO5_BIAS: + this->normalize1(); break; + case math::HJWENO5_BIAS: + this->normalize1(); break; + default: + OPENVDB_THROW(ValueError, "Spatial difference scheme not supported!"); + } +} + +template +template +inline void +LevelSetTracker::normalize1() +{ + switch (mTemporalScheme) { + case math::TVD_RK1: + this->normalize2(); break; + case math::TVD_RK2: + this->normalize2(); break; + case math::TVD_RK3: + this->normalize2(); break; + default: + OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); + } +} + +template +template +inline void +LevelSetTracker::normalize2() +{ + Normalizer tmp(*this); + tmp.normalize(); +} + +template +template +inline void +LevelSetTracker::Normalizer:: +normalize() +{ + /// Make sure we have enough temporal auxiliary buffers + mTracker.mLeafs->rebuildAuxBuffers(TemporalScheme == math::TVD_RK3 ? 2 : 1); + + const ValueType dt = (TemporalScheme == math::TVD_RK1 ? ValueType(0.3) : + TemporalScheme == math::TVD_RK2 ? ValueType(0.9) : ValueType(1.0)) + * ValueType(mTracker.voxelSize()); + + for (int n=0, e=mTracker.getNormCount(); n < e; ++n) { + + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + switch(TemporalScheme) {//switch is resolved at compile-time + case math::TVD_RK1: + //std::cerr << "1"; + // Perform one explicit Euler step: t1 = t0 + dt + // Phi_t1(0) = Phi_t0(0) - dt * VdotG_t0(1) + mTask = boost::bind(&Normalizer::euler1, _1, _2, dt, /*result=*/1); + // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) + this->cook(1); + break; + case math::TVD_RK2: + //std::cerr << "2"; + // Perform one explicit Euler step: t1 = t0 + dt + // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(1) + mTask = boost::bind(&Normalizer::euler1, _1, _2, dt, /*result=*/1); + // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) + this->cook(1); + + // Convex combine explict Euler step: t2 = t0 + dt + // Phi_t2(1) = 1/2 * Phi_t0(1) + 1/2 * (Phi_t1(0) - dt * V.Grad_t1(0)) + mTask = boost::bind(&Normalizer::euler2, + _1, _2, dt, ValueType(0.5), /*phi=*/1, /*result=*/1); + // Cook and swap buffer 0 and 1 such that Phi_t2(0) and Phi_t1(1) + this->cook(1); + break; + case math::TVD_RK3: + //std::cerr << "3"; + // Perform one explicit Euler step: t1 = t0 + dt + // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(1) + mTask = boost::bind(&Normalizer::euler1, _1, _2, dt, /*result=*/1); + // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) + this->cook(1); + + // Convex combine explict Euler step: t2 = t0 + dt/2 + // Phi_t2(2) = 3/4 * Phi_t0(1) + 1/4 * (Phi_t1(0) - dt * V.Grad_t1(0)) + mTask = boost::bind(&Normalizer::euler2, + _1, _2, dt, ValueType(0.75), /*phi=*/1, /*result=*/2); + // Cook and swap buffer 0 and 2 such that Phi_t2(0) and Phi_t1(2) + this->cook(2); + + // Convex combine explict Euler step: t3 = t0 + dt + // Phi_t3(2) = 1/3 * Phi_t0(1) + 2/3 * (Phi_t2(0) - dt * V.Grad_t2(0) + mTask = boost::bind(&Normalizer::euler2, + _1, _2, dt, ValueType(1.0/3.0), /*phi=*/1, /*result=*/2); + // Cook and swap buffer 0 and 2 such that Phi_t3(0) and Phi_t2(2) + this->cook(2); + break; + default: + OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); + } + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + mTracker.mLeafs->removeAuxBuffers(); +} + +/// Private method to perform the task (serial or threaded) and +/// subsequently swap the leaf buffers. +template +template +inline void +LevelSetTracker::Normalizer:: +cook(int swapBuffer) +{ + mTracker.startInterrupter("Normalizing Level Set"); + + if (mTracker.getGrainSize()>0) { + tbb::parallel_for(mTracker.mLeafs->getRange(mTracker.getGrainSize()), *this); + } else { + (*this)(mTracker.mLeafs->getRange()); + } + + mTracker.mLeafs->swapLeafBuffer(swapBuffer, mTracker.getGrainSize()==0); + + mTracker.endInterrupter(); +} + + +/// Perform normalization using one of the upwinding schemes +/// This currently supports only forward Euler time integration +/// and is not expected to work well with the higher-order spactial schemes +template +template +inline void +LevelSetTracker::Normalizer:: +euler1(const RangeType &range, ValueType dt, Index resultBuffer) +{ + typedef math::BIAS_SCHEME Scheme; + typedef typename Scheme::template ISStencil::StencilType Stencil; + typedef typename LeafType::ValueOnCIter VoxelIterT; + mTracker.checkInterrupter(); + const ValueType one(1.0), invDx = one/mTracker.voxelSize(); + Stencil stencil(mTracker.grid()); + for (size_t n=range.begin(), e=range.end(); n != e; ++n) { + BufferType& result = mTracker.mLeafs->getBuffer(n, resultBuffer); + const LeafType& leaf = mTracker.mLeafs->leaf(n); + for (VoxelIterT iter = leaf.cbeginValueOn(); iter; ++iter) { + stencil.moveTo(iter); + const ValueType normSqGradPhi = + math::ISGradientNormSqrd::result(stencil); + const ValueType phi0 = stencil.getValue(); + const ValueType diff = math::Sqrt(normSqGradPhi)*invDx - one; + const ValueType S = phi0 / (math::Sqrt(math::Pow2(phi0) + normSqGradPhi)); + result.setValue(iter.pos(), phi0 - dt * S * diff); + } + } +} + +template +template +inline void +LevelSetTracker::Normalizer:: +euler2(const RangeType& range, ValueType dt, ValueType alpha, Index phiBuffer, Index resultBuffer) +{ + typedef math::BIAS_SCHEME Scheme; + typedef typename Scheme::template ISStencil::StencilType Stencil; + typedef typename LeafType::ValueOnCIter VoxelIterT; + mTracker.checkInterrupter(); + const ValueType one(1.0), beta = one - alpha, invDx = one/mTracker.voxelSize(); + Stencil stencil(mTracker.grid()); + for (size_t n=range.begin(), e=range.end(); n != e; ++n) { + const BufferType& phi = mTracker.mLeafs->getBuffer(n, phiBuffer); + BufferType& result = mTracker.mLeafs->getBuffer(n, resultBuffer); + const LeafType& leaf = mTracker.mLeafs->leaf(n); + for (VoxelIterT iter = leaf.cbeginValueOn(); iter; ++iter) { + stencil.moveTo(iter); + const ValueType normSqGradPhi = + math::ISGradientNormSqrd::result(stencil); + const ValueType phi0 = stencil.getValue(); + const ValueType diff = math::Sqrt(normSqGradPhi)*invDx - one; + const ValueType S = phi0 / (math::Sqrt(math::Pow2(phi0) + normSqGradPhi)); + result.setValue(iter.pos(), alpha*phi[iter.pos()] + beta*(phi0 - dt * S * diff)); + } + } +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_LEVEL_SET_TRACKER_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/LevelSetUtil.h b/openvdb_2_3_0_library/openvdb/tools/LevelSetUtil.h new file mode 100755 index 0000000..a7f5e06 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/LevelSetUtil.h @@ -0,0 +1,399 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file tools/LevelSetUtil.h +/// +/// @brief Miscellaneous utilities that operate primarily or exclusively +/// on level set grids + +#ifndef OPENVDB_TOOLS_LEVELSETUTIL_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVELSETUTIL_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +// MS Visual C++ requires this extra level of indirection in order to compile +// THIS MUST EXIST IN AN UNNAMED NAMESPACE IN ORDER TO COMPILE ON WINDOWS +namespace { + +template +inline typename GridType::ValueType lsutilGridMax() +{ + return std::numeric_limits::max(); +} + +template +inline typename GridType::ValueType lsutilGridZero() +{ + return zeroVal(); +} + +} // unnamed namespace + + +//////////////////////////////////////// + + +/// @brief Threaded method to convert a sparse level set/SDF into a sparse fog volume +/// +/// @details For a level set, the active and negative-valued interior half of the +/// narrow band becomes a linear ramp from 0 to 1; the inactive interior becomes +/// active with a constant value of 1; and the exterior, including the background +/// and the active exterior half of the narrow band, becomes inactive with a constant +/// value of 0. The interior, though active, remains sparse. +/// @details For a generic SDF, a specified cutoff distance determines the width +/// of the ramp, but otherwise the result is the same as for a level set. +/// +/// @param grid level set/SDF grid to transform +/// @param cutoffDistance optional world space cutoff distance for the ramp +/// (automatically clamped if greater than the interior +/// narrow band width) +template +inline void +sdfToFogVolume( + GridType& grid, + typename GridType::ValueType cutoffDistance = lsutilGridMax()); + + +//////////////////////////////////////// + + +/// @brief Threaded method to extract an interior region mask from a level set/SDF grid +/// +/// @return a shared pointer to a new boolean grid with the same tree configuration and +/// transform as the incoming @c grid and whose active voxels correspond to +/// the interior of the input SDF +/// +/// @param grid a level set/SDF grid +/// @param iso threshold below which values are considered to be part of the interior region +/// +template +inline typename Grid::Type>::Ptr +sdfInteriorMask( + const GridType& grid, + typename GridType::ValueType iso = lsutilGridZero()); + + +//////////////////////////////////////// + + +/// @brief Threaded operator that finds the minimum and maximum values +/// among the active leaf-level voxels of a grid +/// @details This is useful primarily for level set grids, which have +/// no active tiles (all of their active voxels are leaf-level). +template +class MinMaxVoxel +{ +public: + typedef tree::LeafManager LeafArray; + typedef typename TreeType::ValueType ValueType; + + // LeafArray = openvdb::tree::LeafManager leafs(myTree) + MinMaxVoxel(LeafArray&); + + void runParallel(); + void runSerial(); + + const ValueType& minVoxel() const { return mMin; } + const ValueType& maxVoxel() const { return mMax; } + + inline MinMaxVoxel(const MinMaxVoxel&, tbb::split); + inline void operator()(const tbb::blocked_range&); + inline void join(const MinMaxVoxel&); + +private: + LeafArray& mLeafArray; + ValueType mMin, mMax; +}; + + +//////////////////////////////////////// + + +// Internal utility objects and implementation details +namespace internal { + +template +class FogVolumeOp +{ +public: + FogVolumeOp(ValueType cutoffDistance) + : mWeight(ValueType(1.0) / cutoffDistance) + { + } + + // cutoff has to be < 0.0 + template + void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const + { + const ValueType zero = zeroVal(); + + for (typename LeafNodeType::ValueAllIter iter = leaf.beginValueAll(); iter; ++iter) { + ValueType& value = const_cast(iter.getValue()); + if (value > zero) { + value = zero; + iter.setValueOff(); + } else { + value = std::min(ValueType(1.0), value * mWeight); + iter.setValueOn(value > zero); + } + } + } + +private: + ValueType mWeight; +}; // class FogVolumeOp + + +template +class InteriorMaskOp +{ +public: + InteriorMaskOp(const TreeType& tree, typename TreeType::ValueType iso) + : mTree(tree) + , mIso(iso) + { + } + + template + void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const + { + const Coord origin = leaf.origin(); + const typename TreeType::LeafNodeType* refLeafPt = mTree.probeConstLeaf(origin); + + if (refLeafPt != NULL) { + + const typename TreeType::LeafNodeType& refLeaf = *refLeafPt; + typename LeafNodeType::ValueAllIter iter = leaf.beginValueAll(); + + for (; iter; ++iter) { + if (refLeaf.getValue(iter.pos()) < mIso) { + iter.setValueOn(); + } else { + iter.setValueOff(); + } + } + } + } + +private: + const TreeType& mTree; + typename TreeType::ValueType mIso; +}; // class InteriorMaskOp + +} // namespace internal + + +//////////////////////////////////////// + + +template +MinMaxVoxel::MinMaxVoxel(LeafArray& leafs) + : mLeafArray(leafs) + , mMin(std::numeric_limits::max()) + , mMax(-mMin) +{ +} + + +template +inline +MinMaxVoxel::MinMaxVoxel(const MinMaxVoxel& rhs, tbb::split) + : mLeafArray(rhs.mLeafArray) + , mMin(rhs.mMin) + , mMax(rhs.mMax) +{ +} + + +template +void +MinMaxVoxel::runParallel() +{ + tbb::parallel_reduce(mLeafArray.getRange(), *this); +} + + +template +void +MinMaxVoxel::runSerial() +{ + (*this)(mLeafArray.getRange()); +} + + +template +inline void +MinMaxVoxel::operator()(const tbb::blocked_range& range) +{ + typename TreeType::LeafNodeType::ValueOnCIter iter; + + for (size_t n = range.begin(); n < range.end(); ++n) { + iter = mLeafArray.leaf(n).cbeginValueOn(); + for (; iter; ++iter) { + const ValueType value = iter.getValue(); + mMin = std::min(mMin, value); + mMax = std::max(mMax, value); + } + } +} + + +template +inline void +MinMaxVoxel::join(const MinMaxVoxel& rhs) +{ + mMin = std::min(mMin, rhs.mMin); + mMax = std::max(mMax, rhs.mMax); +} + + + +//////////////////////////////////////// + + +template +inline void +sdfToFogVolume(GridType& grid, typename GridType::ValueType cutoffDistance) +{ + typedef typename GridType::TreeType TreeType; + typedef typename GridType::ValueType ValueType; + + cutoffDistance = -std::abs(cutoffDistance); + + TreeType& tree = const_cast(grid.tree()); + + { // Transform all voxels (parallel, over leaf nodes) + tree::LeafManager leafs(tree); + + MinMaxVoxel minmax(leafs); + minmax.runParallel(); + + // Clamp to the interior band width. + if (minmax.minVoxel() > cutoffDistance) { + cutoffDistance = minmax.minVoxel(); + } + + leafs.foreach(internal::FogVolumeOp(cutoffDistance)); + } + + // Transform all tile values (serial, but the iteration + // is constrained from descending into leaf nodes) + const ValueType zero = zeroVal(); + typename TreeType::ValueAllIter iter(tree); + iter.setMaxDepth(TreeType::ValueAllIter::LEAF_DEPTH - 1); + + for ( ; iter; ++iter) { + ValueType& value = const_cast(iter.getValue()); + + if (value > zero) { + value = zero; + iter.setValueOff(); + } else { + value = ValueType(1.0); + iter.setActiveState(true); + } + } + + // Update the tree background value. + + typename TreeType::Ptr newTree(new TreeType(/*background=*/zero)); + newTree->merge(tree); + // This is faster than calling Tree::setBackground, since we only need + // to update the value that is returned for coordinates that don't fall + // inside an allocated node. All inactive tiles and voxels have already + // been updated in the previous step so the Tree::setBackground method + // will in this case do a redundant traversal of the tree to update the + // inactive values once more. + + //newTree->pruneInactive(); + grid.setTree(newTree); + + grid.setGridClass(GRID_FOG_VOLUME); +} + + +//////////////////////////////////////// + + +template +inline typename Grid::Type>::Ptr +sdfInteriorMask(const GridType& grid, typename GridType::ValueType iso) +{ + typedef typename GridType::TreeType::template ValueConverter::Type BoolTreeType; + typedef Grid BoolGridType; + + typename BoolGridType::Ptr maskGrid(BoolGridType::create(false)); + maskGrid->setTransform(grid.transform().copy()); + BoolTreeType& maskTree = maskGrid->tree(); + + maskTree.topologyUnion(grid.tree()); + + { // Evaluate voxels (parallel, over leaf nodes) + + tree::LeafManager leafs(maskTree); + + leafs.foreach(internal::InteriorMaskOp(grid.tree(), iso)); + } + + // Evaluate tile values (serial, but the iteration + // is constrained from descending into leaf nodes) + + tree::ValueAccessor acc(grid.tree()); + typename BoolTreeType::ValueAllIter iter(maskTree); + iter.setMaxDepth(BoolTreeType::ValueAllIter::LEAF_DEPTH - 1); + + for ( ; iter; ++iter) { + iter.setActiveState(acc.getValue(iter.getCoord()) < iso); + } + + maskTree.pruneInactive(); + + return maskGrid; +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_LEVELSETUTIL_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/MeshToVolume.h b/openvdb_2_3_0_library/openvdb/tools/MeshToVolume.h new file mode 100755 index 0000000..457c0b6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/MeshToVolume.h @@ -0,0 +1,3233 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED + +#include +#include +#include // for ISGradientNormSqrd +#include // for closestPointOnTriangleToPoint() +#include // for dilateVoxels() +#include +#include // for nearestCoord() + +#include +#include +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + + +//////////////////////////////////////// + + +// Wrapper functions for the MeshToVolume converter + + +/// @brief Convert a triangle mesh to a level set volume. +/// +/// @return a grid of type @c GridType containing a narrow-band level set +/// representation of the input mesh. +/// +/// @throw TypeError if @c GridType is not scalar or not floating-point +/// +/// @note Requires a closed surface but not necessarily a manifold surface. +/// Supports surfaces with self intersections and degenerate faces +/// and is independent of mesh surface normals. +/// +/// @param xform transform for the output grid +/// @param points list of world space point positions +/// @param triangles triangle index list +/// @param halfWidth half the width of the narrow band, in voxel units +template +inline typename GridType::Ptr +meshToLevelSet( + const openvdb::math::Transform& xform, + const std::vector& points, + const std::vector& triangles, + float halfWidth = float(LEVEL_SET_HALF_WIDTH)); + + +/// @brief Convert a quad mesh to a level set volume. +/// +/// @return a grid of type @c GridType containing a narrow-band level set +/// representation of the input mesh. +/// +/// @throw TypeError if @c GridType is not scalar or not floating-point +/// +/// @note Requires a closed surface but not necessarily a manifold surface. +/// Supports surfaces with self intersections and degenerate faces +/// and is independent of mesh surface normals. +/// +/// @param xform transform for the output grid +/// @param points list of world space point positions +/// @param quads quad index list +/// @param halfWidth half the width of the narrow band, in voxel units +template +inline typename GridType::Ptr +meshToLevelSet( + const openvdb::math::Transform& xform, + const std::vector& points, + const std::vector& quads, + float halfWidth = float(LEVEL_SET_HALF_WIDTH)); + + +/// @brief Convert a triangle and quad mesh to a level set volume. +/// +/// @return a grid of type @c GridType containing a narrow-band level set +/// representation of the input mesh. +/// +/// @throw TypeError if @c GridType is not scalar or not floating-point +/// +/// @note Requires a closed surface but not necessarily a manifold surface. +/// Supports surfaces with self intersections and degenerate faces +/// and is independent of mesh surface normals. +/// +/// @param xform transform for the output grid +/// @param points list of world space point positions +/// @param triangles triangle index list +/// @param quads quad index list +/// @param halfWidth half the width of the narrow band, in voxel units +template +inline typename GridType::Ptr +meshToLevelSet( + const openvdb::math::Transform& xform, + const std::vector& points, + const std::vector& triangles, + const std::vector& quads, + float halfWidth = float(LEVEL_SET_HALF_WIDTH)); + + +/// @brief Convert a triangle and quad mesh to a signed distance field +/// with an asymmetrical narrow band. +/// +/// @return a grid of type @c GridType containing a narrow-band signed +/// distance field representation of the input mesh. +/// +/// @throw TypeError if @c GridType is not scalar or not floating-point +/// +/// @note Requires a closed surface but not necessarily a manifold surface. +/// Supports surfaces with self intersections and degenerate faces +/// and is independent of mesh surface normals. +/// +/// @param xform transform for the output grid +/// @param points list of world space point positions +/// @param triangles triangle index list +/// @param quads quad index list +/// @param exBandWidth the exterior narrow-band width in voxel units +/// @param inBandWidth the interior narrow-band width in voxel units +template +inline typename GridType::Ptr +meshToSignedDistanceField( + const openvdb::math::Transform& xform, + const std::vector& points, + const std::vector& triangles, + const std::vector& quads, + float exBandWidth, + float inBandWidth); + + +/// @brief Convert a triangle and quad mesh to an unsigned distance field. +/// +/// @return a grid of type @c GridType containing a narrow-band unsigned +/// distance field representation of the input mesh. +/// +/// @throw TypeError if @c GridType is not scalar or not floating-point +/// +/// @note Does not requires a closed surface. +/// +/// @param xform transform for the output grid +/// @param points list of world space point positions +/// @param triangles triangle index list +/// @param quads quad index list +/// @param bandWidth the width of the narrow band, in voxel units +template +inline typename GridType::Ptr +meshToUnsignedDistanceField( + const openvdb::math::Transform& xform, + const std::vector& points, + const std::vector& triangles, + const std::vector& quads, + float bandWidth); + + +//////////////////////////////////////// + + +/// Conversion flags, used to control the MeshToVolume output +enum { GENERATE_PRIM_INDEX_GRID = 0x1, OUTPUT_RAW_DATA = 0x2}; + + +// MeshToVolume +template +class MeshToVolume +{ +public: + typedef typename FloatGridT::TreeType FloatTreeT; + typedef typename FloatTreeT::ValueType FloatValueT; + typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; + typedef Grid IntGridT; + typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; + typedef Grid BoolGridT; + + MeshToVolume(openvdb::math::Transform::Ptr&, int conversionFlags = 0, + InterruptT *interrupter = NULL, int signSweeps = 1); + + /// @brief Mesh to Level Set / Signed Distance Field conversion + /// + /// @note Requires a closed surface but not necessarily a manifold surface. + /// Supports surfaces with self intersections, degenerate faces and + /// is independent of mesh surface normals. + /// + /// @param pointList List of points in grid index space, preferably unique + /// and shared by different polygons. + /// @param polygonList List of triangles and/or quads. + /// @param exBandWidth The exterior narrow-band width in voxel units. + /// @param inBandWidth The interior narrow-band width in voxel units. + void convertToLevelSet( + const std::vector& pointList, + const std::vector& polygonList, + FloatValueT exBandWidth = FloatValueT(LEVEL_SET_HALF_WIDTH), + FloatValueT inBandWidth = FloatValueT(LEVEL_SET_HALF_WIDTH)); + + /// @brief Mesh to Unsigned Distance Field conversion + /// + /// @note Does not requires a closed surface. + /// + /// @param pointList List of points in grid index space, preferably unique + /// and shared by different polygons. + /// @param polygonList List of triangles and/or quads. + /// @param exBandWidth The narrow-band width in voxel units. + void convertToUnsignedDistanceField(const std::vector& pointList, + const std::vector& polygonList, FloatValueT exBandWidth); + + void clear(); + + /// Returns a narrow-band (signed) distance field / level set grid. + typename FloatGridT::Ptr distGridPtr() const { return mDistGrid; } + + /// Returns a grid containing the closest-primitive index for each + /// voxel in the narrow-band. + typename IntGridT::Ptr indexGridPtr() const { return mIndexGrid; } + +private: + // disallow copy by assignment + void operator=(const MeshToVolume&) {} + + void doConvert(const std::vector&, const std::vector&, + FloatValueT exBandWidth, FloatValueT inBandWidth, bool unsignedDistField = false); + + bool wasInterrupted(int percent = -1) const { + return mInterrupter && mInterrupter->wasInterrupted(percent); + } + + openvdb::math::Transform::Ptr mTransform; + int mConversionFlags, mSignSweeps; + + typename FloatGridT::Ptr mDistGrid; + typename IntGridT::Ptr mIndexGrid; + typename BoolGridT::Ptr mIntersectingVoxelsGrid; + + InterruptT *mInterrupter; +}; + + +//////////////////////////////////////// + + +/// @brief Extracts and stores voxel edge intersection data from a mesh. +class MeshToVoxelEdgeData +{ +public: + + ////////// + + ///@brief Internal edge data type. + struct EdgeData { + EdgeData(float dist = 1.0) + : mXDist(dist), mYDist(dist), mZDist(dist) + , mXPrim(util::INVALID_IDX) + , mYPrim(util::INVALID_IDX) + , mZPrim(util::INVALID_IDX) + { + } + + //@{ + /// Required by several of the tree nodes + /// @note These methods don't perform meaningful operations. + bool operator< (const EdgeData&) const { return false; }; + bool operator> (const EdgeData&) const { return false; }; + template EdgeData operator+(const T&) const { return *this; } + template EdgeData operator-(const T&) const { return *this; } + EdgeData operator-() const { return *this; } + //@} + + bool operator==(const EdgeData& rhs) const + { + return mXPrim == rhs.mXPrim && mYPrim == rhs.mYPrim && mZPrim == rhs.mZPrim; + } + + float mXDist, mYDist, mZDist; + Index32 mXPrim, mYPrim, mZPrim; + }; + + typedef tree::Tree4::Type TreeType; + typedef tree::ValueAccessor Accessor; + + + ////////// + + + MeshToVoxelEdgeData(); + + + /// @brief Threaded method to extract voxel edge data, the closest + /// intersection point and corresponding primitive index, + /// from the given mesh. + /// + /// @param pointList List of points in grid index space, preferably unique + /// and shared by different polygons. + /// @param polygonList List of triangles and/or quads. + void convert(const std::vector& pointList, const std::vector& polygonList); + + + /// @brief Returns intersection points with corresponding primitive + /// indices for the given @c ijk voxel. + void getEdgeData(Accessor& acc, const Coord& ijk, + std::vector& points, std::vector& primitives); + + /// @return An accessor of @c MeshToVoxelEdgeData::Accessor type that + /// provides random read access to the internal tree. + Accessor getAccessor() { return Accessor(mTree); } + +private: + void operator=(const MeshToVoxelEdgeData&) {} + TreeType mTree; + class GenEdgeData; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + + +// Internal utility objects and implementation details + +namespace internal { + + +class PointTransform +{ +public: + PointTransform(const std::vector& pointsIn, std::vector& pointsOut, + const math::Transform& xform) + : mPointsIn(pointsIn) + , mPointsOut(&pointsOut) + , mXform(xform) + { + } + + void run(bool threaded = true) + { + if (threaded) { + tbb::parallel_for(tbb::blocked_range(0, mPointsOut->size()), *this); + } else { + (*this)(tbb::blocked_range(0, mPointsOut->size())); + } + } + + inline void operator()(const tbb::blocked_range& range) const + { + for (size_t n = range.begin(); n < range.end(); ++n) { + (*mPointsOut)[n] = mXform.worldToIndex(mPointsIn[n]); + } + } + +private: + const std::vector& mPointsIn; + std::vector * const mPointsOut; + const math::Transform& mXform; +}; + + +template +struct Tolerance +{ + static ValueType epsilon() { return ValueType(1e-7); } + static ValueType minNarrowBandWidth() { return ValueType(1.0 + 1e-6); } +}; + + +template +inline void +combine(FloatTreeT& lhsDist, IntTreeT& lhsIndex, FloatTreeT& rhsDist, IntTreeT& rhsIndex) +{ + typedef typename FloatTreeT::ValueType FloatValueT; + typename tree::ValueAccessor lhsDistAccessor(lhsDist); + typename tree::ValueAccessor lhsIndexAccessor(lhsIndex); + typename tree::ValueAccessor rhsIndexAccessor(rhsIndex); + typename FloatTreeT::LeafCIter iter = rhsDist.cbeginLeaf(); + + FloatValueT rhsValue; + Coord ijk; + + for ( ; iter; ++iter) { + typename FloatTreeT::LeafNodeType::ValueOnCIter it = iter->cbeginValueOn(); + + for ( ; it; ++it) { + + ijk = it.getCoord(); + rhsValue = it.getValue(); + FloatValueT& lhsValue = const_cast(lhsDistAccessor.getValue(ijk)); + + if (-rhsValue < std::abs(lhsValue)) { + lhsValue = rhsValue; + lhsIndexAccessor.setValue(ijk, rhsIndexAccessor.getValue(ijk)); + } + } + } +} + + +//////////////////////////////////////// + + +/// MeshVoxelizer +/// @brief TBB body object to voxelize a mesh of triangles and/or quads into a collection +/// of VDB grids, namely a squared distance grid, a closest primitive grid and an +/// intersecting voxels grid (masks the mesh intersecting voxels) +/// @note Only the leaf nodes that intersect the mesh are allocated, and only voxels in +/// a narrow band (of two to three voxels in proximity to the mesh's surface) are activated. +/// They are populated with distance values and primitive indices. +template +class MeshVoxelizer +{ +public: + typedef typename FloatTreeT::ValueType FloatValueT; + typedef typename FloatTreeT::LeafNodeType FloatLeafT; + typedef typename tree::ValueAccessor FloatAccessorT; + typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; + typedef typename IntTreeT::LeafNodeType IntLeafT; + typedef typename tree::ValueAccessor IntAccessorT; + typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; + typedef typename BoolTreeT::LeafNodeType BoolLeafT; + typedef typename tree::ValueAccessor BoolAccessorT; + + + MeshVoxelizer(const std::vector& pointList, + const std::vector& polygonList, InterruptT *interrupter = NULL); + + ~MeshVoxelizer() {} + + void run(bool threaded = true); + + MeshVoxelizer(MeshVoxelizer& rhs, tbb::split); + void operator() (const tbb::blocked_range &range); + void join(MeshVoxelizer& rhs); + + FloatTreeT& sqrDistTree() { return mSqrDistTree; } + IntTreeT& primIndexTree() { return mPrimIndexTree; } + BoolTreeT& intersectionTree() { return mIntersectionTree; } + +private: + // disallow copy by assignment + void operator=(const MeshVoxelizer&) {} + bool wasInterrupted() const { return mInterrupter && mInterrupter->wasInterrupted(); } + + bool evalVoxel(const Coord& ijk, const Int32 polyIdx); + + const std::vector& mPointList; + const std::vector& mPolygonList; + + FloatTreeT mSqrDistTree; + FloatAccessorT mSqrDistAccessor; + + IntTreeT mPrimIndexTree; + IntAccessorT mPrimIndexAccessor; + + BoolTreeT mIntersectionTree; + BoolAccessorT mIntersectionAccessor; + + // Used internally for acceleration + IntTreeT mLastPrimTree; + IntAccessorT mLastPrimAccessor; + + InterruptT *mInterrupter; + + + struct Primitive { Vec3d a, b, c, d; Int32 index; }; + + template + bool evalPrimitive(const Coord&, const Primitive&); + + template + void voxelize(const Primitive&); +}; + + +template +void +MeshVoxelizer::run(bool threaded) +{ + if (threaded) { + tbb::parallel_reduce(tbb::blocked_range(0, mPolygonList.size()), *this); + } else { + (*this)(tbb::blocked_range(0, mPolygonList.size())); + } +} + +template +MeshVoxelizer::MeshVoxelizer( + const std::vector& pointList, const std::vector& polygonList, + InterruptT *interrupter) + : mPointList(pointList) + , mPolygonList(polygonList) + , mSqrDistTree(std::numeric_limits::max()) + , mSqrDistAccessor(mSqrDistTree) + , mPrimIndexTree(Int32(util::INVALID_IDX)) + , mPrimIndexAccessor(mPrimIndexTree) + , mIntersectionTree(false) + , mIntersectionAccessor(mIntersectionTree) + , mLastPrimTree(Int32(util::INVALID_IDX)) + , mLastPrimAccessor(mLastPrimTree) + , mInterrupter(interrupter) +{ +} + +template +MeshVoxelizer::MeshVoxelizer( + MeshVoxelizer& rhs, tbb::split) + : mPointList(rhs.mPointList) + , mPolygonList(rhs.mPolygonList) + , mSqrDistTree(std::numeric_limits::max()) + , mSqrDistAccessor(mSqrDistTree) + , mPrimIndexTree(Int32(util::INVALID_IDX)) + , mPrimIndexAccessor(mPrimIndexTree) + , mIntersectionTree(false) + , mIntersectionAccessor(mIntersectionTree) + , mLastPrimTree(Int32(util::INVALID_IDX)) + , mLastPrimAccessor(mLastPrimTree) + , mInterrupter(rhs.mInterrupter) +{ +} + + +template +void +MeshVoxelizer::operator()(const tbb::blocked_range &range) +{ + Primitive prim; + + for (size_t n = range.begin(); n < range.end(); ++n) { + + if (mInterrupter && mInterrupter->wasInterrupted()) { + tbb::task::self().cancel_group_execution(); + break; + } + + const Vec4I& verts = mPolygonList[n]; + + prim.index = Int32(n); + prim.a = Vec3d(mPointList[verts[0]]); + prim.b = Vec3d(mPointList[verts[1]]); + prim.c = Vec3d(mPointList[verts[2]]); + + if (util::INVALID_IDX != verts[3]) { + prim.d = Vec3d(mPointList[verts[3]]); + voxelize(prim); + } else { + voxelize(prim); + } + } +} + + +template +template +void +MeshVoxelizer::voxelize(const Primitive& prim) +{ + std::deque coordList; + Coord ijk, nijk; + + ijk = util::nearestCoord(prim.a); + coordList.push_back(ijk); + + evalPrimitive(ijk, prim); + + while (!coordList.empty()) { + if(wasInterrupted()) break; + + ijk = coordList.back(); + coordList.pop_back(); + + mIntersectionAccessor.setActiveState(ijk, true); + + for (Int32 i = 0; i < 26; ++i) { + nijk = ijk + util::COORD_OFFSETS[i]; + if (prim.index != mLastPrimAccessor.getValue(nijk)) { + mLastPrimAccessor.setValue(nijk, prim.index); + if(evalPrimitive(nijk, prim)) coordList.push_back(nijk); + } + } + } +} + + +template +template +bool +MeshVoxelizer::evalPrimitive(const Coord& ijk, const Primitive& prim) +{ + Vec3d uvw, voxelCenter(ijk[0], ijk[1], ijk[2]); + + // Evaluate first triangle + FloatValueT dist = FloatValueT((voxelCenter - + closestPointOnTriangleToPoint(prim.a, prim.c, prim.b, voxelCenter, uvw)).lengthSqr()); + + if (IsQuad) { + // Split quad into a second triangle and calculate distance. + FloatValueT secondDist = FloatValueT((voxelCenter - + closestPointOnTriangleToPoint(prim.a, prim.d, prim.c, voxelCenter, uvw)).lengthSqr()); + + if (secondDist < dist) dist = secondDist; + } + + FloatValueT oldDist = std::abs(mSqrDistAccessor.getValue(ijk)); + + if (dist < oldDist) { + mSqrDistAccessor.setValue(ijk, -dist); + mPrimIndexAccessor.setValue(ijk, prim.index); + } else if (math::isExactlyEqual(dist, oldDist)) { + // makes reduction deterministic when different polygons + // produce the same distance value. + mPrimIndexAccessor.setValue(ijk, std::min(prim.index, mPrimIndexAccessor.getValue(ijk))); + } + + return (dist < 0.86602540378443861); +} + + +template +void +MeshVoxelizer::join(MeshVoxelizer& rhs) +{ + typedef typename FloatTreeT::RootNodeType FloatRootNodeT; + typedef typename FloatRootNodeT::NodeChainType FloatNodeChainT; + BOOST_STATIC_ASSERT(boost::mpl::size::value > 1); + typedef typename boost::mpl::at >::type FloatInternalNodeT; + + typedef typename IntTreeT::RootNodeType IntRootNodeT; + typedef typename IntRootNodeT::NodeChainType IntNodeChainT; + BOOST_STATIC_ASSERT(boost::mpl::size::value > 1); + typedef typename boost::mpl::at >::type IntInternalNodeT; + + const FloatValueT background = std::numeric_limits::max(); + + Coord ijk; + Index offset; + + rhs.mSqrDistTree.clearAllAccessors(); + rhs.mPrimIndexTree.clearAllAccessors(); + + typename FloatTreeT::LeafIter leafIt = rhs.mSqrDistTree.beginLeaf(); + for ( ; leafIt; ++leafIt) { + + ijk = leafIt->origin(); + FloatLeafT* lhsDistLeafPt = mSqrDistAccessor.probeLeaf(ijk); + + if (!lhsDistLeafPt) { + + // Steals leaf nodes through their parent, always the last internal-node + // stored in the ValueAccessor's node chain, avoiding the overhead of + // the root node. This is significantly faster than going through the + // tree or root node. + mSqrDistAccessor.addLeaf(rhs.mSqrDistAccessor.probeLeaf(ijk)); + FloatInternalNodeT* floatNode = + rhs.mSqrDistAccessor.template getNode(); + floatNode->template stealNode(ijk, background, false); + + mPrimIndexAccessor.addLeaf(rhs.mPrimIndexAccessor.probeLeaf(ijk)); + IntInternalNodeT* intNode = + rhs.mPrimIndexAccessor.template getNode(); + intNode->template stealNode(ijk, util::INVALID_IDX, false); + + } else { + + IntLeafT* lhsIdxLeafPt = mPrimIndexAccessor.probeLeaf(ijk); + IntLeafT* rhsIdxLeafPt = rhs.mPrimIndexAccessor.probeLeaf(ijk); + FloatValueT lhsValue, rhsValue; + + typename FloatLeafT::ValueOnCIter it = leafIt->cbeginValueOn(); + for ( ; it; ++it) { + + offset = it.pos(); + + lhsValue = std::abs(lhsDistLeafPt->getValue(offset)); + rhsValue = std::abs(it.getValue()); + + if (rhsValue < lhsValue) { + lhsDistLeafPt->setValueOn(offset, it.getValue()); + lhsIdxLeafPt->setValueOn(offset, rhsIdxLeafPt->getValue(offset)); + } else if (math::isExactlyEqual(rhsValue, lhsValue)) { + lhsIdxLeafPt->setValueOn(offset, + std::min(lhsIdxLeafPt->getValue(offset), rhsIdxLeafPt->getValue(offset))); + } + } + } + } + + mIntersectionTree.merge(rhs.mIntersectionTree); + + rhs.mSqrDistTree.clear(); + rhs.mPrimIndexTree.clear(); + rhs.mIntersectionTree.clear(); +} + + +//////////////////////////////////////// + + +// ContourTracer +/// @brief TBB body object that partitions a volume into 2D slices that can be processed +/// in parallel and marks the exterior contour of disjoint voxel sets in each slice +template +class ContourTracer +{ +public: + typedef typename FloatTreeT::ValueType FloatValueT; + typedef typename tree::ValueAccessor DistAccessorT; + typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; + typedef typename tree::ValueAccessor BoolAccessorT; + + ContourTracer(FloatTreeT&, const BoolTreeT&, InterruptT *interrupter = NULL); + ~ContourTracer() {} + + void run(bool threaded = true); + + ContourTracer(const ContourTracer& rhs); + void operator()(const tbb::blocked_range &range) const; + +private: + void operator=(const ContourTracer&) {} + + int sparseScan(int slice) const; + + FloatTreeT& mDistTree; + DistAccessorT mDistAccessor; + + const BoolTreeT& mIntersectionTree; + BoolAccessorT mIntersectionAccessor; + + CoordBBox mBBox; + + /// List of value-depth dependant step sizes. + std::vector mStepSize; + + InterruptT *mInterrupter; +}; + + +template +void +ContourTracer::run(bool threaded) +{ + if (threaded) { + tbb::parallel_for(tbb::blocked_range(mBBox.min()[0], mBBox.max()[0]+1), *this); + } else { + (*this)(tbb::blocked_range(mBBox.min()[0], mBBox.max()[0]+1)); + } +} + + +template +ContourTracer::ContourTracer( + FloatTreeT& distTree, const BoolTreeT& intersectionTree, InterruptT *interrupter) + : mDistTree(distTree) + , mDistAccessor(mDistTree) + , mIntersectionTree(intersectionTree) + , mIntersectionAccessor(mIntersectionTree) + , mBBox(CoordBBox()) + , mStepSize(0) + , mInterrupter(interrupter) +{ + // Build the step size table for different tree value depths. + std::vector dims; + mDistTree.getNodeLog2Dims(dims); + + mStepSize.resize(dims.size()+1, 1); + Index exponent = 0; + for (int idx = static_cast(dims.size()) - 1; idx > -1; --idx) { + exponent += dims[idx]; + mStepSize[idx] = 1 << exponent; + } + + mDistTree.evalLeafBoundingBox(mBBox); + + // Make sure that mBBox coincides with the min and max corners of the internal nodes. + const int tileDim = mStepSize[0]; + + for (size_t i = 0; i < 3; ++i) { + + int n; + double diff = std::abs(double(mBBox.min()[i])) / double(tileDim); + + if (mBBox.min()[i] <= tileDim) { + n = int(std::ceil(diff)); + mBBox.min()[i] = - n * tileDim; + } else { + n = int(std::floor(diff)); + mBBox.min()[i] = n * tileDim; + } + + n = int(std::ceil(std::abs(double(mBBox.max()[i] - mBBox.min()[i])) / double(tileDim))); + mBBox.max()[i] = mBBox.min()[i] + n * tileDim; + } +} + + +template +ContourTracer::ContourTracer( + const ContourTracer &rhs) + : mDistTree(rhs.mDistTree) + , mDistAccessor(mDistTree) + , mIntersectionTree(rhs.mIntersectionTree) + , mIntersectionAccessor(mIntersectionTree) + , mBBox(rhs.mBBox) + , mStepSize(rhs.mStepSize) + , mInterrupter(rhs.mInterrupter) +{ +} + + +template +void +ContourTracer::operator()(const tbb::blocked_range &range) const +{ + // Slice up the volume and trace contours. + int iStep = 1; + for (int n = range.begin(); n < range.end(); n += iStep) { + + if (mInterrupter && mInterrupter->wasInterrupted()) { + tbb::task::self().cancel_group_execution(); + break; + } + + iStep = sparseScan(n); + } +} + + +template +int +ContourTracer::sparseScan(int slice) const +{ + bool lastVoxelWasOut = true; + int last_k = mBBox.min()[2]; + + Coord ijk(slice, mBBox.min()[1], mBBox.min()[2]); + Coord step(mStepSize[mDistAccessor.getValueDepth(ijk) + 1]); + Coord n_ijk; + + for (ijk[1] = mBBox.min()[1]; ijk[1] <= mBBox.max()[1]; ijk[1] += step[1]) { // j + + if (mInterrupter && mInterrupter->wasInterrupted()) { + break; + } + + step[1] = mStepSize[mDistAccessor.getValueDepth(ijk) + 1]; + step[0] = std::min(step[0], step[1]); + + for (ijk[2] = mBBox.min()[2]; ijk[2] <= mBBox.max()[2]; ijk[2] += step[2]) { // k + + step[2] = mStepSize[mDistAccessor.getValueDepth(ijk) + 1]; + step[1] = std::min(step[1], step[2]); + step[0] = std::min(step[0], step[2]); + + // If the current voxel is set? + if (mDistAccessor.isValueOn(ijk)) { + + // Is this a boundary voxel? + if (mIntersectionAccessor.isValueOn(ijk)) { + + lastVoxelWasOut = false; + last_k = ijk[2]; + + } else if (lastVoxelWasOut) { + + FloatValueT& val = const_cast(mDistAccessor.getValue(ijk)); + val = -val; // flip sign + + } else { + + FloatValueT val; + for (Int32 n = 3; n < 6; n += 2) { + n_ijk = ijk + util::COORD_OFFSETS[n]; + + if (mDistAccessor.probeValue(n_ijk, val) && val > 0) { + lastVoxelWasOut = true; + break; + } + } + + if (lastVoxelWasOut) { + + FloatValueT& v = const_cast(mDistAccessor.getValue(ijk)); + v = -v; // flip sign + + const int tmp_k = ijk[2]; + + // backtrace + for (--ijk[2]; ijk[2] >= last_k; --ijk[2]) { + if (mIntersectionAccessor.isValueOn(ijk)) break; + FloatValueT& vb = + const_cast(mDistAccessor.getValue(ijk)); + if (vb < FloatValueT(0.0)) vb = -vb; // flip sign + } + + last_k = tmp_k; + ijk[2] = tmp_k; + + } else { + last_k = std::min(ijk[2], last_k); + } + + } + + } // end isValueOn check + } // end k + } // end j + return step[0]; +} + + +//////////////////////////////////////// + + +/// @brief TBB body object that that finds seed points for the parallel flood fill. +template +class SignMask +{ +public: + typedef typename FloatTreeT::ValueType FloatValueT; + typedef typename FloatTreeT::LeafNodeType FloatLeafT; + typedef tree::LeafManager FloatLeafManager; + typedef typename tree::ValueAccessor FloatAccessorT; + typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; + typedef typename BoolTreeT::LeafNodeType BoolLeafT; + typedef typename tree::ValueAccessor BoolAccessorT; + typedef typename tree::ValueAccessor BoolConstAccessorT; + + + SignMask(const FloatLeafManager&, const FloatTreeT&, const BoolTreeT&, + InterruptT *interrupter = NULL); + + ~SignMask() {} + + void run(bool threaded = true); + + SignMask(SignMask& rhs, tbb::split); + void operator() (const tbb::blocked_range &range); + void join(SignMask& rhs); + + BoolTreeT& signMaskTree() { return mSignMaskTree; } + +private: + // disallow copy by assignment + void operator=(const SignMask&) {} + bool wasInterrupted() const { return mInterrupter && mInterrupter->wasInterrupted(); } + + const FloatLeafManager& mDistLeafs; + const FloatTreeT& mDistTree; + const BoolTreeT& mIntersectionTree; + + BoolTreeT mSignMaskTree; + + InterruptT *mInterrupter; +}; // class SignMask + + +template +SignMask::SignMask( + const FloatLeafManager& distLeafs, const FloatTreeT& distTree, + const BoolTreeT& intersectionTree, InterruptT *interrupter) + : mDistLeafs(distLeafs) + , mDistTree(distTree) + , mIntersectionTree(intersectionTree) + , mSignMaskTree(false) + , mInterrupter(interrupter) +{ +} + + +template +SignMask::SignMask( + SignMask& rhs, tbb::split) + : mDistLeafs(rhs.mDistLeafs) + , mDistTree(rhs.mDistTree) + , mIntersectionTree(rhs.mIntersectionTree) + , mSignMaskTree(false) + , mInterrupter(rhs.mInterrupter) +{ +} + + +template +void +SignMask::run(bool threaded) +{ + if (threaded) tbb::parallel_reduce(mDistLeafs.getRange(), *this); + else (*this)(mDistLeafs.getRange()); +} + + +template +void +SignMask::operator()(const tbb::blocked_range &range) +{ + FloatAccessorT distAcc(mDistTree); + BoolConstAccessorT intersectionAcc(mIntersectionTree); + BoolAccessorT maskAcc(mSignMaskTree); + + FloatValueT value; + CoordBBox bbox; + Coord& maxCoord = bbox.max(); + Coord& minCoord = bbox.min(); + Coord ijk; + const int extent = BoolLeafT::DIM - 1; + + for (size_t n = range.begin(); n < range.end(); ++n) { + + const FloatLeafT& distLeaf = mDistLeafs.leaf(n); + + minCoord = distLeaf.origin(); + maxCoord[0] = minCoord[0] + extent; + maxCoord[1] = minCoord[1] + extent; + maxCoord[2] = minCoord[2] + extent; + + const BoolLeafT* intersectionLeaf = intersectionAcc.probeConstLeaf(minCoord); + + BoolLeafT* maskLeafPt = new BoolLeafT(minCoord, false); + BoolLeafT& maskLeaf = *maskLeafPt; + bool addLeaf = false; + + bbox.expand(-1); + + typename FloatLeafT::ValueOnCIter it = distLeaf.cbeginValueOn(); + for (; it; ++it) { + if (intersectionLeaf && intersectionLeaf->isValueOn(it.pos())) continue; + if (it.getValue() < FloatValueT(0.0)) { + ijk = it.getCoord(); + if (bbox.isInside(ijk)) { + for (size_t i = 0; i < 6; ++i) { + if (distLeaf.probeValue(ijk+util::COORD_OFFSETS[i], value) && value>0.0) { + maskLeaf.setValueOn(ijk); + addLeaf = true; + break; + } + } + } else { + for (size_t i = 0; i < 6; ++i) { + if (distAcc.probeValue(ijk+util::COORD_OFFSETS[i], value) && value>0.0) { + maskLeaf.setValueOn(ijk); + addLeaf = true; + break; + } + } + } + } + } + + if (addLeaf) maskAcc.addLeaf(maskLeafPt); + else delete maskLeafPt; + } +} + + +template +void +SignMask::join(SignMask& rhs) +{ + mSignMaskTree.merge(rhs.mSignMaskTree); +} + + +//////////////////////////////////////// + + +/// @brief TBB body object that performs a parallel flood fill +template +class PropagateSign +{ +public: + typedef typename FloatTreeT::ValueType FloatValueT; + typedef typename FloatTreeT::LeafNodeType FloatLeafT; + typedef typename tree::ValueAccessor FloatAccessorT; + typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; + typedef typename BoolTreeT::LeafNodeType BoolLeafT; + typedef tree::LeafManager BoolLeafManager; + typedef typename tree::ValueAccessor BoolAccessorT; + typedef typename tree::ValueAccessor BoolConstAccessorT; + + PropagateSign(BoolLeafManager&, FloatTreeT&, const BoolTreeT&, InterruptT *interrupter = NULL); + + ~PropagateSign() {} + + void run(bool threaded = true); + + PropagateSign(PropagateSign& rhs, tbb::split); + void operator() (const tbb::blocked_range &range); + void join(PropagateSign& rhs); + + BoolTreeT& signMaskTree() { return mSignMaskTree; } + +private: + // disallow copy by assignment + void operator=(const PropagateSign&); + bool wasInterrupted() const { return mInterrupter && mInterrupter->wasInterrupted(); } + + BoolLeafManager& mOldSignMaskLeafs; + FloatTreeT& mDistTree; + const BoolTreeT& mIntersectionTree; + + BoolTreeT mSignMaskTree; + InterruptT *mInterrupter; +}; + + +template +PropagateSign::PropagateSign(BoolLeafManager& signMaskLeafs, + FloatTreeT& distTree, const BoolTreeT& intersectionTree, InterruptT *interrupter) + : mOldSignMaskLeafs(signMaskLeafs) + , mDistTree(distTree) + , mIntersectionTree(intersectionTree) + , mSignMaskTree(false) + , mInterrupter(interrupter) +{ +} + + +template +PropagateSign::PropagateSign( + PropagateSign& rhs, tbb::split) + : mOldSignMaskLeafs(rhs.mOldSignMaskLeafs) + , mDistTree(rhs.mDistTree) + , mIntersectionTree(rhs.mIntersectionTree) + , mSignMaskTree(false) + , mInterrupter(rhs.mInterrupter) +{ +} + + +template +void +PropagateSign::run(bool threaded) +{ + if (threaded) tbb::parallel_reduce(mOldSignMaskLeafs.getRange(), *this); + else (*this)(mOldSignMaskLeafs.getRange()); +} + + +template +void +PropagateSign::operator()(const tbb::blocked_range &range) +{ + FloatAccessorT distAcc(mDistTree); + BoolConstAccessorT intersectionAcc(mIntersectionTree); + BoolAccessorT maskAcc(mSignMaskTree); + + std::deque coordList; + + FloatValueT value; + CoordBBox bbox; + Coord& maxCoord = bbox.max(); + Coord& minCoord = bbox.min(); + Coord ijk, nijk; + const int extent = BoolLeafT::DIM - 1; + + for (size_t n = range.begin(); n < range.end(); ++n) { + BoolLeafT& oldMaskLeaf = mOldSignMaskLeafs.leaf(n); + + minCoord = oldMaskLeaf.origin(); + maxCoord[0] = minCoord[0] + extent; + maxCoord[1] = minCoord[1] + extent; + maxCoord[2] = minCoord[2] + extent; + + FloatLeafT& distLeaf = *distAcc.probeLeaf(minCoord); + const BoolLeafT* intersectionLeaf = intersectionAcc.probeConstLeaf(minCoord); + + typename BoolLeafT::ValueOnCIter it = oldMaskLeaf.cbeginValueOn(); + for (; it; ++it) { + coordList.push_back(it.getCoord()); + + while (!coordList.empty()) { + + ijk = coordList.back(); + coordList.pop_back(); + + FloatValueT& dist = const_cast(distLeaf.getValue(ijk)); + if (dist < FloatValueT(0.0)) { + dist = -dist; // flip sign + + for (size_t i = 0; i < 6; ++i) { + nijk = ijk + util::COORD_OFFSETS[i]; + if (bbox.isInside(nijk)) { + if (intersectionLeaf && intersectionLeaf->isValueOn(nijk)) continue; + + if (distLeaf.probeValue(nijk, value) && value < 0.0) { + coordList.push_back(nijk); + } + + } else { + if(!intersectionAcc.isValueOn(nijk) && + distAcc.probeValue(nijk, value) && value < 0.0) { + maskAcc.setValueOn(nijk); + } + } + } + } + } + } + } +} + + +template +void +PropagateSign::join(PropagateSign& rhs) +{ + mSignMaskTree.merge(rhs.mSignMaskTree); +} + + +//////////////////////////////////////// + + +// IntersectingVoxelSign +/// @brief TBB body object that traversers all intersecting voxels (defined by the +/// intersectingVoxelsGrid) and potentially flips their sign, by comparing the "closest point" +/// directions of outside-marked and non-intersecting neighboring voxels +template +class IntersectingVoxelSign +{ +public: + typedef typename FloatTreeT::ValueType FloatValueT; + typedef typename tree::ValueAccessor FloatAccessorT; + typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; + typedef typename tree::ValueAccessor IntAccessorT; + typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; + typedef typename tree::ValueAccessor BoolAccessorT; + typedef tree::LeafManager BoolLeafManager; + + IntersectingVoxelSign( + const std::vector& pointList, + const std::vector& polygonList, + FloatTreeT& distTree, + IntTreeT& indexTree, + BoolTreeT& intersectionTree, + BoolLeafManager& leafs); + + ~IntersectingVoxelSign() {} + + void run(bool threaded = true); + + IntersectingVoxelSign(const IntersectingVoxelSign &rhs); + void operator()(const tbb::blocked_range&) const; + +private: + void operator=(const IntersectingVoxelSign&) {} + + Vec3d getClosestPoint(const Coord& ijk, const Vec4I& prim) const; + + std::vector const * const mPointList; + std::vector const * const mPolygonList; + + FloatTreeT& mDistTree; + IntTreeT& mIndexTree; + BoolTreeT& mIntersectionTree; + + BoolLeafManager& mLeafs; +}; + + +template +void +IntersectingVoxelSign::run(bool threaded) +{ + if (threaded) tbb::parallel_for(mLeafs.getRange(), *this); + else (*this)(mLeafs.getRange()); +} + + +template +IntersectingVoxelSign::IntersectingVoxelSign( + const std::vector& pointList, + const std::vector& polygonList, + FloatTreeT& distTree, + IntTreeT& indexTree, + BoolTreeT& intersectionTree, + BoolLeafManager& leafs) + : mPointList(&pointList) + , mPolygonList(&polygonList) + , mDistTree(distTree) + , mIndexTree(indexTree) + , mIntersectionTree(intersectionTree) + , mLeafs(leafs) +{ +} + + +template +IntersectingVoxelSign::IntersectingVoxelSign( + const IntersectingVoxelSign &rhs) + : mPointList(rhs.mPointList) + , mPolygonList(rhs.mPolygonList) + , mDistTree(rhs.mDistTree) + , mIndexTree(rhs.mIndexTree) + , mIntersectionTree(rhs.mIntersectionTree) + , mLeafs(rhs.mLeafs) +{ +} + + +template +void +IntersectingVoxelSign::operator()( + const tbb::blocked_range& range) const +{ + Coord ijk, nijk; + + FloatAccessorT distAcc(mDistTree); + BoolAccessorT maskAcc(mIntersectionTree); + IntAccessorT idxAcc(mIndexTree); + + FloatValueT tmpValue; + Vec3d cpt, center, dir1, dir2; + + typename BoolTreeT::LeafNodeType::ValueOnCIter iter; + for (size_t n = range.begin(); n < range.end(); ++n) { + iter = mLeafs.leaf(n).cbeginValueOn(); + for (; iter; ++iter) { + + ijk = iter.getCoord(); + + FloatValueT value = distAcc.getValue(ijk); + + if (!(value < FloatValueT(0.0))) continue; + + center = Vec3d(ijk[0], ijk[1], ijk[2]); + + for (Int32 i = 0; i < 26; ++i) { + nijk = ijk + util::COORD_OFFSETS[i]; + + if (!maskAcc.isValueOn(nijk) && distAcc.probeValue(nijk, tmpValue)) { + if (tmpValue < FloatValueT(0.0)) continue; + + const Vec4I& prim = (*mPolygonList)[idxAcc.getValue(nijk)]; + + cpt = getClosestPoint(nijk, prim); + + dir1 = center - cpt; + dir1.normalize(); + + dir2 = Vec3d(nijk[0], nijk[1], nijk[2]) - cpt; + dir2.normalize(); + + if (dir2.dot(dir1) > 0.0) { + distAcc.setValue(ijk, -value); + break; + } + } + } + } + } +} + + +template +Vec3d +IntersectingVoxelSign::getClosestPoint(const Coord& ijk, const Vec4I& prim) const +{ + Vec3d voxelCenter(ijk[0], ijk[1], ijk[2]); + + // Evaluate first triangle + const Vec3d a((*mPointList)[prim[0]]); + const Vec3d b((*mPointList)[prim[1]]); + const Vec3d c((*mPointList)[prim[2]]); + + Vec3d uvw; + Vec3d cpt1 = closestPointOnTriangleToPoint(a, c, b, voxelCenter, uvw); + + // Evaluate second triangle if quad. + if (prim[3] != util::INVALID_IDX) { + + Vec3d diff1 = voxelCenter - cpt1; + + const Vec3d d((*mPointList)[prim[3]]); + + Vec3d cpt2 = closestPointOnTriangleToPoint(a, d, c, voxelCenter, uvw); + Vec3d diff2 = voxelCenter - cpt2; + + if (diff2.lengthSqr() < diff1.lengthSqr()) { + return cpt2; + } + } + + return cpt1; +} + + +//////////////////////////////////////// + + +// IntersectingVoxelCleaner +/// @brief TBB body object that removes intersecting voxels that were set via +/// voxelization of self-intersecting parts of a mesh +template +class IntersectingVoxelCleaner +{ +public: + typedef typename FloatTreeT::ValueType FloatValueT; + typedef typename tree::ValueAccessor DistAccessorT; + typedef typename FloatTreeT::LeafNodeType DistLeafT; + typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; + typedef typename tree::ValueAccessor IntAccessorT; + typedef typename IntTreeT::LeafNodeType IntLeafT; + typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; + typedef typename tree::ValueAccessor BoolAccessorT; + typedef typename BoolTreeT::LeafNodeType BoolLeafT; + typedef tree::LeafManager BoolLeafManager; + + IntersectingVoxelCleaner(FloatTreeT& distTree, IntTreeT& indexTree, + BoolTreeT& intersectionTree, BoolLeafManager& leafs); + + ~IntersectingVoxelCleaner() {} + + void run(bool threaded = true); + + IntersectingVoxelCleaner(const IntersectingVoxelCleaner &rhs); + void operator()(const tbb::blocked_range&) const; + +private: + void operator=(const IntersectingVoxelCleaner&) {} + + FloatTreeT& mDistTree; + IntTreeT& mIndexTree; + BoolTreeT& mIntersectionTree; + BoolLeafManager& mLeafs; +}; + + +template +void +IntersectingVoxelCleaner::run(bool threaded) +{ + if (threaded) tbb::parallel_for(mLeafs.getRange(), *this); + else (*this)(mLeafs.getRange()); + + mIntersectionTree.pruneInactive(); +} + + +template +IntersectingVoxelCleaner::IntersectingVoxelCleaner( + FloatTreeT& distTree, + IntTreeT& indexTree, + BoolTreeT& intersectionTree, + BoolLeafManager& leafs) + : mDistTree(distTree) + , mIndexTree(indexTree) + , mIntersectionTree(intersectionTree) + , mLeafs(leafs) +{ +} + + +template +IntersectingVoxelCleaner::IntersectingVoxelCleaner( + const IntersectingVoxelCleaner& rhs) + : mDistTree(rhs.mDistTree) + , mIndexTree(rhs.mIndexTree) + , mIntersectionTree(rhs.mIntersectionTree) + , mLeafs(rhs.mLeafs) +{ +} + + +template +void +IntersectingVoxelCleaner::operator()( + const tbb::blocked_range& range) const +{ + Coord ijk, m_ijk; + bool turnOff; + FloatValueT value; + Index offset; + + typename BoolLeafT::ValueOnCIter iter; + + IntAccessorT indexAcc(mIndexTree); + DistAccessorT distAcc(mDistTree); + BoolAccessorT maskAcc(mIntersectionTree); + + for (size_t n = range.begin(); n < range.end(); ++n) { + + BoolLeafT& maskLeaf = mLeafs.leaf(n); + + ijk = maskLeaf.origin(); + + DistLeafT * distLeaf = distAcc.probeLeaf(ijk); + if (distLeaf) { + iter = maskLeaf.cbeginValueOn(); + for (; iter; ++iter) { + + offset = iter.pos(); + + if(distLeaf->getValue(offset) > 0.0) continue; + + ijk = iter.getCoord(); + turnOff = true; + for (Int32 m = 0; m < 26; ++m) { + m_ijk = ijk + util::COORD_OFFSETS[m]; + if (distAcc.probeValue(m_ijk, value)) { + if (value > 0.0) { + turnOff = false; + break; + } + } + } + + if (turnOff) { + maskLeaf.setValueOff(offset); + distLeaf->setValueOn(offset, -0.86602540378443861); + } + } + } + } +} + + +//////////////////////////////////////// + + +// ShellVoxelCleaner +/// @brief TBB body object that removes non-intersecting voxels that where set by rasterizing +/// self-intersecting parts of the mesh. +template +class ShellVoxelCleaner +{ +public: + typedef typename FloatTreeT::ValueType FloatValueT; + typedef typename tree::ValueAccessor DistAccessorT; + typedef typename FloatTreeT::LeafNodeType DistLeafT; + typedef tree::LeafManager DistArrayT; + typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; + typedef typename tree::ValueAccessor IntAccessorT; + typedef typename IntTreeT::LeafNodeType IntLeafT; + typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; + typedef typename tree::ValueAccessor BoolAccessorT; + typedef typename BoolTreeT::LeafNodeType BoolLeafT; + + ShellVoxelCleaner(FloatTreeT& distTree, DistArrayT& leafs, IntTreeT& indexTree, + BoolTreeT& intersectionTree); + + ~ShellVoxelCleaner() {} + + void run(bool threaded = true); + + ShellVoxelCleaner(const ShellVoxelCleaner &rhs); + void operator()(const tbb::blocked_range&) const; + +private: + void operator=(const ShellVoxelCleaner&) {} + + FloatTreeT& mDistTree; + DistArrayT& mLeafs; + IntTreeT& mIndexTree; + BoolTreeT& mIntersectionTree; +}; + + +template +void +ShellVoxelCleaner::run(bool threaded) +{ + if (threaded) tbb::parallel_for(mLeafs.getRange(), *this); + else (*this)(mLeafs.getRange()); + + mDistTree.pruneInactive(); + mIndexTree.pruneInactive(); +} + + +template +ShellVoxelCleaner::ShellVoxelCleaner( + FloatTreeT& distTree, + DistArrayT& leafs, + IntTreeT& indexTree, + BoolTreeT& intersectionTree) + : mDistTree(distTree) + , mLeafs(leafs) + , mIndexTree(indexTree) + , mIntersectionTree(intersectionTree) +{ +} + + +template +ShellVoxelCleaner::ShellVoxelCleaner( + const ShellVoxelCleaner &rhs) + : mDistTree(rhs.mDistTree) + , mLeafs(rhs.mLeafs) + , mIndexTree(rhs.mIndexTree) + , mIntersectionTree(rhs.mIntersectionTree) +{ +} + + +template +void +ShellVoxelCleaner::operator()( + const tbb::blocked_range& range) const +{ + Coord ijk, m_ijk; + bool turnOff; + FloatValueT value; + Index offset; + + typename DistLeafT::ValueOnCIter iter; + const FloatValueT distBG = mDistTree.background(); + const Int32 indexBG = mIntersectionTree.background(); + + IntAccessorT indexAcc(mIndexTree); + DistAccessorT distAcc(mDistTree); + BoolAccessorT maskAcc(mIntersectionTree); + + + for (size_t n = range.begin(); n < range.end(); ++n) { + + DistLeafT& distLeaf = mLeafs.leaf(n); + + ijk = distLeaf.origin(); + + const BoolLeafT* maskLeaf = maskAcc.probeConstLeaf(ijk); + IntLeafT& indexLeaf = *indexAcc.probeLeaf(ijk); + + iter = distLeaf.cbeginValueOn(); + for (; iter; ++iter) { + + value = iter.getValue(); + if(value > 0.0) continue; + + offset = iter.pos(); + if (maskLeaf && maskLeaf->isValueOn(offset)) continue; + + ijk = iter.getCoord(); + turnOff = true; + for (Int32 m = 0; m < 26; ++m) { + m_ijk = ijk + util::COORD_OFFSETS[m]; + if (maskAcc.isValueOn(m_ijk)) { + turnOff = false; + break; + } + } + + if (turnOff) { + distLeaf.setValueOff(offset, distBG); + indexLeaf.setValueOff(offset, indexBG); + } + } + } +} + + +//////////////////////////////////////// + + +template +struct CopyActiveVoxelsOp +{ + typedef typename tree::ValueAccessor AccessorT; + + CopyActiveVoxelsOp(TreeType& tree) : mAcc(tree) { } + + template + void operator()(LeafNodeType &leaf, size_t) const + { + LeafNodeType* rhsLeaf = const_cast(mAcc.probeLeaf(leaf.origin())); + typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); + for (; iter; ++iter) { + rhsLeaf->setValueOnly(iter.pos(), iter.getValue()); + } + } + +private: + AccessorT mAcc; +}; + + +// ExpandNB +/// @brief TBB body object to expand the level set narrow band +/// @note The interior and exterior widths should be in world space units and squared. +template +class ExpandNB +{ +public: + typedef typename FloatTreeT::ValueType FloatValueT; + typedef typename FloatTreeT::LeafNodeType FloatLeafT; + typedef typename tree::ValueAccessor FloatAccessorT; + typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; + typedef typename IntTreeT::LeafNodeType IntLeafT; + typedef typename tree::ValueAccessor IntAccessorT; + typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; + typedef typename BoolTreeT::LeafNodeType BoolLeafT; + typedef tree::LeafManager BoolLeafManager; + typedef typename tree::ValueAccessor BoolAccessorT; + + ExpandNB(BoolLeafManager& leafs, + FloatTreeT& distTree, IntTreeT& indexTree, BoolTreeT& maskTree, + FloatValueT exteriorBandWidth, FloatValueT interiorBandWidth, FloatValueT voxelSize, + const std::vector& pointList, const std::vector& polygonList); + + void run(bool threaded = true); + + void operator()(const tbb::blocked_range&); + void join(ExpandNB&); + ExpandNB(const ExpandNB&, tbb::split); + ~ExpandNB() {} + +private: + void operator=(const ExpandNB&) {} + + double evalVoxelDist(const Coord&, FloatAccessorT&, IntAccessorT&, + BoolAccessorT&, std::vector&, Int32&) const; + + double evalVoxelDist(const Coord&, FloatLeafT&, IntLeafT&, + BoolLeafT&, std::vector&, Int32&) const; + + double closestPrimDist(const Coord&, std::vector&, Int32&) const; + + BoolLeafManager& mMaskLeafs; + + FloatTreeT& mDistTree; + IntTreeT& mIndexTree; + BoolTreeT& mMaskTree; + + const FloatValueT mExteriorBandWidth, mInteriorBandWidth, mVoxelSize; + const std::vector& mPointList; + const std::vector& mPolygonList; + + FloatTreeT mNewDistTree; + IntTreeT mNewIndexTree; + BoolTreeT mNewMaskTree; +}; + + +template +ExpandNB::ExpandNB( + BoolLeafManager& leafs, + FloatTreeT& distTree, + IntTreeT& indexTree, + BoolTreeT& maskTree, + FloatValueT exteriorBandWidth, + FloatValueT interiorBandWidth, + FloatValueT voxelSize, + const std::vector& pointList, + const std::vector& polygonList) + : mMaskLeafs(leafs) + , mDistTree(distTree) + , mIndexTree(indexTree) + , mMaskTree(maskTree) + , mExteriorBandWidth(exteriorBandWidth) + , mInteriorBandWidth(interiorBandWidth) + , mVoxelSize(voxelSize) + , mPointList(pointList) + , mPolygonList(polygonList) + , mNewDistTree(std::numeric_limits::max()) + , mNewIndexTree(Int32(util::INVALID_IDX)) + , mNewMaskTree(false) +{ +} + + +template +ExpandNB::ExpandNB(const ExpandNB& rhs, tbb::split) + : mMaskLeafs(rhs.mMaskLeafs) + , mDistTree(rhs.mDistTree) + , mIndexTree(rhs.mIndexTree) + , mMaskTree(rhs.mMaskTree) + , mExteriorBandWidth(rhs.mExteriorBandWidth) + , mInteriorBandWidth(rhs.mInteriorBandWidth) + , mVoxelSize(rhs.mVoxelSize) + , mPointList(rhs.mPointList) + , mPolygonList(rhs.mPolygonList) + , mNewDistTree(std::numeric_limits::max()) + , mNewIndexTree(Int32(util::INVALID_IDX)) + , mNewMaskTree(false) +{ +} + + +template +void +ExpandNB::run(bool threaded) +{ + if (threaded) tbb::parallel_reduce(mMaskLeafs.getRange(), *this); + else (*this)(mMaskLeafs.getRange()); + + // Copy only the active voxels (tree::merge does branch stealing + // which also moves indicative values). + mDistTree.topologyUnion(mNewDistTree); + tree::LeafManager leafs(mNewDistTree); + leafs.foreach(CopyActiveVoxelsOp(mDistTree)); + + mIndexTree.merge(mNewIndexTree); + + mMaskTree.clear(); + mMaskTree.merge(mNewMaskTree); + +} + + +template +void +ExpandNB::operator()(const tbb::blocked_range& range) +{ + Coord ijk; + Int32 closestPrim = 0; + Index pos = 0; + FloatValueT distance; + bool inside; + + FloatAccessorT newDistAcc(mNewDistTree); + IntAccessorT newIndexAcc(mNewIndexTree); + BoolAccessorT newMaskAcc(mNewMaskTree); + + FloatAccessorT distAcc(mDistTree); + IntAccessorT indexAcc(mIndexTree); + BoolAccessorT maskAcc(mMaskTree); + + CoordBBox bbox; + std::vector primitives(18); + + for (size_t n = range.begin(); n < range.end(); ++n) { + + BoolLeafT& maskLeaf = mMaskLeafs.leaf(n); + + if (maskLeaf.isEmpty()) continue; + + ijk = maskLeaf.origin(); + + FloatLeafT* distLeafPt = distAcc.probeLeaf(ijk); + + if (!distLeafPt) { + distLeafPt = new FloatLeafT(ijk, distAcc.getValue(ijk)); + newDistAcc.addLeaf(distLeafPt); + } + + IntLeafT* indexLeafPt = indexAcc.probeLeaf(ijk); + if (!indexLeafPt) indexLeafPt = newIndexAcc.touchLeaf(ijk); + + bbox = maskLeaf.getNodeBoundingBox(); + bbox.expand(-1); + + typename BoolLeafT::ValueOnIter iter = maskLeaf.beginValueOn(); + for (; iter; ++iter) { + + ijk = iter.getCoord(); + + if (bbox.isInside(ijk)) { + distance = evalVoxelDist(ijk, *distLeafPt, *indexLeafPt, maskLeaf, + primitives, closestPrim); + } else { + distance = evalVoxelDist(ijk, distAcc, indexAcc, maskAcc, + primitives, closestPrim); + } + + pos = iter.pos(); + + inside = distLeafPt->getValue(pos) < FloatValueT(0.0); + + if (!inside && distance < mExteriorBandWidth) { + distLeafPt->setValueOn(pos, distance); + indexLeafPt->setValueOn(pos, closestPrim); + } else if (inside && distance < mInteriorBandWidth) { + distLeafPt->setValueOn(pos, -distance); + indexLeafPt->setValueOn(pos, closestPrim); + } else { + continue; + } + + for (Int32 i = 0; i < 6; ++i) { + newMaskAcc.setValueOn(ijk + util::COORD_OFFSETS[i]); + } + } + } +} + + +template +double +ExpandNB::evalVoxelDist( + const Coord& ijk, + FloatAccessorT& distAcc, + IntAccessorT& indexAcc, + BoolAccessorT& maskAcc, + std::vector& prims, + Int32& closestPrim) const +{ + FloatValueT tmpDist, minDist = std::numeric_limits::max(); + prims.clear(); + + // Collect primitive indices from active neighbors and min distance. + Coord n_ijk; + for (Int32 n = 0; n < 18; ++n) { + n_ijk = ijk + util::COORD_OFFSETS[n]; + if (!maskAcc.isValueOn(n_ijk) && distAcc.probeValue(n_ijk, tmpDist)) { + prims.push_back(indexAcc.getValue(n_ijk)); + tmpDist = std::abs(tmpDist); + if (tmpDist < minDist) minDist = tmpDist; + } + } + + // Calc. this voxels distance to the closest primitive. + tmpDist = FloatValueT(closestPrimDist(ijk, prims, closestPrim)); + + // Forces the gradient to be monotonic for non-manifold + // polygonal models with self-intersections. + return tmpDist > minDist ? tmpDist : minDist + mVoxelSize; +} + + +// Leaf specialized version. +template +double +ExpandNB::evalVoxelDist( + const Coord& ijk, + FloatLeafT& distLeaf, + IntLeafT& indexLeaf, + BoolLeafT& maskLeaf, + std::vector& prims, + Int32& closestPrim) const +{ + FloatValueT tmpDist, minDist = std::numeric_limits::max(); + prims.clear(); + + Index pos; + for (Int32 n = 0; n < 18; ++n) { + pos = FloatLeafT::coordToOffset(ijk + util::COORD_OFFSETS[n]); + if (!maskLeaf.isValueOn(pos) && distLeaf.probeValue(pos, tmpDist)) { + prims.push_back(indexLeaf.getValue(pos)); + tmpDist = std::abs(tmpDist); + if (tmpDist < minDist) minDist = tmpDist; + } + } + + tmpDist = FloatValueT(closestPrimDist(ijk, prims, closestPrim)); + return tmpDist > minDist ? tmpDist : minDist + mVoxelSize; +} + + +template +double +ExpandNB::closestPrimDist(const Coord& ijk, + std::vector& prims, Int32& closestPrim) const +{ + std::sort(prims.begin(), prims.end()); + + Int32 lastPrim = -1; + Vec3d uvw, voxelCenter(ijk[0], ijk[1], ijk[2]); + double primDist, tmpDist, dist = std::numeric_limits::max(); + + for (size_t n = 0, N = prims.size(); n < N; ++n) { + if (prims[n] == lastPrim) continue; + + lastPrim = prims[n]; + + const Vec4I& verts = mPolygonList[lastPrim]; + + // Evaluate first triangle + const Vec3d a(mPointList[verts[0]]); + const Vec3d b(mPointList[verts[1]]); + const Vec3d c(mPointList[verts[2]]); + + primDist = (voxelCenter - + closestPointOnTriangleToPoint(a, c, b, voxelCenter, uvw)).lengthSqr(); + + // Split-up quad into a second triangle and calac distance. + if (util::INVALID_IDX != verts[3]) { + const Vec3d d(mPointList[verts[3]]); + + tmpDist = (voxelCenter - + closestPointOnTriangleToPoint(a, d, c, voxelCenter, uvw)).lengthSqr(); + + if (tmpDist < primDist) primDist = tmpDist; + } + + if (primDist < dist) { + dist = primDist; + closestPrim = lastPrim; + } + } + + return std::sqrt(dist) * double(mVoxelSize); +} + + +template +void +ExpandNB::join(ExpandNB& rhs) +{ + mNewDistTree.merge(rhs.mNewDistTree); + mNewIndexTree.merge(rhs.mNewIndexTree); + mNewMaskTree.merge(rhs.mNewMaskTree); +} + + +//////////////////////////////////////// + + +template +struct SqrtAndScaleOp +{ + SqrtAndScaleOp(ValueType voxelSize, bool unsignedDist = false) + : mVoxelSize(voxelSize) + , mUnsigned(unsignedDist) + { + } + + template + void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const + { + ValueType w[2]; + w[0] = mVoxelSize; + w[1] = -mVoxelSize; + + typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); + for (; iter; ++iter) { + ValueType& val = const_cast(iter.getValue()); + val = w[!mUnsigned && int(val < ValueType(0.0))] * std::sqrt(std::abs(val)); + } + } + +private: + ValueType mVoxelSize; + const bool mUnsigned; +}; + + +template +struct VoxelSignOp +{ + VoxelSignOp(ValueType exBandWidth, ValueType inBandWidth) + : mExBandWidth(exBandWidth) + , mInBandWidth(inBandWidth) + { + } + + template + void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const + { + ValueType bgValues[2]; + bgValues[0] = mExBandWidth; + bgValues[1] = -mInBandWidth; + + typename LeafNodeType::ValueOffIter iter = leaf.beginValueOff(); + + for (; iter; ++iter) { + ValueType& val = const_cast(iter.getValue()); + val = bgValues[int(val < ValueType(0.0))]; + } + } + +private: + ValueType mExBandWidth, mInBandWidth; +}; + + +template +struct TrimOp +{ + TrimOp(ValueType exBandWidth, ValueType inBandWidth) + : mExBandWidth(exBandWidth) + , mInBandWidth(inBandWidth) + { + } + + template + void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const + { + typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); + + for (; iter; ++iter) { + ValueType& val = const_cast(iter.getValue()); + const bool inside = val < ValueType(0.0); + + if (inside && !(val > -mInBandWidth)) { + val = -mInBandWidth; + iter.setValueOff(); + } else if (!inside && !(val < mExBandWidth)) { + val = mExBandWidth; + iter.setValueOff(); + } + } + } + +private: + ValueType mExBandWidth, mInBandWidth; +}; + + +template +struct OffsetOp +{ + OffsetOp(ValueType offset): mOffset(offset) {} + + void resetOffset(ValueType offset) { mOffset = offset; } + + template + void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const + { + typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); + for (; iter; ++iter) { + ValueType& val = const_cast(iter.getValue()); + val += mOffset; + } + } + +private: + ValueType mOffset; +}; + + +template +struct RenormOp +{ + typedef math::BIAS_SCHEME Scheme; + typedef typename Scheme::template ISStencil::StencilType Stencil; + typedef tree::LeafManager LeafManagerType; + typedef typename LeafManagerType::BufferType BufferType; + + RenormOp(GridType& grid, LeafManagerType& leafs, ValueType voxelSize, ValueType cfl = 1.0) + : mGrid(grid) + , mLeafs(leafs) + , mVoxelSize(voxelSize) + , mCFL(cfl) + { + } + + void resetCFL(ValueType cfl) { mCFL = cfl; } + + template + void operator()(LeafNodeType &leaf, size_t leafIndex) const + { + const ValueType dt = mCFL * mVoxelSize, one(1.0), invDx = one / mVoxelSize; + Stencil stencil(mGrid); + BufferType& buffer = mLeafs.getBuffer(leafIndex, 1); + + typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); + for (; iter; ++iter) { + stencil.moveTo(iter); + + const ValueType normSqGradPhi = + math::ISGradientNormSqrd::result(stencil); + + const ValueType phi0 = iter.getValue(); + const ValueType diff = math::Sqrt(normSqGradPhi) * invDx - one; + const ValueType S = phi0 / (math::Sqrt(math::Pow2(phi0) + normSqGradPhi)); + + buffer.setValue(iter.pos(), phi0 - dt * S * diff); + } + } + +private: + GridType& mGrid; + LeafManagerType& mLeafs; + ValueType mVoxelSize, mCFL; +}; + + +template +struct MinOp +{ + typedef tree::LeafManager LeafManagerType; + typedef typename LeafManagerType::BufferType BufferType; + + MinOp(LeafManagerType& leafs): mLeafs(leafs) {} + + template + void operator()(LeafNodeType &leaf, size_t leafIndex) const + { + BufferType& buffer = mLeafs.getBuffer(leafIndex, 1); + typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); + + for (; iter; ++iter) { + ValueType& val = const_cast(iter.getValue()); + val = std::min(val, buffer.getValue(iter.pos())); + } + } + +private: + LeafManagerType& mLeafs; +}; + + +template +struct MergeBufferOp +{ + typedef tree::LeafManager LeafManagerType; + typedef typename LeafManagerType::BufferType BufferType; + + MergeBufferOp(LeafManagerType& leafs, size_t bufferIndex = 1) + : mLeafs(leafs) + , mBufferIndex(bufferIndex) + { + } + + template + void operator()(LeafNodeType &leaf, size_t leafIndex) const + { + BufferType& buffer = mLeafs.getBuffer(leafIndex, mBufferIndex); + typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); + Index offset; + + for (; iter; ++iter) { + offset = iter.pos(); + leaf.setValueOnly(offset, buffer.getValue(offset)); + } + } + +private: + LeafManagerType& mLeafs; + const size_t mBufferIndex; +}; + + +template +struct LeafTopologyDiffOp +{ + typedef typename tree::ValueAccessor AccessorT; + typedef typename TreeType::LeafNodeType LeafNodeT; + + LeafTopologyDiffOp(TreeType& tree) : mAcc(tree) { } + + template + void operator()(LeafNodeType &leaf, size_t) const + { + const LeafNodeT* rhsLeaf = mAcc.probeConstLeaf(leaf.origin()); + if (rhsLeaf) leaf.topologyDifference(*rhsLeaf, false); + } + +private: + AccessorT mAcc; +}; + + +template +struct SDFPrune +{ + SDFPrune(const ValueType& out, const ValueType& in) + : outside(out) + , inside(in) + { + } + + template + bool operator()(ChildType& child) + { + child.pruneOp(*this); + if (!child.isInactive()) return false; + value = math::isNegative(child.getFirstValue()) ? inside : outside; + return true; + } + + static const bool state = false; + const ValueType outside, inside; + ValueType value; +}; + + +} // internal namespace + + +//////////////////////////////////////// + + +// MeshToVolume + +template +MeshToVolume::MeshToVolume( + openvdb::math::Transform::Ptr& transform, int conversionFlags, + InterruptT *interrupter, int signSweeps) + : mTransform(transform) + , mConversionFlags(conversionFlags) + , mSignSweeps(signSweeps) + , mInterrupter(interrupter) +{ + clear(); + mSignSweeps = std::min(mSignSweeps, 1); +} + + +template +void +MeshToVolume::clear() +{ + mDistGrid = FloatGridT::create(std::numeric_limits::max()); + mIndexGrid = IntGridT::create(Int32(util::INVALID_IDX)); + mIntersectingVoxelsGrid = BoolGridT::create(false); +} + + +template +inline void +MeshToVolume::convertToLevelSet( + const std::vector& pointList, const std::vector& polygonList, + FloatValueT exBandWidth, FloatValueT inBandWidth) +{ + // The narrow band width is exclusive, the shortest valid distance has to be > 1 voxel + exBandWidth = std::max(internal::Tolerance::minNarrowBandWidth(), exBandWidth); + inBandWidth = std::max(internal::Tolerance::minNarrowBandWidth(), inBandWidth); + const FloatValueT vs = mTransform->voxelSize()[0]; + doConvert(pointList, polygonList, vs * exBandWidth, vs * inBandWidth); + mDistGrid->setGridClass(GRID_LEVEL_SET); +} + + +template +inline void +MeshToVolume::convertToUnsignedDistanceField( + const std::vector& pointList, const std::vector& polygonList, + FloatValueT exBandWidth) +{ + // The narrow band width is exclusive, the shortest valid distance has to be > 1 voxel + exBandWidth = std::max(internal::Tolerance::minNarrowBandWidth(), exBandWidth); + const FloatValueT vs = mTransform->voxelSize()[0]; + doConvert(pointList, polygonList, vs * exBandWidth, 0.0, true); + mDistGrid->setGridClass(GRID_UNKNOWN); +} + + +template +void +MeshToVolume::doConvert( + const std::vector& pointList, const std::vector& polygonList, + FloatValueT exBandWidth, FloatValueT inBandWidth, bool unsignedDistField) +{ + mDistGrid->setTransform(mTransform); + mIndexGrid->setTransform(mTransform); + const bool rawData = OUTPUT_RAW_DATA & mConversionFlags; + + // The progress estimates given to the interrupter are based on the + // observed average time for each stage and therefore not alway + // accurate. The goal is to give some progression feedback to the user. + + if (wasInterrupted(1)) return; + + // Voxelize mesh + { + internal::MeshVoxelizer + voxelizer(pointList, polygonList, mInterrupter); + + voxelizer.run(); + + if (wasInterrupted(18)) return; + + mDistGrid->tree().merge(voxelizer.sqrDistTree()); + mIndexGrid->tree().merge(voxelizer.primIndexTree()); + mIntersectingVoxelsGrid->tree().merge(voxelizer.intersectionTree()); + } + + if (!unsignedDistField) { + // Determine the inside/outside state for the narrow band of voxels. + { + // Slices up the volume and label the exterior contour of each slice in parallel. + internal::ContourTracer trace( + mDistGrid->tree(), mIntersectingVoxelsGrid->tree(), mInterrupter); + for (int i = 0; i < mSignSweeps; ++i) { + + if (wasInterrupted(19)) return; + + trace.run(); + + if (wasInterrupted(24)) return; + + // Propagate sign information between the slices. + BoolTreeT signMaskTree(false); + { + tree::LeafManager leafs(mDistGrid->tree()); + internal::SignMask signMaskOp(leafs, + mDistGrid->tree(), mIntersectingVoxelsGrid->tree(), mInterrupter); + signMaskOp.run(); + signMaskTree.merge(signMaskOp.signMaskTree()); + } + + if (wasInterrupted(25)) return; + + while (true) { + tree::LeafManager leafs(signMaskTree); + if(leafs.leafCount() == 0) break; + + internal::PropagateSign sign(leafs, + mDistGrid->tree(), mIntersectingVoxelsGrid->tree(), mInterrupter); + + sign.run(); + + signMaskTree.clear(); + signMaskTree.merge(sign.signMaskTree()); + } + } + } + + + if (wasInterrupted(28)) return; + { + tree::LeafManager leafs(mIntersectingVoxelsGrid->tree()); + + // Determine the sign of the mesh intersecting voxels. + internal::IntersectingVoxelSign sign(pointList, polygonList, + mDistGrid->tree(), mIndexGrid->tree(), mIntersectingVoxelsGrid->tree(), leafs); + + sign.run(); + + if (wasInterrupted(34)) return; + + // Remove mesh intersecting voxels that where set by rasterizing + // self-intersecting portions of the mesh. + internal::IntersectingVoxelCleaner cleaner(mDistGrid->tree(), + mIndexGrid->tree(), mIntersectingVoxelsGrid->tree(), leafs); + cleaner.run(); + } + + // Remove shell voxels that where set by rasterizing + // self-intersecting portions of the mesh. + { + tree::LeafManager leafs(mDistGrid->tree()); + + internal::ShellVoxelCleaner cleaner(mDistGrid->tree(), + leafs, mIndexGrid->tree(), mIntersectingVoxelsGrid->tree()); + + cleaner.run(); + } + + if (wasInterrupted(38)) return; + + } else { // if unsigned dist. field + inBandWidth = FloatValueT(0.0); + } + + if (mDistGrid->activeVoxelCount() == 0) return; + + mIntersectingVoxelsGrid->clear(); + const FloatValueT voxelSize(mTransform->voxelSize()[0]); + + { // Transform values (world space scaling etc.) + tree::LeafManager leafs(mDistGrid->tree()); + leafs.foreach(internal::SqrtAndScaleOp(voxelSize, unsignedDistField)); + } + + if (wasInterrupted(40)) return; + + if (!unsignedDistField) { // Propagate sign information to inactive values. + mDistGrid->tree().root().setBackground(exBandWidth, /*updateChildNodes=*/false); + mDistGrid->tree().signedFloodFill(exBandWidth, -inBandWidth); + } + + if (wasInterrupted(46)) return; + + // Narrow-band dilation + const FloatValueT minWidth = voxelSize * 2.0; + if (inBandWidth > minWidth || exBandWidth > minWidth) { + + // Create the initial voxel mask. + BoolTreeT maskTree(false); + maskTree.topologyUnion(mDistGrid->tree()); + + if (wasInterrupted(48)) return; + + internal::LeafTopologyDiffOp diffOp(mDistGrid->tree()); + openvdb::tools::dilateVoxels(maskTree); + + unsigned maxIterations = std::numeric_limits::max(); + float progress = 48, step = 0.0; + // progress estimation.. + double estimated = + 2.0 * std::ceil((std::max(inBandWidth, exBandWidth) - minWidth) / voxelSize); + if (estimated < double(maxIterations)) { + maxIterations = unsigned(estimated); + step = 42.0 / float(maxIterations); + } + + unsigned count = 0; + while (true) { + + if (wasInterrupted(int(progress))) return; + + tree::LeafManager leafs(maskTree); + + if (leafs.leafCount() == 0) break; + + leafs.foreach(diffOp); + + internal::ExpandNB expand( + leafs, mDistGrid->tree(), mIndexGrid->tree(), maskTree, + exBandWidth, inBandWidth, voxelSize, pointList, polygonList); + + expand.run(); + + if ((++count) >= maxIterations) break; + progress += step; + } + } + + if (!bool(GENERATE_PRIM_INDEX_GRID & mConversionFlags)) mIndexGrid->clear(); + + if (wasInterrupted(80)) return; + + // Renormalize distances to smooth out bumps caused by self-intersecting + // and overlapping portions of the mesh and renormalize the level set. + if (!unsignedDistField && !rawData) { + + mDistGrid->tree().pruneLevelSet(); + tree::LeafManager leafs(mDistGrid->tree(), 1); + + const FloatValueT offset = 0.8 * voxelSize; + if (wasInterrupted(82)) return; + + internal::OffsetOp offsetOp(-offset); + + leafs.foreach(offsetOp); + + if (wasInterrupted(84)) return; + + leafs.foreach(internal::RenormOp(*mDistGrid, leafs, voxelSize)); + + leafs.foreach(internal::MinOp(leafs)); + + if (wasInterrupted(95)) return; + + offsetOp.resetOffset(offset - internal::Tolerance::epsilon()); + leafs.foreach(offsetOp); + } + + if (wasInterrupted(98)) return; + + const FloatValueT minTrimWidth = voxelSize * 4.0; + if (inBandWidth < minTrimWidth || exBandWidth < minTrimWidth) { + + // If the narrow band was not expanded, we might need to trim off + // some of the active voxels in order to respect the narrow band limits. + // (The mesh voxelization step generates some extra 'shell' voxels) + + tree::LeafManager leafs(mDistGrid->tree()); + leafs.foreach(internal::TrimOp( + exBandWidth, unsignedDistField ? exBandWidth : inBandWidth)); + + internal::SDFPrune sdfPrune(exBandWidth, -inBandWidth); + mDistGrid->tree().pruneOp(sdfPrune); + } +} + + +//////////////////////////////////////// + + +/// @internal This overload is enabled only for grids with a scalar, floating-point ValueType. +template +inline typename boost::enable_if, +typename GridType::Ptr>::type +doMeshConversion( + const openvdb::math::Transform& xform, + const std::vector& points, + const std::vector& triangles, + const std::vector& quads, + float exBandWidth, + float inBandWidth, + bool unsignedDistanceField = false) +{ + std::vector indexSpacePoints(points.size()); + + { // Copy and transform (required for MeshToVolume) points to grid space. + internal::PointTransform ptnXForm(points, indexSpacePoints, xform); + ptnXForm.run(); + } + + // Copy primitives + std::vector primitives(triangles.size() + quads.size()); + + for (size_t n = 0, N = triangles.size(); n < N; ++n) { + Vec4I& prim = primitives[n]; + const Vec3I& triangle = triangles[n]; + prim[0] = triangle[0]; + prim[1] = triangle[1]; + prim[2] = triangle[2]; + prim[3] = util::INVALID_IDX; + } + + for (size_t n = 0, N = quads.size(); n < N; ++n) { + primitives[n + triangles.size()] = quads[n]; + } + + typename GridType::ValueType exWidth(exBandWidth); + typename GridType::ValueType inWidth(inBandWidth); + + + math::Transform::Ptr transform = xform.copy(); + MeshToVolume vol(transform); + + if (!unsignedDistanceField) { + vol.convertToLevelSet(indexSpacePoints, primitives, exWidth, inWidth); + } else { + vol.convertToUnsignedDistanceField(indexSpacePoints, primitives, exWidth); + } + + return vol.distGridPtr(); +} + + +/// @internal This overload is enabled only for grids that do not have a scalar, +/// floating-point ValueType. +template +inline typename boost::disable_if, +typename GridType::Ptr>::type +doMeshConversion( + const math::Transform& /*xform*/, + const std::vector& /*points*/, + const std::vector& /*triangles*/, + const std::vector& /*quads*/, + float /*exBandWidth*/, + float /*inBandWidth*/, + bool unsignedDistanceField = false) +{ + OPENVDB_THROW(TypeError, + "mesh to volume conversion is supported only for scalar, floating-point grids"); +} + + +//////////////////////////////////////// + + +template +inline typename GridType::Ptr +meshToLevelSet( + const openvdb::math::Transform& xform, + const std::vector& points, + const std::vector& triangles, + float halfWidth) +{ + std::vector quads(0); + return doMeshConversion(xform, points, triangles, quads, + halfWidth, halfWidth); +} + + +template +inline typename GridType::Ptr +meshToLevelSet( + const openvdb::math::Transform& xform, + const std::vector& points, + const std::vector& quads, + float halfWidth) +{ + std::vector triangles(0); + return doMeshConversion(xform, points, triangles, quads, + halfWidth, halfWidth); +} + + +template +inline typename GridType::Ptr +meshToLevelSet( + const openvdb::math::Transform& xform, + const std::vector& points, + const std::vector& triangles, + const std::vector& quads, + float halfWidth) +{ + return doMeshConversion(xform, points, triangles, quads, + halfWidth, halfWidth); +} + + +template +inline typename GridType::Ptr +meshToSignedDistanceField( + const openvdb::math::Transform& xform, + const std::vector& points, + const std::vector& triangles, + const std::vector& quads, + float exBandWidth, + float inBandWidth) +{ + return doMeshConversion(xform, points, triangles, + quads, exBandWidth, inBandWidth); +} + + +template +inline typename GridType::Ptr +meshToUnsignedDistanceField( + const openvdb::math::Transform& xform, + const std::vector& points, + const std::vector& triangles, + const std::vector& quads, + float bandWidth) +{ + return doMeshConversion(xform, points, triangles, quads, + bandWidth, bandWidth, true); +} + + +//////////////////////////////////////////////////////////////////////////////// + + +// Required by several of the tree nodes +inline std::ostream& +operator<<(std::ostream& ostr, const MeshToVoxelEdgeData::EdgeData& rhs) +{ + ostr << "{[ " << rhs.mXPrim << ", " << rhs.mXDist << "]"; + ostr << " [ " << rhs.mYPrim << ", " << rhs.mYDist << "]"; + ostr << " [ " << rhs.mZPrim << ", " << rhs.mZDist << "]}"; + return ostr; +} + +// Required by math::Abs +inline MeshToVoxelEdgeData::EdgeData +Abs(const MeshToVoxelEdgeData::EdgeData& x) +{ + return x; +} + + +//////////////////////////////////////// + + +class MeshToVoxelEdgeData::GenEdgeData +{ +public: + + GenEdgeData( + const std::vector& pointList, + const std::vector& polygonList); + + void run(bool threaded = true); + + GenEdgeData(GenEdgeData& rhs, tbb::split); + inline void operator() (const tbb::blocked_range &range); + inline void join(GenEdgeData& rhs); + + inline TreeType& tree() { return mTree; } + +private: + void operator=(const GenEdgeData&) {} + + struct Primitive { Vec3d a, b, c, d; Int32 index; }; + + template + inline void voxelize(const Primitive&); + + template + inline bool evalPrimitive(const Coord&, const Primitive&); + + inline bool rayTriangleIntersection( const Vec3d& origin, const Vec3d& dir, + const Vec3d& a, const Vec3d& b, const Vec3d& c, double& t); + + + TreeType mTree; + Accessor mAccessor; + + const std::vector& mPointList; + const std::vector& mPolygonList; + + // Used internally for acceleration + typedef TreeType::ValueConverter::Type IntTreeT; + IntTreeT mLastPrimTree; + tree::ValueAccessor mLastPrimAccessor; +}; // class MeshToVoxelEdgeData::GenEdgeData + + +inline +MeshToVoxelEdgeData::GenEdgeData::GenEdgeData( + const std::vector& pointList, + const std::vector& polygonList) + : mTree(EdgeData()) + , mAccessor(mTree) + , mPointList(pointList) + , mPolygonList(polygonList) + , mLastPrimTree(Int32(util::INVALID_IDX)) + , mLastPrimAccessor(mLastPrimTree) +{ +} + + +inline +MeshToVoxelEdgeData::GenEdgeData::GenEdgeData(GenEdgeData& rhs, tbb::split) + : mTree(EdgeData()) + , mAccessor(mTree) + , mPointList(rhs.mPointList) + , mPolygonList(rhs.mPolygonList) + , mLastPrimTree(Int32(util::INVALID_IDX)) + , mLastPrimAccessor(mLastPrimTree) +{ +} + + +inline void +MeshToVoxelEdgeData::GenEdgeData::run(bool threaded) +{ + if (threaded) { + tbb::parallel_reduce(tbb::blocked_range(0, mPolygonList.size()), *this); + } else { + (*this)(tbb::blocked_range(0, mPolygonList.size())); + } +} + + +inline void +MeshToVoxelEdgeData::GenEdgeData::join(GenEdgeData& rhs) +{ + typedef TreeType::RootNodeType RootNodeType; + typedef RootNodeType::NodeChainType NodeChainType; + BOOST_STATIC_ASSERT(boost::mpl::size::value > 1); + typedef boost::mpl::at >::type InternalNodeType; + + Coord ijk; + Index offset; + + rhs.mTree.clearAllAccessors(); + + TreeType::LeafIter leafIt = rhs.mTree.beginLeaf(); + for ( ; leafIt; ++leafIt) { + ijk = leafIt->origin(); + + TreeType::LeafNodeType* lhsLeafPt = mTree.probeLeaf(ijk); + + if (!lhsLeafPt) { + + mAccessor.addLeaf(rhs.mAccessor.probeLeaf(ijk)); + InternalNodeType* node = rhs.mAccessor.getNode(); + node->stealNode(ijk, EdgeData(), false); + rhs.mAccessor.clear(); + + } else { + + TreeType::LeafNodeType::ValueOnCIter it = leafIt->cbeginValueOn(); + for ( ; it; ++it) { + + offset = it.pos(); + const EdgeData& rhsValue = it.getValue(); + + if (!lhsLeafPt->isValueOn(offset)) { + lhsLeafPt->setValueOn(offset, rhsValue); + } else { + + EdgeData& lhsValue = const_cast(lhsLeafPt->getValue(offset)); + + if (rhsValue.mXDist < lhsValue.mXDist) { + lhsValue.mXDist = rhsValue.mXDist; + lhsValue.mXPrim = rhsValue.mXPrim; + } + + if (rhsValue.mYDist < lhsValue.mYDist) { + lhsValue.mYDist = rhsValue.mYDist; + lhsValue.mYPrim = rhsValue.mYPrim; + } + + if (rhsValue.mZDist < lhsValue.mZDist) { + lhsValue.mZDist = rhsValue.mZDist; + lhsValue.mZPrim = rhsValue.mZPrim; + } + + } + } // end value iteration + } + } // end leaf iteration +} + + +inline void +MeshToVoxelEdgeData::GenEdgeData::operator()(const tbb::blocked_range &range) +{ + Primitive prim; + + for (size_t n = range.begin(); n < range.end(); ++n) { + + const Vec4I& verts = mPolygonList[n]; + + prim.index = Int32(n); + prim.a = Vec3d(mPointList[verts[0]]); + prim.b = Vec3d(mPointList[verts[1]]); + prim.c = Vec3d(mPointList[verts[2]]); + + if (util::INVALID_IDX != verts[3]) { + prim.d = Vec3d(mPointList[verts[3]]); + voxelize(prim); + } else { + voxelize(prim); + } + } +} + + +template +inline void +MeshToVoxelEdgeData::GenEdgeData::voxelize(const Primitive& prim) +{ + std::deque coordList; + Coord ijk, nijk; + + ijk = util::nearestCoord(prim.a); + coordList.push_back(ijk); + + evalPrimitive(ijk, prim); + + while (!coordList.empty()) { + + ijk = coordList.back(); + coordList.pop_back(); + + for (Int32 i = 0; i < 26; ++i) { + nijk = ijk + util::COORD_OFFSETS[i]; + + if (prim.index != mLastPrimAccessor.getValue(nijk)) { + mLastPrimAccessor.setValue(nijk, prim.index); + if(evalPrimitive(nijk, prim)) coordList.push_back(nijk); + } + } + } +} + + +template +inline bool +MeshToVoxelEdgeData::GenEdgeData::evalPrimitive(const Coord& ijk, const Primitive& prim) +{ + Vec3d uvw, org(ijk[0], ijk[1], ijk[2]); + bool intersecting = false; + double t; + + EdgeData edgeData; + mAccessor.probeValue(ijk, edgeData); + + // Evaluate first triangle + double dist = (org - + closestPointOnTriangleToPoint(prim.a, prim.c, prim.b, org, uvw)).lengthSqr(); + + if (rayTriangleIntersection(org, Vec3d(1.0, 0.0, 0.0), prim.a, prim.c, prim.b, t)) { + if (t < edgeData.mXDist) { + edgeData.mXDist = t; + edgeData.mXPrim = prim.index; + intersecting = true; + } + } + + if (rayTriangleIntersection(org, Vec3d(0.0, 1.0, 0.0), prim.a, prim.c, prim.b, t)) { + if (t < edgeData.mYDist) { + edgeData.mYDist = t; + edgeData.mYPrim = prim.index; + intersecting = true; + } + } + + if (rayTriangleIntersection(org, Vec3d(0.0, 0.0, 1.0), prim.a, prim.c, prim.b, t)) { + if (t < edgeData.mZDist) { + edgeData.mZDist = t; + edgeData.mZPrim = prim.index; + intersecting = true; + } + } + + if (IsQuad) { + // Split quad into a second triangle and calculate distance. + double secondDist = (org - + closestPointOnTriangleToPoint(prim.a, prim.d, prim.c, org, uvw)).lengthSqr(); + + if (secondDist < dist) dist = secondDist; + + if (rayTriangleIntersection(org, Vec3d(1.0, 0.0, 0.0), prim.a, prim.d, prim.c, t)) { + if (t < edgeData.mXDist) { + edgeData.mXDist = t; + edgeData.mXPrim = prim.index; + intersecting = true; + } + } + + if (rayTriangleIntersection(org, Vec3d(0.0, 1.0, 0.0), prim.a, prim.d, prim.c, t)) { + if (t < edgeData.mYDist) { + edgeData.mYDist = t; + edgeData.mYPrim = prim.index; + intersecting = true; + } + } + + if (rayTriangleIntersection(org, Vec3d(0.0, 0.0, 1.0), prim.a, prim.d, prim.c, t)) { + if (t < edgeData.mZDist) { + edgeData.mZDist = t; + edgeData.mZPrim = prim.index; + intersecting = true; + } + } + } + + if (intersecting) mAccessor.setValue(ijk, edgeData); + + return (dist < 0.86602540378443861); +} + + +inline bool +MeshToVoxelEdgeData::GenEdgeData::rayTriangleIntersection( + const Vec3d& origin, const Vec3d& dir, + const Vec3d& a, const Vec3d& b, const Vec3d& c, + double& t) +{ + // Check if ray is parallel with triangle + + Vec3d e1 = b - a; + Vec3d e2 = c - a; + Vec3d s1 = dir.cross(e2); + + double divisor = s1.dot(e1); + if (!(std::abs(divisor) > 0.0)) return false; + + // Compute barycentric coordinates + + double inv_divisor = 1.0 / divisor; + Vec3d d = origin - a; + double b1 = d.dot(s1) * inv_divisor; + + if (b1 < 0.0 || b1 > 1.0) return false; + + Vec3d s2 = d.cross(e1); + double b2 = dir.dot(s2) * inv_divisor; + + if (b2 < 0.0 || (b1 + b2) > 1.0) return false; + + // Compute distance to intersection point + + t = e2.dot(s2) * inv_divisor; + return (t < 0.0) ? false : true; +} + + +//////////////////////////////////////// + + +inline +MeshToVoxelEdgeData::MeshToVoxelEdgeData() + : mTree(EdgeData()) +{ +} + + +inline void +MeshToVoxelEdgeData::convert( + const std::vector& pointList, + const std::vector& polygonList) +{ + GenEdgeData converter(pointList, polygonList); + converter.run(); + + mTree.clear(); + mTree.merge(converter.tree()); +} + + +inline void +MeshToVoxelEdgeData::getEdgeData( + Accessor& acc, + const Coord& ijk, + std::vector& points, + std::vector& primitives) +{ + EdgeData data; + Vec3d point; + + Coord coord = ijk; + + if (acc.probeValue(coord, data)) { + + if (data.mXPrim != util::INVALID_IDX) { + point[0] = double(coord[0]) + data.mXDist; + point[1] = double(coord[1]); + point[2] = double(coord[2]); + + points.push_back(point); + primitives.push_back(data.mXPrim); + } + + if (data.mYPrim != util::INVALID_IDX) { + point[0] = double(coord[0]); + point[1] = double(coord[1]) + data.mYDist; + point[2] = double(coord[2]); + + points.push_back(point); + primitives.push_back(data.mYPrim); + } + + if (data.mZPrim != util::INVALID_IDX) { + point[0] = double(coord[0]); + point[1] = double(coord[1]); + point[2] = double(coord[2]) + data.mZDist; + + points.push_back(point); + primitives.push_back(data.mZPrim); + } + + } + + coord[0] += 1; + + if (acc.probeValue(coord, data)) { + + if (data.mYPrim != util::INVALID_IDX) { + point[0] = double(coord[0]); + point[1] = double(coord[1]) + data.mYDist; + point[2] = double(coord[2]); + + points.push_back(point); + primitives.push_back(data.mYPrim); + } + + if (data.mZPrim != util::INVALID_IDX) { + point[0] = double(coord[0]); + point[1] = double(coord[1]); + point[2] = double(coord[2]) + data.mZDist; + + points.push_back(point); + primitives.push_back(data.mZPrim); + } + } + + coord[2] += 1; + + if (acc.probeValue(coord, data)) { + if (data.mYPrim != util::INVALID_IDX) { + point[0] = double(coord[0]); + point[1] = double(coord[1]) + data.mYDist; + point[2] = double(coord[2]); + + points.push_back(point); + primitives.push_back(data.mYPrim); + } + } + + coord[0] -= 1; + + if (acc.probeValue(coord, data)) { + + if (data.mXPrim != util::INVALID_IDX) { + point[0] = double(coord[0]) + data.mXDist; + point[1] = double(coord[1]); + point[2] = double(coord[2]); + + points.push_back(point); + primitives.push_back(data.mXPrim); + } + + if (data.mYPrim != util::INVALID_IDX) { + point[0] = double(coord[0]); + point[1] = double(coord[1]) + data.mYDist; + point[2] = double(coord[2]); + + points.push_back(point); + primitives.push_back(data.mYPrim); + } + } + + + coord[1] += 1; + + if (acc.probeValue(coord, data)) { + + if (data.mXPrim != util::INVALID_IDX) { + point[0] = double(coord[0]) + data.mXDist; + point[1] = double(coord[1]); + point[2] = double(coord[2]); + + points.push_back(point); + primitives.push_back(data.mXPrim); + } + } + + coord[2] -= 1; + + if (acc.probeValue(coord, data)) { + + if (data.mXPrim != util::INVALID_IDX) { + point[0] = double(coord[0]) + data.mXDist; + point[1] = double(coord[1]); + point[2] = double(coord[2]); + + points.push_back(point); + primitives.push_back(data.mXPrim); + } + + if (data.mZPrim != util::INVALID_IDX) { + point[0] = double(coord[0]); + point[1] = double(coord[1]); + point[2] = double(coord[2]) + data.mZDist; + + points.push_back(point); + primitives.push_back(data.mZPrim); + } + } + + coord[0] += 1; + + if (acc.probeValue(coord, data)) { + + if (data.mZPrim != util::INVALID_IDX) { + point[0] = double(coord[0]); + point[1] = double(coord[1]); + point[2] = double(coord[2]) + data.mZDist; + + points.push_back(point); + primitives.push_back(data.mZPrim); + } + } +} + + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/Morphology.h b/openvdb_2_3_0_library/openvdb/tools/Morphology.h new file mode 100755 index 0000000..90696a5 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/Morphology.h @@ -0,0 +1,504 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_TOOLS_MORPHOLOGY_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_MORPHOLOGY_HAS_BEEN_INCLUDED + +#include +#include +#include // for isApproxEqual() +#include +#include +#include +#include "ValueTransformer.h" // for foreach() + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +//@{ +/// Topologically dilate all leaf-level active voxels in the given tree, +/// i.e., expand the set of active voxels by @a count voxels in the +x, -x, +/// +y, -y, +z and -z directions, but don't change the values of any voxels, +/// only their active states. +/// @todo Currently operates only on leaf voxels; need to extend to tiles. +template OPENVDB_STATIC_SPECIALIZATION +inline void dilateVoxels(TreeType& tree, int count=1); + +template OPENVDB_STATIC_SPECIALIZATION +inline void dilateVoxels(tree::LeafManager& manager, int count = 1); +//@} + +//@{ +/// Topologically erode all leaf-level active voxels in the given tree, +/// i.e., shrink the set of active voxels by @a count voxels in the +x, -x, +/// +y, -y, +z and -z directions, but don't change the values of any voxels, +/// only their active states. +/// @todo Currently operates only on leaf voxels; need to extend to tiles. +template OPENVDB_STATIC_SPECIALIZATION +inline void erodeVoxels(TreeType& tree, int count=1); + +template OPENVDB_STATIC_SPECIALIZATION +inline void erodeVoxels(tree::LeafManager& manager, int count = 1); +//@} + + +/// @brief Mark as active any inactive tiles or voxels in the given grid or tree +/// whose values are equal to @a value (optionally to within the given @a tolerance). +template +inline void activate( + GridOrTree&, + const typename GridOrTree::ValueType& value, + const typename GridOrTree::ValueType& tolerance = zeroVal() +); + + +/// @brief Mark as inactive any active tiles or voxels in the given grid or tree +/// whose values are equal to @a value (optionally to within the given @a tolerance). +template +inline void deactivate( + GridOrTree&, + const typename GridOrTree::ValueType& value, + const typename GridOrTree::ValueType& tolerance = zeroVal() +); + + +//////////////////////////////////////// + + +/// Mapping from a Log2Dim to a data type of size 2^Log2Dim bits +template struct DimToWord {}; +template<> struct DimToWord<3> { typedef uint8_t Type; }; +template<> struct DimToWord<4> { typedef uint16_t Type; }; +template<> struct DimToWord<5> { typedef uint32_t Type; }; +template<> struct DimToWord<6> { typedef uint64_t Type; }; + + +//////////////////////////////////////// + + +template +class Morphology +{ +public: + typedef tree::LeafManager ManagerType; + + Morphology(TreeType& tree): + mOwnsManager(true), mManager(new ManagerType(tree)), mAcc(tree), mSteps(1) {} + Morphology(ManagerType* mgr): + mOwnsManager(false), mManager(mgr), mAcc(mgr->tree()), mSteps(1) {} + virtual ~Morphology() { if (mOwnsManager) delete mManager; } + void dilateVoxels(); + void dilateVoxels(int count) { for (int i=0; idilateVoxels(); } + void erodeVoxels(int count = 1) { mSteps = count; this->doErosion(); } + +private: + void doErosion(); + + typedef typename TreeType::LeafNodeType LeafType; + typedef typename LeafType::NodeMaskType MaskType; + typedef tree::ValueAccessor AccessorType; + + const bool mOwnsManager; + ManagerType* mManager; + AccessorType mAcc; + int mSteps; + + static const int LEAF_DIM = LeafType::DIM; + static const int LEAF_LOG2DIM = LeafType::LOG2DIM; + typedef typename DimToWord::Type Word; + + struct Neighbor { + LeafType* leaf;//null if a tile + bool init;//true if initialization is required + bool isOn;//true if an active tile + Neighbor() : leaf(NULL), init(true) {} + inline void clear() { init = true; } + template + void scatter(AccessorType& acc, const Coord &xyz, int indx, Word oldWord) + { + if (init) { + init = false; + Coord orig = xyz.offsetBy(DX*LEAF_DIM, DY*LEAF_DIM, DZ*LEAF_DIM); + leaf = acc.probeLeaf(orig); + if (leaf==NULL && !acc.isValueOn(orig)) leaf = acc.touchLeaf(orig); + } + static const int N = (LEAF_DIM -1 )*(DY + DX*LEAF_DIM); + if (leaf) leaf->getValueMask().template getWord(indx-N) |= oldWord; + } + template + Word gather(AccessorType& acc, const Coord &xyz, int indx) + { + if (init) { + init = false; + Coord orig = xyz.offsetBy(DX*LEAF_DIM, DY*LEAF_DIM, DZ*LEAF_DIM); + leaf = acc.probeLeaf(orig); + isOn = leaf ? false : acc.isValueOn(orig); + } + static const int N = (LEAF_DIM -1 )*(DY + DX*LEAF_DIM); + return leaf ? leaf->getValueMask().template getWord(indx-N) + : isOn ? ~Word(0) : Word(0); + } + };// Neighbor + + + struct ErodeVoxelsOp { + ErodeVoxelsOp(std::vector& masks, ManagerType& manager) + : mSavedMasks(masks) , mManager(manager) {} + + void runParallel() { tbb::parallel_for(mManager.getRange(), *this); } + void operator()(const tbb::blocked_range& range) const; + + private: + std::vector& mSavedMasks; + ManagerType& mManager; + };// ErodeVoxelsOp + + + struct MaskManager { + MaskManager(std::vector& masks, ManagerType& manager) + : mMasks(masks) , mManager(manager), mSaveMasks(true) {} + + void save() { mSaveMasks = true; tbb::parallel_for(mManager.getRange(), *this); } + void update() { mSaveMasks = false; tbb::parallel_for(mManager.getRange(), *this); } + void operator()(const tbb::blocked_range& range) const + { + if (mSaveMasks) { + for (size_t i = range.begin(); i < range.end(); ++i) { + mMasks[i] = mManager.leaf(i).getValueMask(); + } + } else { + for (size_t i = range.begin(); i < range.end(); ++i) { + mManager.leaf(i).setValueMask(mMasks[i]); + } + } + } + + private: + std::vector& mMasks; + ManagerType& mManager; + bool mSaveMasks; + };// MaskManager +}; + + +template +void +Morphology::dilateVoxels() +{ + /// @todo Currently operates only on leaf voxels; need to extend to tiles. + const int leafCount = mManager->leafCount(); + + // Save the value masks of all leaf nodes. + std::vector savedMasks(leafCount); + MaskManager masks(savedMasks, *mManager); + masks.save(); + + Neighbor NN[6]; + Coord origin; + for (int leafIdx = 0; leafIdx < leafCount; ++leafIdx) { + const MaskType& oldMask = savedMasks[leafIdx];//original bit-mask of current leaf node + LeafType& leaf = mManager->leaf(leafIdx);//current leaf node + leaf.getOrigin(origin);// origin of the current leaf node. + for (int x = 0; x < LEAF_DIM; ++x ) { + for (int y = 0, n = (x << LEAF_LOG2DIM); y < LEAF_DIM; ++y, ++n) { + // Extract the portion of the original mask that corresponds to a row in z. + const Word oldWord = oldMask.template getWord(n); + if (oldWord == 0) continue; // no active voxels + + // dilate current leaf or neighbor in negative x-direction + if (x > 0) { + leaf.getValueMask().template getWord(n-LEAF_DIM) |= oldWord; + } else { + NN[0].template scatter<-1, 0, 0>(mAcc, origin, n, oldWord); + } + // dilate current leaf or neighbor in positive x-direction + if (x < LEAF_DIM - 1) { + leaf.getValueMask().template getWord(n+LEAF_DIM) |= oldWord; + } else { + NN[1].template scatter< 1, 0, 0>(mAcc, origin, n, oldWord); + } + // dilate current leaf or neighbor in negative y-direction + if (y > 0) { + leaf.getValueMask().template getWord(n-1) |= oldWord; + } else { + NN[2].template scatter< 0,-1, 0>(mAcc, origin, n, oldWord); + } + // dilate current leaf or neighbor in positive y-direction + if (y < LEAF_DIM - 1) { + leaf.getValueMask().template getWord(n+1) |= oldWord; + } else { + NN[3].template scatter< 0, 1, 0>(mAcc, origin, n, oldWord); + } + // Dilate the current leaf node in the z direction by ORing its mask + // with itself shifted first left and then right by one bit. + leaf.getValueMask().template getWord(n) |= (oldWord >> 1) | (oldWord << 1); + // dilate neighbor in negative z-direction + if (Word w = oldWord<<(LEAF_DIM-1)) { + NN[4].template scatter< 0, 0,-1>(mAcc, origin, n, w); + } + // dilate neighbot in positive z-direction + if (Word w = oldWord>>(LEAF_DIM-1)) { + NN[5].template scatter< 0, 0, 1>(mAcc, origin, n, w); + } + }// loop over y + }//loop over x + for (int i=0; i<6; ++i) NN[i].clear(); + }//loop over leafs + + mManager->rebuildLeafArray(); +} + + +template +void +Morphology::ErodeVoxelsOp::operator()(const tbb::blocked_range& range) const +{ + AccessorType acc(mManager.tree()); + Neighbor NN[6]; + Coord origin; + for (size_t leafIdx = range.begin(); leafIdx < range.end(); ++leafIdx) { + LeafType& leaf = mManager.leaf(leafIdx);//current leaf node + if (leaf.isEmpty()) continue; + MaskType& newMask = mSavedMasks[leafIdx];//original bit-mask of current leaf node + leaf.getOrigin(origin);// origin of the current leaf node. + for (int x = 0; x < LEAF_DIM; ++x ) { + for (int y = 0, n = (x << LEAF_LOG2DIM); y < LEAF_DIM; ++y, ++n) { + // Extract the portion of the original mask that corresponds to a row in z. + Word& w = newMask.template getWord(n); + if (w == 0) continue; // no active voxels + + // Erode in two z directions (this is first since it uses the original w) + w &= (w<<1 | (NN[4].template gather<0,0,-1>(acc, origin, n)>>(LEAF_DIM-1))) & + (w>>1 | (NN[5].template gather<0,0, 1>(acc, origin, n)<<(LEAF_DIM-1))); + + // dilate current leaf or neighbor in negative x-direction + w &= (x == 0) ? NN[0].template gather<-1, 0, 0>(acc, origin, n) : + leaf.getValueMask().template getWord(n-LEAF_DIM); + + // dilate current leaf or neighbor in positive x-direction + w &= (x == LEAF_DIM-1) ? NN[1].template gather< 1, 0, 0>(acc, origin, n) : + leaf.getValueMask().template getWord(n+LEAF_DIM); + + // dilate current leaf or neighbor in negative y-direction + w &= (y == 0) ? NN[2].template gather< 0,-1, 0>(acc, origin, n) : + leaf.getValueMask().template getWord(n-1); + + // dilate current leaf or neighbor in positive y-direction + w &= (y == LEAF_DIM-1) ? NN[3].template gather< 0, 1, 0>(acc, origin, n) : + leaf.getValueMask().template getWord(n+1); + }// loop over y + }//loop over x + for (int i=0; i<6; ++i) NN[i].clear(); + }//loop over leafs +} + + +template +void +Morphology::doErosion() +{ + /// @todo Currently operates only on leaf voxels; need to extend to tiles. + const int leafCount = mManager->leafCount(); + + // Save the value masks of all leaf nodes. + std::vector savedMasks(leafCount); + MaskManager masks(savedMasks, *mManager); + masks.save(); + + ErodeVoxelsOp erode(savedMasks, *mManager); + for (int i = 0; i < mSteps; ++i) { + erode.runParallel(); + masks.update(); + } + + mManager->tree().pruneLevelSet(); +} + + +//////////////////////////////////////// + + +template +OPENVDB_STATIC_SPECIALIZATION inline void +dilateVoxels(tree::LeafManager& manager, int count) +{ + if (count > 0 ) { + Morphology m(&manager); + m.dilateVoxels(count); + } +} + +template +OPENVDB_STATIC_SPECIALIZATION inline void +dilateVoxels(TreeType& tree, int count) +{ + if (count > 0 ) { + Morphology m(tree); + m.dilateVoxels(count); + } +} + +template +OPENVDB_STATIC_SPECIALIZATION inline void +erodeVoxels(tree::LeafManager& manager, int count) +{ + if (count > 0 ) { + Morphology m(&manager); + m.erodeVoxels(count); + } +} + +template +OPENVDB_STATIC_SPECIALIZATION inline void +erodeVoxels(TreeType& tree, int count) +{ + if (count > 0 ) { + Morphology m(tree); + m.erodeVoxels(count); + } +} + + +//////////////////////////////////////// + + +namespace activation { + +template +class ActivationOp +{ +public: + typedef typename TreeType::ValueType ValueT; + + ActivationOp(bool state, const ValueT& val, const ValueT& tol) + : mActivate(state) + , mValue(val) + , mTolerance(tol) + {} + + void operator()(const typename TreeType::ValueOnIter& it) const + { + if (math::isApproxEqual(*it, mValue, mTolerance)) { + it.setValueOff(); + } + } + + void operator()(const typename TreeType::ValueOffIter& it) const + { + if (math::isApproxEqual(*it, mValue, mTolerance)) { + it.setActiveState(/*on=*/true); + } + } + + void operator()(const typename TreeType::LeafIter& lit) const + { + typedef typename TreeType::LeafNodeType LeafT; + LeafT& leaf = *lit; + if (mActivate) { + for (typename LeafT::ValueOffIter it = leaf.beginValueOff(); it; ++it) { + if (math::isApproxEqual(*it, mValue, mTolerance)) { + leaf.setValueOn(it.pos()); + } + } + } else { + for (typename LeafT::ValueOnIter it = leaf.beginValueOn(); it; ++it) { + if (math::isApproxEqual(*it, mValue, mTolerance)) { + leaf.setValueOff(it.pos()); + } + } + } + } + +private: + bool mActivate; + const ValueT mValue, mTolerance; +}; // class ActivationOp + +} // namespace activation + + +template +inline void +activate(GridOrTree& gridOrTree, const typename GridOrTree::ValueType& value, + const typename GridOrTree::ValueType& tolerance) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeType; + + TreeType& tree = Adapter::tree(gridOrTree); + + activation::ActivationOp op(/*activate=*/true, value, tolerance); + + // Process all leaf nodes in parallel. + foreach(tree.beginLeaf(), op); + + // Process all other inactive values serially (because changing active states + // is not thread-safe unless no two threads modify the same node). + typename TreeType::ValueOffIter it = tree.beginValueOff(); + it.setMaxDepth(tree.treeDepth() - 2); + foreach(it, op, /*threaded=*/false); +} + + +template +inline void +deactivate(GridOrTree& gridOrTree, const typename GridOrTree::ValueType& value, + const typename GridOrTree::ValueType& tolerance) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType TreeType; + + TreeType& tree = Adapter::tree(gridOrTree); + + activation::ActivationOp op(/*activate=*/false, value, tolerance); + + // Process all leaf nodes in parallel. + foreach(tree.beginLeaf(), op); + + // Process all other active values serially (because changing active states + // is not thread-safe unless no two threads modify the same node). + typename TreeType::ValueOnIter it = tree.beginValueOn(); + it.setMaxDepth(tree.treeDepth() - 2); + foreach(it, op, /*threaded=*/false); +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_MORPHOLOGY_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/ParticlesToLevelSet.h b/openvdb_2_3_0_library/openvdb/tools/ParticlesToLevelSet.h new file mode 100755 index 0000000..83c1246 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/ParticlesToLevelSet.h @@ -0,0 +1,774 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @author Ken Museth +/// +/// @file ParticlesToLevelSet.h +/// +/// @brief This tool converts particles (with position, radius and +/// velocity) into a singed distance field encoded as a narrow band +/// level set. Optionally arbitrary attributes on the particles can +/// be transferred resulting in an additional attribute grid with the +/// same topology as the level set grid. +/// +/// @note This fast particle to level set converter is always intended +/// to be combined with some kind of surface post processing, +/// i.e. tools::Filter. Without such post processing the generated +/// surface is typically too noisy and blooby. However it serves as a +/// great and fast starting point for subsequent level set surface +/// processing and convolution. +/// +/// The @c ParticleListT template argument below refers to any class +/// with the following interface (see unittest/TestParticlesToLevelSet.cc +/// and SOP_DW_OpenVDBParticleVoxelizer for practical examples): +/// @code +/// +/// class ParticleList { +/// ... +/// public: +/// +/// // Return the total number of particles in list. +/// // Always required! +/// size_t size() const; +/// +/// // Get the world space position of n'th particle. +/// // Required by ParticledToLevelSet::rasterizeSphere(*this,radius). +/// void getPos(size_t n, Vec3R& xyz) const; +/// +/// // Get the world space position and radius of n'th particle. +/// // Required by ParticledToLevelSet::rasterizeSphere(*this). +/// void getPosRad(size_t n, Vec3R& xyz, Real& rad) const; +/// +/// // Get the world space position, radius and velocity of n'th particle. +/// // Required by ParticledToLevelSet::rasterizeSphere(*this,radius). +/// void getPosRadVel(size_t n, Vec3R& xyz, Real& rad, Vec3R& vel) const; +/// +/// // Get the attribute of the n'th particle. AttributeType is user-defined! +/// // Only required if attribute transfer is enabled in ParticledToLevelSet. +/// void getAtt(AttributeType& att) const; +/// }; +/// @endcode +/// +/// @note See unittest/TestParticlesToLevelSet.cc for an example. +/// +/// The @c InterruptT template argument below refers to any class +/// with the following interface: +/// @code +/// class Interrupter { +/// ... +/// public: +/// void start(const char* name = NULL)// called when computations begin +/// void end() // called when computations end +/// bool wasInterrupted(int percent=-1)// return true to break computation +/// }; +/// @endcode +/// +/// @note If no template argument is provided for this InterruptT +/// the util::NullInterrupter is used which implies that all +/// interrupter calls are no-ops (i.e. incurs no computational overhead). + +#ifndef OPENVDB_TOOLS_PARTICLES_TO_LEVELSET_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_PARTICLES_TO_LEVELSET_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Composite.h" // for csgUnion() + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + + +// This is a simple type that combines a distance value and a particle +// attribute. It's required for attribute transfer which is performed +// in the ParticlesToLevelSet::Raster memberclass defined below. +namespace local {template class BlindData;} + +template +class ParticlesToLevelSet +{ +public: + + typedef typename boost::is_void::type DisableT; + typedef InterrupterT InterrupterType; + + typedef SdfGridT SdfGridType; + typedef typename SdfGridT::ValueType SdfType; + + typedef typename boost::mpl::if_::type AttType; + typedef typename SdfGridT::template ValueConverter::Type AttGridType; + + BOOST_STATIC_ASSERT(boost::is_floating_point::value); + + /// @brief Constructor using an exiting signed distance, + /// i.e. narrow band level set, grid. + /// + /// @param grid Level set grid in which particles are rasterized + /// @param interrupt Callback to interrupt a long-running process + /// + /// @note The input grid is assumed to be a valid level set and if + /// it already contains voxels (with SDF values) partices are unioned + /// onto the exisinting level set surface. However, if attribute tranfer + /// is enabled, i.e. AttributeT != void, attributes are only + /// generated for voxels that overlap with particles, not the existing + /// voxels in the input grid (for which no attributes exist!). + /// + /// @details The width in voxel units of the generated narrow band level set is + /// given by 2*background/dx, where background is the background value + /// stored in the grid, and dx is the voxel size derived from the + /// transform also stored in the grid. Also note that -background + /// corresponds to the constant value inside the generated narrow + /// band level sets. Finally the default NullInterrupter should + /// compile out interruption checks during optimization, thus + /// incurring no run-time overhead. + explicit ParticlesToLevelSet(SdfGridT& grid, InterrupterT* interrupt = NULL); + + /// Destructor + ~ParticlesToLevelSet() { delete mBlindGrid; } + + /// @brief This methods syncs up the level set and attribute grids + /// and therefore needs to be called before any of these grids are + /// used and after the last call to any of the rasterizer methods. + /// + /// @note Avoid calling this method more then once and only after + /// all the particles have been rasterized. It has no effect if + /// attribute transfer is disabled, i.e. AttributeT = void. + void finalize(); + + /// @brief Return a shared pointer to the grid containing the + /// (optional) attribute. + /// + /// @warning If attribute transfer was disabled, i.e. AttributeT = + /// void, or finalize() was not called the pointer is NULL! + typename AttGridType::Ptr attributeGrid() { return mAttGrid; } + + /// @brief Return the size of a voxel in world units + Real getVoxelSize() const { return mDx; } + + /// @brief Return the half-width of the narrow band in voxel units + Real getHalfWidth() const { return mHalfWidth; } + + /// @brief Return the smallest radius allowed in voxel units + Real getRmin() const { return mRmin; } + /// @brief Return the largest radius allowed in voxel units + Real getRmax() const { return mRmax; } + + /// @brief Return true if any particles were ignored due to their size + bool ignoredParticles() const { return mMinCount>0 || mMaxCount>0; } + /// @brief Return number of small particles that were ignore due to Rmin + size_t getMinCount() const { return mMinCount; } + /// @brief Return number of large particles that were ignore due to Rmax + size_t getMaxCount() const { return mMaxCount; } + + /// @brief set the smallest radius allowed in voxel units + void setRmin(Real Rmin) { mRmin = math::Max(Real(0),Rmin); } + /// @brief set the largest radius allowed in voxel units + void setRmax(Real Rmax) { mRmax = math::Max(mRmin,Rmax); } + + /// @brief Rreturn the grain-size used for multi-threading + int getGrainSize() const { return mGrainSize; } + /// @brief Set the grain-size used for multi-threading. + /// @note A grainsize of 0 or less disables multi-threading! + void setGrainSize(int grainSize) { mGrainSize = grainSize; } + + /// @brief Rasterize a sphere per particle derived from their + /// position and radius. All spheres are CSG unioned. + /// + /// @param pa Particles with position and radius. + template + void rasterizeSpheres(const ParticleListT& pa); + + /// @brief Rasterize a sphere per particle derived from their + /// position and constant radius. All spheres are CSG unioned. + /// + /// @param pa Particles with position. + /// @param radius Constant particle radius in world units. + template + void rasterizeSpheres(const ParticleListT& pa, Real radius); + + /// @brief Rasterize a trail per particle derived from their + /// position, radius and velocity. Each trail is generated + /// as CSG unions of sphere instances with decreasing radius. + /// + /// @param pa particles with position, radius and velocity. + /// @param delta controls distance between sphere instances + /// (default=1). Be careful not to use too small values since this + /// can lead to excessive computation per trail (which the + /// interrupter can't stop). + /// + /// @note The direction of a trail is inverse to the direction of + /// the velocity vector, and the length is given by |V|. The radius + /// at the head of the trail is given by the radius of the particle + /// and the radius at the tail of the trail is Rmin voxel units which + /// has a default value of 1.5 corresponding to the Nyquist + /// frequency! + template + void rasterizeTrails(const ParticleListT& pa, Real delta=1.0); + +private: + + typedef local::BlindData BlindType; + typedef typename SdfGridT::template ValueConverter::Type BlindGridType; + + /// Class with multi-threaded implementation of particle rasterization + template struct Raster; + + SdfGridType* mSdfGrid; + typename AttGridType::Ptr mAttGrid; + BlindGridType* mBlindGrid; + InterrupterT* mInterrupter; + Real mDx, mHalfWidth; + Real mRmin, mRmax;//ignore particles outside this range of radii in voxel + size_t mMinCount, mMaxCount;//counters for ignored particles! + int mGrainSize; + +};//end of ParticlesToLevelSet class + +template +inline ParticlesToLevelSet:: +ParticlesToLevelSet(SdfGridT& grid, InterrupterT* interrupter) : + mSdfGrid(&grid), + mBlindGrid(NULL), + mInterrupter(interrupter), + mDx(grid.voxelSize()[0]), + mHalfWidth(grid.background()/mDx), + mRmin(1.5),// corresponds to the Nyquist grid sampling frequency + mRmax(100.0),// corresponds to a huge particle (probably too large!) + mMinCount(0), + mMaxCount(0), + mGrainSize(1) +{ + if (!mSdfGrid->hasUniformVoxels() ) { + OPENVDB_THROW(RuntimeError, + "ParticlesToLevelSet only supports uniform voxels!"); + } + if (mSdfGrid->getGridClass() != GRID_LEVEL_SET) { + OPENVDB_THROW(RuntimeError, + "ParticlesToLevelSet only supports level sets!" + "\nUse Grid::setGridClass(openvdb::GRID_LEVEL_SET)"); + } + + if (!DisableT::value) { + mBlindGrid = new BlindGridType(BlindType(grid.background())); + mBlindGrid->setTransform(mSdfGrid->transform().copy()); + } +} + +template +template +inline void ParticlesToLevelSet:: +rasterizeSpheres(const ParticleListT& pa) +{ + if (DisableT::value) { + Raster r(*this, mSdfGrid, pa); + r.rasterizeSpheres(); + } else { + Raster r(*this, mBlindGrid, pa); + r.rasterizeSpheres(); + } +} + +template +template +inline void ParticlesToLevelSet:: +rasterizeSpheres(const ParticleListT& pa, Real radius) +{ + if (DisableT::value) { + Raster r(*this, mSdfGrid, pa); + r.rasterizeSpheres(radius/mDx); + } else { + Raster r(*this, mBlindGrid, pa); + r.rasterizeSpheres(radius/mDx); + } +} + +template +template +inline void ParticlesToLevelSet:: +rasterizeTrails(const ParticleListT& pa, Real delta) +{ + if (DisableT::value) { + Raster r(*this, mSdfGrid, pa); + r.rasterizeTrails(delta); + } else { + Raster r(*this, mBlindGrid, pa); + r.rasterizeTrails(delta); + } +} + +template +inline void +ParticlesToLevelSet::finalize() +{ + if (mBlindGrid==NULL) return; + + typedef typename SdfGridType::TreeType SdfTreeT; + typedef typename AttGridType::TreeType AttTreeT; + typedef typename BlindGridType::TreeType BlindTreeT; + // Use topology copy constructors since output grids have the same topology as mBlindDataGrid + const BlindTreeT& tree = mBlindGrid->tree(); + + // New level set tree + typename SdfTreeT::Ptr sdfTree(new SdfTreeT( + tree, tree.background().visible(), openvdb::TopologyCopy())); + + // Note this overwrites any existing attribute grids! + typename AttTreeT::Ptr attTree(new AttTreeT( + tree, tree.background().blind(), openvdb::TopologyCopy())); + mAttGrid = typename AttGridType::Ptr(new AttGridType(attTree)); + mAttGrid->setTransform(mBlindGrid->transform().copy()); + + // Extract the level set and IDs from mBlindDataGrid. We will + // explore the fact that by design active values always live + // at the leaf node level, i.e. no active tiles exist in level sets + typedef typename BlindTreeT::LeafCIter LeafIterT; + typedef typename BlindTreeT::LeafNodeType LeafT; + typedef typename SdfTreeT::LeafNodeType SdfLeafT; + typedef typename AttTreeT::LeafNodeType AttLeafT; + for (LeafIterT n = tree.cbeginLeaf(); n; ++n) { + const LeafT& leaf = *n; + const openvdb::Coord xyz = leaf.origin(); + // Get leafnodes that were allocated during topology contruction! + SdfLeafT* sdfLeaf = sdfTree->probeLeaf(xyz); + AttLeafT* attLeaf = attTree->probeLeaf(xyz); + for (typename LeafT::ValueOnCIter m=leaf.cbeginValueOn(); m; ++m) { + // Use linear offset (vs coordinate) access for better performance! + const openvdb::Index k = m.pos(); + const BlindType& v = *m; + sdfLeaf->setValueOnly(k, v.visible()); + attLeaf->setValueOnly(k, v.blind()); + } + } + sdfTree->signedFloodFill();//required since we only transferred active voxels! + + if (mSdfGrid->empty()) { + mSdfGrid->setTree(sdfTree); + } else { + tools::csgUnion(mSdfGrid->tree(), *sdfTree, /*prune=*/true); + } +} + +/////////////////////////////////////////////////////////// + +template +template +struct ParticlesToLevelSet::Raster +{ + typedef typename boost::is_void::type DisableT; + typedef ParticlesToLevelSet ParticlesToLevelSetT; + typedef typename ParticlesToLevelSetT::SdfType SdfT;//type of signed distance values + typedef typename ParticlesToLevelSetT::AttType AttT;//type of particle attribute + typedef typename GridT::ValueType ValueT; + typedef typename GridT::Accessor AccessorT; + + /// @brief Main constructor + Raster(ParticlesToLevelSetT& parent, GridT* grid, const ParticleListT& particles) + : mParent(parent), + mParticles(particles), + mGrid(grid), + mMap(*(mGrid->transform().baseMap())), + mMinCount(0), + mMaxCount(0), + mOwnsGrid(false) + { + } + + /// @brief Copy constructor called by tbb threads + Raster(Raster& other, tbb::split) + : mParent(other.mParent), + mParticles(other.mParticles), + mGrid(new GridT(*other.mGrid, openvdb::ShallowCopy())), + mMap(other.mMap), + mMinCount(0), + mMaxCount(0), + mTask(other.mTask), + mOwnsGrid(true) + { + mGrid->newTree(); + } + + virtual ~Raster() { if (mOwnsGrid) delete mGrid; } + + /// @brief Rasterize a sphere per particle derived from their + /// position and radius. All spheres are CSG unioned. + void rasterizeSpheres() + { + mMinCount = mMaxCount = 0; + if (mParent.mInterrupter) { + mParent.mInterrupter->start("Rasterizing particles to level set using spheres"); + } + mTask = boost::bind(&Raster::rasterSpheres, _1, _2); + this->cook(); + if (mParent.mInterrupter) mParent.mInterrupter->end(); + } + /// @brief Rasterize a sphere per particle derived from their + /// position and constant radius. All spheres are CSG unioned. + /// @param radius constant radius of all particles in voxel units. + void rasterizeSpheres(Real radius) + { + mMinCount = radius < mParent.mRmin ? mParticles.size() : 0; + mMaxCount = radius > mParent.mRmax ? mParticles.size() : 0; + if (mMinCount>0 || mMaxCount>0) {//skipping all particles! + mParent.mMinCount = mMinCount; + mParent.mMaxCount = mMaxCount; + } else { + if (mParent.mInterrupter) { + mParent.mInterrupter->start( + "Rasterizing particles to level set using const spheres"); + } + mTask = boost::bind(&Raster::rasterFixedSpheres, _1, _2, SdfT(radius)); + this->cook(); + if (mParent.mInterrupter) mParent.mInterrupter->end(); + } + } + /// @brief Rasterize a trail per particle derived from their + /// position, radius and velocity. Each trail is generated + /// as CSG unions of sphere instances with decreasing radius. + /// + /// @param delta controls distance between sphere instances + /// (default=1). Be careful not to use too small values since this + /// can lead to excessive computation per trail (which the + /// interrupter can't stop). + /// + /// @note The direction of a trail is inverse to the direction of + /// the velocity vector, and the length is given by |V|. The radius + /// at the head of the trail is given by the radius of the particle + /// and the radius at the tail of the trail is Rmin voxel units which + /// has a default value of 1.5 corresponding to the Nyquist frequency! + void rasterizeTrails(Real delta=1.0) + { + mMinCount = mMaxCount = 0; + if (mParent.mInterrupter) { + mParent.mInterrupter->start("Rasterizing particles to level set using trails"); + } + mTask = boost::bind(&Raster::rasterTrails, _1, _2, SdfT(delta)); + this->cook(); + if (mParent.mInterrupter) mParent.mInterrupter->end(); + } + + /// @brief Kicks off the optionally multithreaded computation + void operator()(const tbb::blocked_range& r) + { + assert(mTask); + mTask(this, r); + mParent.mMinCount = mMinCount; + mParent.mMaxCount = mMaxCount; + } + + /// @brief Reguired by tbb::parallel_reduce + void join(Raster& other) + { + tools::csgUnion(*mGrid, *other.mGrid, /*prune=*/true); + mMinCount += other.mMinCount; + mMaxCount += other.mMaxCount; + } +private: + /// Disallow assignment since some of the members are references + Raster& operator=(const Raster& other) { return *this; } + + /// @return true if the particle is too small or too large + bool ignoreParticle(SdfT R) + { + if (R < mParent.mRmin) {// below the cutoff radius + ++mMinCount; + return true; + } + if (R > mParent.mRmax) {// above the cutoff radius + ++mMaxCount; + return true; + } + return false; + } + /// @brief Reguired by tbb::parallel_reduce to multithreaded + /// rasterization of particles as spheres with variable radius + /// + /// @param r tbb's default range referring to the list of particles + void rasterSpheres(const tbb::blocked_range& r) + { + AccessorT acc = mGrid->getAccessor(); // local accessor + bool run = true; + const SdfT invDx = 1/mParent.mDx; + AttT att; + Vec3R pos; + Real rad; + for (Index32 id = r.begin(), e=r.end(); run && id != e; ++id) { + mParticles.getPosRad(id, pos, rad); + const SdfT R = invDx * rad;// in voxel units + if (this->ignoreParticle(R)) continue; + const Vec3R P = mMap.applyInverseMap(pos); + this->getAtt(id, att); + run = this->makeSphere(P, R, att, acc); + }//end loop over particles + } + /// @brief Reguired by tbb::parallel_reduce to multithreaded + /// rasterization of particles as spheres with a fixed radius + /// + /// @param r tbb's default range referring to the list of particles + void rasterFixedSpheres(const tbb::blocked_range& r, SdfT R) + { + const SdfT dx = mParent.mDx, w = mParent.mHalfWidth;// in voxel units + AccessorT acc = mGrid->getAccessor(); // local accessor + const ValueT inside = -mGrid->background(); + const SdfT max = R + w;// maximum distance in voxel units + const SdfT max2 = math::Pow2(max);//square of maximum distance in voxel units + const SdfT min2 = math::Pow2(math::Max(SdfT(0), R - w));//square of minimum distance + ValueT v; + size_t count = 0; + AttT att; + Vec3R pos; + for (size_t id = r.begin(), e=r.end(); id != e; ++id) { + this->getAtt(id, att); + mParticles.getPos(id, pos); + const Vec3R P = mMap.applyInverseMap(pos); + const Coord a(math::Floor(P[0]-max),math::Floor(P[1]-max),math::Floor(P[2]-max)); + const Coord b(math::Ceil( P[0]+max),math::Ceil( P[1]+max),math::Ceil( P[2]+max)); + for ( Coord c = a; c.x() <= b.x(); ++c.x() ) { + //only check interrupter every 32'th scan in x + if (!(count++ & (1<<5)-1) && util::wasInterrupted(mParent.mInterrupter)) { + tbb::task::self().cancel_group_execution(); + return; + } + SdfT x2 = math::Pow2( c.x() - P[0] ); + for ( c.y() = a.y(); c.y() <= b.y(); ++c.y() ) { + SdfT x2y2 = x2 + math::Pow2( c.y() - P[1] ); + for ( c.z() = a.z(); c.z() <= b.z(); ++c.z() ) { + SdfT x2y2z2 = x2y2 + math::Pow2(c.z()- P[2]);//square distance from c to P + if ( x2y2z2 >= max2 || (!acc.probeValue(c,v) && v& r, SdfT delta) + { + AccessorT acc = mGrid->getAccessor(); // local accessor + bool run = true; + AttT att; + Vec3R pos, vel; + Real rad; + const Vec3R origin = mMap.applyInverseMap(Vec3R(0,0,0)); + const SdfT Rmin = mParent.mRmin, invDx = 1/mParent.mDx; + for (size_t id = r.begin(), e=r.end(); run && id != e; ++id) { + mParticles.getPosRadVel(id, pos, rad, vel); + const SdfT R0 = invDx*rad; + if (this->ignoreParticle(R0)) continue; + this->getAtt(id, att); + const Vec3R P0 = mMap.applyInverseMap(pos); + const Vec3R V = mMap.applyInverseMap(vel) - origin;//exclude translation + const SdfT speed = V.length(), inv_speed=1.0/speed; + const Vec3R N = -V*inv_speed;// inverse normalized direction + Vec3R P = P0;// local position of instance + SdfT R = R0, d=0;// local radius and length of trail + for (size_t m=0; run && d <= speed ; ++m) { + run = this->makeSphere(P, R, att, acc); + P += 0.5*delta*R*N;// adaptive offset along inverse velocity direction + d = (P-P0).length();// current length of trail + R = R0-(R0-Rmin)*d*inv_speed;// R = R0 -> mRmin(e.g. 1.5) + }//end loop over sphere instances + }//end loop over particles + } + + void cook() + { + if (mParent.mGrainSize>0) { + tbb::parallel_reduce( + tbb::blocked_range(0,mParticles.size(),mParent.mGrainSize), *this); + } else { + (*this)(tbb::blocked_range(0, mParticles.size())); + } + } + + /// @brief Rasterize sphere at position P and radius R into a + /// narrow-band level set with half-width, mHalfWidth. + /// @return false if it was interrupted + /// + /// @param P coordinates of the particle position in voxel units + /// @param R radius of particle in voxel units + /// @param id + /// @param accessor grid accessor with a private copy of the grid + /// + /// @note For best performance all computations are performed in + /// voxel-space with the important exception of the final level set + /// value that is converted to world units (e.g. the grid stores + /// the closest Euclidian signed distances measured in world + /// units). Also note we use the convention of positive distances + /// outside the surface an negative distances inside the surface. + bool makeSphere(const Vec3R &P, SdfT R, const AttT& att, AccessorT& acc) + { + const ValueT inside = -mGrid->background(); + const SdfT dx = mParent.mDx, w = mParent.mHalfWidth; + const SdfT max = R + w;// maximum distance in voxel units + const Coord a(math::Floor(P[0]-max),math::Floor(P[1]-max),math::Floor(P[2]-max)); + const Coord b(math::Ceil( P[0]+max),math::Ceil( P[1]+max),math::Ceil( P[2]+max)); + const SdfT max2 = math::Pow2(max);//square of maximum distance in voxel units + const SdfT min2 = math::Pow2(math::Max(SdfT(0), R - w));//square of minimum distance + ValueT v; + size_t count = 0; + for ( Coord c = a; c.x() <= b.x(); ++c.x() ) { + //only check interrupter every 32'th scan in x + if (!(count++ & (1<<5)-1) && util::wasInterrupted(mParent.mInterrupter)) { + tbb::task::self().cancel_group_execution(); + return false; + } + SdfT x2 = math::Pow2( c.x() - P[0] ); + for ( c.y() = a.y(); c.y() <= b.y(); ++c.y() ) { + SdfT x2y2 = x2 + math::Pow2( c.y() - P[1] ); + for ( c.z() = a.z(); c.z() <= b.z(); ++c.z() ) { + SdfT x2y2z2 = x2y2 + math::Pow2( c.z() - P[2] );//square distance from c to P + if ( x2y2z2 >= max2 || (!acc.probeValue(c,v) && v&)> FuncType; + + template + typename boost::enable_if::type + getAtt(size_t, AttT&) const {;} + + template + typename boost::disable_if::type + getAtt(size_t n, AttT& a) const {mParticles.getAtt(n, a);} + + template + typename boost::enable_if, ValueT>::type + Merge(T s, const AttT&) const { return s; } + + template + typename boost::disable_if, ValueT>::type + Merge(T s, const AttT& a) const { return ValueT(s,a); } + + ParticlesToLevelSetT& mParent; + const ParticleListT& mParticles;//list of particles + GridT* mGrid; + const math::MapBase& mMap; + size_t mMinCount, mMaxCount;//counters for ignored particles! + FuncType mTask; + const bool mOwnsGrid; +};//end of Raster struct + + +///////////////////// YOU CAN SAFELY IGNORE THIS SECTION ///////////////////// + +namespace local { +// This is a simple type that combines a distance value and a particle +// attribute. It's required for attribute transfer which is defined in the +// Raster class above. +template +class BlindData +{ + public: + typedef VisibleT type; + typedef VisibleT VisibleType; + typedef BlindT BlindType; + explicit BlindData() {} + explicit BlindData(VisibleT v) : mVisible(v) {} + BlindData(VisibleT v, BlindT b) : mVisible(v), mBlind(b) {} + BlindData& operator=(const BlindData& rhs) + { + mVisible = rhs.mVisible; + mBlind = rhs.mBlind; + return *this; + } + const VisibleT& visible() const { return mVisible; } + const BlindT& blind() const { return mBlind; } + bool operator==(const BlindData& rhs) const { return mVisible == rhs.mVisible; } + bool operator< (const BlindData& rhs) const { return mVisible < rhs.mVisible; }; + bool operator> (const BlindData& rhs) const { return mVisible > rhs.mVisible; }; + BlindData operator+(const BlindData& rhs) const { return BlindData(mVisible + rhs.mVisible); }; + BlindData operator+(const VisibleT& rhs) const { return BlindData(mVisible + rhs); }; + BlindData operator-(const BlindData& rhs) const { return BlindData(mVisible - rhs.mVisible); }; + BlindData operator-() const { return BlindData(-mVisible, mBlind); } +protected: + VisibleT mVisible; + BlindT mBlind; +}; +// Required by several of the tree nodes +template +inline std::ostream& operator<<(std::ostream& ostr, const BlindData& rhs) +{ + ostr << rhs.visible(); + return ostr; +} +// Required by math::Abs +template +inline BlindData Abs(const BlindData& x) +{ + return BlindData(math::Abs(x.visible()), x.blind()); +} +}// local namespace + +////////////////////////////////////////////////////////////////////////////// + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_PARTICLES_TO_LEVELSET_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/PointAdvect.h b/openvdb_2_3_0_library/openvdb/tools/PointAdvect.h new file mode 100755 index 0000000..97b40d6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/PointAdvect.h @@ -0,0 +1,524 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @author Ken Museth, D.J. Hill (openvdb port, added staggered grid support) +/// @file PointAdvect.h +/// +/// @brief Class PointAdvect advects points (with position) in a static velocity field + +#ifndef OPENVDB_TOOLS_POINT_ADVECT_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_POINT_ADVECT_HAS_BEEN_INCLUDED + +#include +#include // min +#include // Vec3 types and version number +#include // grid +#include +#include "Interpolation.h" // sampling + +#include +#include // threading +#include // threading +#include // for cancel + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// Class that holds a Vec3 grid, to be interpreted as the closest point to a constraint +/// surface. Supports a method to allow a point to be projected onto the closest point +/// on the constraint surface. Uses Caching. +template +class ClosestPointProjector +{ +public: + typedef CptGridT CptGridType; + typedef typename CptGridType::ConstAccessor CptAccessor; + typedef typename CptGridType::ValueType CptValueType; + + ClosestPointProjector(): + mCptIterations(0) + { + } + ClosestPointProjector(const CptGridType& cptGrid, int n): + mCptGrid(&cptGrid), + mCptAccessor(cptGrid.getAccessor()), + mCptIterations(n) + { + } + ClosestPointProjector(const ClosestPointProjector &other): + mCptGrid(other.mCptGrid), + mCptAccessor(mCptGrid->getAccessor()), + mCptIterations(other.mCptIterations) + { + } + void setConstraintIterations(unsigned int cptIterations) { mCptIterations = cptIterations; } + unsigned int numIterations() { return mCptIterations;}; + + // point constraint + template + inline void projectToConstraintSurface(LocationType& W) const + { + /// Entries in the CPT tree are the closest point to the constraint surface. + /// The interpolation step in sample introduces error so that the result + /// of a single sample may not lie exactly on the surface. The iterations + /// in the loop exist to minimize this error. + CptValueType result(W[0], W[1],W[2]); + for (unsigned int i = 0; i < mCptIterations; ++i) { + const Vec3R location = mCptGrid->worldToIndex(Vec3R(result[0], result[1], result[2])); + BoxSampler::sample(mCptAccessor, location, result); + } + W[0] = result[0]; + W[1] = result[1]; + W[2] = result[2]; + } + +private: + const CptGridType* mCptGrid; // Closest-Point-Transform vector field + CptAccessor mCptAccessor; + unsigned int mCptIterations; +};// end of ClosestPointProjector class + + +/// Class to hold a Vec3 field interperated as a velocity field. +/// Primarily exists to provide a method(s) that integrate a passive +/// point forward in the velocity field for a single time-step (dt) +template +class VelocitySampler +{ +public: + typedef typename GridT::ConstAccessor VelAccessor; + typedef typename GridT::ValueType VelValueType; + + VelocitySampler(const GridT& velGrid): + mVelGrid(&velGrid), + mVelAccessor(mVelGrid->getAccessor()) + { + } + VelocitySampler(const VelocitySampler& other): + mVelGrid(other.mVelGrid), + mVelAccessor(mVelGrid->getAccessor()) + { + } + ~VelocitySampler() + { + } + /// Samples the velocity at position W onto result. Supports both + /// staggered (i.e. MAC) and collocated velocity grids. + template + inline void sample(const LocationType& W, VelValueType& result) const + { + const Vec3R location = mVelGrid->worldToIndex(Vec3R(W[0], W[1], W[2])); + /// Note this if-branch is optimized away at compile time + if (StaggeredVelocity) { + // the velocity Grid stores data in MAC-style staggered layout + StaggeredBoxSampler::sample(mVelAccessor, location, result); + } else { + // the velocity Grid uses collocated data + BoxSampler::sample(mVelAccessor, location, result); + } + } + +private: + // holding the Grids for the transforms + const GridT* mVelGrid; // Velocity vector field + VelAccessor mVelAccessor; +};// end of VelocitySampler class + + +/// @brief Performs runge-kutta time integration of variable order in +/// a static velocity field +template +class VelocityIntegrator +{ +public: + typedef typename GridT::ValueType VecType; + typedef typename VecType::ValueType ElementType; + + VelocityIntegrator(const GridT& velGrid): + mVelField(velGrid) + { + } + // variable order Runge-Kutta time integration for a single time step + template + void rungeKutta(const float dt, LocationType& loc) { + VecType P(loc[0],loc[1],loc[2]), V0, V1, V2, V3; + + BOOST_STATIC_ASSERT((Order < 5) && (Order > -1)); + /// Note the if-braching below is optimized away at compile time + if (Order == 0) { + // do nothing + return ; + } else if (Order == 1) { + mVelField.sample(P, V0); + P = dt*V0; + + } else if (Order == 2) { + mVelField.sample(P, V0); + mVelField.sample(P + ElementType(0.5) * ElementType(dt) * V0, V1); + P = dt*V1; + + } else if (Order == 3) { + mVelField.sample(P, V0); + mVelField.sample(P+ElementType(0.5)*ElementType(dt)*V0, V1); + mVelField.sample(P+dt*(ElementType(2.0)*V1-V0), V2); + P = dt*(V0 + ElementType(4.0)*V1 + V2)*ElementType(1.0/6.0); + + } else if (Order == 4) { + mVelField.sample(P, V0); + mVelField.sample(P+ElementType(0.5)*ElementType(dt)*V0, V1); + mVelField.sample(P+ElementType(0.5)*ElementType(dt)*V1, V2); + mVelField.sample(P+ dt*V2, V3); + P = dt*(V0 + ElementType(2.0)*(V1 + V2) + V3)*ElementType(1.0/6.0); + + } + loc += LocationType(P[0], P[1], P[2]); + + } +private: + VelocitySampler mVelField; +};// end of VelocityIntegrator class + + +//////////////////////////////////////// + + +/// Performs passive or constrained advection of points in a velocity field +/// represented by an OpenVDB grid and an optional closest-point-transform (CPT) +/// represented in another OpenVDB grid. Note the CPT is assumed to be +/// in world coordinates and NOT index coordinates! +/// Supports both collocated velocity grids and staggered velocity grids +/// +/// The @c PointListT template argument refers to any class with the following +/// interface (e.g., std::vector): +/// @code +/// class PointList { +/// ... +/// public: +/// typedef internal_vector3_type value_type; // must support [] component access +/// openvdb::Index size() const; // number of points in list +/// value_type& operator[](int n); // world space position of nth point +/// }; +/// @endcode +/// +/// @note All methods (except size) are assumed to be thread-safe and +/// the positions are returned as non-const references since the +/// advection method needs to modify them! +template, + bool StaggeredVelocity = false, + typename InterrupterType = util::NullInterrupter> +class PointAdvect +{ +public: + typedef GridT GridType; + typedef PointListT PointListType; + typedef typename PointListT::value_type LocationType; + typedef VelocityIntegrator VelocityFieldIntegrator; + + PointAdvect(const GridT& velGrid, InterrupterType* interrupter=NULL) : + mVelGrid(&velGrid), + mPoints(NULL), + mIntegrationOrder(1), + mThreaded(true), + mInterrupter(interrupter) + { + } + PointAdvect(const PointAdvect &other) : + mVelGrid(other.mVelGrid), + mPoints(other.mPoints), + mDt(other.mDt), + mAdvIterations(other.mAdvIterations), + mIntegrationOrder(other.mIntegrationOrder), + mThreaded(other.mThreaded), + mInterrupter(other.mInterrupter) + { + } + virtual ~PointAdvect() + { + } + /// If the order of the integration is set to zero no advection is performed + bool earlyOut() const { return (mIntegrationOrder==0);} + /// get & set + void setThreaded(bool threaded) { mThreaded = threaded; } + bool getThreaded() { return mThreaded; } + void setIntegrationOrder(unsigned int order) {mIntegrationOrder = order;} + + /// Constrained advection of a list of points over a time = dt * advIterations + void advect(PointListT& points, float dt, unsigned int advIterations = 1) + { + if (this->earlyOut()) return; // nothing to do! + mPoints = &points; + mDt = dt; + mAdvIterations = advIterations; + + if (mInterrupter) mInterrupter->start("Advecting points by OpenVDB velocity field: "); + if (mThreaded) { + tbb::parallel_for(tbb::blocked_range(0, mPoints->size()), *this); + } else { + (*this)(tbb::blocked_range(0, mPoints->size())); + } + if (mInterrupter) mInterrupter->end(); + } + + /// Never call this method directly - it is use by TBB and has to be public! + void operator() (const tbb::blocked_range &range) const + { + if (mInterrupter && mInterrupter->wasInterrupted()) { + tbb::task::self().cancel_group_execution(); + } + + VelocityFieldIntegrator velField(*mVelGrid); + switch (mIntegrationOrder) { + case 1: + { + for (size_t n = range.begin(); n != range.end(); ++n) { + LocationType& X0 = (*mPoints)[n]; + // loop over number of time steps + for (unsigned int i = 0; i < mAdvIterations; ++i) { + velField.template rungeKutta<1>(mDt, X0); + } + } + } + break; + case 2: + { + for (size_t n = range.begin(); n != range.end(); ++n) { + LocationType& X0 = (*mPoints)[n]; + // loop over number of time steps + for (unsigned int i = 0; i < mAdvIterations; ++i) { + velField.template rungeKutta<2>(mDt, X0); + } + } + } + break; + case 3: + { + for (size_t n = range.begin(); n != range.end(); ++n) { + LocationType& X0 = (*mPoints)[n]; + // loop over number of time steps + for (unsigned int i = 0; i < mAdvIterations; ++i) { + velField.template rungeKutta<3>(mDt, X0); + } + } + } + break; + case 4: + { + for (size_t n = range.begin(); n != range.end(); ++n) { + LocationType& X0 = (*mPoints)[n]; + // loop over number of time steps + for (unsigned int i = 0; i < mAdvIterations; ++i) { + velField.template rungeKutta<4>(mDt, X0); + } + } + } + break; + } + } + +private: + // the velocity field + const GridType* mVelGrid; + + // vertex list of all the points + PointListT* mPoints; + + // time integration parameters + float mDt; // time step + unsigned int mAdvIterations; // number of time steps + unsigned int mIntegrationOrder; + + // operational parameters + bool mThreaded; + InterrupterType* mInterrupter; + +};//end of PointAdvect class + + +template, + bool StaggeredVelocity = false, + typename CptGridType = GridT, + typename InterrupterType = util::NullInterrupter> +class ConstrainedPointAdvect +{ +public: + typedef GridT GridType; + typedef typename PointListT::value_type LocationType; + typedef VelocityIntegrator VelocityIntegratorType; + typedef ClosestPointProjector ClosestPointProjectorType; + typedef PointListT PointListType; + + ConstrainedPointAdvect(const GridType& velGrid, + const GridType& cptGrid, int cptn, InterrupterType* interrupter = NULL): + mVelGrid(&velGrid), + mCptGrid(&cptGrid), + mCptIter(cptn), + mInterrupter(interrupter) + { + } + ConstrainedPointAdvect(const ConstrainedPointAdvect& other): + mVelGrid(other.mVelGrid), + mCptGrid(other.mCptGrid), + mCptIter(other.mCptIter), + mPoints(other.mPoints), + mDt(other.mDt), + mAdvIterations(other.mAdvIterations), + mIntegrationOrder(other.mIntegrationOrder), + mThreaded(other.mThreaded), + mInterrupter(other.mInterrupter) + { + } + virtual ~ConstrainedPointAdvect(){} + + void setConstraintIterations(unsigned int cptIter) {mCptIter = cptIter;} + void setIntegrationOrder(unsigned int order) {mIntegrationOrder = order;} + + void setThreaded(bool threaded) { mThreaded = threaded; } + bool getThreaded() { return mThreaded; } + + /// Constrained Advection a list of points over a time = dt * advIterations + void advect(PointListT& points, float dt, unsigned int advIterations = 1) + { + mPoints = &points; + mDt = dt; + + if (mIntegrationOrder==0 && mCptIter == 0) { + return; // nothing to do! + } + (mIntegrationOrder>0) ? mAdvIterations = advIterations : mAdvIterations = 1; + + if (mInterrupter) mInterrupter->start("Advecting points by OpenVDB velocity field: "); + const int N = mPoints->size(); + + if (mThreaded) { + tbb::parallel_for(tbb::blocked_range(0, N), *this); + } else { + (*this)(tbb::blocked_range(0, N)); + } + if (mInterrupter) mInterrupter->end(); + } + + + /// Never call this method directly - it is use by TBB and has to be public! + void operator() (const tbb::blocked_range &range) const + { + if (mInterrupter && mInterrupter->wasInterrupted()) { + tbb::task::self().cancel_group_execution(); + } + + VelocityIntegratorType velField(*mVelGrid); + ClosestPointProjectorType cptField(*mCptGrid, mCptIter); + switch (mIntegrationOrder) { + case 0://pure CPT projection + { + for (size_t n = range.begin(); n != range.end(); ++n) { + LocationType& X0 = (*mPoints)[n]; + for (unsigned int i = 0; i < mAdvIterations; ++i) { + cptField.projectToConstraintSurface(X0); + } + } + } + break; + case 1://1'th order advection and CPT projection + { + for (size_t n = range.begin(); n != range.end(); ++n) { + LocationType& X0 = (*mPoints)[n]; + for (unsigned int i = 0; i < mAdvIterations; ++i) { + velField.template rungeKutta<1>(mDt, X0); + cptField.projectToConstraintSurface(X0); + } + } + } + break; + case 2://2'nd order advection and CPT projection + { + for (size_t n = range.begin(); n != range.end(); ++n) { + LocationType& X0 = (*mPoints)[n]; + for (unsigned int i = 0; i < mAdvIterations; ++i) { + velField.template rungeKutta<2>(mDt, X0); + cptField.projectToConstraintSurface(X0); + } + } + } + break; + + case 3://3'rd order advection and CPT projection + { + for (size_t n = range.begin(); n != range.end(); ++n) { + LocationType& X0 = (*mPoints)[n]; + for (unsigned int i = 0; i < mAdvIterations; ++i) { + velField.template rungeKutta<3>(mDt, X0); + cptField.projectToConstraintSurface(X0); + } + } + } + break; + case 4://4'th order advection and CPT projection + { + for (size_t n = range.begin(); n != range.end(); ++n) { + LocationType& X0 = (*mPoints)[n]; + for (unsigned int i = 0; i < mAdvIterations; ++i) { + velField.template rungeKutta<4>(mDt, X0); + cptField.projectToConstraintSurface(X0); + } + } + } + break; + } + } + +private: + const GridType* mVelGrid; // the velocity field + const GridType* mCptGrid; + int mCptIter; + PointListT* mPoints; // vertex list of all the points + + // time integration parameters + float mDt; // time step + unsigned int mAdvIterations; // number of time steps + unsigned int mIntegrationOrder; // order of Runge-Kutta integration + // operational parameters + bool mThreaded; + InterrupterType* mInterrupter; +};// end of ConstrainedPointAdvect class + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_POINT_ADVECT_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/PointScatter.h b/openvdb_2_3_0_library/openvdb/tools/PointScatter.h new file mode 100755 index 0000000..76d54a8 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/PointScatter.h @@ -0,0 +1,326 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file PointScatter.h + +#ifndef OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED + +#include +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief The two point scatters UniformPointScatter and +/// NonUniformPointScatter depend on the following two classes: +/// +/// The @c PointAccessorType template argument below refers to any class +/// with the following interface: +/// @code +/// class PointAccessor { +/// ... +/// public: +/// void add(const openvdb::Vec3R &pos);// appends point with world positions pos +/// }; +/// @endcode +/// +/// +/// The @c InterruptType template argument below refers to any class +/// with the following interface: +/// @code +/// class Interrupter { +/// ... +/// public: +/// void start(const char* name = NULL)// called when computations begin +/// void end() // called when computations end +/// bool wasInterrupted(int percent=-1)// return true to break computation +///}; +/// @endcode +/// +/// @note If no template argument is provided for this InterruptType +/// the util::NullInterrupter is used which implies that all +/// interrupter calls are no-ops (i.e. incurs no computational overhead). + + +/// @brief Uniform scatters of point in the active voxels. +/// The point count is either explicitly defined or implicitly +/// through the specification of a global density (=points-per-volume) +/// +/// @note This uniform scattering technique assumes that the number of +/// points is generally smaller than the number of active voxels +/// (including virtual active voxels in active tiles). +template +class UniformPointScatter +{ +public: + UniformPointScatter(PointAccessorType& points, + int pointCount, + RandomGenerator& randGen, + InterruptType* interrupt = NULL): + mPoints(points), + mInterrupter(interrupt), + mPointCount(pointCount), + mPointsPerVolume(0.0f), + mVoxelCount(0), + mRandomGen(randGen) + { + } + UniformPointScatter(PointAccessorType& points, + float pointsPerVolume, + RandomGenerator& randGen, + InterruptType* interrupt = NULL): + mPoints(points), + mInterrupter(interrupt), + mPointCount(0), + mPointsPerVolume(pointsPerVolume), + mVoxelCount(0), + mRandomGen(randGen) + { + } + + /// This is the main functor method implementing the actual scattering of points. + template + void operator()(const GridT& grid) + { + mVoxelCount = grid.activeVoxelCount(); + if (mVoxelCount == 0) return; + const openvdb::Index64 voxelId = mVoxelCount - 1; + const openvdb::Vec3d dim = grid.voxelSize(); + if (mPointsPerVolume>0) { + if (mInterrupter) mInterrupter->start("Uniform scattering with fixed point density"); + mPointCount = int(mPointsPerVolume * dim[0]*dim[1]*dim[2] * mVoxelCount); + } else if (mPointCount>0) { + if (mInterrupter) mInterrupter->start("Uniform scattering with fixed point count"); + mPointsPerVolume = mPointCount/float(dim[0]*dim[1]*dim[2] * mVoxelCount); + } else { + return; + } + openvdb::CoordBBox bbox; + /// build sorted multi-map of random voxel-ids to contain a point + std::multiset mVoxelSet; + const double maxId = static_cast(voxelId); + for (int i=0, chunks=100000; i::iterator voxelIter = + mVoxelSet.begin(), voxelEnd = mVoxelSet.end(); + typename GridT::ValueOnCIter valueIter = grid.cbeginValueOn(); + mPointCount = 0;//addPoint increments this counter + size_t interruptCount = 0; + for (openvdb::Index64 n=valueIter.getVoxelCount(); voxelIter != voxelEnd; ++voxelIter) { + //only check interrupter for every 32'th particle + if (!(interruptCount++ & (1<<5)-1) && util::wasInterrupted(mInterrupter)) return; + while ( n <= *voxelIter ) { + ++valueIter; + n += valueIter.getVoxelCount(); + } + if (valueIter.isVoxelValue()) {// a majorty is expected to be voxels + const openvdb::Coord min = valueIter.getCoord(); + const openvdb::Vec3R dmin(min.x()-0.5, min.y()-0.5, min.z()-0.5); + this->addPoint(grid, dmin); + } else {// tiles contain multiple (virtual) voxels + valueIter.getBoundingBox(bbox); + const openvdb::Coord size(bbox.extents()); + const openvdb::Vec3R dmin(bbox.min().x()-0.5, + bbox.min().y()-0.5, + bbox.min().z()-0.5); + this->addPoint(grid, dmin, size); + } + } + if (mInterrupter) mInterrupter->end(); + } + + // The following methods should only be called after the + // the operator() method was called + void print(const std::string &name, std::ostream& os = std::cout) const + { + os << "Uniformely scattered " << mPointCount << " points into " << mVoxelCount + << " active voxels in \"" << name << "\" corresponding to " + << mPointsPerVolume << " points per volume." << std::endl; + } + + int getPointCount() const { return mPointCount; } + float getPointsPerVolume() const { return mPointsPerVolume; } + openvdb::Index64 getVoxelCount() const { return mVoxelCount; } + +private: + PointAccessorType& mPoints; + InterruptType* mInterrupter; + int mPointCount; + float mPointsPerVolume; + openvdb::Index64 mVoxelCount; + RandomGenerator& mRandomGen; + boost::uniform_01 mRandom; + + double getRand() { return mRandom(mRandomGen); } + + template + inline void addPoint(const GridT &grid, const openvdb::Vec3R &pos, const openvdb::Vec3R &delta) + { + mPoints.add(grid.indexToWorld(pos + delta)); + ++mPointCount; + } + template + inline void addPoint(const GridT &grid, const openvdb::Vec3R &dmin) + { + this->addPoint(grid, dmin, openvdb::Vec3R(getRand(),getRand(),getRand())); + } + template + inline void addPoint(const GridT &grid, const openvdb::Vec3R &dmin, const openvdb::Coord &size) + { + const openvdb::Vec3R d(size.x()*getRand(),size.y()*getRand(),size.z()*getRand()); + this->addPoint(grid, dmin, d); + } +}; // class UniformPointScatter + + +/// @brief Non-uniform scatters of point in the active voxels. +/// The local point count is implicitly defined as a product of +/// of a global density and the local voxel (or tile) value. +/// +/// @note This scattering technique can be significantly slower +/// than a uniform scattering since its computational complexity +/// is proportional to the active voxel (and tile) count. +template +class NonUniformPointScatter +{ +public: + NonUniformPointScatter(PointAccessorType& points, + float pointsPerVolume, + RandomGenerator& randGen, + InterruptType* interrupt = NULL): + mPoints(points), + mInterrupter(interrupt), + mPointCount(0), + mPointsPerVolume(pointsPerVolume),//note this is NOT the local point density + mVoxelCount(0), + mRandomGen(randGen) + { + } + + /// This is the main functor method implementing the actual scattering of points. + template + void operator()(const GridT& grid) + { + mVoxelCount = grid.activeVoxelCount(); + if (mVoxelCount == 0) return;//throw std::runtime_error("No voxels in which to scatter points!"); + if (mInterrupter) mInterrupter->start("Non-uniform scattering with local point density"); + const openvdb::Vec3d dim = grid.voxelSize(); + const double volumePerVoxel = dim[0]*dim[1]*dim[2], + pointsPerVoxel = mPointsPerVolume * volumePerVoxel; + openvdb::CoordBBox bbox; + size_t interruptCount = 0; + for (typename GridT::ValueOnCIter iter = grid.cbeginValueOn(); iter; ++iter) { + //only check interrupter for every 32'th active value + if (!(interruptCount++ & (1<<5)-1) && util::wasInterrupted(mInterrupter)) return; + const double d = (*iter) * pointsPerVoxel * iter.getVoxelCount(); + const int n = int(d); + if (iter.isVoxelValue()) { // a majorty is expected to be voxels + const openvdb::Coord min = iter.getCoord(); + const openvdb::Vec3R dmin(min.x()-0.5, min.y()-0.5, min.z()-0.5); + for (int i = 0; i < n; ++i) this->addPoint(grid, dmin); + if (getRand() < (d - n)) this->addPoint(grid, dmin); + } else { // tiles contain multiple (virtual) voxels + iter.getBoundingBox(bbox); + const openvdb::Coord size(bbox.extents()); + const openvdb::Vec3R dmin(bbox.min().x()-0.5, + bbox.min().y()-0.5, + bbox.min().z()-0.5); + for (int i = 0; i < n; ++i) this->addPoint(grid, dmin, size); + if (getRand() < (d - n)) this->addPoint(grid, dmin, size); + } + }//loop over the active values + if (mInterrupter) mInterrupter->end(); + } + + // The following methods should only be called after the + // the operator() method was called + void print(const std::string &name, std::ostream& os = std::cout) const + { + os << "Non-uniformely scattered " << mPointCount << " points into " << mVoxelCount + << " active voxels in \"" << name << "\"." << std::endl; + } + + int getPointCount() const { return mPointCount; } + openvdb::Index64 getVoxelCount() const { return mVoxelCount; } + +private: + PointAccessorType& mPoints; + InterruptType* mInterrupter; + int mPointCount; + float mPointsPerVolume; + openvdb::Index64 mVoxelCount; + RandomGenerator& mRandomGen; + boost::uniform_01 mRandom; + + double getRand() { return mRandom(mRandomGen); } + + template + inline void addPoint(const GridT &grid, const openvdb::Vec3R &pos, const openvdb::Vec3R &delta) + { + mPoints.add(grid.indexToWorld(pos + delta)); + ++mPointCount; + } + template + inline void addPoint(const GridT &grid, const openvdb::Vec3R &dmin) + { + this->addPoint(grid, dmin, openvdb::Vec3R(getRand(),getRand(),getRand())); + } + template + inline void addPoint(const GridT &grid, const openvdb::Vec3R &dmin, const openvdb::Coord &size) + { + const openvdb::Vec3R d(size.x()*getRand(),size.y()*getRand(),size.z()*getRand()); + this->addPoint(grid, dmin, d); + } + +}; // class NonUniformPointScatter + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/RayIntersector.h b/openvdb_2_3_0_library/openvdb/tools/RayIntersector.h new file mode 100755 index 0000000..f714289 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/RayIntersector.h @@ -0,0 +1,690 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +/// +/// @file RayIntersector.h +/// +/// @author Ken Museth +/// +/// @brief Accelerated intersection of a ray with a narrow-band level +/// set or a generic (e.g. density) volume. This will of course be +/// useful for respectively surface and volume rendering. +/// +/// @details This file defines two main classes, +/// LevelSetRayIntersector and VolumeRayIntersector, as well as the +/// three support classes LevelSetHDDA, VolumeHDDA and LinearSearchImpl. +/// The LevelSetRayIntersector is templated on the LinearSearchImpl class +/// and calls instances of the LevelSetHDDA class. The reason to split +/// level set ray intersection into three classes is twofold. First +/// LevelSetRayIntersector defines the public API for client code and +/// LinearSearchImpl defines the actual algorithm used for the +/// ray level-set intersection. In other words this design will allow +/// for the public API to be fixed while the intersection algorithm +/// can change without resolving to (slow) virtual methods. Second, +/// LevelSetHDDA, which implements a hierarchical Differential Digital +/// Analyzer, relies on partial template specialization, so it has to +/// be a standalone class (as opposed to a member class of +/// LevelSetRayIntersector). The VolumeRayIntersector is conceptually +/// much simpler then the LevelSetRayIntersector, and hence it only +/// depends on VolumeHDDA that implements the hierarchical +/// Differential Digital Analyzer. + + +#ifndef OPENVDB_TOOLS_RAYINTERSECTOR_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_RAYINTERSECTOR_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include "Morphology.h" +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +// Helper class that implements the actual search of the zero-crossing +// of the level set along the direction of a ray. This particular +// implementation uses iterative linear search. +template +class LinearSearchImpl; + + +///////////////////////////////////// LevelSetRayIntersector ///////////////////////////////////// + + +/// @brief This class provides the public API for intersecting a ray +/// with a narrow-band level set. +/// +/// @details It wraps an SearchImplT with a simple public API and +/// performs the actual hierarchical tree node and voxel traversal. +/// +/// @warning Use the (default) copy-constructor to make sure each +/// computational thread has their own instance of this class. This is +/// important since the SearchImplT contains a ValueAccessor that is +/// not thread-safe. However copying is very efficient. +/// +/// @see tools/RayTracer.h for examples of intended usage. +/// +/// @todo Add TrilinearSearchImpl, as an alternative to LinearSearchImpl, +/// that performs analytical 3D trilinear intersection tests, i.e., solves +/// cubic equations. This is slower but also more accurate than the 1D +/// linear interpolation in LinearSearchImpl. +template, + int NodeLevel = GridT::TreeType::RootNodeType::ChildNodeType::LEVEL, + typename RayT = math::Ray > +class LevelSetRayIntersector +{ +public: + typedef GridT GridType; + typedef RayT RayType; + typedef typename RayT::RealType RealType; + typedef typename RayT::Vec3T Vec3Type; + typedef typename GridT::ValueType ValueT; + typedef typename GridT::TreeType TreeT; + + BOOST_STATIC_ASSERT( NodeLevel >= -1 && NodeLevel < int(TreeT::DEPTH)-1); + BOOST_STATIC_ASSERT(boost::is_floating_point::value); + + /// @brief Constructor + /// @param grid level set grid to intersect rays against. + /// @param isoValue optional iso-value for the ray-intersection. + LevelSetRayIntersector(const GridT& grid, const ValueT& isoValue = zeroVal()) + : mTester(grid, isoValue) + { + if (!grid.hasUniformVoxels() ) { + OPENVDB_THROW(RuntimeError, + "LevelSetRayIntersector only supports uniform voxels!"); + } + if (grid.getGridClass() != GRID_LEVEL_SET) { + OPENVDB_THROW(RuntimeError, + "LevelSetRayIntersector only supports level sets!" + "\nUse Grid::setGridClass(openvdb::GRID_LEVEL_SET)"); + } + } + + /// @brief Return the iso-value used for ray-intersections + const ValueT& getIsoValue() const { return mTester.getIsoValue(); } + + /// @brief Return @c true if the index-space ray intersects the level set. + /// @param iRay ray represented in index space. + bool intersectsIS(const RayType& iRay) const + { + if (!mTester.setIndexRay(iRay)) return false;//missed bbox + return math::LevelSetHDDA::test(mTester); + } + + /// @brief Return @c true if the index-space ray intersects the level set + /// @param iRay ray represented in index space. + /// @param iTime if an intersection was found it is assigned the time of the + /// intersection along the index ray. + bool intersectsIS(const RayType& iRay, Real &iTime) const + { + if (!mTester.setIndexRay(iRay)) return false;//missed bbox + iTime = mTester.getIndexTime(); + return math::LevelSetHDDA::test(mTester); + } + + /// @brief Return @c true if the index-space ray intersects the level set. + /// @param iRay ray represented in index space. + /// @param xyz if an intersection was found it is assigned the + /// intersection point in index space, otherwise it is unchanged. + bool intersectsIS(const RayType& iRay, Vec3Type& xyz) const + { + if (!mTester.setIndexRay(iRay)) return false;//missed bbox + if (!math::LevelSetHDDA::test(mTester)) return false;//missed level set + mTester.getIndexPos(xyz); + return true; + } + + /// @brief Return @c true if the index-space ray intersects the level set. + /// @param iRay ray represented in index space. + /// @param xyz if an intersection was found it is assigned the + /// intersection point in index space, otherwise it is unchanged. + /// @param iTime if an intersection was found it is assigned the time of the + /// intersection along the index ray. + bool intersectsIS(const RayType& iRay, Vec3Type& xyz, Real &iTime) const + { + if (!mTester.setIndexRay(iRay)) return false;//missed bbox + if (!math::LevelSetHDDA::test(mTester)) return false;//missed level set + mTester.getIndexPos(xyz); + iTime = mTester.getIndexTime(); + return true; + } + + /// @brief Return @c true if the world-space ray intersects the level set. + /// @param wRay ray represented in world space. + bool intersectsWS(const RayType& wRay) const + { + if (!mTester.setWorldRay(wRay)) return false;//missed bbox + return math::LevelSetHDDA::test(mTester); + } + + /// @brief Return @c true if the world-space ray intersects the level set. + /// @param wRay ray represented in world space. + /// @param wTime if an intersection was found it is assigned the time of the + /// intersection along the world ray. + bool intersectsWS(const RayType& wRay, Real &wTime) const + { + if (!mTester.setWorldRay(wRay)) return false;//missed bbox + wTime = mTester.getWorldTime(); + return math::LevelSetHDDA::test(mTester); + } + + /// @brief Return @c true if the world-space ray intersects the level set. + /// @param wRay ray represented in world space. + /// @param world if an intersection was found it is assigned the + /// intersection point in world space, otherwise it is unchanged + bool intersectsWS(const RayType& wRay, Vec3Type& world) const + { + if (!mTester.setWorldRay(wRay)) return false;//missed bbox + if (!math::LevelSetHDDA::test(mTester)) return false;//missed level set + mTester.getWorldPos(world); + return true; + } + + /// @brief Return @c true if the world-space ray intersects the level set. + /// @param wRay ray represented in world space. + /// @param world if an intersection was found it is assigned the + /// intersection point in world space, otherwise it is unchanged. + /// @param wTime if an intersection was found it is assigned the time of the + /// intersection along the world ray. + bool intersectsWS(const RayType& wRay, Vec3Type& world, Real &wTime) const + { + if (!mTester.setWorldRay(wRay)) return false;//missed bbox + if (!math::LevelSetHDDA::test(mTester)) return false;//missed level set + mTester.getWorldPos(world); + wTime = mTester.getWorldTime(); + return true; + } + + /// @brief Return @c true if the world-space ray intersects the level set. + /// @param wRay ray represented in world space. + /// @param world if an intersection was found it is assigned the + /// intersection point in world space, otherwise it is unchanged. + /// @param normal if an intersection was found it is assigned the normal + /// of the level set surface in world space, otherwise it is unchanged. + bool intersectsWS(const RayType& wRay, Vec3Type& world, Vec3Type& normal) const + { + if (!mTester.setWorldRay(wRay)) return false;//missed bbox + if (!math::LevelSetHDDA::test(mTester)) return false;//missed level set + mTester.getWorldPosAndNml(world, normal); + return true; + } + + /// @brief Return @c true if the world-space ray intersects the level set. + /// @param wRay ray represented in world space. + /// @param world if an intersection was found it is assigned the + /// intersection point in world space, otherwise it is unchanged. + /// @param normal if an intersection was found it is assigned the normal + /// of the level set surface in world space, otherwise it is unchanged. + /// @param wTime if an intersection was found it is assigned the time of the + /// intersection along the world ray. + bool intersectsWS(const RayType& wRay, Vec3Type& world, Vec3Type& normal, Real &wTime) const + { + if (!mTester.setWorldRay(wRay)) return false;//missed bbox + if (!math::LevelSetHDDA::test(mTester)) return false;//missed level set + mTester.getWorldPosAndNml(world, normal); + wTime = mTester.getWorldTime(); + return true; + } + +private: + + mutable SearchImplT mTester; + +};// LevelSetRayIntersector + + +////////////////////////////////////// VolumeRayIntersector ////////////////////////////////////// + + +/// @brief This class provides the public API for intersecting a ray +/// with a generic (e.g. density) volume. +/// @details Internally it performs the actual hierarchical tree node traversal. +/// @warning Use the (default) copy-constructor to make sure each +/// computational thread has their own instance of this class. This is +/// important since it contains a ValueAccessor that is +/// not thread-safe and a CoordBBox of the active voxels that should +/// not be re-computed for each thread. However copying is very efficient. +/// @par Example: +/// @code +/// // Create an instance for the master thread +/// VolumeRayIntersector inter(grid); +/// // For each additional thread use the copy contructor. This +/// // amortizes the overhead of computing the bbox of the active voxels! +/// VolumeRayIntersector inter2(inter); +/// // Before each ray-traversal set the index ray. +/// iter.setIndexRay(ray); +/// // or world ray +/// iter.setWorldRay(ray); +/// // Now you can begin the ray-marching using consecutive calls to VolumeRayIntersector::march +/// double t0=0, t1=0;// note the entry and exit times are with respect to the INDEX ray +/// while ( inter.march(t0, t1) ) { +/// // perform line-integration between t0 and t1 +/// }} +/// @endcode +template > +class VolumeRayIntersector +{ +public: + typedef GridT GridType; + typedef RayT RayType; + typedef typename RayT::RealType RealType; + typedef typename GridT::TreeType::RootNodeType RootType; + typedef tree::Tree::Type> TreeT; + + BOOST_STATIC_ASSERT( NodeLevel >= 0 && NodeLevel < int(TreeT::DEPTH)-1); + + /// @brief Grid constructor + /// @param grid Generic grid to intersect rays against. + /// @param dilationCount The number of voxel dilations performed + /// on (a boolean copy of) the input grid. This allows the + /// intersector to account for the size of interpolation kernels + /// in client code. + /// @throw RuntimeError if the voxels of the grid are not uniform + /// or the grid is empty. + VolumeRayIntersector(const GridT& grid, int dilationCount = 0) + : mIsMaster(true) + , mTree(new TreeT(grid.tree(), false, TopologyCopy())) + , mGrid(&grid) + , mAccessor(*mTree) + { + if (!grid.hasUniformVoxels() ) { + OPENVDB_THROW(RuntimeError, + "VolumeRayIntersector only supports uniform voxels!"); + } + if ( grid.empty() ) { + OPENVDB_THROW(RuntimeError, "LinearSearchImpl does not supports empty grids"); + } + + // Dilate active voxels to better account for the size of interpolation kernels + tools::dilateVoxels(*mTree, dilationCount); + + mTree->root().evalActiveBoundingBox(mBBox, /*visit individual voxels*/false); + + mBBox.max().offset(1);//padding so the bbox of a node becomes (origin,origin + node_dim) + } + + /// @brief Grid and BBox constructor + /// @param grid Generic grid to intersect rays against. + /// @param bbox The axis-aligned bounding-box in the index space of the grid. + /// @warning It is assumed that bbox = (min, min + dim) where min denotes + /// to the smallest grid coordinates and dim are the integer length of the bbox. + /// @throw RuntimeError if the voxels of the grid are not uniform + /// or the grid is empty. + VolumeRayIntersector(const GridT& grid, const math::CoordBBox& bbox) + : mIsMaster(true) + , mTree(new TreeT(grid.tree(), false, TopologyCopy())) + , mGrid(&grid) + , mAccessor(*mTree) + , mBBox(bbox) + { + if (!grid.hasUniformVoxels() ) { + OPENVDB_THROW(RuntimeError, + "VolumeRayIntersector only supports uniform voxels!"); + } + if ( grid.empty() ) { + OPENVDB_THROW(RuntimeError, "LinearSearchImpl does not supports empty grids"); + } + } + + /// @brief Shallow copy constructor + /// @warning This copy constructor creates shallow copies of data + /// members of the instance passed as the argument. For + /// performance reasons we are not using shared pointers (their + /// mutex-lock impairs multi-threading). + VolumeRayIntersector(const VolumeRayIntersector& other) + : mIsMaster(false) + , mTree(other.mTree)//shallow copy + , mGrid(other.mGrid)//shallow copy + , mAccessor(*mTree)//deep copy + , mRay(other.mRay)//deep copy + , mTmax(other.mTmax)//deep copy + , mBBox(other.mBBox)//deep copy + { + } + + /// @brief Destructor + ~VolumeRayIntersector() { if (mIsMaster) delete mTree; } + + /// @brief Return @c false if the index ray misses the bbox of the grid. + /// @param iRay Ray represented in index space. + /// @warning Call this method (or setWorldRay) before the ray + /// traversal starts and use the return value to decide if further + /// marching is required. + inline bool setIndexRay(const RayT& iRay) + { + mRay = iRay; + const bool hit = mRay.clip(mBBox); + if (hit) mTmax = mRay.t1(); + return hit; + } + + /// @brief Return @c false if the world ray misses the bbox of the grid. + /// @param wRay Ray represented in world space. + /// @warning Call this method (or setIndexRay) before the ray + /// traversal starts and use the return value to decide if further + /// marching is required. + /// @details Since hit times are computed with repect to the ray + /// represented in index space of the current grid, it is + /// recommended that either the client code uses getIndexPos to + /// compute index position from hit times or alternatively keeps + /// an instance of the index ray and instead uses setIndexRay to + /// initialize the ray. + inline bool setWorldRay(const RayT& wRay) + { + return this->setIndexRay(wRay.worldToIndex(*mGrid)); + } + + inline typename RayT::TimeSpan march() + { + const typename RayT::TimeSpan t = mHDDA.march(mRay, mAccessor); + if (t.t1>0) mRay.setTimes(t.t1 + math::Delta::value(), mTmax); + return t; + } + + /// @brief Return @c true if the ray intersects active values, + /// i.e. either active voxels or tiles. Only when a hit is + /// detected are t0 and t1 updated with the corresponding entry + /// and exit times along the INDEX ray! + /// @param t0 If the return value > 0 this is the time of the + /// first hit of an active tile or leaf. + /// @param t1 If the return value > t0 this is the time of the + /// first hit (> t0) of an inactive tile or exit point of the + /// BBOX for the leaf nodes. + /// @warning t0 and t1 are computed with repect to the ray represented in + /// index space of the current grid, not world space! + inline bool march(Real& t0, Real& t1) + { + const typename RayT::TimeSpan t = this->march(); + t.get(t0, t1); + return t.valid(); + } + + inline void hits(std::vector& list) + { + mHDDA.hits(mRay, mAccessor, list); + } + + /// @brief Return the floating-point index position along the + /// current index ray at the specified time. + inline Vec3R getIndexPos(Real time) const { return mRay(time); } + + /// @brief Return the floating-point world position along the + /// current index ray at the specified time. + inline Vec3R getWorldPos(Real time) const { return mGrid->indexToWorld(mRay(time)); } + + inline Real getWorldTime(Real time) const + { + return time*mGrid->transform().baseMap()->applyJacobian(mRay.dir()).length(); + } + + /// @brief Return a const reference to the input grid. + const GridT& grid() const { return *mGrid; } + + /// @brief Return a const reference to the (potentially dilated) + /// bool tree used to accelerate the ray marching. + const TreeT& tree() const { return *mTree; } + + /// @brief Return a const reference to the BBOX of the grid + const math::CoordBBox& bbox() const { return mBBox; } + + /// @brief Print bbox, statistics, memory usage and other information. + /// @param os a stream to which to write textual information + /// @param verboseLevel 1: print bbox only; 2: include boolean tree + /// statistics; 3: include memory usage + void print(std::ostream& os = std::cout, int verboseLevel = 1) + { + if (verboseLevel>0) { + os << "BBox: " << mBBox << std::endl; + if (verboseLevel==2) { + mTree->print(os, 1); + } else if (verboseLevel>2) { + mTree->print(os, 2); + } + } + } + +private: + + typedef typename tree::ValueAccessor AccessorT; + + const bool mIsMaster; + TreeT* mTree; + const GridT* mGrid; + AccessorT mAccessor; + RayT mRay; + Real mTmax; + math::CoordBBox mBBox; + math::VolumeHDDA mHDDA; + +};// VolumeRayIntersector + + +//////////////////////////////////////// LinearSearchImpl //////////////////////////////////////// + + +/// @brief Implements linear iterative search for an iso-value of +/// the level set along along the direction of the ray. +/// +/// @note Since this class is used internally in +/// LevelSetRayIntersector (define above) and LevelSetHDDA (defined below) +/// client code should never interact directly with its API. This also +/// explains why we are not concerned with the fact that several of +/// its methods are unsafe to call unless roots were already detected. +/// +/// @details It is approximate due to the limited number of iterations +/// which can can be defined with a template parameter. However the default value +/// has proven surprisingly accurate and fast. In fact more iterations +/// are not guaranteed to give significantly better results. +/// +/// @warning Since the root-searching algorithm is approximate +/// (first-order) it is possible to miss intersections if the +/// iso-value is too close to the inside or outside of the narrow +/// band (typically a distance less then a voxel unit). +/// +/// @warning Since this class internally stores a ValueAccessor it is NOT thread-safe, +/// so make sure to give each thread its own instance. This of course also means that +/// the cost of allocating an instance should (if possible) be amortized over +/// as many ray intersections as possible. +template +class LinearSearchImpl +{ +public: + typedef math::Ray RayT; + typedef typename GridT::ValueType ValueT; + typedef typename GridT::ConstAccessor AccessorT; + typedef math::BoxStencil StencilT; + typedef typename StencilT::Vec3Type Vec3T; + + /// @brief Constructor from a grid. + /// @throw RunTimeError if the grid is empty. + /// @throw ValueError if the isoValue is not inside the narrow-band. + LinearSearchImpl(const GridT& grid, const ValueT& isoValue = zeroVal()) + : mStencil(grid), + mIsoValue(isoValue), + mMinValue(isoValue-2*grid.voxelSize()[0]), + mMaxValue(isoValue+2*grid.voxelSize()[0]) + { + if ( grid.empty() ) { + OPENVDB_THROW(RuntimeError, "LinearSearchImpl does not supports empty grids"); + } + if (mIsoValue<= -grid.background() || + mIsoValue>= grid.background() ){ + OPENVDB_THROW(ValueError, "The iso-value must be inside the narrow-band!"); + } + grid.tree().root().evalActiveBoundingBox(mBBox, /*visit individual voxels*/false); + } + + /// @brief Return the iso-value used for ray-intersections + const ValueT& getIsoValue() const { return mIsoValue; } + + /// @brief Return @c false the ray misses the bbox of the grid. + /// @param iRay Ray represented in index space. + /// @warning Call this method before the ray traversal starts. + inline bool setIndexRay(const RayT& iRay) + { + mRay = iRay; + return mRay.clip(mBBox);//did it hit the bbox + } + + /// @brief Return @c false the ray misses the bbox of the grid. + /// @param wRay Ray represented in world space. + /// @warning Call this method before the ray traversal starts. + inline bool setWorldRay(const RayT& wRay) + { + mRay = wRay.worldToIndex(mStencil.grid()); + return mRay.clip(mBBox);//did it hit the bbox + } + + /// @brief Get the intersection point in index space. + /// @param xyz The position in index space of the intersection. + inline void getIndexPos(Vec3d& xyz) const { xyz = mRay(mTime); } + + /// @brief Get the intersection point in world space. + /// @param xyz The position in world space of the intersection. + inline void getWorldPos(Vec3d& xyz) const { xyz = mStencil.grid().indexToWorld(mRay(mTime)); } + + /// @brief Get the intersection point and normal in world space + /// @param xyz The position in world space of the intersection. + /// @param nml The surface normal in world space of the intersection. + inline void getWorldPosAndNml(Vec3d& xyz, Vec3d& nml) + { + this->getIndexPos(xyz); + mStencil.moveTo(xyz); + nml = mStencil.gradient(xyz); + nml.normalize(); + xyz = mStencil.grid().indexToWorld(xyz); + } + + /// @brief Return the time of intersection along the index ray. + inline RealT getIndexTime() const { return mTime; } + + /// @brief Return the time of intersection along the world ray. + inline RealT getWorldTime() const + { + return mTime*mStencil.grid().transform().baseMap()->applyJacobian(mRay.dir()).length(); + } + +private: + + /// @brief Initiate the local voxel intersection test. + /// @warning Make sure to call this method before the local voxel intersection test. + inline void init(RealT t0) + { + mT[0] = t0; + mV[0] = this->interpValue(t0); + } + + inline void setRange(RealT t0, RealT t1) { mRay.setTimes(t0, t1); } + + /// @brief Return a const reference to the ray. + inline const RayT& ray() const { return mRay; } + + /// @brief Return true if a node of the the specified type exists at ijk. + template + inline bool hasNode(const Coord& ijk) + { + return mStencil.accessor().template probeConstNode(ijk) != NULL; + } + + /// @brief Return @c true if an intersection is detected. + /// @param ijk Grid coordinate of the node origin or voxel being tested. + /// @param time Time along the index ray being tested. + /// @warning Only if and intersection is detected is it safe to + /// call getIndexPos, getWorldPos and getWorldPosAndNml! + inline bool operator()(const Coord& ijk, RealT time) + { + ValueT V; + if (mStencil.accessor().probeValue(ijk, V) &&//within narrow band + V>mMinValue && VinterpValue(time); + if (math::ZeroCrossing(mV[0], mV[1])) { + mTime = this->interpTime(); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + for (int n=0; Iterations>0 && ninterpValue(mTime); + const int m = math::ZeroCrossing(mV[0], V) ? 1 : 0; + mV[m] = V; + mT[m] = mTime; + mTime = this->interpTime(); + } + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + return true; + } + mT[0] = mT[1]; + mV[0] = mV[1]; + } + return false; + } + + inline RealT interpTime() + { + assert(math::isApproxLarger(mT[1], mT[0], 1e-6)); + return mT[0]+(mT[1]-mT[0])*mV[0]/(mV[0]-mV[1]); + } + + inline RealT interpValue(RealT time) + { + const Vec3R pos = mRay(time); + mStencil.moveTo(pos); + return mStencil.interpolation(pos) - mIsoValue; + } + + template friend struct math::LevelSetHDDA; + + RayT mRay; + StencilT mStencil; + RealT mTime;//time of intersection + ValueT mV[2]; + RealT mT[2]; + const ValueT mIsoValue, mMinValue, mMaxValue; + math::CoordBBox mBBox; +};// LinearSearchImpl + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_RAYINTERSECTOR_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/RayTracer.h b/openvdb_2_3_0_library/openvdb/tools/RayTracer.h new file mode 100755 index 0000000..6d06d33 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/RayTracer.h @@ -0,0 +1,977 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +/// +/// @file RayTracer.h +/// +/// @author Ken Museth +/// +/// @brief Defines two simple but multithreaded renders, a level-set +/// ray tracer and a volume render. To support these renders we also define +/// perspective and orthographic cameras (both designed to mimic a Houdini camera), +/// a Film class and some rather naive shaders. +/// +/// @note These classes are included mainly as reference implementations for +/// ray-tracing of OpenVDB volumes. In other words they are not intended for +/// production-quality rendering, but could be used for fast pre-visualiztion +/// or as a startingpoint for a more serious render. + +#ifndef OPENVDB_TOOLS_RAYTRACER_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_RAYTRACER_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef OPENVDB_TOOLS_RAYTRACER_USE_EXR +#include +#include +#include +#include +#include +#endif + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +// Forward declarations +class BaseCamera; +class BaseShader; + +/// @brief Ray-trace a volume. +template +inline void rayTrace(const GridT&, + const BaseShader&, + BaseCamera&, + size_t pixelSamples = 1, + unsigned int seed = 0, + bool threaded = true); + +/// @brief Ray-trace a volume using a given ray intersector. +template +inline void rayTrace(const GridT&, + const IntersectorT&, + const BaseShader&, + BaseCamera&, + size_t pixelSamples = 1, + unsigned int seed = 0, + bool threaded = true); + + +///////////////////////////////LEVEL SET RAY TRACER /////////////////////////////////////// + +/// @brief A (very) simple multithreaded ray tracer specifically for narrow-band level sets. +/// @details Included primarily as a reference implementation. +template > +class LevelSetRayTracer +{ +public: + typedef GridT GridType; + typedef typename IntersectorT::Vec3Type Vec3Type; + typedef typename IntersectorT::RayType RayType; + + /// @brief Constructor based on an instance of the grid to be rendered. + LevelSetRayTracer(const GridT& grid, + const BaseShader& shader, + BaseCamera& camera, + size_t pixelSamples = 1, + unsigned int seed = 0); + + /// @brief Constructor based on an instance of the intersector + /// performing the ray-intersections. + LevelSetRayTracer(const IntersectorT& inter, + const BaseShader& shader, + BaseCamera& camera, + size_t pixelSamples = 1, + unsigned int seed = 0); + + /// @brief Copy constructor + LevelSetRayTracer(const LevelSetRayTracer& other); + + /// @brief Destructor + ~LevelSetRayTracer(); + + /// @brief Set the level set grid to be ray-traced + void setGrid(const GridT& grid); + + /// @brief Set the intersector that performs the actual + /// intersection of the rays against the narrow-band level set. + void setIntersector(const IntersectorT& inter); + + /// @brief Set the shader derived from the abstract BaseShader class. + /// + /// @note The shader is not assumed to be thread-safe so each + /// thread will get it's only deep copy. For instance it could + /// contains a ValueAccessor into another grid with auxiliary + /// shading information. Thus, make sure it is relatively + /// light-weight and efficient to copy (which is the case for ValueAccesors). + void setShader(const BaseShader& shader); + + /// @brief Set the camera derived from the abstract BaseCamera class. + void setCamera(BaseCamera& camera); + + /// @brief Set the number of pixel samples and the seed for + /// jittered sub-rays. A value larger then one implies + /// anti-aliasing by jittered super-sampling. + /// @throw ValueError if pixelSamples is equal to zero. + void setPixelSamples(size_t pixelSamples, unsigned int seed = 0); + + /// @brief Perform the actual (potentially multithreaded) ray-tracing. + void render(bool threaded = true) const; + + /// @brief Public method required by tbb::parallel_for. + /// @warning Never call it directly. + void operator()(const tbb::blocked_range& range) const; + +private: + const bool mIsMaster; + double* mRand; + IntersectorT mInter; + boost::scoped_ptr mShader; + BaseCamera* mCamera; + size_t mSubPixels; +};// LevelSetRayTracer + + +///////////////////////////////VOLUME RENDER /////////////////////////////////////// + +/// @brief A (very) simple multithreaded volume render specifically for scalar density. +/// @details Included primarily as a reference implementation. +/// @note It will only compile if the IntersectorT is templated on a Grid with a +/// floating-point voxel type. +template +class VolumeRender +{ +public: + + typedef typename IntersectorT::GridType GridType; + typedef typename IntersectorT::RayType RayType; + typedef typename GridType::ValueType ValueType; + typedef typename GridType::ConstAccessor AccessorType; + typedef tools::GridSampler SamplerType; + BOOST_STATIC_ASSERT(boost::is_floating_point::value); + + /// @brief Constructor taking an intersector and a base camera. + VolumeRender(const IntersectorT& inter, BaseCamera& camera); + + /// @brief Copy constructor which creates a thread-safe clone + VolumeRender(const VolumeRender& other); + + /// @brief Perform the actual (potentially multithreaded) volume rendering. + void render(bool threaded=true) const; + + /// @brief Set the camera derived from the abstract BaseCamera class. + void setCamera(BaseCamera& camera) { mCamera = &camera; } + + /// @brief Set the intersector that performs the actual + /// intersection of the rays against the volume. + void setIntersector(const IntersectorT& inter); + + /// @brief Set the vector components of a directional light source + /// @throw ArithmeticError if input is a null vector. + void setLightDir(Real x, Real y, Real z) { mLightDir = Vec3R(x,y,z).unit(); } + + /// @brief Set the color of the direcitonal light source. + void setLightColor(Real r, Real g, Real b) { mLightColor = Vec3R(r,g,b); } + + /// @brief Set the integration step-size in voxel units for the primay ray. + void setPrimaryStep(Real primaryStep) { mPrimaryStep = primaryStep; } + + /// @brief Set the integration step-size in voxel units for the primay ray. + void setShadowStep(Real shadowStep) { mShadowStep = shadowStep; } + + /// @brief Set Scattering coefficients. + void setScattering(Real x, Real y, Real z) { mScattering = Vec3R(x,y,z); } + + /// @brief Set absorption coefficients. + void setAbsorption(Real x, Real y, Real z) { mAbsorption = Vec3R(x,y,z); } + + /// @brief Set parameter that imitates multi-scattering. A value + /// of zero implies no multi-scattering. + void setLightGain(Real gain) { mLightGain = gain; } + + /// @brief Set the cut-off value for density and transmittance. + void setCutOff(Real cutOff) { mCutOff = cutOff; } + + /// @brief Print parameters, statistics, memory usage and other information. + /// @param os a stream to which to write textual information + /// @param verboseLevel 1: print parameters only; 2: include grid + /// statistics; 3: include memory usage + void print(std::ostream& os = std::cout, int verboseLevel = 1); + + /// @brief Public method required by tbb::parallel_for. + /// @warning Never call it directly. + void operator()(const tbb::blocked_range& range) const; + +private: + + AccessorType mAccessor; + BaseCamera* mCamera; + boost::scoped_ptr mPrimary, mShadow; + Real mPrimaryStep, mShadowStep, mCutOff, mLightGain; + Vec3R mLightDir, mLightColor, mAbsorption, mScattering; +};//VolumeRender + +//////////////////////////////////////// FILM //////////////////////////////////////// + +/// @brief A simple class that allows for concurrent writes to pixels in an image, +/// background initialization of the image, and PPM or EXR file output. +class Film +{ +public: + /// @brief Floating-point RGBA components in the range [0, 1]. + /// @details This is our preferred representation for color processing. + struct RGBA + { + typedef float ValueT; + + RGBA() : r(0), g(0), b(0), a(1) {} + explicit RGBA(ValueT intensity) : r(intensity), g(intensity), b(intensity), a(1) {} + RGBA(ValueT _r, ValueT _g, ValueT _b, ValueT _a=1.0f) : r(_r), g(_g), b(_b), a(_a) {} + + RGBA operator* (ValueT scale) const { return RGBA(r*scale, g*scale, b*scale);} + RGBA operator+ (const RGBA& rhs) const { return RGBA(r+rhs.r, g+rhs.g, b+rhs.b);} + RGBA operator* (const RGBA& rhs) const { return RGBA(r*rhs.r, g*rhs.g, b*rhs.b);} + RGBA& operator+=(const RGBA& rhs) { r+=rhs.r; g+=rhs.g; b+=rhs.b, a+=rhs.a; return *this;} + + void over(const RGBA& rhs) + { + const float s = rhs.a*(1.0f-a); + r = a*r+s*rhs.r; + g = a*g+s*rhs.g; + b = a*b+s*rhs.b; + a = a + s; + } + + ValueT r, g, b, a; + }; + + + Film(size_t width, size_t height) + : mWidth(width), mHeight(height), mSize(width*height), mPixels(new RGBA[mSize]) + { + } + Film(size_t width, size_t height, const RGBA& bg) + : mWidth(width), mHeight(height), mSize(width*height), mPixels(new RGBA[mSize]) + { + this->fill(bg); + } + + const RGBA& pixel(size_t w, size_t h) const + { + assert(w < mWidth); + assert(h < mHeight); + return mPixels[w + h*mWidth]; + } + + RGBA& pixel(size_t w, size_t h) + { + assert(w < mWidth); + assert(h < mHeight); + return mPixels[w + h*mWidth]; + } + + void fill(const RGBA& rgb=RGBA(0)) { for (size_t i=0; i buffer(new unsigned char[3*mSize]); + unsigned char *tmp = buffer.get(), *q = tmp; + RGBA* p = mPixels.get(); + size_t n = mSize; + while (n--) { + *q++ = static_cast(255.0f*(*p ).r); + *q++ = static_cast(255.0f*(*p ).g); + *q++ = static_cast(255.0f*(*p++).b); + } + + std::ofstream os(name.c_str(), std::ios_base::binary); + if (!os.is_open()) { + std::cerr << "Error opening PPM file \"" << name << "\"" << std::endl; + return; + } + + os << "P6\n" << mWidth << " " << mHeight << "\n255\n"; + os.write((const char *)&(*tmp), 3*mSize*sizeof(unsigned char)); + } + +#ifdef OPENVDB_TOOLS_RAYTRACER_USE_EXR + void saveEXR(const std::string& fileName, size_t compression = 2, size_t threads = 8) + { + std::string name(fileName + ".exr"); + + if (threads>0) Imf::setGlobalThreadCount(threads); + Imf::Header header(mWidth, mHeight); + if (compression==0) header.compression() = Imf::NO_COMPRESSION; + if (compression==1) header.compression() = Imf::RLE_COMPRESSION; + if (compression>=2) header.compression() = Imf::ZIP_COMPRESSION; + header.channels().insert("R", Imf::Channel(Imf::FLOAT)); + header.channels().insert("G", Imf::Channel(Imf::FLOAT)); + header.channels().insert("B", Imf::Channel(Imf::FLOAT)); + header.channels().insert("A", Imf::Channel(Imf::FLOAT)); + + Imf::FrameBuffer framebuffer; + framebuffer.insert("R", Imf::Slice( Imf::FLOAT, (char *) &(mPixels[0].r), + sizeof (RGBA), sizeof (RGBA) * mWidth)); + framebuffer.insert("G", Imf::Slice( Imf::FLOAT, (char *) &(mPixels[0].g), + sizeof (RGBA), sizeof (RGBA) * mWidth)); + framebuffer.insert("B", Imf::Slice( Imf::FLOAT, (char *) &(mPixels[0].b), + sizeof (RGBA), sizeof (RGBA) * mWidth)); + framebuffer.insert("A", Imf::Slice( Imf::FLOAT, (char *) &(mPixels[0].a), + sizeof (RGBA), sizeof (RGBA) * mWidth)); + + Imf::OutputFile file(name.c_str(), header); + file.setFrameBuffer(framebuffer); + file.writePixels(mHeight); + } +#endif + + size_t width() const { return mWidth; } + size_t height() const { return mHeight; } + size_t numPixels() const { return mSize; } + const RGBA* pixels() const { return mPixels.get(); } + +private: + size_t mWidth, mHeight, mSize; + boost::scoped_array mPixels; +};// Film + + +//////////////////////////////////////// CAMERAS //////////////////////////////////////// + +/// Abstract base class for the perspective and orthographic cameras +class BaseCamera +{ +public: + BaseCamera(Film& film, const Vec3R& rotation, const Vec3R& translation, + double frameWidth, double nearPlane, double farPlane) + : mFilm(&film) + , mScaleWidth(frameWidth) + , mScaleHeight(frameWidth*film.height()/double(film.width())) + { + assert(nearPlane > 0 && farPlane > nearPlane); + mScreenToWorld.accumPostRotation(math::X_AXIS, rotation[0] * M_PI / 180.0); + mScreenToWorld.accumPostRotation(math::Y_AXIS, rotation[1] * M_PI / 180.0); + mScreenToWorld.accumPostRotation(math::Z_AXIS, rotation[2] * M_PI / 180.0); + mScreenToWorld.accumPostTranslation(translation); + this->initRay(nearPlane, farPlane); + } + + virtual ~BaseCamera() {} + + Film::RGBA& pixel(size_t i, size_t j) { return mFilm->pixel(i, j); } + + size_t width() const { return mFilm->width(); } + size_t height() const { return mFilm->height(); } + + /// Rotate the camera so its negative z-axis points at xyz and its + /// y axis is in the plane of the xyz and up vectors. In other + /// words the camera will look at xyz and use up as the + /// horizontal direction. + void lookAt(const Vec3R& xyz, const Vec3R& up = Vec3R(0.0, 1.0, 0.0)) + { + const Vec3R orig = mScreenToWorld.applyMap(Vec3R(0.0)); + const Vec3R dir = orig - xyz; + try { + Mat4d xform = math::aim(dir, up); + xform.postTranslate(orig); + mScreenToWorld = math::AffineMap(xform); + this->initRay(mRay.t0(), mRay.t1()); + } catch (...) {} + } + + Vec3R rasterToScreen(double i, double j, double z) const + { + return Vec3R( (2 * i / mFilm->width() - 1) * mScaleWidth, + (1 - 2 * j / mFilm->height()) * mScaleHeight, z ); + } + + /// @brief Return a Ray in world space given the pixel indices and + /// optional offsets in the range [0, 1]. An offset of 0.5 corresponds + /// to the center of the pixel. + virtual math::Ray getRay( + size_t i, size_t j, double iOffset = 0.5, double jOffset = 0.5) const = 0; + +protected: + void initRay(double t0, double t1) + { + mRay.setTimes(t0, t1); + mRay.setEye(mScreenToWorld.applyMap(Vec3R(0.0))); + mRay.setDir(mScreenToWorld.applyJacobian(Vec3R(0.0, 0.0, -1.0))); + } + + Film* mFilm; + double mScaleWidth, mScaleHeight; + math::Ray mRay; + math::AffineMap mScreenToWorld; +};// BaseCamera + + +class PerspectiveCamera: public BaseCamera +{ + public: + /// @brief Constructor + /// @param film film (i.e. image) defining the pixel resolution + /// @param rotation rotation in degrees of the camera in world space + /// (applied in x, y, z order) + /// @param translation translation of the camera in world-space units, + /// applied after rotation + /// @param focalLength focal length of the camera in mm + /// (the default of 50mm corresponds to Houdini's default camera) + /// @param aperture width in mm of the frame, i.e., the visible field + /// (the default 41.2136 mm corresponds to Houdini's default camera) + /// @param nearPlane depth of the near clipping plane in world-space units + /// @param farPlane depth of the far clipping plane in world-space units + /// + /// @details If no rotation or translation is provided, the camera is placed + /// at (0,0,0) in world space and points in the direction of the negative z axis. + PerspectiveCamera(Film& film, + const Vec3R& rotation = Vec3R(0.0), + const Vec3R& translation = Vec3R(0.0), + double focalLength = 50.0, + double aperture = 41.2136, + double nearPlane = 1e-3, + double farPlane = std::numeric_limits::max()) + : BaseCamera(film, rotation, translation, 0.5*aperture/focalLength, nearPlane, farPlane) + { + } + + virtual ~PerspectiveCamera() {} + + /// @brief Return a Ray in world space given the pixel indices and + /// optional offsets in the range [0,1]. An offset of 0.5 corresponds + /// to the center of the pixel. + virtual math::Ray getRay( + size_t i, size_t j, double iOffset = 0.5, double jOffset = 0.5) const + { + math::Ray ray(mRay); + Vec3R dir = BaseCamera::rasterToScreen(i + iOffset, j + jOffset, -1.0); + dir = BaseCamera::mScreenToWorld.applyJacobian(dir); + dir.normalize(); + ray.scaleTimes(1.0/dir.dot(ray.dir())); + ray.setDir(dir); + return ray; + } + + /// @brief Return the horizontal field of view in degrees given a + /// focal lenth in mm and the specified aperture in mm. + static double focalLengthToFieldOfView(double length, double aperture) + { + return 360.0 / M_PI * atan(aperture/(2.0*length)); + } + /// @brief Return the focal length in mm given a horizontal field of + /// view in degrees and the specified aperture in mm. + static double fieldOfViewToFocalLength(double fov, double aperture) + { + return aperture/(2.0*(tan(fov * M_PI / 360.0))); + } +};// PerspectiveCamera + + +class OrthographicCamera: public BaseCamera +{ +public: + /// @brief Constructor + /// @param film film (i.e. image) defining the pixel resolution + /// @param rotation rotation in degrees of the camera in world space + /// (applied in x, y, z order) + /// @param translation translation of the camera in world-space units, + /// applied after rotation + /// @param frameWidth width in of the frame in world-space units + /// @param nearPlane depth of the near clipping plane in world-space units + /// @param farPlane depth of the far clipping plane in world-space units + /// + /// @details If no rotation or translation is provided, the camera is placed + /// at (0,0,0) in world space and points in the direction of the negative z axis. + OrthographicCamera(Film& film, + const Vec3R& rotation = Vec3R(0.0), + const Vec3R& translation = Vec3R(0.0), + double frameWidth = 1.0, + double nearPlane = 1e-3, + double farPlane = std::numeric_limits::max()) + : BaseCamera(film, rotation, translation, 0.5*frameWidth, nearPlane, farPlane) + { + } + virtual ~OrthographicCamera() {} + + virtual math::Ray getRay( + size_t i, size_t j, double iOffset = 0.5, double jOffset = 0.5) const + { + math::Ray ray(mRay); + Vec3R eye = BaseCamera::rasterToScreen(i + iOffset, j + jOffset, 0.0); + ray.setEye(BaseCamera::mScreenToWorld.applyMap(eye)); + return ray; + } +};// OrthographicCamera + + +//////////////////////////////////////// SHADERS //////////////////////////////////////// + + +/// Abstract base class for the shaders +class BaseShader +{ +public: + typedef math::Ray RayT; + BaseShader() {} + virtual ~BaseShader() {} + /// @brief Defines the interface of the virtual function that returns a RGB color. + /// @param xyz World position of the intersection point. + /// @param nml Normal in world space at the intersection point. + /// @param dir Direction of the ray in world space. + virtual Film::RGBA operator()(const Vec3R& xyz, const Vec3R& nml, const Vec3R& dir) const = 0; + virtual BaseShader* copy() const = 0; +}; + + +/// Shader that produces a simple matte +class MatteShader: public BaseShader +{ +public: + MatteShader(const Film::RGBA& c = Film::RGBA(1.0f)): mRGBA(c) {} + virtual ~MatteShader() {} + virtual Film::RGBA operator()(const Vec3R&, const Vec3R&, const Vec3R&) const + { + return mRGBA; + } + virtual BaseShader* copy() const { return new MatteShader(*this); } + +private: + const Film::RGBA mRGBA; +}; + + +/// Color shader that treats the surface normal (x, y, z) as an RGB color +class NormalShader: public BaseShader +{ +public: + NormalShader(const Film::RGBA& c = Film::RGBA(1.0f)) : mRGBA(c*0.5f) {} + virtual ~NormalShader() {} + virtual Film::RGBA operator()(const Vec3R&, const Vec3R& normal, const Vec3R&) const + { + return mRGBA*Film::RGBA(normal[0]+1.0f, normal[1]+1.0f, normal[2]+1.0f); + } + virtual BaseShader* copy() const { return new NormalShader(*this); } + +private: + const Film::RGBA mRGBA; +}; + + +/// Color shader that treats position (x, y, z) as an RGB color in a +/// cube defined from an axis-aligned bounding box in world space. +class PositionShader: public BaseShader +{ +public: + PositionShader(const math::BBox& bbox, const Film::RGBA& c = Film::RGBA(1.0f)) + : mMin(bbox.min()), mInvDim(1.0/bbox.extents()), mRGBA(c) {} + virtual ~PositionShader() {} + virtual Film::RGBA operator()(const Vec3R& xyz, const Vec3R&, const Vec3R&) const + { + const Vec3R rgb = (xyz - mMin)*mInvDim; + return mRGBA*Film::RGBA(rgb[0], rgb[1], rgb[2]); + } + virtual BaseShader* copy() const { return new PositionShader(*this); } + + private: + const Vec3R mMin, mInvDim; + const Film::RGBA mRGBA; +}; + +/// @brief Simple diffuse Lambertian surface shader +/// @details Diffuse simply means the color is constant (e.g., white), and +/// Lambertian implies that the (radiant) intensity is directly proportional +/// to the cosine of the angle between the surface normal and the direction +/// of the light source. +class DiffuseShader: public BaseShader +{ +public: + DiffuseShader(const Film::RGBA& d = Film::RGBA(1.0f)): mRGBA(d) {} + virtual Film::RGBA operator()(const Vec3R&, const Vec3R& normal, const Vec3R& rayDir) const + { + // We assume a single directional light source at the camera, + // so the cosine of the angle between the surface normal and the + // direction of the light source becomes the dot product of the + // surface normal and inverse direction of the ray. We also ignore + // negative dot products, corresponding to strict one-sided shading. + //return mRGBA * math::Max(0.0, normal.dot(-rayDir)); + + // We take the abs of the dot product corresponding to having + // light sources at +/- rayDir, i.e., two-sided shading. + return mRGBA * math::Abs(normal.dot(rayDir)); + } + virtual BaseShader* copy() const { return new DiffuseShader(*this); } + +private: + const Film::RGBA mRGBA; +}; + + +//////////////////////////////////////// RAYTRACER //////////////////////////////////////// + +template +inline void rayTrace(const GridT& grid, + const BaseShader& shader, + BaseCamera& camera, + size_t pixelSamples, + unsigned int seed, + bool threaded) +{ + LevelSetRayTracer > + tracer(grid, shader, camera, pixelSamples, seed); + tracer.render(threaded); +} + + +template +inline void rayTrace(const GridT&, + const IntersectorT& inter, + const BaseShader& shader, + BaseCamera& camera, + size_t pixelSamples, + unsigned int seed, + bool threaded) +{ + LevelSetRayTracer tracer(inter, shader, camera, pixelSamples, seed); + tracer.render(threaded); +} + + +//////////////////////////////////////// LevelSetRayTracer //////////////////////////////////////// + + +template +inline LevelSetRayTracer:: +LevelSetRayTracer(const GridT& grid, + const BaseShader& shader, + BaseCamera& camera, + size_t pixelSamples, + unsigned int seed) + : mIsMaster(true), + mRand(NULL), + mInter(grid), + mShader(shader.copy()), + mCamera(&camera) +{ + this->setPixelSamples(pixelSamples, seed); +} + +template +inline LevelSetRayTracer:: +LevelSetRayTracer(const IntersectorT& inter, + const BaseShader& shader, + BaseCamera& camera, + size_t pixelSamples, + unsigned int seed) + : mIsMaster(true), + mRand(NULL), + mInter(inter), + mShader(shader.copy()), + mCamera(&camera) +{ + this->setPixelSamples(pixelSamples, seed); +} + +template +inline LevelSetRayTracer:: +LevelSetRayTracer(const LevelSetRayTracer& other) : + mIsMaster(false), + mRand(other.mRand), + mInter(other.mInter), + mShader(other.mShader->copy()), + mCamera(other.mCamera), + mSubPixels(other.mSubPixels) +{ +} + +template +inline LevelSetRayTracer:: +~LevelSetRayTracer() +{ + if (mIsMaster) delete [] mRand; +} + +template +inline void LevelSetRayTracer:: +setGrid(const GridT& grid) +{ + assert(mIsMaster); + mInter = IntersectorT(grid); +} + +template +inline void LevelSetRayTracer:: +setIntersector(const IntersectorT& inter) +{ + assert(mIsMaster); + mInter = inter; +} + +template +inline void LevelSetRayTracer:: +setShader(const BaseShader& shader) +{ + assert(mIsMaster); + mShader.reset(shader.copy()); +} + +template +inline void LevelSetRayTracer:: +setCamera(BaseCamera& camera) +{ + assert(mIsMaster); + mCamera = &camera; +} + +template +inline void LevelSetRayTracer:: +setPixelSamples(size_t pixelSamples, unsigned int seed) +{ + assert(mIsMaster); + if (pixelSamples == 0) { + OPENVDB_THROW(ValueError, "pixelSamples must be larger then zero!"); + } + mSubPixels = pixelSamples - 1; + delete [] mRand; + if (mSubPixels > 0) { + mRand = new double[16]; + math::Rand01 rand(seed);//offsets for anti-aliaing by jittered super-sampling + for (size_t i=0; i<16; ++i) mRand[i] = rand(); + } else { + mRand = NULL; + } +} + +template +inline void LevelSetRayTracer:: +render(bool threaded) const +{ + tbb::blocked_range range(0, mCamera->height()); + threaded ? tbb::parallel_for(range, *this) : (*this)(range); +} + +template +inline void LevelSetRayTracer:: +operator()(const tbb::blocked_range& range) const +{ + const BaseShader& shader = *mShader; + Vec3Type xyz, nml; + const float frac = 1.0f / (1.0f + mSubPixels); + for (size_t j=range.begin(), n=0, je = range.end(); jwidth(); ipixel(i,j); + RayType ray = mCamera->getRay(i, j);//primary ray + Film::RGBA c = mInter.intersectsWS(ray, xyz, nml) ? shader(xyz, nml, ray.dir()) : bg; + for (size_t k=0; kgetRay(i, j, mRand[n & 15], mRand[(n+1) & 15]); + c += mInter.intersectsWS(ray, xyz, nml) ? shader(xyz, nml, ray.dir()) : bg; + }//loop over sub-pixels + bg = c*frac; + }//loop over image height + }//loop over image width +} + +//////////////////////////////////////// VolumeRender //////////////////////////////////////// + +template +inline VolumeRender:: +VolumeRender(const IntersectorT& inter, BaseCamera& camera) + : mAccessor(inter.grid().getConstAccessor()) + , mCamera(&camera) + , mPrimary(new IntersectorT(inter)) + , mShadow(new IntersectorT(inter)) + , mPrimaryStep(1.0) + , mShadowStep(3.0) + , mCutOff(0.005) + , mLightGain(0.2) + , mLightDir(Vec3R(0.3, 0.3, 0).unit()) + , mLightColor(0.7, 0.7, 0.7) + , mAbsorption(0.1) + , mScattering(1.5) +{ +} + +template +inline VolumeRender:: +VolumeRender(const VolumeRender& other) + : mAccessor(other.mAccessor) + , mCamera(other.mCamera) + , mPrimary(new IntersectorT(*(other.mPrimary))) + , mShadow(new IntersectorT(*(other.mShadow))) + , mPrimaryStep(other.mPrimaryStep) + , mShadowStep(other.mShadowStep) + , mCutOff(other.mCutOff) + , mLightGain(other.mLightGain) + , mLightDir(other.mLightDir) + , mLightColor(other.mLightColor) + , mAbsorption(other.mAbsorption) + , mScattering(other.mScattering) +{ +} + +template +inline void VolumeRender:: +print(std::ostream& os, int verboseLevel) +{ + if (verboseLevel>0) { + os << "\nPrimary step: " << mPrimaryStep + << "\nShadow step: " << mShadowStep + << "\nCutoff: " << mCutOff + << "\nLightGain: " << mLightGain + << "\nLightDir: " << mLightDir + << "\nLightColor: " << mLightColor + << "\nAbsorption: " << mAbsorption + << "\nScattering: " << mScattering << std::endl; + } + mPrimary->print(os, verboseLevel); +} + +template +inline void VolumeRender:: +setIntersector(const IntersectorT& inter) +{ + mPrimary.reset(new IntersectorT(inter)); + mShadow.reset( new IntersectorT(inter)); +} + +template +inline void VolumeRender:: +render(bool threaded) const +{ + tbb::blocked_range range(0, mCamera->height()); + threaded ? tbb::parallel_for(range, *this) : (*this)(range); +} + +template +inline void VolumeRender:: +operator()(const tbb::blocked_range& range) const +{ + SamplerType sampler(mAccessor, mShadow->grid().transform());//light-weight wrapper + + // Any variable prefixed with p (or s) means it's associate with a primay (or shadow) ray + const Vec3R extinction = -mScattering-mAbsorption, One(1.0); + const Vec3R albedo = mLightColor*mScattering/(mScattering+mAbsorption);//single scattering + const Real sGain = mLightGain;//in-scattering along shadow ray + const Real pStep = mPrimaryStep;//Integration step along primary ray in voxel units + const Real sStep = mShadowStep;//Integration step along shadow ray in voxel units + const Real cutoff = mCutOff;//Cutoff for density and transmittance + + // For the sake of completeness we show how to use two different + // methods (hits/march) in VolumeRayIntersector that produce + // segments along the ray that intersects active values. Comment out + // the line below to use VolumeRayIntersector::march instead of + // VolumeRayIntersector::hits. +#define USE_HITS +#ifdef USE_HITS + std::vector pTS, sTS; +#endif + + RayType sRay(Vec3R(0), mLightDir);//Shadow ray + for (size_t j=range.begin(), je = range.end(); jwidth(); ipixel(i, j); + bg.a = bg.r = bg.g = bg.b = 0; + RayType pRay = mCamera->getRay(i, j);// Primary ray + if( !mPrimary->setWorldRay(pRay)) continue; + Vec3R pTrans(1.0), pLumi(0.0); +#ifndef USE_HITS + Real pT0, pT1; + while (mPrimary->march(pT0, pT1)) { + for (Real pT = pStep*ceil(pT0/pStep); pT <= pT1; pT += pStep) { +#else + mPrimary->hits(pTS); + for (size_t k=0; kgetWorldPos(pT); + const Real density = sampler.wsSample(pPos); + if (density < cutoff) continue; + const Vec3R dT = math::Exp(extinction * density * pStep); + Vec3R sTrans(1.0); + sRay.setEye(pPos); + if( !mShadow->setWorldRay(sRay)) continue; +#ifndef USE_HITS + Real sT0, sT1; + while (mShadow->march(sT0, sT1)) { + for (Real sT = sStep*ceil(sT0/sStep); sT <= sT1; sT+= sStep) { +#else + mShadow->hits(sTS); + for (size_t l=0; lgetWorldPos(sT)); + if (d < cutoff) continue; + sTrans *= math::Exp(extinction * d * sStep/(1.0+sT*sGain)); + if (sTrans.lengthSqr() +#include +#include +#include "ValueTransformer.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Iterate over a scalar grid and compute a histogram of the values +/// of the voxels that are visited, or iterate over a vector-valued grid +/// and compute a histogram of the magnitudes of the vectors. +/// @param iter an iterator over the values of a grid or its tree +/// (@c Grid::ValueOnCIter, @c Tree::ValueOffIter, etc.) +/// @param minVal the smallest value that can be added to the histogram +/// @param maxVal the largest value that can be added to the histogram +/// @param numBins the number of histogram bins +/// @param threaded if true, iterate over the grid in parallel +template +inline math::Histogram +histogram(const IterT& iter, double minVal, double maxVal, + size_t numBins = 10, bool threaded = true); + + +/// @brief Iterate over a scalar grid and compute statistics (mean, variance, etc.) +/// of the values of the voxels that are visited, or iterate over a vector-valued grid +/// and compute statistics of the magnitudes of the vectors. +/// @param iter an iterator over the values of a grid or its tree +/// (@c Grid::ValueOnCIter, @c Tree::ValueOffIter, etc.) +/// @param threaded if true, iterate over the grid in parallel +template +inline math::Stats +statistics(const IterT& iter, bool threaded = true); + + +/// @brief Iterate over a grid and compute statistics (mean, variance, etc.) of +/// the values produced by applying the given functor at each voxel that is visited. +/// @param iter an iterator over the values of a grid or its tree +/// (@c Grid::ValueOnCIter, @c Tree::ValueOffIter, etc.) +/// @param op a functor of the form void op(const IterT&, math::Stats&), +/// where @c IterT is the type of @a iter, that inserts zero or more +/// floating-point values into the provided @c math::Stats object +/// @param threaded if true, iterate over the grid in parallel +/// @note When @a threaded is true, each thread gets its own copy of the functor. +/// +/// @par Example: +/// Compute statistics of just the active and positive-valued voxels of a scalar, +/// floating-point grid. +/// @code +/// struct Local { +/// static inline +/// void addIfPositive(const FloatGrid::ValueOnCIter& iter, math::Stats& stats) +/// { +/// const float f = *iter; +/// if (f > 0.0) { +/// if (iter.isVoxelValue()) stats.add(f); +/// else stats.add(f, iter.getVoxelCount()); +/// } +/// } +/// }; +/// FloatGrid grid = ...; +/// math::Stats stats = +/// tools::statistics(grid.cbeginValueOn(), Local::addIfPositive, /*threaded=*/true); +/// @endcode +template +inline math::Stats +statistics(const IterT& iter, const ValueOp& op, bool threaded); + + +/// @brief Iterate over a grid and compute statistics (mean, variance, etc.) +/// of the values produced by applying a given operator (see math/Operators.h) +/// at each voxel that is visited. +/// @param iter an iterator over the values of a grid or its tree +/// (@c Grid::ValueOnCIter, @c Tree::ValueOffIter, etc.) +/// @param op an operator object with a method of the form +/// double result(Accessor&, const Coord&) +/// @param threaded if true, iterate over the grid in parallel +/// @note World-space operators, whose @c result() methods are of the form +/// double result(const Map&, Accessor&, const Coord&), must be wrapped +/// in a math::MapAdapter. +/// @note Vector-valued operators like math::Gradient must be wrapped in an adapter +/// such as math::OpMagnitude. +/// +/// @par Example: +/// Compute statistics of the magnitude of the gradient at the active voxels of +/// a scalar, floating-point grid. (Note the use of the math::MapAdapter and +/// math::OpMagnitude adapters.) +/// @code +/// FloatGrid grid = ...; +/// +/// // Assume that we know that the grid has a uniform scale map. +/// typedef math::UniformScaleMap MapType; +/// // Specify a world-space gradient operator that uses first-order differencing. +/// typedef math::Gradient GradientOp; +/// // Wrap the operator with an adapter that computes the magnitude of the gradient. +/// typedef math::OpMagnitude MagnitudeOp; +/// // Wrap the operator with an adapter that associates a map with it. +/// typedef math::MapAdapter CompoundOp; +/// +/// if (MapType::Ptr map = grid.constTransform().constMap()) { +/// math::Stats stats = tools::opStatistics(grid.cbeginValueOn(), CompoundOp(*map)); +/// } +/// @endcode +/// +/// @par Example: +/// Compute statistics of the divergence at the active voxels of a vector-valued grid. +/// @code +/// Vec3SGrid grid = ...; +/// +/// // Assume that we know that the grid has a uniform scale map. +/// typedef math::UniformScaleMap MapType; +/// // Specify a world-space divergence operator that uses first-order differencing. +/// typedef math::Divergence DivergenceOp; +/// // Wrap the operator with an adapter that associates a map with it. +/// typedef math::MapAdapter CompoundOp; +/// +/// if (MapType::Ptr map = grid.constTransform().constMap()) { +/// math::Stats stats = tools::opStatistics(grid.cbeginValueOn(), CompoundOp(*map)); +/// } +/// @endcode +/// +/// @par Example: +/// As above, but computing the divergence in index space. +/// @code +/// Vec3SGrid grid = ...; +/// +/// // Specify an index-space divergence operator that uses first-order differencing. +/// typedef math::ISDivergence DivergenceOp; +/// +/// math::Stats stats = tools::opStatistics(grid.cbeginValueOn(), DivergenceOp()); +/// @endcode +template +inline math::Stats +opStatistics(const IterT& iter, const OperatorT& op = OperatorT(), bool threaded = true); + + +//////////////////////////////////////// + + +namespace stats_internal { + +/// @todo This traits class is needed because tree::TreeValueIteratorBase uses +/// the name ValueT for the type of the value to which the iterator points, +/// whereas node-level iterators use the name ValueType. +template +struct IterTraits { + typedef typename IterT::ValueType ValueType; +}; + +template +struct IterTraits > { + typedef typename tree::TreeValueIteratorBase::ValueT ValueType; +}; + + +// Helper class to compute a scalar value from either a scalar or a vector value +// (the latter by computing the vector's magnitude) +template struct GetValImpl; + +template +struct GetValImpl { + static inline double get(const T& val) { return double(val); } +}; + +template +struct GetValImpl { + static inline double get(const T& val) { return val.length(); } +}; + + +// Helper class to compute a scalar value from a tree or node iterator +// that points to a value in either a scalar or a vector grid, and to +// add that value to a math::Stats object. +template +struct GetVal +{ + typedef typename IterTraits::ValueType ValueT; + typedef GetValImpl::IsVec> ImplT; + + inline void operator()(const IterT& iter, StatsT& stats) const { + if (iter.isVoxelValue()) stats.add(ImplT::get(*iter)); + else stats.add(ImplT::get(*iter), iter.getVoxelCount()); + } +}; + + +// Helper class to accumulate scalar voxel values or vector voxel magnitudes +// into a math::Stats object +template +struct StatsOp +{ + StatsOp(const ValueOp& op): getValue(op) {} + + // Accumulate voxel and tile values into this functor's Stats object. + inline void operator()(const IterT& iter) { getValue(iter, stats); } + + // Accumulate another functor's Stats object into this functor's. + inline void join(StatsOp& other) { stats.add(other.stats); } + + math::Stats stats; + ValueOp getValue; +}; + + +// Helper class to accumulate scalar voxel values or vector voxel magnitudes +// into a math::Histogram object +template +struct HistOp +{ + HistOp(const ValueOp& op, double vmin, double vmax, size_t bins): + hist(vmin, vmax, bins), getValue(op) + {} + + // Accumulate voxel and tile values into this functor's Histogram object. + inline void operator()(const IterT& iter) { getValue(iter, hist); } + + // Accumulate another functor's Histogram object into this functor's. + inline void join(HistOp& other) { hist.add(other.hist); } + + math::Histogram hist; + ValueOp getValue; +}; + + +// Helper class to apply an operator such as math::Gradient or math::Laplacian +// to voxels and accumulate the scalar results or the magnitudes of vector results +// into a math::Stats object +template +struct MathOp +{ + typedef typename IterT::TreeT TreeT; + typedef typename TreeT::ValueType ValueT; + typedef typename tree::ValueAccessor ConstAccessor; + + // Each thread gets its own accessor and its own copy of the operator. + ConstAccessor mAcc; + OpT mOp; + math::Stats mStats; + + template + static inline TreeT* THROW_IF_NULL(TreeT* ptr) { + if (ptr == NULL) OPENVDB_THROW(ValueError, "iterator references a null tree"); + return ptr; + } + + MathOp(const IterT& iter, const OpT& op): + mAcc(*THROW_IF_NULL(iter.getTree())), mOp(op) + {} + + // Accumulate voxel and tile values into this functor's Stats object. + void operator()(const IterT& it) + { + if (it.isVoxelValue()) { + // Add the magnitude of the gradient at a single voxel. + mStats.add(mOp.result(mAcc, it.getCoord())); + } else { + // Iterate over the voxels enclosed by a tile and add the results + // of applying the operator at each voxel. + /// @todo This could be specialized to be done more efficiently for some operators. + /// For example, all voxels in the interior of a tile (i.e., not on the borders) + /// have gradient zero, so there's no need to apply the operator to every voxel. + CoordBBox bbox = it.getBoundingBox(); + Coord xyz; + int &x = xyz.x(), &y = xyz.y(), &z = xyz.z(); + for (x = bbox.min().x(); x <= bbox.max().x(); ++x) { + for (y = bbox.min().y(); y <= bbox.max().y(); ++y) { + for (z = bbox.min().z(); z <= bbox.max().z(); ++z) { + mStats.add(mOp.result(mAcc, it.getCoord())); + } + } + } + } + } + + // Accumulate another functor's Stats object into this functor's. + inline void join(MathOp& other) { mStats.add(other.mStats); } +}; // struct MathOp + +} // namespace stats_internal + + +template +inline math::Histogram +histogram(const IterT& iter, double vmin, double vmax, size_t numBins, bool threaded) +{ + typedef stats_internal::GetVal ValueOp; + ValueOp valOp; + stats_internal::HistOp op(valOp, vmin, vmax, numBins); + tools::accumulate(iter, op, threaded); + return op.hist; +} + + +template +inline math::Stats +statistics(const IterT& iter, bool threaded) +{ + stats_internal::GetVal valOp; + return statistics(iter, valOp, threaded); +} + + +template +inline math::Stats +statistics(const IterT& iter, const ValueOp& valOp, bool threaded) +{ + stats_internal::StatsOp op(valOp); + tools::accumulate(iter, op, threaded); + return op.stats; +} + + +template +inline math::Stats +opStatistics(const IterT& iter, const OperatorT& op, bool threaded) +{ + stats_internal::MathOp func(iter, op); + tools::accumulate(iter, func, threaded); + return func.mStats; +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_STATISTICS_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/ValueTransformer.h b/openvdb_2_3_0_library/openvdb/tools/ValueTransformer.h new file mode 100755 index 0000000..d65b35b --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/ValueTransformer.h @@ -0,0 +1,707 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file ValueTransformer.h +/// +/// @author Peter Cucka +/// +/// tools::foreach() and tools::transformValues() transform the values in a grid +/// by iterating over the grid with a user-supplied iterator and applying a +/// user-supplied functor at each step of the iteration. With tools::foreach(), +/// the transformation is done in-place on the input grid, whereas with +/// tools::transformValues(), transformed values are written to an output grid +/// (which can, for example, have a different value type than the input grid). +/// Both functions can optionally transform multiple values of the grid in parallel. +/// +/// tools::accumulate() can be used to accumulate the results of applying a functor +/// at each step of a grid iteration. (The functor is responsible for storing and +/// updating intermediate results.) When the iteration is done serially the behavior is +/// the same as with tools::foreach(), but when multiple values are processed in parallel, +/// an additional step is performed: when any two threads finish processing, +/// @c op.join(otherOp) is called on one thread's functor to allow it to coalesce +/// its intermediate result with the other thread's. +/// +/// Finally, tools::setValueOnMin(), tools::setValueOnMax(), tools::setValueOnSum() +/// and tools::setValueOnMult() are wrappers around Tree::modifyValue() (or +/// ValueAccessor::modifyValue()) for some commmon in-place operations. +/// These are typically significantly faster than calling getValue() followed by setValue(). + +#ifndef OPENVDB_TOOLS_VALUETRANSFORMER_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_VALUETRANSFORMER_HAS_BEEN_INCLUDED + +#include // for std::min(), std::max() +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// Iterate over a grid and at each step call @c op(iter). +/// @param iter an iterator over a grid or its tree (@c Grid::ValueOnCIter, +/// @c Tree::NodeIter, etc.) +/// @param op a functor of the form void op(const IterT&), where @c IterT is +/// the type of @a iter +/// @param threaded if true, transform multiple values of the grid in parallel +/// @param shareOp if true and @a threaded is true, all threads use the same functor; +/// otherwise, each thread gets its own copy of the @e original functor +/// +/// @par Example: +/// Multiply all values (both set and unset) of a scalar, floating-point grid by two. +/// @code +/// struct Local { +/// static inline void op(const FloatGrid::ValueAllIter& iter) { +/// iter.setValue(*iter * 2); +/// } +/// }; +/// FloatGrid grid = ...; +/// tools::foreach(grid.beginValueAll(), Local::op); +/// @endcode +/// +/// @par Example: +/// Rotate all active vectors of a vector grid by 45 degrees about the y axis. +/// @code +/// namespace { +/// struct MatMul { +/// math::Mat3s M; +/// MatMul(const math::Mat3s& mat): M(mat) {} +/// inline void operator()(const VectorGrid::ValueOnIter& iter) const { +/// iter.setValue(M.transform(*iter)); +/// } +/// }; +/// } +/// { +/// VectorGrid grid = ...; +/// tools::foreach(grid.beginValueOn(), +/// MatMul(math::rotation(math::Y, M_PI_4))); +/// } +/// @endcode +/// +/// @note For more complex operations that require finer control over threading, +/// consider using @c tbb::parallel_for() or @c tbb::parallel_reduce() in conjunction +/// with a tree::IteratorRange that wraps a grid or tree iterator. +template +inline void foreach(const IterT& iter, XformOp& op, + bool threaded = true, bool shareOp = true); + +template +inline void foreach(const IterT& iter, const XformOp& op, + bool threaded = true, bool shareOp = true); + + +/// Iterate over a grid and at each step call op(iter, accessor) to +/// populate (via the accessor) the given output grid, whose @c ValueType +/// need not be the same as the input grid's. +/// @param inIter a non-const or (preferably) @c const iterator over an +/// input grid or its tree (@c Grid::ValueOnCIter, @c Tree::NodeIter, etc.) +/// @param outGrid an empty grid to be populated +/// @param op a functor of the form +/// void op(const InIterT&, OutGridT::ValueAccessor&), +/// where @c InIterT is the type of @a inIter +/// @param threaded if true, transform multiple values of the input grid in parallel +/// @param shareOp if true and @a threaded is true, all threads use the same functor; +/// otherwise, each thread gets its own copy of the @e original functor +/// @param merge how to merge intermediate results from multiple threads (see Types.h) +/// +/// @par Example: +/// Populate a scalar floating-point grid with the lengths of the vectors from all +/// active voxels of a vector-valued input grid. +/// @code +/// struct Local { +/// static void op( +/// const Vec3fGrid::ValueOnCIter& iter, +/// FloatGrid::ValueAccessor& accessor) +/// { +/// if (iter.isVoxelValue()) { // set a single voxel +/// accessor.setValue(iter.getCoord(), iter->length()); +/// } else { // fill an entire tile +/// CoordBBox bbox; +/// iter.getBoundingBox(bbox); +/// accessor.getTree()->fill(bbox, iter->length()); +/// } +/// } +/// }; +/// Vec3fGrid inGrid = ...; +/// FloatGrid outGrid; +/// tools::transformValues(inGrid.cbeginValueOn(), outGrid, Local::op); +/// @endcode +/// +/// @note For more complex operations that require finer control over threading, +/// consider using @c tbb::parallel_for() or @c tbb::parallel_reduce() in conjunction +/// with a tree::IteratorRange that wraps a grid or tree iterator. +template +inline void transformValues(const InIterT& inIter, OutGridT& outGrid, + XformOp& op, bool threaded = true, bool shareOp = true, + MergePolicy merge = MERGE_ACTIVE_STATES); + +#ifndef _MSC_VER +template +inline void transformValues(const InIterT& inIter, OutGridT& outGrid, + const XformOp& op, bool threaded = true, bool shareOp = true, + MergePolicy merge = MERGE_ACTIVE_STATES); +#endif + + +/// Iterate over a grid and at each step call @c op(iter). If threading is enabled, +/// call @c op.join(otherOp) to accumulate intermediate results from pairs of threads. +/// @param iter an iterator over a grid or its tree (@c Grid::ValueOnCIter, +/// @c Tree::NodeIter, etc.) +/// @param op a functor with a join method of the form void join(XformOp&) +/// and a call method of the form void op(const IterT&), +/// where @c IterT is the type of @a iter +/// @param threaded if true, transform multiple values of the grid in parallel +/// @note If @a threaded is true, each thread gets its own copy of the @e original functor. +/// The order in which threads are joined is unspecified. +/// @note If @a threaded is false, the join method is never called. +/// +/// @par Example: +/// Compute the average of the active values of a scalar, floating-point grid +/// using the math::Stats class. +/// @code +/// namespace { +/// struct Average { +/// math::Stats stats; +/// +/// // Accumulate voxel and tile values into this functor's Stats object. +/// inline void operator()(const FloatGrid::ValueOnCIter& iter) { +/// if (iter.isVoxelValue()) stats.add(*iter); +/// else stats.add(*iter, iter.getVoxelCount()); +/// } +/// +/// // Accumulate another functor's Stats object into this functor's. +/// inline void join(Average& other) { stats.add(other.stats); } +/// +/// // Return the cumulative result. +/// inline double average() const { return stats.mean(); } +/// }; +/// } +/// { +/// FloatGrid grid = ...; +/// Average op; +/// tools::accumulate(grid.cbeginValueOn(), op); +/// double average = op.average(); +/// } +/// @endcode +/// +/// @note For more complex operations that require finer control over threading, +/// consider using @c tbb::parallel_for() or @c tbb::parallel_reduce() in conjunction +/// with a tree::IteratorRange that wraps a grid or tree iterator. +template +inline void accumulate(const IterT& iter, XformOp& op, bool threaded = true); + + +/// @brief Set the value of the voxel at the given coordinates in @a tree to +/// the minimum of its current value and @a value, and mark the voxel as active. +/// @details This is typically significantly faster than calling getValue() +/// followed by setValueOn(). +/// @note @a TreeT can be either a Tree or a ValueAccessor. +template +inline void setValueOnMin(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value); + +/// @brief Set the value of the voxel at the given coordinates in @a tree to +/// the maximum of its current value and @a value, and mark the voxel as active. +/// @details This is typically significantly faster than calling getValue() +/// followed by setValueOn(). +/// @note @a TreeT can be either a Tree or a ValueAccessor. +template +inline void setValueOnMax(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value); + +/// @brief Set the value of the voxel at the given coordinates in @a tree to +/// the sum of its current value and @a value, and mark the voxel as active. +/// @details This is typically significantly faster than calling getValue() +/// followed by setValueOn(). +/// @note @a TreeT can be either a Tree or a ValueAccessor. +template +inline void setValueOnSum(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value); + +/// @brief Set the value of the voxel at the given coordinates in @a tree to +/// the product of its current value and @a value, and mark the voxel as active. +/// @details This is typically significantly faster than calling getValue() +/// followed by setValueOn(). +/// @note @a TreeT can be either a Tree or a ValueAccessor. +template +inline void setValueOnMult(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value); + + +//////////////////////////////////////// + + +namespace valxform { + +template +struct MinOp { + const ValueType val; + MinOp(const ValueType& v): val(v) {} + inline void operator()(ValueType& v) const { v = std::min(v, val); } +}; + +template +struct MaxOp { + const ValueType val; + MaxOp(const ValueType& v): val(v) {} + inline void operator()(ValueType& v) const { v = std::max(v, val); } +}; + +template +struct SumOp { + const ValueType val; + SumOp(const ValueType& v): val(v) {} + inline void operator()(ValueType& v) const { v += val; } +}; + +template +struct MultOp { + const ValueType val; + MultOp(const ValueType& v): val(v) {} + inline void operator()(ValueType& v) const { v *= val; } +}; + +} + + +template +inline void +setValueOnMin(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value) +{ + tree.modifyValue(xyz, valxform::MinOp(value)); +} + + +template +inline void +setValueOnMax(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value) +{ + tree.modifyValue(xyz, valxform::MaxOp(value)); +} + + +template +inline void +setValueOnSum(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value) +{ + tree.modifyValue(xyz, valxform::SumOp(value)); +} + + +template +inline void +setValueOnMult(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value) +{ + tree.modifyValue(xyz, valxform::MultOp(value)); +} + + +//////////////////////////////////////// + + +namespace valxform { + +template +class SharedOpApplier +{ +public: + typedef typename tree::IteratorRange IterRange; + + SharedOpApplier(const IterT& iter, OpT& op): mIter(iter), mOp(op) {} + + void process(bool threaded = true) + { + IterRange range(mIter); + if (threaded) { + tbb::parallel_for(range, *this); + } else { + (*this)(range); + } + } + + void operator()(IterRange& r) const { for ( ; r; ++r) mOp(r.iterator()); } + +private: + IterT mIter; + OpT& mOp; +}; + + +template +class CopyableOpApplier +{ +public: + typedef typename tree::IteratorRange IterRange; + + CopyableOpApplier(const IterT& iter, const OpT& op): mIter(iter), mOp(op), mOrigOp(&op) {} + + // When splitting this task, give the subtask a copy of the original functor, + // not of this task's functor, which might have been modified arbitrarily. + CopyableOpApplier(const CopyableOpApplier& other): + mIter(other.mIter), mOp(*other.mOrigOp), mOrigOp(other.mOrigOp) {} + + void process(bool threaded = true) + { + IterRange range(mIter); + if (threaded) { + tbb::parallel_for(range, *this); + } else { + (*this)(range); + } + } + + void operator()(IterRange& r) const { for ( ; r; ++r) mOp(r.iterator()); } + +private: + IterT mIter; + OpT mOp; // copy of original functor + OpT const * const mOrigOp; // pointer to original functor +}; + +} // namespace valxform + + +template +inline void +foreach(const IterT& iter, XformOp& op, bool threaded, bool shared) +{ + if (shared) { + typename valxform::SharedOpApplier proc(iter, op); + proc.process(threaded); + } else { + typedef typename valxform::CopyableOpApplier Processor; + Processor proc(iter, op); + proc.process(threaded); + } +} + +template +inline void +foreach(const IterT& iter, const XformOp& op, bool threaded, bool /*shared*/) +{ + // Const ops are shared across threads, not copied. + typename valxform::SharedOpApplier proc(iter, op); + proc.process(threaded); +} + + +//////////////////////////////////////// + + +namespace valxform { + +template +class SharedOpTransformer +{ +public: + typedef typename InIterT::TreeT InTreeT; + typedef typename tree::IteratorRange IterRange; + typedef typename OutTreeT::ValueType OutValueT; + + SharedOpTransformer(const InIterT& inIter, OutTreeT& outTree, OpT& op, MergePolicy merge): + mIsRoot(true), + mInputIter(inIter), + mInputTree(inIter.getTree()), + mOutputTree(&outTree), + mOp(op), + mMergePolicy(merge) + { + if (static_cast(mInputTree) == static_cast(mOutputTree)) { + OPENVDB_LOG_INFO("use tools::foreach(), not transformValues()," + " to transform a grid in place"); + } + } + + /// Splitting constructor + SharedOpTransformer(SharedOpTransformer& other, tbb::split): + mIsRoot(false), + mInputIter(other.mInputIter), + mInputTree(other.mInputTree), + mOutputTree(new OutTreeT(zeroVal())), + mOp(other.mOp), + mMergePolicy(other.mMergePolicy) + {} + + ~SharedOpTransformer() + { + // Delete the output tree only if it was allocated locally + // (the top-level output tree was supplied by the caller). + if (!mIsRoot) { + delete mOutputTree; + mOutputTree = NULL; + } + } + + void process(bool threaded = true) + { + if (!mInputTree || !mOutputTree) return; + + IterRange range(mInputIter); + + // Independently transform elements in the iterator range, + // either in parallel or serially. + if (threaded) { + tbb::parallel_reduce(range, *this); + } else { + (*this)(range); + } + } + + /// Transform each element in the given range. + void operator()(IterRange& range) const + { + if (!mOutputTree) return; + typename tree::ValueAccessor outAccessor(*mOutputTree); + for ( ; range; ++range) { + mOp(range.iterator(), outAccessor); + } + } + + void join(const SharedOpTransformer& other) + { + if (mOutputTree && other.mOutputTree) { + mOutputTree->merge(*other.mOutputTree, mMergePolicy); + } + } + +private: + bool mIsRoot; + InIterT mInputIter; + const InTreeT* mInputTree; + OutTreeT* mOutputTree; + OpT& mOp; + MergePolicy mMergePolicy; +}; // class SharedOpTransformer + + +template +class CopyableOpTransformer +{ +public: + typedef typename InIterT::TreeT InTreeT; + typedef typename tree::IteratorRange IterRange; + typedef typename OutTreeT::ValueType OutValueT; + + CopyableOpTransformer(const InIterT& inIter, OutTreeT& outTree, + const OpT& op, MergePolicy merge): + mIsRoot(true), + mInputIter(inIter), + mInputTree(inIter.getTree()), + mOutputTree(&outTree), + mOp(op), + mOrigOp(&op), + mMergePolicy(merge) + { + if (static_cast(mInputTree) == static_cast(mOutputTree)) { + OPENVDB_LOG_INFO("use tools::foreach(), not transformValues()," + " to transform a grid in place"); + } + } + + // When splitting this task, give the subtask a copy of the original functor, + // not of this task's functor, which might have been modified arbitrarily. + CopyableOpTransformer(CopyableOpTransformer& other, tbb::split): + mIsRoot(false), + mInputIter(other.mInputIter), + mInputTree(other.mInputTree), + mOutputTree(new OutTreeT(zeroVal())), + mOp(*other.mOrigOp), + mOrigOp(other.mOrigOp), + mMergePolicy(other.mMergePolicy) + {} + + ~CopyableOpTransformer() + { + // Delete the output tree only if it was allocated locally + // (the top-level output tree was supplied by the caller). + if (!mIsRoot) { + delete mOutputTree; + mOutputTree = NULL; + } + } + + void process(bool threaded = true) + { + if (!mInputTree || !mOutputTree) return; + + IterRange range(mInputIter); + + // Independently transform elements in the iterator range, + // either in parallel or serially. + if (threaded) { + tbb::parallel_reduce(range, *this); + } else { + (*this)(range); + } + } + + /// Transform each element in the given range. + void operator()(IterRange& range) + { + if (!mOutputTree) return; + typename tree::ValueAccessor outAccessor(*mOutputTree); + for ( ; range; ++range) { + mOp(range.iterator(), outAccessor); + } + } + + void join(const CopyableOpTransformer& other) + { + if (mOutputTree && other.mOutputTree) { + mOutputTree->merge(*other.mOutputTree, mMergePolicy); + } + } + +private: + bool mIsRoot; + InIterT mInputIter; + const InTreeT* mInputTree; + OutTreeT* mOutputTree; + OpT mOp; // copy of original functor + OpT const * const mOrigOp; // pointer to original functor + MergePolicy mMergePolicy; +}; // class CopyableOpTransformer + +} // namespace valxform + + +//////////////////////////////////////// + + +template +inline void +transformValues(const InIterT& inIter, OutGridT& outGrid, XformOp& op, + bool threaded, bool shared, MergePolicy merge) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType OutTreeT; + if (shared) { + typedef typename valxform::SharedOpTransformer Processor; + Processor proc(inIter, Adapter::tree(outGrid), op, merge); + proc.process(threaded); + } else { + typedef typename valxform::CopyableOpTransformer Processor; + Processor proc(inIter, Adapter::tree(outGrid), op, merge); + proc.process(threaded); + } +} + +#ifndef _MSC_VER +template +inline void +transformValues(const InIterT& inIter, OutGridT& outGrid, const XformOp& op, + bool threaded, bool /*share*/, MergePolicy merge) +{ + typedef TreeAdapter Adapter; + typedef typename Adapter::TreeType OutTreeT; + // Const ops are shared across threads, not copied. + typedef typename valxform::SharedOpTransformer Processor; + Processor proc(inIter, Adapter::tree(outGrid), op, merge); + proc.process(threaded); +} +#endif + + +//////////////////////////////////////// + + +namespace valxform { + +template +class OpAccumulator +{ +public: + typedef typename tree::IteratorRange IterRange; + + // The root task makes a const copy of the original functor (mOrigOp) + // and keeps a pointer to the original functor (mOp), which it then modifies. + // Each subtask keeps a const pointer to the root task's mOrigOp + // and makes and then modifies a non-const copy (mOp) of it. + OpAccumulator(const IterT& iter, OpT& op): + mIsRoot(true), + mIter(iter), + mOp(&op), + mOrigOp(new OpT(op)) + {} + + // When splitting this task, give the subtask a copy of the original functor, + // not of this task's functor, which might have been modified arbitrarily. + OpAccumulator(OpAccumulator& other, tbb::split): + mIsRoot(false), + mIter(other.mIter), + mOp(new OpT(*other.mOrigOp)), + mOrigOp(other.mOrigOp) + {} + + ~OpAccumulator() { if (mIsRoot) delete mOrigOp; else delete mOp; } + + void process(bool threaded = true) + { + IterRange range(mIter); + if (threaded) { + tbb::parallel_reduce(range, *this); + } else { + (*this)(range); + } + } + + void operator()(IterRange& r) { for ( ; r; ++r) (*mOp)(r.iterator()); } + + void join(OpAccumulator& other) { mOp->join(*other.mOp); } + +private: + bool mIsRoot; + IterT mIter; + OpT* mOp; // pointer to original functor, which might get modified + OpT const * const mOrigOp; // const copy of original functor +}; // class OpAccumulator + +} // namespace valxform + + +//////////////////////////////////////// + + +template +inline void +accumulate(const IterT& iter, XformOp& op, bool threaded) +{ + typename valxform::OpAccumulator proc(iter, op); + proc.process(threaded); +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_VALUETRANSFORMER_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/VectorTransformer.h b/openvdb_2_3_0_library/openvdb/tools/VectorTransformer.h new file mode 100755 index 0000000..21fb79a --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/VectorTransformer.h @@ -0,0 +1,158 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 VectorTransformer.h + +#ifndef OPENVDB_TOOLS_VECTORTRANSFORMER_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_VECTORTRANSFORMER_HAS_BEEN_INCLUDED + +#include +#include +#include +#include "ValueTransformer.h" // for tools::foreach() +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Apply an affine transform to the voxel values of a vector-valued grid +/// in accordance with the grid's vector type (covariant, contravariant, etc.). +/// @throw TypeError if the grid is not vector-valued +template +inline void +transformVectors(GridType&, const Mat4d&); + + +//////////////////////////////////////// + + +// Functors for use with tools::foreach() to transform vector voxel values + +struct HomogeneousMatMul +{ + const Mat4d mat; + HomogeneousMatMul(const Mat4d& _mat): mat(_mat) {} + template void operator()(const TreeIterT& it) const + { + Vec3d v(*it); + it.setValue(mat.transformH(v)); + } +}; + +struct MatMul +{ + const Mat4d mat; + MatMul(const Mat4d& _mat): mat(_mat) {} + template + void operator()(const TreeIterT& it) const + { + Vec3d v(*it); + it.setValue(mat.transform3x3(v)); + } +}; + +struct MatMulNormalize +{ + const Mat4d mat; + MatMulNormalize(const Mat4d& _mat): mat(_mat) {} + template + void operator()(const TreeIterT& it) const + { + Vec3d v(*it); + v = mat.transform3x3(v); + v.normalize(); + it.setValue(v); + } +}; + + +/// @internal This overload is enabled only for scalar-valued grids. +template inline +typename boost::disable_if_c::IsVec, void>::type +doTransformVectors(GridType&, const Mat4d&) +{ + OPENVDB_THROW(TypeError, "tools::transformVectors() requires a vector-valued grid"); +} + +/// @internal This overload is enabled only for vector-valued grids. +template inline +typename boost::enable_if_c::IsVec, void>::type +doTransformVectors(GridType& grid, const Mat4d& mat) +{ + if (!grid.isInWorldSpace()) return; + + const VecType vecType = grid.getVectorType(); + switch (vecType) { + case VEC_COVARIANT: + case VEC_COVARIANT_NORMALIZE: + { + Mat4d invmat = mat.inverse(); + invmat = invmat.transpose(); + + if (vecType == VEC_COVARIANT_NORMALIZE) { + foreach(grid.beginValueAll(), MatMulNormalize(invmat)); + } else { + foreach(grid.beginValueAll(), MatMul(invmat)); + } + break; + } + + case VEC_CONTRAVARIANT_RELATIVE: + foreach(grid.beginValueAll(), MatMul(mat)); + break; + + case VEC_CONTRAVARIANT_ABSOLUTE: + foreach(grid.beginValueAll(), HomogeneousMatMul(mat)); + break; + + case VEC_INVARIANT: + break; + } +} + + +template +inline void +transformVectors(GridType& grid, const Mat4d& mat) +{ + doTransformVectors(grid, mat); +} + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_VECTORTRANSFORMER_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/VolumeToMesh.h b/openvdb_2_3_0_library/openvdb/tools/VolumeToMesh.h new file mode 100755 index 0000000..ef0f39c --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/VolumeToMesh.h @@ -0,0 +1,4699 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_TOOLS_VOLUME_TO_MESH_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_VOLUME_TO_MESH_HAS_BEEN_INCLUDED + +#include // for OPENVDB_HAS_CXX11 +#include +#include // for COORD_OFFSETS +#include // for ISGradient +#include // for dilateVoxels() +#include + +#include +#include +#include +#include +#include + +#include +#include // for auto_ptr/unique_ptr + + +////////// + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + + +//////////////////////////////////////// + + +// Wrapper functions for the VolumeToMesh converter + + +/// @brief Uniformly mesh any scalar grid that has a continuous isosurface. +/// +/// @param grid a scalar grid to mesh +/// @param points output list of world space points +/// @param quads output quad index list +/// @param isovalue determines which isosurface to mesh +template +void +volumeToMesh( + const GridType& grid, + std::vector& points, + std::vector& quads, + double isovalue = 0.0); + + +/// @brief Adaptively mesh any scalar grid that has a continuous isosurface. +/// +/// @param grid a scalar grid to mesh +/// @param points output list of world space points +/// @param triangles output quad index list +/// @param quads output quad index list +/// @param isovalue determines which isosurface to mesh +/// @param adaptivity surface adaptivity threshold [0 to 1] +template +void +volumeToMesh( + const GridType& grid, + std::vector& points, + std::vector& triangles, + std::vector& quads, + double isovalue = 0.0, + double adaptivity = 0.0); + + +//////////////////////////////////////// + + +/// @brief Polygon flags, used for reference based meshing. +enum { POLYFLAG_EXTERIOR = 0x1, POLYFLAG_FRACTURE_SEAM = 0x2, POLYFLAG_SUBDIVIDED = 0x4}; + + +/// @brief Collection of quads and triangles +class PolygonPool +{ +public: + + inline PolygonPool(); + inline PolygonPool(const size_t numQuads, const size_t numTriangles); + + + inline void copy(const PolygonPool& rhs); + + inline void resetQuads(size_t size); + inline void clearQuads(); + + inline void resetTriangles(size_t size); + inline void clearTriangles(); + + + // polygon accessor methods + + const size_t& numQuads() const { return mNumQuads; } + + openvdb::Vec4I& quad(size_t n) { return mQuads[n]; } + const openvdb::Vec4I& quad(size_t n) const { return mQuads[n]; } + + + const size_t& numTriangles() const { return mNumTriangles; } + + openvdb::Vec3I& triangle(size_t n) { return mTriangles[n]; } + const openvdb::Vec3I& triangle(size_t n) const { return mTriangles[n]; } + + + // polygon flags accessor methods + + char& quadFlags(size_t n) { return mQuadFlags[n]; } + const char& quadFlags(size_t n) const { return mQuadFlags[n]; } + + char& triangleFlags(size_t n) { return mTriangleFlags[n]; } + const char& triangleFlags(size_t n) const { return mTriangleFlags[n]; } + + + // reduce the polygon containers, n has to + // be smaller than the current container size. + + inline bool trimQuads(const size_t n, bool reallocate = false); + inline bool trimTrinagles(const size_t n, bool reallocate = false); + +private: + // disallow copy by assignment + void operator=(const PolygonPool&) {} + + size_t mNumQuads, mNumTriangles; + boost::scoped_array mQuads; + boost::scoped_array mTriangles; + boost::scoped_array mQuadFlags, mTriangleFlags; +}; + + +/// @{ +/// @brief Point and primitive list types. +typedef boost::scoped_array PointList; +typedef boost::scoped_array PolygonPoolList; +/// @} + + +//////////////////////////////////////// + + +/// @brief Mesh any scalar grid that has a continuous isosurface. +class VolumeToMesh +{ +public: + + /// @param isovalue Determines which isosurface to mesh. + /// @param adaptivity Adaptivity threshold [0 to 1] + VolumeToMesh(double isovalue = 0, double adaptivity = 0); + + + ////////// + + // Mesh data accessors + + const size_t& pointListSize() const; + PointList& pointList(); + + const size_t& polygonPoolListSize() const; + PolygonPoolList& polygonPoolList(); + const PolygonPoolList& polygonPoolList() const; + + std::vector& pointFlags(); + const std::vector& pointFlags() const; + + + ////////// + + + /// @brief Main call + /// @note Call with scalar typed grid. + template + void operator()(const GridT&); + + + ////////// + + + /// @brief When surfacing fractured SDF fragments, the original unfractured + /// SDF grid can be used to eliminate seam lines and tag polygons that are + /// coincident with the reference surface with the @c POLYFLAG_EXTERIOR + /// flag and polygons that are in proximity to the seam lines with the + /// @c POLYFLAG_FRACTURE_SEAM flag. (The performance cost for using this + /// reference based scheme compared to the regular meshing scheme is + /// approximately 15% for the first fragment and neglect-able for + /// subsequent fragments.) + /// + /// @note Attributes from the original asset such as uv coordinates, normals etc. + /// are typically transfered to polygons that are marked with the + /// @c POLYFLAG_EXTERIOR flag. Polygons that are not marked with this flag + /// are interior to reference surface and might need projected UV coordinates + /// or a different material. Polygons marked as @c POLYFLAG_FRACTURE_SEAM can + /// be used to drive secondary elements such as debris and dust in a FX pipeline. + /// + /// @param grid reference surface grid of @c GridT type. + /// @param secAdaptivity Secondary adaptivity threshold [0 to 1]. Used in regions + /// that do not exist in the reference grid. (Parts of the + /// fragment surface that are not coincident with the + /// reference surface.) + void setRefGrid(const GridBase::ConstPtr& grid, double secAdaptivity = 0); + + + /// @param mask A boolean grid whose active topology defines the region to mesh. + /// @param invertMask Toggle to mesh the complement of the mask. + /// @note The mask's tree configuration has to match @c GridT's tree configuration. + void setSurfaceMask(const GridBase::ConstPtr& mask, bool invertMask = false); + + /// @param grid A scalar grid used as an spatial multiplier for the adaptivity threshold. + /// @note The grid's tree configuration has to match @c GridT's tree configuration. + void setSpatialAdaptivity(const GridBase::ConstPtr& grid); + + + /// @param tree A boolean tree whose active topology defines the adaptivity mask. + /// @note The tree configuration has to match @c GridT's tree configuration. + void setAdaptivityMask(const TreeBase::ConstPtr& tree); + + + /// @brief Subdivide volume and mesh into disjoint parts + /// @param partitions Number of partitions. + /// @param activePart Specific partition to mesh, 0 to @c partitions - 1. + void partition(unsigned partitions = 1, unsigned activePart = 0); + +private: + + PointList mPoints; + PolygonPoolList mPolygons; + + size_t mPointListSize, mSeamPointListSize, mPolygonPoolListSize; + double mIsovalue, mPrimAdaptivity, mSecAdaptivity; + + GridBase::ConstPtr mRefGrid, mSurfaceMaskGrid, mAdaptivityGrid; + TreeBase::ConstPtr mAdaptivityMaskTree; + + TreeBase::Ptr mRefSignTree, mRefIdxTree; + + bool mInvertSurfaceMask; + unsigned mPartitions, mActivePart; + + boost::scoped_array mQuantizedSeamPoints; + + std::vector mPointFlags; +}; + + +//////////////////////////////////////// + + +/// @brief Given a set of tangent elements, @c points with corresponding @c normals, +/// this method returns the intersection point of all tangent elements. +/// +/// @note Used to extract surfaces with sharp edges and corners from volume data, +/// see the following paper for details: "Feature Sensitive Surface +/// Extraction from Volume Data, Kobbelt et al. 2001". +inline Vec3d findFeaturePoint( + const std::vector& points, + const std::vector& normals) +{ + typedef math::Mat3d Mat3d; + + Vec3d avgPos(0.0); + + if (points.empty()) return avgPos; + + for (size_t n = 0, N = points.size(); n < N; ++n) { + avgPos += points[n]; + } + + avgPos /= double(points.size()); + + // Unique components of the 3x3 A^TA matrix, where A is + // the matrix of normals. + double m00=0,m01=0,m02=0, + m11=0,m12=0, + m22=0; + + // The rhs vector, A^Tb, where b = n dot p + Vec3d rhs(0.0); + + for (size_t n = 0, N = points.size(); n < N; ++n) { + + const Vec3d& n_ref = normals[n]; + + // A^TA + m00 += n_ref[0] * n_ref[0]; // diagonal + m11 += n_ref[1] * n_ref[1]; + m22 += n_ref[2] * n_ref[2]; + + m01 += n_ref[0] * n_ref[1]; // Upper-tri + m02 += n_ref[0] * n_ref[2]; + m12 += n_ref[1] * n_ref[2]; + + // A^Tb (centered around the origin) + rhs += n_ref * n_ref.dot(points[n] - avgPos); + } + + Mat3d A(m00,m01,m02, + m01,m11,m12, + m02,m12,m22); + + /* + // Inverse + const double det = A.det(); + if (det > 0.01) { + Mat3d A_inv = A.adjoint(); + A_inv *= (1.0 / det); + + return avgPos + A_inv * rhs; + } + */ + + // Compute the pseudo inverse + + math::Mat3d eigenVectors; + Vec3d eigenValues; + + diagonalizeSymmetricMatrix(A, eigenVectors, eigenValues, 300); + + Mat3d D = Mat3d::identity(); + + + double tolerance = std::max(std::abs(eigenValues[0]), std::abs(eigenValues[1])); + tolerance = std::max(tolerance, std::abs(eigenValues[2])); + tolerance *= 0.01; + + int clamped = 0; + for (int i = 0; i < 3; ++i ) { + if (std::abs(eigenValues[i]) < tolerance) { + D[i][i] = 0.0; + ++clamped; + } else { + D[i][i] = 1.0 / eigenValues[i]; + } + } + + // Assemble the pseudo inverse and calc. the intersection point + if (clamped < 3) { + Mat3d pseudoInv = eigenVectors * D * eigenVectors.transpose(); + return avgPos + pseudoInv * rhs; + } + + return avgPos; +} + + +//////////////////////////////////////// + + +// Internal utility methods +namespace internal { + +template +struct UniquePtr +{ +#ifdef OPENVDB_HAS_CXX11 + typedef std::unique_ptr type; +#else + typedef std::auto_ptr type; +#endif +}; + + +/// @brief Bit-flags used to classify cells. +enum { SIGNS = 0xFF, EDGES = 0xE00, INSIDE = 0x100, + XEDGE = 0x200, YEDGE = 0x400, ZEDGE = 0x800, SEAM = 0x1000}; + + +/// @brief Used to quickly determine if a given cell is adaptable. +const bool sAdaptable[256] = { + 1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,1,0,1,0,1,0,1, + 1,0,1,1,0,0,1,1,0,0,0,1,0,0,1,1,1,1,1,1,0,0,1,1,0,1,0,1,0,0,0,1, + 1,0,0,0,1,0,1,1,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,0,1,1,1,0,1,1,0,0,0,0,1,0,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0,1, + 1,0,0,0,0,0,0,0,1,1,0,1,1,1,1,1,1,1,0,1,0,0,0,0,1,1,0,1,1,1,0,1, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,1,1,0,1,0,0,0,1, + 1,0,0,0,1,0,1,0,1,1,0,0,1,1,1,1,1,1,0,0,1,0,0,0,1,1,0,0,1,1,0,1, + 1,0,1,0,1,0,1,0,1,0,0,0,1,0,1,1,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1}; + + +/// @brief Contains the ambiguous face index for certain cell configuration. +const unsigned char sAmbiguousFace[256] = { + 0,0,0,0,0,5,0,0,0,0,5,0,0,0,0,0,0,0,1,0,0,5,1,0,4,0,0,0,4,0,0,0, + 0,1,0,0,2,0,0,0,0,1,5,0,2,0,0,0,0,0,0,0,2,0,0,0,4,0,0,0,0,0,0,0, + 0,0,2,2,0,5,0,0,3,3,0,0,0,0,0,0,6,6,0,0,6,0,0,0,0,0,0,0,0,0,0,0, + 0,1,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,4,0,4,3,0,3,0,0,0,5,0,0,0,0,0,0,0,1,0,3,0,0,0,0,0,0,0,0,0,0,0, + 6,0,6,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + + +/// @brief Lookup table for different cell sign configurations. The first entry specifies +/// the total number of points that need to be generated inside a cell and the +/// remaining 12 entries indicate different edge groups. +const unsigned char sEdgeGroupTable[256][13] = { + {0,0,0,0,0,0,0,0,0,0,0,0,0},{1,1,0,0,1,0,0,0,0,1,0,0,0},{1,1,1,0,0,0,0,0,0,0,1,0,0}, + {1,0,1,0,1,0,0,0,0,1,1,0,0},{1,0,1,1,0,0,0,0,0,0,0,1,0},{1,1,1,1,1,0,0,0,0,1,0,1,0}, + {1,1,0,1,0,0,0,0,0,0,1,1,0},{1,0,0,1,1,0,0,0,0,1,1,1,0},{1,0,0,1,1,0,0,0,0,0,0,0,1}, + {1,1,0,1,0,0,0,0,0,1,0,0,1},{1,1,1,1,1,0,0,0,0,0,1,0,1},{1,0,1,1,0,0,0,0,0,1,1,0,1}, + {1,0,1,0,1,0,0,0,0,0,0,1,1},{1,1,1,0,0,0,0,0,0,1,0,1,1},{1,1,0,0,1,0,0,0,0,0,1,1,1}, + {1,0,0,0,0,0,0,0,0,1,1,1,1},{1,0,0,0,0,1,0,0,1,1,0,0,0},{1,1,0,0,1,1,0,0,1,0,0,0,0}, + {1,1,1,0,0,1,0,0,1,1,1,0,0},{1,0,1,0,1,1,0,0,1,0,1,0,0},{2,0,1,1,0,2,0,0,2,2,0,1,0}, + {1,1,1,1,1,1,0,0,1,0,0,1,0},{1,1,0,1,0,1,0,0,1,1,1,1,0},{1,0,0,1,1,1,0,0,1,0,1,1,0}, + {1,0,0,1,1,1,0,0,1,1,0,0,1},{1,1,0,1,0,1,0,0,1,0,0,0,1},{2,2,1,1,2,1,0,0,1,2,1,0,1}, + {1,0,1,1,0,1,0,0,1,0,1,0,1},{1,0,1,0,1,1,0,0,1,1,0,1,1},{1,1,1,0,0,1,0,0,1,0,0,1,1}, + {2,1,0,0,1,2,0,0,2,1,2,2,2},{1,0,0,0,0,1,0,0,1,0,1,1,1},{1,0,0,0,0,1,1,0,0,0,1,0,0}, + {1,1,0,0,1,1,1,0,0,1,1,0,0},{1,1,1,0,0,1,1,0,0,0,0,0,0},{1,0,1,0,1,1,1,0,0,1,0,0,0}, + {1,0,1,1,0,1,1,0,0,0,1,1,0},{2,2,2,1,1,1,1,0,0,1,2,1,0},{1,1,0,1,0,1,1,0,0,0,0,1,0}, + {1,0,0,1,1,1,1,0,0,1,0,1,0},{2,0,0,2,2,1,1,0,0,0,1,0,2},{1,1,0,1,0,1,1,0,0,1,1,0,1}, + {1,1,1,1,1,1,1,0,0,0,0,0,1},{1,0,1,1,0,1,1,0,0,1,0,0,1},{1,0,1,0,1,1,1,0,0,0,1,1,1}, + {2,1,1,0,0,2,2,0,0,2,1,2,2},{1,1,0,0,1,1,1,0,0,0,0,1,1},{1,0,0,0,0,1,1,0,0,1,0,1,1}, + {1,0,0,0,0,0,1,0,1,1,1,0,0},{1,1,0,0,1,0,1,0,1,0,1,0,0},{1,1,1,0,0,0,1,0,1,1,0,0,0}, + {1,0,1,0,1,0,1,0,1,0,0,0,0},{1,0,1,1,0,0,1,0,1,1,1,1,0},{2,1,1,2,2,0,2,0,2,0,1,2,0}, + {1,1,0,1,0,0,1,0,1,1,0,1,0},{1,0,0,1,1,0,1,0,1,0,0,1,0},{1,0,0,1,1,0,1,0,1,1,1,0,1}, + {1,1,0,1,0,0,1,0,1,0,1,0,1},{2,1,2,2,1,0,2,0,2,1,0,0,2},{1,0,1,1,0,0,1,0,1,0,0,0,1}, + {2,0,2,0,2,0,1,0,1,2,2,1,1},{2,2,2,0,0,0,1,0,1,0,2,1,1},{2,2,0,0,2,0,1,0,1,2,0,1,1}, + {1,0,0,0,0,0,1,0,1,0,0,1,1},{1,0,0,0,0,0,1,1,0,0,0,1,0},{2,1,0,0,1,0,2,2,0,1,0,2,0}, + {1,1,1,0,0,0,1,1,0,0,1,1,0},{1,0,1,0,1,0,1,1,0,1,1,1,0},{1,0,1,1,0,0,1,1,0,0,0,0,0}, + {1,1,1,1,1,0,1,1,0,1,0,0,0},{1,1,0,1,0,0,1,1,0,0,1,0,0},{1,0,0,1,1,0,1,1,0,1,1,0,0}, + {1,0,0,1,1,0,1,1,0,0,0,1,1},{1,1,0,1,0,0,1,1,0,1,0,1,1},{2,1,2,2,1,0,1,1,0,0,1,2,1}, + {2,0,1,1,0,0,2,2,0,2,2,1,2},{1,0,1,0,1,0,1,1,0,0,0,0,1},{1,1,1,0,0,0,1,1,0,1,0,0,1}, + {1,1,0,0,1,0,1,1,0,0,1,0,1},{1,0,0,0,0,0,1,1,0,1,1,0,1},{1,0,0,0,0,1,1,1,1,1,0,1,0}, + {1,1,0,0,1,1,1,1,1,0,0,1,0},{2,1,1,0,0,2,2,1,1,1,2,1,0},{2,0,2,0,2,1,1,2,2,0,1,2,0}, + {1,0,1,1,0,1,1,1,1,1,0,0,0},{2,2,2,1,1,2,2,1,1,0,0,0,0},{2,2,0,2,0,1,1,2,2,2,1,0,0}, + {2,0,0,1,1,2,2,1,1,0,2,0,0},{2,0,0,1,1,1,1,2,2,1,0,1,2},{2,2,0,2,0,2,2,1,1,0,0,2,1}, + {4,3,2,2,3,4,4,1,1,3,4,2,1},{3,0,2,2,0,1,1,3,3,0,1,2,3},{2,0,2,0,2,2,2,1,1,2,0,0,1}, + {2,1,1,0,0,1,1,2,2,0,0,0,2},{3,1,0,0,1,2,2,3,3,1,2,0,3},{2,0,0,0,0,1,1,2,2,0,1,0,2}, + {1,0,0,0,0,1,0,1,0,0,1,1,0},{1,1,0,0,1,1,0,1,0,1,1,1,0},{1,1,1,0,0,1,0,1,0,0,0,1,0}, + {1,0,1,0,1,1,0,1,0,1,0,1,0},{1,0,1,1,0,1,0,1,0,0,1,0,0},{2,1,1,2,2,2,0,2,0,2,1,0,0}, + {1,1,0,1,0,1,0,1,0,0,0,0,0},{1,0,0,1,1,1,0,1,0,1,0,0,0},{1,0,0,1,1,1,0,1,0,0,1,1,1}, + {2,2,0,2,0,1,0,1,0,1,2,2,1},{2,2,1,1,2,2,0,2,0,0,0,1,2},{2,0,2,2,0,1,0,1,0,1,0,2,1}, + {1,0,1,0,1,1,0,1,0,0,1,0,1},{2,2,2,0,0,1,0,1,0,1,2,0,1},{1,1,0,0,1,1,0,1,0,0,0,0,1}, + {1,0,0,0,0,1,0,1,0,1,0,0,1},{1,0,0,0,0,0,0,1,1,1,1,1,0},{1,1,0,0,1,0,0,1,1,0,1,1,0}, + {1,1,1,0,0,0,0,1,1,1,0,1,0},{1,0,1,0,1,0,0,1,1,0,0,1,0},{1,0,1,1,0,0,0,1,1,1,1,0,0}, + {2,2,2,1,1,0,0,1,1,0,2,0,0},{1,1,0,1,0,0,0,1,1,1,0,0,0},{1,0,0,1,1,0,0,1,1,0,0,0,0}, + {2,0,0,2,2,0,0,1,1,2,2,2,1},{2,1,0,1,0,0,0,2,2,0,1,1,2},{3,2,1,1,2,0,0,3,3,2,0,1,3}, + {2,0,1,1,0,0,0,2,2,0,0,1,2},{2,0,1,0,1,0,0,2,2,1,1,0,2},{2,1,1,0,0,0,0,2,2,0,1,0,2}, + {2,1,0,0,1,0,0,2,2,1,0,0,2},{1,0,0,0,0,0,0,1,1,0,0,0,1},{1,0,0,0,0,0,0,1,1,0,0,0,1}, + {1,1,0,0,1,0,0,1,1,1,0,0,1},{2,1,1,0,0,0,0,2,2,0,1,0,2},{1,0,1,0,1,0,0,1,1,1,1,0,1}, + {1,0,1,1,0,0,0,1,1,0,0,1,1},{2,1,1,2,2,0,0,1,1,1,0,1,2},{1,1,0,1,0,0,0,1,1,0,1,1,1}, + {2,0,0,1,1,0,0,2,2,2,2,2,1},{1,0,0,1,1,0,0,1,1,0,0,0,0},{1,1,0,1,0,0,0,1,1,1,0,0,0}, + {1,1,1,1,1,0,0,1,1,0,1,0,0},{1,0,1,1,0,0,0,1,1,1,1,0,0},{1,0,1,0,1,0,0,1,1,0,0,1,0}, + {1,1,1,0,0,0,0,1,1,1,0,1,0},{1,1,0,0,1,0,0,1,1,0,1,1,0},{1,0,0,0,0,0,0,1,1,1,1,1,0}, + {1,0,0,0,0,1,0,1,0,1,0,0,1},{1,1,0,0,1,1,0,1,0,0,0,0,1},{1,1,1,0,0,1,0,1,0,1,1,0,1}, + {1,0,1,0,1,1,0,1,0,0,1,0,1},{1,0,1,1,0,1,0,1,0,1,0,1,1},{2,2,2,1,1,2,0,2,0,0,0,2,1}, + {2,1,0,1,0,2,0,2,0,1,2,2,1},{2,0,0,2,2,1,0,1,0,0,1,1,2},{1,0,0,1,1,1,0,1,0,1,0,0,0}, + {1,1,0,1,0,1,0,1,0,0,0,0,0},{2,1,2,2,1,2,0,2,0,1,2,0,0},{1,0,1,1,0,1,0,1,0,0,1,0,0}, + {1,0,1,0,1,1,0,1,0,1,0,1,0},{1,1,1,0,0,1,0,1,0,0,0,1,0},{2,2,0,0,2,1,0,1,0,2,1,1,0}, + {1,0,0,0,0,1,0,1,0,0,1,1,0},{1,0,0,0,0,1,1,1,1,0,1,0,1},{2,1,0,0,1,2,1,1,2,2,1,0,1}, + {1,1,1,0,0,1,1,1,1,0,0,0,1},{2,0,2,0,2,1,2,2,1,1,0,0,2},{2,0,1,1,0,1,2,2,1,0,1,2,1}, + {4,1,1,3,3,2,4,4,2,2,1,4,3},{2,2,0,2,0,2,1,1,2,0,0,1,2},{3,0,0,1,1,2,3,3,2,2,0,3,1}, + {1,0,0,1,1,1,1,1,1,0,1,0,0},{2,2,0,2,0,1,2,2,1,1,2,0,0},{2,2,1,1,2,2,1,1,2,0,0,0,0}, + {2,0,1,1,0,2,1,1,2,2,0,0,0},{2,0,2,0,2,2,1,1,2,0,2,1,0},{3,1,1,0,0,3,2,2,3,3,1,2,0}, + {2,1,0,0,1,1,2,2,1,0,0,2,0},{2,0,0,0,0,2,1,1,2,2,0,1,0},{1,0,0,0,0,0,1,1,0,1,1,0,1}, + {1,1,0,0,1,0,1,1,0,0,1,0,1},{1,1,1,0,0,0,1,1,0,1,0,0,1},{1,0,1,0,1,0,1,1,0,0,0,0,1}, + {2,0,2,2,0,0,1,1,0,2,2,1,2},{3,1,1,2,2,0,3,3,0,0,1,3,2},{2,1,0,1,0,0,2,2,0,1,0,2,1}, + {2,0,0,1,1,0,2,2,0,0,0,2,1},{1,0,0,1,1,0,1,1,0,1,1,0,0},{1,1,0,1,0,0,1,1,0,0,1,0,0}, + {2,2,1,1,2,0,1,1,0,2,0,0,0},{1,0,1,1,0,0,1,1,0,0,0,0,0},{2,0,1,0,1,0,2,2,0,1,1,2,0}, + {2,1,1,0,0,0,2,2,0,0,1,2,0},{2,1,0,0,1,0,2,2,0,1,0,2,0},{1,0,0,0,0,0,1,1,0,0,0,1,0}, + {1,0,0,0,0,0,1,0,1,0,0,1,1},{1,1,0,0,1,0,1,0,1,1,0,1,1},{1,1,1,0,0,0,1,0,1,0,1,1,1}, + {2,0,2,0,2,0,1,0,1,1,1,2,2},{1,0,1,1,0,0,1,0,1,0,0,0,1},{2,2,2,1,1,0,2,0,2,2,0,0,1}, + {1,1,0,1,0,0,1,0,1,0,1,0,1},{2,0,0,2,2,0,1,0,1,1,1,0,2},{1,0,0,1,1,0,1,0,1,0,0,1,0}, + {1,1,0,1,0,0,1,0,1,1,0,1,0},{2,2,1,1,2,0,2,0,2,0,2,1,0},{2,0,2,2,0,0,1,0,1,1,1,2,0}, + {1,0,1,0,1,0,1,0,1,0,0,0,0},{1,1,1,0,0,0,1,0,1,1,0,0,0},{1,1,0,0,1,0,1,0,1,0,1,0,0}, + {1,0,0,0,0,0,1,0,1,1,1,0,0},{1,0,0,0,0,1,1,0,0,1,0,1,1},{1,1,0,0,1,1,1,0,0,0,0,1,1}, + {2,2,2,0,0,1,1,0,0,2,1,2,2},{2,0,1,0,1,2,2,0,0,0,2,1,1},{1,0,1,1,0,1,1,0,0,1,0,0,1}, + {2,1,1,2,2,1,1,0,0,0,0,0,2},{2,1,0,1,0,2,2,0,0,1,2,0,1},{2,0,0,2,2,1,1,0,0,0,1,0,2}, + {1,0,0,1,1,1,1,0,0,1,0,1,0},{1,1,0,1,0,1,1,0,0,0,0,1,0},{3,1,2,2,1,3,3,0,0,1,3,2,0}, + {2,0,1,1,0,2,2,0,0,0,2,1,0},{1,0,1,0,1,1,1,0,0,1,0,0,0},{1,1,1,0,0,1,1,0,0,0,0,0,0}, + {2,2,0,0,2,1,1,0,0,2,1,0,0},{1,0,0,0,0,1,1,0,0,0,1,0,0},{1,0,0,0,0,1,0,0,1,0,1,1,1}, + {2,2,0,0,2,1,0,0,1,1,2,2,2},{1,1,1,0,0,1,0,0,1,0,0,1,1},{2,0,1,0,1,2,0,0,2,2,0,1,1}, + {1,0,1,1,0,1,0,0,1,0,1,0,1},{3,1,1,3,3,2,0,0,2,2,1,0,3},{1,1,0,1,0,1,0,0,1,0,0,0,1}, + {2,0,0,2,2,1,0,0,1,1,0,0,2},{1,0,0,1,1,1,0,0,1,0,1,1,0},{2,1,0,1,0,2,0,0,2,2,1,1,0}, + {2,1,2,2,1,1,0,0,1,0,0,2,0},{2,0,1,1,0,2,0,0,2,2,0,1,0},{1,0,1,0,1,1,0,0,1,0,1,0,0}, + {2,1,1,0,0,2,0,0,2,2,1,0,0},{1,1,0,0,1,1,0,0,1,0,0,0,0},{1,0,0,0,0,1,0,0,1,1,0,0,0}, + {1,0,0,0,0,0,0,0,0,1,1,1,1},{1,1,0,0,1,0,0,0,0,0,1,1,1},{1,1,1,0,0,0,0,0,0,1,0,1,1}, + {1,0,1,0,1,0,0,0,0,0,0,1,1},{1,0,1,1,0,0,0,0,0,1,1,0,1},{2,1,1,2,2,0,0,0,0,0,1,0,2}, + {1,1,0,1,0,0,0,0,0,1,0,0,1},{1,0,0,1,1,0,0,0,0,0,0,0,1},{1,0,0,1,1,0,0,0,0,1,1,1,0}, + {1,1,0,1,0,0,0,0,0,0,1,1,0},{2,1,2,2,1,0,0,0,0,1,0,2,0},{1,0,1,1,0,0,0,0,0,0,0,1,0}, + {1,0,1,0,1,0,0,0,0,1,1,0,0},{1,1,1,0,0,0,0,0,0,0,1,0,0},{1,1,0,0,1,0,0,0,0,1,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0}}; + + +//////////////////////////////////////// + +inline bool +isPlanarQuad( + const Vec3d& p0, const Vec3d& p1, + const Vec3d& p2, const Vec3d& p3, + double epsilon = 0.001) +{ + // compute representative plane + Vec3d normal = (p2-p0).cross(p1-p3); + normal.normalize(); + const Vec3d centroid = (p0 + p1 + p2 + p3); + const double d = centroid.dot(normal) * 0.25; + + + // test vertice distance to plane + double absDist = std::abs(p0.dot(normal) - d); + if (absDist > epsilon) return false; + + absDist = std::abs(p1.dot(normal) - d); + if (absDist > epsilon) return false; + + absDist = std::abs(p2.dot(normal) - d); + if (absDist > epsilon) return false; + + absDist = std::abs(p3.dot(normal) - d); + if (absDist > epsilon) return false; + + return true; +} + + +//////////////////////////////////////// + + +/// @{ +/// @brief Utility methods for point quantization. + +enum { MASK_FIRST_10_BITS = 0x000003FF, MASK_DIRTY_BIT = 0x80000000, MASK_INVALID_BIT = 0x40000000 }; + +inline uint32_t +packPoint(const Vec3d& v) +{ + uint32_t data = 0; + + // values are expected to be in the [0.0 to 1.0] range. + assert(!(v.x() > 1.0) && !(v.y() > 1.0) && !(v.z() > 1.0)); + assert(!(v.x() < 0.0) && !(v.y() < 0.0) && !(v.z() < 0.0)); + + data |= (uint32_t(v.x() * 1023.0) & MASK_FIRST_10_BITS) << 20; + data |= (uint32_t(v.y() * 1023.0) & MASK_FIRST_10_BITS) << 10; + data |= (uint32_t(v.z() * 1023.0) & MASK_FIRST_10_BITS); + + return data; +} + +inline Vec3d +unpackPoint(uint32_t data) +{ + Vec3d v; + v.z() = double(data & MASK_FIRST_10_BITS) * 0.0009775171; + data = data >> 10; + v.y() = double(data & MASK_FIRST_10_BITS) * 0.0009775171; + data = data >> 10; + v.x() = double(data & MASK_FIRST_10_BITS) * 0.0009775171; + + return v; +} + +/// @} + +//////////////////////////////////////// + + +/// @brief General method that computes the cell-sign configuration at the given +/// @c ijk coordinate. +template +inline unsigned char +evalCellSigns(const AccessorT& accessor, const Coord& ijk, typename AccessorT::ValueType iso) +{ + unsigned signs = 0; + Coord coord = ijk; // i, j, k + if (accessor.getValue(coord) < iso) signs |= 1u; + coord[0] += 1; // i+1, j, k + if (accessor.getValue(coord) < iso) signs |= 2u; + coord[2] += 1; // i+1, j, k+1 + if (accessor.getValue(coord) < iso) signs |= 4u; + coord[0] = ijk[0]; // i, j, k+1 + if (accessor.getValue(coord) < iso) signs |= 8u; + coord[1] += 1; coord[2] = ijk[2]; // i, j+1, k + if (accessor.getValue(coord) < iso) signs |= 16u; + coord[0] += 1; // i+1, j+1, k + if (accessor.getValue(coord) < iso) signs |= 32u; + coord[2] += 1; // i+1, j+1, k+1 + if (accessor.getValue(coord) < iso) signs |= 64u; + coord[0] = ijk[0]; // i, j+1, k+1 + if (accessor.getValue(coord) < iso) signs |= 128u; + return signs; +} + + +/// @brief Leaf node optimized method that computes the cell-sign configuration +/// at the given local @c offset +template +inline unsigned char +evalCellSigns(const LeafT& leaf, const Index offset, typename LeafT::ValueType iso) +{ + unsigned char signs = 0; + + // i, j, k + if (leaf.getValue(offset) < iso) signs |= 1u; + + // i, j, k+1 + if (leaf.getValue(offset + 1) < iso) signs |= 8u; + + // i, j+1, k + if (leaf.getValue(offset + LeafT::DIM) < iso) signs |= 16u; + + // i, j+1, k+1 + if (leaf.getValue(offset + LeafT::DIM + 1) < iso) signs |= 128u; + + // i+1, j, k + if (leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) ) < iso) signs |= 2u; + + // i+1, j, k+1 + if (leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + 1) < iso) signs |= 4u; + + // i+1, j+1, k + if (leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + LeafT::DIM) < iso) signs |= 32u; + + // i+1, j+1, k+1 + if (leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + LeafT::DIM + 1) < iso) signs |= 64u; + + return signs; +} + + +/// @brief Used to correct topological ambiguities related to two adjacent cells +/// that share an ambiguous face. +template +inline void +correctCellSigns(unsigned char& signs, unsigned char face, + const AccessorT& acc, Coord ijk, typename AccessorT::ValueType iso) +{ + if (face == 1) { + ijk[2] -= 1; + if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 3) signs = ~signs; + } else if (face == 3) { + ijk[2] += 1; + if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 1) signs = ~signs; + } else if (face == 2) { + ijk[0] += 1; + if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 4) signs = ~signs; + } else if (face == 4) { + ijk[0] -= 1; + if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 2) signs = ~signs; + } else if (face == 5) { + ijk[1] -= 1; + if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 6) signs = ~signs; + } else if (face == 6) { + ijk[1] += 1; + if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 5) signs = ~signs; + } +} + + +template +inline bool +isNonManifold(const AccessorT& accessor, const Coord& ijk, + typename AccessorT::ValueType isovalue, const int dim) +{ + int hDim = dim >> 1; + bool m, p[8]; // Corner signs + + Coord coord = ijk; // i, j, k + p[0] = accessor.getValue(coord) < isovalue; + coord[0] += dim; // i+dim, j, k + p[1] = accessor.getValue(coord) < isovalue; + coord[2] += dim; // i+dim, j, k+dim + p[2] = accessor.getValue(coord) < isovalue; + coord[0] = ijk[0]; // i, j, k+dim + p[3] = accessor.getValue(coord) < isovalue; + coord[1] += dim; coord[2] = ijk[2]; // i, j+dim, k + p[4] = accessor.getValue(coord) < isovalue; + coord[0] += dim; // i+dim, j+dim, k + p[5] = accessor.getValue(coord) < isovalue; + coord[2] += dim; // i+dim, j+dim, k+dim + p[6] = accessor.getValue(coord) < isovalue; + coord[0] = ijk[0]; // i, j+dim, k+dim + p[7] = accessor.getValue(coord) < isovalue; + + // Check if the corner sign configuration is ambiguous + unsigned signs = 0; + if (p[0]) signs |= 1u; + if (p[1]) signs |= 2u; + if (p[2]) signs |= 4u; + if (p[3]) signs |= 8u; + if (p[4]) signs |= 16u; + if (p[5]) signs |= 32u; + if (p[6]) signs |= 64u; + if (p[7]) signs |= 128u; + if (!sAdaptable[signs]) return true; + + // Manifold check + + // Evaluate edges + int i = ijk[0], ip = ijk[0] + hDim, ipp = ijk[0] + dim; + int j = ijk[1], jp = ijk[1] + hDim, jpp = ijk[1] + dim; + int k = ijk[2], kp = ijk[2] + hDim, kpp = ijk[2] + dim; + + // edge 1 + coord.reset(ip, j, k); + m = accessor.getValue(coord) < isovalue; + if (p[0] != m && p[1] != m) return true; + + // edge 2 + coord.reset(ipp, j, kp); + m = accessor.getValue(coord) < isovalue; + if (p[1] != m && p[2] != m) return true; + + // edge 3 + coord.reset(ip, j, kpp); + m = accessor.getValue(coord) < isovalue; + if (p[2] != m && p[3] != m) return true; + + // edge 4 + coord.reset(i, j, kp); + m = accessor.getValue(coord) < isovalue; + if (p[0] != m && p[3] != m) return true; + + // edge 5 + coord.reset(ip, jpp, k); + m = accessor.getValue(coord) < isovalue; + if (p[4] != m && p[5] != m) return true; + + // edge 6 + coord.reset(ipp, jpp, kp); + m = accessor.getValue(coord) < isovalue; + if (p[5] != m && p[6] != m) return true; + + // edge 7 + coord.reset(ip, jpp, kpp); + m = accessor.getValue(coord) < isovalue; + if (p[6] != m && p[7] != m) return true; + + // edge 8 + coord.reset(i, jpp, kp); + m = accessor.getValue(coord) < isovalue; + if (p[7] != m && p[4] != m) return true; + + // edge 9 + coord.reset(i, jp, k); + m = accessor.getValue(coord) < isovalue; + if (p[0] != m && p[4] != m) return true; + + // edge 10 + coord.reset(ipp, jp, k); + m = accessor.getValue(coord) < isovalue; + if (p[1] != m && p[5] != m) return true; + + // edge 11 + coord.reset(ipp, jp, kpp); + m = accessor.getValue(coord) < isovalue; + if (p[2] != m && p[6] != m) return true; + + + // edge 12 + coord.reset(i, jp, kpp); + m = accessor.getValue(coord) < isovalue; + if (p[3] != m && p[7] != m) return true; + + + // Evaluate faces + + // face 1 + coord.reset(ip, jp, k); + m = accessor.getValue(coord) < isovalue; + if (p[0] != m && p[1] != m && p[4] != m && p[5] != m) return true; + + // face 2 + coord.reset(ipp, jp, kp); + m = accessor.getValue(coord) < isovalue; + if (p[1] != m && p[2] != m && p[5] != m && p[6] != m) return true; + + // face 3 + coord.reset(ip, jp, kpp); + m = accessor.getValue(coord) < isovalue; + if (p[2] != m && p[3] != m && p[6] != m && p[7] != m) return true; + + // face 4 + coord.reset(i, jp, kp); + m = accessor.getValue(coord) < isovalue; + if (p[0] != m && p[3] != m && p[4] != m && p[7] != m) return true; + + // face 5 + coord.reset(ip, j, kp); + m = accessor.getValue(coord) < isovalue; + if (p[0] != m && p[1] != m && p[2] != m && p[3] != m) return true; + + // face 6 + coord.reset(ip, jpp, kp); + m = accessor.getValue(coord) < isovalue; + if (p[4] != m && p[5] != m && p[6] != m && p[7] != m) return true; + + // test cube center + coord.reset(ip, jp, kp); + m = accessor.getValue(coord) < isovalue; + if (p[0] != m && p[1] != m && p[2] != m && p[3] != m && + p[4] != m && p[5] != m && p[6] != m && p[7] != m) return true; + + return false; +} + + +//////////////////////////////////////// + + +template +inline void +mergeVoxels(LeafType& leaf, const Coord& start, int dim, int regionId) +{ + Coord ijk, end = start; + end[0] += dim; + end[1] += dim; + end[2] += dim; + + for (ijk[0] = start[0]; ijk[0] < end[0]; ++ijk[0]) { + for (ijk[1] = start[1]; ijk[1] < end[1]; ++ijk[1]) { + for (ijk[2] = start[2]; ijk[2] < end[2]; ++ijk[2]) { + if(leaf.isValueOn(ijk)) leaf.setValue(ijk, regionId); + } + } + } +} + + +// Note that we must use ValueType::value_type or else Visual C++ gets confused +// thinking that it is a constructor. +template +inline bool +isMergable(LeafType& leaf, const Coord& start, int dim, + typename LeafType::ValueType::value_type adaptivity) +{ + if (adaptivity < 1e-6) return false; + + typedef typename LeafType::ValueType VecT; + Coord ijk, end = start; + end[0] += dim; + end[1] += dim; + end[2] += dim; + + std::vector norms; + for (ijk[0] = start[0]; ijk[0] < end[0]; ++ijk[0]) { + for (ijk[1] = start[1]; ijk[1] < end[1]; ++ijk[1]) { + for (ijk[2] = start[2]; ijk[2] < end[2]; ++ijk[2]) { + + if(!leaf.isValueOn(ijk)) continue; + norms.push_back(leaf.getValue(ijk)); + } + } + } + + size_t N = norms.size(); + for (size_t ni = 0; ni < N; ++ni) { + VecT n_i = norms[ni]; + for (size_t nj = 0; nj < N; ++nj) { + VecT n_j = norms[nj]; + if ((1.0 - n_i.dot(n_j)) > adaptivity) return false; + } + } + return true; +} + + +//////////////////////////////////////// + + +template +class SignData +{ +public: + typedef typename TreeT::ValueType ValueT; + typedef tree::ValueAccessor AccessorT; + + typedef typename TreeT::template ValueConverter::Type IntTreeT; + typedef tree::ValueAccessor IntAccessorT; + + typedef typename TreeT::template ValueConverter::Type Int16TreeT; + typedef tree::ValueAccessor Int16AccessorT; + + ////////// + + + SignData(const TreeT& distTree, const LeafManagerT& leafs, ValueT iso); + + void run(bool threaded = true); + + typename Int16TreeT::Ptr signTree() const { return mSignTree; } + typename IntTreeT::Ptr idxTree() const { return mIdxTree; } + + ////////// + + SignData(SignData&, tbb::split); + void operator()(const tbb::blocked_range&); + void join(const SignData& rhs) + { + mSignTree->merge(*rhs.mSignTree); + mIdxTree->merge(*rhs.mIdxTree); + } + +private: + + const TreeT& mDistTree; + AccessorT mDistAcc; + + const LeafManagerT& mLeafs; + ValueT mIsovalue; + + typename Int16TreeT::Ptr mSignTree; + Int16AccessorT mSignAcc; + + typename IntTreeT::Ptr mIdxTree; + IntAccessorT mIdxAcc; + +}; + + +template +SignData::SignData(const TreeT& distTree, + const LeafManagerT& leafs, ValueT iso) + : mDistTree(distTree) + , mDistAcc(mDistTree) + , mLeafs(leafs) + , mIsovalue(iso) + , mSignTree(new Int16TreeT(0)) + , mSignAcc(*mSignTree) + , mIdxTree(new IntTreeT(int(util::INVALID_IDX))) + , mIdxAcc(*mIdxTree) +{ +} + + +template +SignData::SignData(SignData& rhs, tbb::split) + : mDistTree(rhs.mDistTree) + , mDistAcc(mDistTree) + , mLeafs(rhs.mLeafs) + , mIsovalue(rhs.mIsovalue) + , mSignTree(new Int16TreeT(0)) + , mSignAcc(*mSignTree) + , mIdxTree(new IntTreeT(int(util::INVALID_IDX))) + , mIdxAcc(*mIdxTree) +{ +} + + +template +void +SignData::run(bool threaded) +{ + if (threaded) tbb::parallel_reduce(mLeafs.getRange(), *this); + else (*this)(mLeafs.getRange()); +} + +template +void +SignData::operator()(const tbb::blocked_range& range) +{ + typedef typename Int16TreeT::LeafNodeType Int16LeafT; + typedef typename IntTreeT::LeafNodeType IntLeafT; + typename LeafManagerT::TreeType::LeafNodeType::ValueOnCIter iter; + unsigned char signs, face; + Coord ijk, coord; + + typename internal::UniquePtr::type signLeafPt(new Int16LeafT(ijk, 0)); + + for (size_t n = range.begin(); n != range.end(); ++n) { + + bool collectedData = false; + + coord = mLeafs.leaf(n).origin(); + + if (!signLeafPt.get()) signLeafPt.reset(new Int16LeafT(coord, 0)); + else signLeafPt->setOrigin(coord); + + const typename TreeT::LeafNodeType *leafPt = mDistAcc.probeConstLeaf(coord); + + coord.offset(TreeT::LeafNodeType::DIM - 1); + + for (iter = mLeafs.leaf(n).cbeginValueOn(); iter; ++iter) { + + ijk = iter.getCoord(); + + if (leafPt && ijk[0] < coord[0] && ijk[1] < coord[1] && ijk[2] < coord[2]) { + signs = evalCellSigns(*leafPt, iter.pos(), mIsovalue); + } else { + signs = evalCellSigns(mDistAcc, ijk, mIsovalue); + } + + if (signs != 0 && signs != 0xFF) { + Int16 flags = (signs & 0x1) ? INSIDE : 0; + + if (bool(signs & 0x1) != bool(signs & 0x2)) flags |= XEDGE; + if (bool(signs & 0x1) != bool(signs & 0x10)) flags |= YEDGE; + if (bool(signs & 0x1) != bool(signs & 0x8)) flags |= ZEDGE; + + face = internal::sAmbiguousFace[signs]; + if (face != 0) correctCellSigns(signs, face, mDistAcc, ijk, mIsovalue); + + flags |= Int16(signs); + + signLeafPt->setValue(ijk, flags); + collectedData = true; + } + } + + if (collectedData) { + + IntLeafT* idxLeaf = mIdxAcc.touchLeaf(coord); + idxLeaf->topologyUnion(*signLeafPt); + typename IntLeafT::ValueOnIter it = idxLeaf->beginValueOn(); + for (; it; ++it) { + it.setValue(0); + } + + mSignAcc.addLeaf(signLeafPt.release()); + } + } +} + + +//////////////////////////////////////// + + +/// @brief Counts the total number of points per leaf, accounts for cells with multiple points. +class CountPoints +{ +public: + CountPoints(std::vector& pointList) : mPointList(pointList) {} + + template + void operator()(LeafNodeType &leaf, size_t leafIndex) const + { + size_t points = 0; + + typename LeafNodeType::ValueOnCIter iter = leaf.cbeginValueOn(); + for (; iter; ++iter) { + points += size_t(sEdgeGroupTable[(SIGNS & iter.getValue())][0]); + } + + mPointList[leafIndex] = points; + } + +private: + std::vector& mPointList; +}; + + +/// @brief Computes the point list indices for the index tree. +template +class MapPoints +{ +public: + typedef tree::ValueAccessor Int16AccessorT; + + MapPoints(std::vector& pointList, const Int16TreeT& signTree) + : mPointList(pointList) + , mSignAcc(signTree) + { + } + + template + void operator()(LeafNodeType &leaf, size_t leafIndex) const + { + size_t ptnIdx = mPointList[leafIndex]; + typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); + + const typename Int16TreeT::LeafNodeType *signLeafPt = + mSignAcc.probeConstLeaf(leaf.origin()); + + for (; iter; ++iter) { + iter.setValue(ptnIdx); + unsigned signs = SIGNS & signLeafPt->getValue(iter.pos()); + ptnIdx += size_t(sEdgeGroupTable[signs][0]); + } + } + +private: + std::vector& mPointList; + Int16AccessorT mSignAcc; +}; + + +/// @brief Counts the total number of points per collapsed region +template +class CountRegions +{ +public: + typedef tree::ValueAccessor IntAccessorT; + typedef typename IntTreeT::LeafNodeType IntLeafT; + + CountRegions(IntTreeT& idxTree, std::vector& regions) + : mIdxAcc(idxTree) + , mRegions(regions) + { + } + + template + void operator()(LeafNodeType &leaf, size_t leafIndex) const + { + + size_t regions = 0; + + IntLeafT tmpLeaf(*mIdxAcc.probeConstLeaf(leaf.origin())); + + typename IntLeafT::ValueOnIter iter = tmpLeaf.beginValueOn(); + for (; iter; ++iter) { + if(iter.getValue() == 0) { + iter.setValueOff(); + regions += size_t(sEdgeGroupTable[(SIGNS & leaf.getValue(iter.pos()))][0]); + } + } + + int onVoxelCount = int(tmpLeaf.onVoxelCount()); + while (onVoxelCount > 0) { + ++regions; + iter = tmpLeaf.beginValueOn(); + int regionId = iter.getValue(); + for (; iter; ++iter) { + if (iter.getValue() == regionId) { + iter.setValueOff(); + --onVoxelCount; + } + } + } + + mRegions[leafIndex] = regions; + } + +private: + IntAccessorT mIdxAcc; + std::vector& mRegions; +}; + + +//////////////////////////////////////// + + +// @brief linear interpolation. +inline double evalRoot(double v0, double v1, double iso) { return (iso - v0) / (v1 - v0); } + + +/// @brief Extracts the eight corner values for leaf inclusive cells. +template +inline void +collectCornerValues(const LeafT& leaf, const Index offset, std::vector& values) +{ + values[0] = double(leaf.getValue(offset)); // i, j, k + values[3] = double(leaf.getValue(offset + 1)); // i, j, k+1 + values[4] = double(leaf.getValue(offset + LeafT::DIM)); // i, j+1, k + values[7] = double(leaf.getValue(offset + LeafT::DIM + 1)); // i, j+1, k+1 + values[1] = double(leaf.getValue(offset + (LeafT::DIM * LeafT::DIM))); // i+1, j, k + values[2] = double(leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + 1)); // i+1, j, k+1 + values[5] = double(leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + LeafT::DIM)); // i+1, j+1, k + values[6] = double(leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + LeafT::DIM + 1)); // i+1, j+1, k+1 +} + + +/// @brief Extracts the eight corner values for a cell starting at the given @ijk coordinate. +template +inline void +collectCornerValues(const AccessorT& acc, const Coord& ijk, std::vector& values) +{ + Coord coord = ijk; + values[0] = double(acc.getValue(coord)); // i, j, k + + coord[0] += 1; + values[1] = double(acc.getValue(coord)); // i+1, j, k + + coord[2] += 1; + values[2] = double(acc.getValue(coord)); // i+i, j, k+1 + + coord[0] = ijk[0]; + values[3] = double(acc.getValue(coord)); // i, j, k+1 + + coord[1] += 1; coord[2] = ijk[2]; + values[4] = double(acc.getValue(coord)); // i, j+1, k + + coord[0] += 1; + values[5] = double(acc.getValue(coord)); // i+1, j+1, k + + coord[2] += 1; + values[6] = double(acc.getValue(coord)); // i+1, j+1, k+1 + + coord[0] = ijk[0]; + values[7] = double(acc.getValue(coord)); // i, j+1, k+1 +} + + +/// @brief Computes the average cell point for a given edge group. +inline Vec3d +computePoint(const std::vector& values, unsigned char signs, + unsigned char edgeGroup, double iso) +{ + Vec3d avg(0.0, 0.0, 0.0); + int samples = 0; + + if (sEdgeGroupTable[signs][1] == edgeGroup) { // Edged: 0 - 1 + avg[0] += evalRoot(values[0], values[1], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][2] == edgeGroup) { // Edged: 1 - 2 + avg[0] += 1.0; + avg[2] += evalRoot(values[1], values[2], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][3] == edgeGroup) { // Edged: 3 - 2 + avg[0] += evalRoot(values[3], values[2], iso); + avg[2] += 1.0; + ++samples; + } + + if (sEdgeGroupTable[signs][4] == edgeGroup) { // Edged: 0 - 3 + avg[2] += evalRoot(values[0], values[3], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][5] == edgeGroup) { // Edged: 4 - 5 + avg[0] += evalRoot(values[4], values[5], iso); + avg[1] += 1.0; + ++samples; + } + + if (sEdgeGroupTable[signs][6] == edgeGroup) { // Edged: 5 - 6 + avg[0] += 1.0; + avg[1] += 1.0; + avg[2] += evalRoot(values[5], values[6], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][7] == edgeGroup) { // Edged: 7 - 6 + avg[0] += evalRoot(values[7], values[6], iso); + avg[1] += 1.0; + avg[2] += 1.0; + ++samples; + } + + if (sEdgeGroupTable[signs][8] == edgeGroup) { // Edged: 4 - 7 + avg[1] += 1.0; + avg[2] += evalRoot(values[4], values[7], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][9] == edgeGroup) { // Edged: 0 - 4 + avg[1] += evalRoot(values[0], values[4], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][10] == edgeGroup) { // Edged: 1 - 5 + avg[0] += 1.0; + avg[1] += evalRoot(values[1], values[5], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][11] == edgeGroup) { // Edged: 2 - 6 + avg[0] += 1.0; + avg[1] += evalRoot(values[2], values[6], iso); + avg[2] += 1.0; + ++samples; + } + + if (sEdgeGroupTable[signs][12] == edgeGroup) { // Edged: 3 - 7 + avg[1] += evalRoot(values[3], values[7], iso); + avg[2] += 1.0; + ++samples; + } + + if (samples > 1) { + double w = 1.0 / double(samples); + avg[0] *= w; + avg[1] *= w; + avg[2] *= w; + } + + return avg; +} + + +/// @brief Computes the average cell point for a given edge group, ignoring edge +/// samples present in the @c signsMask configuration. +inline int +computeMaskedPoint(Vec3d& avg, const std::vector& values, unsigned char signs, + unsigned char signsMask, unsigned char edgeGroup, double iso) +{ + avg = Vec3d(0.0, 0.0, 0.0); + int samples = 0; + + if (sEdgeGroupTable[signs][1] == edgeGroup + && sEdgeGroupTable[signsMask][1] == 0) { // Edged: 0 - 1 + avg[0] += evalRoot(values[0], values[1], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][2] == edgeGroup + && sEdgeGroupTable[signsMask][2] == 0) { // Edged: 1 - 2 + avg[0] += 1.0; + avg[2] += evalRoot(values[1], values[2], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][3] == edgeGroup + && sEdgeGroupTable[signsMask][3] == 0) { // Edged: 3 - 2 + avg[0] += evalRoot(values[3], values[2], iso); + avg[2] += 1.0; + ++samples; + } + + if (sEdgeGroupTable[signs][4] == edgeGroup + && sEdgeGroupTable[signsMask][4] == 0) { // Edged: 0 - 3 + avg[2] += evalRoot(values[0], values[3], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][5] == edgeGroup + && sEdgeGroupTable[signsMask][5] == 0) { // Edged: 4 - 5 + avg[0] += evalRoot(values[4], values[5], iso); + avg[1] += 1.0; + ++samples; + } + + if (sEdgeGroupTable[signs][6] == edgeGroup + && sEdgeGroupTable[signsMask][6] == 0) { // Edged: 5 - 6 + avg[0] += 1.0; + avg[1] += 1.0; + avg[2] += evalRoot(values[5], values[6], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][7] == edgeGroup + && sEdgeGroupTable[signsMask][7] == 0) { // Edged: 7 - 6 + avg[0] += evalRoot(values[7], values[6], iso); + avg[1] += 1.0; + avg[2] += 1.0; + ++samples; + } + + if (sEdgeGroupTable[signs][8] == edgeGroup + && sEdgeGroupTable[signsMask][8] == 0) { // Edged: 4 - 7 + avg[1] += 1.0; + avg[2] += evalRoot(values[4], values[7], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][9] == edgeGroup + && sEdgeGroupTable[signsMask][9] == 0) { // Edged: 0 - 4 + avg[1] += evalRoot(values[0], values[4], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][10] == edgeGroup + && sEdgeGroupTable[signsMask][10] == 0) { // Edged: 1 - 5 + avg[0] += 1.0; + avg[1] += evalRoot(values[1], values[5], iso); + ++samples; + } + + if (sEdgeGroupTable[signs][11] == edgeGroup + && sEdgeGroupTable[signsMask][11] == 0) { // Edged: 2 - 6 + avg[0] += 1.0; + avg[1] += evalRoot(values[2], values[6], iso); + avg[2] += 1.0; + ++samples; + } + + if (sEdgeGroupTable[signs][12] == edgeGroup + && sEdgeGroupTable[signsMask][12] == 0) { // Edged: 3 - 7 + avg[1] += evalRoot(values[3], values[7], iso); + avg[2] += 1.0; + ++samples; + } + + if (samples > 1) { + double w = 1.0 / double(samples); + avg[0] *= w; + avg[1] *= w; + avg[2] *= w; + } + + return samples; +} + + +/// @brief Computes the average cell point for a given edge group, by computing +/// convex weights based on the distance from the sample point @c p. +inline Vec3d +computeWeightedPoint(const Vec3d& p, const std::vector& values, + unsigned char signs, unsigned char edgeGroup, double iso) +{ + std::vector samples; + samples.reserve(8); + + std::vector weights; + weights.reserve(8); + + Vec3d avg(0.0, 0.0, 0.0); + + if (sEdgeGroupTable[signs][1] == edgeGroup) { // Edged: 0 - 1 + avg[0] = evalRoot(values[0], values[1], iso); + avg[1] = 0.0; + avg[2] = 0.0; + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + if (sEdgeGroupTable[signs][2] == edgeGroup) { // Edged: 1 - 2 + avg[0] = 1.0; + avg[1] = 0.0; + avg[2] = evalRoot(values[1], values[2], iso); + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + if (sEdgeGroupTable[signs][3] == edgeGroup) { // Edged: 3 - 2 + avg[0] = evalRoot(values[3], values[2], iso); + avg[1] = 0.0; + avg[2] = 1.0; + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + if (sEdgeGroupTable[signs][4] == edgeGroup) { // Edged: 0 - 3 + avg[0] = 0.0; + avg[1] = 0.0; + avg[2] = evalRoot(values[0], values[3], iso); + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + if (sEdgeGroupTable[signs][5] == edgeGroup) { // Edged: 4 - 5 + avg[0] = evalRoot(values[4], values[5], iso); + avg[1] = 1.0; + avg[2] = 0.0; + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + if (sEdgeGroupTable[signs][6] == edgeGroup) { // Edged: 5 - 6 + avg[0] = 1.0; + avg[1] = 1.0; + avg[2] = evalRoot(values[5], values[6], iso); + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + if (sEdgeGroupTable[signs][7] == edgeGroup) { // Edged: 7 - 6 + avg[0] = evalRoot(values[7], values[6], iso); + avg[1] = 1.0; + avg[2] = 1.0; + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + if (sEdgeGroupTable[signs][8] == edgeGroup) { // Edged: 4 - 7 + avg[0] = 0.0; + avg[1] = 1.0; + avg[2] = evalRoot(values[4], values[7], iso); + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + if (sEdgeGroupTable[signs][9] == edgeGroup) { // Edged: 0 - 4 + avg[0] = 0.0; + avg[1] = evalRoot(values[0], values[4], iso); + avg[2] = 0.0; + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + if (sEdgeGroupTable[signs][10] == edgeGroup) { // Edged: 1 - 5 + avg[0] = 1.0; + avg[1] = evalRoot(values[1], values[5], iso); + avg[2] = 0.0; + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + if (sEdgeGroupTable[signs][11] == edgeGroup) { // Edged: 2 - 6 + avg[0] = 1.0; + avg[1] = evalRoot(values[2], values[6], iso); + avg[2] = 1.0; + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + if (sEdgeGroupTable[signs][12] == edgeGroup) { // Edged: 3 - 7 + avg[0] = 0.0; + avg[1] = evalRoot(values[3], values[7], iso); + avg[2] = 1.0; + + samples.push_back(avg); + weights.push_back((avg-p).lengthSqr()); + } + + + double minWeight = std::numeric_limits::max(); + double maxWeight = -std::numeric_limits::max(); + + for (size_t i = 0, I = weights.size(); i < I; ++i) { + minWeight = std::min(minWeight, weights[i]); + maxWeight = std::max(maxWeight, weights[i]); + } + + const double offset = maxWeight + minWeight * 0.1; + for (size_t i = 0, I = weights.size(); i < I; ++i) { + weights[i] = offset - weights[i]; + } + + + double weightSum = 0.0; + for (size_t i = 0, I = weights.size(); i < I; ++i) { + weightSum += weights[i]; + } + + avg[0] = 0.0; + avg[1] = 0.0; + avg[2] = 0.0; + + if (samples.size() > 1) { + for (size_t i = 0, I = samples.size(); i < I; ++i) { + avg += samples[i] * (weights[i] / weightSum); + } + } else { + avg = samples.front(); + } + + return avg; +} + + +/// @brief Computes the average cell points defined by the sign configuration +/// @c signs and the given corner values @c values. +inline void +computeCellPoints(std::vector& points, + const std::vector& values, unsigned char signs, double iso) +{ + for (size_t n = 1, N = sEdgeGroupTable[signs][0] + 1; n < N; ++n) { + points.push_back(computePoint(values, signs, n, iso)); + } +} + + +/// @brief Given a sign configuration @c lhsSigns and an edge group @c groupId, +/// finds the corresponding edge group in a different sign configuration +/// @c rhsSigns. Returns -1 if no match is found. +inline int +matchEdgeGroup(unsigned char groupId, unsigned char lhsSigns, unsigned char rhsSigns) +{ + int id = -1; + for (size_t i = 1; i <= 12; ++i) { + if (sEdgeGroupTable[lhsSigns][i] == groupId && sEdgeGroupTable[rhsSigns][i] != 0) { + id = sEdgeGroupTable[rhsSigns][i]; + break; + } + } + return id; +} + + +/// @brief Computes the average cell points defined by the sign configuration +/// @c signs and the given corner values @c values. Combines data from +/// two different level sets to eliminate seam lines when meshing +/// fractured segments. +inline void +computeCellPoints(std::vector& points, std::vector& weightedPointMask, + const std::vector& lhsValues, const std::vector& rhsValues, + unsigned char lhsSigns, unsigned char rhsSigns, + double iso, size_t pointIdx, const boost::scoped_array& seamPoints) +{ + for (size_t n = 1, N = sEdgeGroupTable[lhsSigns][0] + 1; n < N; ++n) { + + int id = matchEdgeGroup(n, lhsSigns, rhsSigns); + + if (id != -1) { + + const unsigned char e(id); + uint32_t& quantizedPoint = seamPoints[pointIdx + (id - 1)]; + + if ((quantizedPoint & MASK_DIRTY_BIT) && !(quantizedPoint & MASK_INVALID_BIT)) { + Vec3d p = unpackPoint(quantizedPoint); + points.push_back(computeWeightedPoint(p, rhsValues, rhsSigns, e, iso)); + weightedPointMask.push_back(true); + } else { + points.push_back(computePoint(rhsValues, rhsSigns, e, iso)); + weightedPointMask.push_back(false); + } + + } else { + points.push_back(computePoint(lhsValues, lhsSigns, n, iso)); + weightedPointMask.push_back(false); + } + } +} + + +template +class GenPoints +{ +public: + typedef tree::ValueAccessor AccessorT; + + typedef typename TreeT::template ValueConverter::Type IntTreeT; + typedef tree::ValueAccessor IntAccessorT; + typedef tree::ValueAccessor IntCAccessorT; + + typedef typename TreeT::template ValueConverter::Type Int16TreeT; + typedef tree::ValueAccessor Int16CAccessorT; + + typedef boost::scoped_array QuantizedPointList; + + ////////// + + + GenPoints(const LeafManagerT& signLeafs, const TreeT& distTree, + IntTreeT& idxTree, PointList& points, std::vector& indices, + const math::Transform& xform, double iso); + + void run(bool threaded = true); + + void setRefData(const Int16TreeT* refSignTree = NULL, const TreeT* refDistTree = NULL, + IntTreeT* refIdxTree = NULL, const QuantizedPointList* seamPoints = NULL, + std::vector* mSeamPointMaskPt = NULL); + + ////////// + + + void operator()(const tbb::blocked_range&) const; + +private: + const LeafManagerT& mSignLeafs; + + AccessorT mDistAcc; + IntTreeT& mIdxTree; + + PointList& mPoints; + std::vector& mIndices; + const math::Transform& mTransform; + const double mIsovalue; + + // reference data + const Int16TreeT *mRefSignTreePt; + const TreeT* mRefDistTreePt; + const IntTreeT* mRefIdxTreePt; + const QuantizedPointList* mSeamPointsPt; + std::vector* mSeamPointMaskPt; +}; + + +template +GenPoints::GenPoints(const LeafManagerT& signLeafs, + const TreeT& distTree, IntTreeT& idxTree, PointList& points, + std::vector& indices, const math::Transform& xform, double iso) + : mSignLeafs(signLeafs) + , mDistAcc(distTree) + , mIdxTree(idxTree) + , mPoints(points) + , mIndices(indices) + , mTransform(xform) + , mIsovalue(iso) + , mRefSignTreePt(NULL) + , mRefDistTreePt(NULL) + , mRefIdxTreePt(NULL) + , mSeamPointsPt(NULL) + , mSeamPointMaskPt(NULL) +{ +} + + +template +void +GenPoints::run(bool threaded) +{ + if (threaded) tbb::parallel_for(mSignLeafs.getRange(), *this); + else (*this)(mSignLeafs.getRange()); +} + + +template +void +GenPoints::setRefData(const Int16TreeT *refSignTree, const TreeT *refDistTree, + IntTreeT* refIdxTree, const QuantizedPointList* seamPoints, std::vector* seamPointMask) +{ + mRefSignTreePt = refSignTree; + mRefDistTreePt = refDistTree; + mRefIdxTreePt = refIdxTree; + mSeamPointsPt = seamPoints; + mSeamPointMaskPt = seamPointMask; +} + + +template +void +GenPoints::operator()( + const tbb::blocked_range& range) const +{ + typename IntTreeT::LeafNodeType::ValueOnIter iter; + unsigned char signs, refSigns; + Index offset; + Coord ijk, coord; + std::vector points(4); + std::vector weightedPointMask(4); + std::vector values(8), refValues(8); + + + IntAccessorT idxAcc(mIdxTree); + + // reference data accessors + boost::scoped_ptr refSignAcc; + if (mRefSignTreePt) refSignAcc.reset(new Int16CAccessorT(*mRefSignTreePt)); + + boost::scoped_ptr refIdxAcc; + if (mRefIdxTreePt) refIdxAcc.reset(new IntCAccessorT(*mRefIdxTreePt)); + + boost::scoped_ptr refDistAcc; + if (mRefDistTreePt) refDistAcc.reset(new AccessorT(*mRefDistTreePt)); + + + for (size_t n = range.begin(); n != range.end(); ++n) { + + coord = mSignLeafs.leaf(n).origin(); + + const typename TreeT::LeafNodeType *leafPt = mDistAcc.probeConstLeaf(coord); + typename IntTreeT::LeafNodeType *idxLeafPt = idxAcc.probeLeaf(coord); + + + // reference data leafs + const typename Int16TreeT::LeafNodeType *refSignLeafPt = NULL; + if (refSignAcc) refSignLeafPt = refSignAcc->probeConstLeaf(coord); + + const typename IntTreeT::LeafNodeType *refIdxLeafPt = NULL; + if (refIdxAcc) refIdxLeafPt = refIdxAcc->probeConstLeaf(coord); + + const typename TreeT::LeafNodeType *refDistLeafPt = NULL; + if (refDistAcc) refDistLeafPt = refDistAcc->probeConstLeaf(coord); + + + // generate cell points + size_t ptnIdx = mIndices[n]; + coord.offset(TreeT::LeafNodeType::DIM - 1); + + + + for (iter = idxLeafPt->beginValueOn(); iter; ++iter) { + + if(iter.getValue() != 0) continue; + + iter.setValue(ptnIdx); + iter.setValueOff(); + offset = iter.pos(); + ijk = iter.getCoord(); + + const bool inclusiveCell = ijk[0] < coord[0] && ijk[1] < coord[1] && ijk[2] < coord[2]; + + const Int16& flags = mSignLeafs.leaf(n).getValue(offset); + signs = SIGNS & flags; + refSigns = 0; + + if ((flags & SEAM) && refSignLeafPt && refIdxLeafPt) { + if (refSignLeafPt->isValueOn(offset)) { + refSigns = (SIGNS & refSignLeafPt->getValue(offset)); + } + } + + + if (inclusiveCell) collectCornerValues(*leafPt, offset, values); + else collectCornerValues(mDistAcc, ijk, values); + + + points.clear(); + weightedPointMask.clear(); + + if (refSigns == 0) { + computeCellPoints(points, values, signs, mIsovalue); + } else { + + if (inclusiveCell) collectCornerValues(*refDistLeafPt, offset, refValues); + else collectCornerValues(*refDistAcc, ijk, refValues); + + computeCellPoints(points, weightedPointMask, values, refValues, signs, refSigns, + mIsovalue, refIdxLeafPt->getValue(offset), *mSeamPointsPt); + } + + + for (size_t i = 0, I = points.size(); i < I; ++i) { + + // offset by cell-origin + points[i][0] += double(ijk[0]); + points[i][1] += double(ijk[1]); + points[i][2] += double(ijk[2]); + + + points[i] = mTransform.indexToWorld(points[i]); + + mPoints[ptnIdx][0] = float(points[i][0]); + mPoints[ptnIdx][1] = float(points[i][1]); + mPoints[ptnIdx][2] = float(points[i][2]); + + if (mSeamPointMaskPt && !weightedPointMask.empty() && weightedPointMask[i]) { + (*mSeamPointMaskPt)[ptnIdx] = 1; + } + + ++ptnIdx; + } + } + + // generate collapsed region points + int onVoxelCount = int(idxLeafPt->onVoxelCount()); + while (onVoxelCount > 0) { + + iter = idxLeafPt->beginValueOn(); + int regionId = iter.getValue(), count = 0; + + Vec3d avg(0.0), point; + + for (; iter; ++iter) { + if (iter.getValue() != regionId) continue; + + iter.setValue(ptnIdx); + iter.setValueOff(); + --onVoxelCount; + + ijk = iter.getCoord(); + offset = iter.pos(); + + signs = (SIGNS & mSignLeafs.leaf(n).getValue(offset)); + + if (ijk[0] < coord[0] && ijk[1] < coord[1] && ijk[2] < coord[2]) { + collectCornerValues(*leafPt, offset, values); + } else { + collectCornerValues(mDistAcc, ijk, values); + } + + points.clear(); + computeCellPoints(points, values, signs, mIsovalue); + + avg[0] += double(ijk[0]) + points[0][0]; + avg[1] += double(ijk[1]) + points[0][1]; + avg[2] += double(ijk[2]) + points[0][2]; + + ++count; + } + + + if (count > 1) { + double w = 1.0 / double(count); + avg[0] *= w; + avg[1] *= w; + avg[2] *= w; + } + + avg = mTransform.indexToWorld(avg); + + mPoints[ptnIdx][0] = float(avg[0]); + mPoints[ptnIdx][1] = float(avg[1]); + mPoints[ptnIdx][2] = float(avg[2]); + + ++ptnIdx; + } + } +} + + +//////////////////////////////////////// + + +template +class SeamWeights +{ +public: + typedef tree::ValueAccessor AccessorT; + + typedef typename TreeT::template ValueConverter::Type IntTreeT; + typedef tree::ValueAccessor IntAccessorT; + + typedef typename TreeT::template ValueConverter::Type Int16TreeT; + typedef tree::ValueAccessor Int16AccessorT; + + typedef boost::scoped_array QuantizedPointList; + + ////////// + + SeamWeights(const TreeT& distTree, const Int16TreeT& refSignTree, + IntTreeT& refIdxTree, QuantizedPointList& points, double iso); + + template + void operator()(LeafNodeType &signLeaf, size_t leafIndex) const; + +private: + AccessorT mDistAcc; + Int16AccessorT mRefSignAcc; + IntAccessorT mRefIdxAcc; + + QuantizedPointList& mPoints; + const double mIsovalue; +}; + + +template +SeamWeights::SeamWeights(const TreeT& distTree, const Int16TreeT& refSignTree, + IntTreeT& refIdxTree, QuantizedPointList& points, double iso) + : mDistAcc(distTree) + , mRefSignAcc(refSignTree) + , mRefIdxAcc(refIdxTree) + , mPoints(points) + , mIsovalue(iso) +{ +} + + +template +template +void +SeamWeights::operator()(LeafNodeType &signLeaf, size_t /*leafIndex*/) const +{ + Coord coord = signLeaf.origin(); + const typename Int16TreeT::LeafNodeType *refSignLeafPt = mRefSignAcc.probeConstLeaf(coord); + + if (!refSignLeafPt) return; + + const typename TreeT::LeafNodeType *distLeafPt = mDistAcc.probeConstLeaf(coord); + const typename IntTreeT::LeafNodeType *refIdxLeafPt = mRefIdxAcc.probeConstLeaf(coord); + + std::vector values(8); + unsigned char lhsSigns, rhsSigns; + Vec3d point; + Index offset; + + Coord ijk; + coord.offset(TreeT::LeafNodeType::DIM - 1); + + typename LeafNodeType::ValueOnCIter iter = signLeaf.cbeginValueOn(); + for (; iter; ++iter) { + + offset = iter.pos(); + ijk = iter.getCoord(); + + const bool inclusiveCell = ijk[0] < coord[0] && ijk[1] < coord[1] && ijk[2] < coord[2]; + + if ((iter.getValue() & SEAM) && refSignLeafPt->isValueOn(offset)) { + + lhsSigns = SIGNS & iter.getValue(); + rhsSigns = SIGNS & refSignLeafPt->getValue(offset); + + + if (inclusiveCell) { + collectCornerValues(*distLeafPt, offset, values); + } else { + collectCornerValues(mDistAcc, ijk, values); + } + + + for (size_t n = 1, N = sEdgeGroupTable[lhsSigns][0] + 1; n < N; ++n) { + + int id = matchEdgeGroup(n, lhsSigns, rhsSigns); + + if (id != -1) { + + uint32_t& data = mPoints[refIdxLeafPt->getValue(offset) + (id - 1)]; + + if (!(data & MASK_DIRTY_BIT)) { + + int smaples = computeMaskedPoint(point, values, lhsSigns, rhsSigns, n, mIsovalue); + + if (smaples > 0) data = packPoint(point); + else data = MASK_INVALID_BIT; + + data |= MASK_DIRTY_BIT; + } + } + } + } + } +} + + +//////////////////////////////////////// + + +template +class MergeVoxelRegions +{ +public: + typedef typename TreeT::ValueType ValueT; + typedef tree::ValueAccessor AccessorT; + + typedef typename TreeT::template ValueConverter::Type IntTreeT; + typedef tree::ValueAccessor IntAccessorT; + + typedef typename TreeT::template ValueConverter::Type BoolTreeT; + + typedef typename LeafManagerT::TreeType::template ValueConverter::Type Int16TreeT; + typedef tree::ValueAccessor Int16AccessorT; + + typedef typename TreeT::template ValueConverter::Type FloatTreeT; + typedef Grid FloatGridT; + + + ////////// + + MergeVoxelRegions(const LeafManagerT& signLeafs, const Int16TreeT& signTree, + const TreeT& distTree, IntTreeT& idxTree, ValueT iso, ValueT adaptivity); + + void run(bool threaded = true); + + void setSpatialAdaptivity( + const math::Transform& distGridXForm, const FloatGridT& adaptivityField); + + void setAdaptivityMask(const BoolTreeT* mask); + + void setRefData(const Int16TreeT* signTree, ValueT adaptivity); + + ////////// + + + void operator()(const tbb::blocked_range&) const; + +private: + + const LeafManagerT& mSignLeafs; + + const Int16TreeT& mSignTree; + Int16AccessorT mSignAcc; + + const TreeT& mDistTree; + AccessorT mDistAcc; + + IntTreeT& mIdxTree; + ValueT mIsovalue, mSurfaceAdaptivity, mInternalAdaptivity; + + const math::Transform* mTransform; + const FloatGridT* mAdaptivityGrid; + const BoolTreeT* mMask; + + const Int16TreeT* mRefSignTree; +}; + +template +MergeVoxelRegions::MergeVoxelRegions( + const LeafManagerT& signLeafs, const Int16TreeT& signTree, + const TreeT& distTree, IntTreeT& idxTree, ValueT iso, ValueT adaptivity) + : mSignLeafs(signLeafs) + , mSignTree(signTree) + , mSignAcc(mSignTree) + , mDistTree(distTree) + , mDistAcc(mDistTree) + , mIdxTree(idxTree) + , mIsovalue(iso) + , mSurfaceAdaptivity(adaptivity) + , mInternalAdaptivity(adaptivity) + , mTransform(NULL) + , mAdaptivityGrid(NULL) + , mMask(NULL) + , mRefSignTree(NULL) +{ +} + + +template +void +MergeVoxelRegions::run(bool threaded) +{ + if (threaded) tbb::parallel_for(mSignLeafs.getRange(), *this); + else (*this)(mSignLeafs.getRange()); +} + + +template +void +MergeVoxelRegions::setSpatialAdaptivity( + const math::Transform& distGridXForm, const FloatGridT& adaptivityField) +{ + mTransform = &distGridXForm; + mAdaptivityGrid = &adaptivityField; +} + + +template +void +MergeVoxelRegions::setAdaptivityMask(const BoolTreeT* mask) +{ + mMask = mask; +} + +template +void +MergeVoxelRegions::setRefData(const Int16TreeT* signTree, ValueT adaptivity) +{ + mRefSignTree = signTree; + mInternalAdaptivity = adaptivity; +} + + +template +void +MergeVoxelRegions::operator()(const tbb::blocked_range& range) const +{ + typedef math::Vec3 Vec3T; + + typedef typename TreeT::LeafNodeType LeafT; + typedef typename IntTreeT::LeafNodeType IntLeafT; + typedef typename BoolTreeT::LeafNodeType BoolLeafT; + typedef typename LeafT::template ValueConverter::Type Vec3LeafT; + + const int LeafDim = LeafT::DIM; + + IntAccessorT idxAcc(mIdxTree); + + typename LeafManagerT::TreeType::LeafNodeType::ValueOnCIter iter; + + typedef typename tree::ValueAccessor FloatTreeCAccessorT; + boost::scoped_ptr adaptivityAcc; + if (mAdaptivityGrid) { + adaptivityAcc.reset(new FloatTreeCAccessorT(mAdaptivityGrid->tree())); + } + + typedef typename tree::ValueAccessor Int16TreeCAccessorT; + boost::scoped_ptr refAcc; + if (mRefSignTree) { + refAcc.reset(new Int16TreeCAccessorT(*mRefSignTree)); + } + + typedef typename tree::ValueAccessor BoolTreeCAccessorT; + boost::scoped_ptr maskAcc; + if (mMask) { + maskAcc.reset(new BoolTreeCAccessorT(*mMask)); + } + + // Allocate reusable leaf buffers + BoolLeafT mask; + Vec3LeafT gradientBuffer; + Coord ijk, nijk, coord, end; + + for (size_t n = range.begin(); n != range.end(); ++n) { + + const Coord& origin = mSignLeafs.leaf(n).origin(); + + ValueT adaptivity = mSurfaceAdaptivity; + + if (refAcc && refAcc->probeConstLeaf(origin) == NULL) { + adaptivity = mInternalAdaptivity; + } + + IntLeafT& idxLeaf = *idxAcc.probeLeaf(origin); + + end[0] = origin[0] + LeafDim; + end[1] = origin[1] + LeafDim; + end[2] = origin[2] + LeafDim; + + mask.setValuesOff(); + + // Mask off seam line adjacent voxels + if (maskAcc) { + const BoolLeafT* maskLeaf = maskAcc->probeConstLeaf(origin); + if (maskLeaf != NULL) { + typename BoolLeafT::ValueOnCIter it; + for (it = maskLeaf->cbeginValueOn(); it; ++it) { + ijk = it.getCoord(); + coord[0] = ijk[0] - (ijk[0] % 2); + coord[1] = ijk[1] - (ijk[1] % 2); + coord[2] = ijk[2] - (ijk[2] % 2); + mask.setActiveState(coord, true); + } + } + } + + + LeafT adaptivityLeaf(origin, adaptivity); + + if (mAdaptivityGrid) { + for (Index offset = 0; offset < LeafT::NUM_VALUES; ++offset) { + ijk = adaptivityLeaf.offsetToGlobalCoord(offset); + Vec3d xyz = mAdaptivityGrid->transform().worldToIndex( + mTransform->indexToWorld(ijk)); + ValueT tmpA = ValueT(adaptivityAcc->getValue(util::nearestCoord(xyz))); + adaptivityLeaf.setValueOnly(offset, tmpA * adaptivity); + } + } + + // Mask off ambiguous voxels + for (iter = mSignLeafs.leaf(n).cbeginValueOn(); iter; ++iter) { + ijk = iter.getCoord(); + coord[0] = ijk[0] - (ijk[0] % 2); + coord[1] = ijk[1] - (ijk[1] % 2); + coord[2] = ijk[2] - (ijk[2] % 2); + if(mask.isValueOn(coord)) continue; + + + + int flags = int(iter.getValue()); + unsigned char signs = SIGNS & flags; + if ((flags & SEAM) || !sAdaptable[signs] || sEdgeGroupTable[signs][0] > 1) { + mask.setActiveState(coord, true); + continue; + } + + for (int i = 0; i < 26; ++i) { + nijk = ijk + util::COORD_OFFSETS[i]; + signs = SIGNS & mSignAcc.getValue(nijk); + if (!sAdaptable[signs] || sEdgeGroupTable[signs][0] > 1) { + mask.setActiveState(coord, true); + break; + } + } + } + + int dim = 2; + // Mask off topologically ambiguous 2x2x2 voxel sub-blocks + for (ijk[0] = origin[0]; ijk[0] < end[0]; ijk[0] += dim) { + for (ijk[1] = origin[1]; ijk[1] < end[1]; ijk[1] += dim) { + for (ijk[2] = origin[2]; ijk[2] < end[2]; ijk[2] += dim) { + if (isNonManifold(mDistAcc, ijk, mIsovalue, dim)) { + mask.setActiveState(ijk, true); + } + } + } + } + + // Compute the gradient for the remaining voxels + gradientBuffer.setValuesOff(); + for (iter = mSignLeafs.leaf(n).cbeginValueOn(); iter; ++iter) { + + ijk = iter.getCoord(); + coord[0] = ijk[0] - (ijk[0] % dim); + coord[1] = ijk[1] - (ijk[1] % dim); + coord[2] = ijk[2] - (ijk[2] % dim); + if(mask.isValueOn(coord)) continue; + + Vec3T norm(math::ISGradient::result(mDistAcc, ijk)); + // Normalize (Vec3's normalize uses isApproxEqual, which uses abs and does more work) + ValueT length = norm.length(); + if (length > ValueT(1.0e-7)) { + norm *= ValueT(1.0) / length; + } + gradientBuffer.setValue(ijk, norm); + } + + int regionId = 1, next_dim = dim << 1; + + // Process the first adaptivity level. + for (ijk[0] = 0; ijk[0] < LeafDim; ijk[0] += dim) { + coord[0] = ijk[0] - (ijk[0] % next_dim); + for (ijk[1] = 0; ijk[1] < LeafDim; ijk[1] += dim) { + coord[1] = ijk[1] - (ijk[1] % next_dim); + for (ijk[2] = 0; ijk[2] < LeafDim; ijk[2] += dim) { + coord[2] = ijk[2] - (ijk[2] % next_dim); + adaptivity = adaptivityLeaf.getValue(ijk); + if (mask.isValueOn(ijk) || !isMergable(gradientBuffer, ijk, dim, adaptivity)) { + mask.setActiveState(coord, true); + continue; + } + mergeVoxels(idxLeaf, ijk, dim, regionId++); + } + } + } + + + // Process remaining adaptivity levels + for (dim = 4; dim < LeafDim; dim = dim << 1) { + next_dim = dim << 1; + coord[0] = ijk[0] - (ijk[0] % next_dim); + for (ijk[0] = origin[0]; ijk[0] < end[0]; ijk[0] += dim) { + coord[1] = ijk[1] - (ijk[1] % next_dim); + for (ijk[1] = origin[1]; ijk[1] < end[1]; ijk[1] += dim) { + coord[2] = ijk[2] - (ijk[2] % next_dim); + for (ijk[2] = origin[2]; ijk[2] < end[2]; ijk[2] += dim) { + adaptivity = adaptivityLeaf.getValue(ijk); + if (mask.isValueOn(ijk) || isNonManifold(mDistAcc, ijk, mIsovalue, dim) || + !isMergable(gradientBuffer, ijk, dim, adaptivity)) + { + mask.setActiveState(coord, true); + continue; + } + mergeVoxels(idxLeaf, ijk, dim, regionId++); + } + } + } + } + + adaptivity = adaptivityLeaf.getValue(origin); + if (!(mask.isValueOn(origin) || isNonManifold(mDistAcc, origin, mIsovalue, LeafDim)) + && isMergable(gradientBuffer, origin, LeafDim, adaptivity)) + { + mergeVoxels(idxLeaf, origin, LeafDim, regionId++); + } + } +} + + +//////////////////////////////////////// + + +// Constructs qudas +struct UniformPrimBuilder +{ + UniformPrimBuilder(): mIdx(0), mPolygonPool(NULL) {} + + void init(const size_t upperBound, PolygonPool& quadPool) + { + mPolygonPool = &quadPool; + mPolygonPool->resetQuads(upperBound); + mIdx = 0; + } + + void addPrim(const Vec4I& verts, bool reverse, char flags = 0) + { + if (!reverse) { + mPolygonPool->quad(mIdx) = verts; + } else { + Vec4I& quad = mPolygonPool->quad(mIdx); + quad[0] = verts[3]; + quad[1] = verts[2]; + quad[2] = verts[1]; + quad[3] = verts[0]; + } + mPolygonPool->quadFlags(mIdx) = flags; + ++mIdx; + } + + void done() + { + mPolygonPool->trimQuads(mIdx); + } + +private: + size_t mIdx; + PolygonPool* mPolygonPool; +}; + + +// Constructs qudas and triangles +struct AdaptivePrimBuilder +{ + AdaptivePrimBuilder() : mQuadIdx(0), mTriangleIdx(0), mPolygonPool(NULL) {} + + void init(const size_t upperBound, PolygonPool& polygonPool) + { + mPolygonPool = &polygonPool; + mPolygonPool->resetQuads(upperBound); + mPolygonPool->resetTriangles(upperBound); + + mQuadIdx = 0; + mTriangleIdx = 0; + } + + void addPrim(const Vec4I& verts, bool reverse, char flags = 0) + { + if (verts[0] != verts[1] && verts[0] != verts[2] && verts[0] != verts[3] + && verts[1] != verts[2] && verts[1] != verts[3] && verts[2] != verts[3]) { + mPolygonPool->quadFlags(mQuadIdx) = flags; + addQuad(verts, reverse); + } else if ( + verts[0] == verts[3] && + verts[1] != verts[2] && + verts[1] != verts[0] && + verts[2] != verts[0]) { + mPolygonPool->triangleFlags(mTriangleIdx) = flags; + addTriangle(verts[0], verts[1], verts[2], reverse); + } else if ( + verts[1] == verts[2] && + verts[0] != verts[3] && + verts[0] != verts[1] && + verts[3] != verts[1]) { + mPolygonPool->triangleFlags(mTriangleIdx) = flags; + addTriangle(verts[0], verts[1], verts[3], reverse); + } else if ( + verts[0] == verts[1] && + verts[2] != verts[3] && + verts[2] != verts[0] && + verts[3] != verts[0]) { + mPolygonPool->triangleFlags(mTriangleIdx) = flags; + addTriangle(verts[0], verts[2], verts[3], reverse); + } else if ( + verts[2] == verts[3] && + verts[0] != verts[1] && + verts[0] != verts[2] && + verts[1] != verts[2]) { + mPolygonPool->triangleFlags(mTriangleIdx) = flags; + addTriangle(verts[0], verts[1], verts[2], reverse); + } + } + + + void done() + { + mPolygonPool->trimQuads(mQuadIdx, /*reallocate=*/true); + mPolygonPool->trimTrinagles(mTriangleIdx, /*reallocate=*/true); + } + +private: + + void addQuad(const Vec4I& verts, bool reverse) + { + if (!reverse) { + mPolygonPool->quad(mQuadIdx) = verts; + } else { + Vec4I& quad = mPolygonPool->quad(mQuadIdx); + quad[0] = verts[3]; + quad[1] = verts[2]; + quad[2] = verts[1]; + quad[3] = verts[0]; + } + ++mQuadIdx; + } + + void addTriangle(unsigned v0, unsigned v1, unsigned v2, bool reverse) + { + Vec3I& prim = mPolygonPool->triangle(mTriangleIdx); + + prim[1] = v1; + + if (!reverse) { + prim[0] = v0; + prim[2] = v2; + } else { + prim[0] = v2; + prim[2] = v0; + } + ++mTriangleIdx; + } + + size_t mQuadIdx, mTriangleIdx; + PolygonPool *mPolygonPool; +}; + + +template +inline void +constructPolygons(Int16 flags, Int16 refFlags, const Vec4i& offsets, const Coord& ijk, + const SignAccT& signAcc, const IdxAccT& idxAcc, PrimBuilder& mesher, Index32 pointListSize) +{ + const Index32 v0 = idxAcc.getValue(ijk); + if (v0 == util::INVALID_IDX) return; + + char tag[2]; + tag[0] = (flags & SEAM) ? POLYFLAG_FRACTURE_SEAM : 0; + tag[1] = tag[0] | char(POLYFLAG_EXTERIOR); + + const bool isInside = flags & INSIDE; + + Coord coord; + openvdb::Vec4I quad; + unsigned char cell; + Index32 tmpIdx = 0; + + if (flags & XEDGE) { + + quad[0] = v0 + offsets[0]; + + // i, j-1, k + coord[0] = ijk[0]; + coord[1] = ijk[1] - 1; + coord[2] = ijk[2]; + + quad[1] = idxAcc.getValue(coord); + cell = SIGNS & signAcc.getValue(coord); + if (sEdgeGroupTable[cell][0] > 1) { + tmpIdx = quad[1] + Index32(sEdgeGroupTable[cell][5] - 1); + if (tmpIdx < pointListSize) quad[1] = tmpIdx; + } + + // i, j-1, k-1 + coord[2] -= 1; + + quad[2] = idxAcc.getValue(coord); + cell = SIGNS & signAcc.getValue(coord); + if (sEdgeGroupTable[cell][0] > 1) { + tmpIdx = quad[2] + Index32(sEdgeGroupTable[cell][7] - 1); + if (tmpIdx < pointListSize) quad[2] = tmpIdx; + } + + // i, j, k-1 + coord[1] = ijk[1]; + + quad[3] = idxAcc.getValue(coord); + cell = SIGNS & signAcc.getValue(coord); + if (sEdgeGroupTable[cell][0] > 1) { + tmpIdx = quad[3] + Index32(sEdgeGroupTable[cell][3] - 1); + if (tmpIdx < pointListSize) quad[3] = tmpIdx; + } + + if (quad[1] != util::INVALID_IDX && + quad[2] != util::INVALID_IDX && quad[3] != util::INVALID_IDX) { + mesher.addPrim(quad, isInside, tag[bool(refFlags & XEDGE)]); + } + } + + + if (flags & YEDGE) { + + quad[0] = v0 + offsets[1]; + + // i, j, k-1 + coord[0] = ijk[0]; + coord[1] = ijk[1]; + coord[2] = ijk[2] - 1; + + quad[1] = idxAcc.getValue(coord); + cell = SIGNS & signAcc.getValue(coord); + if (sEdgeGroupTable[cell][0] > 1) { + tmpIdx = quad[1] + Index32(sEdgeGroupTable[cell][12] - 1); + if (tmpIdx < pointListSize) quad[1] = tmpIdx; + } + + // i-1, j, k-1 + coord[0] -= 1; + + quad[2] = idxAcc.getValue(coord); + cell = SIGNS & signAcc.getValue(coord); + if (sEdgeGroupTable[cell][0] > 1) { + tmpIdx = quad[2] + Index32(sEdgeGroupTable[cell][11] - 1); + if (tmpIdx < pointListSize) quad[2] = tmpIdx; + } + + // i-1, j, k + coord[2] = ijk[2]; + + quad[3] = idxAcc.getValue(coord); + cell = SIGNS & signAcc.getValue(coord); + if (sEdgeGroupTable[cell][0] > 1) { + tmpIdx = quad[3] + Index32(sEdgeGroupTable[cell][10] - 1); + if (tmpIdx < pointListSize) quad[3] = tmpIdx; + } + + if (quad[1] != util::INVALID_IDX && + quad[2] != util::INVALID_IDX && quad[3] != util::INVALID_IDX) { + mesher.addPrim(quad, isInside, tag[bool(refFlags & YEDGE)]); + } + } + + if (flags & ZEDGE) { + + quad[0] = v0 + offsets[2]; + + // i, j-1, k + coord[0] = ijk[0]; + coord[1] = ijk[1] - 1; + coord[2] = ijk[2]; + + quad[1] = idxAcc.getValue(coord); + cell = SIGNS & signAcc.getValue(coord); + if (sEdgeGroupTable[cell][0] > 1) { + tmpIdx = quad[1] + Index32(sEdgeGroupTable[cell][8] - 1); + if (tmpIdx < pointListSize) quad[1] = tmpIdx; + } + + // i-1, j-1, k + coord[0] -= 1; + + quad[2] = idxAcc.getValue(coord); + cell = SIGNS & signAcc.getValue(coord); + if (sEdgeGroupTable[cell][0] > 1) { + tmpIdx = quad[2] + Index32(sEdgeGroupTable[cell][6] - 1); + if (tmpIdx < pointListSize) quad[2] = tmpIdx; + } + + // i-1, j, k + coord[1] = ijk[1]; + + quad[3] = idxAcc.getValue(coord); + cell = SIGNS & signAcc.getValue(coord); + if (sEdgeGroupTable[cell][0] > 1) { + tmpIdx = quad[3] + Index32(sEdgeGroupTable[cell][2] - 1); + if (tmpIdx < pointListSize) quad[3] = tmpIdx; + } + + if (quad[1] != util::INVALID_IDX && + quad[2] != util::INVALID_IDX && quad[3] != util::INVALID_IDX) { + mesher.addPrim(quad, !isInside, tag[bool(refFlags & ZEDGE)]); + } + } +} + + +//////////////////////////////////////// + + +template +class GenPolygons +{ +public: + typedef typename LeafManagerT::TreeType::template ValueConverter::Type IntTreeT; + typedef typename LeafManagerT::TreeType::template ValueConverter::Type Int16TreeT; + + typedef tree::ValueAccessor IntAccessorT; + typedef tree::ValueAccessor Int16AccessorT; + + ////////// + + + GenPolygons(const LeafManagerT& signLeafs, const Int16TreeT& signTree, + const IntTreeT& idxTree, PolygonPoolList& polygons, Index32 pointListSize); + + void run(bool threaded = true); + + + void setRefSignTree(const Int16TreeT *r) { mRefSignTree = r; } + + ////////// + + + void operator()(const tbb::blocked_range&) const; + +private: + const LeafManagerT& mSignLeafs; + const Int16TreeT& mSignTree; + const IntTreeT& mIdxTree; + const PolygonPoolList& mPolygonPoolList; + const Index32 mPointListSize; + + const Int16TreeT *mRefSignTree; + }; + + +template +GenPolygons::GenPolygons(const LeafManagerT& signLeafs, + const Int16TreeT& signTree, const IntTreeT& idxTree, PolygonPoolList& polygons, + Index32 pointListSize) + : mSignLeafs(signLeafs) + , mSignTree(signTree) + , mIdxTree(idxTree) + , mPolygonPoolList(polygons) + , mPointListSize(pointListSize) + , mRefSignTree(NULL) +{ +} + +template +void +GenPolygons::run(bool threaded) +{ + if (threaded) tbb::parallel_for(mSignLeafs.getRange(), *this); + else (*this)(mSignLeafs.getRange()); +} + +template +void +GenPolygons::operator()( + const tbb::blocked_range& range) const +{ + typename LeafManagerT::TreeType::LeafNodeType::ValueOnCIter iter; + IntAccessorT idxAcc(mIdxTree); + Int16AccessorT signAcc(mSignTree); + + + PrimBuilder mesher; + size_t edgeCount; + Coord ijk, origin; + + + // reference data + boost::scoped_ptr refSignAcc; + if (mRefSignTree) refSignAcc.reset(new Int16AccessorT(*mRefSignTree)); + + + for (size_t n = range.begin(); n != range.end(); ++n) { + + origin = mSignLeafs.leaf(n).origin(); + + // Get an upper bound on the number of primitives. + edgeCount = 0; + iter = mSignLeafs.leaf(n).cbeginValueOn(); + for (; iter; ++iter) { + if (iter.getValue() & XEDGE) ++edgeCount; + if (iter.getValue() & YEDGE) ++edgeCount; + if (iter.getValue() & ZEDGE) ++edgeCount; + } + + if(edgeCount == 0) continue; + + mesher.init(edgeCount, mPolygonPoolList[n]); + + const typename Int16TreeT::LeafNodeType *signleafPt = signAcc.probeConstLeaf(origin); + const typename IntTreeT::LeafNodeType *idxLeafPt = idxAcc.probeConstLeaf(origin); + + if (!signleafPt || !idxLeafPt) continue; + + + const typename Int16TreeT::LeafNodeType *refSignLeafPt = NULL; + if (refSignAcc) refSignLeafPt = refSignAcc->probeConstLeaf(origin); + + Vec4i offsets; + + iter = mSignLeafs.leaf(n).cbeginValueOn(); + for (; iter; ++iter) { + ijk = iter.getCoord(); + + Int16 flags = iter.getValue(); + + if (!(flags & 0xE00)) continue; + + Int16 refFlags = 0; + if (refSignLeafPt) { + refFlags = refSignLeafPt->getValue(iter.pos()); + } + + offsets[0] = 0; + offsets[1] = 0; + offsets[2] = 0; + + const unsigned char cell = (SIGNS & flags); + + if (sEdgeGroupTable[cell][0] > 1) { + offsets[0] = (sEdgeGroupTable[cell][1] - 1); + offsets[1] = (sEdgeGroupTable[cell][9] - 1); + offsets[2] = (sEdgeGroupTable[cell][4] - 1); + } + + if (ijk[0] > origin[0] && ijk[1] > origin[1] && ijk[2] > origin[2]) { + constructPolygons(flags, refFlags, offsets, ijk, *signleafPt, *idxLeafPt, mesher, mPointListSize); + } else { + constructPolygons(flags, refFlags, offsets, ijk, signAcc, idxAcc, mesher, mPointListSize); + } + } + + mesher.done(); + } +} + + +//////////////////////////////////////// + +// Masking and mesh partitioning + +struct PartOp +{ + + PartOp(size_t leafCount, size_t partitions, size_t activePart) + { + size_t leafSegments = leafCount / partitions; + mStart = leafSegments * activePart; + mEnd = activePart >= (partitions - 1) ? leafCount : mStart + leafSegments; + } + + template + void operator()(LeafNodeType &leaf, size_t leafIndex) const + { + if (leafIndex < mStart || leafIndex >= mEnd) leaf.setValuesOff(); + } + +private: + size_t mStart, mEnd; +}; + + +//////////////////////////////////////// + + +template +class PartGen +{ +public: + typedef tree::LeafManager LeafManagerT; + typedef typename SrcTreeT::template ValueConverter::Type BoolTreeT; + typedef tree::ValueAccessor BoolAccessorT; + + ////////// + + + PartGen(const LeafManagerT& leafs, size_t partitions, size_t activePart); + + void run(bool threaded = true); + + BoolTreeT& tree() { return mTree; } + + + ////////// + + PartGen(PartGen&, tbb::split); + void operator()(const tbb::blocked_range&); + void join(PartGen& rhs) { mTree.merge(rhs.mTree); } + +private: + const LeafManagerT& mLeafManager; + BoolTreeT mTree; + size_t mStart, mEnd; +}; + +template +PartGen::PartGen(const LeafManagerT& leafs, size_t partitions, size_t activePart) + : mLeafManager(leafs) + , mTree(false) + , mStart(0) + , mEnd(0) +{ + size_t leafCount = leafs.leafCount(); + size_t leafSegments = leafCount / partitions; + mStart = leafSegments * activePart; + mEnd = activePart >= (partitions - 1) ? leafCount : mStart + leafSegments; +} + +template +PartGen::PartGen(PartGen& rhs, tbb::split) + : mLeafManager(rhs.mLeafManager) + , mTree(false) + , mStart(rhs.mStart) + , mEnd(rhs.mEnd) +{ +} + + +template +void +PartGen::run(bool threaded) +{ + if (threaded) tbb::parallel_reduce(mLeafManager.getRange(), *this); + else (*this)(mLeafManager.getRange()); +} + + +template +void +PartGen::operator()(const tbb::blocked_range& range) +{ + Coord ijk; + BoolAccessorT acc(mTree); + + typedef typename BoolTreeT::LeafNodeType BoolLeafT; + typename SrcTreeT::LeafNodeType::ValueOnCIter iter; + + for (size_t n = range.begin(); n != range.end(); ++n) { + if (n < mStart || n >= mEnd) continue; + BoolLeafT* leaf = acc.touchLeaf(mLeafManager.leaf(n).origin()); + leaf->topologyUnion(mLeafManager.leaf(n)); + } +} + + +//////////////////////////////////////// + + +template +class GenSeamMask +{ +public: + typedef typename TreeT::template ValueConverter::Type BoolTreeT; + + ////////// + + GenSeamMask(const LeafManagerT& leafs, const TreeT& tree); + + void run(bool threaded = true); + + BoolTreeT& mask() { return mMaskTree; } + + ////////// + + GenSeamMask(GenSeamMask&, tbb::split); + void operator()(const tbb::blocked_range&); + void join(GenSeamMask& rhs) { mMaskTree.merge(rhs.mMaskTree); } + +private: + + const LeafManagerT& mLeafManager; + const TreeT& mTree; + + BoolTreeT mMaskTree; +}; + + +template +GenSeamMask::GenSeamMask(const LeafManagerT& leafs, const TreeT& tree) + : mLeafManager(leafs) + , mTree(tree) + , mMaskTree(false) +{ +} + + +template +GenSeamMask::GenSeamMask(GenSeamMask& rhs, tbb::split) + : mLeafManager(rhs.mLeafManager) + , mTree(rhs.mTree) + , mMaskTree(false) +{ +} + + +template +void +GenSeamMask::run(bool threaded) +{ + if (threaded) tbb::parallel_reduce(mLeafManager.getRange(), *this); + else (*this)(mLeafManager.getRange()); +} + + +template +void +GenSeamMask::operator()(const tbb::blocked_range& range) +{ + Coord ijk; + tree::ValueAccessor acc(mTree); + tree::ValueAccessor maskAcc(mMaskTree); + + typename LeafManagerT::TreeType::LeafNodeType::ValueOnCIter it; + + for (size_t n = range.begin(); n != range.end(); ++n) { + + it = mLeafManager.leaf(n).cbeginValueOn(); + + for (; it; ++it) { + + ijk = it.getCoord(); + + unsigned char rhsSigns = acc.getValue(ijk) & SIGNS; + + if (sEdgeGroupTable[rhsSigns][0] > 0) { + unsigned char lhsSigns = it.getValue() & SIGNS; + if (rhsSigns != lhsSigns) { + maskAcc.setValueOn(ijk); + } + } + } + } +} + + +//////////////////////////////////////// + + +template +class TagSeamEdges +{ +public: + typedef tree::ValueAccessor AccessorT; + + TagSeamEdges(const TreeT& tree) : mAcc(tree) {} + + template + void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const + { + const typename TreeT::LeafNodeType *maskLeaf = + mAcc.probeConstLeaf(leaf.origin()); + + if (!maskLeaf) return; + + typename LeafNodeType::ValueOnIter it = leaf.beginValueOn(); + + for (; it; ++it) { + + if (maskLeaf->isValueOn(it.pos())) { + it.setValue(it.getValue() | SEAM); + } + } + } + +private: + AccessorT mAcc; +}; + + + +template +struct MaskEdges +{ + typedef tree::ValueAccessor BoolAccessorT; + + MaskEdges(const BoolTreeT& valueMask) : mMaskAcc(valueMask) {} + + template + void operator()(LeafNodeType &leaf, size_t /*leafIndex*/) const + { + typename LeafNodeType::ValueOnIter it = leaf.beginValueOn(); + + const typename BoolTreeT::LeafNodeType * maskLeaf = + mMaskAcc.probeConstLeaf(leaf.origin()); + + if (maskLeaf) { + for (; it; ++it) { + if (!maskLeaf->isValueOn(it.pos())) { + it.setValue(0x1FF & it.getValue()); + } + } + } else { + for (; it; ++it) { + it.setValue(0x1FF & it.getValue()); + } + } + } + +private: + BoolAccessorT mMaskAcc; +}; + + +class FlagUsedPoints +{ +public: + ////////// + + FlagUsedPoints(const PolygonPoolList& polygons, size_t polyListCount, + std::vector& usedPointMask) + : mPolygons(polygons) + , mPolyListCount(polyListCount) + , mUsedPointMask(usedPointMask) + { + } + + void run(bool threaded = true) + { + if (threaded) { + tbb::parallel_for(tbb::blocked_range(0, mPolyListCount), *this); + } else { + (*this)(tbb::blocked_range(0, mPolyListCount)); + } + } + + ////////// + + void operator()(const tbb::blocked_range& range) const + { + // Concurrent writes to same memory address can occur, but + // all threads are writing the same value and char is atomic. + for (size_t n = range.begin(); n != range.end(); ++n) { + const PolygonPool& polygons = mPolygons[n]; + for (size_t i = 0; i < polygons.numQuads(); ++i) { + const Vec4I& quad = polygons.quad(i); + mUsedPointMask[quad[0]] = 1; + mUsedPointMask[quad[1]] = 1; + mUsedPointMask[quad[2]] = 1; + mUsedPointMask[quad[3]] = 1; + } + + for (size_t i = 0; i < polygons.numTriangles(); ++i) { + const Vec3I& triangle = polygons.triangle(i); + mUsedPointMask[triangle[0]] = 1; + mUsedPointMask[triangle[1]] = 1; + mUsedPointMask[triangle[2]] = 1; + } + } + } + + +private: + const PolygonPoolList& mPolygons; + size_t mPolyListCount; + std::vector& mUsedPointMask; +}; + +class RemapIndices +{ +public: + ////////// + + RemapIndices(PolygonPoolList& polygons, + size_t polyListCount, const std::vector& indexMap) + : mPolygons(polygons) + , mPolyListCount(polyListCount) + , mIndexMap(indexMap) + { + } + + void run(bool threaded = true) + { + if (threaded) { + tbb::parallel_for(tbb::blocked_range(0, mPolyListCount), *this); + } else { + (*this)(tbb::blocked_range(0, mPolyListCount)); + } + } + + ////////// + + void operator()(const tbb::blocked_range& range) const + { + for (size_t n = range.begin(); n != range.end(); ++n) { + PolygonPool& polygons = mPolygons[n]; + for (size_t i = 0; i < polygons.numQuads(); ++i) { + Vec4I& quad = polygons.quad(i); + quad[0] = mIndexMap[quad[0]]; + quad[1] = mIndexMap[quad[1]]; + quad[2] = mIndexMap[quad[2]]; + quad[3] = mIndexMap[quad[3]]; + } + + for (size_t i = 0; i < polygons.numTriangles(); ++i) { + Vec3I& triangle = polygons.triangle(i); + triangle[0] = mIndexMap[triangle[0]]; + triangle[1] = mIndexMap[triangle[1]]; + triangle[2] = mIndexMap[triangle[2]]; + } + } + } + + +private: + PolygonPoolList& mPolygons; + size_t mPolyListCount; + const std::vector& mIndexMap; +}; + + +class MovePoints +{ +public: + ////////// + + MovePoints( + internal::UniquePtr::type& newPointList, + const PointList& oldPointList, + const std::vector& indexMap, + const std::vector& usedPointMask) + : mNewPointList(newPointList) + , mOldPointList(oldPointList) + , mIndexMap(indexMap) + , mUsedPointMask(usedPointMask) + { + } + + void run(bool threaded = true) + { + if (threaded) { + tbb::parallel_for(tbb::blocked_range(0, mIndexMap.size()), *this); + } else { + (*this)(tbb::blocked_range(0, mIndexMap.size())); + } + } + + ////////// + + void operator()(const tbb::blocked_range& range) const + { + for (size_t n = range.begin(); n != range.end(); ++n) { + if (mUsedPointMask[n]) { + const size_t index = mIndexMap[n]; + mNewPointList.get()[index] = mOldPointList[n]; + } + } + } + +private: + internal::UniquePtr::type& mNewPointList; + const PointList& mOldPointList; + const std::vector& mIndexMap; + const std::vector& mUsedPointMask; +}; + + +//////////////////////////////////////// + + +template +class GenTopologyMask +{ +public: + typedef tree::LeafManager LeafManagerT; + typedef typename SrcTreeT::template ValueConverter::Type BoolTreeT; + typedef tree::ValueAccessor SrcAccessorT; + typedef tree::ValueAccessor BoolAccessorT; + typedef Grid BoolGridT; + + + ////////// + + + GenTopologyMask(const BoolGridT& mask, const LeafManagerT& srcLeafs, + const math::Transform& srcXForm, bool invertMask); + + void run(bool threaded = true); + + BoolTreeT& tree() { return mTree; } + + + ////////// + + GenTopologyMask(GenTopologyMask&, tbb::split); + + void operator()(const tbb::blocked_range&); + + void join(GenTopologyMask& rhs) { mTree.merge(rhs.mTree); } + +private: + + const BoolGridT& mMask; + const LeafManagerT& mLeafManager; + const math::Transform& mSrcXForm; + bool mInvertMask; + BoolTreeT mTree; +}; + + +template +GenTopologyMask::GenTopologyMask(const BoolGridT& mask, const LeafManagerT& srcLeafs, + const math::Transform& srcXForm, bool invertMask) + : mMask(mask) + , mLeafManager(srcLeafs) + , mSrcXForm(srcXForm) + , mInvertMask(invertMask) + , mTree(false) +{ +} + + +template +GenTopologyMask::GenTopologyMask(GenTopologyMask& rhs, tbb::split) + : mMask(rhs.mMask) + , mLeafManager(rhs.mLeafManager) + , mSrcXForm(rhs.mSrcXForm) + , mInvertMask(rhs.mInvertMask) + , mTree(false) +{ +} + + +template +void +GenTopologyMask::run(bool threaded) +{ + if (threaded) { + tbb::parallel_reduce(mLeafManager.getRange(), *this); + } else { + (*this)(mLeafManager.getRange()); + } +} + + +template +void +GenTopologyMask::operator()(const tbb::blocked_range& range) +{ + Coord ijk; + Vec3d xyz; + typedef typename BoolTreeT::LeafNodeType BoolLeafT; + const math::Transform& maskXForm = mMask.transform(); + tree::ValueAccessor maskAcc(mMask.tree()); + tree::ValueAccessor acc(mTree); + + typename SrcTreeT::LeafNodeType::ValueOnCIter iter; + for (size_t n = range.begin(); n != range.end(); ++n) { + + ijk = mLeafManager.leaf(n).origin(); + BoolLeafT* leaf = new BoolLeafT(ijk, false); + bool addLeaf = false; + + if (maskXForm == mSrcXForm) { + + const BoolLeafT* maskLeaf = maskAcc.probeConstLeaf(ijk); + + if (maskLeaf) { + + for (iter = mLeafManager.leaf(n).cbeginValueOn(); iter; ++iter) { + Index pos = iter.pos(); + if(maskLeaf->isValueOn(pos) != mInvertMask) { + leaf->setValueOn(pos); + addLeaf = true; + } + } + + } else if (maskAcc.isValueOn(ijk) != mInvertMask) { + leaf->topologyUnion(mLeafManager.leaf(n)); + addLeaf = true; + } + + } else { + for (iter = mLeafManager.leaf(n).cbeginValueOn(); iter; ++iter) { + ijk = iter.getCoord(); + xyz = maskXForm.worldToIndex(mSrcXForm.indexToWorld(ijk)); + if(maskAcc.isValueOn(util::nearestCoord(xyz)) != mInvertMask) { + leaf->setValueOn(iter.pos()); + addLeaf = true; + } + } + } + + if (addLeaf) acc.addLeaf(leaf); + else delete leaf; + } +} + + +//////////////////////////////////////// + + +template +class GenBoundaryMask +{ +public: + typedef typename SrcTreeT::template ValueConverter::Type IntTreeT; + typedef typename SrcTreeT::template ValueConverter::Type BoolTreeT; + typedef tree::LeafManager LeafManagerT; + + ////////// + + GenBoundaryMask(const LeafManagerT& leafs, const BoolTreeT&, const IntTreeT&); + + void run(bool threaded = true); + + BoolTreeT& tree() { return mTree; } + + ////////// + + GenBoundaryMask(GenBoundaryMask&, tbb::split); + void operator()(const tbb::blocked_range&); + void join(GenBoundaryMask& rhs) { mTree.merge(rhs.mTree); } + +private: + // This typedef is needed for Windows + typedef tree::ValueAccessor IntTreeAccessorT; + + bool neighboringLeaf(const Coord&, const IntTreeAccessorT&) const; + + const LeafManagerT& mLeafManager; + const BoolTreeT& mMaskTree; + const IntTreeT& mIdxTree; + BoolTreeT mTree; + CoordBBox mLeafBBox; +}; + + +template +GenBoundaryMask::GenBoundaryMask(const LeafManagerT& leafs, + const BoolTreeT& maskTree, const IntTreeT& auxTree) + : mLeafManager(leafs) + , mMaskTree(maskTree) + , mIdxTree(auxTree) + , mTree(false) +{ + mIdxTree.evalLeafBoundingBox(mLeafBBox); + mLeafBBox.expand(IntTreeT::LeafNodeType::DIM); +} + + +template +GenBoundaryMask::GenBoundaryMask(GenBoundaryMask& rhs, tbb::split) + : mLeafManager(rhs.mLeafManager) + , mMaskTree(rhs.mMaskTree) + , mIdxTree(rhs.mIdxTree) + , mTree(false) + , mLeafBBox(rhs.mLeafBBox) +{ +} + + +template +void +GenBoundaryMask::run(bool threaded) +{ + if (threaded) { + tbb::parallel_reduce(mLeafManager.getRange(), *this); + } else { + (*this)(mLeafManager.getRange()); + } +} + + +template +bool +GenBoundaryMask::neighboringLeaf(const Coord& ijk, const IntTreeAccessorT& acc) const +{ + if (acc.probeConstLeaf(ijk)) return true; + + const int dim = IntTreeT::LeafNodeType::DIM; + + // face adjacent neghbours + if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1], ijk[2]))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1], ijk[2]))) return true; + if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] + dim, ijk[2]))) return true; + if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] - dim, ijk[2]))) return true; + if (acc.probeConstLeaf(Coord(ijk[0], ijk[1], ijk[2] + dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0], ijk[1], ijk[2] - dim))) return true; + + // edge adjacent neighbors + if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1], ijk[2] - dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1], ijk[2] - dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1], ijk[2] + dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1], ijk[2] + dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] + dim, ijk[2]))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] + dim, ijk[2]))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] - dim, ijk[2]))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] - dim, ijk[2]))) return true; + if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] - dim, ijk[2] + dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] - dim, ijk[2] - dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] + dim, ijk[2] + dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] + dim, ijk[2] - dim))) return true; + + // corner adjacent neighbors + if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] - dim, ijk[2] - dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] - dim, ijk[2] + dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] - dim, ijk[2] + dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] - dim, ijk[2] - dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] + dim, ijk[2] - dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] + dim, ijk[2] + dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] + dim, ijk[2] + dim))) return true; + if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] + dim, ijk[2] - dim))) return true; + + return false; +} + + +template +void +GenBoundaryMask::operator()(const tbb::blocked_range& range) +{ + Coord ijk; + tree::ValueAccessor maskAcc(mMaskTree); + tree::ValueAccessor idxAcc(mIdxTree); + tree::ValueAccessor acc(mTree); + + typename SrcTreeT::LeafNodeType::ValueOnCIter iter; + + for (size_t n = range.begin(); n != range.end(); ++n) { + + const typename SrcTreeT::LeafNodeType& + leaf = mLeafManager.leaf(n); + + ijk = leaf.origin(); + + if (!mLeafBBox.isInside(ijk) || !neighboringLeaf(ijk, idxAcc)) continue; + + const typename BoolTreeT::LeafNodeType* + maskLeaf = maskAcc.probeConstLeaf(ijk); + + if (!maskLeaf || !leaf.hasSameTopology(maskLeaf)) { + acc.touchLeaf(ijk)->topologyUnion(leaf); + } + } +} + + +//////////////////////////////////////// + + +template +class GenTileMask +{ +public: + typedef typename TreeT::template ValueConverter::Type BoolTreeT; + + typedef typename TreeT::ValueType ValueT; + + ////////// + + GenTileMask(const std::vector& tiles, const TreeT& distTree, ValueT iso); + + void run(bool threaded = true); + + BoolTreeT& tree() { return mTree; } + + ////////// + + GenTileMask(GenTileMask&, tbb::split); + void operator()(const tbb::blocked_range&); + void join(GenTileMask& rhs) { mTree.merge(rhs.mTree); } + +private: + + const std::vector& mTiles; + const TreeT& mDistTree; + ValueT mIsovalue; + + BoolTreeT mTree; +}; + + +template +GenTileMask::GenTileMask( + const std::vector& tiles, const TreeT& distTree, ValueT iso) + : mTiles(tiles) + , mDistTree(distTree) + , mIsovalue(iso) + , mTree(false) +{ +} + + +template +GenTileMask::GenTileMask(GenTileMask& rhs, tbb::split) + : mTiles(rhs.mTiles) + , mDistTree(rhs.mDistTree) + , mIsovalue(rhs.mIsovalue) + , mTree(false) +{ +} + + +template +void +GenTileMask::run(bool threaded) +{ + if (threaded) tbb::parallel_reduce(tbb::blocked_range(0, mTiles.size()), *this); + else (*this)(tbb::blocked_range(0, mTiles.size())); +} + + +template +void +GenTileMask::operator()(const tbb::blocked_range& range) +{ + tree::ValueAccessor distAcc(mDistTree); + CoordBBox region, bbox; + Coord ijk, nijk; + bool processRegion = true; + ValueT value; + + + for (size_t n = range.begin(); n != range.end(); ++n) { + + const Vec4i& tile = mTiles[n]; + + bbox.min()[0] = tile[0]; + bbox.min()[1] = tile[1]; + bbox.min()[2] = tile[2]; + + bbox.max() = bbox.min(); + bbox.max().offset(tile[3]); + + const bool thisInside = (distAcc.getValue(bbox.min()) < mIsovalue); + const int thisDepth = distAcc.getValueDepth(bbox.min()); + + // eval x-edges + + ijk = bbox.max(); + nijk = ijk; + ++nijk[0]; + + processRegion = true; + if (thisDepth >= distAcc.getValueDepth(nijk)) { + processRegion = thisInside != (distAcc.getValue(nijk) < mIsovalue); + } + + + if (processRegion) { + region = bbox; + region.min()[0] = region.max()[0] = ijk[0]; + mTree.fill(region, true); + } + + + ijk = bbox.min(); + --ijk[0]; + + processRegion = true; + if (thisDepth >= distAcc.getValueDepth(ijk)) { + processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); + } + + if (processRegion) { + region = bbox; + region.min()[0] = region.max()[0] = ijk[0]; + mTree.fill(region, true); + } + + + // eval y-edges + + ijk = bbox.max(); + nijk = ijk; + ++nijk[1]; + + processRegion = true; + if (thisDepth >= distAcc.getValueDepth(nijk)) { + processRegion = thisInside != (distAcc.getValue(nijk) < mIsovalue); + } + + if (processRegion) { + region = bbox; + region.min()[1] = region.max()[1] = ijk[1]; + mTree.fill(region, true); + } + + + ijk = bbox.min(); + --ijk[1]; + + processRegion = true; + if (thisDepth >= distAcc.getValueDepth(ijk)) { + processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); + } + + if (processRegion) { + region = bbox; + region.min()[1] = region.max()[1] = ijk[1]; + mTree.fill(region, true); + } + + + // eval z-edges + + ijk = bbox.max(); + nijk = ijk; + ++nijk[2]; + + processRegion = true; + if (thisDepth >= distAcc.getValueDepth(nijk)) { + processRegion = thisInside != (distAcc.getValue(nijk) < mIsovalue); + } + + if (processRegion) { + region = bbox; + region.min()[2] = region.max()[2] = ijk[2]; + mTree.fill(region, true); + } + + ijk = bbox.min(); + --ijk[2]; + + processRegion = true; + if (thisDepth >= distAcc.getValueDepth(ijk)) { + processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); + } + + if (processRegion) { + region = bbox; + region.min()[2] = region.max()[2] = ijk[2]; + mTree.fill(region, true); + } + + + ijk = bbox.min(); + --ijk[1]; + --ijk[2]; + + processRegion = true; + if (thisDepth >= distAcc.getValueDepth(ijk)) { + processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); + } + + if (processRegion) { + region = bbox; + region.min()[1] = region.max()[1] = ijk[1]; + region.min()[2] = region.max()[2] = ijk[2]; + mTree.fill(region, true); + } + + + ijk = bbox.min(); + --ijk[0]; + --ijk[1]; + + processRegion = true; + if (thisDepth >= distAcc.getValueDepth(ijk)) { + processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); + } + + if (processRegion) { + region = bbox; + region.min()[1] = region.max()[1] = ijk[1]; + region.min()[0] = region.max()[0] = ijk[0]; + mTree.fill(region, true); + } + + ijk = bbox.min(); + --ijk[0]; + --ijk[2]; + + processRegion = true; + if (thisDepth >= distAcc.getValueDepth(ijk)) { + processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); + } + + if (processRegion) { + region = bbox; + region.min()[2] = region.max()[2] = ijk[2]; + region.min()[0] = region.max()[0] = ijk[0]; + mTree.fill(region, true); + } + } +} + + +//////////////////////////////////////// + + +template +inline void +tileData(const DistTreeT& distTree, SignTreeT& signTree, IdxTreeT& idxTree, double iso) +{ + typename DistTreeT::ValueOnCIter tileIter(distTree); + tileIter.setMaxDepth(DistTreeT::ValueOnCIter::LEAF_DEPTH - 1); + + if (!tileIter) return; // volume has no active tiles. + + size_t tileCount = 0; + for ( ; tileIter; ++tileIter) { + ++tileCount; + } + + std::vector tiles(tileCount); + + tileCount = 0; + tileIter = distTree.cbeginValueOn(); + tileIter.setMaxDepth(DistTreeT::ValueOnCIter::LEAF_DEPTH - 1); + + CoordBBox bbox; + for (; tileIter; ++tileIter) { + Vec4i& tile = tiles[tileCount++]; + tileIter.getBoundingBox(bbox); + tile[0] = bbox.min()[0]; + tile[1] = bbox.min()[1]; + tile[2] = bbox.min()[2]; + tile[3] = bbox.max()[0] - bbox.min()[0]; + } + + typename DistTreeT::ValueType isovalue = typename DistTreeT::ValueType(iso); + + GenTileMask tileMask(tiles, distTree, isovalue); + tileMask.run(); + + typedef typename DistTreeT::template ValueConverter::Type BoolTreeT; + typedef tree::LeafManager BoolLeafManagerT; + + BoolLeafManagerT leafs(tileMask.tree()); + + + internal::SignData op(distTree, leafs, isovalue); + op.run(); + + signTree.merge(*op.signTree()); + idxTree.merge(*op.idxTree()); +} + + +//////////////////////////////////////// + + +// Utility class for the volumeToMesh wrapper +class PointListCopy +{ +public: + PointListCopy(const PointList& pointsIn, std::vector& pointsOut) + : mPointsIn(pointsIn) , mPointsOut(pointsOut) + { + } + + void operator()(const tbb::blocked_range& range) const + { + for (size_t n = range.begin(); n < range.end(); ++n) { + mPointsOut[n] = mPointsIn[n]; + } + } + +private: + const PointList& mPointsIn; + std::vector& mPointsOut; +}; + + +// Checks if the isovalue is in proximity to the active voxel boundary. +template +inline bool +needsActiveVoxePadding(const LeafManagerT& leafs, double iso, double voxelSize) +{ + double interiorWidth = 0.0, exteriorWidth = 0.0; + { + typename LeafManagerT::TreeType::LeafNodeType::ValueOffCIter it; + bool foundInterior = false, foundExterior = false; + for (size_t n = 0, N = leafs.leafCount(); n < N; ++n) { + + for (it = leafs.leaf(n).cbeginValueOff(); it; ++it) { + double value = double(it.getValue()); + if (value < 0.0) { + interiorWidth = value; + foundInterior = true; + } else if (value > 0.0) { + exteriorWidth = value; + foundExterior = true; + } + + if (foundInterior && foundExterior) break; + } + + if (foundInterior && foundExterior) break; + } + + } + + double minDist = std::min(std::abs(interiorWidth - iso), std::abs(exteriorWidth - iso)); + return !(minDist > (2.0 * voxelSize)); +} + + +} // end namespace internal + + +//////////////////////////////////////// + + +inline +PolygonPool::PolygonPool() + : mNumQuads(0) + , mNumTriangles(0) + , mQuads(NULL) + , mTriangles(NULL) + , mQuadFlags(NULL) + , mTriangleFlags(NULL) +{ +} + + +inline +PolygonPool::PolygonPool(const size_t numQuads, const size_t numTriangles) + : mNumQuads(numQuads) + , mNumTriangles(numTriangles) + , mQuads(new openvdb::Vec4I[mNumQuads]) + , mTriangles(new openvdb::Vec3I[mNumTriangles]) + , mQuadFlags(new char[mNumQuads]) + , mTriangleFlags(new char[mNumTriangles]) +{ +} + + +inline void +PolygonPool::copy(const PolygonPool& rhs) +{ + resetQuads(rhs.numQuads()); + resetTriangles(rhs.numTriangles()); + + for (size_t i = 0; i < mNumQuads; ++i) { + mQuads[i] = rhs.mQuads[i]; + mQuadFlags[i] = rhs.mQuadFlags[i]; + } + + for (size_t i = 0; i < mNumTriangles; ++i) { + mTriangles[i] = rhs.mTriangles[i]; + mTriangleFlags[i] = rhs.mTriangleFlags[i]; + } +} + + +inline void +PolygonPool::resetQuads(size_t size) +{ + mNumQuads = size; + mQuads.reset(new openvdb::Vec4I[mNumQuads]); + mQuadFlags.reset(new char[mNumQuads]); +} + + +inline void +PolygonPool::clearQuads() +{ + mNumQuads = 0; + mQuads.reset(NULL); + mQuadFlags.reset(NULL); +} + + +inline void +PolygonPool::resetTriangles(size_t size) +{ + mNumTriangles = size; + mTriangles.reset(new openvdb::Vec3I[mNumTriangles]); + mTriangleFlags.reset(new char[mNumTriangles]); +} + + +inline void +PolygonPool::clearTriangles() +{ + mNumTriangles = 0; + mTriangles.reset(NULL); + mTriangleFlags.reset(NULL); +} + + +inline bool +PolygonPool::trimQuads(const size_t n, bool reallocate) +{ + if (!(n < mNumQuads)) return false; + + if (reallocate) { + + if (n == 0) { + mQuads.reset(NULL); + } else { + + boost::scoped_array quads(new openvdb::Vec4I[n]); + boost::scoped_array flags(new char[n]); + + for (size_t i = 0; i < n; ++i) { + quads[i] = mQuads[i]; + flags[i] = mQuadFlags[i]; + } + + mQuads.swap(quads); + mQuadFlags.swap(flags); + } + } + + mNumQuads = n; + return true; +} + + +inline bool +PolygonPool::trimTrinagles(const size_t n, bool reallocate) +{ + if (!(n < mNumTriangles)) return false; + + if (reallocate) { + + if (n == 0) { + mTriangles.reset(NULL); + } else { + + boost::scoped_array triangles(new openvdb::Vec3I[n]); + boost::scoped_array flags(new char[n]); + + for (size_t i = 0; i < n; ++i) { + triangles[i] = mTriangles[i]; + flags[i] = mTriangleFlags[i]; + } + + mTriangles.swap(triangles); + mTriangleFlags.swap(flags); + } + } + + mNumTriangles = n; + return true; +} + + +//////////////////////////////////////// + + +inline VolumeToMesh::VolumeToMesh(double isovalue, double adaptivity) + : mPoints(NULL) + , mPolygons() + , mPointListSize(0) + , mSeamPointListSize(0) + , mPolygonPoolListSize(0) + , mIsovalue(isovalue) + , mPrimAdaptivity(adaptivity) + , mSecAdaptivity(0.0) + , mRefGrid(GridBase::ConstPtr()) + , mSurfaceMaskGrid(GridBase::ConstPtr()) + , mAdaptivityGrid(GridBase::ConstPtr()) + , mAdaptivityMaskTree(TreeBase::ConstPtr()) + , mRefSignTree(TreeBase::Ptr()) + , mRefIdxTree(TreeBase::Ptr()) + , mInvertSurfaceMask(false) + , mPartitions(1) + , mActivePart(0) + , mQuantizedSeamPoints(NULL) + , mPointFlags(0) +{ +} + + +inline PointList& +VolumeToMesh::pointList() +{ + return mPoints; +} + + +inline const size_t& +VolumeToMesh::pointListSize() const +{ + return mPointListSize; +} + + +inline PolygonPoolList& +VolumeToMesh::polygonPoolList() +{ + return mPolygons; +} + + +inline const PolygonPoolList& +VolumeToMesh::polygonPoolList() const +{ + return mPolygons; +} + + +inline const size_t& +VolumeToMesh::polygonPoolListSize() const +{ + return mPolygonPoolListSize; +} + + +inline void +VolumeToMesh::setRefGrid(const GridBase::ConstPtr& grid, double secAdaptivity) +{ + mRefGrid = grid; + mSecAdaptivity = secAdaptivity; + + // Clear out old auxiliary data + mRefSignTree = TreeBase::Ptr(); + mRefIdxTree = TreeBase::Ptr(); + mSeamPointListSize = 0; + mQuantizedSeamPoints.reset(NULL); +} + + +inline void +VolumeToMesh::setSurfaceMask(const GridBase::ConstPtr& mask, bool invertMask) +{ + mSurfaceMaskGrid = mask; + mInvertSurfaceMask = invertMask; +} + + +inline void +VolumeToMesh::setSpatialAdaptivity(const GridBase::ConstPtr& grid) +{ + mAdaptivityGrid = grid; +} + + +inline void +VolumeToMesh::setAdaptivityMask(const TreeBase::ConstPtr& tree) +{ + mAdaptivityMaskTree = tree; +} + + +inline void +VolumeToMesh::partition(unsigned partitions, unsigned activePart) +{ + mPartitions = std::max(partitions, unsigned(1)); + mActivePart = std::min(activePart, mPartitions-1); +} + + +inline std::vector& +VolumeToMesh::pointFlags() +{ + return mPointFlags; +} + + +inline const std::vector& +VolumeToMesh::pointFlags() const +{ + return mPointFlags; +} + + +template +inline void +VolumeToMesh::operator()(const GridT& distGrid) +{ + typedef typename GridT::TreeType DistTreeT; + typedef tree::LeafManager DistLeafManagerT; + typedef typename DistTreeT::ValueType DistValueT; + + typedef typename DistTreeT::template ValueConverter::Type BoolTreeT; + typedef tree::LeafManager BoolLeafManagerT; + typedef Grid BoolGridT; + + typedef typename DistTreeT::template ValueConverter::Type Int16TreeT; + typedef tree::LeafManager Int16LeafManagerT; + + typedef typename DistTreeT::template ValueConverter::Type IntTreeT; + typedef typename DistTreeT::template ValueConverter::Type FloatTreeT; + typedef Grid FloatGridT; + + + const openvdb::math::Transform& transform = distGrid.transform(); + const DistTreeT& distTree = distGrid.tree(); + const DistValueT isovalue = DistValueT(mIsovalue); + + typename Int16TreeT::Ptr signTreePt; + typename IntTreeT::Ptr idxTreePt; + typename BoolTreeT::Ptr pointMask; + + BoolTreeT valueMask(false), seamMask(false); + const bool adaptive = mPrimAdaptivity > 1e-7 || mSecAdaptivity > 1e-7; + bool maskEdges = false; + + + const BoolGridT * surfaceMask = NULL; + if (mSurfaceMaskGrid && mSurfaceMaskGrid->type() == BoolGridT::gridType()) { + surfaceMask = static_cast(mSurfaceMaskGrid.get()); + } + + const FloatGridT * adaptivityField = NULL; + if (mAdaptivityGrid && mAdaptivityGrid->type() == FloatGridT::gridType()) { + adaptivityField = static_cast(mAdaptivityGrid.get()); + } + + if (mAdaptivityMaskTree && mAdaptivityMaskTree->type() == BoolTreeT::treeType()) { + const BoolTreeT *adaptivityMaskPt = + static_cast(mAdaptivityMaskTree.get()); + seamMask.topologyUnion(*adaptivityMaskPt); + } + + + // Collect auxiliary data + { + DistLeafManagerT distLeafs(distTree); + + // Check if the isovalue is in proximity to the active voxel boundary. + bool padActiveVoxels = false; + int padVoxels = 3; + + if (distGrid.getGridClass() != GRID_LEVEL_SET) { + padActiveVoxels = true; + } else { + padActiveVoxels = internal::needsActiveVoxePadding(distLeafs, + mIsovalue, transform.voxelSize()[0]); + } + + // always pad the active region for small volumes (the performance hit is neglectable). + if (!padActiveVoxels) { + Coord dim; + distTree.evalActiveVoxelDim(dim); + int maxDim = std::max(std::max(dim[0], dim[1]), dim[2]); + if (maxDim < 1000) { + padActiveVoxels = true; + padVoxels = 1; + } + } + + if (surfaceMask || mPartitions > 1) { + + maskEdges = true; + + if (surfaceMask) { + + { // Mask + internal::GenTopologyMask masking( + *surfaceMask, distLeafs, transform, mInvertSurfaceMask); + masking.run(); + valueMask.merge(masking.tree()); + } + + if (mPartitions > 1) { // Partition + tree::LeafManager leafs(valueMask); + leafs.foreach(internal::PartOp(leafs.leafCount() , mPartitions, mActivePart)); + valueMask.pruneInactive(); + } + + } else { // Partition + + internal::PartGen partitioner(distLeafs, mPartitions, mActivePart); + partitioner.run(); + valueMask.merge(partitioner.tree()); + } + + { + if (padActiveVoxels) tools::dilateVoxels(valueMask, padVoxels); + BoolLeafManagerT leafs(valueMask); + + internal::SignData + signDataOp(distTree, leafs, isovalue); + signDataOp.run(); + + signTreePt = signDataOp.signTree(); + idxTreePt = signDataOp.idxTree(); + } + + { + internal::GenBoundaryMask boundary(distLeafs, valueMask, *idxTreePt); + boundary.run(); + + BoolLeafManagerT bleafs(boundary.tree()); + + internal::SignData + signDataOp(distTree, bleafs, isovalue); + signDataOp.run(); + + signTreePt->merge(*signDataOp.signTree()); + idxTreePt->merge(*signDataOp.idxTree()); + } + + } else { + + // Collect voxel-sign configurations + if (padActiveVoxels) { + + BoolTreeT regionMask(false); + regionMask.topologyUnion(distTree); + tools::dilateVoxels(regionMask, padVoxels); + + BoolLeafManagerT leafs(regionMask); + + internal::SignData + signDataOp(distTree, leafs, isovalue); + signDataOp.run(); + + signTreePt = signDataOp.signTree(); + idxTreePt = signDataOp.idxTree(); + } else { + + internal::SignData + signDataOp(distTree, distLeafs, isovalue); + signDataOp.run(); + + signTreePt = signDataOp.signTree(); + idxTreePt = signDataOp.idxTree(); + } + } + + } + + + // Collect auxiliary data from active tiles + internal::tileData(distTree, *signTreePt, *idxTreePt, isovalue); + + // Optionally collect auxiliary data from a reference level set. + Int16TreeT *refSignTreePt = NULL; + IntTreeT *refIdxTreePt = NULL; + const DistTreeT *refDistTreePt = NULL; + + if (mRefGrid && mRefGrid->type() == GridT::gridType()) { + + const GridT* refGrid = static_cast(mRefGrid.get()); + refDistTreePt = &refGrid->tree(); + + // Collect and cache auxiliary data from the reference grid. + if (!mRefSignTree && !mRefIdxTree) { + + DistLeafManagerT refDistLeafs(*refDistTreePt); + internal::SignData + signDataOp(*refDistTreePt, refDistLeafs, isovalue); + + signDataOp.run(); + + mRefSignTree = signDataOp.signTree(); + mRefIdxTree = signDataOp.idxTree(); + } + + // Get cached auxiliary data + if (mRefSignTree && mRefIdxTree) { + refSignTreePt = static_cast(mRefSignTree.get()); + refIdxTreePt = static_cast(mRefIdxTree.get()); + } + } + + + // Process auxiliary data + Int16LeafManagerT signLeafs(*signTreePt); + + if (maskEdges) { + signLeafs.foreach(internal::MaskEdges(valueMask)); + valueMask.clear(); + } + + + // Generate the seamline mask + if (refSignTreePt) { + internal::GenSeamMask seamOp(signLeafs, *refSignTreePt); + seamOp.run(); + + tools::dilateVoxels(seamOp.mask(), 3); + signLeafs.foreach(internal::TagSeamEdges(seamOp.mask())); + + seamMask.merge(seamOp.mask()); + } + + + std::vector regions(signLeafs.leafCount(), 0); + if (regions.empty()) return; + + if (adaptive) { + + internal::MergeVoxelRegions merge( + signLeafs, *signTreePt, distTree, *idxTreePt, isovalue, DistValueT(mPrimAdaptivity)); + + if (adaptivityField) { + merge.setSpatialAdaptivity(transform, *adaptivityField); + } + + if (refSignTreePt || mAdaptivityMaskTree) { + merge.setAdaptivityMask(&seamMask); + } + + if (refSignTreePt) { + merge.setRefData(refSignTreePt, DistValueT(mSecAdaptivity)); + } + + merge.run(); + + signLeafs.foreach(internal::CountRegions(*idxTreePt, regions)); + + } else { + signLeafs.foreach(internal::CountPoints(regions)); + } + + + { + mPointListSize = 0; + size_t tmp = 0; + for (size_t n = 0, N = regions.size(); n < N; ++n) { + tmp = regions[n]; + regions[n] = mPointListSize; + mPointListSize += tmp; + } + } + + + // Generate the unique point list + mPoints.reset(new openvdb::Vec3s[mPointListSize]); + mPointFlags.clear(); + + // Generate seam line sample points + if (refSignTreePt && refIdxTreePt) { + + if (mSeamPointListSize == 0) { + + std::vector pointMap; + + { + Int16LeafManagerT refSignLeafs(*refSignTreePt); + pointMap.resize(refSignLeafs.leafCount(), 0); + + refSignLeafs.foreach(internal::CountPoints(pointMap)); + + size_t tmp = 0; + for (size_t n = 0, N = pointMap.size(); n < N; ++n) { + tmp = pointMap[n]; + pointMap[n] = mSeamPointListSize; + mSeamPointListSize += tmp; + } + } + + if (!pointMap.empty() && mSeamPointListSize != 0) { + + mQuantizedSeamPoints.reset(new uint32_t[mSeamPointListSize]); + memset(mQuantizedSeamPoints.get(), 0, sizeof(uint32_t) * mSeamPointListSize); + + typedef tree::LeafManager IntLeafManagerT; + + IntLeafManagerT refIdxLeafs(*refIdxTreePt); + refIdxLeafs.foreach(internal::MapPoints(pointMap, *refSignTreePt)); + } + } + + if (mSeamPointListSize != 0) { + signLeafs.foreach(internal::SeamWeights( + distTree, *refSignTreePt, *refIdxTreePt, mQuantizedSeamPoints, mIsovalue)); + } + } + + + internal::GenPoints + pointOp(signLeafs, distTree, *idxTreePt, mPoints, regions, transform, mIsovalue); + + + if (mSeamPointListSize != 0) { + mPointFlags.resize(mPointListSize); + pointOp.setRefData(refSignTreePt, refDistTreePt, refIdxTreePt, + &mQuantizedSeamPoints, &mPointFlags); + } + + pointOp.run(); + + + mPolygonPoolListSize = signLeafs.leafCount(); + mPolygons.reset(new PolygonPool[mPolygonPoolListSize]); + + + if (adaptive) { + + internal::GenPolygons + mesher(signLeafs, *signTreePt, *idxTreePt, mPolygons, Index32(mPointListSize)); + + mesher.setRefSignTree(refSignTreePt); + mesher.run(); + + } else { + + internal::GenPolygons + mesher(signLeafs, *signTreePt, *idxTreePt, mPolygons, Index32(mPointListSize)); + + mesher.setRefSignTree(refSignTreePt); + mesher.run(); + } + + // Clean up unused points, only necessary if masking and/or + // automatic mesh partitioning is enabled. + if ((surfaceMask || mPartitions > 1) && mPointListSize > 0) { + + // Flag used points + std::vector usedPointMask(mPointListSize, 0); + + internal::FlagUsedPoints flagPoints(mPolygons, mPolygonPoolListSize, usedPointMask); + flagPoints.run(); + + // Create index map + std::vector indexMap(mPointListSize); + size_t usedPointCount = 0; + for (size_t p = 0; p < mPointListSize; ++p) { + if (usedPointMask[p]) indexMap[p] = usedPointCount++; + } + + if (usedPointCount < mPointListSize) { + + // move points + internal::UniquePtr::type + newPointList(new openvdb::Vec3s[usedPointCount]); + + internal::MovePoints movePoints(newPointList, mPoints, indexMap, usedPointMask); + movePoints.run(); + + mPointListSize = usedPointCount; + mPoints.reset(newPointList.release()); + + // update primitives + internal::RemapIndices remap(mPolygons, mPolygonPoolListSize, indexMap); + remap.run(); + } + } + + + // Subdivide nonplanar quads near the seamline edges + // todo: thread and clean up + if (refSignTreePt || refIdxTreePt || refDistTreePt) { + std::vector newPoints; + + for (size_t n = 0; n < mPolygonPoolListSize; ++n) { + + PolygonPool& polygons = mPolygons[n]; + + std::vector nonPlanarQuads; + nonPlanarQuads.reserve(polygons.numQuads()); + + for (size_t i = 0; i < polygons.numQuads(); ++i) { + + char& flags = polygons.quadFlags(i); + + if ((flags & POLYFLAG_FRACTURE_SEAM) && !(flags & POLYFLAG_EXTERIOR)) { + + openvdb::Vec4I& quad = polygons.quad(i); + + const bool edgePoly = mPointFlags[quad[0]] || mPointFlags[quad[1]] + || mPointFlags[quad[2]] || mPointFlags[quad[3]]; + + if (!edgePoly) continue; + + const Vec3s& p0 = mPoints[quad[0]]; + const Vec3s& p1 = mPoints[quad[1]]; + const Vec3s& p2 = mPoints[quad[2]]; + const Vec3s& p3 = mPoints[quad[3]]; + + if (!internal::isPlanarQuad(p0, p1, p2, p3, 1e-6f)) { + nonPlanarQuads.push_back(i); + } + } + } + + + if (!nonPlanarQuads.empty()) { + + PolygonPool tmpPolygons; + + tmpPolygons.resetQuads(polygons.numQuads() - nonPlanarQuads.size()); + tmpPolygons.resetTriangles(polygons.numTriangles() + 4 * nonPlanarQuads.size()); + + size_t triangleIdx = 0; + for (size_t i = 0; i < nonPlanarQuads.size(); ++i) { + + size_t& quadIdx = nonPlanarQuads[i]; + + openvdb::Vec4I& quad = polygons.quad(quadIdx); + char& quadFlags = polygons.quadFlags(quadIdx); + //quadFlags |= POLYFLAG_SUBDIVIDED; + + Vec3s centroid = (mPoints[quad[0]] + mPoints[quad[1]] + + mPoints[quad[2]] + mPoints[quad[3]]) * 0.25; + + size_t pointIdx = newPoints.size() + mPointListSize; + + newPoints.push_back(centroid); + + + { + Vec3I& triangle = tmpPolygons.triangle(triangleIdx); + + triangle[0] = quad[0]; + triangle[1] = pointIdx; + triangle[2] = quad[3]; + + tmpPolygons.triangleFlags(triangleIdx) = quadFlags; + + if (mPointFlags[triangle[0]] || mPointFlags[triangle[2]]) { + tmpPolygons.triangleFlags(triangleIdx) |= POLYFLAG_SUBDIVIDED; + } + } + + ++triangleIdx; + + { + Vec3I& triangle = tmpPolygons.triangle(triangleIdx); + + triangle[0] = quad[0]; + triangle[1] = quad[1]; + triangle[2] = pointIdx; + + tmpPolygons.triangleFlags(triangleIdx) = quadFlags; + + if (mPointFlags[triangle[0]] || mPointFlags[triangle[1]]) { + tmpPolygons.triangleFlags(triangleIdx) |= POLYFLAG_SUBDIVIDED; + } + } + + ++triangleIdx; + + { + Vec3I& triangle = tmpPolygons.triangle(triangleIdx); + + triangle[0] = quad[1]; + triangle[1] = quad[2]; + triangle[2] = pointIdx; + + tmpPolygons.triangleFlags(triangleIdx) = quadFlags; + + if (mPointFlags[triangle[0]] || mPointFlags[triangle[1]]) { + tmpPolygons.triangleFlags(triangleIdx) |= POLYFLAG_SUBDIVIDED; + } + } + + + ++triangleIdx; + + { + Vec3I& triangle = tmpPolygons.triangle(triangleIdx); + + triangle[0] = quad[2]; + triangle[1] = quad[3]; + triangle[2] = pointIdx; + + tmpPolygons.triangleFlags(triangleIdx) = quadFlags; + + if (mPointFlags[triangle[0]] || mPointFlags[triangle[1]]) { + tmpPolygons.triangleFlags(triangleIdx) |= POLYFLAG_SUBDIVIDED; + } + } + + ++triangleIdx; + + quad[0] = util::INVALID_IDX; + } + + + for (size_t i = 0; i < polygons.numTriangles(); ++i) { + tmpPolygons.triangle(triangleIdx) = polygons.triangle(i); + tmpPolygons.triangleFlags(triangleIdx) = polygons.triangleFlags(i); + ++triangleIdx; + } + + + size_t quadIdx = 0; + for (size_t i = 0; i < polygons.numQuads(); ++i) { + openvdb::Vec4I& quad = polygons.quad(i); + + if (quad[0] != util::INVALID_IDX) { + tmpPolygons.quad(quadIdx) = quad; + tmpPolygons.quadFlags(quadIdx) = polygons.quadFlags(i); + ++quadIdx; + } + } + + + polygons.copy(tmpPolygons); + } + + } + + + if (!newPoints.empty()) { + + size_t newPointCount = newPoints.size() + mPointListSize; + + internal::UniquePtr::type + newPointList(new openvdb::Vec3s[newPointCount]); + + for (size_t i = 0; i < mPointListSize; ++i) { + newPointList.get()[i] = mPoints[i]; + } + + for (size_t i = mPointListSize; i < newPointCount; ++i) { + newPointList.get()[i] = newPoints[i - mPointListSize]; + } + + mPointListSize = newPointCount; + mPoints.reset(newPointList.release()); + mPointFlags.resize(mPointListSize, 0); + } + } +} + + +//////////////////////////////////////// + + +template +inline void +volumeToMesh( + const GridType& grid, + std::vector& points, + std::vector& triangles, + std::vector& quads, + double isovalue, + double adaptivity) +{ + VolumeToMesh mesher(isovalue, adaptivity); + mesher(grid); + + // Preallocate the point list + points.clear(); + points.resize(mesher.pointListSize()); + + { // Copy points + internal::PointListCopy ptnCpy(mesher.pointList(), points); + tbb::parallel_for(tbb::blocked_range(0, points.size()), ptnCpy); + mesher.pointList().reset(NULL); + } + + PolygonPoolList& polygonPoolList = mesher.polygonPoolList(); + + { // Preallocate primitive lists + size_t numQuads = 0, numTriangles = 0; + for (size_t n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { + openvdb::tools::PolygonPool& polygons = polygonPoolList[n]; + numTriangles += polygons.numTriangles(); + numQuads += polygons.numQuads(); + } + + triangles.clear(); + triangles.resize(numTriangles); + quads.clear(); + quads.resize(numQuads); + } + + // Copy primitives + size_t qIdx = 0, tIdx = 0; + for (size_t n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { + openvdb::tools::PolygonPool& polygons = polygonPoolList[n]; + + for (size_t i = 0, I = polygons.numQuads(); i < I; ++i) { + quads[qIdx++] = polygons.quad(i); + } + + for (size_t i = 0, I = polygons.numTriangles(); i < I; ++i) { + triangles[tIdx++] = polygons.triangle(i); + } + } +} + + +template +void +volumeToMesh( + const GridType& grid, + std::vector& points, + std::vector& quads, + double isovalue) +{ + std::vector triangles(0); + volumeToMesh(grid,points, triangles, quads, isovalue, 0.0); +} + + +//////////////////////////////////////// + + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_VOLUME_TO_MESH_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tools/VolumeToSpheres.h b/openvdb_2_3_0_library/openvdb/tools/VolumeToSpheres.h new file mode 100755 index 0000000..f233eef --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tools/VolumeToSpheres.h @@ -0,0 +1,1034 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_TOOLS_VOLUME_TO_SPHERES_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_VOLUME_TO_SPHERES_HAS_BEEN_INCLUDED + +#include +#include +#include // for erodeVoxels() + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include // std::numeric_limits + +////////// + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + + +/// @brief Threaded method to fill a closed level set or fog volume +/// with adaptively sized spheres. +/// +/// @param grid a scalar gird to fill with spheres. +/// +/// @param spheres a @c Vec4 array representing the spheres that returned by this +/// method. The first three components specify the sphere center +/// and the fourth is the radius. The spheres in this array are +/// ordered by radius, biggest to smallest. +/// +/// @param maxSphereCount no more than this number of spheres are generated. +/// +/// @param overlapping toggle to allow spheres to overlap/intersect +/// +/// @param minRadius determines the smallest sphere size in voxel units. +/// +/// @param maxRadius determines the largest sphere size in voxel units. +/// +/// @param isovalue the crossing point of the volume values that is considered +/// the surface. The zero default value works for signed distance +/// fields while fog volumes require a larger positive value, +/// 0.5 is a good initial guess. +/// +/// @param instanceCount how many interior points to consider for the sphere placement, +/// increasing this count increases the chances of finding optimal +/// sphere sizes. +/// +/// @param interrupter a pointer adhering to the util::NullInterrupter interface +/// +template +inline void +fillWithSpheres( + const GridT& grid, + std::vector& spheres, + int maxSphereCount, + bool overlapping = false, + float minRadius = 1.0, + float maxRadius = std::numeric_limits::max(), + float isovalue = 0.0, + int instanceCount = 10000, + InterrupterT* interrupter = NULL); + + +/// @brief @c fillWithSpheres method variant that automatically infers +/// the util::NullInterrupter. +template +inline void +fillWithSpheres( + const GridT& grid, + std::vector& spheres, + int maxSphereCount, + bool overlapping = false, + float minRadius = 1.0, + float maxRadius = std::numeric_limits::max(), + float isovalue = 0.0, + int instanceCount = 10000) +{ + fillWithSpheres(grid, spheres, + maxSphereCount, overlapping, minRadius, maxRadius, isovalue, instanceCount); +} + + +//////////////////////////////////////// + + +/// @brief Accelerated closest surface point queries for narrow band level sets. +/// Supports queries that originate at arbitrary worldspace locations, is +/// not confined to the narrow band region of the input volume geometry. +template +class ClosestSurfacePoint +{ +public: + typedef typename GridT::TreeType TreeT; + typedef typename TreeT::template ValueConverter::Type IntTreeT; + typedef typename TreeT::template ValueConverter::Type Int16TreeT; + + + ClosestSurfacePoint(); + + + /// @brief Extracts the surface points and constructs a spatial acceleration structure. + /// + /// @param grid a scalar gird, level set or fog volume. + /// + /// @param isovalue the crossing point of the volume values that is considered + /// the surface. The zero default value works for signed distance + /// fields while fog volumes require a larger positive value, + /// 0.5 is a good initial guess. + /// + /// @param interrupter a pointer adhering to the util::NullInterrupter interface. + /// + template + void initialize(const GridT& grid, float isovalue = 0.0, InterrupterT* interrupter = NULL); + + + /// @brief @c initialize method variant that automatically infers + /// the util::NullInterrupter. + void initialize(const GridT& grid, float isovalue = 0.0); + + + + /// @brief Computes distance to closest surface. + /// + /// @param points search locations in world space. + /// + /// @param distances list of closest surface point distances, populated by this method. + /// + bool search(const std::vector& points, std::vector& distances); + + + /// @brief Performs closest point searches. + /// + /// @param points search locations in world space to be replaced by their closest + /// surface point. + /// + /// @param distances list of closest surface point distances, populated by this method. + /// + bool searchAndReplace(std::vector& points, std::vector& distances); + + + /// @{ + /// @brief Tree accessors + const IntTreeT& indexTree() const { return *mIdxTreePt; } + const Int16TreeT& signTree() const { return *mSignTreePt; } + /// @} + +private: + typedef typename IntTreeT::LeafNodeType IntLeafT; + typedef std::pair IndexRange; + + bool mIsInitialized; + std::vector mLeafBoundingSpheres, mNodeBoundingSpheres; + std::vector mLeafRanges; + std::vector mLeafNodes; + PointList mSurfacePointList; + size_t mPointListSize, mMaxNodeLeafs; + float mMaxRadiusSqr; + typename IntTreeT::Ptr mIdxTreePt; + typename Int16TreeT::Ptr mSignTreePt; + + bool search(std::vector&, std::vector&, bool transformPoints); +}; + + +//////////////////////////////////////// + + + + +// Internal utility methods + + +namespace internal { + +struct PointAccessor +{ + PointAccessor(std::vector& points) + : mPoints(points) + { + } + + void add(const Vec3R &pos) + { + mPoints.push_back(pos); + } +private: + std::vector& mPoints; +}; + + +template +class LeafBS +{ +public: + + LeafBS(std::vector& leafBoundingSpheres, + const std::vector& leafNodes, + const math::Transform& transform, + const PointList& surfacePointList); + + void run(bool threaded = true); + + + void operator()(const tbb::blocked_range&) const; + +private: + std::vector& mLeafBoundingSpheres; + const std::vector& mLeafNodes; + const math::Transform& mTransform; + const PointList& mSurfacePointList; +}; + +template +LeafBS::LeafBS( + std::vector& leafBoundingSpheres, + const std::vector& leafNodes, + const math::Transform& transform, + const PointList& surfacePointList) + : mLeafBoundingSpheres(leafBoundingSpheres) + , mLeafNodes(leafNodes) + , mTransform(transform) + , mSurfacePointList(surfacePointList) +{ +} + +template +void +LeafBS::run(bool threaded) +{ + if (threaded) { + tbb::parallel_for(tbb::blocked_range(0, mLeafNodes.size()), *this); + } else { + (*this)(tbb::blocked_range(0, mLeafNodes.size())); + } +} + +template +void +LeafBS::operator()(const tbb::blocked_range& range) const +{ + typename IntLeafT::ValueOnCIter iter; + Vec3s avg; + + for (size_t n = range.begin(); n != range.end(); ++n) { + + avg[0] = 0.0; + avg[1] = 0.0; + avg[2] = 0.0; + + int count = 0; + for (iter = mLeafNodes[n]->cbeginValueOn(); iter; ++iter) { + avg += mSurfacePointList[iter.getValue()]; + ++count; + } + + if (count > 1) avg *= float(1.0 / double(count)); + + float maxDist = 0.0; + + for (iter = mLeafNodes[n]->cbeginValueOn(); iter; ++iter) { + float tmpDist = (mSurfacePointList[iter.getValue()] - avg).lengthSqr(); + if (tmpDist > maxDist) maxDist = tmpDist; + } + + Vec4R& sphere = mLeafBoundingSpheres[n]; + + sphere[0] = avg[0]; + sphere[1] = avg[1]; + sphere[2] = avg[2]; + sphere[3] = maxDist * 2.0; // padded radius + } +} + + +class NodeBS +{ +public: + typedef std::pair IndexRange; + + NodeBS(std::vector& nodeBoundingSpheres, + const std::vector& leafRanges, + const std::vector& leafBoundingSpheres); + + void run(bool threaded = true); + + + void operator()(const tbb::blocked_range&) const; + +private: + std::vector& mNodeBoundingSpheres; + const std::vector& mLeafRanges; + const std::vector& mLeafBoundingSpheres; +}; + +inline +NodeBS::NodeBS(std::vector& nodeBoundingSpheres, + const std::vector& leafRanges, + const std::vector& leafBoundingSpheres) + : mNodeBoundingSpheres(nodeBoundingSpheres) + , mLeafRanges(leafRanges) + , mLeafBoundingSpheres(leafBoundingSpheres) +{ +} + +inline void +NodeBS::run(bool threaded) +{ + if (threaded) { + tbb::parallel_for(tbb::blocked_range(0, mLeafRanges.size()), *this); + } else { + (*this)(tbb::blocked_range(0, mLeafRanges.size())); + } +} + +inline void +NodeBS::operator()(const tbb::blocked_range& range) const +{ + Vec3s avg, pos; + + for (size_t n = range.begin(); n != range.end(); ++n) { + + avg[0] = 0.0; + avg[1] = 0.0; + avg[2] = 0.0; + + int count = mLeafRanges[n].second - mLeafRanges[n].first; + + for (size_t i = mLeafRanges[n].first; i < mLeafRanges[n].second; ++i) { + avg[0] += mLeafBoundingSpheres[i][0]; + avg[1] += mLeafBoundingSpheres[i][1]; + avg[2] += mLeafBoundingSpheres[i][2]; + } + + if (count > 1) avg *= float(1.0 / double(count)); + + + float maxDist = 0.0; + + for (size_t i = mLeafRanges[n].first; i < mLeafRanges[n].second; ++i) { + pos[0] = mLeafBoundingSpheres[i][0]; + pos[1] = mLeafBoundingSpheres[i][1]; + pos[2] = mLeafBoundingSpheres[i][2]; + + float tmpDist = (pos - avg).lengthSqr() + mLeafBoundingSpheres[i][3]; + if (tmpDist > maxDist) maxDist = tmpDist; + } + + Vec4R& sphere = mNodeBoundingSpheres[n]; + + sphere[0] = avg[0]; + sphere[1] = avg[1]; + sphere[2] = avg[2]; + sphere[3] = maxDist * 2.0; // padded radius + } +} + + + +//////////////////////////////////////// + + +template +class ClosestPointDist +{ +public: + typedef std::pair IndexRange; + + ClosestPointDist( + std::vector& instancePoints, + std::vector& instanceDistances, + const PointList& surfacePointList, + const std::vector& leafNodes, + const std::vector& leafRanges, + const std::vector& leafBoundingSpheres, + const std::vector& nodeBoundingSpheres, + size_t maxNodeLeafs, + bool transformPoints = false); + + + void run(bool threaded = true); + + + void operator()(const tbb::blocked_range&) const; + +private: + + void evalLeaf(size_t index, const IntLeafT& leaf) const; + void evalNode(size_t pointIndex, size_t nodeIndex) const; + + + std::vector& mInstancePoints; + std::vector& mInstanceDistances; + + const PointList& mSurfacePointList; + + const std::vector& mLeafNodes; + const std::vector& mLeafRanges; + const std::vector& mLeafBoundingSpheres; + const std::vector& mNodeBoundingSpheres; + + std::vector mLeafDistances, mNodeDistances; + + const bool mTransformPoints; + size_t mClosestPointIndex; +}; + + +template +ClosestPointDist::ClosestPointDist( + std::vector& instancePoints, + std::vector& instanceDistances, + const PointList& surfacePointList, + const std::vector& leafNodes, + const std::vector& leafRanges, + const std::vector& leafBoundingSpheres, + const std::vector& nodeBoundingSpheres, + size_t maxNodeLeafs, + bool transformPoints) + : mInstancePoints(instancePoints) + , mInstanceDistances(instanceDistances) + , mSurfacePointList(surfacePointList) + , mLeafNodes(leafNodes) + , mLeafRanges(leafRanges) + , mLeafBoundingSpheres(leafBoundingSpheres) + , mNodeBoundingSpheres(nodeBoundingSpheres) + , mLeafDistances(maxNodeLeafs, 0.0) + , mNodeDistances(leafRanges.size(), 0.0) + , mTransformPoints(transformPoints) + , mClosestPointIndex(0) +{ +} + + +template +void +ClosestPointDist::run(bool threaded) +{ + if (threaded) { + tbb::parallel_for(tbb::blocked_range(0, mInstancePoints.size()), *this); + } else { + (*this)(tbb::blocked_range(0, mInstancePoints.size())); + } +} + +template +void +ClosestPointDist::evalLeaf(size_t index, const IntLeafT& leaf) const +{ + typename IntLeafT::ValueOnCIter iter; + const Vec3s center = mInstancePoints[index]; + size_t& closestPointIndex = const_cast(mClosestPointIndex); + + for (iter = leaf.cbeginValueOn(); iter; ++iter) { + + const Vec3s& point = mSurfacePointList[iter.getValue()]; + float tmpDist = (point - center).lengthSqr(); + + if (tmpDist < mInstanceDistances[index]) { + mInstanceDistances[index] = tmpDist; + closestPointIndex = iter.getValue(); + } + } +} + + +template +void +ClosestPointDist::evalNode(size_t pointIndex, size_t nodeIndex) const +{ + const Vec3R& pos = mInstancePoints[pointIndex]; + float minDist = mInstanceDistances[pointIndex]; + size_t minDistIdx = 0; + Vec3R center; + bool updatedDist = false; + + for (size_t i = mLeafRanges[nodeIndex].first, n = 0; i < mLeafRanges[nodeIndex].second; ++i, ++n) { + + float& distToLeaf = const_cast(mLeafDistances[n]); + + center[0] = mLeafBoundingSpheres[i][0]; + center[1] = mLeafBoundingSpheres[i][1]; + center[2] = mLeafBoundingSpheres[i][2]; + + distToLeaf = (pos - center).lengthSqr() - mLeafBoundingSpheres[i][3]; + + if (distToLeaf < minDist) { + minDist = distToLeaf; + minDistIdx = i; + updatedDist = true; + } + } + + if (!updatedDist) return; + + evalLeaf(pointIndex, *mLeafNodes[minDistIdx]); + + for (size_t i = mLeafRanges[nodeIndex].first, n = 0; i < mLeafRanges[nodeIndex].second; ++i, ++n) { + if (mLeafDistances[n] < mInstanceDistances[pointIndex] && i != minDistIdx) { + evalLeaf(pointIndex, *mLeafNodes[i]); + } + } +} + + +template +void +ClosestPointDist::operator()(const tbb::blocked_range& range) const +{ + Vec3R center; + for (size_t n = range.begin(); n != range.end(); ++n) { + + const Vec3R& pos = mInstancePoints[n]; + float minDist = mInstanceDistances[n]; + size_t minDistIdx = 0; + + for (size_t i = 0, I = mNodeDistances.size(); i < I; ++i) { + float& distToNode = const_cast(mNodeDistances[i]); + + center[0] = mNodeBoundingSpheres[i][0]; + center[1] = mNodeBoundingSpheres[i][1]; + center[2] = mNodeBoundingSpheres[i][2]; + + distToNode = (pos - center).lengthSqr() - mNodeBoundingSpheres[i][3]; + + if (distToNode < minDist) { + minDist = distToNode; + minDistIdx = i; + } + } + + evalNode(n, minDistIdx); + + for (size_t i = 0, I = mNodeDistances.size(); i < I; ++i) { + if (mNodeDistances[i] < mInstanceDistances[n] && i != minDistIdx) { + evalNode(n, i); + } + } + + mInstanceDistances[n] = std::sqrt(mInstanceDistances[n]); + + if (mTransformPoints) mInstancePoints[n] = mSurfacePointList[mClosestPointIndex]; + } +} + + +class UpdatePoints +{ +public: + UpdatePoints( + const Vec4s& sphere, + const std::vector& points, + std::vector& distances, + std::vector& mask, + bool overlapping); + + float radius() const { return mRadius; } + int index() const { return mIndex; }; + + void run(bool threaded = true); + + + UpdatePoints(UpdatePoints&, tbb::split); + void operator()(const tbb::blocked_range& range); + void join(const UpdatePoints& rhs) + { + if (rhs.mRadius > mRadius) { + mRadius = rhs.mRadius; + mIndex = rhs.mIndex; + } + } + +private: + + const Vec4s& mSphere; + const std::vector& mPoints; + + std::vector& mDistances; + std::vector& mMask; + + bool mOverlapping; + float mRadius; + int mIndex; +}; + +inline +UpdatePoints::UpdatePoints( + const Vec4s& sphere, + const std::vector& points, + std::vector& distances, + std::vector& mask, + bool overlapping) + : mSphere(sphere) + , mPoints(points) + , mDistances(distances) + , mMask(mask) + , mOverlapping(overlapping) + , mRadius(0.0) + , mIndex(0) +{ +} + +inline +UpdatePoints::UpdatePoints(UpdatePoints& rhs, tbb::split) + : mSphere(rhs.mSphere) + , mPoints(rhs.mPoints) + , mDistances(rhs.mDistances) + , mMask(rhs.mMask) + , mOverlapping(rhs.mOverlapping) + , mRadius(rhs.mRadius) + , mIndex(rhs.mIndex) +{ +} + +inline void +UpdatePoints::run(bool threaded) +{ + if (threaded) { + tbb::parallel_reduce(tbb::blocked_range(0, mPoints.size()), *this); + } else { + (*this)(tbb::blocked_range(0, mPoints.size())); + } +} + +inline void +UpdatePoints::operator()(const tbb::blocked_range& range) +{ + Vec3s pos; + for (size_t n = range.begin(); n != range.end(); ++n) { + if (mMask[n]) continue; + + pos.x() = float(mPoints[n].x()) - mSphere[0]; + pos.y() = float(mPoints[n].y()) - mSphere[1]; + pos.z() = float(mPoints[n].z()) - mSphere[2]; + + float dist = pos.length(); + + if (dist < mSphere[3]) { + mMask[n] = 1; + continue; + } + + if (!mOverlapping) { + mDistances[n] = std::min(mDistances[n], (dist - mSphere[3])); + } + + if (mDistances[n] > mRadius) { + mRadius = mDistances[n]; + mIndex = n; + } + } +} + + +} // namespace internal + + +//////////////////////////////////////// + + +template +inline void +fillWithSpheres( + const GridT& grid, + std::vector& spheres, + int maxSphereCount, + bool overlapping, + float minRadius, + float maxRadius, + float isovalue, + int instanceCount, + InterrupterT* interrupter) +{ + spheres.clear(); + spheres.reserve(maxSphereCount); + + const bool addNBPoints = grid.activeVoxelCount() < 10000; + int instances = std::max(instanceCount, maxSphereCount); + + typedef typename GridT::TreeType TreeT; + typedef typename GridT::ValueType ValueT; + + typedef typename TreeT::template ValueConverter::Type BoolTreeT; + typedef typename TreeT::template ValueConverter::Type IntTreeT; + typedef typename TreeT::template ValueConverter::Type Int16TreeT; + + typedef tree::LeafManager LeafManagerT; + typedef tree::LeafManager IntLeafManagerT; + typedef tree::LeafManager Int16LeafManagerT; + + + typedef boost::mt11213b RandGen; + RandGen mtRand(/*seed=*/0); + + const TreeT& tree = grid.tree(); + const math::Transform& transform = grid.transform(); + + std::vector instancePoints; + + { // Scatter candidate sphere centroids (instancePoints) + typename Grid::Ptr interiorMaskPtr; + + if (grid.getGridClass() == GRID_LEVEL_SET) { + interiorMaskPtr = sdfInteriorMask(grid, ValueT(isovalue)); + } else { + interiorMaskPtr = typename Grid::Ptr(Grid::create(false)); + interiorMaskPtr->setTransform(transform.copy()); + interiorMaskPtr->tree().topologyUnion(tree); + } + + if (interrupter && interrupter->wasInterrupted()) return; + + erodeVoxels(interiorMaskPtr->tree(), 1); + + instancePoints.reserve(instances); + internal::PointAccessor ptnAcc(instancePoints); + + UniformPointScatter + scatter(ptnAcc, (addNBPoints ? (instances / 2) : instances), mtRand, interrupter); + + scatter(*interiorMaskPtr); + } + + if (interrupter && interrupter->wasInterrupted()) return; + + std::vector instanceRadius; + + ClosestSurfacePoint csp; + csp.initialize(grid, isovalue, interrupter); + + // add extra instance points in the interior narrow band. + if (instancePoints.size() < instances) { + const Int16TreeT& signTree = csp.signTree(); + typename Int16TreeT::LeafNodeType::ValueOnCIter it; + typename Int16TreeT::LeafCIter leafIt = signTree.cbeginLeaf(); + + for (; leafIt; ++leafIt) { + for (it = leafIt->cbeginValueOn(); it; ++it) { + const int flags = it.getValue(); + if (!(0xE00 & flags) && (flags & 0x100)) { + instancePoints.push_back(transform.indexToWorld(it.getCoord())); + } + + if (instancePoints.size() == instances) break; + } + if (instancePoints.size() == instances) break; + } + } + + + if (interrupter && interrupter->wasInterrupted()) return; + + if (!csp.search(instancePoints, instanceRadius)) return; + + std::vector instanceMask(instancePoints.size(), 0); + float largestRadius = 0.0; + int largestRadiusIdx = 0; + + for (size_t n = 0, N = instancePoints.size(); n < N; ++n) { + if (instanceRadius[n] > largestRadius) { + largestRadius = instanceRadius[n]; + largestRadiusIdx = n; + } + } + + Vec3s pos; + Vec4s sphere; + minRadius *= transform.voxelSize()[0]; + maxRadius *= transform.voxelSize()[0]; + + for (size_t s = 0, S = std::min(size_t(maxSphereCount), instancePoints.size()); s < S; ++s) { + + if (interrupter && interrupter->wasInterrupted()) return; + + largestRadius = std::min(maxRadius, largestRadius); + + if (s != 0 && largestRadius < minRadius) break; + + sphere[0] = float(instancePoints[largestRadiusIdx].x()); + sphere[1] = float(instancePoints[largestRadiusIdx].y()); + sphere[2] = float(instancePoints[largestRadiusIdx].z()); + sphere[3] = largestRadius; + + spheres.push_back(sphere); + instanceMask[largestRadiusIdx] = 1; + + internal::UpdatePoints op(sphere, instancePoints, instanceRadius, instanceMask, overlapping); + op.run(); + + largestRadius = op.radius(); + largestRadiusIdx = op.index(); + } +} + +//////////////////////////////////////// + + +template +ClosestSurfacePoint::ClosestSurfacePoint() + : mIsInitialized(false) + , mLeafBoundingSpheres(0) + , mNodeBoundingSpheres(0) + , mLeafRanges(0) + , mLeafNodes(0) + , mSurfacePointList() + , mPointListSize(0) + , mMaxNodeLeafs(0) + , mMaxRadiusSqr(0.0) + , mIdxTreePt() +{ +} + +template +void +ClosestSurfacePoint::initialize(const GridT& grid, float isovalue) +{ + initialize(grid, isovalue, NULL); +} + + +template +template +void +ClosestSurfacePoint::initialize( + const GridT& grid, float isovalue, InterrupterT* interrupter) +{ + mIsInitialized = false; + typedef tree::LeafManager LeafManagerT; + typedef tree::LeafManager IntLeafManagerT; + typedef tree::LeafManager Int16LeafManagerT; + typedef typename GridT::ValueType ValueT; + + const TreeT& tree = grid.tree(); + const math::Transform& transform = grid.transform(); + + { // Extract surface point cloud + + { + LeafManagerT leafs(tree); + internal::SignData + signDataOp(tree, leafs, ValueT(isovalue)); + + signDataOp.run(); + + mSignTreePt = signDataOp.signTree(); + mIdxTreePt = signDataOp.idxTree(); + } + + if (interrupter && interrupter->wasInterrupted()) return; + + Int16LeafManagerT signLeafs(*mSignTreePt); + + std::vector regions(signLeafs.leafCount(), 0); + signLeafs.foreach(internal::CountPoints(regions)); + + mPointListSize = 0; + for (size_t tmp = 0, n = 0, N = regions.size(); n < N; ++n) { + tmp = regions[n]; + regions[n] = mPointListSize; + mPointListSize += tmp; + } + + if (mPointListSize == 0) return; + + mSurfacePointList.reset(new Vec3s[mPointListSize]); + + internal::GenPoints + pointOp(signLeafs, tree, *mIdxTreePt, mSurfacePointList, regions, transform, isovalue); + + pointOp.run(); + + mIdxTreePt->topologyUnion(*mSignTreePt); + } + + if (interrupter && interrupter->wasInterrupted()) return; + + // estimate max sphere radius (sqr dist) + CoordBBox bbox = grid.evalActiveVoxelBoundingBox(); + + Vec3s dim = transform.indexToWorld(bbox.min()) - + transform.indexToWorld(bbox.max()); + + dim[0] = std::abs(dim[0]); + dim[1] = std::abs(dim[1]); + dim[2] = std::abs(dim[2]); + + mMaxRadiusSqr = std::min(std::min(dim[0], dim[1]), dim[2]); + mMaxRadiusSqr *= 0.51; + mMaxRadiusSqr *= mMaxRadiusSqr; + + + IntLeafManagerT idxLeafs(*mIdxTreePt); + + + typedef typename IntTreeT::RootNodeType IntRootNodeT; + typedef typename IntRootNodeT::NodeChainType IntNodeChainT; + BOOST_STATIC_ASSERT(boost::mpl::size::value > 1); + typedef typename boost::mpl::at >::type IntInternalNodeT; + + + typename IntTreeT::NodeCIter nIt = mIdxTreePt->cbeginNode(); + nIt.setMinDepth(IntTreeT::NodeCIter::LEAF_DEPTH - 1); + nIt.setMaxDepth(IntTreeT::NodeCIter::LEAF_DEPTH - 1); + + std::vector internalNodes; + + const IntInternalNodeT* node = NULL; + for (; nIt; ++nIt) { + nIt.getNode(node); + if (node) internalNodes.push_back(node); + } + + std::vector().swap(mLeafRanges); + mLeafRanges.resize(internalNodes.size()); + + std::vector().swap(mLeafNodes); + mLeafNodes.reserve(idxLeafs.leafCount()); + + typename IntInternalNodeT::ChildOnCIter leafIt; + mMaxNodeLeafs = 0; + for (size_t n = 0, N = internalNodes.size(); n < N; ++n) { + + mLeafRanges[n].first = mLeafNodes.size(); + + size_t leafCount = 0; + for (leafIt = internalNodes[n]->cbeginChildOn(); leafIt; ++leafIt) { + mLeafNodes.push_back(&(*leafIt)); + ++leafCount; + } + + mMaxNodeLeafs = std::max(leafCount, mMaxNodeLeafs); + + mLeafRanges[n].second = mLeafNodes.size(); + } + + std::vector().swap(mLeafBoundingSpheres); + mLeafBoundingSpheres.resize(mLeafNodes.size()); + + internal::LeafBS leafBS(mLeafBoundingSpheres, mLeafNodes, transform, mSurfacePointList); + leafBS.run(); + + + std::vector().swap(mNodeBoundingSpheres); + mNodeBoundingSpheres.resize(internalNodes.size()); + + internal::NodeBS nodeBS(mNodeBoundingSpheres, mLeafRanges, mLeafBoundingSpheres); + nodeBS.run(); + mIsInitialized = true; +} + + +template +bool +ClosestSurfacePoint::search(std::vector& points, + std::vector& distances, bool transformPoints) +{ + if (!mIsInitialized) return false; + + distances.clear(); + distances.resize(points.size(), mMaxRadiusSqr); + + internal::ClosestPointDist cpd(points, distances, mSurfacePointList, + mLeafNodes, mLeafRanges, mLeafBoundingSpheres, mNodeBoundingSpheres, + mMaxNodeLeafs, transformPoints); + + cpd.run(); + + return true; +} + + +template +bool +ClosestSurfacePoint::search(const std::vector& points, std::vector& distances) +{ + return search(const_cast& >(points), distances, false); +} + + +template +bool +ClosestSurfacePoint::searchAndReplace(std::vector& points, std::vector& distances) +{ + return search(points, distances, true); +} + + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_VOLUME_TO_MESH_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tree/InternalNode.h b/openvdb_2_3_0_library/openvdb/tree/InternalNode.h new file mode 100755 index 0000000..d727fce --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tree/InternalNode.h @@ -0,0 +1,2894 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file InternalNode.h +/// +/// @brief Internal table nodes for OpenVDB trees + +#ifndef OPENVDB_TREE_INTERNALNODE_HAS_BEEN_INCLUDED +#define OPENVDB_TREE_INTERNALNODE_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include // for io::readData(), etc. +#include // for Abs(), isExactlyEqual() +#include +#include +#include "Iterator.h" +#include "NodeUnion.h" +#include "Util.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tree { + +template struct SameInternalConfig; // forward declaration + + +template +class InternalNode +{ +public: + typedef _ChildNodeType ChildNodeType; + typedef typename ChildNodeType::LeafNodeType LeafNodeType; + typedef typename ChildNodeType::ValueType ValueType; + typedef NodeUnion UnionType; + typedef util::NodeMask NodeMaskType; + + static const Index + LOG2DIM = Log2Dim, + TOTAL = Log2Dim + ChildNodeType::TOTAL, + DIM = 1 << TOTAL, + NUM_VALUES = 1 << (3 * Log2Dim), + LEVEL = 1 + ChildNodeType::LEVEL; // level 0 = leaf + static const Index64 + NUM_VOXELS = uint64_t(1) << (3 * TOTAL); // total # of voxels represented by this node + + /// @brief ValueConverter::Type is the type of an InternalNode having the same + /// child hierarchy and dimensions as this node but a different value type, T. + template + struct ValueConverter { + typedef InternalNode::Type, Log2Dim> Type; + }; + + /// @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; + }; + + + InternalNode() {} + + explicit InternalNode(const ValueType& offValue); + + InternalNode(const Coord&, const ValueType& value, bool active = false); + + /// Deep copy constructor + InternalNode(const InternalNode&); + + /// Value conversion copy constructor + template + explicit InternalNode(const InternalNode& other); + + /// Topology copy constructor + template + InternalNode(const InternalNode& other, + const ValueType& background, TopologyCopy); + + /// Topology copy constructor + template + InternalNode(const InternalNode& other, + const ValueType& offValue, const ValueType& onValue, TopologyCopy); + + virtual ~InternalNode(); + +protected: + typedef typename NodeMaskType::OnIterator MaskOnIterator; + typedef typename NodeMaskType::OffIterator MaskOffIterator; + typedef typename NodeMaskType::DenseIterator MaskDenseIterator; + + // Type tags to disambiguate template instantiations + struct ValueOn {}; struct ValueOff {}; struct ValueAll {}; + struct ChildOn {}; struct ChildOff {}; struct ChildAll {}; + + // The following class templates implement the iterator interfaces specified in Iterator.h + // by providing getItem(), setItem() and/or modifyItem() methods. + + template + struct ChildIter: public SparseIteratorBase< + MaskIterT, ChildIter, NodeT, ChildT> + { + ChildIter() {} + ChildIter(const MaskIterT& iter, NodeT* parent): SparseIteratorBase< + MaskIterT, ChildIter, NodeT, ChildT>(iter, parent) {} + + ChildT& getItem(Index pos) const + { + 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 + + template + struct ValueIter: public SparseIteratorBase< + MaskIterT, ValueIter, NodeT, ValueT> + { + ValueIter() {} + ValueIter(const MaskIterT& iter, NodeT* parent): SparseIteratorBase< + MaskIterT, ValueIter, NodeT, ValueT>(iter, parent) {} + + const ValueT& getItem(Index pos) const { return this->parent().mNodes[pos].getValue(); } + + // Note: setItem() can't be called on const iterators. + void setItem(Index pos, const ValueT& v) const { this->parent().mNodes[pos].setValue(v); } + + // Note: modifyItem() can't be called on const iterators. + template + void modifyItem(Index pos, const ModifyOp& op) const + { + op(this->parent().mNodes[pos].getValue()); + } + };// ValueIter + + template + struct DenseIter: public DenseIteratorBase< + MaskDenseIterator, DenseIter, NodeT, ChildT, ValueT> + { + typedef DenseIteratorBase BaseT; + typedef typename BaseT::NonConstValueType NonConstValueT; + + DenseIter() {} + DenseIter(const MaskDenseIterator& iter, NodeT* parent): + DenseIteratorBase(iter, parent) {} + + bool getItem(Index pos, ChildT*& child, NonConstValueT& value) const + { + if (this->parent().isChildMaskOn(pos)) { + child = this->parent().getChildNode(pos); + return true; + } + child = NULL; + 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) + typedef ChildIter ChildOnIter; + typedef ChildIter ChildOnCIter; + typedef ValueIter ChildOffIter; + typedef ValueIter ChildOffCIter; + typedef DenseIter ChildAllIter; + typedef DenseIter ChildAllCIter; + + typedef ValueIter ValueOnIter; + typedef ValueIter ValueOnCIter; + typedef ValueIter ValueOffIter; + typedef ValueIter ValueOffCIter; + typedef ValueIter ValueAllIter; + typedef ValueIter ValueAllCIter; + + ChildOnCIter cbeginChildOn() const { return ChildOnCIter(mChildMask.beginOn(), this); } + ChildOffCIter cbeginChildOff() const { return ChildOffCIter(mChildMask.beginOff(), this); } + ChildAllCIter cbeginChildAll() const { return ChildAllCIter(mChildMask.beginDense(), this); } + ChildOnCIter beginChildOn() const { return cbeginChildOn(); } + ChildOffCIter beginChildOff() const { return cbeginChildOff(); } + ChildAllCIter beginChildAll() const { return cbeginChildAll(); } + ChildOnIter beginChildOn() { return ChildOnIter(mChildMask.beginOn(), this); } + ChildOffIter beginChildOff() { return ChildOffIter(mChildMask.beginOff(), this); } + ChildAllIter beginChildAll() { return ChildAllIter(mChildMask.beginDense(), this); } + + ValueOnCIter cbeginValueOn() const { return ValueOnCIter(mValueMask.beginOn(), this); } + ValueOffCIter cbeginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } + ValueAllCIter cbeginValueAll() const { return ValueAllCIter(mChildMask.beginOff(), this); } + ValueOnCIter beginValueOn() const { return cbeginValueOn(); } + ValueOffCIter beginValueOff() const { return cbeginValueOff(); } + ValueAllCIter beginValueAll() const { return cbeginValueAll(); } + ValueOnIter beginValueOn() { return ValueOnIter(mValueMask.beginOn(), this); } + ValueOffIter beginValueOff() { return ValueOffIter(mValueMask.beginOff(), this); } + ValueAllIter beginValueAll() { return ValueAllIter(mChildMask.beginOff(), this); } + + + static Index dim() { return DIM; } + static Index getLevel() { return LEVEL; } + static void getNodeLog2Dims(std::vector& dims); + static Index getChildDim() { return ChildNodeType::DIM; } + + /// Return the linear table offset of the given global or local coordinates. + static Index coordToOffset(const Coord& xyz); + /// @brief Return the local coordinates for a linear table offset, + /// where offset 0 has coordinates (0, 0, 0). + static void offsetToLocalCoord(Index n, Coord& xyz); + /// Return the global coordinates for a linear table offset. + Coord offsetToGlobalCoord(Index n) const; + + /// Return the grid index coordinates of this node's local origin. + const Coord& origin() const { return mOrigin; } + /// 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); } + + bool isEmpty() const { return mChildMask.isOff(); } + + /// Return @c true if all of this node's table entries have the same active state + /// and the same constant value to within the given tolerance, + /// and return that value in @a constValue and the active state in @a state. + bool isConstant(ValueType& constValue, bool& state, + const ValueType& tolerance = zeroVal()) const; + /// Return @c true if this node has no children and only contains inactive values. + bool isInactive() const { return this->isChildMaskOff() && this->isValueMaskOff(); } + + /// Return @c true if the voxel at the given coordinates is active. + bool isValueOn(const Coord& xyz) const; + /// Return @c true if the voxel at the given offset is active. + bool isValueOn(Index offset) const { return mValueMask.isOn(offset); } + + /// Return @c true if this node or any of its child nodes have any active tiles. + bool hasActiveTiles() const; + + const ValueType& getValue(const Coord& xyz) const; + bool probeValue(const Coord& xyz, ValueType& value) const; + + /// @brief Return the level of the tree (0 = leaf) at which the value + /// at the given coordinates resides. + Index getValueLevel(const Coord& xyz) const; + + /// @brief If the first entry in this node's table is a tile, return the tile's value. + /// Otherwise, return the result of calling getFirstValue() on the child. + const ValueType& getFirstValue() const; + /// @brief If the last entry in this node's table is a tile, return the tile's value. + /// Otherwise, return the result of calling getLastValue() on the child. + const ValueType& getLastValue() const; + + /// Set the active state of the voxel at the given coordinates but don't change its value. + void setActiveState(const Coord& xyz, bool on); + /// Set the value of the voxel at the given coordinates but don't change its active state. + void setValueOnly(const Coord& xyz, const ValueType& value); + /// Mark the voxel at the given coordinates as active but don't change its value. + void setValueOn(const Coord& xyz); + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValueOn(const Coord& xyz, const ValueType& value); + /// Mark the voxel at the given coordinates as inactive but don't change its value. + void setValueOff(const Coord& xyz); + /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. + void setValueOff(const Coord& xyz, const ValueType& value); + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + template + void modifyValue(const Coord& xyz, const ModifyOp& op); + /// Apply a functor to the voxel at the given coordinates. + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op); + + /// Return the value of the voxel at the given coordinates and, if necessary, update + /// the accessor with pointers to the nodes along the path from the root node to + /// the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + const ValueType& getValueAndCache(const Coord& xyz, AccessorT&) const; + + /// Return @c true if the voxel at the given coordinates is active and, if necessary, + /// update the accessor with pointers to the nodes along the path from the root node + /// to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + bool isValueOnAndCache(const Coord& xyz, AccessorT&) const; + + /// Change the value of the voxel at the given coordinates and mark it as active. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void setValueAndCache(const Coord& xyz, const ValueType& value, AccessorT&); + + /// Set the value of the voxel at the given coordinate but preserves its active state. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void setValueOnlyAndCache(const Coord& xyz, const ValueType& value, AccessorT&); + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); + + /// Apply a functor to the voxel at the given coordinates. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void modifyValueAndActiveStateAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); + + /// Change the value of the voxel at the given coordinates and mark it as inactive. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT&); + + /// Set the active state of the voxel at the given coordinates without changing its value. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void setActiveStateAndCache(const Coord& xyz, bool on, AccessorT&); + + /// Return, in @a value, the value of the voxel at the given coordinates and, + /// if necessary, update the accessor with pointers to the nodes along + /// the path from the root node to the node containing the voxel. + /// @return @c true if the voxel at the given coordinates is active + /// @note Used internally by ValueAccessor. + template + bool probeValueAndCache(const Coord& xyz, ValueType& value, AccessorT&) const; + + /// @brief Return the level of the tree (0 = leaf) at which the value + /// at the given coordinates resides. + /// + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + Index getValueLevelAndCache(const Coord& xyz, AccessorT&) const; + + /// Mark all values (both tiles and voxels) as active. + void setValuesOn(); + + + // + // I/O + // + void writeTopology(std::ostream&, bool toHalf = false) const; + void readTopology(std::istream&, bool fromHalf = false); + void writeBuffers(std::ostream&, bool toHalf = false) const; + void readBuffers(std::istream&, bool fromHalf = false); + + + // + // Aux methods + // + /// @brief Set all voxels within an axis-aligned box to a constant value. + /// (The min and max coordinates are inclusive.) + void fill(const CoordBBox& bbox, const ValueType&, bool active = true); + + /// @brief Overwrite each inactive value in this node and in any child nodes with + /// a new value whose magnitude is equal to the specified background value and whose + /// sign is consistent with that of the lexicographically closest active value. + /// @details This is primarily useful for propagating the sign from the (active) voxels + /// in a narrow-band level set to the inactive voxels outside the narrow band. + void signedFloodFill(const ValueType& background); + + /// @brief Overwrite each inactive value in this node and in any child nodes with + /// either @a outside or @a inside, depending on the sign of the lexicographically + /// closest active value. + /// @details Specifically, an inactive value is set to @a outside if the closest active + /// value in the lexicographic direction is positive, otherwise it is set to @a inside. + void signedFloodFill(const ValueType& outside, const ValueType& inside); + + /// Change the sign of all the values represented in this node and + /// its child nodes. + void negate(); + + /// Densify active tiles, i.e., replace them with leaf-level active voxels. + void voxelizeActiveTiles(); + + /// @brief Copy into a dense grid the values of the voxels that lie within + /// a given bounding box. + /// @param bbox inclusive bounding box of the voxels to be copied into the dense grid + /// @param dense dense grid with a stride in @e z of one (see tools::Dense + /// in tools/Dense.h for the required API) + /// @note @a bbox is assumed to be identical to or contained in the coordinate domains + /// of both the dense grid and this node, i.e., no bounds checking is performed. + template + void copyToDense(const CoordBBox& bbox, DenseT& dense) const; + + /// @brief Efficiently merge another tree into this tree using one of several schemes. + /// @warning This operation cannibalizes the other tree. + template + void merge(InternalNode& other, const ValueType& background, const ValueType& otherBackground); + + /// @brief Merge, using one of several schemes, this node (and its descendants) + /// with a tile of the same dimensions and the given value and active state. + template void merge(const ValueType& tileValue, bool tileActive); + + /// @brief Union this branch's set of active values with the other branch's + /// active values. The value type of the other branch can be different. + /// @details The resulting state of a value is active if the corresponding value + /// was already active OR if it is active in the other tree. Also, a resulting + /// value maps to a voxel if the corresponding value already mapped to a voxel + /// OR if it is a voxel in the other tree. Thus, a resulting value can only + /// map to a tile if the corresponding value already mapped to a tile + /// AND if it is a tile value in other tree. + /// + /// Specifically, active tiles and voxels in this branch are not changed, and + /// tiles or voxels that were inactive in this branch but active in the other branch + /// are marked as active in this branch but left with their original values. + template + void topologyUnion(const InternalNode& other); + + /// @brief Intersects this tree's set of active values with the active values + /// of the other tree, whose @c ValueType may be different. + /// @details The resulting state of a value is active only if the corresponding + /// value was already active AND if it is active in the other tree. Also, a + /// resulting value maps to a voxel if the corresponding value + /// already mapped to an active voxel in either of the two grids + /// and it maps to an active tile or voxel in the other grid. + /// + /// @note This operation can delete branches in this grid if they + /// overlap with inactive tiles in the other grid. Likewise active + /// voxels can be turned into unactive voxels resulting in leaf + /// nodes with no active values. Thus, it is recommended to + /// subsequently call prune. + template + void topologyIntersection(const InternalNode& other, + const ValueType& background); + + /// @brief Difference this node's set of active values with the active values + /// of the other node, whose @c ValueType may be different. So a + /// resulting voxel will be active only if the original voxel is + /// active in this node and inactive in the other node. + /// + /// @details The last dummy argument is required to match the signature + /// for InternalNode::topologyDifference. + /// + /// @note This operation modifies only active states, not + /// values. Also note that this operation can result in all voxels + /// being inactive so consider subsequnetly calling prune. + template + void topologyDifference(const InternalNode& other, + const ValueType& background); + + template + void combine(InternalNode& other, CombineOp&); + template + void combine(const ValueType& value, bool valueIsActive, CombineOp&); + + template + void combine2(const InternalNode& other0, const 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; + + /// @brief Call the @c PruneOp functor for each child node and, if the functor + /// returns @c true, prune the node and replace it with a tile. + /// + /// This method is used to implement all of the various pruning algorithms + /// (prune(), pruneInactive(), etc.). It should rarely be called directly. + /// @see openvdb/tree/Util.h for the definition of the @c PruneOp functor + template void pruneOp(PruneOp&); + + /// @brief Reduce the memory footprint of this tree by replacing with tiles + /// any nodes whose values are all the same (optionally to within a tolerance) + /// and have the same active state. + void prune(const ValueType& tolerance = zeroVal()); + + /// @brief Reduce the memory footprint of this tree by replacing with + /// tiles of the given value any nodes whose values are all inactive. + void pruneInactive(const ValueType&); + + /// @brief Reduce the memory footprint of this tree by replacing with + /// background tiles any nodes whose values are all inactive. + void pruneInactive(); + + /// @brief Add the specified leaf to this node, possibly creating a child branch + /// in the process. If the leaf node already exists, replace it. + void addLeaf(LeafNodeType* leaf); + + /// @brief Same as addLeaf() except, if necessary, update the accessor with pointers + /// to the nodes along the path from the root node to the node containing the coordinate. + template + void addLeafAndCache(LeafNodeType* leaf, AccessorT&); + + /// @brief Return a pointer to the node of type @c NodeT that contains voxel (x, y, z) + /// and replace it with a tile of the specified value and state. + /// If no such node exists, leave the tree unchanged and return @c NULL. + /// + /// @note The caller takes ownership of the node and is responsible for deleting it. + /// + /// @warning Since this method potentially removes nodes and branches of the tree, + /// it is important to clear the caches of all ValueAccessors associated with this tree. + template + NodeT* stealNode(const Coord& xyz, const ValueType& value, bool state); + + /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), + /// possibly creating a parent branch or deleting a child branch in the process. + void addTile(Index level, const Coord& xyz, const ValueType& value, bool state); + + /// @brief Delete any existing child branch at the specified offset and add a tile. + void addTile(Index offset, const ValueType& value, bool state); + + /// @brief Same as addTile() except, if necessary, update the accessor with pointers + /// to the nodes along the path from the root node to the node containing (x, y, z). + template + void addTileAndCache(Index level, const Coord& xyz, const ValueType&, bool state, AccessorT&); + + //@{ + /// @brief Return a pointer to the node that contains voxel (x, y, z). + /// If no such node exists, return NULL. + template NodeType* probeNode(const Coord& xyz); + template const NodeType* probeConstNode(const Coord& xyz) const; + //@} + + //@{ + /// @brief Same as probeNode() except, if necessary, update the accessor with pointers + /// to the nodes along the path from the root node to the node containing (x, y, z). + template + NodeType* probeNodeAndCache(const Coord& xyz, AccessorT&); + template + const NodeType* probeConstNodeAndCache(const Coord& xyz, AccessorT&) const; + //@} + + //@{ + /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). + /// If no such node exists, return NULL. + LeafNodeType* probeLeaf(const Coord& xyz); + const LeafNodeType* probeConstLeaf(const Coord& xyz) const; + const LeafNodeType* probeLeaf(const Coord& xyz) const; + //@} + + //@{ + /// @brief Same as probeLeaf() except, if necessary, update the accessor with pointers + /// to the nodes along the path from the root node to the node containing (x, y, z). + template + LeafNodeType* probeLeafAndCache(const Coord& xyz, AccessorT& acc); + template + const LeafNodeType* probeConstLeafAndCache(const Coord& xyz, AccessorT& acc) const; + template + const LeafNodeType* probeLeafAndCache(const Coord& xyz, AccessorT& acc) const; + //@} + + /// @brief Return the leaf node that contains voxel (x, y, z). + /// If no such node exists, create one, but preserve the values and + /// active states of all voxels. + /// + /// @details Use this method to preallocate a static tree topology + /// over which to safely perform multithreaded processing. + LeafNodeType* touchLeaf(const Coord& xyz); + + /// @brief Same as touchLeaf() except, if necessary, update the accessor with pointers + /// to the nodes along the path from the root node to the node containing the coordinate. + template + LeafNodeType* touchLeafAndCache(const Coord& xyz, AccessorT&); + + /// @brief Change inactive tiles or voxels with value oldBackground to newBackground + /// or -oldBackground to -newBackground. Active values are unchanged. + void resetBackground(const ValueType& oldBackground, const ValueType& newBackground); + + /// @brief Return @c true if the given tree branch has the same node and active value + /// topology as this tree branch (but possibly a different @c ValueType). + template + bool hasSameTopology(const InternalNode* other) const; + +protected: + //@{ + /// Allow iterators to call mask accessor methods (setValueMask(), setChildMask(), etc.). + /// @todo Make mask accessors public? + friend class IteratorBase; + friend class IteratorBase; + friend class IteratorBase; + //@} + + /// @brief During topology-only construction, access is needed + /// to protected/private members of other template instances. + template friend class InternalNode; + + // Mask accessors +public: + bool isValueMaskOn(Index n) const { return mValueMask.isOn(n); } + bool isValueMaskOn() const { return mValueMask.isOn(); } + bool isValueMaskOff(Index n) const { return mValueMask.isOff(n); } + bool isValueMaskOff() const { return mValueMask.isOff(); } + bool isChildMaskOn(Index n) const { return mChildMask.isOn(n); } + bool isChildMaskOff(Index n) const { return mChildMask.isOff(n); } + bool isChildMaskOff() const { return mChildMask.isOff(); } +protected: + //@{ + /// Use a mask accessor to ensure consistency between the child and value masks; + /// i.e., the value mask should always be off wherever the child mask is on. + void setValueMask(Index n, bool on) { mValueMask.set(n, mChildMask.isOn(n) ? false : on); } + //@} + + void makeChildNodeEmpty(Index n, const ValueType& value); + void setChildNode( Index i, ChildNodeType* child);//assumes a tile + void resetChildNode(Index i, ChildNodeType* child);//checks for an existing child + ChildNodeType* unsetChildNode(Index i, const ValueType& value); + + template + static inline void doVisit(NodeT&, VisitorOp&); + + template + static inline void doVisit2Node(NodeT&, OtherNodeT&, VisitorOp&); + + template + static inline void doVisit2(NodeT&, OtherChildAllIterT&, VisitorOp&, bool otherIsLHS); + + ///@{ + /// @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; + ///@} + + + 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); +} + + +template +inline +InternalNode::InternalNode(const InternalNode& other): + mChildMask(other.mChildMask), + mValueMask(other.mValueMask), + mOrigin(other.mOrigin) +{ + for (Index i = 0; i < NUM_VALUES; ++i) { + if (isChildMaskOn(i)) { + mNodes[i].setChild(new ChildNodeType(*(other.mNodes[i].getChild()))); + } else { + mNodes[i].setValue(other.mNodes[i].getValue()); + } + } +} + + +// 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) +{ + struct Local { + /// @todo Consider using a value conversion functor passed as an argument instead. + static inline ValueType + convertValue(const typename OtherChildNodeType::ValueType& val) { return ValueType(val); } + }; + + for (Index i = 0; i < NUM_VALUES; ++i) { + if (other.mChildMask.isOff(i)) { + mNodes[i].setValue(Local::convertValue(other.mNodes[i].getValue())); + } else { + mNodes[i].setChild(new ChildNodeType(*(other.mNodes[i].getChild()))); + } + } +} + + +template +template +inline +InternalNode::InternalNode(const InternalNode& other, + const ValueType& offValue, const ValueType& onValue, TopologyCopy): + mChildMask(other.mChildMask), + mValueMask(other.mValueMask), + mOrigin(other.mOrigin) +{ + for (Index i = 0; i < NUM_VALUES; ++i) { + if (isChildMaskOn(i)) { + mNodes[i].setChild(new ChildNodeType(*(other.mNodes[i].getChild()), + offValue, onValue, TopologyCopy())); + } else { + mNodes[i].setValue(isValueMaskOn(i) ? onValue : offValue); + } + } +} + +template +template +inline +InternalNode::InternalNode(const InternalNode& other, + const ValueType& background, TopologyCopy): + mChildMask(other.mChildMask), + mValueMask(other.mValueMask), + mOrigin(other.mOrigin) +{ + for (Index i = 0; i < NUM_VALUES; ++i) mNodes[i].setValue(background); + for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { + mNodes[iter.pos()].setChild(new ChildNodeType(*(other.mNodes[iter.pos()].getChild()), + background, TopologyCopy())); + } +} + + +template +inline +InternalNode::~InternalNode() +{ + for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { + delete mNodes[iter.pos()].getChild(); + } +} + + +//////////////////////////////////////// + + +template +inline Index32 +InternalNode::leafCount() const +{ + if (ChildNodeType::getLevel() == 0) return mChildMask.countOn(); + Index32 sum = 0; + for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { + sum += iter->leafCount(); + } + return sum; +} + + +template +inline Index32 +InternalNode::nonLeafCount() const +{ + Index32 sum = 1; + if (ChildNodeType::getLevel() == 0) return sum; + for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { + sum += iter->nonLeafCount(); + } + return sum; +} + + +template +inline Index64 +InternalNode::onVoxelCount() const +{ + Index64 sum = ChildT::NUM_VOXELS * mValueMask.countOn(); + for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { + sum += iter->onVoxelCount(); + } + return sum; +} + + +template +inline Index64 +InternalNode::offVoxelCount() const +{ + Index64 sum = ChildT::NUM_VOXELS * (NUM_VALUES-mValueMask.countOn()-mChildMask.countOn()); + for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { + sum += iter->offVoxelCount(); + } + return sum; +} + + +template +inline Index64 +InternalNode::onLeafVoxelCount() const +{ + Index64 sum = 0; + for (ChildOnCIter iter = this->beginChildOn(); iter; ++iter) { + sum += mNodes[iter.pos()].getChild()->onLeafVoxelCount(); + } + return sum; +} + + +template +inline Index64 +InternalNode::offLeafVoxelCount() const +{ + Index64 sum = 0; + for (ChildOnCIter iter = this->beginChildOn(); iter; ++iter) { + sum += mNodes[iter.pos()].getChild()->offLeafVoxelCount(); + } + return sum; +} + +template +inline Index64 +InternalNode::onTileCount() const +{ + Index64 sum = mValueMask.countOn(); + for (ChildOnCIter iter = this->cbeginChildOn(); LEVEL>1 && iter; ++iter) { + sum += iter->onTileCount(); + } + return sum; +} + +template +inline Index64 +InternalNode::memUsage() const +{ + Index64 sum = NUM_VALUES * sizeof(UnionType) + mChildMask.memUsage() + + mValueMask.memUsage() + sizeof(mOrigin); + for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { + sum += iter->memUsage(); + } + return sum; +} + + +template +inline void +InternalNode::evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels) const +{ + if (bbox.isInside(this->getNodeBoundingBox())) return; + + for (ValueOnCIter i = this->cbeginValueOn(); i; ++i) { + bbox.expand(i.getCoord(), ChildT::DIM); + } + for (ChildOnCIter i = this->cbeginChildOn(); i; ++i) { + i->evalActiveBoundingBox(bbox, visitVoxels); + } +} + + +//////////////////////////////////////// + + +template +template +inline void +InternalNode::pruneOp(PruneOp& op) +{ + for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { + const Index i = iter.pos(); + ChildT* child = mNodes[i].getChild(); + if (!op(*child)) continue; + delete child; + mChildMask.setOff(i); + mValueMask.set(i, op.state); + mNodes[i].setValue(op.value); + } + +} + + +template +inline void +InternalNode::prune(const ValueType& tolerance) +{ + TolerancePrune op(tolerance); + this->pruneOp(op); +} + + +template +inline void +InternalNode::pruneInactive(const ValueType& bg) +{ + InactivePrune op(bg); + this->pruneOp(op); +} + + +template +inline void +InternalNode::pruneInactive() +{ + this->pruneInactive(this->getBackground()); +} + + +//////////////////////////////////////// + + +template +template +inline NodeT* +InternalNode::stealNode(const Coord& xyz, const ValueType& value, bool state) +{ + if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || + NodeT::LEVEL > ChildT::LEVEL) return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + const Index n = this->coordToOffset(xyz); + if (mChildMask.isOff(n)) return NULL; + ChildT* child = mNodes[n].getChild(); + if (boost::is_same::value) { + mChildMask.setOff(n); + mValueMask.set(n, state); + mNodes[n].setValue(value); + } + return (boost::is_same::value) + ? reinterpret_cast(child) + : child->template stealNode(xyz, value, state); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +//////////////////////////////////////// + + +template +template +inline NodeT* +InternalNode::probeNode(const Coord& xyz) +{ + if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || + NodeT::LEVEL > ChildT::LEVEL) return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + const Index n = this->coordToOffset(xyz); + if (mChildMask.isOff(n)) return NULL; + ChildT* child = mNodes[n].getChild(); + return (boost::is_same::value) + ? reinterpret_cast(child) + : child->template probeNode(xyz); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +template +template +inline NodeT* +InternalNode::probeNodeAndCache(const Coord& xyz, AccessorT& acc) +{ + if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || + NodeT::LEVEL > ChildT::LEVEL) return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + const Index n = this->coordToOffset(xyz); + if (mChildMask.isOff(n)) return NULL; + ChildT* child = mNodes[n].getChild(); + acc.insert(xyz, child); + return (boost::is_same::value) + ? reinterpret_cast(child) + : child->template probeNodeAndCache(xyz, acc); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +template +template +inline const NodeT* +InternalNode::probeConstNode(const Coord& xyz) const +{ + if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || + NodeT::LEVEL > ChildT::LEVEL) return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + const Index n = this->coordToOffset(xyz); + if (mChildMask.isOff(n)) return NULL; + const ChildT* child = mNodes[n].getChild(); + return (boost::is_same::value) + ? reinterpret_cast(child) + : child->template probeConstNode(xyz); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +template +template +inline const NodeT* +InternalNode::probeConstNodeAndCache(const Coord& xyz, AccessorT& acc) const +{ + if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || + NodeT::LEVEL > ChildT::LEVEL) return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + const Index n = this->coordToOffset(xyz); + if (mChildMask.isOff(n)) return NULL; + const ChildT* child = mNodes[n].getChild(); + acc.insert(xyz, child); + return (boost::is_same::value) + ? reinterpret_cast(child) + : child->template probeConstNodeAndCache(xyz, acc); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +//////////////////////////////////////// + + +template +inline typename ChildT::LeafNodeType* +InternalNode::probeLeaf(const Coord& xyz) +{ + return this->template probeNode(xyz); +} + + +template +template +inline typename ChildT::LeafNodeType* +InternalNode::probeLeafAndCache(const Coord& xyz, AccessorT& acc) +{ + return this->template probeNodeAndCache(xyz, acc); +} + + +template +template +inline const typename ChildT::LeafNodeType* +InternalNode::probeLeafAndCache(const Coord& xyz, AccessorT& acc) const +{ + return this->probeConstLeafAndCache(xyz, acc); +} + + +template +inline const typename ChildT::LeafNodeType* +InternalNode::probeConstLeaf(const Coord& xyz) const +{ + return this->template probeConstNode(xyz); +} + + +template +template +inline const typename ChildT::LeafNodeType* +InternalNode::probeConstLeafAndCache(const Coord& xyz, AccessorT& acc) const +{ + return this->template probeConstNodeAndCache(xyz, acc); +} + + +//////////////////////////////////////// + + +template +inline void +InternalNode::addLeaf(LeafNodeType* leaf) +{ + assert(leaf != NULL); + const Coord& xyz = leaf->origin(); + const Index n = this->coordToOffset(xyz); + ChildT* child = NULL; + if (mChildMask.isOff(n)) { + if (ChildT::LEVEL>0) { + child = new ChildT(xyz, mNodes[n].getValue(), mValueMask.isOn(n)); + } else { + child = reinterpret_cast(leaf); + } + this->setChildNode(n, child); + } else { + if (ChildT::LEVEL>0) { + child = mNodes[n].getChild(); + } else { + delete mNodes[n].getChild(); + child = reinterpret_cast(leaf); + mNodes[n].setChild(child); + } + } + child->addLeaf(leaf); +} + + +template +template +inline void +InternalNode::addLeafAndCache(LeafNodeType* leaf, AccessorT& acc) +{ + assert(leaf != NULL); + const Coord& xyz = leaf->origin(); + const Index n = this->coordToOffset(xyz); + ChildT* child = NULL; + if (mChildMask.isOff(n)) { + if (ChildT::LEVEL>0) { + child = new ChildT(xyz, mNodes[n].getValue(), mValueMask.isOn(n)); + acc.insert(xyz, child);//we only cache internal nodes + } else { + child = reinterpret_cast(leaf); + } + this->setChildNode(n, child); + } else { + if (ChildT::LEVEL>0) { + child = mNodes[n].getChild(); + acc.insert(xyz, child);//we only cache internal nodes + } else { + delete mNodes[n].getChild(); + child = reinterpret_cast(leaf); + mNodes[n].setChild(child); + } + } + child->addLeafAndCache(leaf, acc); +} + + +//////////////////////////////////////// + + +template +inline void +InternalNode::addTile(Index n, const ValueType& value, bool state) +{ + assert(n < NUM_VALUES); + this->makeChildNodeEmpty(n, value); + mValueMask.set(n, state); +} + + +template +inline void +InternalNode::addTile(Index level, const Coord& xyz, + const ValueType& value, bool state) +{ + if (LEVEL >= level) { + const Index n = this->coordToOffset(xyz); + if (mChildMask.isOff(n)) {// tile case + if (LEVEL > level) { + ChildT* child = new ChildT(xyz, mNodes[n].getValue(), mValueMask.isOn(n)); + this->setChildNode(n, child); + child->addTile(level, xyz, value, state); + } else { + mValueMask.set(n, state); + mNodes[n].setValue(value); + } + } else {// child branch case + ChildT* child = mNodes[n].getChild(); + if (LEVEL > level) { + child->addTile(level, xyz, value, state); + } else { + delete child; + mChildMask.setOff(n); + mValueMask.set(n, state); + mNodes[n].setValue(value); + } + } + } +} + + +template +template +inline void +InternalNode::addTileAndCache(Index level, const Coord& xyz, + const ValueType& value, bool state, AccessorT& acc) +{ + if (LEVEL >= level) { + const Index n = this->coordToOffset(xyz); + if (mChildMask.isOff(n)) {// tile case + if (LEVEL > level) { + ChildT* child = new ChildT(xyz, mNodes[n].getValue(), mValueMask.isOn(n)); + this->setChildNode(n, child); + acc.insert(xyz, child); + child->addTileAndCache(level, xyz, value, state, acc); + } else { + mValueMask.set(n, state); + mNodes[n].setValue(value); + } + } else {// child branch case + ChildT* child = mNodes[n].getChild(); + if (LEVEL > level) { + acc.insert(xyz, child); + child->addTileAndCache(level, xyz, value, state, acc); + } else { + delete child; + mChildMask.setOff(n); + mValueMask.set(n, state); + mNodes[n].setValue(value); + } + } + } +} + + +//////////////////////////////////////// + + +template +inline typename ChildT::LeafNodeType* +InternalNode::touchLeaf(const Coord& xyz) +{ + const Index n = this->coordToOffset(xyz); + ChildT* child = NULL; + if (mChildMask.isOff(n)) { + child = new ChildT(xyz, mNodes[n].getValue(), mValueMask.isOn(n)); + this->setChildNode(n, child); + } else { + child = mNodes[n].getChild(); + } + return child->touchLeaf(xyz); +} + + +template +template +inline typename ChildT::LeafNodeType* +InternalNode::touchLeafAndCache(const Coord& xyz, AccessorT& acc) +{ + const Index n = this->coordToOffset(xyz); + if (mChildMask.isOff(n)) { + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), mValueMask.isOn(n))); + } + acc.insert(xyz, mNodes[n].getChild()); + return mNodes[n].getChild()->touchLeafAndCache(xyz, acc); +} + + +//////////////////////////////////////// + + +template +inline bool +InternalNode::isConstant(ValueType& constValue, bool& state, + const ValueType& tolerance) const +{ + bool allEqual = true, firstValue = true, valueState = true; + ValueType value = zeroVal(); + for (Index i = 0; allEqual && i < NUM_VALUES; ++i) { + if (this->isChildMaskOff(i)) { + // If entry i is a value, check if it is within tolerance + // and whether its active state matches the other entries. + if (firstValue) { + firstValue = false; + valueState = isValueMaskOn(i); + value = mNodes[i].getValue(); + } else { + allEqual = (isValueMaskOn(i) == valueState) + && math::isApproxEqual(mNodes[i].getValue(), value, tolerance); + } + } else { + // If entry i is a child, check if the child is constant and within tolerance + // and whether its active state matches the other entries. + ValueType childValue = zeroVal(); + bool isChildOn = false; + if (mNodes[i].getChild()->isConstant(childValue, isChildOn, tolerance)) { + if (firstValue) { + firstValue = false; + valueState = isChildOn; + value = childValue; + } else { + allEqual = (isChildOn == valueState) + && math::isApproxEqual(childValue, value, tolerance); + } + } else { // child is not constant + allEqual = false; + } + } + } + if (allEqual) { + constValue = value; + state = valueState; + } + return allEqual; +} + + +//////////////////////////////////////// + + +template +inline bool +InternalNode::hasActiveTiles() const +{ + const bool anyActiveTiles = !mValueMask.isOff(); + if (LEVEL==1 || anyActiveTiles) return anyActiveTiles; + for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { + if (iter->hasActiveTiles()) return true; + } + return false; +} + + +template +inline bool +InternalNode::isValueOn(const Coord& xyz) const +{ + const Index n = this->coordToOffset(xyz); + if (this->isChildMaskOff(n)) return this->isValueMaskOn(n); + return mNodes[n].getChild()->isValueOn(xyz); +} + +template +template +inline bool +InternalNode::isValueOnAndCache(const Coord& xyz, AccessorT& acc) const +{ + const Index n = this->coordToOffset(xyz); + if (this->isChildMaskOff(n)) return this->isValueMaskOn(n); + acc.insert(xyz, mNodes[n].getChild()); + return mNodes[n].getChild()->isValueOnAndCache(xyz, acc); +} + + +template +inline const typename ChildT::ValueType& +InternalNode::getValue(const Coord& xyz) const +{ + const Index n = this->coordToOffset(xyz); + return this->isChildMaskOff(n) ? mNodes[n].getValue() + : mNodes[n].getChild()->getValue(xyz); +} + +template +template +inline const typename ChildT::ValueType& +InternalNode::getValueAndCache(const Coord& xyz, AccessorT& acc) const +{ + const Index n = this->coordToOffset(xyz); + if (this->isChildMaskOn(n)) { + acc.insert(xyz, mNodes[n].getChild()); + return mNodes[n].getChild()->getValueAndCache(xyz, acc); + } + return mNodes[n].getValue(); +} + + +template +inline Index +InternalNode::getValueLevel(const Coord& xyz) const +{ + const Index n = this->coordToOffset(xyz); + return this->isChildMaskOff(n) ? LEVEL : mNodes[n].getChild()->getValueLevel(xyz); +} + +template +template +inline Index +InternalNode::getValueLevelAndCache(const Coord& xyz, AccessorT& acc) const +{ + const Index n = this->coordToOffset(xyz); + if (this->isChildMaskOn(n)) { + acc.insert(xyz, mNodes[n].getChild()); + return mNodes[n].getChild()->getValueLevelAndCache(xyz, acc); + } + return LEVEL; +} + + +template +inline bool +InternalNode::probeValue(const Coord& xyz, ValueType& value) const +{ + const Index n = this->coordToOffset(xyz); + if (this->isChildMaskOff(n)) { + value = mNodes[n].getValue(); + return this->isValueMaskOn(n); + } + return mNodes[n].getChild()->probeValue(xyz, value); +} + +template +template +inline bool +InternalNode::probeValueAndCache(const Coord& xyz, + ValueType& value, AccessorT& acc) const +{ + const Index n = this->coordToOffset(xyz); + if (this->isChildMaskOn(n)) { + acc.insert(xyz, mNodes[n].getChild()); + return mNodes[n].getChild()->probeValueAndCache(xyz, value, acc); + } + value = mNodes[n].getValue(); + return this->isValueMaskOn(n); +} + + +template +inline void +InternalNode::setValueOff(const Coord& xyz) +{ + const Index n = this->coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild && this->isValueMaskOn(n)) { + // If the voxel belongs to a constant tile that is active, + // a child subtree must be constructed. + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), /*active=*/true)); + } + if (hasChild) mNodes[n].getChild()->setValueOff(xyz); +} + + +template +inline void +InternalNode::setValueOn(const Coord& xyz) +{ + const Index n = this->coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild && !this->isValueMaskOn(n)) { + // If the voxel belongs to a constant tile that is inactive, + // a child subtree must be constructed. + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), /*active=*/false)); + } + if (hasChild) mNodes[n].getChild()->setValueOn(xyz); +} + + +template +inline void +InternalNode::setValueOff(const Coord& xyz, const ValueType& value) +{ + const Index n = InternalNode::coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild) { + const bool active = this->isValueMaskOn(n); + if (active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { + // If the voxel belongs to a tile that is either active or that + // has a constant value that is different from the one provided, + // a child subtree must be constructed. + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); + } + } + if (hasChild) mNodes[n].getChild()->setValueOff(xyz, value); +} + +template +template +inline void +InternalNode::setValueOffAndCache(const Coord& xyz, + const ValueType& value, AccessorT& acc) +{ + const Index n = InternalNode::coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild) { + const bool active = this->isValueMaskOn(n); + if (active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { + // If the voxel belongs to a tile that is either active or that + // has a constant value that is different from the one provided, + // a child subtree must be constructed. + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); + } + } + if (hasChild) { + ChildT* child = mNodes[n].getChild(); + acc.insert(xyz, child); + child->setValueOffAndCache(xyz, value, acc); + } +} + + +template +inline void +InternalNode::setValueOn(const Coord& xyz, const ValueType& value) +{ + const Index n = this->coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild) { + const bool active = this->isValueMaskOn(n); // tile's active state + if (!active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { + // If the voxel belongs to a tile that is either inactive or that + // has a constant value that is different from the one provided, + // a child subtree must be constructed. + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); + } + } + if (hasChild) mNodes[n].getChild()->setValueOn(xyz, value); +} + +template +template +inline void +InternalNode::setValueAndCache(const Coord& xyz, + const ValueType& value, AccessorT& acc) +{ + const Index n = this->coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild) { + const bool active = this->isValueMaskOn(n); + if (!active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { + // If the voxel belongs to a tile that is either inactive or that + // has a constant value that is different from the one provided, + // a child subtree must be constructed. + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); + } + } + if (hasChild) { + acc.insert(xyz, mNodes[n].getChild()); + mNodes[n].getChild()->setValueAndCache(xyz, value, acc); + } +} + + +template +inline void +InternalNode::setValueOnly(const Coord& xyz, const ValueType& value) +{ + const Index n = this->coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild && !math::isExactlyEqual(mNodes[n].getValue(), value)) { + // If the voxel has a tile value that is different from the one provided, + // a child subtree must be constructed. + const bool active = this->isValueMaskOn(n); + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); + } + if (hasChild) mNodes[n].getChild()->setValueOnly(xyz, value); +} + +template +template +inline void +InternalNode::setValueOnlyAndCache(const Coord& xyz, + const ValueType& value, AccessorT& acc) +{ + const Index n = this->coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild && !math::isExactlyEqual(mNodes[n].getValue(), value)) { + // If the voxel has a tile value that is different from the one provided, + // a child subtree must be constructed. + const bool active = this->isValueMaskOn(n); + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); + } + if (hasChild) { + acc.insert(xyz, mNodes[n].getChild()); + mNodes[n].getChild()->setValueOnlyAndCache(xyz, value, acc); + } +} + + +template +inline void +InternalNode::setActiveState(const Coord& xyz, bool on) +{ + const Index n = this->coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild) { + if (on != this->isValueMaskOn(n)) { + // If the voxel belongs to a tile with the wrong active state, + // then a child subtree must be constructed. + // 'on' is the voxel's new state, therefore '!on' is the tile's current state + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), !on)); + } + } + if (hasChild) mNodes[n].getChild()->setActiveState(xyz, on); +} + +template +template +inline void +InternalNode::setActiveStateAndCache(const Coord& xyz, bool on, AccessorT& acc) +{ + const Index n = this->coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild) { + if (on != this->isValueMaskOn(n)) { + // If the voxel belongs to a tile with the wrong active state, + // then a child subtree must be constructed. + // 'on' is the voxel's new state, therefore '!on' is the tile's current state + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), !on)); + } + } + if (hasChild) { + ChildT* child = mNodes[n].getChild(); + acc.insert(xyz, child); + child->setActiveStateAndCache(xyz, on, acc); + } +} + + +template +inline void +InternalNode::setValuesOn() +{ + mValueMask = !mChildMask; + for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { + mNodes[iter.pos()].getChild()->setValuesOn(); + } +} + + +template +template +inline void +InternalNode::modifyValue(const Coord& xyz, const ModifyOp& op) +{ + const Index n = InternalNode::coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild) { + // Need to create a child if the tile is inactive, + // in order to activate voxel (x, y, z). + const bool active = this->isValueMaskOn(n); + bool createChild = !active; + if (!createChild) { + // Need to create a child if applying the functor + // to the tile value produces a different value. + const ValueType& tileVal = mNodes[n].getValue(); + ValueType modifiedVal = tileVal; + op(modifiedVal); + createChild = !math::isExactlyEqual(tileVal, modifiedVal); + } + if (createChild) { + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); + } + } + if (hasChild) mNodes[n].getChild()->modifyValue(xyz, op); +} + +template +template +inline void +InternalNode::modifyValueAndCache(const Coord& xyz, const ModifyOp& op, + AccessorT& acc) +{ + const Index n = InternalNode::coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild) { + // Need to create a child if the tile is inactive, + // in order to activate voxel (x, y, z). + const bool active = this->isValueMaskOn(n); + bool createChild = !active; + if (!createChild) { + // Need to create a child if applying the functor + // to the tile value produces a different value. + const ValueType& tileVal = mNodes[n].getValue(); + ValueType modifiedVal = tileVal; + op(modifiedVal); + createChild = !math::isExactlyEqual(tileVal, modifiedVal); + } + if (createChild) { + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); + } + } + if (hasChild) { + ChildNodeType* child = mNodes[n].getChild(); + acc.insert(xyz, child); + child->modifyValueAndCache(xyz, op, acc); + } +} + + +template +template +inline void +InternalNode::modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) +{ + const Index n = InternalNode::coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild) { + const bool tileState = this->isValueMaskOn(n); + const ValueType& tileVal = mNodes[n].getValue(); + bool modifiedState = !tileState; + ValueType modifiedVal = tileVal; + op(modifiedVal, modifiedState); + // Need to create a child if applying the functor to the tile + // produces a different value or active state. + if (modifiedState != tileState || !math::isExactlyEqual(modifiedVal, tileVal)) { + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, tileVal, tileState)); + } + } + if (hasChild) mNodes[n].getChild()->modifyValueAndActiveState(xyz, op); +} + +template +template +inline void +InternalNode::modifyValueAndActiveStateAndCache( + const Coord& xyz, const ModifyOp& op, AccessorT& acc) +{ + const Index n = InternalNode::coordToOffset(xyz); + bool hasChild = this->isChildMaskOn(n); + if (!hasChild) { + const bool tileState = this->isValueMaskOn(n); + const ValueType& tileVal = mNodes[n].getValue(); + bool modifiedState = !tileState; + ValueType modifiedVal = tileVal; + op(modifiedVal, modifiedState); + // Need to create a child if applying the functor to the tile + // produces a different value or active state. + if (modifiedState != tileState || !math::isExactlyEqual(modifiedVal, tileVal)) { + hasChild = true; + this->setChildNode(n, new ChildNodeType(xyz, tileVal, tileState)); + } + } + if (hasChild) { + ChildNodeType* child = mNodes[n].getChild(); + acc.insert(xyz, child); + child->modifyValueAndActiveStateAndCache(xyz, op, acc); + } +} + + +//////////////////////////////////////// + + +template +inline void +InternalNode::fill(const CoordBBox& bbox, const ValueType& value, bool active) +{ + Coord xyz, tileMin, tileMax; + for (int x = bbox.min().x(); x <= bbox.max().x(); x = tileMax.x() + 1) { + xyz.setX(x); + for (int y = bbox.min().y(); y <= bbox.max().y(); y = tileMax.y() + 1) { + xyz.setY(y); + for (int z = bbox.min().z(); z <= bbox.max().z(); z = tileMax.z() + 1) { + xyz.setZ(z); + + // Get the bounds of the tile that contains voxel (x, y, z). + const Index n = this->coordToOffset(xyz); + tileMin = this->offsetToGlobalCoord(n); + tileMax = tileMin.offsetBy(ChildT::DIM - 1); + + if (xyz != tileMin || Coord::lessThan(bbox.max(), tileMax)) { + // If the box defined by (xyz, bbox.max()) doesn't completely enclose + // the tile to which xyz belongs, create a child node (or retrieve + // the existing one). + ChildT* child = NULL; + if (this->isChildMaskOff(n)) { + // Replace the tile with a newly-created child that is initialized + // with the tile's value and active state. + child = new ChildT(xyz, mNodes[n].getValue(), this->isValueMaskOn(n)); + this->setChildNode(n, child); + } else { + child = mNodes[n].getChild(); + } + + // Forward the fill request to the child. + if (child) { + child->fill(CoordBBox(xyz, Coord::minComponent(bbox.max(), tileMax)), + value, active); + } + + } else { + // If the box given by (xyz, bbox.max()) completely encloses + // the tile to which xyz belongs, create the tile (if it + // doesn't already exist) and give it the fill value. + this->makeChildNodeEmpty(n, value); + mValueMask.set(n, active); + } + } + } + } +} + + +//////////////////////////////////////// + + +template +template +inline void +InternalNode::copyToDense(const CoordBBox& bbox, DenseT& dense) const +{ + typedef typename DenseT::ValueType DenseValueType; + + const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); + const Coord& min = dense.bbox().min(); + for (Coord xyz = bbox.min(), max; xyz[0] <= bbox.max()[0]; xyz[0] = max[0] + 1) { + for (xyz[1] = bbox.min()[1]; xyz[1] <= bbox.max()[1]; xyz[1] = max[1] + 1) { + for (xyz[2] = bbox.min()[2]; xyz[2] <= bbox.max()[2]; xyz[2] = max[2] + 1) { + const Index n = this->coordToOffset(xyz); + // Get max coordinates of the child node that contains voxel xyz. + max = this->offsetToGlobalCoord(n).offsetBy(ChildT::DIM-1); + + // Get the bbox of the interection of bbox and the child node + CoordBBox sub(xyz, Coord::minComponent(bbox.max(), max)); + + if (this->isChildMaskOn(n)) {//is a child + mNodes[n].getChild()->copyToDense(sub, dense); + } else {//a tile value + const ValueType value = mNodes[n].getValue(); + sub.translate(-min); + DenseValueType* a0 = dense.data() + zStride*sub.min()[2]; + for (Int32 x=sub.min()[0], ex=sub.max()[0]+1; x +inline void +InternalNode::writeTopology(std::ostream& os, bool toHalf) const +{ + mChildMask.save(os); + mValueMask.save(os); + + { + // Copy all of this node's values into an array. + boost::shared_array values(new ValueType[NUM_VALUES]); + const ValueType zero = zeroVal(); + for (Index i = 0; i < NUM_VALUES; ++i) { + values[i] = (mChildMask.isOff(i) ? mNodes[i].getValue() : zero); + } + // Compress (optionally) and write out the contents of the array. + io::writeCompressedValues(os, values.get(), NUM_VALUES, mValueMask, mChildMask, toHalf); + } + // Write out the child nodes in order. + for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { + iter->writeTopology(os, toHalf); + } +} + + +template +inline void +InternalNode::readTopology(std::istream& is, bool fromHalf) +{ + mChildMask.load(is); + mValueMask.load(is); + + if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_INTERNALNODE_COMPRESSION) { + for (Index i = 0; i < NUM_VALUES; ++i) { + if (this->isChildMaskOn(i)) { + ChildNodeType* child = + new ChildNodeType(offsetToGlobalCoord(i), zeroVal()); + mNodes[i].setChild(child); + child->readTopology(is); + } else { + ValueType value; + is.read(reinterpret_cast(&value), sizeof(ValueType)); + mNodes[i].setValue(value); + } + } + } else { + const bool oldVersion = + (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION); + const Index numValues = (oldVersion ? mChildMask.countOff() : NUM_VALUES); + { + // Read in (and uncompress, if necessary) all of this node's values + // into a contiguous array. + boost::shared_array values(new ValueType[numValues]); + io::readCompressedValues(is, values.get(), numValues, mValueMask, fromHalf); + + // Copy values from the array into this node's table. + if (oldVersion) { + Index n = 0; + for (ValueAllIter iter = this->beginValueAll(); iter; ++iter) { + mNodes[iter.pos()].setValue(values[n++]); + } + assert(n == numValues); + } else { + for (ValueAllIter iter = this->beginValueAll(); iter; ++iter) { + mNodes[iter.pos()].setValue(values[iter.pos()]); + } + } + } + // Read in all child nodes and insert them into the table at their proper locations. + for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { + ChildNodeType* child = new ChildNodeType(iter.getCoord(), zeroVal()); + mNodes[iter.pos()].setChild(child); + child->readTopology(is, fromHalf); + } + } +} + + +//////////////////////////////////////// + + +template +inline const typename ChildT::ValueType& +InternalNode::getFirstValue() const +{ + return (this->isChildMaskOn(0) ? mNodes[0].getChild()->getFirstValue() : mNodes[0].getValue()); +} + + +template +inline const typename ChildT::ValueType& +InternalNode::getLastValue() const +{ + const Index n = NUM_VALUES - 1; + return (this->isChildMaskOn(n) ? mNodes[n].getChild()->getLastValue() : mNodes[n].getValue()); +} + + +//////////////////////////////////////// + + +template +inline void +InternalNode::signedFloodFill(const ValueType& background) +{ + this->signedFloodFill(background, math::negative(background)); +} + + +template +inline void +InternalNode::signedFloodFill(const ValueType& outsideValue, + const ValueType& insideValue) +{ + // First, flood fill all child nodes. + for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { + iter->signedFloodFill(outsideValue, insideValue); + } + const Index first = mChildMask.findFirstOn(); + if (first < NUM_VALUES) { + bool xInside = math::isNegative(mNodes[first].getChild()->getFirstValue()), + yInside = xInside, zInside = xInside; + for (Index x = 0; x != (1 << Log2Dim); ++x) { + const int x00 = x << (2 * Log2Dim); // offset for block(x, 0, 0) + if (isChildMaskOn(x00)) { + xInside = math::isNegative(mNodes[x00].getChild()->getLastValue()); + } + yInside = xInside; + for (Index y = 0; y != (1 << Log2Dim); ++y) { + const Index xy0 = x00 + (y << Log2Dim); // offset for block(x, y, 0) + if (isChildMaskOn(xy0)) { + yInside = math::isNegative(mNodes[xy0].getChild()->getLastValue()); + } + zInside = yInside; + for (Index z = 0; z != (1 << Log2Dim); ++z) { + const Index xyz = xy0 + z; // offset for block(x, y, z) + if (isChildMaskOn(xyz)) { + zInside = math::isNegative(mNodes[xyz].getChild()->getLastValue()); + } else { + mNodes[xyz].setValue(zInside ? insideValue : outsideValue); + } + } + } + } + } else {//no child nodes exist simply use the sign of the first tile value. + const ValueType v = math::isNegative(mNodes[0].getValue()) ? insideValue : outsideValue; + for (Index i = 0; i < NUM_VALUES; ++i) mNodes[i].setValue(v); + } +} + + +template +inline void +InternalNode::negate() +{ + for (Index i = 0; i < NUM_VALUES; ++i) { + if (this->isChildMaskOn(i)) { + mNodes[i].getChild()->negate(); + } else { + mNodes[i].setValue(math::negative(mNodes[i].getValue())); + } + } + +} + + +template +inline void +InternalNode::voxelizeActiveTiles() +{ + for (ValueOnIter iter = this->beginValueOn(); iter; ++iter) { + this->setChildNode(iter.pos(), new ChildNodeType(iter.getCoord(), iter.getValue(), true)); + } + for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) iter->voxelizeActiveTiles(); +} + + +//////////////////////////////////////// + + +template +template +inline void +InternalNode::merge(InternalNode& other, + const ValueType& background, const ValueType& otherBackground) +{ + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + + switch (Policy) { + + case MERGE_ACTIVE_STATES: + default: + { + for (ChildOnIter iter = other.beginChildOn(); iter; ++iter) { + const Index n = iter.pos(); + if (mChildMask.isOn(n)) { + // Merge this node's child with the other node's child. + mNodes[n].getChild()->template merge(*iter, + background, otherBackground); + } else if (mValueMask.isOff(n)) { + // Replace this node's inactive tile with the other node's child + // and replace the other node's child with a tile of undefined value + // (which is okay since the other tree is assumed to be cannibalized + // in the process of merging). + ChildNodeType* child = other.mNodes[n].getChild(); + other.mChildMask.setOff(n); + child->resetBackground(otherBackground, background); + this->setChildNode(n, child); + } + } + + // Copy active tile values. + for (ValueOnCIter iter = other.cbeginValueOn(); iter; ++iter) { + const Index n = iter.pos(); + if (mValueMask.isOff(n)) { + // Replace this node's child or inactive tile with the other node's active tile. + this->makeChildNodeEmpty(n, iter.getValue()); + mValueMask.setOn(n); + } + } + break; + } + + case MERGE_NODES: + { + for (ChildOnIter iter = other.beginChildOn(); iter; ++iter) { + const Index n = iter.pos(); + if (mChildMask.isOn(n)) { + // Merge this node's child with the other node's child. + mNodes[n].getChild()->template merge(*iter, background, otherBackground); + } else { + // Replace this node's tile (regardless of its active state) with + // the other node's child and replace the other node's child with + // a tile of undefined value (which is okay since the other tree + // is assumed to be cannibalized in the process of merging). + ChildNodeType* child = other.mNodes[n].getChild(); + other.mChildMask.setOff(n); + child->resetBackground(otherBackground, background); + this->setChildNode(n, child); + } + } + break; + } + + case MERGE_ACTIVE_STATES_AND_NODES: + { + // Transfer children from the other tree to this tree. + for (ChildOnIter iter = other.beginChildOn(); iter; ++iter) { + const Index n = iter.pos(); + if (mChildMask.isOn(n)) { + // Merge this node's child with the other node's child. + mNodes[n].getChild()->template merge(*iter, background, otherBackground); + } else { + // Replace this node's tile with the other node's child, leaving the other + // node with an inactive tile of undefined value (which is okay since + // the other tree is assumed to be cannibalized in the process of merging). + ChildNodeType* child = other.mNodes[n].getChild(); + other.mChildMask.setOff(n); + child->resetBackground(otherBackground, background); + if (mValueMask.isOn(n)) { + // Merge the child with this node's active tile. + child->template merge(mNodes[n].getValue(), /*on=*/true); + mValueMask.setOff(n); + } + mChildMask.setOn(n); + mNodes[n].setChild(child); + } + } + + // Merge active tiles into this tree. + for (ValueOnCIter iter = other.cbeginValueOn(); iter; ++iter) { + const Index n = iter.pos(); + if (mChildMask.isOn(n)) { + // Merge the other node's active tile into this node's child. + mNodes[n].getChild()->template merge(iter.getValue(), /*on=*/true); + } else if (mValueMask.isOff(n)) { + // Replace this node's inactive tile with the other node's active tile. + mNodes[n].setValue(iter.getValue()); + mValueMask.setOn(n); + } + } + break; + } + + } + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +template +template +inline void +InternalNode::merge(const ValueType& tileValue, bool tileActive) +{ + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + + if (Policy != MERGE_ACTIVE_STATES_AND_NODES) return; + + // For MERGE_ACTIVE_STATES_AND_NODES, inactive tiles in the other tree are ignored. + if (!tileActive) return; + + // Iterate over this node's children and inactive tiles. + for (ValueOffIter iter = this->beginValueOff(); iter; ++iter) { + const Index n = iter.pos(); + if (mChildMask.isOn(n)) { + // Merge the other node's active tile into this node's child. + mNodes[n].getChild()->template merge(tileValue, /*on=*/true); + } else { + // Replace this node's inactive tile with the other node's active tile. + iter.setValue(tileValue); + mValueMask.setOn(n); + } + } + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +//////////////////////////////////////// + + +template +template +inline void +InternalNode::topologyUnion(const InternalNode& other) +{ + typedef typename InternalNode::ChildOnCIter OtherChildIter; + typedef typename InternalNode::ValueOnCIter OtherValueIter; + + // Loop over other node's child nodes + for (OtherChildIter iter = other.cbeginChildOn(); iter; ++iter) { + const Index i = iter.pos(); + if (mChildMask.isOn(i)) {//this has a child node + mNodes[i].getChild()->topologyUnion(*iter); + } else {// this is a tile so replace it with a child branch with identical topology + ChildNodeType* child = new ChildNodeType(*iter, mNodes[i].getValue(), TopologyCopy()); + if (mValueMask.isOn(i)) { + mValueMask.isOff(i);//we're replacing the active tile with a child branch + child->setValuesOn();//activate all values since it was an active tile + } + mChildMask.setOn(i); + mNodes[i].setChild(child); + } + } + // Loop over other node's active tiles + for (OtherValueIter iter = other.cbeginValueOn(); iter; ++iter) { + const Index i = iter.pos(); + if (mChildMask.isOn(i)) { + mNodes[i].getChild()->setValuesOn(); + } else if (mValueMask.isOff(i)) { //inactive tile + mValueMask.setOn(i); + } + } +} + +template +template +inline void +InternalNode::topologyIntersection(const InternalNode& other, + const ValueType& background) +{ + // Loop over this node's child nodes + for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { + const Index i = iter.pos(); + if (other.mChildMask.isOn(i)) {//other also has a child node + iter->topologyIntersection(*(other.mNodes[i].getChild()), background); + } else if (other.mValueMask.isOff(i)) {//other is an inactive tile + delete mNodes[i].getChild();//convert child to an inactive tile + mNodes[i].setValue(background); + mChildMask.setOff(i); + mValueMask.setOff(i); + } + } + + // Loop over this node's active tiles + for (ValueOnCIter iter = this->cbeginValueOn(); iter; ++iter) { + const Index i = iter.pos(); + if (other.mChildMask.isOn(i)) {//other has a child node + ChildNodeType* child = new ChildNodeType(*(other.mNodes[i].getChild()), + *iter, TopologyCopy()); + this->setChildNode(i, child);//replace the active tile with a child branch + } else if (other.mValueMask.isOff(i)) {//other is an inactive tile + mValueMask.setOff(i);//convert active tile to an inactive tile + } + } +} + +template +template +inline void +InternalNode::topologyDifference(const InternalNode& other, + const ValueType& background) +{ + typedef typename InternalNode::ChildOnCIter OtherChildIter; + typedef typename InternalNode::ValueOnCIter OtherValueIter; + + // Loop over other node's child nodes + for (OtherChildIter iter = other.cbeginChildOn(); iter; ++iter) { + const Index i = iter.pos(); + if (mChildMask.isOn(i)) {//this has a child node + mNodes[i].getChild()->topologyDifference(*iter, background); + } else if (mValueMask.isOn(i)) {// this is an active tile + ChildNodeType* child = new ChildNodeType(iter.getCoord(), mNodes[i].getValue(), true); + child->topologyDifference(*iter, background); + this->setChildNode(i, child);//we're replacing the active tile with a child branch + } + } + + // Loop over other node's active tiles + for (OtherValueIter iter = other.cbeginValueOn(); iter; ++iter) { + const Index i = iter.pos(); + if (mChildMask.isOn(i)) {//this has a child node + delete mNodes[i].getChild();//convert child to an inactive tile + mNodes[i].setValue(background); + mChildMask.setOff(i); + mValueMask.setOff(i); + } else if (mValueMask.isOn(i)) {//this is an active tile + mValueMask.setOff(i);//convert active tile to an inactive tile + } + } +} + +//////////////////////////////////////// + + +template +template +inline void +InternalNode::combine(InternalNode& other, CombineOp& op) +{ + const ValueType zero = zeroVal(); + + CombineArgs args; + + for (Index i = 0; i < NUM_VALUES; ++i) { + if (this->isChildMaskOff(i) && other.isChildMaskOff(i)) { + // Both this node and the other node have constant values (tiles). + // Combine the two values and store the result as this node's new tile value. + op(args.setARef(mNodes[i].getValue()) + .setAIsActive(isValueMaskOn(i)) + .setBRef(other.mNodes[i].getValue()) + .setBIsActive(other.isValueMaskOn(i))); + mNodes[i].setValue(args.result()); + mValueMask.set(i, args.resultIsActive()); + } else if (this->isChildMaskOn(i) && other.isChildMaskOff(i)) { + // Combine this node's child with the other node's constant value. + ChildNodeType* child = mNodes[i].getChild(); + assert(child); + if (child) { + child->combine(other.mNodes[i].getValue(), other.isValueMaskOn(i), op); + } + } else if (this->isChildMaskOff(i) && other.isChildMaskOn(i)) { + // Combine this node's constant value with the other node's child. + ChildNodeType* child = other.mNodes[i].getChild(); + assert(child); + if (child) { + // Combine this node's constant value with the other node's child, + // but use a new functor in which the A and B values are swapped, + // since the constant value is the A value, not the B value. + SwappedCombineOp swappedOp(op); + child->combine(mNodes[i].getValue(), isValueMaskOn(i), swappedOp); + + // Steal the other node's child. + other.mChildMask.setOff(i); + other.mNodes[i].setValue(zero); + this->setChildNode(i, child); + } + + } else /*if (isChildMaskOn(i) && other.isChildMaskOn(i))*/ { + // Combine this node's child with the other node's child. + ChildNodeType + *child = mNodes[i].getChild(), + *otherChild = other.mNodes[i].getChild(); + assert(child); + assert(otherChild); + if (child && otherChild) { + child->combine(*otherChild, op); + } + } + } +} + + +template +template +inline void +InternalNode::combine(const ValueType& value, bool valueIsActive, CombineOp& op) +{ + CombineArgs args; + + for (Index i = 0; i < NUM_VALUES; ++i) { + if (this->isChildMaskOff(i)) { + // Combine this node's constant value with the given constant value. + op(args.setARef(mNodes[i].getValue()) + .setAIsActive(isValueMaskOn(i)) + .setBRef(value) + .setBIsActive(valueIsActive)); + mNodes[i].setValue(args.result()); + mValueMask.set(i, args.resultIsActive()); + } else /*if (isChildMaskOn(i))*/ { + // Combine this node's child with the given constant value. + ChildNodeType* child = mNodes[i].getChild(); + assert(child); + if (child) child->combine(value, valueIsActive, op); + } + } +} + + +//////////////////////////////////////// + + +template +template +inline void +InternalNode::combine2(const InternalNode& other0, const 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. + BOOST_STATIC_ASSERT(OtherNodeT::NUM_VALUES == NodeT::NUM_VALUES); + BOOST_STATIC_ASSERT(OtherNodeT::LEVEL == NodeT::LEVEL); + + typename NodeT::ValueType val; + typename OtherNodeT::ValueType otherVal; + + ChildAllIterT iter = self.beginChildAll(); + OtherChildAllIterT otherIter = other.beginChildAll(); + + for ( ; iter && otherIter; ++iter, ++otherIter) + { + const size_t skipBranch = static_cast(op(iter, otherIter)); + + typename ChildAllIterT::ChildNodeType* child = + (skipBranch & 1U) ? NULL : iter.probeChild(val); + typename OtherChildAllIterT::ChildNodeType* otherChild = + (skipBranch & 2U) ? NULL : otherIter.probeChild(otherVal); + + if (child != NULL && otherChild != NULL) { + child->visit2Node(*otherChild, op); + } else if (child != NULL) { + child->visit2(otherIter, op); + } else if (otherChild != NULL) { + otherChild->visit2(iter, op, /*otherIsLHS=*/true); + } + } +} + + +//////////////////////////////////////// + + +template +template +inline void +InternalNode::visit2(OtherChildAllIterType& otherIter, + VisitorOp& op, bool otherIsLHS) +{ + doVisit2( + *this, otherIter, op, otherIsLHS); +} + + +template +template +inline void +InternalNode::visit2(OtherChildAllIterType& otherIter, + VisitorOp& op, bool otherIsLHS) const +{ + doVisit2( + *this, otherIter, op, otherIsLHS); +} + + +template +template +inline void +InternalNode::doVisit2(NodeT& self, OtherChildAllIterT& otherIter, + VisitorOp& op, bool otherIsLHS) +{ + if (!otherIter) return; + + const size_t skipBitMask = (otherIsLHS ? 2U : 1U); + + typename NodeT::ValueType val; + for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { + const size_t skipBranch = static_cast( + otherIsLHS ? op(otherIter, iter) : op(iter, otherIter)); + + typename ChildAllIterT::ChildNodeType* child = + (skipBranch & skipBitMask) ? NULL : iter.probeChild(val); + + if (child != NULL) child->visit2(otherIter, op, otherIsLHS); + } +} + + +//////////////////////////////////////// + + +template +inline void +InternalNode::writeBuffers(std::ostream& os, bool toHalf) const +{ + for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { + iter->writeBuffers(os, toHalf); + } +} + + +template +inline void +InternalNode::readBuffers(std::istream& is, bool fromHalf) +{ + for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { + iter->readBuffers(is, fromHalf); + } +} + + +//////////////////////////////////////// + + +template +void +InternalNode::getNodeLog2Dims(std::vector& dims) +{ + dims.push_back(Log2Dim); + ChildNodeType::getNodeLog2Dims(dims); +} + + +template +inline void +InternalNode::offsetToLocalCoord(Index n, Coord &xyz) +{ + assert(n<(1<<3*Log2Dim)); + xyz.setX(n >> 2*Log2Dim); + n &= ((1<<2*Log2Dim)-1); + xyz.setY(n >> Log2Dim); + xyz.setZ(n & ((1< +inline Index +InternalNode::coordToOffset(const Coord& xyz) +{ + return (((xyz[0] & (DIM-1u)) >> ChildNodeType::TOTAL) << 2*Log2Dim) + + (((xyz[1] & (DIM-1u)) >> ChildNodeType::TOTAL) << Log2Dim) + + ((xyz[2] & (DIM-1u)) >> ChildNodeType::TOTAL); +} + + +template +inline Coord +InternalNode::offsetToGlobalCoord(Index n) const +{ + Coord local; + this->offsetToLocalCoord(n, local); + local <<= ChildT::TOTAL; + return local + this->origin(); +} + + +//////////////////////////////////////// + + +template +inline void +InternalNode::resetBackground(const ValueType& oldBackground, + const ValueType& newBackground) +{ + if (math::isExactlyEqual(oldBackground, newBackground)) return; + for (Index i = 0; i < NUM_VALUES; ++i) { + if (this->isChildMaskOn(i)) { + mNodes[i].getChild()->resetBackground(oldBackground, newBackground); + } else if (this->isValueMaskOff(i)) { + if (math::isApproxEqual(mNodes[i].getValue(), oldBackground)) { + mNodes[i].setValue(newBackground); + } else if (math::isApproxEqual(mNodes[i].getValue(), math::negative(oldBackground))) { + mNodes[i].setValue(math::negative(newBackground)); + } + } + } +} + + +template +template +inline bool +InternalNode::hasSameTopology( + const InternalNode* other) const +{ + if (Log2Dim != OtherLog2Dim || mChildMask != other->mChildMask || + mValueMask != other->mValueMask) return false; + for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { + if (!iter->hasSameTopology(other->mNodes[iter.pos()].getChild())) return false; + } + return true; +} + + +template +inline void +InternalNode::resetChildNode(Index i, ChildNodeType* child) +{ + assert(child); + if (this->isChildMaskOn(i)) { + delete mNodes[i].getChild(); + } else { + mChildMask.setOn(i); + mValueMask.setOff(i); + } + mNodes[i].setChild(child); +} + +template +inline void +InternalNode::setChildNode(Index i, ChildNodeType* child) +{ + assert(child); + assert(mChildMask.isOff(i)); + mChildMask.setOn(i); + mValueMask.setOff(i); + mNodes[i].setChild(child); +} + + +template +inline ChildT* +InternalNode::unsetChildNode(Index i, const ValueType& value) +{ + if (this->isChildMaskOff(i)) { + mNodes[i].setValue(value); + return NULL; + } + ChildNodeType* child = mNodes[i].getChild(); + mChildMask.setOff(i); + mNodes[i].setValue(value); + return child; +} + + +template +inline void +InternalNode::makeChildNodeEmpty(Index n, const ValueType& value) +{ + delete this->unsetChildNode(n, value); +} + +template +inline ChildT* +InternalNode::getChildNode(Index n) +{ + 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-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tree/Iterator.h b/openvdb_2_3_0_library/openvdb/tree/Iterator.h new file mode 100755 index 0000000..dec27e4 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tree/Iterator.h @@ -0,0 +1,290 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file Iterator.h +/// +/// @author Peter Cucka and Ken Museth + +#ifndef OPENVDB_TREE_ITERATOR_HAS_BEEN_INCLUDED +#define OPENVDB_TREE_ITERATOR_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tree { + +/// @brief Base class for iterators over internal and leaf nodes +/// +/// This class is typically not instantiated directly, since it doesn't provide methods +/// to dereference the iterator. Those methods (@vdblink::tree::SparseIteratorBase::operator*() +/// operator*()@endlink, @vdblink::tree::SparseIteratorBase::setValue() setValue()@endlink, etc.) +/// are implemented in the @vdblink::tree::SparseIteratorBase sparse@endlink and +/// @vdblink::tree::DenseIteratorBase dense@endlink iterator subclasses. +template +class IteratorBase +{ +public: + IteratorBase(): mParentNode(NULL) {} + IteratorBase(const MaskIterT& iter, NodeT* parent): + mParentNode(parent), mMaskIter(iter) {} + + void operator=(const IteratorBase& other) + { + mParentNode = other.mParentNode; + mMaskIter = other.mMaskIter; + } + + bool operator==(const IteratorBase& other) const + { + return (mParentNode == other.mParentNode) && (mMaskIter == other.mMaskIter); + } + bool operator!=(const IteratorBase& other) const + { + return !(*this == other); + } + + /// Return a pointer to the node (if any) over which this iterator is iterating. + NodeT* getParentNode() const { return mParentNode; } + /// @brief Return a reference to the node over which this iterator is iterating. + /// @throw ValueError if there is no parent node. + NodeT& parent() const + { + if (!mParentNode) OPENVDB_THROW(ValueError, "iterator references a null node"); + return *mParentNode; + } + + /// Return this iterator's position as an index into the parent node's table. + Index offset() const { return mMaskIter.offset(); } + + /// Identical to offset + Index pos() const { return mMaskIter.offset(); } + + /// Return @c true if this iterator is not yet exhausted. + bool test() const { return mMaskIter.test(); } + /// Return @c true if this iterator is not yet exhausted. + operator bool() const { return this->test(); } + + /// Advance to the next item in the parent node's table. + bool next() { return mMaskIter.next(); } + /// Advance to the next item in the parent node's table. + void increment() { mMaskIter.increment(); } + /// Advance to the next item in the parent node's table. + IteratorBase& operator++() { this->increment(); return *this; } + /// Advance @a n items in the parent node's table. + void increment(Index n) { mMaskIter.increment(n); } + + /// @brief Return @c true if this iterator is pointing to an active value. + /// Return @c false if it is pointing to either an inactive value or a child node. + bool isValueOn() const { return parent().isValueMaskOn(this->pos()); } + /// @brief If this iterator is pointing to a value, set the value's active state. + /// Otherwise, do nothing. + void setValueOn(bool on = true) const { parent().setValueMask(this->pos(), on); } + /// @brief If this iterator is pointing to a value, mark the value as inactive. + /// @details If this iterator is pointing to a child node, then the current item + /// in the parent node's table is required to be inactive. In that case, + /// this method has no effect. + void setValueOff() const { parent().mValueMask.setOff(this->pos()); } + + /// Return the coordinates of the item to which this iterator is pointing. + Coord getCoord() const { return parent().offsetToGlobalCoord(this->pos()); } + /// Return in @a xyz the coordinates of the item to which this iterator is pointing. + void getCoord(Coord& xyz) const { xyz = this->getCoord(); } + +private: + /// @note This parent node pointer is mutable, because setValueOn() and + /// setValueOff(), though const, need to call non-const methods on the parent. + /// There is a distinction between a const iterator (e.g., const ValueOnIter), + /// which is an iterator that can't be incremented, and an iterator over + /// a const node (e.g., ValueOnCIter), which might be const or non-const itself + /// but can't call non-const methods like setValue() on the node. + mutable NodeT* mParentNode; + MaskIterT mMaskIter; +}; // class IteratorBase + + +//////////////////////////////////////// + + +/// @brief Base class for sparse iterators over internal and leaf nodes +template< + typename MaskIterT, // mask iterator type (OnIterator, OffIterator, etc.) + typename IterT, // SparseIteratorBase subclass (the "Curiously Recurring Template Pattern") + typename NodeT, // type of node over which to iterate + typename ItemT> // type of value to which this iterator points +struct SparseIteratorBase: public IteratorBase +{ + typedef NodeT NodeType; + typedef ItemT ValueType; + typedef typename boost::remove_const::type NonConstNodeType; + typedef typename boost::remove_const::type NonConstValueType; + static const bool IsSparseIterator = true, IsDenseIterator = false; + + SparseIteratorBase() {} + SparseIteratorBase(const MaskIterT& iter, NodeT* parent): + IteratorBase(iter, parent) {} + + /// @brief Return the item at the given index in the parent node's table. + /// @note All subclasses must implement this accessor. + ItemT& getItem(Index) const; + /// @brief Set the value of the item at the given index in the parent node's table. + /// @note All non-const iterator subclasses must implement this accessor. + void setItem(Index, const ItemT&) const; + + /// Return a reference to the item to which this iterator is pointing. + ItemT& operator*() const { return this->getValue(); } + /// Return a pointer to the item to which this iterator is pointing. + ItemT* operator->() const { return &(this->operator*()); } + + /// Return the item to which this iterator is pointing. + ItemT& getValue() const + { + return static_cast(this)->getItem(this->pos()); // static polymorphism + } + /// @brief Set the value of the item to which this iterator is pointing. + /// (Not valid for const iterators.) + void setValue(const ItemT& value) const + { + BOOST_STATIC_ASSERT(!boost::is_const::value); + static_cast(this)->setItem(this->pos(), value); // static polymorphism + } + /// @brief Apply a functor to the item to which this iterator is pointing. + /// (Not valid for const iterators.) + /// @param op a functor of the form void op(ValueType&) const that modifies + /// its argument in place + /// @see Tree::modifyValue() + template + void modifyValue(const ModifyOp& op) const + { + BOOST_STATIC_ASSERT(!boost::is_const::value); + static_cast(this)->modifyItem(this->pos(), op); // static polymorphism + } +}; // class SparseIteratorBase + + +//////////////////////////////////////// + + +/// @brief Base class for dense iterators over internal and leaf nodes +/// @note Dense iterators have no @c %operator*() or @c %operator->(), +/// because their return type would have to vary depending on whether +/// the iterator is pointing to a value or a child node. +template< + typename MaskIterT, // mask iterator type (typically a DenseIterator) + typename IterT, // DenseIteratorBase subclass (the "Curiously Recurring Template Pattern") + typename NodeT, // type of node over which to iterate + typename SetItemT, // type of set value (ChildNodeType, for non-leaf nodes) + typename UnsetItemT> // type of unset value (ValueType, usually) +struct DenseIteratorBase: public IteratorBase +{ + typedef NodeT NodeType; + typedef UnsetItemT ValueType; + typedef SetItemT ChildNodeType; + typedef typename boost::remove_const::type NonConstNodeType; + typedef typename boost::remove_const::type NonConstValueType; + typedef typename boost::remove_const::type NonConstChildNodeType; + static const bool IsSparseIterator = false, IsDenseIterator = true; + + DenseIteratorBase() {} + DenseIteratorBase(const MaskIterT& iter, NodeT* parent): + IteratorBase(iter, parent) {} + + /// @brief Return @c true if the item at the given index in the parent node's table + /// is a set value and return either the set value in @a child or the unset value + /// in @a value. + /// @note All subclasses must implement this accessor. + bool getItem(Index, SetItemT*& child, NonConstValueType& value) const; + /// @brief Set the value of the item at the given index in the parent node's table. + /// @note All non-const iterator subclasses must implement this accessor. + void setItem(Index, SetItemT*) const; + /// @brief "Unset" the value of the item at the given index in the parent node's table. + /// @note All non-const iterator subclasses must implement this accessor. + void unsetItem(Index, const UnsetItemT&) const; + + /// Return @c true if this iterator is pointing to a child node. + bool isChildNode() const { return this->parent().isChildMaskOn(this->pos()); } + + /// @brief If this iterator is pointing to a child node, return a pointer to the node. + /// Otherwise, return NULL and, in @a value, the value to which this iterator is pointing. + SetItemT* probeChild(NonConstValueType& value) const + { + SetItemT* child = NULL; + static_cast(this)->getItem(this->pos(), child, value); // static polymorphism + return child; + } + /// @brief If this iterator is pointing to a child node, return @c true and return + /// a pointer to the child node in @a child. Otherwise, return @c false and return + /// the value to which this iterator is pointing in @a value. + bool probeChild(SetItemT*& child, NonConstValueType& value) const + { + child = probeChild(value); + return (child != NULL); + } + + /// @brief Return @c true if this iterator is pointing to a value and return + /// the value in @a value. Otherwise, return @c false. + bool probeValue(NonConstValueType& value) const + { + SetItemT* child = NULL; + const bool isChild = static_cast(this)-> // static polymorphism + getItem(this->pos(), child, value); + return !isChild; + } + + /// @brief Replace with the given child node the item in the parent node's table + /// to which this iterator is pointing. + void setChild(SetItemT* child) const + { + static_cast(this)->setItem(this->pos(), child); // static polymorphism + } + + /// @brief Replace with the given value the item in the parent node's table + /// to which this iterator is pointing. + void setValue(const UnsetItemT& value) const + { + static_cast(this)->unsetItem(this->pos(), value); // static polymorphism + } +}; // struct DenseIteratorBase + +} // namespace tree +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TREE_ITERATOR_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tree/LeafManager.h b/openvdb_2_3_0_library/openvdb/tree/LeafManager.h new file mode 100755 index 0000000..73b9c05 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tree/LeafManager.h @@ -0,0 +1,610 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file LeafManager.h +/// +/// A LeafManager manages a linear array of pointers to a given tree's +/// leaf nodes, as well as optional auxiliary buffers (one or more per leaf) +/// that can be swapped with the leaf nodes' voxel data buffers. +/// The leaf array is useful for multithreaded computations over +/// leaf voxels in a tree with static topology but varying voxel values. +/// The auxiliary buffers are convenient for temporal integration. +/// Efficient methods are provided for multithreaded swapping and synching +/// (i.e., copying the contents) of these buffers. + +#ifndef OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED +#define OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include "TreeIterator.h" // for CopyConstness + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tree { + +namespace leafmgr { + +//@{ +/// Useful traits for Tree types +template struct TreeTraits { + static const bool IsConstTree = false; + typedef typename TreeT::LeafIter LeafIterType; +}; +template struct TreeTraits { + static const bool IsConstTree = true; + typedef typename TreeT::LeafCIter LeafIterType; +}; +//@} + +} // namespace leafmgr + + +/// This helper class implements LeafManager methods that need to be +/// specialized for const vs. non-const trees. +template +struct LeafManagerImpl +{ + typedef typename ManagerT::RangeType RangeT; + typedef typename ManagerT::LeafType LeafT; + typedef typename ManagerT::BufferType BufT; + + static inline void doSwapLeafBuffer(const RangeT& r, size_t auxBufferIdx, + LeafT** leafs, BufT* bufs, size_t bufsPerLeaf) + { + for (size_t n = r.begin(), m = r.end(), N = bufsPerLeaf; n != m; ++n) { + leafs[n]->swap(bufs[n * N + auxBufferIdx]); + } + } +}; + + +//////////////////////////////////////// + + +/// @brief This class manages a linear array of pointers to a given tree's +/// leaf nodes, as well as optional auxiliary buffers (one or more per leaf) +/// that can be swapped with the leaf nodes' voxel data buffers. +/// @details The leaf array is useful for multithreaded computations over +/// leaf voxels in a tree with static topology but varying voxel values. +/// The auxiliary buffers are convenient for temporal integration. +/// Efficient methods are provided for multithreaded swapping and sync'ing +/// (i.e., copying the contents) of these buffers. +/// +/// @note Buffer index 0 denotes a leaf node's internal voxel data buffer. +/// Any auxiliary buffers are indexed starting from one. +template +class LeafManager +{ +public: + typedef TreeT TreeType; + typedef typename TreeType::LeafNodeType NonConstLeafType; + typedef typename CopyConstness::Type LeafType; + typedef typename leafmgr::TreeTraits::LeafIterType LeafIterType; + typedef typename LeafType::Buffer NonConstBufferType; + typedef typename CopyConstness::Type BufferType; + typedef tbb::blocked_range RangeType;//leaf index range + + static const bool IsConstTree = leafmgr::TreeTraits::IsConstTree; + + class LeafRange + { + public: + class Iterator + { + public: + Iterator(const LeafRange& range, size_t pos): mRange(range), mPos(pos) + { + assert(this->isValid()); + } + Iterator& operator=(const Iterator& other) + { + mRange = other.mRange; mPos = other.mPos; return *this; + } + /// Advance to the next leaf node. + Iterator& operator++() { ++mPos; return *this; } + /// Return a reference to the leaf node to which this iterator is pointing. + LeafType& operator*() const { return mRange.mLeafManager.leaf(mPos); } + /// Return a pointer to the leaf node to which this iterator is pointing. + LeafType* operator->() const { return &(this->operator*()); } + /// @brief Return the nth buffer for the leaf node to which this iterator is pointing, + /// where n = @a bufferIdx and n = 0 corresponds to the leaf node's own buffer. + BufferType& buffer(size_t bufferIdx) + { + return mRange.mLeafManager.getBuffer(mPos, bufferIdx); + } + /// Return the index into the leaf array of the current leaf node. + size_t pos() const { return mPos; } + bool isValid() const { return mPos>=mRange.mBegin && mPos<=mRange.mEnd; } + /// Return @c true if this iterator is not yet exhausted. + bool test() const { return mPos < mRange.mEnd; } + /// Return @c true if this iterator is not yet exhausted. + operator bool() const { return this->test(); } + /// Return @c true if this iterator is exhausted. + bool empty() const { return !this->test(); } + bool operator!=(const Iterator& other) const + { + return (mPos != other.mPos) || (&mRange != &other.mRange); + } + bool operator==(const Iterator& other) const { return !(*this != other); } + const LeafRange& leafRange() const { return mRange; } + + private: + const LeafRange& mRange; + size_t mPos; + };// end Iterator + + LeafRange(size_t begin, size_t end, const LeafManager& leafManager, size_t grainSize=1): + mEnd(end), mBegin(begin), mGrainSize(grainSize), mLeafManager(leafManager) {} + + Iterator begin() const {return Iterator(*this, mBegin);} + + Iterator end() const {return Iterator(*this, mEnd);} + + size_t size() const { return mEnd - mBegin; } + + size_t grainsize() const { return mGrainSize; } + + const LeafManager& leafManager() const { return mLeafManager; } + + bool empty() const {return !(mBegin < mEnd);} + + bool is_divisible() const {return mGrainSize < this->size();} + + LeafRange(LeafRange& r, tbb::split): + mEnd(r.mEnd), mBegin(doSplit(r)), mGrainSize(r.mGrainSize), + mLeafManager(r.mLeafManager) {} + + private: + size_t mEnd, mBegin, mGrainSize; + const LeafManager& mLeafManager; + + static size_t doSplit(LeafRange& r) + { + assert(r.is_divisible()); + size_t middle = r.mBegin + (r.mEnd - r.mBegin) / 2u; + r.mEnd = middle; + return middle; + } + };// end of LeafRange + + /// @brief Constructor from a tree reference and an auxiliary buffer count + /// (default is no auxiliary buffers) + LeafManager(TreeType& tree, size_t auxBuffersPerLeaf=0, bool serial=false): + mTree(&tree), + mLeafCount(0), + mAuxBufferCount(0), + mAuxBuffersPerLeaf(auxBuffersPerLeaf), + mLeafs(NULL), + mAuxBuffers(NULL), + mTask(0), + mIsMaster(true) + { + this->rebuild(serial); + } + + /// Shallow copy constructor called by tbb::parallel_for() threads + /// + /// @note This should never get called directly + LeafManager(const LeafManager& other): + mTree(other.mTree), + mLeafCount(other.mLeafCount), + mAuxBufferCount(other.mAuxBufferCount), + mAuxBuffersPerLeaf(other.mAuxBuffersPerLeaf), + mLeafs(other.mLeafs), + mAuxBuffers(other.mAuxBuffers), + mTask(other.mTask), + mIsMaster(false) + { + } + + virtual ~LeafManager() + { + if (mIsMaster) { + delete [] mLeafs; + delete [] mAuxBuffers; + } + } + + /// @brief (Re)initialize by resizing (if necessary) and repopulating the leaf array + /// and by deleting existing auxiliary buffers and allocating new ones. + /// @details Call this method if the tree's topology, and therefore the number + /// of leaf nodes, changes. New auxiliary buffers are initialized with copies + /// of corresponding leaf node buffers. + void rebuild(bool serial=false) + { + this->initLeafArray(); + this->initAuxBuffers(serial); + } + //@{ + /// Repopulate the leaf array and delete and reallocate auxiliary buffers. + void rebuild(size_t auxBuffersPerLeaf, bool serial=false) + { + mAuxBuffersPerLeaf = auxBuffersPerLeaf; + this->rebuild(serial); + } + void rebuild(TreeType& tree, bool serial=false) + { + mTree = &tree; + this->rebuild(serial); + } + void rebuild(TreeType& tree, size_t auxBuffersPerLeaf, bool serial=false) + { + mTree = &tree; + mAuxBuffersPerLeaf = auxBuffersPerLeaf; + this->rebuild(serial); + } + //@} + /// @brief Change the number of auxiliary buffers. + /// @details If auxBuffersPerLeaf is 0, all existing auxiliary buffers are deleted. + /// New auxiliary buffers are initialized with copies of corresponding leaf node buffers. + /// This method does not rebuild the leaf array. + void rebuildAuxBuffers(size_t auxBuffersPerLeaf, bool serial=false) + { + mAuxBuffersPerLeaf = auxBuffersPerLeaf; + this->initAuxBuffers(serial); + } + /// @brief Remove the auxiliary buffers, but don't rebuild the leaf array. + void removeAuxBuffers() { this->rebuildAuxBuffers(0); } + + /// @brief Remove the auxiliary buffers and rebuild the leaf array. + void rebuildLeafArray() + { + this->removeAuxBuffers(); + this->initLeafArray(); + } + + /// Return the total number of allocated auxiliary buffers. + size_t auxBufferCount() const { return mAuxBufferCount; } + /// Return the number of auxiliary buffers per leaf node. + size_t auxBuffersPerLeaf() const { return mAuxBuffersPerLeaf; } + + /// Return the number of leaf nodes. + size_t leafCount() const { return mLeafCount; } + + /// Return the tree associated with this manager. + TreeType& tree() { return *mTree; } + + /// Return @c true if the tree associated with this manager is immutable. + bool isConstTree() const { return this->IsConstTree; } + + /// @brief Return a pointer to the leaf node at index @a leafIdx in the array. + /// @note For performance reasons no range check is performed (other than an assertion)! + LeafType& leaf(size_t leafIdx) const { assert(leafIdx 0), + /// but it is not safe to modify the leaf buffer (@a bufferIdx = 0). + BufferType& getBuffer(size_t leafIdx, size_t bufferIdx) const + { + assert(leafIdx < mLeafCount); + assert(bufferIdx == 0 || bufferIdx - 1 < mAuxBuffersPerLeaf); + return bufferIdx == 0 ? mLeafs[leafIdx]->buffer() + : mAuxBuffers[leafIdx * mAuxBuffersPerLeaf + bufferIdx - 1]; + } + + /// @brief Return a @c tbb::blocked_range of leaf array indices. + /// + /// @note Consider using leafRange() instead, which provides access methods + /// to leaf nodes and buffers. + RangeType getRange(size_t grainsize = 1) const { return RangeType(0, mLeafCount, grainsize); } + + /// Return a TBB-compatible LeafRange. + LeafRange leafRange(size_t grainsize = 1) const + { + return LeafRange(0, mLeafCount, *this, grainsize); + } + + /// @brief Swap each leaf node's buffer with the nth corresponding auxiliary buffer, + /// where n = @a bufferIdx. + /// @return @c true if the swap was successful + /// @param bufferIdx index of the buffer that will be swapped with + /// the corresponding leaf node buffer + /// @param serial if false, swap buffers in parallel using multiple threads. + /// @note Recall that the indexing of auxiliary buffers is 1-based, since + /// buffer index 0 denotes the leaf node buffer. So buffer index 1 denotes + /// the first auxiliary buffer. + bool swapLeafBuffer(size_t bufferIdx, bool serial = false) + { + if (bufferIdx == 0 || bufferIdx > mAuxBuffersPerLeaf || this->isConstTree()) return false; + mTask = boost::bind(&LeafManager::doSwapLeafBuffer, _1, _2, bufferIdx - 1); + this->cook(serial ? 0 : 512); + return true;//success + } + /// @brief Swap any two buffers for each leaf node. + /// @note Recall that the indexing of auxiliary buffers is 1-based, since + /// buffer index 0 denotes the leaf node buffer. So buffer index 1 denotes + /// the first auxiliary buffer. + bool swapBuffer(size_t bufferIdx1, size_t bufferIdx2, bool serial = false) + { + const size_t b1 = std::min(bufferIdx1, bufferIdx2); + const size_t b2 = std::max(bufferIdx1, bufferIdx2); + if (b1 == b2 || b2 > mAuxBuffersPerLeaf) return false; + if (b1 == 0) { + if (this->isConstTree()) return false; + mTask = boost::bind(&LeafManager::doSwapLeafBuffer, _1, _2, b2-1); + } else { + mTask = boost::bind(&LeafManager::doSwapAuxBuffer, _1, _2, b1-1, b2-1); + } + this->cook(serial ? 0 : 512); + return true;//success + } + + /// @brief Sync up the specified auxiliary buffer with the corresponding leaf node buffer. + /// @return @c true if the sync was successful + /// @param bufferIdx index of the buffer that will contain a + /// copy of the corresponding leaf node buffer + /// @param serial if false, sync buffers in parallel using multiple threads. + /// @note Recall that the indexing of auxiliary buffers is 1-based, since + /// buffer index 0 denotes the leaf node buffer. So buffer index 1 denotes + /// the first auxiliary buffer. + bool syncAuxBuffer(size_t bufferIdx, bool serial = false) + { + if (bufferIdx == 0 || bufferIdx > mAuxBuffersPerLeaf) return false; + mTask = boost::bind(&LeafManager::doSyncAuxBuffer, _1, _2, bufferIdx - 1); + this->cook(serial ? 0 : 64); + return true;//success + } + + /// @brief Sync up all auxiliary buffers with their corresponding leaf node buffers. + /// @return true if the sync was successful + /// @param serial if false, sync buffers in parallel using multiple threads. + bool syncAllBuffers(bool serial = false) + { + switch (mAuxBuffersPerLeaf) { + case 0: return false;//nothing to do + case 1: mTask = boost::bind(&LeafManager::doSyncAllBuffers1, _1, _2); break; + case 2: mTask = boost::bind(&LeafManager::doSyncAllBuffers2, _1, _2); break; + default: mTask = boost::bind(&LeafManager::doSyncAllBuffersN, _1, _2); break; + } + this->cook(serial ? 0 : 64); + return true;//success + } + + /// @brief Threaded method that applies a user-supplied functor + /// to each leaf node in the LeafManager + /// + /// @param op user-supplied functor, see examples for interface details. + /// @param threaded optional toggle to disable threading, on by default. + /// @param grainSize optional parameter to specify the grainsize + /// for threading, one by default. + /// + /// @warning The functor object is deep-copied to create TBB tasks. + /// + /// @par Example: + /// @code + /// // Functor to offset a tree's voxel values with values from another tree. + /// template + /// struct OffsetOp + /// { + /// typedef tree::ValueAccessor Accessor; + /// + /// OffsetOp(const TreeType& tree): mRhsTreeAcc(tree) {} + /// + /// template + /// void operator()(LeafNodeType &lhsLeaf, size_t) const + /// { + /// const LeafNodeType * rhsLeaf = mRhsTreeAcc.probeConstLeaf(lhsLeaf.origin()); + /// if (rhsLeaf) { + /// typename LeafNodeType::ValueOnIter iter = lhsLeaf.beginValueOn(); + /// for (; iter; ++iter) { + /// iter.setValue(iter.getValue() + rhsLeaf->getValue(iter.pos())); + /// } + /// } + /// } + /// private: + /// Accessor mRhsTreeAcc; + /// }; + /// + /// // usage: + /// tree::LeafManager leafNodes(lhsTree); + /// leafNodes.foreach(OffsetOp(rhsTree)); + /// + /// // A functor that performs a min operation between different auxiliary buffers. + /// template + /// struct MinOp + /// { + /// typedef typename LeafManagerType::BufferType BufferType; + /// + /// MinOp(LeafManagerType& leafNodes): mLeafs(leafNodes) {} + /// + /// template + /// void operator()(LeafNodeType &leaf, size_t leafIndex) const + /// { + /// // get the first buffer + /// BufferType& buffer = mLeafs.getBuffer(leafIndex, 1); + /// + /// // min ... + /// } + /// private: + /// LeafManagerType& mLeafs; + /// }; + /// @endcode + template + void foreach(const LeafOp& op, bool threaded = true, size_t grainSize=1) + { + LeafTransformer transform(op); + transform.run(this->leafRange(grainSize), threaded); + } + + //////////////////////////////////////////////////////////////////////////////////// + // All methods below are for internal use only and should never be called directly + + /// Used internally by tbb::parallel_for() - never call it directly! + void operator()(const RangeType& r) const + { + if (mTask) mTask(const_cast(this), r); + else OPENVDB_THROW(ValueError, "task is undefined"); + } + + + +private: + void initLeafArray() + { + const size_t leafCount = mTree->leafCount(); + if (leafCount != mLeafCount) { + delete [] mLeafs; + mLeafs = (leafCount == 0) ? NULL : new LeafType*[leafCount]; + mLeafCount = leafCount; + } + LeafIterType iter = mTree->beginLeaf(); + for (size_t n = 0; n != leafCount; ++n, ++iter) mLeafs[n] = iter.getLeaf(); + } + + void initAuxBuffers(bool serial) + { + const size_t auxBufferCount = mLeafCount * mAuxBuffersPerLeaf; + if (auxBufferCount != mAuxBufferCount) { + delete [] mAuxBuffers; + mAuxBuffers = (auxBufferCount == 0) ? NULL : new NonConstBufferType[auxBufferCount]; + mAuxBufferCount = auxBufferCount; + } + this->syncAllBuffers(serial); + } + + void cook(size_t grainsize) + { + if (grainsize>0) { + tbb::parallel_for(this->getRange(grainsize), *this); + } else { + (*this)(this->getRange()); + } + } + + void doSwapLeafBuffer(const RangeType& r, size_t auxBufferIdx) + { + LeafManagerImpl::doSwapLeafBuffer( + r, auxBufferIdx, mLeafs, mAuxBuffers, mAuxBuffersPerLeaf); + } + + void doSwapAuxBuffer(const RangeType& r, size_t auxBufferIdx1, size_t auxBufferIdx2) + { + for (size_t N = mAuxBuffersPerLeaf, n = N*r.begin(), m = N*r.end(); n != m; n+=N) { + mAuxBuffers[n + auxBufferIdx1].swap(mAuxBuffers[n + auxBufferIdx2]); + } + } + + void doSyncAuxBuffer(const RangeType& r, size_t auxBufferIdx) + { + for (size_t n = r.begin(), m = r.end(), N = mAuxBuffersPerLeaf; n != m; ++n) { + mAuxBuffers[n*N + auxBufferIdx] = mLeafs[n]->buffer(); + } + } + + void doSyncAllBuffers1(const RangeType& r) + { + for (size_t n = r.begin(), m = r.end(); n != m; ++n) { + mAuxBuffers[n] = mLeafs[n]->buffer(); + } + } + + void doSyncAllBuffers2(const RangeType& r) + { + for (size_t n = r.begin(), m = r.end(); n != m; ++n) { + const BufferType& leafBuffer = mLeafs[n]->buffer(); + mAuxBuffers[2*n ] = leafBuffer; + mAuxBuffers[2*n+1] = leafBuffer; + } + } + + void doSyncAllBuffersN(const RangeType& r) + { + for (size_t n = r.begin(), m = r.end(), N = mAuxBuffersPerLeaf; n != m; ++n) { + const BufferType& leafBuffer = mLeafs[n]->buffer(); + for (size_t i=n*N, j=i+N; i!=j; ++i) mAuxBuffers[i] = leafBuffer; + } + } + + /// @brief Private member class that applies a user-defined + /// functor to all the leaf nodes. + template + struct LeafTransformer + { + LeafTransformer(const LeafOp& leafOp) : mLeafOp(leafOp) {} + void run(const LeafRange& range, bool threaded = true) + { + threaded ? tbb::parallel_for(range, *this) : (*this)(range); + } + void operator()(const LeafRange& range) const + { + for (typename LeafRange::Iterator it = range.begin(); it; ++it) mLeafOp(*it, it.pos()); + } + const LeafOp mLeafOp; + }; + + typedef typename boost::function FuncType; + + TreeType* mTree; + size_t mLeafCount, mAuxBufferCount, mAuxBuffersPerLeaf; + LeafType** mLeafs;//array of LeafNode pointers + NonConstBufferType* mAuxBuffers;//array of auxiliary buffers + FuncType mTask; + const bool mIsMaster; +};//end of LeafManager class + + +// Partial specializations of LeafManager methods for const trees +template +struct LeafManagerImpl > +{ + typedef LeafManager ManagerT; + typedef typename ManagerT::RangeType RangeT; + typedef typename ManagerT::LeafType LeafT; + typedef typename ManagerT::BufferType BufT; + + static inline void doSwapLeafBuffer(const RangeT&, size_t /*auxBufferIdx*/, + LeafT**, BufT*, size_t /*bufsPerLeaf*/) + { + // Buffers can't be swapped into const trees. + } +}; + +} // namespace tree +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tree/LeafNode.h b/openvdb_2_3_0_library/openvdb/tree/LeafNode.h new file mode 100755 index 0000000..cd2e1b6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tree/LeafNode.h @@ -0,0 +1,1792 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_TREE_LEAFNODE_HAS_BEEN_INCLUDED +#define OPENVDB_TREE_LEAFNODE_HAS_BEEN_INCLUDED + +#include +#include // for std::swap +#include // for std::memcpy() +#include +#include +#include +#include +#include +#include +#include +#include // for io::readData(), etc. +#include "Iterator.h" +#include "Util.h" + + +class TestLeaf; +template class TestLeafIO; + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tree { + +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: + typedef T ValueType; + typedef LeafNode LeafNodeType; + typedef boost::shared_ptr Ptr; + typedef util::NodeMask NodeMaskType; + + static const Index + LOG2DIM = Log2Dim, // needed by parent nodes + TOTAL = Log2Dim, // needed by parent nodes + DIM = 1 << TOTAL, // dimension along one coordinate direction + NUM_VALUES = 1 << 3 * Log2Dim, + NUM_VOXELS = NUM_VALUES, // total number of voxels represented by this node + SIZE = NUM_VALUES, + LEVEL = 0; // level 0 = leaf + + /// @brief ValueConverter::Type is the type of a LeafNode having the same + /// dimensions as this node but a different value type, T. + template + struct ValueConverter { + typedef LeafNode Type; + }; + + /// @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; + }; + + + /// @brief Stores the actual values in the LeafNode. Its dimension + /// it fixed to 2^(3*Log2Dim) + class Buffer + { + public: + /// @brief Empty default constructor + Buffer(): mData(new ValueType[SIZE]) {} + /// @brief Constructs a buffer populated with the specified value + Buffer(const ValueType& val) : mData(new ValueType[SIZE]) { this->fill(val); } + /// @brief Copy constructor + Buffer(const Buffer& other) : mData(new ValueType[SIZE]) { *this = other; } + /// @brief Destructor + ~Buffer() { delete [] mData; } + /// @brief Populates the buffer with a constant value + void fill(const ValueType& val) + { + ValueType* target = mData; + Index n = SIZE; + while (n--) *target++ = val; + } + /// Return a const reference to the i'th element of the Buffer + const ValueType& getValue(Index i) const { assert(i < SIZE); return mData[i]; } + /// Return a const reference to the i'th element of the Buffer + const ValueType& operator[](Index i) const { return this->getValue(i); } + /// Set the i'th value of the Buffer to the specified value + void setValue(Index i, const ValueType& val) { assert(i < SIZE); mData[i] = val; } + /// Assigns the values in the other Buffer to this Buffer + Buffer& operator=(const Buffer& other) + { + ValueType* target = mData; + const ValueType* source = other.mData; + Index n = SIZE; + while (n--) *target++ = *source++; + return *this; + } + /// Return true if the values in the other buffer exactly + /// equates the values in this buffer + bool operator==(const Buffer& other) const + { + const ValueType* target = mData; + const ValueType* source = other.mData; + Index n = SIZE; + while (n && math::isExactlyEqual(*target++, *source++)) --n; + return n == 0; + } + /// Return true if any of the values in the other buffer do + /// not exactly equate the values in this buffer + bool operator!=(const Buffer& other) const { return !(other == *this); } + /// Replace the values in this Buffer with the values in the other Buffer + void swap(Buffer& other) + { + ValueType* tmp = mData; + mData = other.mData; + other.mData = tmp; + } + /// Return the memory-footprint of this Buffer in units of bytes + static Index memUsage() { return sizeof(ValueType*) + SIZE * sizeof(ValueType); } + /// Return the number of values represented in this Buffer + static Index size() { return SIZE; } + + private: + /// This direct access method is private since it makes + /// assumptions about the implementations of the memory layout. + ValueType& operator[](Index i) { assert(i < SIZE); return mData[i]; } + + friend class ::TestLeaf; + // Allow the parent LeafNode to access this Buffer's data pointer. + friend class LeafNode; + + ValueType* mData; + }; // class Buffer + + + /// Default constructor + LeafNode(); + + /// @brief Constructor + /// @param coords the grid index coordinates of a voxel + /// @param value a value with which to fill the buffer + /// @param active the active state to which to initialize all voxels + explicit LeafNode(const Coord& coords, + const ValueType& value = zeroVal(), + bool active = false); + + /// Deep copy constructor + LeafNode(const LeafNode&); + + /// 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(); } + + /// 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: + typedef typename NodeMaskType::OnIterator MaskOnIterator; + typedef typename NodeMaskType::OffIterator MaskOffIterator; + typedef typename NodeMaskType::DenseIterator MaskDenseIterator; + + // Type tags to disambiguate template instantiations + struct ValueOn {}; struct ValueOff {}; struct ValueAll {}; + struct ChildOn {}; struct ChildOff {}; struct ChildAll {}; + + template + struct ValueIter: + // Derives from SparseIteratorBase, but can also be used as a dense iterator, + // if MaskIterT is a dense mask iterator type. + public SparseIteratorBase< + MaskIterT, ValueIter, NodeT, ValueT> + { + typedef SparseIteratorBase BaseT; + + ValueIter() {} + ValueIter(const MaskIterT& iter, NodeT* parent): BaseT(iter, parent) {} + + ValueT& getItem(Index pos) const { return this->parent().getValue(pos); } + ValueT& getValue() const { return this->parent().getValue(this->pos()); } + + // Note: setItem() can't be called on const iterators. + void setItem(Index pos, const ValueT& value) const + { + this->parent().setValueOnly(pos, value); + } + // Note: setValue() can't be called on const iterators. + void setValue(const ValueT& value) const + { + this->parent().setValueOnly(this->pos(), value); + } + + // Note: modifyItem() can't be called on const iterators. + template + void modifyItem(Index n, const ModifyOp& op) const { this->parent().modifyValue(n, op); } + // Note: modifyValue() can't be called on const iterators. + template + void modifyValue(const ModifyOp& op) const { this->parent().modifyValue(this->pos(), op); } + }; + + /// Leaf nodes have no children, so their child iterators have no get/set accessors. + template + struct ChildIter: + public SparseIteratorBase, NodeT, ValueType> + { + ChildIter() {} + ChildIter(const MaskIterT& iter, NodeT* parent): SparseIteratorBase< + MaskIterT, ChildIter, NodeT, ValueType>(iter, parent) {} + }; + + template + struct DenseIter: public DenseIteratorBase< + MaskDenseIterator, DenseIter, NodeT, /*ChildT=*/void, ValueT> + { + typedef DenseIteratorBase BaseT; + typedef typename BaseT::NonConstValueType NonConstValueT; + + DenseIter() {} + DenseIter(const MaskDenseIterator& iter, NodeT* parent): BaseT(iter, parent) {} + + bool getItem(Index pos, void*& child, NonConstValueT& value) const + { + value = this->parent().getValue(pos); + child = NULL; + return false; // no child + } + + // Note: setItem() can't be called on const iterators. + //void setItem(Index pos, void* child) const {} + + // Note: unsetItem() can't be called on const iterators. + void unsetItem(Index pos, const ValueT& value) const + { + this->parent().setValueOnly(pos, value); + } + }; + +public: + typedef ValueIter ValueOnIter; + typedef ValueIter ValueOnCIter; + typedef ValueIter ValueOffIter; + typedef ValueIter ValueOffCIter; + typedef ValueIter ValueAllIter; + typedef ValueIter ValueAllCIter; + typedef ChildIter ChildOnIter; + typedef ChildIter ChildOnCIter; + typedef ChildIter ChildOffIter; + typedef ChildIter ChildOffCIter; + typedef DenseIter ChildAllIter; + typedef DenseIter ChildAllCIter; + + ValueOnCIter cbeginValueOn() const { return ValueOnCIter(mValueMask.beginOn(), this); } + ValueOnCIter beginValueOn() const { return ValueOnCIter(mValueMask.beginOn(), this); } + ValueOnIter beginValueOn() { return ValueOnIter(mValueMask.beginOn(), this); } + ValueOffCIter cbeginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } + ValueOffCIter beginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } + ValueOffIter beginValueOff() { return ValueOffIter(mValueMask.beginOff(), this); } + ValueAllCIter cbeginValueAll() const { return ValueAllCIter(mValueMask.beginDense(), this); } + ValueAllCIter beginValueAll() const { return ValueAllCIter(mValueMask.beginDense(), this); } + ValueAllIter beginValueAll() { return ValueAllIter(mValueMask.beginDense(), this); } + + ValueOnCIter cendValueOn() const { return ValueOnCIter(mValueMask.endOn(), this); } + ValueOnCIter endValueOn() const { return ValueOnCIter(mValueMask.endOn(), this); } + ValueOnIter endValueOn() { return ValueOnIter(mValueMask.endOn(), this); } + ValueOffCIter cendValueOff() const { return ValueOffCIter(mValueMask.endOff(), this); } + ValueOffCIter endValueOff() const { return ValueOffCIter(mValueMask.endOff(), this); } + ValueOffIter endValueOff() { return ValueOffIter(mValueMask.endOff(), this); } + ValueAllCIter cendValueAll() const { return ValueAllCIter(mValueMask.endDense(), this); } + ValueAllCIter endValueAll() const { return ValueAllCIter(mValueMask.endDense(), this); } + ValueAllIter endValueAll() { return ValueAllIter(mValueMask.endDense(), this); } + + // Note that [c]beginChildOn() and [c]beginChildOff() actually return end iterators, + // because leaf nodes have no children. + ChildOnCIter cbeginChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } + ChildOnCIter beginChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } + ChildOnIter beginChildOn() { return ChildOnIter(mValueMask.endOn(), this); } + ChildOffCIter cbeginChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } + ChildOffCIter beginChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } + ChildOffIter beginChildOff() { return ChildOffIter(mValueMask.endOff(), this); } + ChildAllCIter cbeginChildAll() const { return ChildAllCIter(mValueMask.beginDense(), this); } + ChildAllCIter beginChildAll() const { return ChildAllCIter(mValueMask.beginDense(), this); } + ChildAllIter beginChildAll() { return ChildAllIter(mValueMask.beginDense(), this); } + + ChildOnCIter cendChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } + ChildOnCIter endChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } + ChildOnIter endChildOn() { return ChildOnIter(mValueMask.endOn(), this); } + ChildOffCIter cendChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } + ChildOffCIter endChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } + ChildOffIter endChildOff() { return ChildOffIter(mValueMask.endOff(), this); } + ChildAllCIter cendChildAll() const { return ChildAllCIter(mValueMask.endDense(), this); } + ChildAllCIter endChildAll() const { return ChildAllCIter(mValueMask.endDense(), this); } + ChildAllIter endChildAll() { return ChildAllIter(mValueMask.endDense(), this); } + + // + // Buffer management + // + /// @brief Exchange this node's data buffer with the given data buffer + /// without changing the active states of the values. + void swap(Buffer& other) { mBuffer.swap(other); } + const Buffer& buffer() const { return mBuffer; } + Buffer& buffer() { return mBuffer; } + + // + // I/O methods + // + /// @brief Read in just the topology. + /// @param is the stream from which to read + /// @param fromHalf if true, floating-point input values are assumed to be 16-bit + void readTopology(std::istream& is, bool fromHalf = false); + /// @brief Write out just the topology. + /// @param os the stream to which to write + /// @param toHalf if true, output floating-point values as 16-bit half floats + void writeTopology(std::ostream& os, bool toHalf = false) const; + + /// @brief Read buffers from a stream. + /// @param is the stream from which to read + /// @param fromHalf if true, floating-point input values are assumed to be 16-bit + void readBuffers(std::istream& is, bool fromHalf = false); + /// @brief Write buffers to a stream. + /// @param os the stream to which to write + /// @param toHalf if true, output floating-point values as 16-bit half floats + void writeBuffers(std::ostream& os, bool toHalf = false) const; + + size_t streamingSize(bool toHalf = false) const; + + // + // Accessor methods + // + /// Return the value of the voxel at the given coordinates. + const ValueType& getValue(const Coord& xyz) const; + /// Return the value of the voxel at the given linear offset. + const ValueType& getValue(Index offset) const; + + /// @brief Return @c true if the voxel at the given coordinates is active. + /// @param xyz the coordinates of the voxel to be probed + /// @param[out] val the value of the voxel at the given coordinates + bool probeValue(const Coord& xyz, ValueType& val) const; + /// @brief Return @c true if the voxel at the given offset is active. + /// @param offset the linear offset of the voxel to be probed + /// @param[out] val the value of the voxel at the given coordinates + bool probeValue(Index offset, ValueType& val) const; + + /// Return the level (i.e., 0) at which leaf node values reside. + static Index getValueLevel(const Coord&) { return LEVEL; } + + /// Set the active state of the voxel at the given coordinates but don't change its value. + void setActiveState(const Coord& xyz, bool on); + /// Set the active state of the voxel at the given offset but don't change its value. + void setActiveState(Index offset, bool on) { assert(offsetsetValueOn(LeafNode::coordToOffset(xyz), val); + } + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValue(const Coord& xyz, const ValueType& val) { this->setValueOn(xyz, val); }; + /// Set the value of the voxel at the given offset and mark the voxel as active. + void setValueOn(Index offset, const ValueType& val) { + mBuffer[offset] = val; + mValueMask.setOn(offset); + } + + /// @brief Apply a functor to the value of the voxel at the given offset + /// and mark the voxel as active. + template + void modifyValue(Index offset, const ModifyOp& op) + { + op(mBuffer[offset]); + mValueMask.setOn(offset); + } + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + template + void modifyValue(const Coord& xyz, const ModifyOp& op) + { + this->modifyValue(this->coordToOffset(xyz), op); + } + + /// Apply a functor to the voxel at the given coordinates. + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) + { + const Index offset = this->coordToOffset(xyz); + bool state = mValueMask.isOn(offset); + op(mBuffer[offset], state); + mValueMask.set(offset, state); + } + + /// Mark all voxels as active but don't change their values. + void setValuesOn() { mValueMask.setOn(); } + /// Mark all voxels as inactive but don't change their values. + void setValuesOff() { mValueMask.setOff(); } + + /// Return @c true if the voxel at the given coordinates is active. + bool isValueOn(const Coord& xyz) const {return this->isValueOn(LeafNode::coordToOffset(xyz));} + /// Return @c true if the voxel at the given offset is active. + bool isValueOn(Index offset) const { return mValueMask.isOn(offset); } + + /// Return @c false since leaf nodes never contain tiles. + static bool hasActiveTiles() { return false; } + + /// Set all voxels within an axis-aligned box to the specified value and active state. + void fill(const CoordBBox& bbox, const ValueType&, bool active = true); + + /// Set all voxels to the specified value but don't change their active states. + void fill(const ValueType& value); + /// Set all voxels to the specified value and active state. + void fill(const ValueType& value, bool active); + + /// @brief Copy into a dense grid the values of the voxels that lie within + /// a given bounding box. + /// + /// @param bbox inclusive bounding box of the voxels to be copied into the dense grid + /// @param dense dense grid with a stride in @e z of one (see tools::Dense + /// in tools/Dense.h for the required API) + /// + /// @note @a bbox is assumed to be identical to or contained in the coordinate domains + /// of both the dense grid and this node, i.e., no bounds checking is performed. + /// @note Consider using tools::CopyToDense in tools/Dense.h + /// instead of calling this method directly. + template + void copyToDense(const CoordBBox& bbox, DenseT& dense) const; + + /// @brief Copy from a dense grid into this node the values of the voxels + /// that lie within a given bounding box. + /// @details Only values that are different (by more than the given tolerance) + /// from the background value will be active. Other values are inactive + /// and truncated to the background value. + /// + /// @param bbox inclusive bounding box of the voxels to be copied into this node + /// @param dense dense grid with a stride in @e z of one (see tools::Dense + /// in tools/Dense.h for the required API) + /// @param background background value of the tree that this node belongs to + /// @param tolerance tolerance within which a value equals the background value + /// + /// @note @a bbox is assumed to be identical to or contained in the coordinate domains + /// of both the dense grid and this node, i.e., no bounds checking is performed. + /// @note Consider using tools::CopyFromDense in tools/Dense.h + /// instead of calling this method directly. + template + void copyFromDense(const CoordBBox& bbox, const DenseT& dense, + const ValueType& background, const ValueType& tolerance); + + /// @brief Return the value of the voxel at the given coordinates. + /// @note Used internally by ValueAccessor. + template + const ValueType& getValueAndCache(const Coord& xyz, AccessorT&) const + { + return this->getValue(xyz); + } + + /// @brief Return @c true if the voxel at the given coordinates is active. + /// @note Used internally by ValueAccessor. + template + bool isValueOnAndCache(const Coord& xyz, AccessorT&) const { return this->isValueOn(xyz); } + + /// @brief Change the value of the voxel at the given coordinates and mark it as active. + /// @note Used internally by ValueAccessor. + template + void setValueAndCache(const Coord& xyz, const ValueType& val, AccessorT&) + { + this->setValueOn(xyz, val); + } + + /// @brief Change the value of the voxel at the given coordinates + /// but preserve its state. + /// @note Used internally by ValueAccessor. + template + void setValueOnlyAndCache(const Coord& xyz, const ValueType& val, AccessorT&) + { + this->setValueOnly(xyz, val); + } + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + /// @note Used internally by ValueAccessor. + template + void modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&) + { + this->modifyValue(xyz, op); + } + + /// Apply a functor to the voxel at the given coordinates. + /// @note Used internally by ValueAccessor. + template + void modifyValueAndActiveStateAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&) + { + this->modifyValueAndActiveState(xyz, op); + } + + /// @brief Change the value of the voxel at the given coordinates and mark it as inactive. + /// @note Used internally by ValueAccessor. + template + void setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT&) + { + this->setValueOff(xyz, value); + } + + /// @brief Set the active state of the voxel at the given coordinates + /// without changing its value. + /// @note Used internally by ValueAccessor. + template + void setActiveStateAndCache(const Coord& xyz, bool on, AccessorT&) + { + this->setActiveState(xyz, on); + } + + /// @brief Return @c true if the voxel at the given coordinates is active + /// and return the voxel value in @a val. + /// @note Used internally by ValueAccessor. + template + bool probeValueAndCache(const Coord& xyz, ValueType& val, AccessorT&) const + { + return this->probeValue(xyz, val); + } + + /// @brief Return the value of the voxel at the given coordinates and return + /// its active state and level (i.e., 0) in @a state and @a level. + /// @note Used internally by ValueAccessor. + template + const ValueType& getValue(const Coord& xyz, bool& state, int& level, AccessorT&) const + { + const Index offset = this->coordToOffset(xyz); + state = mValueMask.isOn(offset); + level = LEVEL; + return mBuffer[offset]; + } + + /// @brief Return the LEVEL (=0) at which leaf node values reside. + /// @note Used internally by ValueAccessor (note last argument is a dummy). + template + static Index getValueLevelAndCache(const Coord&, AccessorT&) { return LEVEL; } + + /// @brief Return a const reference to the first value in the buffer. + /// @note Though it is potentially risky you can convert this + /// to a non-const pointer by means of const_case&. + const ValueType& getFirstValue() const { return mBuffer[0]; } + /// Return a const reference to the last value in the buffer. + const ValueType& getLastValue() const { return mBuffer[SIZE - 1]; } + + /// @brief Replace inactive occurrences of @a oldBackground with @a newBackground, + /// and inactive occurrences of @a -oldBackground with @a -newBackground. + void resetBackground(const ValueType& oldBackground, const ValueType& newBackground); + + /// @brief Overwrite each inactive value in this node and in any child nodes with + /// a new value whose magnitude is equal to the specified background value and whose + /// sign is consistent with that of the lexicographically closest active value. + /// @details This is primarily useful for propagating the sign from the (active) voxels + /// in a narrow-band level set to the inactive voxels outside the narrow band. + void signedFloodFill(const ValueType& background); + + /// @brief Overwrite each inactive value in this node and in any child nodes with + /// either @a outside or @a inside, depending on the sign of the lexicographically + /// closest active value. + /// @details Specifically, an inactive value is set to @a outside if the closest active + /// value in the lexicographic direction is positive, otherwise it is set to @a inside. + void signedFloodFill(const ValueType& outside, const ValueType& inside); + + void negate(); + + void voxelizeActiveTiles() {}; + + template void merge(const LeafNode&); + template void merge(const ValueType& tileValue, bool tileActive); + template + void merge(const LeafNode& other, const ValueType& /*bg*/, const ValueType& /*otherBG*/); + + /// @brief Union this node's set of active values with the active values + /// of the other node, whose @c ValueType may be different. So a + /// resulting voxel will be active if either of the original voxels + /// were active. + /// + /// @note This operation modifies only active states, not values. + template + void topologyUnion(const LeafNode& other); + + /// @brief Intersect this node's set of active values with the active values + /// of the other node, whose @c ValueType may be different. So a + /// resulting voxel will be active only if both of the original voxels + /// were active. + /// + /// @details The last dummy argument is required to match the signature + /// for InternalNode::topologyIntersection. + /// + /// @note This operation modifies only active states, not + /// values. Also note that this operation can result in all voxels + /// being inactive so consider subsequnetly calling prune. + template + void topologyIntersection(const LeafNode& other, const ValueType&); + + /// @brief Difference this node's set of active values with the active values + /// of the other node, whose @c ValueType may be different. So a + /// resulting voxel will be active only if the original voxel is + /// active in this LeafNode and inactive in the other LeafNode. + /// + /// @details The last dummy argument is required to match the signature + /// for InternalNode::topologyDifference. + /// + /// @note This operation modifies only active states, not values. + /// Also, 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. + template void pruneOp(PruneOp&) {} + void prune(const ValueType& /*tolerance*/ = zeroVal()) {} + void pruneInactive(const ValueType&) {} + void addLeaf(LeafNode*) {} + template + void addLeafAndCache(LeafNode*, AccessorT&) {} + template + NodeT* stealNode(const Coord&, const ValueType&, bool) { return NULL; } + template + NodeT* probeNode(const Coord&) { return NULL; } + template + const NodeT* probeConstNode(const Coord&) const { return NULL; } + //@} + + void addTile(Index level, const Coord&, const ValueType&, bool); + void addTile(Index offset, const ValueType&, bool); + template + void addTileAndCache(Index, const Coord&, const ValueType&, bool, AccessorT&); + + //@{ + /// @brief Return a pointer to this node. + LeafNode* touchLeaf(const Coord&) { return this; } + template + LeafNode* touchLeafAndCache(const Coord&, AccessorT&) { return this; } + template + NodeT* probeNodeAndCache(const Coord&, AccessorT&) + { + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if (!(boost::is_same::value)) return NULL; + return reinterpret_cast(this); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + LeafNode* probeLeaf(const Coord&) { return this; } + template + LeafNode* probeLeafAndCache(const Coord&, AccessorT&) { return this; } + //@} + //@{ + /// @brief Return a @const pointer to this node. + const LeafNode* probeConstLeaf(const Coord&) const { return this; } + template + const LeafNode* probeConstLeafAndCache(const Coord&, AccessorT&) const { return this; } + template + const LeafNode* probeLeafAndCache(const Coord&, AccessorT&) const { return this; } + const LeafNode* probeLeaf(const Coord&) const { return this; } + template + const NodeT* probeConstNodeAndCache(const Coord&, AccessorT&) const + { + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if (!(boost::is_same::value)) return NULL; + return reinterpret_cast(this); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + //@} + + /// Return @c true if all of this node's values have the same active state + /// and are equal to within the given tolerance, and return the value in @a constValue + /// and the active state in @a state. + bool isConstant(ValueType& constValue, bool& state, + const ValueType& tolerance = zeroVal()) const; + /// Return @c true if all of this node's values are inactive. + bool isInactive() const { return mValueMask.isOff(); } + +protected: + friend class ::TestLeaf; + template friend class ::TestLeafIO; + + // During topology-only construction, access is needed + // to protected/private members of other template instances. + template friend class LeafNode; + + friend struct ValueIter; + friend struct ValueIter; + friend struct ValueIter; + friend struct ValueIter; + friend struct ValueIter; + friend struct ValueIter; + + // Allow iterators to call mask accessor methods (see below). + /// @todo Make mask accessors public? + friend class IteratorBase; + friend class IteratorBase; + friend class IteratorBase; + + /// Buffer containing the actual data values + Buffer mBuffer; + /// Bitmask that determines which voxels are active + NodeMaskType mValueMask; + /// Global grid index coordinates (x,y,z) of the local origin of this node + Coord mOrigin; + + // Mask accessors +public: + bool isValueMaskOn(Index n) const { return mValueMask.isOn(n); } + bool isValueMaskOn() const { return mValueMask.isOn(); } + bool isValueMaskOff(Index n) const { return mValueMask.isOff(n); } + bool isValueMaskOff() const { return mValueMask.isOff(); } + const NodeMaskType& getValueMask() const { return mValueMask; } + NodeMaskType& getValueMask() { return mValueMask; } + void setValueMask(const NodeMaskType& mask) { mValueMask = mask; } + bool isChildMaskOn(Index) const { return false; } // leaf nodes have no children + bool isChildMaskOff(Index) const { return true; } + bool isChildMaskOff() const { return true; } +protected: + void setValueMask(Index n, bool on) { mValueMask.set(n, on); } + void setValueMaskOn(Index n) { mValueMask.setOn(n); } + void setValueMaskOff(Index n) { mValueMask.setOff(n); } + + /// Compute the origin of the leaf node that contains the voxel with the given coordinates. + static void evalNodeOrigin(Coord& xyz) { xyz &= ~(DIM - 1); } + + template + static inline void doVisit(NodeT&, VisitorOp&); + + template + static inline void doVisit2Node(NodeT& self, OtherNodeT& other, VisitorOp&); + + template + static inline void doVisit2(NodeT& self, OtherChildAllIterT&, VisitorOp&, bool otherIsLHS); + +}; // end of LeafNode class + + +//////////////////////////////////////// + + +//@{ +/// 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))) +{ +} + + +template +inline +LeafNode::LeafNode(const LeafNode &other): + mBuffer(other.mBuffer), + mValueMask(other.mValueMask), + 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.mValueMask), + 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.mValueMask), + mOrigin(other.mOrigin) +{ +} + + +template +template +inline +LeafNode::LeafNode(const LeafNode& other, + const ValueType& offValue, const ValueType& onValue, TopologyCopy): + mValueMask(other.mValueMask), + mOrigin(other.mOrigin) +{ + for (Index i = 0; i < SIZE; ++i) { + mBuffer[i] = (mValueMask.isOn(i) ? onValue : offValue); + } +} + + +template +inline +LeafNode::~LeafNode() +{ +} + + +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[offset] = val; + mValueMask.setOff(offset); +} + + +template +inline void +LeafNode::setActiveState(const Coord& xyz, bool on) +{ + mValueMask.set(this->coordToOffset(xyz), on); +} + + +template +inline void +LeafNode::setValueOnly(const Coord& xyz, const ValueType& val) +{ + this->setValueOnly(LeafNode::coordToOffset(xyz), val); +} + +template +inline void +LeafNode::setValueOnly(Index offset, const ValueType& val) +{ + assert(offset +inline void +LeafNode::fill(const CoordBBox& bbox, const ValueType& value, bool active) +{ + for (Int32 x = bbox.min().x(); x <= bbox.max().x(); ++x) { + const Index offsetX = (x & (DIM-1u)) << 2*Log2Dim; + for (Int32 y = bbox.min().y(); y <= bbox.max().y(); ++y) { + const Index offsetXY = offsetX + ((y & (DIM-1u)) << Log2Dim); + for (Int32 z = bbox.min().z(); z <= bbox.max().z(); ++z) { + const Index offset = offsetXY + (z & (DIM-1u)); + mBuffer[offset] = value; + mValueMask.set(offset, active); + } + } + } +} + +template +inline void +LeafNode::fill(const ValueType& value) +{ + mBuffer.fill(value); +} + +template +inline void +LeafNode::fill(const ValueType& value, bool active) +{ + mBuffer.fill(value); + mValueMask.set(active); +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::copyToDense(const CoordBBox& bbox, DenseT& dense) const +{ + typedef typename DenseT::ValueType DenseValueType; + + const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); + const Coord& min = dense.bbox().min(); + DenseValueType* t0 = dense.data() + zStride * (bbox.min()[2] - min[2]); // target array + const T* s0 = &mBuffer[bbox.min()[2] & (DIM-1u)]; // source array + for (Int32 x = bbox.min()[0], ex = bbox.max()[0] + 1; x < ex; ++x) { + DenseValueType* t1 = t0 + xStride * (x - min[0]); + const T* s1 = s0 + ((x & (DIM-1u)) << 2*Log2Dim); + for (Int32 y = bbox.min()[1], ey = bbox.max()[1] + 1; y < ey; ++y) { + DenseValueType* t2 = t1 + yStride * (y - min[1]); + const T* s2 = s1 + ((y & (DIM-1u)) << Log2Dim); + for (Int32 z = bbox.min()[2], ez = bbox.max()[2] + 1; z < ez; ++z, t2 += zStride) { + *t2 = DenseValueType(*s2++); + } + } + } +} + + +template +template +inline void +LeafNode::copyFromDense(const CoordBBox& bbox, const DenseT& dense, + const ValueType& background, const ValueType& tolerance) +{ + typedef typename DenseT::ValueType DenseValueType; + + const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); + const Coord& min = dense.bbox().min(); + + const DenseValueType* s0 = dense.data() + zStride * (bbox.min()[2] - min[2]); // source + const Int32 n0 = bbox.min()[2] & (DIM-1u); + for (Int32 x = bbox.min()[0], ex = bbox.max()[0]+1; x < ex; ++x) { + const DenseValueType* s1 = s0 + xStride * (x - min[0]); + const Int32 n1 = n0 + ((x & (DIM-1u)) << 2*LOG2DIM); + for (Int32 y = bbox.min()[1], ey = bbox.max()[1]+1; y < ey; ++y) { + const DenseValueType* s2 = s1 + yStride * (y - min[1]); + Int32 n2 = n1 + ((y & (DIM-1u)) << LOG2DIM); + for (Int32 z = bbox.min()[2], ez = bbox.max()[2]+1; z < ez; ++z, ++n2, s2 += zStride) { + if (math::isApproxEqual(background, ValueType(*s2), tolerance)) { + mValueMask.setOff(n2); + mBuffer[n2] = background; + } else { + mValueMask.setOn(n2); + mBuffer[n2] = ValueType(*s2); + } + } + } + } +} + + +//////////////////////////////////////// + + +template +inline void +LeafNode::readTopology(std::istream& is, bool /*fromHalf*/) +{ + mValueMask.load(is); +} + + +template +inline void +LeafNode::writeTopology(std::ostream& os, bool /*toHalf*/) const +{ + mValueMask.save(os); +} + + +//////////////////////////////////////// + + +template +inline void +LeafNode::readBuffers(std::istream& is, bool fromHalf) +{ + // Read in the value mask. + mValueMask.load(is); + + int8_t numBuffers = 1; + if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { + // Read in the origin. + is.read(reinterpret_cast(&mOrigin), sizeof(Coord::ValueType) * 3); + + // Read in the number of buffers, which should now always be one. + is.read(reinterpret_cast(&numBuffers), sizeof(int8_t)); + } + + io::readCompressedValues(is, mBuffer.mData, SIZE, mValueMask, fromHalf); + + if (numBuffers > 1) { + // Read in and discard auxiliary buffers that were created with earlier + // versions of the library. (Auxiliary buffers are not mask compressed.) + const bool zipped = io::getDataCompression(is) & io::COMPRESS_ZIP; + Buffer temp; + for (int i = 1; i < numBuffers; ++i) { + if (fromHalf) { + io::HalfReader::isReal, T>::read(is, temp.mData, SIZE, zipped); + } else { + io::readData(is, temp.mData, SIZE, zipped); + } + } + } +} + + +template +inline void +LeafNode::writeBuffers(std::ostream& os, bool toHalf) const +{ + // Write out the value mask. + mValueMask.save(os); + + io::writeCompressedValues(os, mBuffer.mData, SIZE, + mValueMask, /*childMask=*/NodeMaskType(), toHalf); +} + + +//////////////////////////////////////// + + +template +inline bool +LeafNode::operator==(const LeafNode& other) const +{ + return mOrigin == other.mOrigin && + mValueMask == other.mValueMask && + mBuffer == other.mBuffer; +} + + +template +inline Index64 +LeafNode::memUsage() const +{ + return mBuffer.memUsage() + sizeof(mOrigin) + mValueMask.memUsage(); +} + + +template +inline void +LeafNode::evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels) const +{ + CoordBBox this_bbox = this->getNodeBoundingBox(); + if (bbox.isInside(this_bbox)) return;//this LeafNode is already enclosed in the bbox + if (ValueOnCIter iter = this->cbeginValueOn()) {//any active values? + if (visitVoxels) {//use voxel granularity? + this_bbox.reset(); + for(; iter; ++iter) this_bbox.expand(this->offsetToLocalCoord(iter.pos())); + this_bbox.translate(this->origin()); + } + bbox.expand(this_bbox); + } +} + + +template +template +inline bool +LeafNode::hasSameTopology(const LeafNode* other) const +{ + assert(other); + return (Log2Dim == OtherLog2Dim && mValueMask == other->getValueMask()); +} + + +template +inline bool +LeafNode::isConstant(ValueType& constValue, + bool& state, const ValueType& tolerance) const +{ + state = mValueMask.isOn(); + + if (!(state || mValueMask.isOff())) return false; + + bool allEqual = true; + const T value = mBuffer[0]; + for (Index i = 1; allEqual && i < SIZE; ++i) { + /// @todo Alternatively, allEqual = !((maxVal - minVal) > (2 * tolerance)) + allEqual = math::isApproxEqual(mBuffer[i], value, tolerance); + } + if (allEqual) constValue = value; ///< @todo return average/median value? + return allEqual; +} + + +//////////////////////////////////////// + + +template +inline void +LeafNode::addTile(Index level, const Coord& xyz, const ValueType& val, bool active) +{ + assert(level == 0); + this->addTile(this->coordToOffset(xyz), val, active); +} + +template +inline void +LeafNode::addTile(Index offset, const ValueType& val, bool active) +{ + assert(offset < SIZE); + setValueOnly(offset, val); + setActiveState(offset, active); +} + +template +template +inline void +LeafNode::addTileAndCache(Index level, const Coord& xyz, + const ValueType& val, bool active, AccessorT&) +{ + this->addTile(level, xyz, val, active); +} + + +//////////////////////////////////////// + + +template +inline void +LeafNode::signedFloodFill(const ValueType& background) +{ + this->signedFloodFill(background, math::negative(background)); +} + +template +inline void +LeafNode::signedFloodFill(const ValueType& outsideValue, + const ValueType& insideValue) +{ + const Index first = mValueMask.findFirstOn(); + if (first < SIZE) { + bool xInside = math::isNegative(mBuffer[first]), yInside = xInside, zInside = xInside; + for (Index x = 0; x != (1 << Log2Dim); ++x) { + const Index x00 = x << (2 * Log2Dim); + if (mValueMask.isOn(x00)) { + xInside = math::isNegative(mBuffer[x00]); // element(x, 0, 0) + } + yInside = xInside; + for (Index y = 0; y != (1 << Log2Dim); ++y) { + const Index xy0 = x00 + (y << Log2Dim); + if (mValueMask.isOn(xy0)) { + yInside = math::isNegative(mBuffer[xy0]); // element(x, y, 0) + } + zInside = yInside; + for (Index z = 0; z != (1 << Log2Dim); ++z) { + const Index xyz = xy0 + z; // element(x, y, z) + if (mValueMask.isOn(xyz)) { + zInside = math::isNegative(mBuffer[xyz]); + } else { + mBuffer[xyz] = zInside ? insideValue : outsideValue; + } + } + } + } + } else {// if no active voxels exist simply use the sign of the first value + mBuffer.fill(math::isNegative(mBuffer[0]) ? insideValue : outsideValue); + } +} + + +template +inline void +LeafNode::resetBackground(const ValueType& oldBackground, + const ValueType& newBackground) +{ + typename NodeMaskType::OffIterator iter; + // For all inactive values... + for (iter = this->mValueMask.beginOff(); iter; ++iter) { + ValueType &inactiveValue = mBuffer[iter.pos()]; + if (math::isApproxEqual(inactiveValue, oldBackground)) { + inactiveValue = newBackground; + } else if (math::isApproxEqual(inactiveValue, math::negative(oldBackground))) { + inactiveValue = math::negative(newBackground); + } + } +} + + +template +template +inline void +LeafNode::merge(const LeafNode& other) +{ + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if (Policy == MERGE_NODES) return; + typename NodeMaskType::OnIterator iter = other.mValueMask.beginOn(); + for (; iter; ++iter) { + const Index n = iter.pos(); + if (mValueMask.isOff(n)) { + mBuffer[n] = other.mBuffer[n]; + mValueMask.setOn(n); + } + } + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + +template +template +inline void +LeafNode::merge(const LeafNode& other, + const ValueType& /*bg*/, const ValueType& /*otherBG*/) +{ + this->template merge(other); +} + +template +template +inline void +LeafNode::merge(const ValueType& tileValue, bool tileActive) +{ + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if (Policy != MERGE_ACTIVE_STATES_AND_NODES) return; + if (!tileActive) return; + // Replace all inactive values with the active tile value. + for (typename NodeMaskType::OffIterator iter = mValueMask.beginOff(); iter; ++iter) { + const Index n = iter.pos(); + mBuffer[n] = tileValue; + mValueMask.setOn(n); + } + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +template +template +inline void +LeafNode::topologyUnion(const LeafNode& other) +{ + mValueMask |= other.getValueMask(); +} + +template +template +inline void +LeafNode::topologyIntersection(const LeafNode& other, + const ValueType&) +{ + mValueMask &= other.getValueMask(); +} + +template +template +inline void +LeafNode::topologyDifference(const LeafNode& other, + const ValueType&) +{ + mValueMask &= !other.getValueMask(); +} + +template +inline void +LeafNode::negate() +{ + for (Index i = 0; i < SIZE; ++i) { + mBuffer[i] = -mBuffer[i]; + } +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::combine(const LeafNode& other, CombineOp& op) +{ + CombineArgs args; + for (Index i = 0; i < SIZE; ++i) { + op(args.setARef(mBuffer[i]) + .setAIsActive(mValueMask.isOn(i)) + .setBRef(other.mBuffer[i]) + .setBIsActive(other.mValueMask.isOn(i)) + .setResultRef(mBuffer[i])); + mValueMask.set(i, args.resultIsActive()); + } +} + + +template +template +inline void +LeafNode::combine(const ValueType& value, bool valueIsActive, CombineOp& op) +{ + CombineArgs args; + args.setBRef(value).setBIsActive(valueIsActive); + for (Index i = 0; i < SIZE; ++i) { + op(args.setARef(mBuffer[i]) + .setAIsActive(mValueMask.isOn(i)) + .setResultRef(mBuffer[i])); + mValueMask.set(i, args.resultIsActive()); + } +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::combine2(const LeafNode& other, const OtherType& value, + bool valueIsActive, CombineOp& op) +{ + CombineArgs args; + args.setBRef(value).setBIsActive(valueIsActive); + for (Index i = 0; i < SIZE; ++i) { + op(args.setARef(other.mBuffer[i]) + .setAIsActive(other.mValueMask.isOn(i)) + .setResultRef(mBuffer[i])); + mValueMask.set(i, args.resultIsActive()); + } +} + + +template +template +inline void +LeafNode::combine2(const ValueType& value, const OtherNodeT& other, + bool valueIsActive, CombineOp& op) +{ + CombineArgs args; + args.setARef(value).setAIsActive(valueIsActive); + for (Index i = 0; i < SIZE; ++i) { + op(args.setBRef(other.mBuffer[i]) + .setBIsActive(other.mValueMask.isOn(i)) + .setResultRef(mBuffer[i])); + mValueMask.set(i, args.resultIsActive()); + } +} + + +template +template +inline void +LeafNode::combine2(const LeafNode& b0, const OtherNodeT& b1, CombineOp& op) +{ + CombineArgs args; + for (Index i = 0; i < SIZE; ++i) { + mValueMask.set(i, b0.mValueMask.isOn(i) || b1.mValueMask.isOn(i)); + op(args.setARef(b0.mBuffer[i]) + .setAIsActive(b0.mValueMask.isOn(i)) + .setBRef(b1.mBuffer[i]) + .setBIsActive(b1.mValueMask.isOn(i)) + .setResultRef(mBuffer[i])); + mValueMask.set(i, args.resultIsActive()); + } +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::visitActiveBBox(BBoxOp& op) const +{ + if (op.template descent()) { + for (ValueOnCIter i=this->cbeginValueOn(); i; ++i) { +#ifdef _MSC_VER + op.operator()(CoordBBox::createCube(i.getCoord(), 1)); +#else + op.template operator()(CoordBBox::createCube(i.getCoord(), 1)); +#endif + } + } else { +#ifdef _MSC_VER + op.operator()(this->getNodeBoundingBox()); +#else + op.template operator()(this->getNodeBoundingBox()); +#endif + } +} + + +template +template +inline void +LeafNode::visit(VisitorOp& op) +{ + doVisit(*this, op); +} + + +template +template +inline void +LeafNode::visit(VisitorOp& op) const +{ + doVisit(*this, op); +} + + +template +template +inline void +LeafNode::doVisit(NodeT& self, VisitorOp& op) +{ + for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { + op(iter); + } +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::visit2Node(OtherLeafNodeType& other, VisitorOp& op) +{ + doVisit2Node(*this, other, op); +} + + +template +template +inline void +LeafNode::visit2Node(OtherLeafNodeType& other, VisitorOp& op) const +{ + doVisit2Node(*this, other, op); +} + + +template +template< + typename NodeT, + typename OtherNodeT, + typename VisitorOp, + typename ChildAllIterT, + typename OtherChildAllIterT> +inline void +LeafNode::doVisit2Node(NodeT& self, OtherNodeT& other, VisitorOp& op) +{ + // Allow the two nodes to have different ValueTypes, but not different dimensions. + BOOST_STATIC_ASSERT(OtherNodeT::SIZE == NodeT::SIZE); + BOOST_STATIC_ASSERT(OtherNodeT::LEVEL == NodeT::LEVEL); + + ChildAllIterT iter = self.beginChildAll(); + OtherChildAllIterT otherIter = other.beginChildAll(); + + for ( ; iter && otherIter; ++iter, ++otherIter) { + op(iter, otherIter); + } +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::visit2(IterT& otherIter, VisitorOp& op, bool otherIsLHS) +{ + doVisit2( + *this, otherIter, op, otherIsLHS); +} + + +template +template +inline void +LeafNode::visit2(IterT& otherIter, VisitorOp& op, bool otherIsLHS) const +{ + doVisit2( + *this, otherIter, op, otherIsLHS); +} + + +template +template< + typename NodeT, + typename VisitorOp, + typename ChildAllIterT, + typename OtherChildAllIterT> +inline void +LeafNode::doVisit2(NodeT& self, OtherChildAllIterT& otherIter, + VisitorOp& op, bool otherIsLHS) +{ + if (!otherIter) return; + + if (otherIsLHS) { + for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { + op(otherIter, iter); + } + } else { + for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { + op(iter, otherIter); + } + } +} + + +//////////////////////////////////////// + + +template +inline std::ostream& +operator<<(std::ostream& os, const typename LeafNode::Buffer& buf) +{ + for (Index32 i = 0, N = buf.size(); i < N; ++i) os << buf.mData[i] << ", "; + return os; +} + +} // namespace tree +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + + +//////////////////////////////////////// + + +// Specialization for LeafNodes of type bool +#include "LeafNodeBool.h" + +#endif // OPENVDB_TREE_LEAFNODE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tree/LeafNodeBool.h b/openvdb_2_3_0_library/openvdb/tree/LeafNodeBool.h new file mode 100755 index 0000000..f5831dc --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tree/LeafNodeBool.h @@ -0,0 +1,1655 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_TREE_LEAFNODEBOOL_HAS_BEEN_INCLUDED +#define OPENVDB_TREE_LEAFNODEBOOL_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include // for io::readData(), etc. +#include +#include "LeafNode.h" +#include "Iterator.h" +#include "Util.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tree { + +/// @brief LeafNode specialization for values of type bool that stores both +/// the active states and the values of (2^Log2Dim)^3 voxels as bit masks +template +class LeafNode +{ +public: + typedef LeafNode LeafNodeType; + typedef boost::shared_ptr Ptr; + typedef bool ValueType; + typedef util::NodeMask NodeMaskType; + + // These static declarations must be on separate lines to avoid VC9 compiler errors. + static const Index LOG2DIM = Log2Dim; // needed by parent nodes + static const Index TOTAL = Log2Dim; // needed by parent nodes + static const Index DIM = 1 << TOTAL; // dimension along one coordinate direction + static const Index NUM_VALUES = 1 << 3 * Log2Dim; + static const Index NUM_VOXELS = NUM_VALUES; // total number of voxels represented by this node + static const Index SIZE = NUM_VALUES; + static const Index LEVEL = 0; // level 0 = leaf + + /// @brief ValueConverter::Type is the type of a LeafNode having the same + /// dimensions as this node but a different value type, T. + template + struct ValueConverter { typedef LeafNode Type; }; + + /// @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; + }; + + + class Buffer + { + public: + Buffer() {} + Buffer(bool on) : mData(on) {} + Buffer(const NodeMaskType& other): mData(other) {} + Buffer(const Buffer& other): mData(other.mData) {} + ~Buffer() {} + void fill(bool val) { mData.set(val); } + Buffer& operator=(const Buffer& b) { if (&b != this) { mData = b.mData; } return *this; } + + const bool& getValue(Index i) const + { + assert(i < SIZE); + return mData.isOn(i) ? LeafNode::sOn : LeafNode::sOff; + } + const bool& operator[](Index i) const { return this->getValue(i); } + + bool operator==(const Buffer& other) const { return mData == other.mData; } + bool operator!=(const Buffer& other) const { return mData != other.mData; } + + void setValue(Index i, bool val) { assert(i < SIZE); mData.set(i, val); } + + void swap(Buffer& other) { if (&other != this) std::swap(mData, other.mData); } + + Index memUsage() const { return mData.memUsage(); } + static Index size() { return SIZE; } + + private: + friend class ::TestLeaf; + // Allow the parent LeafNode to access this Buffer's bit mask. + friend class LeafNode; + + NodeMaskType mData; + }; // class Buffer + + + /// Default constructor + LeafNode(); + + /// Constructor + /// @param xyz the coordinates of a voxel that lies within the node + /// @param value the initial value for all of this node's voxels + /// @param active the active state to which to initialize all voxels + explicit LeafNode(const Coord& xyz, bool value = false, bool active = false); + + /// Deep copy constructor + LeafNode(const LeafNode&); + + /// 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(); } + + /// 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); + /// Write out the topology and the origin. + void writeBuffers(std::ostream&, bool toHalf = false) const; + + // + // Accessor methods + // + /// Return the value of the voxel at the given coordinates. + const bool& getValue(const Coord& xyz) const; + /// Return the value of the voxel at the given offset. + const bool& getValue(Index offset) const; + + /// @brief Return @c true if the voxel at the given coordinates is active. + /// @param xyz the coordinates of the voxel to be probed + /// @param[out] val the value of the voxel at the given coordinates + bool probeValue(const Coord& xyz, bool& val) const; + + /// Return the level (0) at which leaf node values reside. + static Index getValueLevel(const Coord&) { return LEVEL; } + + /// Set the active state of the voxel at the given coordinates but don't change its value. + void setActiveState(const Coord& xyz, bool on); + /// Set the active state of the voxel at the given offset but don't change its value. + void setActiveState(Index offset, bool on) { assert(offsetcoordToOffset(xyz)); } + /// Mark the voxel at the given offset as inactive but don't change its value. + void setValueOff(Index offset) { assert(offset < SIZE); mValueMask.setOff(offset); } + + /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. + void setValueOff(const Coord& xyz, bool val); + /// Set the value of the voxel at the given offset and mark the voxel as inactive. + void setValueOff(Index offset, bool val); + + /// Mark the voxel at the given coordinates as active but don't change its value. + void setValueOn(const Coord& xyz) { mValueMask.setOn(this->coordToOffset(xyz)); } + /// Mark the voxel at the given offset as active but don't change its value. + void setValueOn(Index offset) { assert(offset < SIZE); mValueMask.setOn(offset); } + + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValueOn(const Coord& xyz, bool val); + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValue(const Coord& xyz, bool val) { this->setValueOn(xyz, val); }; + /// Set the value of the voxel at the given offset and mark the voxel as active. + void setValueOn(Index offset, bool val); + + /// @brief Apply a functor to the value of the voxel at the given offset + /// and mark the voxel as active. + template + void modifyValue(Index offset, const ModifyOp& op); + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + template + void modifyValue(const Coord& xyz, const ModifyOp& op); + + /// Apply a functor to the voxel at the given coordinates. + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op); + + /// Mark all voxels as active but don't change their values. + void setValuesOn() { mValueMask.setOn(); } + /// Mark all voxels as inactive but don't change their values. + void setValuesOff() { mValueMask.setOff(); } + + /// Return @c true if the voxel at the given coordinates is active. + bool isValueOn(const Coord& xyz) const { return mValueMask.isOn(this->coordToOffset(xyz)); } + /// Return @c true if the voxel at the given offset is active. + bool isValueOn(Index offset) const { assert(offset < SIZE); return mValueMask.isOn(offset); } + + /// Return @c false since leaf nodes never contain tiles. + static bool hasActiveTiles() { return false; } + + /// Set all voxels within an axis-aligned box to the specified value and active state. + void fill(const CoordBBox& bbox, bool value, bool active = true); + + /// Set all voxels to the specified value but don't change their active states. + void fill(const bool& value); + /// Set all voxels to the specified value and active state. + void fill(const bool& value, bool active); + + /// @brief Copy into a dense grid the values of the voxels that lie within + /// a given bounding box. + /// + /// @param bbox inclusive bounding box of the voxels to be copied into the dense grid + /// @param dense dense grid with a stride in @e z of one (see tools::Dense + /// in tools/Dense.h for the required API) + /// + /// @note @a bbox is assumed to be identical to or contained in the coordinate domains + /// of both the dense grid and this node, i.e., no bounds checking is performed. + /// @note Consider using tools::CopyToDense in tools/Dense.h + /// instead of calling this method directly. + template + void copyToDense(const CoordBBox& bbox, DenseT& dense) const; + + /// @brief Copy from a dense grid into this node the values of the voxels + /// that lie within a given bounding box. + /// @details Only values that are different (by more than the given tolerance) + /// from the background value will be active. Other values are inactive + /// and truncated to the background value. + /// + /// @param bbox inclusive bounding box of the voxels to be copied into this node + /// @param dense dense grid with a stride in @e z of one (see tools::Dense + /// in tools/Dense.h for the required API) + /// @param background background value of the tree that this node belongs to + /// @param tolerance tolerance within which a value equals the background value + /// + /// @note @a bbox is assumed to be identical to or contained in the coordinate domains + /// of both the dense grid and this node, i.e., no bounds checking is performed. + /// @note Consider using tools::CopyFromDense in tools/Dense.h + /// instead of calling this method directly. + template + void copyFromDense(const CoordBBox& bbox, const DenseT& dense, bool background, bool tolerance); + + /// @brief Return the value of the voxel at the given coordinates. + /// @note Used internally by ValueAccessor. + template + const bool& getValueAndCache(const Coord& xyz, AccessorT&) const {return this->getValue(xyz);} + + /// @brief Return @c true if the voxel at the given coordinates is active. + /// @note Used internally by ValueAccessor. + template + bool isValueOnAndCache(const Coord& xyz, AccessorT&) const { return this->isValueOn(xyz); } + + /// @brief Change the value of the voxel at the given coordinates and mark it as active. + /// @note Used internally by ValueAccessor. + template + void setValueAndCache(const Coord& xyz, bool val, AccessorT&) { this->setValueOn(xyz, val); } + + /// @brief Change the value of the voxel at the given coordinates + /// but preserve its state. + /// @note Used internally by ValueAccessor. + template + void setValueOnlyAndCache(const Coord& xyz, bool val, AccessorT&) {this->setValueOnly(xyz,val);} + + /// @brief Change the value of the voxel at the given coordinates and mark it as inactive. + /// @note Used internally by ValueAccessor. + template + void setValueOffAndCache(const Coord& xyz, bool value, AccessorT&) + { + this->setValueOff(xyz, value); + } + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + /// @note Used internally by ValueAccessor. + template + void modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&) + { + this->modifyValue(xyz, op); + } + + /// Apply a functor to the voxel at the given coordinates. + /// @note Used internally by ValueAccessor. + template + void modifyValueAndActiveStateAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&) + { + this->modifyValueAndActiveState(xyz, op); + } + + /// @brief Set the active state of the voxel at the given coordinates + /// without changing its value. + /// @note Used internally by ValueAccessor. + template + void setActiveStateAndCache(const Coord& xyz, bool on, AccessorT&) + { + this->setActiveState(xyz, on); + } + + /// @brief Return @c true if the voxel at the given coordinates is active + /// and return the voxel value in @a val. + /// @note Used internally by ValueAccessor. + template + bool probeValueAndCache(const Coord& xyz, bool& val, AccessorT&) const + { + return this->probeValue(xyz, val); + } + + /// @brief Return the LEVEL (=0) at which leaf node values reside. + /// @note Used internally by ValueAccessor. + template + static Index getValueLevelAndCache(const Coord&, AccessorT&) { return LEVEL; } + + /// @brief Return a const reference to the first entry in the buffer. + /// @note Since it's actually a reference to a static data member + /// it should not be converted to a non-const pointer! + const bool& getFirstValue() const { if (mValueMask.isOn(0)) return sOn; else return sOff; } + /// @brief Return a const reference to the last entry in the buffer. + /// @note Since it's actually a reference to a static data member + /// it should not be converted to a non-const pointer! + const bool& getLastValue() const { if (mValueMask.isOn(SIZE-1)) return sOn; else return sOff; } + + /// Return @c true if all of this node's voxels have the same active state + /// and are equal to within the given tolerance, and return the value in + /// @a constValue and the active state in @a state. + bool isConstant(bool& constValue, bool& state, bool tolerance = 0) const; + /// Return @c true if all of this node's values are inactive. + bool isInactive() const { return mValueMask.isOff(); } + + void resetBackground(bool oldBackground, bool newBackground); + + void negate() { mBuffer.mData.toggle(); } + + template + void merge(const LeafNode& other, bool bg = false, bool otherBG = false); + template void merge(bool tileValue, bool tileActive); + + void voxelizeActiveTiles() {}; + + /// @brief Union this node's set of active values with the active values + /// of the other node, whose @c ValueType may be different. So a + /// resulting voxel will be active if either of the original voxels + /// were active. + /// + /// @note This operation modifies only active states, not values. + template + void topologyUnion(const LeafNode& other); + + /// @brief Intersect this node's set of active values with the active values + /// of the other node, whose @c ValueType may be different. So a + /// resulting voxel will be active only if both of the original voxels + /// were active. + /// + /// @details The last dummy argument is required to match the signature + /// for InternalNode::topologyIntersection. + /// + /// @note This operation modifies only active states, not + /// values. Also note that this operation can result in all voxels + /// being inactive so consider subsequnetly calling prune. + template + void topologyIntersection(const LeafNode& other, const bool&); + + /// @brief Difference this node's set of active values with the active values + /// of the other node, whose @c ValueType may be different. So a + /// resulting voxel will be active only if the original voxel is + /// active in this LeafNode and inactive in the other LeafNode. + /// + /// @details The last dummy argument is required to match the signature + /// for InternalNode::topologyDifference. + /// + /// @note This operation modifies only active states, not values. + /// Also, 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 signedFloodFill(bool) {} + /// This function exists only to enable template instantiation. + void signedFloodFill(bool, bool) {} + template void pruneOp(PruneOp&) {} + void prune(const ValueType& /*tolerance*/ = zeroVal()) {} + void pruneInactive(const ValueType&) {} + void addLeaf(LeafNode*) {} + template + void addLeafAndCache(LeafNode*, AccessorT&) {} + template + NodeT* stealNode(const Coord&, const ValueType&, bool) { return NULL; } + template + NodeT* probeNode(const Coord&) { return NULL; } + template + const NodeT* probeConstNode(const Coord&) const { return NULL; } + //@} + + void addTile(Index level, const Coord&, bool val, bool active); + void addTile(Index offset, bool val, bool active); + template + void addTileAndCache(Index level, const Coord&, bool val, bool active, AccessorT&); + + //@{ + /// @brief Return a pointer to this node. + LeafNode* touchLeaf(const Coord&) { return this; } + template + LeafNode* touchLeafAndCache(const Coord&, AccessorT&) { return this; } + LeafNode* probeLeaf(const Coord&) { return this; } + template + LeafNode* probeLeafAndCache(const Coord&, AccessorT&) { return this; } + template + NodeT* probeNodeAndCache(const Coord&, AccessorT&) + { + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if (!(boost::is_same::value)) return NULL; + return reinterpret_cast(this); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + //@} + //@{ + /// @brief Return a @const pointer to this node. + const LeafNode* probeLeaf(const Coord&) const { return this; } + template + const LeafNode* probeLeafAndCache(const Coord&, AccessorT&) const { return this; } + const LeafNode* probeConstLeaf(const Coord&) const { return this; } + template + const LeafNode* probeConstLeafAndCache(const Coord&, AccessorT&) const { return this; } + template + const NodeT* probeConstNodeAndCache(const Coord&, AccessorT&) const + { + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if (!(boost::is_same::value)) return NULL; + return reinterpret_cast(this); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + //@} + + // + // Iterators + // +protected: + typedef typename NodeMaskType::OnIterator MaskOnIter; + typedef typename NodeMaskType::OffIterator MaskOffIter; + typedef typename NodeMaskType::DenseIterator MaskDenseIter; + + template + struct ValueIter: + // Derives from SparseIteratorBase, but can also be used as a dense iterator, + // if MaskIterT is a dense mask iterator type. + public SparseIteratorBase, NodeT, ValueT> + { + typedef SparseIteratorBase BaseT; + + ValueIter() {} + ValueIter(const MaskIterT& iter, NodeT* parent): BaseT(iter, parent) {} + + const bool& getItem(Index pos) const { return this->parent().getValue(pos); } + const bool& getValue() const { return this->getItem(this->pos()); } + + // Note: setItem() can't be called on const iterators. + void setItem(Index pos, bool value) const { this->parent().setValueOnly(pos, value); } + // Note: setValue() can't be called on const iterators. + void setValue(bool value) const { this->setItem(this->pos(), value); } + + // Note: modifyItem() can't be called on const iterators. + template + void modifyItem(Index n, const ModifyOp& op) const { this->parent().modifyValue(n, op); } + // Note: modifyValue() can't be called on const iterators. + template + void modifyValue(const ModifyOp& op) const { this->modifyItem(this->pos(), op); } + }; + + /// Leaf nodes have no children, so their child iterators have no get/set accessors. + template + struct ChildIter: + public SparseIteratorBase, NodeT, bool> + { + ChildIter() {} + ChildIter(const MaskIterT& iter, NodeT* parent): SparseIteratorBase< + MaskIterT, ChildIter, NodeT, bool>(iter, parent) {} + }; + + template + struct DenseIter: public DenseIteratorBase< + MaskDenseIter, DenseIter, NodeT, /*ChildT=*/void, ValueT> + { + typedef DenseIteratorBase BaseT; + typedef typename BaseT::NonConstValueType NonConstValueT; + + DenseIter() {} + DenseIter(const MaskDenseIter& iter, NodeT* parent): BaseT(iter, parent) {} + + bool getItem(Index pos, void*& child, NonConstValueT& value) const + { + value = this->parent().getValue(pos); + child = NULL; + return false; // no child + } + + // Note: setItem() can't be called on const iterators. + //void setItem(Index pos, void* child) const {} + + // Note: unsetItem() can't be called on const iterators. + void unsetItem(Index pos, const ValueT& val) const {this->parent().setValueOnly(pos, val);} + }; + +public: + typedef ValueIter ValueOnIter; + typedef ValueIter ValueOnCIter; + typedef ValueIter ValueOffIter; + typedef ValueIter ValueOffCIter; + typedef ValueIter ValueAllIter; + typedef ValueIter ValueAllCIter; + typedef ChildIter ChildOnIter; + typedef ChildIter ChildOnCIter; + typedef ChildIter ChildOffIter; + typedef ChildIter ChildOffCIter; + typedef DenseIter ChildAllIter; + typedef DenseIter ChildAllCIter; + + ValueOnCIter cbeginValueOn() const { return ValueOnCIter(mValueMask.beginOn(), this); } + ValueOnCIter beginValueOn() const { return ValueOnCIter(mValueMask.beginOn(), this); } + ValueOnIter beginValueOn() { return ValueOnIter(mValueMask.beginOn(), this); } + ValueOffCIter cbeginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } + ValueOffCIter beginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } + ValueOffIter beginValueOff() { return ValueOffIter(mValueMask.beginOff(), this); } + ValueAllCIter cbeginValueAll() const { return ValueAllCIter(mValueMask.beginDense(), this); } + ValueAllCIter beginValueAll() const { return ValueAllCIter(mValueMask.beginDense(), this); } + ValueAllIter beginValueAll() { return ValueAllIter(mValueMask.beginDense(), this); } + + ValueOnCIter cendValueOn() const { return ValueOnCIter(mValueMask.endOn(), this); } + ValueOnCIter endValueOn() const { return ValueOnCIter(mValueMask.endOn(), this); } + ValueOnIter endValueOn() { return ValueOnIter(mValueMask.endOn(), this); } + ValueOffCIter cendValueOff() const { return ValueOffCIter(mValueMask.endOff(), this); } + ValueOffCIter endValueOff() const { return ValueOffCIter(mValueMask.endOff(), this); } + ValueOffIter endValueOff() { return ValueOffIter(mValueMask.endOff(), this); } + ValueAllCIter cendValueAll() const { return ValueAllCIter(mValueMask.endDense(), this); } + ValueAllCIter endValueAll() const { return ValueAllCIter(mValueMask.endDense(), this); } + ValueAllIter endValueAll() { return ValueAllIter(mValueMask.endDense(), this); } + + // Note that [c]beginChildOn() and [c]beginChildOff() actually return end iterators, + // because leaf nodes have no children. + ChildOnCIter cbeginChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } + ChildOnCIter beginChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } + ChildOnIter beginChildOn() { return ChildOnIter(mValueMask.endOn(), this); } + ChildOffCIter cbeginChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } + ChildOffCIter beginChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } + ChildOffIter beginChildOff() { return ChildOffIter(mValueMask.endOff(), this); } + ChildAllCIter cbeginChildAll() const { return ChildAllCIter(mValueMask.beginDense(), this); } + ChildAllCIter beginChildAll() const { return ChildAllCIter(mValueMask.beginDense(), this); } + ChildAllIter beginChildAll() { return ChildAllIter(mValueMask.beginDense(), this); } + + ChildOnCIter cendChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } + ChildOnCIter endChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } + ChildOnIter endChildOn() { return ChildOnIter(mValueMask.endOn(), this); } + ChildOffCIter cendChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } + ChildOffCIter endChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } + ChildOffIter endChildOff() { return ChildOffIter(mValueMask.endOff(), this); } + ChildAllCIter cendChildAll() const { return ChildAllCIter(mValueMask.endDense(), this); } + ChildAllCIter endChildAll() const { return ChildAllCIter(mValueMask.endDense(), this); } + ChildAllIter endChildAll() { return ChildAllIter(mValueMask.endDense(), this); } + + // + // Mask accessors + // + bool isValueMaskOn(Index n) const { return mValueMask.isOn(n); } + bool isValueMaskOn() const { return mValueMask.isOn(); } + bool isValueMaskOff(Index n) const { return mValueMask.isOff(n); } + bool isValueMaskOff() const { return mValueMask.isOff(); } + const NodeMaskType& getValueMask() const { return mValueMask; } + NodeMaskType& getValueMask() { return mValueMask; } + void setValueMask(const NodeMaskType& mask) { mValueMask = mask; } + bool isChildMaskOn(Index) const { return false; } // leaf nodes have no children + bool isChildMaskOff(Index) const { return true; } + bool isChildMaskOff() const { return true; } +protected: + void setValueMask(Index n, bool on) { mValueMask.set(n, on); } + void setValueMaskOn(Index n) { mValueMask.setOn(n); } + void setValueMaskOff(Index n) { mValueMask.setOff(n); } + + /// Compute the origin of the leaf node that contains the voxel with the given coordinates. + static void evalNodeOrigin(Coord& xyz) { xyz &= ~(DIM - 1); } + + template + static inline void doVisit(NodeT&, VisitorOp&); + + template + static inline void doVisit2Node(NodeT& self, OtherNodeT& other, VisitorOp&); + + template + static inline void doVisit2(NodeT& self, OtherChildAllIterT&, VisitorOp&, bool otherIsLHS); + + + /// Bitmask that determines which voxels are active + NodeMaskType mValueMask; + /// Bitmask representing the values of voxels + Buffer mBuffer; + /// Global grid index coordinates (x,y,z) of the local origin of this node + Coord mOrigin; + + // These static declarations must be on separate lines to avoid VC9 compiler errors. + static const bool sOn; + static const bool sOff; + +private: + /// @brief During topology-only construction, access is needed + /// to protected/private members of other template instances. + template friend class LeafNode; + + friend struct ValueIter; + friend struct ValueIter; + friend struct ValueIter; + friend struct ValueIter; + friend struct ValueIter; + friend struct ValueIter; + + //@{ + /// Allow iterators to call mask accessor methods (see below). + /// @todo Make mask accessors public? + friend class IteratorBase; + friend class IteratorBase; + friend class IteratorBase; + //@} + +}; // class LeafNode + + +/// @internal For consistency with other nodes and with iterators, methods like +/// LeafNode::getValue() return a reference to a value. Since it's not possible +/// to return a reference to a bit in a node mask, we return a reference to one +/// of the following static values instead. +template const bool LeafNode::sOn = true; +template const bool LeafNode::sOff = false; + + +//////////////////////////////////////// + + +template +inline +LeafNode::LeafNode(): mOrigin(0, 0, 0) +{ +} + + +template +inline +LeafNode::LeafNode(const Coord& xyz, bool value, bool active): + mValueMask(active), + mBuffer(value), + mOrigin(xyz & (~(DIM - 1))) +{ +} + + +template +inline +LeafNode::LeafNode(const LeafNode &other): + mValueMask(other.mValueMask), + 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.getValueMask()), + 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.getValueMask()), + mBuffer(background), + mOrigin(other.origin()) +{ +} + + +template +template +inline +LeafNode::LeafNode(const LeafNode& other, TopologyCopy): + mValueMask(other.getValueMask()), + mBuffer(other.getValueMask()), // value = active state + mOrigin(other.origin()) +{ +} + + +template +template +inline +LeafNode::LeafNode(const LeafNode& other, + bool offValue, bool onValue, TopologyCopy): + mValueMask(other.getValueMask()), + mBuffer(other.getValueMask()), + mOrigin(other.origin()) +{ + if (offValue) { if (!onValue) mBuffer.mData.toggle(); else mBuffer.mData.setOn(); } +} + + +template +inline +LeafNode::~LeafNode() +{ +} + + +//////////////////////////////////////// + + +template +inline Index64 +LeafNode::memUsage() const +{ + return sizeof(mOrigin) + mValueMask.memUsage() + mBuffer.memUsage(); +} + + +template +inline void +LeafNode::evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels) const +{ + CoordBBox this_bbox = this->getNodeBoundingBox(); + if (bbox.isInside(this_bbox)) return;//this LeafNode is already enclosed in the bbox + if (ValueOnCIter iter = this->cbeginValueOn()) {//any active values? + if (visitVoxels) {//use voxel granularity? + this_bbox.reset(); + for(; iter; ++iter) this_bbox.expand(this->offsetToLocalCoord(iter.pos())); + this_bbox.translate(this->origin()); + } + bbox.expand(this_bbox); + } +} + + +template +template +inline bool +LeafNode::hasSameTopology(const LeafNode* other) const +{ + assert(other); + return (Log2Dim == OtherLog2Dim && mValueMask == other->getValueMask()); +} + + +template +inline std::string +LeafNode::str() const +{ + std::ostringstream ostr; + ostr << "LeafNode @" << mOrigin << ": "; + for (Index32 n = 0; n < SIZE; ++n) ostr << (mValueMask.isOn(n) ? '#' : '.'); + return ostr.str(); +} + + +//////////////////////////////////////// + + +template +inline Index +LeafNode::coordToOffset(const Coord& xyz) +{ + assert ((xyz[0] & (DIM-1u)) < DIM && (xyz[1] & (DIM-1u)) < DIM && (xyz[2] & (DIM-1u)) < DIM); + return ((xyz[0] & (DIM-1u)) << 2*Log2Dim) + + ((xyz[1] & (DIM-1u)) << Log2Dim) + + (xyz[2] & (DIM-1u)); +} + + +template +inline Coord +LeafNode::offsetToLocalCoord(Index n) +{ + assert(n < (1 << 3*Log2Dim)); + Coord xyz; + xyz.setX(n >> 2*Log2Dim); + n &= ((1 << 2*Log2Dim) - 1); + xyz.setY(n >> Log2Dim); + xyz.setZ(n & ((1 << Log2Dim) - 1)); + return xyz; +} + + +template +inline Coord +LeafNode::offsetToGlobalCoord(Index n) const +{ + return (this->offsetToLocalCoord(n) + this->origin()); +} + + +//////////////////////////////////////// + + +template +inline void +LeafNode::readTopology(std::istream& is, bool /*fromHalf*/) +{ + mValueMask.load(is); +} + + +template +inline void +LeafNode::writeTopology(std::ostream& os, bool /*toHalf*/) const +{ + mValueMask.save(os); +} + + +template +inline void +LeafNode::readBuffers(std::istream& is, bool /*fromHalf*/) +{ + // Read in the value mask. + mValueMask.load(is); + // Read in the origin. + is.read(reinterpret_cast(&mOrigin), sizeof(Coord::ValueType) * 3); + + if (io::getFormatVersion(is) >= OPENVDB_FILE_VERSION_BOOL_LEAF_OPTIMIZATION) { + // Read in the mask for the voxel values. + mBuffer.mData.load(is); + } else { + // Older files stored one or more bool arrays. + + // Read in the number of buffers, which should now always be one. + int8_t numBuffers = 0; + is.read(reinterpret_cast(&numBuffers), sizeof(int8_t)); + + // Read in the buffer. + // (Note: prior to the bool leaf optimization, buffers were always compressed.) + boost::shared_array buf(new bool[SIZE]); + io::readData(is, buf.get(), SIZE, /*isCompressed=*/true); + + // Transfer values to mBuffer. + mBuffer.mData.setOff(); + for (Index i = 0; i < SIZE; ++i) { + if (buf[i]) mBuffer.mData.setOn(i); + } + + if (numBuffers > 1) { + // Read in and discard auxiliary buffers that were created with + // earlier versions of the library. + for (int i = 1; i < numBuffers; ++i) { + io::readData(is, buf.get(), SIZE, /*isCompressed=*/true); + } + } + } +} + + +template +inline void +LeafNode::writeBuffers(std::ostream& os, bool /*toHalf*/) const +{ + // Write out the value mask. + mValueMask.save(os); + // Write out the origin. + os.write(reinterpret_cast(&mOrigin), sizeof(Coord::ValueType) * 3); + // Write out the voxel values. + mBuffer.mData.save(os); +} + + +//////////////////////////////////////// + + +template +inline bool +LeafNode::operator==(const LeafNode& other) const +{ + return (mValueMask == other.mValueMask && mBuffer.mData == other.mBuffer.mData); +} + + +template +inline bool +LeafNode::operator!=(const LeafNode& other) const +{ + return !(this->operator==(other)); +} + + +//////////////////////////////////////// + + +template +inline bool +LeafNode::isConstant(bool& constValue, bool& state, bool tolerance) const +{ + state = mValueMask.isOn(); + + if (!(state || mValueMask.isOff())) return false; + + // Note: if tolerance is true (i.e., 1), then all boolean values compare equal. + if (!tolerance && !(mBuffer.mData.isOn() || mBuffer.mData.isOff())) return false; + + state = mValueMask.isOn(); + constValue = mBuffer.mData.isOn(); + return true; +} + + +//////////////////////////////////////// + + +template +inline void +LeafNode::addTile(Index level, const Coord& xyz, bool val, bool active) +{ + assert(level == 0); + this->addTile(this->coordToOffset(xyz), val, active); +} + +template +inline void +LeafNode::addTile(Index offset, bool val, bool active) +{ + assert(offset < SIZE); + setValueOnly(offset, val); + setActiveState(offset, active); +} + +template +template +inline void +LeafNode::addTileAndCache(Index level, const Coord& xyz, + bool val, bool active, AccessorT&) +{ + this->addTile(level, xyz, val, active); +} + + +//////////////////////////////////////// + + +template +inline const bool& +LeafNode::getValue(const Coord& xyz) const +{ + // This *CANNOT* use operator ? because Visual C++ + if (mBuffer.mData.isOn(this->coordToOffset(xyz))) return sOn; else return sOff; +} + + +template +inline const bool& +LeafNode::getValue(Index offset) const +{ + assert(offset < SIZE); + // This *CANNOT* use operator ? for Windows + if (mBuffer.mData.isOn(offset)) return sOn; else return sOff; +} + + +template +inline bool +LeafNode::probeValue(const Coord& xyz, bool& val) const +{ + const Index offset = this->coordToOffset(xyz); + val = mBuffer.mData.isOn(offset); + return mValueMask.isOn(offset); +} + + +template +inline void +LeafNode::setValueOn(const Coord& xyz, bool val) +{ + this->setValueOn(this->coordToOffset(xyz), val); +} + + +template +inline void +LeafNode::setValueOn(Index offset, bool val) +{ + assert(offset < SIZE); + mValueMask.setOn(offset); + mBuffer.mData.set(offset, val); +} + + +template +inline void +LeafNode::setValueOnly(const Coord& xyz, bool val) +{ + this->setValueOnly(this->coordToOffset(xyz), val); +} + + +template +inline void +LeafNode::setActiveState(const Coord& xyz, bool on) +{ + mValueMask.set(this->coordToOffset(xyz), on); +} + + +template +inline void +LeafNode::setValueOff(const Coord& xyz, bool val) +{ + this->setValueOff(this->coordToOffset(xyz), val); +} + + +template +inline void +LeafNode::setValueOff(Index offset, bool val) +{ + assert(offset < SIZE); + mValueMask.setOff(offset); + mBuffer.mData.set(offset, val); +} + + +template +template +inline void +LeafNode::modifyValue(Index offset, const ModifyOp& op) +{ + bool val = mBuffer.mData.isOn(offset); + op(val); + mBuffer.mData.set(offset, val); + mValueMask.setOn(offset); +} + + +template +template +inline void +LeafNode::modifyValue(const Coord& xyz, const ModifyOp& op) +{ + this->modifyValue(this->coordToOffset(xyz), op); +} + + +template +template +inline void +LeafNode::modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) +{ + const Index offset = this->coordToOffset(xyz); + bool val = mBuffer.mData.isOn(offset), state = mValueMask.isOn(offset); + op(val, state); + mBuffer.mData.set(offset, val); + mValueMask.set(offset, state); +} + + +//////////////////////////////////////// + + +template +inline void +LeafNode::resetBackground(bool oldBackground, bool newBackground) +{ + if (newBackground != oldBackground) { + // Flip mBuffer's background bits and zero its foreground bits. + NodeMaskType bgMask = !(mBuffer.mData | mValueMask); + // Overwrite mBuffer's background bits, leaving its foreground bits intact. + mBuffer.mData = (mBuffer.mData & mValueMask) | bgMask; + } +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::merge(const LeafNode& other, bool /*bg*/, bool /*otherBG*/) +{ + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if (Policy == MERGE_NODES) return; + for (typename NodeMaskType::OnIterator iter = other.mValueMask.beginOn(); iter; ++iter) { + const Index n = iter.pos(); + if (mValueMask.isOff(n)) { + mBuffer.mData.set(n, other.mBuffer.mData.isOn(n)); + mValueMask.setOn(n); + } + } + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + +template +template +inline void +LeafNode::merge(bool tileValue, bool tileActive) +{ + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if (Policy != MERGE_ACTIVE_STATES_AND_NODES) return; + if (!tileActive) return; + // Replace all inactive values with the active tile value. + if (tileValue) mBuffer.mData |= !mValueMask; // -0=>1, +0=>0, -1=>1, +1=>1 (-,+ = off,on) + else mBuffer.mData &= mValueMask; // -0=>0, +0=>0, -1=>0, +1=>1 + mValueMask.setOn(); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::topologyUnion(const LeafNode& other) +{ + mValueMask |= other.getValueMask(); +} + + +template +template +inline void +LeafNode::topologyIntersection(const LeafNode& other, + const bool&) +{ + mValueMask &= other.getValueMask(); +} + + +template +template +inline void +LeafNode::topologyDifference(const LeafNode& other, + const bool&) +{ + mValueMask &= !other.getValueMask(); +} + + +//////////////////////////////////////// + + +template +inline void +LeafNode::fill(const CoordBBox& bbox, bool value, bool active) +{ + for (Int32 x = bbox.min().x(); x <= bbox.max().x(); ++x) { + const Index offsetX = (x & (DIM-1u))<<2*Log2Dim; + for (Int32 y = bbox.min().y(); y <= bbox.max().y(); ++y) { + const Index offsetXY = offsetX + ((y & (DIM-1u))<< Log2Dim); + for (Int32 z = bbox.min().z(); z <= bbox.max().z(); ++z) { + const Index offset = offsetXY + (z & (DIM-1u)); + mValueMask.set(offset, active); + mBuffer.mData.set(offset, value); + } + } + } +} + +template +inline void +LeafNode::fill(const bool& value) +{ + mBuffer.fill(value); +} + +template +inline void +LeafNode::fill(const bool& value, bool active) +{ + mBuffer.fill(value); + mValueMask.set(active); +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::copyToDense(const CoordBBox& bbox, DenseT& dense) const +{ + typedef typename DenseT::ValueType DenseValueType; + + const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); + const Coord& min = dense.bbox().min(); + DenseValueType* t0 = dense.data() + zStride * (bbox.min()[2] - min[2]); // target array + const Int32 n0 = bbox.min()[2] & (DIM-1u); + for (Int32 x = bbox.min()[0], ex = bbox.max()[0] + 1; x < ex; ++x) { + DenseValueType* t1 = t0 + xStride * (x - min[0]); + const Int32 n1 = n0 + ((x & (DIM-1u)) << 2*LOG2DIM); + for (Int32 y = bbox.min()[1], ey = bbox.max()[1] + 1; y < ey; ++y) { + DenseValueType* t2 = t1 + yStride * (y - min[1]); + Int32 n2 = n1 + ((y & (DIM-1u)) << LOG2DIM); + for (Int32 z = bbox.min()[2], ez = bbox.max()[2] + 1; z < ez; ++z, t2 += zStride) { + *t2 = DenseValueType(mBuffer.mData.isOn(n2++)); + } + } + } +} + + +template +template +inline void +LeafNode::copyFromDense(const CoordBBox& bbox, const DenseT& dense, + bool background, bool tolerance) +{ + typedef typename DenseT::ValueType DenseValueType; + + const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); + const Coord& min = dense.bbox().min(); + const DenseValueType* s0 = dense.data() + zStride * (bbox.min()[2] - min[2]); // source + const Int32 n0 = bbox.min()[2] & (DIM-1u); + for (Int32 x = bbox.min()[0], ex = bbox.max()[0] + 1; x < ex; ++x) { + const DenseValueType* s1 = s0 + xStride * (x - min[0]); + const Int32 n1 = n0 + ((x & (DIM-1u)) << 2*LOG2DIM); + for (Int32 y = bbox.min()[1], ey = bbox.max()[1] + 1; y < ey; ++y) { + const DenseValueType* s2 = s1 + yStride * (y - min[1]); + Int32 n2 = n1 + ((y & (DIM-1u)) << LOG2DIM); + for (Int32 z = bbox.min()[2], ez = bbox.max()[2]+1; z < ez; ++z, ++n2, s2 += zStride) { + // Note: if tolerance is true (i.e., 1), then all boolean values compare equal. + if (tolerance || background == bool(*s2)) { + mValueMask.setOff(n2); + mBuffer.mData.set(n2, background); + } else { + mValueMask.setOn(n2); + mBuffer.mData.set(n2, bool(*s2)); + } + } + } + } +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::combine(const LeafNode& other, CombineOp& op) +{ + CombineArgs args; + for (Index i = 0; i < SIZE; ++i) { + bool result = false, aVal = mBuffer.mData.isOn(i), bVal = other.mBuffer.mData.isOn(i); + op(args.setARef(aVal) + .setAIsActive(mValueMask.isOn(i)) + .setBRef(bVal) + .setBIsActive(other.mValueMask.isOn(i)) + .setResultRef(result)); + mValueMask.set(i, args.resultIsActive()); + mBuffer.mData.set(i, result); + } +} + + +template +template +inline void +LeafNode::combine(bool value, bool valueIsActive, CombineOp& op) +{ + CombineArgs args; + args.setBRef(value).setBIsActive(valueIsActive); + for (Index i = 0; i < SIZE; ++i) { + bool result = false, aVal = mBuffer.mData.isOn(i); + op(args.setARef(aVal) + .setAIsActive(mValueMask.isOn(i)) + .setResultRef(result)); + mValueMask.set(i, args.resultIsActive()); + mBuffer.mData.set(i, result); + } +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::combine2(const LeafNode& other, 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.mValueMask.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.mValueMask.isOn(i)) + .setResultRef(result)); + mValueMask.set(i, args.resultIsActive()); + mBuffer.mData.set(i, result); + } +} + + +template +template +inline void +LeafNode::combine2(const LeafNode& b0, const 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.mValueMask.isOn(i) || b1.mValueMask.isOn(i)); + + bool result = false, b0Val = b0.mBuffer.mData.isOn(i), b1Val = b1.mBuffer.mData.isOn(i); + op(args.setARef(b0Val) + .setAIsActive(b0.mValueMask.isOn(i)) + .setBRef(b1Val) + .setBIsActive(b1.mValueMask.isOn(i)) + .setResultRef(result)); + mValueMask.set(i, args.resultIsActive()); + mBuffer.mData.set(i, result); + } +} + + +//////////////////////////////////////// + +template +template +inline void +LeafNode::visitActiveBBox(BBoxOp& op) const +{ + if (op.template descent()) { + for (ValueOnCIter i=this->cbeginValueOn(); i; ++i) { +#ifdef _MSC_VER + op.operator()(CoordBBox::createCube(i.getCoord(), 1)); +#else + op.template operator()(CoordBBox::createCube(i.getCoord(), 1)); +#endif + } + } else { +#ifdef _MSC_VER + op.operator()(this->getNodeBoundingBox()); +#else + op.template operator()(this->getNodeBoundingBox()); +#endif + } +} + + +template +template +inline void +LeafNode::visit(VisitorOp& op) +{ + doVisit(*this, op); +} + + +template +template +inline void +LeafNode::visit(VisitorOp& op) const +{ + doVisit(*this, op); +} + + +template +template +inline void +LeafNode::doVisit(NodeT& self, VisitorOp& op) +{ + for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { + op(iter); + } +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::visit2Node(OtherLeafNodeType& other, VisitorOp& op) +{ + doVisit2Node(*this, other, op); +} + + +template +template +inline void +LeafNode::visit2Node(OtherLeafNodeType& other, VisitorOp& op) const +{ + doVisit2Node(*this, other, op); +} + + +template +template< + typename NodeT, + typename OtherNodeT, + typename VisitorOp, + typename ChildAllIterT, + typename OtherChildAllIterT> +inline void +LeafNode::doVisit2Node(NodeT& self, OtherNodeT& other, VisitorOp& op) +{ + // Allow the two nodes to have different ValueTypes, but not different dimensions. + BOOST_STATIC_ASSERT(OtherNodeT::SIZE == NodeT::SIZE); + BOOST_STATIC_ASSERT(OtherNodeT::LEVEL == NodeT::LEVEL); + + ChildAllIterT iter = self.beginChildAll(); + OtherChildAllIterT otherIter = other.beginChildAll(); + + for ( ; iter && otherIter; ++iter, ++otherIter) { + op(iter, otherIter); + } +} + + +//////////////////////////////////////// + + +template +template +inline void +LeafNode::visit2(IterT& otherIter, VisitorOp& op, bool otherIsLHS) +{ + doVisit2(*this, otherIter, op, otherIsLHS); +} + + +template +template +inline void +LeafNode::visit2(IterT& otherIter, VisitorOp& op, bool otherIsLHS) const +{ + doVisit2(*this, otherIter, op, otherIsLHS); +} + + +template +template< + typename NodeT, + typename VisitorOp, + typename ChildAllIterT, + typename OtherChildAllIterT> +inline void +LeafNode::doVisit2(NodeT& self, OtherChildAllIterT& otherIter, + VisitorOp& op, bool otherIsLHS) +{ + if (!otherIter) return; + + if (otherIsLHS) { + for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { + op(otherIter, iter); + } + } else { + for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { + op(iter, otherIter); + } + } +} + +} // namespace tree +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TREE_LEAFNODEBOOL_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tree/NodeUnion.h b/openvdb_2_3_0_library/openvdb/tree/NodeUnion.h new file mode 100755 index 0000000..c9f8b5a --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tree/NodeUnion.h @@ -0,0 +1,137 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file NodeUnion.h +/// +/// @author Peter Cucka +/// +/// NodeUnion is a templated helper class that controls access to either +/// the child node pointer or the value for a particular element of a root +/// or internal node. For space efficiency, the child pointer and the value +/// are unioned, since the two are never in use simultaneously. +/// Template specializations of NodeUnion allow for values of either POD +/// (int, float, pointer, etc.) or class (std::string, math::Vec, etc.) types. +/// (The latter cannot be stored directly in a union.) + +#ifndef OPENVDB_TREE_NODEUNION_HAS_BEEN_INCLUDED +#define OPENVDB_TREE_NODEUNION_HAS_BEEN_INCLUDED + +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tree { + +// Internal implementation of a union of a child node pointer and a value +template class NodeUnionImpl; + + +// Partial specialization for values of non-class types +// (int, float, pointer, etc.) that stores elements by value +template +class NodeUnionImpl +{ +private: + union { ChildT* child; ValueT value; } mUnion; + +public: + NodeUnionImpl() { setChild(NULL); } + + ChildT* getChild() const { return mUnion.child; } + const ValueT& getValue() const { return mUnion.value; } + ValueT& getValue() { return mUnion.value; } + void setChild(ChildT* child) { mUnion.child = child; } + void setValue(const ValueT& val) { mUnion.value = val; } +}; + + +// Partial specialization for values of class types (std::string, +// math::Vec, etc.) that stores elements by pointer +template +class NodeUnionImpl +{ +private: + union { ChildT* child; ValueT* value; } mUnion; + bool mHasChild; + +public: + NodeUnionImpl(): mHasChild(true) { setChild(NULL); } + NodeUnionImpl(const NodeUnionImpl& other) + { + if (other.mHasChild) setChild(other.getChild()); + else setValue(other.getValue()); + } + NodeUnionImpl& operator=(const NodeUnionImpl& other) + { + if (other.mHasChild) setChild(other.getChild()); + else setValue(other.getValue()); + } + ~NodeUnionImpl() { setChild(NULL); } + + ChildT* getChild() const + { return mHasChild ? mUnion.child : NULL; } + void setChild(ChildT* child) + { + if (!mHasChild) delete mUnion.value; + mUnion.child = child; + mHasChild = true; + } + + const ValueT& getValue() const { return *mUnion.value; } + ValueT& getValue() { return *mUnion.value; } + void setValue(const ValueT& val) + { + /// @todo To minimize storage across nodes, intern and reuse + /// common values, using, e.g., boost::flyweight. + if (!mHasChild) delete mUnion.value; + mUnion.value = new ValueT(val); + mHasChild = false; + } +}; + + +template +struct NodeUnion: public NodeUnionImpl< + boost::is_class::value, ValueT, ChildT> +{ + NodeUnion() {} +}; + +} // namespace tree +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TREE_NODEUNION_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tree/RootNode.h b/openvdb_2_3_0_library/openvdb/tree/RootNode.h new file mode 100755 index 0000000..cd68c75 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tree/RootNode.h @@ -0,0 +1,3263 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +/// +/// @file RootNode.h +/// +/// @brief The root node of an OpenVDB tree + +#ifndef OPENVDB_TREE_ROOTNODE_HAS_BEEN_INCLUDED +#define OPENVDB_TREE_ROOTNODE_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include //for boost::mpl::vector +#include +#include +#include +#include +#include +#include // for truncateRealToHalf() +#include // for isZero(), isExactlyEqual(), etc. +#include +#include // for backward compatibility only (see readTopology()) +#include +#include "Util.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tree { + +// Forward declarations +template struct NodeChain; +template struct SameRootConfig; +template struct RootNodeCopyHelper; +template struct RootNodeCombineHelper; + + +template +class RootNode +{ +public: + typedef ChildType ChildNodeType; + typedef typename ChildType::LeafNodeType LeafNodeType; + typedef typename ChildType::ValueType ValueType; + + static const Index LEVEL = 1 + ChildType::LEVEL; // level 0 = leaf + + /// NodeChainType is a list of this tree's node types, from LeafNodeType to RootNode. + typedef typename NodeChain::Type NodeChainType; + BOOST_STATIC_ASSERT(boost::mpl::size::value == LEVEL + 1); + + /// @brief ValueConverter::Type is the type of a RootNode having the same + /// child hierarchy as this node but a different value type, T. + template + struct ValueConverter { + typedef RootNode::Type> Type; + }; + + /// @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->clearTable(); } + +private: + struct Tile { + Tile(): value(zeroVal()), active(false) {} + Tile(const ValueType& v, bool b): value(v), active(b) {} + ValueType value; + bool active; + }; + + // This lightweight struct pairs child pointers and tiles. + struct NodeStruct { + ChildType* child; + Tile tile; + + NodeStruct(): child(NULL) {} + NodeStruct(ChildType& c): child(&c) {} + NodeStruct(const Tile& t): child(NULL), tile(t) {} + ~NodeStruct() {} ///< @note doesn't delete child + + bool isChild() const { return child != NULL; } + bool isTile() const { return child == NULL; } + bool isTileOff() const { return isTile() && !tile.active; } + bool isTileOn() const { return isTile() && tile.active; } + + void set(ChildType& c) { delete child; child = &c; } + void set(const Tile& t) { delete child; child = NULL; tile = t; } + ChildType& steal(const Tile& t) { ChildType* c = child; child = NULL; tile = t; return *c; } + }; + + typedef std::map MapType; + typedef typename MapType::iterator MapIter; + typedef typename MapType::const_iterator MapCIter; + + typedef std::set CoordSet; + typedef typename CoordSet::iterator CoordSetIter; + typedef typename CoordSet::const_iterator CoordSetCIter; + + static void setTile(const MapIter& i, const Tile& t) { i->second.set(t); } + static void setChild(const MapIter& i, ChildType& c) { i->second.set(c); } + static Tile& getTile(const MapIter& i) { return i->second.tile; } + static const Tile& getTile(const MapCIter& i) { return i->second.tile; } + static ChildType& getChild(const MapIter& i) { return *(i->second.child); } + static const ChildType& getChild(const MapCIter& i) { return *(i->second.child); } + static ChildType& stealChild(const MapIter& i, const Tile& t) {return i->second.steal(t);} + static const ChildType& stealChild(const MapCIter& i,const Tile& t) {return i->second.steal(t);} + + static bool isChild(const MapCIter& i) { return i->second.isChild(); } + static bool isChild(const MapIter& i) { return i->second.isChild(); } + static bool isTile(const MapCIter& i) { return i->second.isTile(); } + static bool isTile(const MapIter& i) { return i->second.isTile(); } + static bool isTileOff(const MapCIter& i) { return i->second.isTileOff(); } + static bool isTileOff(const MapIter& i) { return i->second.isTileOff(); } + static bool isTileOn(const MapCIter& i) { return i->second.isTileOn(); } + static bool isTileOn(const MapIter& i) { return i->second.isTileOn(); } + + struct NullPred { + static inline bool test(const MapIter&) { return true; } + static inline bool test(const MapCIter&) { return true; } + }; + struct ValueOnPred { + static inline bool test(const MapIter& i) { return isTileOn(i); } + static inline bool test(const MapCIter& i) { return isTileOn(i); } + }; + struct ValueOffPred { + static inline bool test(const MapIter& i) { return isTileOff(i); } + static inline bool test(const MapCIter& i) { return isTileOff(i); } + }; + struct ValueAllPred { + static inline bool test(const MapIter& i) { return isTile(i); } + static inline bool test(const MapCIter& i) { return isTile(i); } + }; + struct ChildOnPred { + static inline bool test(const MapIter& i) { return isChild(i); } + static inline bool test(const MapCIter& i) { return isChild(i); } + }; + struct ChildOffPred { + static inline bool test(const MapIter& i) { return isTile(i); } + static inline bool test(const MapCIter& i) { return isTile(i); } + }; + + template + class BaseIter + { + public: + typedef _RootNodeT RootNodeT; + typedef _MapIterT MapIterT; // either MapIter or MapCIter + + bool operator==(const BaseIter& other) const + { + return (mParentNode == other.mParentNode) && (mIter == other.mIter); + } + bool operator!=(const BaseIter& other) const { return !(*this == other); } + + RootNodeT* getParentNode() const { return mParentNode; } + /// Return a reference to the node over which this iterator iterates. + RootNodeT& parent() const + { + if (!mParentNode) OPENVDB_THROW(ValueError, "iterator references a null parent node"); + return *mParentNode; + } + + bool test() const { assert(mParentNode); return mIter != mParentNode->mTable.end(); } + operator bool() const { return this->test(); } + + void increment() { ++mIter; this->skip(); } + bool next() { this->increment(); return this->test(); } + void increment(Index n) { for (int i = 0; i < n && this->next(); ++i) {} } + + /// @brief Return this iterator's position as an offset from + /// the beginning of the parent node's map. + Index pos() const + { + return !mParentNode ? 0U : Index(std::distance(mParentNode->mTable.begin(), mIter)); + } + + bool isValueOn() const { return RootNodeT::isTileOn(mIter); } + bool isValueOff() const { return RootNodeT::isTileOff(mIter); } + void setValueOn(bool on = true) const { mIter->second.tile.active = on; } + void setValueOff() const { mIter->second.tile.active = false; } + + /// Return the coordinates of the item to which this iterator is pointing. + Coord getCoord() const { return mIter->first; } + /// Return in @a xyz the coordinates of the item to which this iterator is pointing. + void getCoord(Coord& xyz) const { xyz = this->getCoord(); } + + protected: + BaseIter(): mParentNode(NULL) {} + BaseIter(RootNodeT& parent, const MapIterT& iter): mParentNode(&parent), mIter(iter) {} + + void skip() { while (this->test() && !FilterPredT::test(mIter)) ++mIter; } + + RootNodeT* mParentNode; + MapIterT mIter; + }; // BaseIter + + template + class ChildIter: public BaseIter + { + public: + typedef BaseIter BaseT; + typedef RootNodeT NodeType; + typedef NodeType ValueType; + typedef ChildNodeT ChildNodeType; + typedef typename boost::remove_const::type NonConstNodeType; + typedef typename boost::remove_const::type NonConstValueType; + typedef typename boost::remove_const::type NonConstChildNodeType; + using BaseT::mIter; + + ChildIter() {} + ChildIter(RootNodeT& parent, const MapIterT& iter): BaseT(parent, iter) { BaseT::skip(); } + + ChildIter& operator++() { BaseT::increment(); return *this; } + + ChildNodeT& getValue() const { return getChild(mIter); } + ChildNodeT& operator*() const { return this->getValue(); } + ChildNodeT* operator->() const { return &this->getValue(); } + }; // ChildIter + + template + class ValueIter: public BaseIter + { + public: + typedef BaseIter BaseT; + typedef RootNodeT NodeType; + typedef ValueT ValueType; + typedef typename boost::remove_const::type NonConstNodeType; + typedef typename boost::remove_const::type NonConstValueType; + using BaseT::mIter; + + ValueIter() {} + ValueIter(RootNodeT& parent, const MapIterT& iter): BaseT(parent, iter) { BaseT::skip(); } + + ValueIter& operator++() { BaseT::increment(); return *this; } + + ValueT& getValue() const { return getTile(mIter).value; } + ValueT& operator*() const { return this->getValue(); } + ValueT* operator->() const { return &(this->getValue()); } + + void setValue(const ValueT& v) const { assert(isTile(mIter)); getTile(mIter).value = v; } + + template + void modifyValue(const ModifyOp& op) const + { + assert(isTile(mIter)); + op(getTile(mIter).value); + } + }; // ValueIter + + template + class DenseIter: public BaseIter + { + public: + typedef BaseIter BaseT; + typedef RootNodeT NodeType; + typedef ValueT ValueType; + typedef ChildNodeT ChildNodeType; + typedef typename boost::remove_const::type NonConstNodeType; + typedef typename boost::remove_const::type NonConstValueType; + typedef typename boost::remove_const::type NonConstChildNodeType; + using BaseT::mIter; + + DenseIter() {} + DenseIter(RootNodeT& parent, const MapIterT& iter): BaseT(parent, iter) {} + + DenseIter& operator++() { BaseT::increment(); return *this; } + + bool isChildNode() const { return isChild(mIter); } + + ChildNodeT* probeChild(NonConstValueType& value) const + { + if (isChild(mIter)) return &getChild(mIter); + value = getTile(mIter).value; + return NULL; + } + bool probeChild(ChildNodeT*& child, NonConstValueType& value) const + { + child = this->probeChild(value); + return child != NULL; + } + bool probeValue(NonConstValueType& value) const { return !this->probeChild(value); } + + void setChild(ChildNodeT& c) const { RootNodeT::setChild(mIter, c); } + void setChild(ChildNodeT* c) const { assert(c != NULL); RootNodeT::setChild(mIter, *c); } + void setValue(const ValueT& v) const + { + if (isTile(mIter)) getTile(mIter).value = v; + /// @internal For consistency with iterators for other node types + /// (see, e.g., InternalNode::DenseIter::unsetItem()), we don't call + /// setTile() here, because that would also delete the child. + else stealChild(mIter, Tile(v, /*active=*/true)); + } + }; // DenseIter + +public: + typedef ChildIter ChildOnIter; + typedef ChildIter ChildOnCIter; + typedef ValueIter ChildOffIter; + typedef ValueIter ChildOffCIter; + typedef DenseIter ChildAllIter; + typedef DenseIter ChildAllCIter; + + typedef ValueIter ValueOnIter; + typedef ValueIter ValueOnCIter; + typedef ValueIter ValueOffIter; + typedef ValueIter ValueOffCIter; + typedef ValueIter ValueAllIter; + typedef ValueIter ValueAllCIter; + + + ChildOnCIter cbeginChildOn() const { return ChildOnCIter(*this, mTable.begin()); } + ChildOffCIter cbeginChildOff() const { return ChildOffCIter(*this, mTable.begin()); } + ChildAllCIter cbeginChildAll() const { return ChildAllCIter(*this, mTable.begin()); } + ChildOnCIter beginChildOn() const { return cbeginChildOn(); } + ChildOffCIter beginChildOff() const { return cbeginChildOff(); } + ChildAllCIter beginChildAll() const { return cbeginChildAll(); } + ChildOnIter beginChildOn() { return ChildOnIter(*this, mTable.begin()); } + ChildOffIter beginChildOff() { return ChildOffIter(*this, mTable.begin()); } + ChildAllIter beginChildAll() { return ChildAllIter(*this, mTable.begin()); } + + ValueOnCIter cbeginValueOn() const { return ValueOnCIter(*this, mTable.begin()); } + ValueOffCIter cbeginValueOff() const { return ValueOffCIter(*this, mTable.begin()); } + ValueAllCIter cbeginValueAll() const { return ValueAllCIter(*this, mTable.begin()); } + ValueOnCIter beginValueOn() const { return cbeginValueOn(); } + ValueOffCIter beginValueOff() const { return cbeginValueOff(); } + ValueAllCIter beginValueAll() const { return cbeginValueAll(); } + ValueOnIter beginValueOn() { return ValueOnIter(*this, mTable.begin()); } + ValueOffIter beginValueOff() { return ValueOffIter(*this, mTable.begin()); } + ValueAllIter beginValueAll() { return ValueAllIter(*this, mTable.begin()); } + + /// Return the total amount of memory in bytes occupied by this node and its children. + Index64 memUsage() const; + + /// @brief Expand the specified bbox so it includes the active tiles of + /// this root node as well as all the active values in its child + /// nodes. If visitVoxels is false LeafNodes will be approximated + /// as dense, i.e. with all voxels active. Else the individual + /// active voxels are visited to produce a tight bbox. + void evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels = true) const; + + /// Return the bounding box of this RootNode, i.e., an infinite bounding box. + static CoordBBox getNodeBoundingBox() { return CoordBBox::inf(); } + + /// @brief Change inactive tiles or voxels with a value equal to +/- the + /// old background to the specified value (with the same sign). Active values + /// are unchanged. + /// @param value The new background value + /// @param updateChildNodes If true (which is the default) the + /// background values of the child nodes is also updated. Else + /// only the background value stored in the RootNode itself is changed. + void setBackground(const ValueType& value, bool updateChildNodes = true); + + /// Return this node's background value. + const ValueType& background() const { return mBackground; } + + /// Return @c true if the given tile is inactive and has the background value. + bool isBackgroundTile(const Tile&) const; + //@{ + /// Return @c true if the given iterator points to an inactive tile with the background value. + bool isBackgroundTile(const MapIter&) const; + bool isBackgroundTile(const MapCIter&) const; + //@} + + /// Return the number of background tiles. + size_t numBackgroundTiles() const; + /// @brief Remove all background tiles. + /// @return the number of tiles removed. + size_t eraseBackgroundTiles(); + void clear() { this->clearTable(); } + + /// Return @c true if this node's table is either empty or contains only background tiles. + bool empty() const { return mTable.size() == numBackgroundTiles(); } + + /// @brief Expand this node's table so that (x, y, z) is included in the index range. + /// @return @c true if an expansion was performed (i.e., if (x, y, z) was not already + /// included in the index range). + bool expand(const Coord& xyz); + + static Index getLevel() { return LEVEL; } + static void getNodeLog2Dims(std::vector& dims); + static Index getChildDim() { return ChildType::DIM; } + + /// Return the number of entries in this node's table. + Index getTableSize() const { return mTable.size(); } + + Index getWidth() const { return this->getMaxIndex()[0] - this->getMinIndex()[0]; } + Index getHeight() const { return this->getMaxIndex()[1] - this->getMinIndex()[1]; } + Index getDepth() const { return this->getMaxIndex()[2] - this->getMinIndex()[2]; } + + /// Return the smallest index of the current tree. + Coord getMinIndex() const; + /// Return the largest index of the current tree. + Coord getMaxIndex() const; + /// Return the current index range. Both min and max are inclusive. + void getIndexRange(CoordBBox& bbox) const; + + /// @brief Return @c true if the given tree has the same node and active value + /// topology as this tree (but possibly a different @c ValueType). + template + bool hasSameTopology(const RootNode& other) const; + + /// Return @c false if the other node's dimensions don't match this node's. + template + static bool hasSameConfiguration(const RootNode& other); + + /// 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; + + bool hasActiveTiles() const; + + const ValueType& getValue(const Coord& xyz) const; + bool probeValue(const Coord& xyz, ValueType& value) const; + + /// @brief Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides. + /// @details If (x, y, z) isn't explicitly represented in the tree (i.e., + /// it is implicitly a background voxel), return -1. + int getValueDepth(const Coord& xyz) const; + + /// Set the active state of the voxel at the given coordinates but don't change its value. + void setActiveState(const Coord& xyz, bool on); + /// Set the value of the voxel at the given coordinates but don't change its active state. + void setValueOnly(const Coord& xyz, const ValueType& value); + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValueOn(const Coord& xyz, const ValueType& value); + /// Mark the voxel at the given coordinates as inactive but don't change its value. + void setValueOff(const Coord& xyz); + /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. + void setValueOff(const Coord& xyz, const ValueType& value); + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + template + void modifyValue(const Coord& xyz, const ModifyOp& op); + /// Apply a functor to the voxel at the given coordinates. + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op); + + /// @brief Set all voxels within a given box to a constant value, if necessary + /// subdividing tiles that intersect the box. + /// @param bbox inclusive coordinates of opposite corners of an axis-aligned box + /// @param value the value to which to set voxels within the box + /// @param active if true, mark voxels within the box as active, + /// otherwise mark them as inactive + void fill(const CoordBBox& bbox, const ValueType& value, bool active = true); + + /// @brief Copy into a dense grid the values of all voxels, both active and inactive, + /// that intersect a given bounding box. + /// @param bbox inclusive bounding box of the voxels to be copied into the dense grid + /// @param dense dense grid with a stride in @e z of one (see tools::Dense + /// in tools/Dense.h for the required API) + template + void copyToDense(const CoordBBox& bbox, DenseT& dense) const; + + + // + // I/O + // + bool writeTopology(std::ostream&, bool toHalf = false) const; + bool readTopology(std::istream&, bool fromHalf = false); + + void writeBuffers(std::ostream&, bool toHalf = false) const; + void readBuffers(std::istream&, bool fromHalf = false); + + + // + // Voxel access + // + /// Return the value of the voxel at the given coordinates and, if necessary, update + /// the accessor with pointers to the nodes along the path from the root node to + /// the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + const ValueType& getValueAndCache(const Coord& xyz, AccessorT&) const; + /// Return @c true if the voxel at the given coordinates is active and, if necessary, + /// update the accessor with pointers to the nodes along the path from the root node + /// to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + bool isValueOnAndCache(const Coord& xyz, AccessorT&) const; + + /// Change the value of the voxel at the given coordinates and mark it as active. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void setValueAndCache(const Coord& xyz, const ValueType& value, AccessorT&); + + /// Set the value of the voxel at the given coordinates without changing its active state. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void setValueOnlyAndCache(const Coord& xyz, const ValueType& value, AccessorT&); + + /// Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); + + /// Apply a functor to the voxel at the given coordinates. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void modifyValueAndActiveStateAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); + + /// Change the value of the voxel at the given coordinates and mark it as inactive. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT&); + + /// Set the active state of the voxel at the given coordinates without changing its value. + /// If necessary, update the accessor with pointers to the nodes along the path + /// from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + void setActiveStateAndCache(const Coord& xyz, bool on, AccessorT&); + + /// Return, in @a value, the value of the voxel at the given coordinates and, + /// if necessary, update the accessor with pointers to the nodes along + /// the path from the root node to the node containing the voxel. + /// @return @c true if the voxel at the given coordinates is active + /// @note Used internally by ValueAccessor. + template + bool probeValueAndCache(const Coord& xyz, ValueType& value, AccessorT&) const; + + /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides. + /// If (x, y, z) isn't explicitly represented in the tree (i.e., it is implicitly + /// a background voxel), return -1. If necessary, update the accessor with pointers + /// to the nodes along the path from the root node to the node containing the voxel. + /// @note Used internally by ValueAccessor. + template + int getValueDepthAndCache(const Coord& xyz, AccessorT&) const; + + /// Call the @c PruneOp functor for each child node and, if the functor + /// returns @c true, prune the node and replace it with a tile. + /// + /// This method is used to implement all of the various pruning algorithms + /// (prune(), pruneInactive(), etc.). It should rarely be called directly. + /// @see openvdb/tree/Util.h for the definition of the @c PruneOp functor + template void pruneOp(PruneOp&); + + /// @brief Reduce the memory footprint of this tree by replacing with tiles + /// any nodes whose values are all the same (optionally to within a tolerance) + /// and have the same active state. + void prune(const ValueType& tolerance = zeroVal()); + + /// @brief Reduce the memory footprint of this tree by replacing with + /// tiles of the given value any nodes whose values are all inactive. + void pruneInactive(const ValueType&); + + /// @brief Reduce the memory footprint of this tree by replacing with + /// background tiles any nodes whose values are all inactive. + void pruneInactive(); + + /// @brief Reduce the memory footprint of this tree by replacing with tiles + /// any non-leaf nodes whose values are all the same (optionally to within a tolerance) + /// and have the same active state. + void pruneTiles(const ValueType& tolerance); + + /// @brief Add the given leaf node to this tree, creating a new branch if necessary. + /// If a leaf node with the same origin already exists, replace it. + void addLeaf(LeafNodeType* leaf); + + /// @brief Same as addLeaf() but, if necessary, update the given accessor with pointers + /// to the nodes along the path from the root node to the node containing the coordinate. + template + void addLeafAndCache(LeafNodeType* leaf, AccessorT&); + + /// @brief Return a pointer to the node of type @c NodeT that contains voxel (x, y, z) + /// and replace it with a tile of the specified value and state. + /// If no such node exists, leave the tree unchanged and return @c NULL. + /// + /// @note The caller takes ownership of the node and is responsible for deleting it. + /// + /// @warning Since this method potentially removes nodes and branches of the tree, + /// it is important to clear the caches of all ValueAccessors associated with this tree. + template + NodeT* stealNode(const Coord& xyz, const ValueType& value, bool state); + + /// @brief Add a tile containing voxel (x, y, z) at the specified tree level, + /// creating a new branch if necessary. Delete any existing lower-level nodes + /// that contain (x, y, z). + void addTile(Index level, const Coord& xyz, const ValueType& value, bool state); + + /// @brief Same as addTile() but, if necessary, update the given accessor with pointers + /// to the nodes along the path from the root node to the node containing the coordinate. + template + void addTileAndCache(Index level, const Coord& xyz, const ValueType&, bool state, AccessorT&); + + /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). + /// If no such node exists, create one that preserves the values and + /// active states of all voxels. + /// @details Use this method to preallocate a static tree topology + /// over which to safely perform multithreaded processing. + LeafNodeType* touchLeaf(const Coord& xyz); + + /// @brief Same as touchLeaf() but, if necessary, update the given accessor with pointers + /// to the nodes along the path from the root node to the node containing the coordinate. + template + LeafNodeType* touchLeafAndCache(const Coord& xyz, AccessorT& acc); + + //@{ + /// @brief Return a pointer to the node that contains voxel (x, y, z). + /// If no such node exists, return NULL. + template + NodeT* probeNode(const Coord& xyz); + template + const NodeT* probeConstNode(const Coord& xyz) const; + //@} + + //@{ + /// @brief Same as probeNode() but, if necessary, update the given accessor with pointers + /// to the nodes along the path from the root node to the node containing the coordinate. + template + NodeT* probeNodeAndCache(const Coord& xyz, AccessorT& acc); + template + const NodeT* probeConstNodeAndCache(const Coord& xyz, AccessorT& acc) const; + //@} + + //@{ + /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). + /// If no such node exists, return NULL. + LeafNodeType* probeLeaf(const Coord& xyz); + const LeafNodeType* probeConstLeaf(const Coord& xyz) const; + const LeafNodeType* probeLeaf(const Coord& xyz) const; + //@} + + //@{ + /// @brief Same as probeLeaf() but, if necessary, update the given accessor with pointers + /// to the nodes along the path from the root node to the node containing the coordinate. + template + LeafNodeType* probeLeafAndCache(const Coord& xyz, AccessorT& acc); + template + const LeafNodeType* probeConstLeafAndCache(const Coord& xyz, AccessorT& acc) const; + template + const LeafNodeType* probeLeafAndCache(const Coord& xyz, AccessorT& acc) const; + //@} + + + // + // Aux methods + // + /// @brief Set the values of all inactive voxels and tiles of a narrow-band + /// level set from the signs of the active voxels, setting outside values to + /// +background and inside values to -background. + /// @warning This method should only be used on closed, narrow-band level sets. + void signedFloodFill(); + + /// @brief Set the values of all inactive voxels and tiles of a narrow-band + /// level set from the signs of the active voxels, setting exterior values to + /// @a outside and interior values to @a inside. Set the background value + /// of this tree to @a outside. + /// @warning This method should only be used on closed, narrow-band level sets. + void signedFloodFill(const ValueType& outside, const ValueType& inside); + + /// Densify active tiles, i.e., replace them with leaf-level active voxels. + void voxelizeActiveTiles(); + + /// @brief Efficiently merge another tree into this tree using one of several schemes. + /// @details This operation is primarily intended to combine trees that are mostly + /// non-overlapping (for example, intermediate trees from computations that are + /// parallelized across disjoint regions of space). + /// @note This operation is not guaranteed to produce an optimally sparse tree. + /// Follow merge() with prune() for optimal sparseness. + /// @warning This operation always empties the other tree. + template void merge(RootNode& other); + + /// @brief Union this tree's set of active values with the active values + /// of the other tree, whose @c ValueType may be different. + /// @details The resulting state of a value is active if the corresponding value + /// was already active OR if it is active in the other tree. Also, a resulting + /// value maps to a voxel if the corresponding value already mapped to a voxel + /// OR if it is a voxel in the other tree. Thus, a resulting value can only + /// map to a tile if the corresponding value already mapped to a tile + /// AND if it is a tile value in other tree. + /// + /// @note This operation modifies only active states, not values. + /// Specifically, active tiles and voxels in this tree are not changed, and + /// tiles or voxels that were inactive in this tree but active in the other tree + /// are marked as active in this tree but left with their original values. + template + void topologyUnion(const RootNode& other); + + /// @brief Intersects this tree's set of active values with the active values + /// of the other tree, whose @c ValueType may be different. + /// @details The resulting state of a value is active only if the corresponding + /// value was already active AND if it is active in the other tree. Also, a + /// resulting value maps to a voxel if the corresponding value + /// already mapped to an active voxel in either of the two grids + /// and it maps to an active tile or voxel in the other grid. + /// + /// @note This operation can delete branches in this grid if they + /// overlap with inactive tiles in the other grid. Likewise active + /// voxels can be turned into inactive voxels resulting in leaf + /// nodes with no active values. Thus, it is recommended to + /// subsequently call prune. + template + void topologyIntersection(const RootNode& other); + + /// @brief Difference this tree's set of active values with the active values + /// of the other tree, whose @c ValueType may be different. So a + /// resulting voxel will be active only if the original voxel is + /// active in this tree and inactive in the other tree. + /// + /// @note This operation can delete branches in this grid if they + /// overlap with active tiles in the other grid. Likewise active + /// voxels can be turned into inactive voxels resulting in leaf + /// nodes with no active values. Thus, it is recommended to + /// subsequently call prune. + template + void topologyDifference(const RootNode& other); + + template + void combine(RootNode& other, CombineOp&, bool prune = false); + + template + void combine2(const RootNode& other0, const 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() {} + inline void clearTable(); + //@{ + /// @internal Used by doVisit2(). + void resetTable(MapType& table) { mTable.swap(table); table.clear(); } + void resetTable(const MapType&) const {} + //@} + + Index getChildCount() const; + Index getTileCount() const; + Index getActiveTileCount() const; + Index getInactiveTileCount() const; + + /// Return a MapType key for the given coordinates. + static Coord coordToKey(const Coord& xyz) { return xyz & ~(ChildType::DIM - 1); } + + /// Insert this node's mTable keys into the given set. + void insertKeys(CoordSet&) const; + + /// Return @c true if this node's mTable contains the given key. + bool hasKey(const Coord& key) const { return mTable.find(key) != mTable.end(); } + //@{ + /// @brief Look up the given key in this node's mTable. + /// @return an iterator pointing to the matching mTable entry or to mTable.end(). + MapIter findKey(const Coord& key) { return mTable.find(key); } + MapCIter findKey(const Coord& key) const { return mTable.find(key); } + //@} + //@{ + /// @brief Convert the given coordinates to a key and look the key up in this node's mTable. + /// @return an iterator pointing to the matching mTable entry or to mTable.end(). + MapIter findCoord(const Coord& xyz) { return mTable.find(coordToKey(xyz)); } + MapCIter findCoord(const Coord& xyz) const { return mTable.find(coordToKey(xyz)); } + //@} + /// @brief Convert the given coordinates to a key and look the key up in this node's mTable. + /// @details If the key is not found, insert a background tile with that key. + /// @return an iterator pointing to the matching mTable entry. + MapIter findOrAddCoord(const Coord& xyz); + + /// @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 { + typedef typename NodeChain::Type SubtreeT; + typedef typename boost::mpl::push_back::type Type; +}; + +/// Specialization to terminate NodeChain +template +struct NodeChain { + typedef typename boost::mpl::vector::type Type; +}; + + +//////////////////////////////////////// + + +//@{ +/// 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) +{ + typedef RootNode OtherRootT; + + enforceSameConfiguration(other); + + const Tile bgTile(backgd, /*active=*/false), fgTile(foregd, true); + this->initTable(); + + for (typename OtherRootT::MapCIter i=other.mTable.begin(), e=other.mTable.end(); i != e; ++i) { + mTable[i->first] = OtherRootT::isTile(i) + ? NodeStruct(OtherRootT::isTileOn(i) ? fgTile : bgTile) + : NodeStruct(*(new ChildT(OtherRootT::getChild(i), backgd, foregd, TopologyCopy()))); + } +} + + +template +template +inline +RootNode::RootNode(const RootNode& other, + const ValueType& backgd, TopologyCopy): + mBackground(backgd) +{ + typedef RootNode OtherRootT; + + 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) + { + typedef typename RootT::ValueType ValueT; + typedef typename RootT::ChildNodeType ChildT; + typedef typename RootT::NodeStruct NodeStruct; + typedef typename RootT::Tile Tile; + typedef typename OtherRootT::ValueType OtherValueT; + typedef typename OtherRootT::ChildNodeType OtherChildT; + typedef typename OtherRootT::MapCIter OtherMapCIter; + typedef typename OtherRootT::Tile OtherTile; + + 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.clearTable(); + 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->clearTable(); + this->initTable(); + + for (MapCIter i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { + mTable[i->first] = + isTile(i) ? NodeStruct(getTile(i)) : NodeStruct(*(new ChildT(getChild(i)))); + } + } + return *this; +} + +// Overload for root nodes of different types +template +template +inline RootNode& +RootNode::operator=(const RootNode& other) +{ + typedef RootNode OtherRootT; + typedef typename OtherRootT::ValueType OtherValueT; + 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 +{ + typedef RootNode OtherRootT; + typedef typename OtherRootT::MapType OtherMapT; + typedef typename OtherRootT::MapIter OtherIterT; + typedef typename OtherRootT::MapCIter OtherCIterT; + + if (!hasSameConfiguration(other)) return false; + + // Create a local copy of the other node's table. + OtherMapT copyOfOtherTable = other.mTable; + + // For each entry in this node's table... + for (MapCIter thisIter = mTable.begin(); thisIter != mTable.end(); ++thisIter) { + if (this->isBackgroundTile(thisIter)) continue; // ignore background tiles + + // Fail if there is no corresponding entry in the other node's table. + OtherCIterT otherIter = other.findKey(thisIter->first); + if (otherIter == other.mTable.end()) return false; + + // Fail if this entry is a tile and the other is a child or vice-versa. + if (isChild(thisIter)) {//thisIter points to a child + if (OtherRootT::isTile(otherIter)) return false; + // Fail if both entries are children, but the children have different topology. + if (!getChild(thisIter).hasSameTopology(&OtherRootT::getChild(otherIter))) return false; + } else {//thisIter points to a tile + if (OtherRootT::isChild(otherIter)) return false; + if (getTile(thisIter).active != OtherRootT::getTile(otherIter).active) return false; + } + + // Remove tiles and child nodes with matching topology from + // the copy of the other node's table. This is required since + // the two root tables can include an arbitrary number of + // background tiles and still have the same topology! + copyOfOtherTable.erase(otherIter->first); + } + // Fail if the remaining entries in copyOfOtherTable are not all background tiles. + for (OtherIterT i = copyOfOtherTable.begin(), e = copyOfOtherTable.end(); i != e; ++i) { + if (!other.isBackgroundTile(i)) return false; + } + return true; +} + + +template +template +inline bool +RootNode::hasSameConfiguration(const RootNode&) +{ + std::vector thisDims, otherDims; + RootNode::getNodeLog2Dims(thisDims); + RootNode::getNodeLog2Dims(otherDims); + return (thisDims == otherDims); +} + + +template +template +inline void +RootNode::enforceSameConfiguration(const RootNode&) +{ + std::vector thisDims, otherDims; + RootNode::getNodeLog2Dims(thisDims); + RootNode::getNodeLog2Dims(otherDims); + if (thisDims != otherDims) { + std::ostringstream ostr; + ostr << "grids have incompatible configurations (" << thisDims[0]; + for (size_t i = 1, N = thisDims.size(); i < N; ++i) ostr << " x " << thisDims[i]; + ostr << " vs. " << otherDims[0]; + for (size_t i = 1, N = otherDims.size(); i < N; ++i) ostr << " x " << otherDims[i]; + ostr << ")"; + OPENVDB_THROW(TypeError, ostr.str()); + } +} + + +template +template +inline bool +RootNode::hasCompatibleValueType(const RootNode&) +{ + typedef typename OtherChildType::ValueType OtherValueType; + return CanConvertType::value; +} + + +template +template +inline void +RootNode::enforceCompatibleValueTypes(const RootNode&) +{ + typedef typename OtherChildType::ValueType OtherValueType; + 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::clearTable() +{ + 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 = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + if (on) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else { + // Nothing to do; (x, y, z) is background and therefore already inactive. + } + } else if (isChild(iter)) { + child = &getChild(iter); + } else if (on != getTile(iter).active) { + child = new ChildT(xyz, getTile(iter).value, !on); + setChild(iter, *child); + } + if (child) child->setActiveState(xyz, on); +} + +template +template +inline void +RootNode::setActiveStateAndCache(const Coord& xyz, bool on, AccessorT& acc) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + if (on) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else { + // Nothing to do; (x, y, z) is background and therefore already inactive. + } + } else if (isChild(iter)) { + child = &getChild(iter); + } else if (on != getTile(iter).active) { + child = new ChildT(xyz, getTile(iter).value, !on); + setChild(iter, *child); + } + if (child) { + acc.insert(xyz, child); + child->setActiveStateAndCache(xyz, on, acc); + } +} + + +template +inline void +RootNode::setValueOff(const Coord& xyz, const ValueType& value) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + if (!math::isExactlyEqual(mBackground, value)) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } + } else if (isChild(iter)) { + child = &getChild(iter); + } else if (isTileOn(iter) || !math::isExactlyEqual(getTile(iter).value, value)) { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + setChild(iter, *child); + } + if (child) child->setValueOff(xyz, value); +} + +template +template +inline void +RootNode::setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + if (!math::isExactlyEqual(mBackground, value)) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } + } else if (isChild(iter)) { + child = &getChild(iter); + } else if (isTileOn(iter) || !math::isExactlyEqual(getTile(iter).value, value)) { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + setChild(iter, *child); + } + if (child) { + acc.insert(xyz, child); + child->setValueOffAndCache(xyz, value, acc); + } +} + + +template +inline void +RootNode::setValueOn(const Coord& xyz, const ValueType& value) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + child = &getChild(iter); + } else if (isTileOff(iter) || !math::isExactlyEqual(getTile(iter).value, value)) { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + setChild(iter, *child); + } + if (child) child->setValueOn(xyz, value); +} + +template +template +inline void +RootNode::setValueAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + child = &getChild(iter); + } else if (isTileOff(iter) || !math::isExactlyEqual(getTile(iter).value, value)) { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + setChild(iter, *child); + } + if (child) { + acc.insert(xyz, child); + child->setValueAndCache(xyz, value, acc); + } +} + + +template +inline void +RootNode::setValueOnly(const Coord& xyz, const ValueType& value) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + child = &getChild(iter); + } else if (!math::isExactlyEqual(getTile(iter).value, value)) { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + setChild(iter, *child); + } + if (child) child->setValueOnly(xyz, value); +} + +template +template +inline void +RootNode::setValueOnlyAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + child = &getChild(iter); + } else if (!math::isExactlyEqual(getTile(iter).value, value)) { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + setChild(iter, *child); + } + if (child) { + acc.insert(xyz, child); + child->setValueOnlyAndCache(xyz, value, acc); + } +} + + +template +template +inline void +RootNode::modifyValue(const Coord& xyz, const ModifyOp& op) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + child = &getChild(iter); + } else { + // Need to create a child if the tile is inactive, + // in order to activate voxel (x, y, z). + bool createChild = isTileOff(iter); + if (!createChild) { + // Need to create a child if applying the functor + // to the tile value produces a different value. + const ValueType& tileVal = getTile(iter).value; + ValueType modifiedVal = tileVal; + op(modifiedVal); + createChild = !math::isExactlyEqual(tileVal, modifiedVal); + } + if (createChild) { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + setChild(iter, *child); + } + } + if (child) child->modifyValue(xyz, op); +} + +template +template +inline void +RootNode::modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT& acc) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + child = &getChild(iter); + } else { + // Need to create a child if the tile is inactive, + // in order to activate voxel (x, y, z). + bool createChild = isTileOff(iter); + if (!createChild) { + // Need to create a child if applying the functor + // to the tile value produces a different value. + const ValueType& tileVal = getTile(iter).value; + ValueType modifiedVal = tileVal; + op(modifiedVal); + createChild = !math::isExactlyEqual(tileVal, modifiedVal); + } + if (createChild) { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + setChild(iter, *child); + } + } + if (child) { + acc.insert(xyz, child); + child->modifyValueAndCache(xyz, op, acc); + } +} + + +template +template +inline void +RootNode::modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + child = &getChild(iter); + } else { + const Tile& tile = getTile(iter); + bool modifiedState = tile.active; + ValueType modifiedVal = tile.value; + op(modifiedVal, modifiedState); + // Need to create a child if applying the functor to the tile + // produces a different value or active state. + if (modifiedState != tile.active || !math::isExactlyEqual(modifiedVal, tile.value)) { + child = new ChildT(xyz, tile.value, tile.active); + setChild(iter, *child); + } + } + if (child) child->modifyValueAndActiveState(xyz, op); +} + +template +template +inline void +RootNode::modifyValueAndActiveStateAndCache( + const Coord& xyz, const ModifyOp& op, AccessorT& acc) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + child = new ChildT(xyz, mBackground); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + child = &getChild(iter); + } else { + const Tile& tile = getTile(iter); + bool modifiedState = tile.active; + ValueType modifiedVal = tile.value; + op(modifiedVal, modifiedState); + // Need to create a child if applying the functor to the tile + // produces a different value or active state. + if (modifiedState != tile.active || !math::isExactlyEqual(modifiedVal, tile.value)) { + child = new ChildT(xyz, tile.value, tile.active); + setChild(iter, *child); + } + } + if (child) { + acc.insert(xyz, child); + child->modifyValueAndActiveStateAndCache(xyz, op, acc); + } +} + + +template +inline bool +RootNode::probeValue(const Coord& xyz, ValueType& value) const +{ + MapCIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + value = mBackground; + return false; + } else if (isChild(iter)) { + return getChild(iter).probeValue(xyz, value); + } + value = getTile(iter).value; + return isTileOn(iter); +} + +template +template +inline bool +RootNode::probeValueAndCache(const Coord& xyz, ValueType& value, AccessorT& acc) const +{ + MapCIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + value = mBackground; + return false; + } else if (isChild(iter)) { + acc.insert(xyz, &getChild(iter)); + return getChild(iter).probeValueAndCache(xyz, value, acc); + } + value = getTile(iter).value; + return isTileOn(iter); +} + + +//////////////////////////////////////// + + +template +inline void +RootNode::fill(const CoordBBox& bbox, const ValueType& value, bool active) +{ + if (bbox.empty()) return; + + Coord xyz, tileMax; + for (int x = bbox.min().x(); x <= bbox.max().x(); x = tileMax.x() + 1) { + xyz.setX(x); + for (int y = bbox.min().y(); y <= bbox.max().y(); y = tileMax.y() + 1) { + xyz.setY(y); + for (int z = bbox.min().z(); z <= bbox.max().z(); z = tileMax.z() + 1) { + xyz.setZ(z); + + // Get the bounds of the tile that contains voxel (x, y, z). + Coord tileMin = coordToKey(xyz); + tileMax = tileMin.offsetBy(ChildT::DIM - 1); + + if (xyz != tileMin || Coord::lessThan(bbox.max(), tileMax)) { + // If the box defined by (xyz, bbox.max()) doesn't completely enclose + // the tile to which xyz belongs, create a child node (or retrieve + // the existing one). + ChildT* child = NULL; + MapIter iter = this->findKey(tileMin); + if (iter == mTable.end()) { + // No child or tile exists. Create a child and initialize it + // with the background value. + child = new ChildT(xyz, mBackground); + mTable[tileMin] = NodeStruct(*child); + } else if (isTile(iter)) { + // Replace the tile with a newly-created child that is initialized + // with the tile's value and active state. + const Tile& tile = getTile(iter); + child = new ChildT(xyz, tile.value, tile.active); + mTable[tileMin] = NodeStruct(*child); + } else if (isChild(iter)) { + child = &getChild(iter); + } + // Forward the fill request to the child. + if (child) { + child->fill(CoordBBox(xyz, Coord::minComponent(bbox.max(), tileMax)), + value, active); + } + } else { + // If the box given by (xyz, bbox.max()) completely encloses + // the tile to which xyz belongs, create the tile (if it + // doesn't already exist) and give it the fill value. + MapIter iter = this->findOrAddCoord(tileMin); + setTile(iter, Tile(value, active)); + } + } + } + } +} + +template +template +inline void +RootNode::copyToDense(const CoordBBox& bbox, DenseT& dense) const +{ + typedef typename DenseT::ValueType DenseValueType; + + const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); + const Coord& min = dense.bbox().min(); + CoordBBox nodeBBox; + for (Coord xyz = bbox.min(); xyz[0] <= bbox.max()[0]; xyz[0] = nodeBBox.max()[0] + 1) { + for (xyz[1] = bbox.min()[1]; xyz[1] <= bbox.max()[1]; xyz[1] = nodeBBox.max()[1] + 1) { + for (xyz[2] = bbox.min()[2]; xyz[2] <= bbox.max()[2]; xyz[2] = nodeBBox.max()[2] + 1) { + + // Get the coordinate bbox of the child node that contains voxel xyz. + nodeBBox = CoordBBox::createCube(coordToKey(xyz), ChildT::DIM); + + // Get the coordinate bbox of the interection of inBBox and nodeBBox + CoordBBox sub(xyz, Coord::minComponent(bbox.max(), nodeBBox.max())); + + MapCIter iter = this->findKey(nodeBBox.min()); + if (iter != mTable.end() && isChild(iter)) {//is a child + getChild(iter).copyToDense(sub, dense); + } else {//is background or a tile value + const ValueType value = iter==mTable.end() ? mBackground : getTile(iter).value; + sub.translate(-min); + DenseValueType* a0 = dense.data() + zStride*sub.min()[2]; + for (Int32 x=sub.min()[0], ex=sub.max()[0]+1; x +inline bool +RootNode::writeTopology(std::ostream& os, bool toHalf) const +{ + if (!toHalf) { + os.write(reinterpret_cast(&mBackground), sizeof(ValueType)); + } else { + ValueType truncatedVal = io::truncateRealToHalf(mBackground); + os.write(reinterpret_cast(&truncatedVal), sizeof(ValueType)); + } + io::setGridBackgroundValuePtr(os, &mBackground); + + const Index numTiles = this->getTileCount(), numChildren = this->getChildCount(); + os.write(reinterpret_cast(&numTiles), sizeof(Index)); + os.write(reinterpret_cast(&numChildren), sizeof(Index)); + + if (numTiles == 0 && numChildren == 0) return false; + + // Write tiles. + for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { + if (isChild(i)) continue; + os.write(reinterpret_cast(i->first.asPointer()), 3 * sizeof(Int32)); + os.write(reinterpret_cast(&getTile(i).value), sizeof(ValueType)); + os.write(reinterpret_cast(&getTile(i).active), sizeof(bool)); + } + // Write child nodes. + for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { + if (isTile(i)) continue; + os.write(reinterpret_cast(i->first.asPointer()), 3 * sizeof(Int32)); + getChild(i).writeTopology(os, toHalf); + } + + return true; // not empty +} + + +template +inline bool +RootNode::readTopology(std::istream& is, bool fromHalf) +{ + // Delete the existing tree. + this->clearTable(); + + if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_ROOTNODE_MAP) { + // Read and convert an older-format RootNode. + + // For backward compatibility with older file formats, read both + // outside and inside background values. + is.read(reinterpret_cast(&mBackground), sizeof(ValueType)); + ValueType inside; + is.read(reinterpret_cast(&inside), sizeof(ValueType)); + + io::setGridBackgroundValuePtr(is, &mBackground); + + // Read the index range. + Coord rangeMin, rangeMax; + is.read(reinterpret_cast(rangeMin.asPointer()), 3 * sizeof(Int32)); + is.read(reinterpret_cast(rangeMax.asPointer()), 3 * sizeof(Int32)); + + this->initTable(); + Index tableSize = 0, log2Dim[4] = { 0, 0, 0, 0 }; + Int32 offset[3]; + for (int i = 0; i < 3; ++i) { + offset[i] = rangeMin[i] >> ChildT::TOTAL; + rangeMin[i] = offset[i] << ChildT::TOTAL; + log2Dim[i] = 1 + util::FindHighestOn((rangeMax[i] >> ChildT::TOTAL) - offset[i]); + tableSize += log2Dim[i]; + rangeMax[i] = (((1 << log2Dim[i]) + offset[i]) << ChildT::TOTAL) - 1; + } + log2Dim[3] = log2Dim[1] + log2Dim[2]; + tableSize = 1U << tableSize; + + // Read masks. + util::RootNodeMask childMask(tableSize), valueMask(tableSize); + childMask.load(is); + valueMask.load(is); + + // Read child nodes/values. + for (Index i = 0; i < tableSize; ++i) { + // Compute origin = offset2coord(i). + Index n = i; + Coord origin; + origin[0] = (n >> log2Dim[3]) + offset[0]; + n &= (1U << log2Dim[3]) - 1; + origin[1] = (n >> log2Dim[2]) + offset[1]; + origin[2] = (n & ((1U << log2Dim[2]) - 1)) + offset[1]; + origin <<= ChildT::TOTAL; + + if (childMask.isOn(i)) { + // Read in and insert a child node. + ChildT* child = new ChildT(origin, mBackground); + child->readTopology(is); + mTable[origin] = NodeStruct(*child); + } else { + // Read in a tile value and insert a tile, but only if the value + // is either active or non-background. + ValueType value; + is.read(reinterpret_cast(&value), sizeof(ValueType)); + if (valueMask.isOn(i) || (!math::isApproxEqual(value, mBackground))) { + mTable[origin] = NodeStruct(Tile(value, valueMask.isOn(i))); + } + } + } + return true; + } + + // Read a RootNode that was stored in the current format. + + is.read(reinterpret_cast(&mBackground), sizeof(ValueType)); + io::setGridBackgroundValuePtr(is, &mBackground); + + Index numTiles = 0, numChildren = 0; + is.read(reinterpret_cast(&numTiles), sizeof(Index)); + is.read(reinterpret_cast(&numChildren), sizeof(Index)); + + if (numTiles == 0 && numChildren == 0) return false; + + Int32 vec[3]; + ValueType value; + bool active; + + // Read tiles. + for (Index n = 0; n < numTiles; ++n) { + is.read(reinterpret_cast(vec), 3 * sizeof(Int32)); + is.read(reinterpret_cast(&value), sizeof(ValueType)); + is.read(reinterpret_cast(&active), sizeof(bool)); + mTable[Coord(vec)] = NodeStruct(Tile(value, active)); + } + + // Read child nodes. + for (Index n = 0; n < numChildren; ++n) { + is.read(reinterpret_cast(vec), 3 * sizeof(Int32)); + Coord origin(vec); + ChildT* child = new ChildT(origin, mBackground); + child->readTopology(is, fromHalf); + mTable[Coord(vec)] = NodeStruct(*child); + } + + return true; // not empty +} + + +template +inline void +RootNode::writeBuffers(std::ostream& os, bool toHalf) const +{ + for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { + if (isChild(i)) getChild(i).writeBuffers(os, toHalf); + } +} + + +template +inline void +RootNode::readBuffers(std::istream& is, bool fromHalf) +{ + for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { + if (isChild(i)) getChild(i).readBuffers(is, fromHalf); + } +} + + +//////////////////////////////////////// + + +template +template +inline void +RootNode::pruneOp(PruneOp& op) +{ + for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { + if (this->isTile(i)|| !op(this->getChild(i))) continue; + this->setTile(i, Tile(op.value, op.state)); + } + this->eraseBackgroundTiles(); +} + + +template +inline void +RootNode::prune(const ValueType& tolerance) +{ + TolerancePrune op(tolerance); + this->pruneOp(op); +} + + +template +inline void +RootNode::pruneInactive(const ValueType& bg) +{ + InactivePrune op(bg); + this->pruneOp(op); +} + + +template +inline void +RootNode::pruneInactive() +{ + this->pruneInactive(mBackground); +} + + +template +inline void +RootNode::pruneTiles(const ValueType& tolerance) +{ + TolerancePrune op(tolerance); + this->pruneOp(op); +} + + +//////////////////////////////////////// + + +template +template +inline NodeT* +RootNode::stealNode(const Coord& xyz, const ValueType& value, bool state) +{ + if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || + NodeT::LEVEL > ChildT::LEVEL) return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end() || isTile(iter)) return NULL; + return (boost::is_same::value) + ? reinterpret_cast(&stealChild(iter, Tile(value, state))) + : getChild(iter).template stealNode(xyz, value, state); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +//////////////////////////////////////// + + +template +inline void +RootNode::addLeaf(LeafNodeType* leaf) +{ + if (leaf == NULL) return; + ChildT* child = NULL; + const Coord& xyz = leaf->origin(); + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + if (ChildT::LEVEL>0) { + child = new ChildT(xyz, mBackground, false); + } else { + child = reinterpret_cast(leaf); + } + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + if (ChildT::LEVEL>0) { + child = &getChild(iter); + } else { + child = reinterpret_cast(leaf); + setChild(iter, *child);//this also deletes the existing child node + } + } else {//tile + if (ChildT::LEVEL>0) { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + } else { + child = reinterpret_cast(leaf); + } + setChild(iter, *child); + } + child->addLeaf(leaf); +} + + +template +template +inline void +RootNode::addLeafAndCache(LeafNodeType* leaf, AccessorT& acc) +{ + if (leaf == NULL) return; + ChildT* child = NULL; + const Coord& xyz = leaf->origin(); + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + if (ChildT::LEVEL>0) { + child = new ChildT(xyz, mBackground, false); + } else { + child = reinterpret_cast(leaf); + } + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + if (ChildT::LEVEL>0) { + child = &getChild(iter); + } else { + child = reinterpret_cast(leaf); + setChild(iter, *child);//this also deletes the existing child node + } + } else {//tile + if (ChildT::LEVEL>0) { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + } else { + child = reinterpret_cast(leaf); + } + setChild(iter, *child); + } + acc.insert(xyz, child); + child->addLeafAndCache(leaf, acc); +} + + +template +inline void +RootNode::addTile(Index level, const Coord& xyz, + const ValueType& value, bool state) +{ + if (LEVEL >= level) { + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) {//background + if (LEVEL > level) { + ChildT* child = new ChildT(xyz, mBackground, false); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + child->addTile(level, xyz, value, state); + } else { + mTable[this->coordToKey(xyz)] = NodeStruct(Tile(value, state)); + } + } else if (isChild(iter)) {//child + if (LEVEL > level) { + getChild(iter).addTile(level, xyz, value, state); + } else { + setTile(iter, Tile(value, state));//this also deletes the existing child node + } + } else {//tile + if (LEVEL > level) { + ChildT* child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + setChild(iter, *child); + child->addTile(level, xyz, value, state); + } else { + setTile(iter, Tile(value, state)); + } + } + } +} + + +template +template +inline void +RootNode::addTileAndCache(Index level, const Coord& xyz, const ValueType& value, + bool state, AccessorT& acc) +{ + if (LEVEL >= level) { + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) {//background + if (LEVEL > level) { + ChildT* child = new ChildT(xyz, mBackground, false); + acc.insert(xyz, child); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + child->addTileAndCache(level, xyz, value, state, acc); + } else { + mTable[this->coordToKey(xyz)] = NodeStruct(Tile(value, state)); + } + } else if (isChild(iter)) {//child + if (LEVEL > level) { + ChildT* child = &getChild(iter); + acc.insert(xyz, child); + child->addTileAndCache(level, xyz, value, state, acc); + } else { + setTile(iter, Tile(value, state));//this also deletes the existing child node + } + } else {//tile + if (LEVEL > level) { + ChildT* child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + acc.insert(xyz, child); + setChild(iter, *child); + child->addTileAndCache(level, xyz, value, state, acc); + } else { + setTile(iter, Tile(value, state)); + } + } + } +} + + +//////////////////////////////////////// + + +template +inline typename ChildT::LeafNodeType* +RootNode::touchLeaf(const Coord& xyz) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + child = new ChildT(xyz, mBackground, false); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + child = &getChild(iter); + } else { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + setChild(iter, *child); + } + return child->touchLeaf(xyz); +} + + +template +template +inline typename ChildT::LeafNodeType* +RootNode::touchLeafAndCache(const Coord& xyz, AccessorT& acc) +{ + ChildT* child = NULL; + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end()) { + child = new ChildT(xyz, mBackground, false); + mTable[this->coordToKey(xyz)] = NodeStruct(*child); + } else if (isChild(iter)) { + child = &getChild(iter); + } else { + child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); + setChild(iter, *child); + } + acc.insert(xyz, child); + return child->touchLeafAndCache(xyz, acc); +} + + +//////////////////////////////////////// + + +template +template +inline NodeT* +RootNode::probeNode(const Coord& xyz) +{ + if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || + NodeT::LEVEL > ChildT::LEVEL) return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end() || isTile(iter)) return NULL; + ChildT* child = &getChild(iter); + return (boost::is_same::value) + ? reinterpret_cast(child) + : child->template probeNode(xyz); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +template +template +inline const NodeT* +RootNode::probeConstNode(const Coord& xyz) const +{ + if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || + NodeT::LEVEL > ChildT::LEVEL) return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + MapCIter iter = this->findCoord(xyz); + if (iter == mTable.end() || isTile(iter)) return NULL; + const ChildT* child = &getChild(iter); + return (boost::is_same::value) + ? reinterpret_cast(child) + : child->template probeConstNode(xyz); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +template +inline typename ChildT::LeafNodeType* +RootNode::probeLeaf(const Coord& xyz) +{ + return this->template probeNode(xyz); +} + + +template +inline const typename ChildT::LeafNodeType* +RootNode::probeConstLeaf(const Coord& xyz) const +{ + return this->template probeConstNode(xyz); +} + + +template +template +inline typename ChildT::LeafNodeType* +RootNode::probeLeafAndCache(const Coord& xyz, AccessorT& acc) +{ + return this->template probeNodeAndCache(xyz, acc); +} + + +template +template +inline const typename ChildT::LeafNodeType* +RootNode::probeConstLeafAndCache(const Coord& xyz, AccessorT& acc) const +{ + return this->template probeConstNodeAndCache(xyz, acc); +} + + +template +template +inline const typename ChildT::LeafNodeType* +RootNode::probeLeafAndCache(const Coord& xyz, AccessorT& acc) const +{ + return this->probeConstLeafAndCache(xyz, acc); +} + + +template +template +inline NodeT* +RootNode::probeNodeAndCache(const Coord& xyz, AccessorT& acc) +{ + if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || + NodeT::LEVEL > ChildT::LEVEL) return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + MapIter iter = this->findCoord(xyz); + if (iter == mTable.end() || isTile(iter)) return NULL; + ChildT* child = &getChild(iter); + acc.insert(xyz, child); + return (boost::is_same::value) + ? reinterpret_cast(child) + : child->template probeNodeAndCache(xyz, acc); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +template +template +inline const NodeT* +RootNode::probeConstNodeAndCache(const Coord& xyz, AccessorT& acc) const +{ + if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || + NodeT::LEVEL > ChildT::LEVEL) return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + MapCIter iter = this->findCoord(xyz); + if (iter == mTable.end() || isTile(iter)) return NULL; + const ChildT* child = &getChild(iter); + acc.insert(xyz, child); + return (boost::is_same::value) + ? reinterpret_cast(child) + : child->template probeConstNodeAndCache(xyz, acc); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +//////////////////////////////////////// + + +template +inline void +RootNode::signedFloodFill() +{ + this->signedFloodFill(mBackground, math::negative(mBackground)); +} + + +template +inline void +RootNode::signedFloodFill(const ValueType& outside, const ValueType& inside) +{ + const ValueType zero = zeroVal(); + + mBackground = outside; + + // First, flood fill all child nodes and put child-keys into a sorted set + CoordSet nodeKeys; + for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { + if (this->isTile(i)) continue; + getChild(i).signedFloodFill(outside, inside); + nodeKeys.insert(i->first);//only add inactive tiles! + } + + // We employ a simple z-scanline algorithm that inserts inactive + // tiles with the inside value if they are sandwiched + // between inside child nodes only! + const Tile insideTile(inside, /*on=*/false); + CoordSetCIter b = nodeKeys.begin(), e = nodeKeys.end(); + if ( b == e ) return; + for (CoordSetCIter a = b++; b != e; ++a, ++b) { + Coord d = *b - *a; // delta of neighboring keys + if (d[0]!=0 || d[1]!=0 || d[2]==Int32(ChildT::DIM)) continue;//not z-scanline or neighbors + MapIter i = mTable.find(*a), j = mTable.find(*b); + const ValueType fill[] = { getChild(i).getLastValue(), getChild(j).getFirstValue() }; + if (!(fill[0] < zero) || !(fill[1] < zero)) continue; // scanline isn't inside + for (Coord c = *a + Coord(0u,0u,ChildT::DIM); c[2] != (*b)[2]; c[2] += ChildT::DIM) { + mTable[c] = insideTile; + } + } +} + + +//////////////////////////////////////// + + +template +inline void +RootNode::voxelizeActiveTiles() +{ + for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { + if (this->isTileOff(i)) continue; + ChildT* child = i->second.child; + if (child==NULL) { + child = new ChildT(i->first, this->getTile(i).value, true); + i->second.child = child; + } + child->voxelizeActiveTiles(); + } +} + + +//////////////////////////////////////// + + +template +template +inline void +RootNode::merge(RootNode& other) +{ + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + + switch (Policy) { + + default: + case MERGE_ACTIVE_STATES: + for (MapIter i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { + MapIter j = mTable.find(i->first); + if (other.isChild(i)) { + if (j == mTable.end()) { // insert other node's child + ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); + child.resetBackground(other.mBackground, mBackground); + mTable[i->first] = NodeStruct(child); + } else if (isTile(j)) { + if (isTileOff(j)) { // replace inactive tile with other node's child + ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); + child.resetBackground(other.mBackground, mBackground); + setChild(j, child); + } + } else { // merge both child nodes + getChild(j).template merge(getChild(i), + other.mBackground, mBackground); + } + } else if (other.isTileOn(i)) { + if (j == mTable.end()) { // insert other node's active tile + mTable[i->first] = i->second; + } else if (!isTileOn(j)) { + // Replace anything except an active tile with the other node's active tile. + setTile(j, Tile(other.getTile(i).value, true)); + } + } + } + break; + + case MERGE_NODES: + for (MapIter i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { + MapIter j = mTable.find(i->first); + if (other.isChild(i)) { + if (j == mTable.end()) { // insert other node's child + ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); + child.resetBackground(other.mBackground, mBackground); + mTable[i->first] = NodeStruct(child); + } else if (isTile(j)) { // replace tile with other node's child + ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); + child.resetBackground(other.mBackground, mBackground); + setChild(j, child); + } else { // merge both child nodes + getChild(j).template merge( + getChild(i), other.mBackground, mBackground); + } + } + } + break; + + case MERGE_ACTIVE_STATES_AND_NODES: + for (MapIter i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { + MapIter j = mTable.find(i->first); + if (other.isChild(i)) { + if (j == mTable.end()) { + // Steal and insert the other node's child. + ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); + child.resetBackground(other.mBackground, mBackground); + mTable[i->first] = NodeStruct(child); + } else if (isTile(j)) { + // Replace this node's tile with the other node's child. + ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); + child.resetBackground(other.mBackground, mBackground); + const Tile tile = getTile(j); + setChild(j, child); + if (tile.active) { + // Merge the other node's child with this node's active tile. + child.template merge( + tile.value, tile.active); + } + } else /*if (isChild(j))*/ { + // Merge the other node's child into this node's child. + getChild(j).template merge(getChild(i), + other.mBackground, mBackground); + } + } else if (other.isTileOn(i)) { + if (j == mTable.end()) { + // Insert a copy of the other node's active tile. + mTable[i->first] = i->second; + } else if (isTileOff(j)) { + // Replace this node's inactive tile with a copy of the other's active tile. + setTile(j, Tile(other.getTile(i).value, true)); + } else if (isChild(j)) { + // Merge the other node's active tile into this node's child. + const Tile& tile = getTile(i); + getChild(j).template merge( + tile.value, tile.active); + } + } // else if (other.isTileOff(i)) {} // ignore the other node's inactive tiles + } + break; + } + + // Empty the other tree so as not to leave it in a partially cannibalized state. + other.clear(); + + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END +} + + +//////////////////////////////////////// + + +template +template +inline void +RootNode::topologyUnion(const RootNode& other) +{ + typedef RootNode OtherRootT; + typedef typename OtherRootT::MapCIter OtherCIterT; + + enforceSameConfiguration(other); + + for (OtherCIterT i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { + MapIter j = mTable.find(i->first); + if (other.isChild(i)) { + if (j == mTable.end()) { // create child branch with identical topology + mTable[i->first] = NodeStruct( + *(new ChildT(other.getChild(i), mBackground, TopologyCopy()))); + } else if (this->isChild(j)) { // union with child branch + this->getChild(j).topologyUnion(other.getChild(i)); + } else {// this is a tile so replace it with a child branch with identical topology + ChildT* child = new ChildT( + other.getChild(i), this->getTile(j).value, TopologyCopy()); + if (this->isTileOn(j)) child->setValuesOn();//this is an active tile + this->setChild(j, *child); + } + } else if (other.isTileOn(i)) { // other is an active tile + if (j == mTable.end()) { // insert an active tile + mTable[i->first] = NodeStruct(Tile(mBackground, true)); + } else if (this->isChild(j)) { + this->getChild(j).setValuesOn(); + } else if (this->isTileOff(j)) { + this->setTile(j, Tile(this->getTile(j).value, true)); + } + } + } +} + +template +template +inline void +RootNode::topologyIntersection(const RootNode& other) +{ + typedef RootNode OtherRootT; + typedef typename OtherRootT::MapCIter OtherCIterT; + + enforceSameConfiguration(other); + + std::set tmp;//keys to erase + for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { + OtherCIterT j = other.mTable.find(i->first); + if (this->isChild(i)) { + if (j == other.mTable.end() || other.isTileOff(j)) { + tmp.insert(i->first);//delete child branch + } else if (other.isChild(j)) { // intersect with child branch + this->getChild(i).topologyIntersection(other.getChild(j), mBackground); + } + } else if (this->isTileOn(i)) { + if (j == other.mTable.end() || other.isTileOff(j)) { + this->setTile(i, Tile(this->getTile(i).value, false));//turn inactive + } else if (other.isChild(j)) { //replace with a child branch with identical topology + ChildT* child = + new ChildT(other.getChild(j), this->getTile(i).value, TopologyCopy()); + this->setChild(i, *child); + } + } + } + for (std::set::iterator i = tmp.begin(), e = tmp.end(); i != e; ++i) mTable.erase(*i); +} + +template +template +inline void +RootNode::topologyDifference(const RootNode& other) +{ + typedef RootNode OtherRootT; + typedef typename OtherRootT::MapCIter OtherCIterT; + + enforceSameConfiguration(other); + + for (OtherCIterT i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { + MapIter j = mTable.find(i->first); + if (other.isChild(i)) { + if (j == mTable.end() || this->isTileOff(j)) { + //do nothing + } else if (this->isChild(j)) { // difference with child branch + this->getChild(j).topologyDifference(other.getChild(i), mBackground); + } else if (this->isTileOn(j)) { + // this is an active tile so create a child node and descent + ChildT* child = new ChildT(j->first, this->getTile(j).value, true); + child->topologyDifference(other.getChild(i), mBackground); + this->setChild(j, *child); + } + } else if (other.isTileOn(i)) { // other is an active tile + if (j == mTable.end() || this->isTileOff(j)) { + // do nothing + } else if (this->isChild(j)) { + mTable.erase(j->first);//delete child + } else if (this->isTileOn(j)) { + this->setTile(j, Tile(this->getTile(j).value, false)); + } + } + } +} + +//////////////////////////////////////// + + +template +template +inline void +RootNode::combine(RootNode& other, CombineOp& op, bool prune) +{ + CombineArgs args; + + CoordSet keys; + this->insertKeys(keys); + other.insertKeys(keys); + + for (CoordSetCIter i = keys.begin(), e = keys.end(); i != e; ++i) { + MapIter iter = findOrAddCoord(*i), otherIter = other.findOrAddCoord(*i); + if (isTile(iter) && isTile(otherIter)) { + // Both this node and the other node have constant values (tiles). + // Combine the two values and store the result as this node's new tile value. + op(args.setARef(getTile(iter).value) + .setAIsActive(isTileOn(iter)) + .setBRef(getTile(otherIter).value) + .setBIsActive(isTileOn(otherIter))); + setTile(iter, Tile(args.result(), args.resultIsActive())); + + } else if (isChild(iter) && isTile(otherIter)) { + // Combine this node's child with the other node's constant value. + ChildT& child = getChild(iter); + child.combine(getTile(otherIter).value, isTileOn(otherIter), op); + + } else if (isTile(iter) && isChild(otherIter)) { + // Combine this node's constant value with the other node's child, + // but use a new functor in which the A and B values are swapped, + // since the constant value is the A value, not the B value. + SwappedCombineOp swappedOp(op); + ChildT& child = getChild(otherIter); + child.combine(getTile(iter).value, isTileOn(iter), swappedOp); + + // Steal the other node's child. + setChild(iter, stealChild(otherIter, Tile())); + + } else /*if (isChild(iter) && isChild(otherIter))*/ { + // Combine this node's child with the other node's child. + ChildT &child = getChild(iter), &otherChild = getChild(otherIter); + child.combine(otherChild, op); + } + if (prune && isChild(iter)) getChild(iter).prune(); + } + + // Combine background values. + op(args.setARef(mBackground).setBRef(other.mBackground)); + mBackground = args.result(); + + // Empty the other tree so as not to leave it in a partially cannibalized state. + other.clear(); +} + + +//////////////////////////////////////// + + +// 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) +{ + typedef typename OtherRootNode::ValueType OtherValueType; + 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); + + typedef typename OtherRootNode::ValueType OtherValueT; + typedef typename OtherRootNode::Tile OtherTileT; + typedef typename OtherRootNode::NodeStruct OtherNodeStructT; + typedef typename OtherRootNode::MapCIter OtherMapCIterT; + + 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) ? NULL : iter.probeChild(val); + typename OtherChildAllIterT::ChildNodeType* otherChild = + (skipBranch & 2U) ? NULL : otherIter.probeChild(otherVal); + + if (child != NULL && otherChild != NULL) { + child->visit2Node(*otherChild, op); + } else if (child != NULL) { + child->visit2(otherIter, op); + } else if (otherChild != NULL) { + otherChild->visit2(iter, op, /*otherIsLHS=*/true); + } + } + // Remove any background tiles that were added above, + // as well as any that were created by the visitors. + copyOfSelf.eraseBackgroundTiles(); + copyOfOther.eraseBackgroundTiles(); + + // If either input node is non-const, replace its table with + // the (possibly modified) copy. + self.resetTable(copyOfSelf.mTable); + other.resetTable(copyOfOther.mTable); +} + +} // namespace tree +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TREE_ROOTNODE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tree/Tree.h b/openvdb_2_3_0_library/openvdb/tree/Tree.h new file mode 100755 index 0000000..e0715ca --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tree/Tree.h @@ -0,0 +1,2196 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file tree/Tree.h + +#ifndef OPENVDB_TREE_TREE_HAS_BEEN_INCLUDED +#define OPENVDB_TREE_TREE_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "RootNode.h" +#include "InternalNode.h" +#include "LeafNode.h" +#include "TreeIterator.h" +#include "ValueAccessor.h" +#include "Util.h" + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tree { + +/// @brief Base class for typed trees +class OPENVDB_API TreeBase +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + TreeBase() {} + virtual ~TreeBase() {} + + /// Return the name of this tree's type. + virtual const Name& type() const = 0; + + /// Return the name of the type of a voxel's value (e.g., "float" or "vec3d"). + virtual Name valueType() const = 0; + + /// Return a pointer to a deep copy of this tree + virtual TreeBase::Ptr copy() const = 0; + + // + // Tree methods + // + /// @brief Return this tree's background value wrapped as metadata. + /// @note Query the metadata object for the value's type. + virtual Metadata::Ptr getBackgroundValue() const { return Metadata::Ptr(); } + + /// @brief Return in @a bbox the axis-aligned bounding box of all + /// leaf nodes and active tiles. + /// @details This is faster then calling evalActiveVoxelBoundingBox, + /// which visits the individual active voxels, and hence + /// evalLeafBoundingBox produces a less tight, i.e. approximate, bbox. + /// @return @c false if the bounding box is empty (in which case + /// the bbox is set to its default value). + virtual bool evalLeafBoundingBox(CoordBBox& bbox) const = 0; + + /// @brief Return in @a dim the dimensions of the axis-aligned bounding box + /// of all leaf nodes. + /// @return @c false if the bounding box is empty. + virtual bool evalLeafDim(Coord& dim) const = 0; + + /// @brief Return in @a bbox the axis-aligned bounding box of all + /// active voxels and tiles. + /// @details This method produces a more accurate, i.e. tighter, + /// bounding box than evalLeafBoundingBox which is approximate but + /// faster. + /// @return @c false if the bounding box is empty (in which case + /// the bbox is set to its default value). + virtual bool evalActiveVoxelBoundingBox(CoordBBox& bbox) const = 0; + + /// @brief Return in @a dim the dimensions of the axis-aligned bounding box of all + /// active voxels. This is a tighter bounding box than the leaf node bounding box. + /// @return @c false if the bounding box is empty. + virtual bool evalActiveVoxelDim(Coord& dim) const = 0; + + virtual void getIndexRange(CoordBBox& bbox) const = 0; + + + // + // Statistics + // + /// @brief Return the depth of this tree. + /// + /// A tree with only a root node and leaf nodes has depth 2, for example. + virtual Index treeDepth() const = 0; + /// Return the number of leaf nodes. + virtual Index32 leafCount() const = 0; + /// Return the number of non-leaf nodes. + virtual Index32 nonLeafCount() const = 0; + /// Return the number of active voxels stored in leaf nodes. + virtual Index64 activeLeafVoxelCount() const = 0; + /// Return the number of inactive voxels stored in leaf nodes. + virtual Index64 inactiveLeafVoxelCount() const = 0; + /// Return the total number of active voxels. + virtual Index64 activeVoxelCount() const = 0; + /// Return the number of inactive voxels within the bounding box of all active voxels. + virtual Index64 inactiveVoxelCount() const = 0; + + /// Return the total amount of memory in bytes occupied by this tree. + virtual Index64 memUsage() const { return 0; } + + + // + // I/O methods + // + /// @brief Read the tree topology from a stream. + /// + /// This will read the tree structure and tile values, but not voxel data. + virtual void readTopology(std::istream&, bool saveFloatAsHalf = false); + /// @brief Write the tree topology to a stream. + /// + /// This will write the tree structure and tile values, but not voxel data. + virtual void writeTopology(std::ostream&, bool saveFloatAsHalf = false) const; + + /// Read all data buffers for this tree. + virtual void readBuffers(std::istream&, bool saveFloatAsHalf = false) = 0; + /// Write out all the data buffers for this tree. + virtual void writeBuffers(std::ostream&, bool saveFloatAsHalf = false) const = 0; + + /// @brief Print statistics, memory usage and other information about this tree. + /// @param os a stream to which to write textual information + /// @param verboseLevel 1: print tree configuration only; 2: include node and + /// voxel statistics; 3: include memory usage + virtual void print(std::ostream& os = std::cout, int verboseLevel = 1) const; + +private: + // Disallow copying of instances of this class. + //TreeBase(const TreeBase& other); + TreeBase& operator=(const TreeBase& other); +}; + + +//////////////////////////////////////// + + +template +class Tree: public TreeBase +{ +public: + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + typedef _RootNodeType RootNodeType; + typedef typename RootNodeType::ValueType ValueType; + typedef typename RootNodeType::LeafNodeType LeafNodeType; + + static const Index DEPTH = RootNodeType::LEVEL + 1; + + /// @brief ValueConverter::Type is the type of a tree having the same + /// hierarchy as this tree but a different value type, T. + /// + /// For example, FloatTree::ValueConverter::Type is equivalent to DoubleTree. + /// @note If the source tree type is a template argument, it might be necessary + /// to write "typename SourceTree::template ValueConverter::Type". + template + struct ValueConverter { + typedef Tree::Type> Type; + }; + + + Tree() {} + + /// Deep copy constructor + Tree(const Tree& other): TreeBase(other), mRoot(other.mRoot) + { + } + + /// @brief 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) {} + + virtual ~Tree() { releaseAllAccessors(); } + + /// Return a pointer to a deep copy of this tree + virtual TreeBase::Ptr copy() const { return TreeBase::Ptr(new Tree(*this)); } + + /// Return the name of the type of a voxel's value (e.g., "float" or "vec3d") + virtual Name valueType() const { return typeNameAsString(); } + + /// Return the name of this type of tree. + static const Name& treeType(); + /// Return the name of this type of tree. + virtual const Name& type() const { return this->treeType(); } + + bool operator==(const Tree&) const { OPENVDB_THROW(NotImplementedError, ""); } + bool operator!=(const Tree&) const { OPENVDB_THROW(NotImplementedError, ""); } + + //@{ + /// Return this tree's root node. + RootNodeType& root() { return mRoot; } + const RootNodeType& root() const { return mRoot; } + //@} + //@{ + /// @brief Return this tree's root node. + /// @deprecated Use root() instead. + OPENVDB_DEPRECATED RootNodeType& getRootNode() { return mRoot; } + OPENVDB_DEPRECATED const RootNodeType& getRootNode() const { return mRoot; } + //@} + + + // + // Tree methods + // + /// @brief Return @c true if the given tree has the same node and active value + /// topology as this tree, whether or not it has the same @c ValueType. + template + bool hasSameTopology(const Tree& other) const; + + virtual bool evalLeafBoundingBox(CoordBBox& bbox) const; + virtual bool evalActiveVoxelBoundingBox(CoordBBox& bbox) const; + virtual bool evalActiveVoxelDim(Coord& dim) const; + virtual bool evalLeafDim(Coord& dim) const; + + /// @brief Traverse the type hierarchy of nodes, and return, in @a dims, a list + /// of the Log2Dims of nodes in order from RootNode to LeafNode. + /// @note Because RootNodes are resizable, the RootNode Log2Dim is 0 for all trees. + static void getNodeLog2Dims(std::vector& dims); + + + // + // I/O methods + // + /// @brief Read the tree topology from a stream. + /// + /// This will read the tree structure and tile values, but not voxel data. + virtual void readTopology(std::istream&, bool saveFloatAsHalf = false); + /// @brief Write the tree topology to a stream. + /// + /// This will write the tree structure and tile values, but not voxel data. + virtual void writeTopology(std::ostream&, bool saveFloatAsHalf = false) const; + /// Read all data buffers for this tree. + virtual void readBuffers(std::istream&, bool saveFloatAsHalf = false); + /// Write out all data buffers for this tree. + virtual void writeBuffers(std::ostream&, bool saveFloatAsHalf = false) const; + + virtual void print(std::ostream& os = std::cout, int verboseLevel = 1) const; + + + // + // Statistics + // + /// @brief Return the depth of this tree. + /// + /// A tree with only a root node and leaf nodes has depth 2, for example. + virtual Index treeDepth() const { return DEPTH; } + /// Return the number of leaf nodes. + virtual Index32 leafCount() const { return mRoot.leafCount(); } + /// Return the number of non-leaf nodes. + virtual Index32 nonLeafCount() const { return mRoot.nonLeafCount(); } + /// Return the number of active voxels stored in leaf nodes. + virtual Index64 activeLeafVoxelCount() const { return mRoot.onLeafVoxelCount(); } + /// Return the number of inactive voxels stored in leaf nodes. + virtual Index64 inactiveLeafVoxelCount() const { return mRoot.offLeafVoxelCount(); } + /// Return the total number of active voxels. + virtual Index64 activeVoxelCount() const { return mRoot.onVoxelCount(); } + /// Return the number of inactive voxels within the bounding box of all active voxels. + virtual Index64 inactiveVoxelCount() const; + + /// Return the total number of active tiles. + /// @note This method is not virtual so as to not change the ABI. + Index64 activeTileCount() const { return mRoot.onTileCount(); } + + /// Return the minimum and maximum active values in this tree. + void evalMinMax(ValueType &min, ValueType &max) const; + + virtual Index64 memUsage() const { return sizeof(*this) + mRoot.memUsage(); } + + + // + // Voxel access methods (using signed indexing) + // + /// Return the value of the voxel at the given coordinates. + const ValueType& getValue(const Coord& xyz) const; + /// @brief Return the value of the voxel at the given coordinates + /// and update the given accessor's node cache. + template const ValueType& getValue(const Coord& xyz, AccessT&) const; + + /// @brief Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides. + /// @details If (x, y, z) isn't explicitly represented in the tree (i.e., it is + /// implicitly a background voxel), return -1. + int getValueDepth(const Coord& xyz) const; + + /// Set the active state of the voxel at the given coordinates but don't change its value. + void setActiveState(const Coord& xyz, bool on); + /// Set the value of the voxel at the given coordinates but don't change its active state. + void setValueOnly(const Coord& xyz, const ValueType& value); + /// Mark the voxel at the given coordinates as active but don't change its value. + void setValueOn(const Coord& xyz); + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValueOn(const Coord& xyz, const ValueType& value); + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValue(const Coord& xyz, const ValueType& value); + /// @brief Set the value of the voxel at the given coordinates, mark the voxel as active, + /// and update the given accessor's node cache. + template void setValue(const Coord& xyz, const ValueType& value, AccessT&); + /// Mark the voxel at the given coordinates as inactive but don't change its value. + void setValueOff(const Coord& xyz); + /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. + void setValueOff(const Coord& xyz, const ValueType& value); + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + /// @details Provided that the functor can be inlined, this is typically + /// significantly faster than calling getValue() followed by setValueOn(). + /// @param xyz the coordinates of a voxel whose value is to be modified + /// @param op a functor of the form void op(ValueType&) const that modifies + /// its argument in place + /// @par Example: + /// @code + /// Coord xyz(1, 0, -2); + /// // Multiply the value of a voxel by a constant and mark the voxel as active. + /// floatTree.modifyValue(xyz, [](float& f) { f *= 0.25; }); // C++11 + /// // Set the value of a voxel to the maximum of its current value and 0.25, + /// // and mark the voxel as active. + /// floatTree.modifyValue(xyz, [](float& f) { f = std::max(f, 0.25f); }); // C++11 + /// @endcode + /// @note The functor is not guaranteed to be called only once. + /// @see tools::foreach() + template + void modifyValue(const Coord& xyz, const ModifyOp& op); + + /// @brief Apply a functor to the voxel at the given coordinates. + /// @details Provided that the functor can be inlined, this is typically + /// significantly faster than calling getValue() followed by setValue(). + /// @param xyz the coordinates of a voxel to be modified + /// @param op a functor of the form void op(ValueType&, bool&) const that + /// modifies its arguments, a voxel's value and active state, in place + /// @par Example: + /// @code + /// Coord xyz(1, 0, -2); + /// // Multiply the value of a voxel by a constant and mark the voxel as inactive. + /// floatTree.modifyValueAndActiveState(xyz, + /// [](float& f, bool& b) { f *= 0.25; b = false; }); // C++11 + /// // Set the value of a voxel to the maximum of its current value and 0.25, + /// // but don't change the voxel's active state. + /// floatTree.modifyValueAndActiveState(xyz, + /// [](float& f, bool&) { f = std::max(f, 0.25f); }); // C++11 + /// @endcode + /// @note The functor is not guaranteed to be called only once. + /// @see tools::foreach() + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op); + + /// @brief Get the value of the voxel at the given coordinates. + /// @return @c true if the value is active. + bool probeValue(const Coord& xyz, ValueType& value) const; + + /// Return @c true if the value at the given coordinates is active. + bool isValueOn(const Coord& xyz) const { return mRoot.isValueOn(xyz); } + /// Return @c true if the value at the given coordinates is inactive. + bool isValueOff(const Coord& xyz) const { return !this->isValueOn(xyz); } + /// Return @c true if this tree has any active tiles. + bool hasActiveTiles() const { return mRoot.hasActiveTiles(); } + + /// @brief Set all voxels within a given axis-aligned box to a constant value. + /// If necessary, subdivide tiles that intersect the box. + /// @param bbox inclusive coordinates of opposite corners of an axis-aligned box + /// @param value the value to which to set voxels within the box + /// @param active if true, mark voxels within the box as active, + /// otherwise mark them as inactive + /// @note This operation generates a sparse, but not always optimally sparse, + /// representation of the filled box. Follow fill operations with a prune() + /// operation for optimal sparseness. + void fill(const CoordBBox& bbox, const ValueType& value, bool active = true); + + /// @brief Call the @c PruneOp functor for each non-root node in the tree. + /// If the functor returns @c true, prune the node and replace it with a tile. + /// + /// This method is used to implement all of the various pruning algorithms + /// (prune(), pruneInactive(), etc.). It should rarely be called directly. + /// @see openvdb/tree/Util.h for the definition of the @c PruneOp functor + template void pruneOp(PruneOp&); + + /// @brief Reduce the memory footprint of this tree by replacing with tiles + /// any nodes whose values are all the same (optionally to within a tolerance) + /// and have the same active state. + void prune(const ValueType& tolerance = zeroVal()); + + /// @brief Reduce the memory footprint of this tree by replacing with + /// tiles of the given value any nodes whose values are all inactive. + void pruneInactive(const ValueType&); + + /// @brief Reduce the memory footprint of this tree by replacing with + /// background tiles any nodes whose values are all inactive. + void pruneInactive(); + + /// @brief Reduce the memory footprint of this tree by replacing nodes + /// whose values are all inactive with inactive tiles having a value equal to + /// the first value encountered in the (inactive) child. + /// @details This method is faster than tolerance-based prune and + /// useful for narrow-band level set applications where inactive + /// values are limited to either an inside or an outside value. + void pruneLevelSet(); + + /// @brief Add the given leaf node to this tree, creating a new branch if necessary. + /// If a leaf node with the same origin already exists, replace it. + void addLeaf(LeafNodeType& leaf) { mRoot.addLeaf(&leaf); } + + /// @brief Add a tile containing voxel (x, y, z) at the specified tree level, + /// creating a new branch if necessary. Delete any existing lower-level nodes + /// that contain (x, y, z). + /// @note @a level must be less than this tree's depth. + void addTile(Index level, const Coord& xyz, const ValueType& value, bool active); + + /// @brief Return a pointer to the node of type @c NodeT that contains voxel (x, y, z) + /// and replace it with a tile of the specified value and state. + /// If no such node exists, leave the tree unchanged and return @c NULL. + /// @note The caller takes ownership of the node and is responsible for deleting it. + template + NodeT* stealNode(const Coord& xyz, const ValueType& value, bool active); + + /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). + /// If no such node exists, create one that preserves the values and + /// active states of all voxels. + /// @details Use this method to preallocate a static tree topology over which to + /// safely perform multithreaded processing. + LeafNodeType* touchLeaf(const Coord& xyz); + + //@{ + /// @brief Return a pointer to the node of type @c NodeType that contains + /// voxel (x, y, z). If no such node exists, return NULL. + template NodeType* probeNode(const Coord& xyz); + template const NodeType* probeConstNode(const Coord& xyz) const; + template const NodeType* probeNode(const Coord& xyz) const; + //@} + + //@{ + /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). + /// If no such node exists, return NULL. + LeafNodeType* probeLeaf(const Coord& xyz); + const LeafNodeType* probeConstLeaf(const Coord& xyz) const; + const LeafNodeType* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } + //@} + + + // + // Aux methods + // + /// @brief Return @c true if this tree contains no nodes other than + /// the root node and no tiles other than background tiles. + bool empty() const { return mRoot.empty(); } + + /// Remove all tiles from this tree and all nodes other than the root node. + void clear() { this->clearAllAccessors(); mRoot.clear(); } + + /// Clear all registered accessors. + void clearAllAccessors(); + + //@{ + /// @brief Register an accessor for this tree. Registered accessors are + /// automatically cleared whenever one of this tree's nodes is deleted. + void attachAccessor(ValueAccessorBase&) const; + void attachAccessor(ValueAccessorBase&) const; + //@} + //@{ + /// Deregister an accessor so that it is no longer automatically cleared. + void releaseAccessor(ValueAccessorBase&) const; + void releaseAccessor(ValueAccessorBase&) const; + //@} + + /// @brief Return this tree's background value wrapped as metadata. + /// @note Query the metadata object for the value's type. + virtual Metadata::Ptr getBackgroundValue() const; + + /// Return this tree's background value. + const ValueType& background() const { return mRoot.background(); } + /// Replace this tree's background value. + void setBackground(const ValueType& background) { mRoot.setBackground(background); } + + /// Min and max are both inclusive. + virtual void getIndexRange(CoordBBox& bbox) const { mRoot.getIndexRange(bbox); } + + /// @brief Set the values of all inactive voxels and tiles of a narrow-band + /// level set from the signs of the active voxels, setting outside values to + /// +background and inside values to -background. + /// @warning This method should only be used on closed, narrow-band level sets. + void signedFloodFill() { mRoot.signedFloodFill(); } + + /// @brief Set the values of all inactive voxels and tiles of a narrow-band + /// level set from the signs of the active voxels, setting exterior values to + /// @a outside and interior values to @a inside. Set the background value + /// of this tree to @a outside. + /// @warning This method should only be used on closed, narrow-band level sets. + void signedFloodFill(const ValueType& outside, const ValueType& inside); + + /// Densify active tiles, i.e., replace them with leaf-level active voxels. + void voxelizeActiveTiles(); + + /// @brief Efficiently merge another tree into this tree using one of several schemes. + /// @details This operation is primarily intended to combine trees that are mostly + /// non-overlapping (for example, intermediate trees from computations that are + /// parallelized across disjoint regions of space). + /// @note This operation is not guaranteed to produce an optimally sparse tree. + /// Follow merge() with prune() for optimal sparseness. + /// @warning This operation always empties the other tree. + void merge(Tree& other, MergePolicy = MERGE_ACTIVE_STATES); + + /// @brief Union this tree's set of active values with the active values + /// of the other tree, whose @c ValueType may be different. + /// @details The resulting state of a value is active if the corresponding value + /// was already active OR if it is active in the other tree. Also, a resulting + /// value maps to a voxel if the corresponding value already mapped to a voxel + /// OR if it is a voxel in the other tree. Thus, a resulting value can only + /// map to a tile if the corresponding value already mapped to a tile + /// AND if it is a tile value in other tree. + /// + /// @note This operation modifies only active states, not values. + /// Specifically, active tiles and voxels in this tree are not changed, and + /// tiles or voxels that were inactive in this tree but active in the other tree + /// are marked as active in this tree but left with their original values. + template + void topologyUnion(const Tree& other); + + /// @brief Intersects this tree's set of active values with the active values + /// of the other tree, whose @c ValueType may be different. + /// @details The resulting state of a value is active only if the corresponding + /// value was already active AND if it is active in the other tree. Also, a + /// resulting value maps to a voxel if the corresponding value + /// already mapped to an active voxel in either of the two grids + /// and it maps to an active tile or voxel in the other grid. + /// + /// @note This operation can delete branches in this grid if they + /// overlap with inactive tiles in the other grid. Likewise active + /// voxels can be turned into unactive voxels resulting in leaf + /// nodes with no active values. Thus, it is recommended to + /// subsequently call prune. + template + void topologyIntersection(const Tree& other); + + /// @brief Difference this tree's set of active values with the active values + /// of the other tree, whose @c ValueType may be different. So a + /// resulting voxel will be active only if the original voxel is + /// active in this tree and inactive in the other tree. + /// + /// @note This operation can delete branches in this grid if they + /// overlap with active tiles in the other grid. Likewise active + /// voxels can be turned into inactive voxels resulting in leaf + /// nodes with no active values. Thus, it is recommended to + /// subsequently call prune. + template + void topologyDifference(const Tree& other); + + /// For a given function @c f, use sparse traversal to compute f(this, other) + /// over all corresponding pairs of values (tile or voxel) of this tree and the other tree + /// and store the result in this tree. + /// This method is typically more space-efficient than the two-tree combine2(), + /// since it moves rather than copies nodes from the other tree into this tree. + /// @note This operation always empties the other tree. + /// @param other a tree of the same type as this tree + /// @param op a functor of the form void op(const T& a, const T& b, T& result), + /// where @c T is this tree's @c ValueType, that computes + /// result = f(a, b) + /// @param prune if true, prune the resulting tree one branch at a time (this is usually + /// more space-efficient than pruning the entire tree in one pass) + /// + /// @par Example: + /// Compute the per-voxel difference between two floating-point trees, + /// @c aTree and @c bTree, and store the result in @c aTree (leaving @c bTree empty). + /// @code + /// { + /// struct Local { + /// static inline void diff(const float& a, const float& b, float& result) { + /// result = a - b; + /// } + /// }; + /// aTree.combine(bTree, Local::diff); + /// } + /// @endcode + /// + /// @par Example: + /// Compute f * a + (1 - f) * b over all voxels of two floating-point trees, + /// @c aTree and @c bTree, and store the result in @c aTree (leaving @c bTree empty). + /// @code + /// namespace { + /// struct Blend { + /// Blend(float f): frac(f) {} + /// inline void operator()(const float& a, const float& b, float& result) const { + /// result = frac * a + (1.0 - frac) * b; + /// } + /// float frac; + /// }; + /// } + /// { + /// aTree.combine(bTree, Blend(0.25)); // 0.25 * a + 0.75 * b + /// } + /// @endcode + template + void combine(Tree& other, CombineOp& op, bool prune = false); +#ifndef _MSC_VER + template + void combine(Tree& other, const CombineOp& op, bool prune = false); +#endif + + /// Like combine(), but with + /// @param other a tree of the same type as this tree + /// @param op a functor of the form void op(CombineArgs& args) that + /// computes args.setResult(f(args.a(), args.b())) and, optionally, + /// args.setResultIsActive(g(args.aIsActive(), args.bIsActive())) + /// for some functions @c f and @c g + /// @param prune if true, prune the resulting tree one branch at a time (this is usually + /// more space-efficient than pruning the entire tree in one pass) + /// + /// This variant passes not only the @em a and @em b values but also the active states + /// of the @em a and @em b values to the functor, which may then return, by calling + /// @c args.setResultIsActive(), a computed active state for the result value. + /// By default, the result is active if either the @em a or the @em b value is active. + /// + /// @see openvdb/Types.h for the definition of the CombineArgs struct. + /// + /// @par Example: + /// Replace voxel values in floating-point @c aTree with corresponding values + /// from floating-point @c bTree (leaving @c bTree empty) wherever the @c bTree + /// values are larger. Also, preserve the active states of any transferred values. + /// @code + /// { + /// struct Local { + /// static inline void max(CombineArgs& args) { + /// if (args.b() > args.a()) { + /// // Transfer the B value and its active state. + /// args.setResult(args.b()); + /// args.setResultIsActive(args.bIsActive()); + /// } else { + /// // Preserve the A value and its active state. + /// args.setResult(args.a()); + /// args.setResultIsActive(args.aIsActive()); + /// } + /// } + /// }; + /// aTree.combineExtended(bTree, Local::max); + /// } + /// @endcode + template + void combineExtended(Tree& other, ExtendedCombineOp& op, bool prune = false); +#ifndef _MSC_VER + template + void combineExtended(Tree& other, const ExtendedCombineOp& op, bool prune = false); +#endif + + /// For a given function @c f, use sparse traversal to compute f(a, b) over all + /// corresponding pairs of values (tile or voxel) of trees A and B and store the result + /// in this tree. + /// @param a,b two trees 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 + /// { + /// typedef typename TreeT::RootNodeType RootT; + /// bool visitedRoot; + /// + /// PrintTreeVisitor(): visitedRoot(false) {} + /// + /// template + /// inline bool operator()(IterT& iter) + /// { + /// if (!visitedRoot && iter.parent().getLevel() == RootT::LEVEL) { + /// visitedRoot = true; + /// std::cout << "Level-" << RootT::LEVEL << " node" << std::endl; + /// } + /// typename IterT::NonConstValueType value; + /// typename IterT::ChildNodeType* child = iter.probeChild(value); + /// if (child == NULL) { + /// std::cout << "Tile with value " << value << std::endl; + /// return true; // no child to visit, so stop descending + /// } + /// std::cout << "Level-" << child->getLevel() << " node" << std::endl; + /// return (child->getLevel() == 0); // don't visit leaf nodes + /// } + /// + /// // The generic method, above, calls iter.probeChild(), which is not defined + /// // for LeafNode::ChildAllIter. These overloads ensure that the generic + /// // method template doesn't get instantiated for LeafNode iterators. + /// bool operator()(typename TreeT::LeafNodeType::ChildAllIter&) { return true; } + /// bool operator()(typename TreeT::LeafNodeType::ChildAllCIter&) { return true; } + /// }; + /// } + /// { + /// PrintTreeVisitor visitor; + /// tree.visit(visitor); + /// } + /// @endcode + template void visit(VisitorOp& op); + template void visit(const VisitorOp& op); + + /// Like visit(), but using @c const iterators, i.e., with + /// @param op a functor of the form template bool op(IterT&), + /// where @c IterT is either a RootNode::ChildAllCIter, + /// an InternalNode::ChildAllCIter or a LeafNode::ChildAllCIter + template void visit(VisitorOp& op) const; + template void visit(const VisitorOp& op) const; + + /// Traverse this tree and another tree in depth-first order, and for corresponding + /// subregions of index space call the given functor with two @c DenseIterators + /// (see Iterator.h), each of which points to either a child node or a tile value + /// of this tree and the other tree. If the A iterator points to a child node + /// and the functor returns a nonzero value with bit 0 set (e.g., 1), do not descend + /// to the child node; instead, continue the traversal at the next A iterator position. + /// Similarly, if the B iterator points to a child node and the functor returns a value + /// with bit 1 set (e.g., 2), continue the traversal at the next B iterator position. + /// @note The other tree must have the same index space and fan-out factors as + /// this tree, but it may have a different @c ValueType and a different topology. + /// @param other a tree of the same type as this tree + /// @param op a functor of the form + /// template int op(AIterT&, BIterT&), + /// where @c AIterT and @c BIterT are any combination of a + /// RootNode::ChildAllIter, an InternalNode::ChildAllIter or a + /// LeafNode::ChildAllIter with an @c OtherTreeType::RootNode::ChildAllIter, + /// an @c OtherTreeType::InternalNode::ChildAllIter + /// or an @c OtherTreeType::LeafNode::ChildAllIter + /// + /// @par Example: + /// Given two trees of the same type, @c aTree and @c bTree, replace leaf nodes of + /// @c aTree with corresponding leaf nodes of @c bTree, leaving @c bTree partially empty. + /// @code + /// namespace { + /// template + /// inline int stealLeafNodes(AIterT& aIter, BIterT& bIter) + /// { + /// typename AIterT::NonConstValueType aValue; + /// typename AIterT::ChildNodeType* aChild = aIter.probeChild(aValue); + /// typename BIterT::NonConstValueType bValue; + /// typename BIterT::ChildNodeType* bChild = bIter.probeChild(bValue); + /// + /// const Index aLevel = aChild->getLevel(), bLevel = bChild->getLevel(); + /// if (aChild && bChild && aLevel == 0 && bLevel == 0) { // both are leaf nodes + /// aIter.setChild(bChild); // give B's child to A + /// bIter.setValue(bValue); // replace B's child with a constant tile value + /// } + /// // Don't iterate over leaf node voxels of either A or B. + /// int skipBranch = (aLevel == 0) ? 1 : 0; + /// if (bLevel == 0) skipBranch = skipBranch | 2; + /// return skipBranch; + /// } + /// } + /// { + /// aTree.visit2(bTree, stealLeafNodes); + /// } + /// @endcode + template + void visit2(OtherTreeType& other, VisitorOp& op); + template + void visit2(OtherTreeType& other, const VisitorOp& op); + + /// Like visit2(), but using @c const iterators, i.e., with + /// @param other a tree of the same type as this tree + /// @param op a functor of the form + /// template int op(AIterT&, BIterT&), + /// where @c AIterT and @c BIterT are any combination of a + /// RootNode::ChildAllCIter, an InternalNode::ChildAllCIter + /// or a LeafNode::ChildAllCIter with an + /// @c OtherTreeType::RootNode::ChildAllCIter, + /// an @c OtherTreeType::InternalNode::ChildAllCIter + /// or an @c OtherTreeType::LeafNode::ChildAllCIter + template + void visit2(OtherTreeType& other, VisitorOp& op) const; + template + void visit2(OtherTreeType& other, const VisitorOp& op) const; + + + // + // Iteration + // + //@{ + /// Return an iterator over children of the root node. + typename RootNodeType::ChildOnCIter beginRootChildren() const { return mRoot.cbeginChildOn(); } + typename RootNodeType::ChildOnCIter cbeginRootChildren() const { return mRoot.cbeginChildOn(); } + typename RootNodeType::ChildOnIter beginRootChildren() { return mRoot.beginChildOn(); } + //@} + + //@{ + /// Return an iterator over non-child entries of the root node's table. + typename RootNodeType::ChildOffCIter beginRootTiles() const { return mRoot.cbeginChildOff(); } + typename RootNodeType::ChildOffCIter cbeginRootTiles() const { return mRoot.cbeginChildOff(); } + typename RootNodeType::ChildOffIter beginRootTiles() { return mRoot.beginChildOff(); } + //@} + + //@{ + /// Return an iterator over all entries of the root node's table. + typename RootNodeType::ChildAllCIter beginRootDense() const { return mRoot.cbeginChildAll(); } + typename RootNodeType::ChildAllCIter cbeginRootDense() const { return mRoot.cbeginChildAll(); } + typename RootNodeType::ChildAllIter beginRootDense() { return mRoot.beginChildAll(); } + //@} + + + //@{ + /// Iterator over all nodes in this tree + typedef NodeIteratorBase NodeIter; + typedef NodeIteratorBase NodeCIter; + //@} + + //@{ + /// Iterator over all leaf nodes in this tree + typedef LeafIteratorBase LeafIter; + typedef LeafIteratorBase LeafCIter; + //@} + + //@{ + /// Return an iterator over all nodes in this tree. + NodeIter beginNode() { return NodeIter(*this); } + NodeCIter beginNode() const { return NodeCIter(*this); } + NodeCIter cbeginNode() const { return NodeCIter(*this); } + //@} + + //@{ + /// Return an iterator over all leaf nodes in this tree. + LeafIter beginLeaf() { return LeafIter(*this); } + LeafCIter beginLeaf() const { return LeafCIter(*this); } + LeafCIter cbeginLeaf() const { return LeafCIter(*this); } + //@} + + typedef TreeValueIteratorBase ValueAllIter; + typedef TreeValueIteratorBase ValueAllCIter; + typedef TreeValueIteratorBase ValueOnIter; + typedef TreeValueIteratorBase ValueOnCIter; + typedef TreeValueIteratorBase ValueOffIter; + typedef TreeValueIteratorBase ValueOffCIter; + + //@{ + /// Return an iterator over all values (tile and voxel) across all nodes. + ValueAllIter beginValueAll() { return ValueAllIter(*this); } + ValueAllCIter beginValueAll() const { return ValueAllCIter(*this); } + ValueAllCIter cbeginValueAll() const { return ValueAllCIter(*this); } + //@} + //@{ + /// Return an iterator over active values (tile and voxel) across all nodes. + ValueOnIter beginValueOn() { return ValueOnIter(*this); } + ValueOnCIter beginValueOn() const { return ValueOnCIter(*this); } + ValueOnCIter cbeginValueOn() const { return ValueOnCIter(*this); } + //@} + //@{ + /// Return an iterator over inactive values (tile and voxel) across all nodes. + ValueOffIter beginValueOff() { return ValueOffIter(*this); } + ValueOffCIter beginValueOff() const { return ValueOffCIter(*this); } + ValueOffCIter cbeginValueOff() const { return ValueOffCIter(*this); } + //@} + + /// @brief Return an iterator of type @c IterT (for example, begin() is + /// equivalent to beginValueOn()). + template IterT begin(); + /// @brief Return a const iterator of type CIterT (for example, cbegin() + /// is equivalent to cbeginValueOn()). + template CIterT cbegin() const; + + +protected: + typedef tbb::concurrent_hash_map*, bool> AccessorRegistry; + typedef tbb::concurrent_hash_map*, bool> ConstAccessorRegistry; + + // Disallow assignment of instances of this class. + Tree& operator=(const Tree&); + + /// @brief Notify all registered accessors, by calling ValueAccessor::release(), + /// that this tree is about to be deleted. + void releaseAllAccessors(); + + + // + // Data members + // + RootNodeType mRoot; // root node of the tree + mutable AccessorRegistry mAccessorRegistry; + mutable ConstAccessorRegistry mConstAccessorRegistry; +}; // end of Tree class + + +/// @brief Tree3::Type is the type of a three-level tree +/// (Root, Internal, Leaf) with value type T and +/// internal and leaf node log dimensions N1 and N2, respectively. +/// @note This is NOT the standard tree configuration (Tree4 is). +template +struct Tree3 { + typedef Tree, N1> > > Type; +}; + + +/// @brief Tree4::Type is the type of a four-level tree +/// (Root, Internal, Internal, Leaf) with value type T and +/// internal and leaf node log dimensions N1, N2 and N3, respectively. +/// @note This is the standard tree configuration. +template +struct Tree4 { + typedef Tree, N2>, N1> > > Type; +}; + + +/// @brief Tree5::Type is the type of a five-level tree +/// (Root, Internal, Internal, Internal, Leaf) with value type T and +/// internal and leaf node log dimensions N1, N2, N3 and N4, respectively. +/// @note This is NOT the standard tree configuration (Tree4 is). +template +struct Tree5 { + typedef Tree, N3>, N2>, N1> > > + Type; +}; + + +//////////////////////////////////////// + + +inline void +TreeBase::readTopology(std::istream& is, bool /*saveFloatAsHalf*/) +{ + int32_t bufferCount; + is.read(reinterpret_cast(&bufferCount), sizeof(int32_t)); + if (bufferCount != 1) OPENVDB_LOG_WARN("multi-buffer trees are no longer supported"); +} + + +inline void +TreeBase::writeTopology(std::ostream& os, bool /*saveFloatAsHalf*/) const +{ + int32_t bufferCount = 1; + os.write(reinterpret_cast(&bufferCount), sizeof(int32_t)); +} + + +inline void +TreeBase::print(std::ostream& os, int /*verboseLevel*/) const +{ + os << " Tree Type: " << type() + << " Active Voxel Count: " << activeVoxelCount() << std::endl + << " Inactive Voxel Count: " << inactiveVoxelCount() << std::endl + << " Leaf Node Count: " << leafCount() << std::endl + << " Non-leaf Node Count: " << nonLeafCount() << std::endl; +} + + +//////////////////////////////////////// + + +// +// Type traits for tree iterators +// + +/// @brief TreeIterTraits provides, for all tree iterators, a begin(tree) function +/// that returns an iterator over a tree of arbitrary type. +template struct TreeIterTraits; + +template struct TreeIterTraits { + static typename TreeT::RootNodeType::ChildOnIter begin(TreeT& tree) { + return tree.beginRootChildren(); + } +}; + +template struct TreeIterTraits { + static typename TreeT::RootNodeType::ChildOnCIter begin(const TreeT& tree) { + return tree.cbeginRootChildren(); + } +}; + +template struct TreeIterTraits { + static typename TreeT::RootNodeType::ChildOffIter begin(TreeT& tree) { + return tree.beginRootTiles(); + } +}; + +template struct TreeIterTraits { + static typename TreeT::RootNodeType::ChildOffCIter begin(const TreeT& tree) { + return tree.cbeginRootTiles(); + } +}; + +template struct TreeIterTraits { + static typename TreeT::RootNodeType::ChildAllIter begin(TreeT& tree) { + return tree.beginRootDense(); + } +}; + +template struct TreeIterTraits { + static typename TreeT::RootNodeType::ChildAllCIter begin(const TreeT& tree) { + return tree.cbeginRootDense(); + } +}; + +template struct TreeIterTraits { + static typename TreeT::NodeIter begin(TreeT& tree) { return tree.beginNode(); } +}; + +template struct TreeIterTraits { + static typename TreeT::NodeCIter begin(const TreeT& tree) { return tree.cbeginNode(); } +}; + +template struct TreeIterTraits { + static typename TreeT::LeafIter begin(TreeT& tree) { return tree.beginLeaf(); } +}; + +template struct TreeIterTraits { + static typename TreeT::LeafCIter begin(const TreeT& tree) { return tree.cbeginLeaf(); } +}; + +template struct TreeIterTraits { + static typename TreeT::ValueOnIter begin(TreeT& tree) { return tree.beginValueOn(); } +}; + +template struct TreeIterTraits { + static typename TreeT::ValueOnCIter begin(const TreeT& tree) { return tree.cbeginValueOn(); } +}; + +template struct TreeIterTraits { + static typename TreeT::ValueOffIter begin(TreeT& tree) { return tree.beginValueOff(); } +}; + +template struct TreeIterTraits { + static typename TreeT::ValueOffCIter begin(const TreeT& tree) { return tree.cbeginValueOff(); } +}; + +template struct TreeIterTraits { + static typename TreeT::ValueAllIter begin(TreeT& tree) { return tree.beginValueAll(); } +}; + +template struct TreeIterTraits { + static typename TreeT::ValueAllCIter begin(const TreeT& tree) { return tree.cbeginValueAll(); } +}; + + +template +template +inline IterT +Tree::begin() +{ + return TreeIterTraits::begin(*this); +} + + +template +template +inline IterT +Tree::cbegin() const +{ + return TreeIterTraits::begin(*this); +} + + +//////////////////////////////////////// + + +template +void +Tree::readTopology(std::istream& is, bool saveFloatAsHalf) +{ + this->clearAllAccessors(); + TreeBase::readTopology(is, saveFloatAsHalf); + mRoot.readTopology(is, saveFloatAsHalf); +} + + +template +void +Tree::writeTopology(std::ostream& os, bool saveFloatAsHalf) const +{ + TreeBase::writeTopology(os, saveFloatAsHalf); + mRoot.writeTopology(os, saveFloatAsHalf); +} + + +template +inline void +Tree::readBuffers(std::istream &is, bool saveFloatAsHalf) +{ + this->clearAllAccessors(); + mRoot.readBuffers(is, saveFloatAsHalf); +} + + +template +inline void +Tree::writeBuffers(std::ostream &os, bool saveFloatAsHalf) const +{ + mRoot.writeBuffers(os, saveFloatAsHalf); +} + + +//////////////////////////////////////// + + +template +inline void +Tree::attachAccessor(ValueAccessorBase& accessor) const +{ + typename AccessorRegistry::accessor a; + mAccessorRegistry.insert(a, &accessor); +} + + +template +inline void +Tree::attachAccessor(ValueAccessorBase& accessor) const +{ + typename ConstAccessorRegistry::accessor a; + mConstAccessorRegistry.insert(a, &accessor); +} + + +template +inline void +Tree::releaseAccessor(ValueAccessorBase& accessor) const +{ + mAccessorRegistry.erase(&accessor); +} + + +template +inline void +Tree::releaseAccessor(ValueAccessorBase& accessor) const +{ + mConstAccessorRegistry.erase(&accessor); +} + + +template +inline void +Tree::clearAllAccessors() +{ + for (typename AccessorRegistry::iterator it = mAccessorRegistry.begin(); + it != mAccessorRegistry.end(); ++it) + { + if (it->first) it->first->clear(); + } + + for (typename ConstAccessorRegistry::iterator it = mConstAccessorRegistry.begin(); + it != mConstAccessorRegistry.end(); ++it) + { + if (it->first) it->first->clear(); + } +} + + +template +inline void +Tree::releaseAllAccessors() +{ + mAccessorRegistry.erase(NULL); + for (typename AccessorRegistry::iterator it = mAccessorRegistry.begin(); + it != mAccessorRegistry.end(); ++it) + { + it->first->release(); + } + mAccessorRegistry.clear(); + + mAccessorRegistry.erase(NULL); + for (typename ConstAccessorRegistry::iterator it = mConstAccessorRegistry.begin(); + it != mConstAccessorRegistry.end(); ++it) + { + it->first->release(); + } + mConstAccessorRegistry.clear(); +} + + +//////////////////////////////////////// + + +template +inline const typename RootNodeType::ValueType& +Tree::getValue(const Coord& xyz) const +{ + return mRoot.getValue(xyz); +} + + +template +template +inline const typename RootNodeType::ValueType& +Tree::getValue(const Coord& xyz, AccessT& accessor) const +{ + return accessor.getValue(xyz); +} + + +template +inline int +Tree::getValueDepth(const Coord& xyz) const +{ + return mRoot.getValueDepth(xyz); +} + + +template +inline void +Tree::setValueOff(const Coord& xyz) +{ + mRoot.setValueOff(xyz); +} + + +template +inline void +Tree::setValueOff(const Coord& xyz, const ValueType& value) +{ + mRoot.setValueOff(xyz, value); +} + + +template +inline void +Tree::setActiveState(const Coord& xyz, bool on) +{ + mRoot.setActiveState(xyz, on); +} + + +template +inline void +Tree::setValue(const Coord& xyz, const ValueType& value) +{ + mRoot.setValueOn(xyz, value); +} + +template +inline void +Tree::setValueOnly(const Coord& xyz, const ValueType& value) +{ + mRoot.setValueOnly(xyz, value); +} + +template +template +inline void +Tree::setValue(const Coord& xyz, const ValueType& value, AccessT& accessor) +{ + accessor.setValue(xyz, value); +} + + +template +inline void +Tree::setValueOn(const Coord& xyz) +{ + mRoot.setActiveState(xyz, true); +} + + +template +inline void +Tree::setValueOn(const Coord& xyz, const ValueType& value) +{ + mRoot.setValueOn(xyz, value); +} + + +template +template +inline void +Tree::modifyValue(const Coord& xyz, const ModifyOp& op) +{ + mRoot.modifyValue(xyz, op); +} + + +template +template +inline void +Tree::modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) +{ + mRoot.modifyValueAndActiveState(xyz, op); +} + + +template +inline bool +Tree::probeValue(const Coord& xyz, ValueType& value) const +{ + return mRoot.probeValue(xyz, value); +} + + +//////////////////////////////////////// + + +template +template +inline void +Tree::pruneOp(PruneOp& op) +{ + this->clearAllAccessors(); + mRoot.pruneOp(op); +} + + +template +inline void +Tree::prune(const ValueType& tolerance) +{ + TolerancePrune op(tolerance); + this->pruneOp(op); +} + + +template +inline void +Tree::pruneInactive(const ValueType& bg) +{ + InactivePrune op(bg); + this->pruneOp(op); +} + + +template +inline void +Tree::pruneInactive() +{ + this->pruneInactive(this->background()); +} + + +template +inline void +Tree::pruneLevelSet() +{ + LevelSetPrune op(this->background()); + this->pruneOp(op); +} + + +template +inline void +Tree::addTile(Index level, const Coord& xyz, + const ValueType& value, bool active) +{ + mRoot.addTile(level, xyz, value, active); +} + + +template +template +inline NodeT* +Tree::stealNode(const Coord& xyz, const ValueType& value, bool active) +{ + this->clearAllAccessors(); + return mRoot.template stealNode(xyz, value, active); +} + + +template +inline typename RootNodeType::LeafNodeType* +Tree::touchLeaf(const Coord& xyz) +{ + return mRoot.touchLeaf(xyz); +} + + +template +inline typename RootNodeType::LeafNodeType* +Tree::probeLeaf(const Coord& xyz) +{ + return mRoot.probeLeaf(xyz); +} + + +template +inline const typename RootNodeType::LeafNodeType* +Tree::probeConstLeaf(const Coord& xyz) const +{ + return mRoot.probeConstLeaf(xyz); +} + + +template +template +inline NodeType* +Tree::probeNode(const Coord& xyz) +{ + return mRoot.template probeNode(xyz); +} + + +template +template +inline const NodeType* +Tree::probeNode(const Coord& xyz) const +{ + return this->template probeConstNode(xyz); +} + + +template +template +inline const NodeType* +Tree::probeConstNode(const Coord& xyz) const +{ + return mRoot.template probeConstNode(xyz); +} + + +//////////////////////////////////////// + + +template +inline void +Tree::fill(const CoordBBox& bbox, const ValueType& value, bool active) +{ + this->clearAllAccessors(); + return mRoot.fill(bbox, value, active); +} + + +template +inline void +Tree::signedFloodFill(const ValueType& outside, const ValueType& inside) +{ + mRoot.signedFloodFill(outside, inside); +} + + +template +Metadata::Ptr +Tree::getBackgroundValue() const +{ + Metadata::Ptr result; + if (Metadata::isRegisteredType(valueType())) { + typedef TypedMetadata MetadataT; + result = Metadata::createMetadata(valueType()); + if (MetadataT* m = dynamic_cast(result.get())) { + m->value() = mRoot.background(); + } + } + return result; +} + + +//////////////////////////////////////// + + +template +inline void +Tree::voxelizeActiveTiles() +{ + this->clearAllAccessors(); + mRoot.voxelizeActiveTiles(); +} + + +template +inline void +Tree::merge(Tree& other, MergePolicy policy) +{ + this->clearAllAccessors(); + other.clearAllAccessors(); + switch (policy) { + case MERGE_ACTIVE_STATES: + mRoot.template merge(other.mRoot); break; + case MERGE_NODES: + mRoot.template merge(other.mRoot); break; + case MERGE_ACTIVE_STATES_AND_NODES: + mRoot.template merge(other.mRoot); break; + } +} + + +template +template +inline void +Tree::topologyUnion(const Tree& other) +{ + this->clearAllAccessors(); + mRoot.topologyUnion(other.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(); + typedef typename OtherTreeType::RootNodeType OtherRootNodeType; + mRoot.template visit2(other.root(), op); +} + + +template +template +inline void +Tree::visit2(OtherTreeType& other, VisitorOp& op) const +{ + typedef typename OtherTreeType::RootNodeType OtherRootNodeType; + 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(); + typedef typename OtherTreeType::RootNodeType OtherRootNodeType; + 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 +{ + typedef typename OtherTreeType::RootNodeType OtherRootNodeType; + mRoot.template visit2(other.root(), op); +} + + +//////////////////////////////////////// + + +template +inline const Name& +Tree::treeType() +{ + static tbb::atomic sTypeName; + if (sTypeName == NULL) { + std::vector dims; + Tree::getNodeLog2Dims(dims); + std::ostringstream ostr; + ostr << "Tree_" << typeNameAsString(); + for (size_t i = 1, N = dims.size(); i < N; ++i) { // start from 1 to skip the RootNode + ostr << "_" << dims[i]; + } + Name* s = new Name(ostr.str()); + if (sTypeName.compare_and_swap(s, NULL) != NULL) delete s; + } + return *sTypeName; +} + + +template +template +inline bool +Tree::hasSameTopology(const Tree& other) const +{ + return mRoot.hasSameTopology(other.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 +{ + minVal = maxVal = zeroVal(); + if (ValueOnCIter iter = this->cbeginValueOn()) { + minVal = maxVal = *iter; + for (++iter; iter; ++iter) { + const ValueType& val = *iter; + if (val < minVal) minVal = val; + if (val > maxVal) maxVal = val; + } + } +} + + +template +inline void +Tree::getNodeLog2Dims(std::vector& dims) +{ + dims.clear(); + RootNodeType::getNodeLog2Dims(dims); +} + + +template +inline void +Tree::print(std::ostream& os, int verboseLevel) const +{ + if (verboseLevel <= 0) return; + + struct OnExit { + std::ostream& os; + std::streamsize savedPrecision; + OnExit(std::ostream& _os): os(_os), savedPrecision(os.precision()) {} + ~OnExit() { os.precision(savedPrecision); } + }; + OnExit restorePrecision(os); + + std::vector dims; + Tree::getNodeLog2Dims(dims); + + std::vector nodeCount; + + os << "Information about Tree:\n" + << " Type: " << this->type() << "\n"; + + os << " Configuration:\n"; + if (verboseLevel <= 1) { + // Print node types and sizes. + os << " Root(" << mRoot.getTableSize() << ")"; + if (dims.size() > 1) { + for (size_t i = 1, N = dims.size() - 1; i < N; ++i) { + os << ", Internal(" << (1 << dims[i]) << "^3)"; + } + os << ", Leaf(" << (1 << *dims.rbegin()) << "^3)\n"; + } + } else { + // Print node types, counts and sizes. + nodeCount.resize(dims.size()); + for (NodeCIter it = cbeginNode(); it; ++it) { + ++(nodeCount[it.getDepth()]); + } + os << " Root(1 x " << mRoot.getTableSize() << ")"; + if (dims.size() > 1) { + for (size_t i = 1, N = dims.size() - 1; i < N; ++i) { + os << ", Internal(" << util::formattedInt(nodeCount[i]); + os << " x " << (1 << dims[i]) << "^3)"; + } + os << ", Leaf(" << util::formattedInt(*nodeCount.rbegin()); + os << " x " << (1 << *dims.rbegin()) << "^3)\n"; + } + } + os << " Background value: " << mRoot.background() << "\n"; + + if (verboseLevel == 1) return; + + // The following is tree information that is expensive to extract. + + if (nodeCount.empty()) { + nodeCount.resize(dims.size()); + for (NodeCIter it = cbeginNode(); it; ++it) { + ++(nodeCount[it.getDepth()]); + } + } + + // Statistics of topology and values + ValueType minVal, maxVal; + this->evalMinMax(minVal, maxVal); + os << " Min value: " << minVal << "\n"; + os << " Max value: " << maxVal << "\n"; + + const uint64_t + leafCount = *nodeCount.rbegin(), + numActiveVoxels = this->activeVoxelCount(), + numActiveLeafVoxels = this->activeLeafVoxelCount(); + + os << " Number of active voxels: " << util::formattedInt(numActiveVoxels) << "\n"; + + Coord dim(0, 0, 0); + uint64_t totalVoxels = 0; + if (numActiveVoxels) { // nonempty + CoordBBox bbox; + this->evalActiveVoxelBoundingBox(bbox); + dim = bbox.extents(); + totalVoxels = dim.x() * uint64_t(dim.y()) * dim.z(); + + os << " Bounding box of active voxels: " << bbox << "\n"; + os << " Dimensions of active voxels: " + << dim[0] << " x " << dim[1] << " x " << dim[2] << "\n"; + + const double activeRatio = (100.0 * numActiveVoxels) / totalVoxels; + os << " Percentage of active voxels: " << std::setprecision(3) << activeRatio << "%\n"; + + if (leafCount>0) { + const double fillRatio = + (100.0 * numActiveLeafVoxels) / (leafCount * LeafNodeType::NUM_VOXELS); + os << " Average leaf node fill ratio: " << fillRatio << "%\n"; + } + } else { + os << " Tree is empty!\n"; + } + os << std::flush; + + if (verboseLevel == 2) return; + + // Memory footprint in bytes + const uint64_t + actualMem = this->memUsage(), + denseMem = sizeof(ValueType) * totalVoxels, + voxelsMem = sizeof(ValueType) * numActiveLeafVoxels; + ///< @todo not accurate for BoolTree (and probably should count tile values) + + os << "Memory footprint:\n"; + util::printBytes(os, actualMem, " Actual footprint: "); + util::printBytes(os, voxelsMem, " Voxel footprint: "); + + if (numActiveVoxels) { + util::printBytes(os, denseMem, " Dense* footprint: "); + os << " Actual footprint is " << (100.0 * actualMem / denseMem) + << "% of dense* footprint\n"; + os << " Leaf voxel footprint is " << (100.0 * voxelsMem / actualMem) + << "% of actual footprint\n"; + os << " *Dense refers to the smallest equivalent non-sparse volume" << std::endl; + } +} + +} // namespace tree +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TREE_TREE_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tree/TreeIterator.h b/openvdb_2_3_0_library/openvdb/tree/TreeIterator.h new file mode 100755 index 0000000..7d5cfba --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tree/TreeIterator.h @@ -0,0 +1,1388 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file TreeIterator.h + +#ifndef OPENVDB_TREE_TREEITERATOR_HAS_BEEN_INCLUDED +#define OPENVDB_TREE_TREEITERATOR_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Prior to 0.96.1, depth-bounded value iterators always descended to the leaf level +// and iterated past leaf nodes. Now, they never descend past the maximum depth. +// Comment out the following line to restore the older, less-efficient behavior: +#define ENABLE_TREE_VALUE_DEPTH_BOUND_OPTIMIZATION + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tree { + +/// CopyConstness::Type is either const T2 or T2 with no const qualifier, +/// depending on whether T1 is const. For example, +/// - CopyConstness::Type is int +/// - CopyConstness::Type is int +/// - CopyConstness::Type is const int +/// - CopyConstness::Type is const int +template struct CopyConstness { + typedef typename boost::remove_const::type Type; +}; +template struct CopyConstness { + typedef const ToType Type; +}; + + +//////////////////////////////////////// + + +namespace iter { + +template +struct InvertedTree { + typedef typename InvertedTree::Type SubtreeT; + typedef typename boost::mpl::push_back::type Type; +}; +template +struct InvertedTree { + typedef typename boost::mpl::vector::type Type; +}; + +} // namespace iter + + +//////////////////////////////////////// + + +/// IterTraits provides the following for iterators of the standard types, +/// i.e., for {Child,Value}{On,Off,All}{Iter,CIter}: +/// - a NodeConverter template to convert an iterator for one type of node +/// to an iterator of the same type for another type of node; for example, +/// IterTraits::NodeConverter::Type +/// is synonymous with LeafNode::ValueOnIter. +/// - a begin(node) function that returns a begin iterator for a node of arbitrary type; +/// for example, IterTraits::begin(leaf) returns +/// leaf.beginValueOn() +/// - a getChild() function that returns a pointer to the child node to which the iterator +/// is currently pointing (always NULL if the iterator is a Value iterator) +template +struct IterTraits +{ + template static ChildT* getChild(const IterT&) { return NULL; } +}; + +template +struct IterTraits +{ + typedef typename NodeT::ChildOnIter IterT; + static IterT begin(NodeT& node) { return node.beginChildOn(); } + template static ChildT* getChild(const IterT& iter) { + return &iter.getValue(); + } + template struct NodeConverter { + typedef typename OtherNodeT::ChildOnIter Type; + }; +}; + +template +struct IterTraits +{ + typedef typename NodeT::ChildOnCIter IterT; + static IterT begin(const NodeT& node) { return node.cbeginChildOn(); } + template static const ChildT* getChild(const IterT& iter) { + return &iter.getValue(); + } + template struct NodeConverter { + typedef typename OtherNodeT::ChildOnCIter Type; + }; +}; + +template +struct IterTraits +{ + typedef typename NodeT::ChildOffIter IterT; + static IterT begin(NodeT& node) { return node.beginChildOff(); } + template struct NodeConverter { + typedef typename OtherNodeT::ChildOffIter Type; + }; +}; + +template +struct IterTraits +{ + typedef typename NodeT::ChildOffCIter IterT; + static IterT begin(const NodeT& node) { return node.cbeginChildOff(); } + template struct NodeConverter { + typedef typename OtherNodeT::ChildOffCIter Type; + }; +}; + +template +struct IterTraits +{ + typedef typename NodeT::ChildAllIter IterT; + static IterT begin(NodeT& node) { return node.beginChildAll(); } + template static ChildT* getChild(const IterT& iter) { + typename IterT::NonConstValueType val; + return iter.probeChild(val); + } + template struct NodeConverter { + typedef typename OtherNodeT::ChildAllIter Type; + }; +}; + +template +struct IterTraits +{ + typedef typename NodeT::ChildAllCIter IterT; + static IterT begin(const NodeT& node) { return node.cbeginChildAll(); } + template static ChildT* getChild(const IterT& iter) { + typename IterT::NonConstValueType val; + return iter.probeChild(val); + } + template struct NodeConverter { + typedef typename OtherNodeT::ChildAllCIter Type; + }; +}; + +template +struct IterTraits +{ + typedef typename NodeT::ValueOnIter IterT; + static IterT begin(NodeT& node) { return node.beginValueOn(); } + template struct NodeConverter { + typedef typename OtherNodeT::ValueOnIter Type; + }; +}; + +template +struct IterTraits +{ + typedef typename NodeT::ValueOnCIter IterT; + static IterT begin(const NodeT& node) { return node.cbeginValueOn(); } + template struct NodeConverter { + typedef typename OtherNodeT::ValueOnCIter Type; + }; +}; + +template +struct IterTraits +{ + typedef typename NodeT::ValueOffIter IterT; + static IterT begin(NodeT& node) { return node.beginValueOff(); } + template struct NodeConverter { + typedef typename OtherNodeT::ValueOffIter Type; + }; +}; + +template +struct IterTraits +{ + typedef typename NodeT::ValueOffCIter IterT; + static IterT begin(const NodeT& node) { return node.cbeginValueOff(); } + template struct NodeConverter { + typedef typename OtherNodeT::ValueOffCIter Type; + }; +}; + +template +struct IterTraits +{ + typedef typename NodeT::ValueAllIter IterT; + static IterT begin(NodeT& node) { return node.beginValueAll(); } + template struct NodeConverter { + typedef typename OtherNodeT::ValueAllIter Type; + }; +}; + +template +struct IterTraits +{ + typedef typename NodeT::ValueAllCIter IterT; + static IterT begin(const NodeT& node) { return node.cbeginValueAll(); } + template struct NodeConverter { + typedef typename OtherNodeT::ValueAllCIter Type; + }; +}; + + +//////////////////////////////////////// + + +/// @brief An IterListItem is an element of a compile-time linked list of iterators +/// to nodes of different types. +/// +/// The list is constructed by traversing the template hierarchy of a Tree in reverse order, +/// so typically the elements will be a LeafNode iterator of some type (e.g., ValueOnCIter), +/// followed by one or more InternalNode iterators of the same type, followed by a RootNode +/// iterator of the same type. +/// +/// The length of the list is fixed at compile time, and because it is implemented using +/// nested, templated classes, much of the list traversal logic can be optimized away. +template +class IterListItem +{ +public: + /// The type of iterator stored in the previous list item + typedef typename PrevItemT::IterT PrevIterT; + /// The type of node (non-const) whose iterator is stored in this list item + typedef typename boost::mpl::front::type _NodeT; + /// The type of iterator stored in this list item (e.g., InternalNode::ValueOnCIter) + typedef typename IterTraits::template + NodeConverter<_NodeT>::Type IterT; + + /// The type of node (const or non-const) over which IterT iterates (e.g., const RootNode<...>) + typedef typename IterT::NodeType NodeT; + /// The type of the node with const qualifiers removed ("Non-Const") + typedef typename IterT::NonConstNodeType NCNodeT; + /// The type of value (with const qualifiers removed) to which the iterator points + typedef typename IterT::NonConstValueType NCValueT; + /// NodeT's child node type, with the same constness (e.g., const InternalNode<...>) + typedef typename CopyConstness::Type ChildT; + /// NodeT's child node type with const qualifiers removed + typedef typename CopyConstness::Type NCChildT; + typedef IterTraits ITraits; + /// NodeT's level in its tree (0 = LeafNode) + static const Index Level = _Level; + + IterListItem(PrevItemT* prev): mNext(this), mPrev(prev) {} + + IterListItem(const IterListItem& other): mIter(other.mIter), mNext(other.mNext), mPrev(NULL) {} + IterListItem& operator=(const IterListItem& other) + { + if (&other != this) { + mIter = other.mIter; + mNext = other.mNext; + mPrev = NULL; ///< @note external call to updateBackPointers() required + } + return *this; + } + + void updateBackPointers(PrevItemT* prev) { mPrev = prev; mNext.updateBackPointers(this); } + + void setIter(const IterT& iter) { mIter = iter; } + template + void setIter(const OtherIterT& iter) { mNext.setIter(iter); } + + /// Return the node over which this list element's iterator iterates. + void getNode(Index lvl, NodeT*& node) const + { + node = (lvl <= Level) ? mIter.getParentNode() : NULL; + } + /// Return the node over which one of the following list elements' iterator iterates. + template + void getNode(Index lvl, OtherNodeT*& node) const { mNext.getNode(lvl, node); } + + /// @brief Initialize the iterator for level @a lvl of the tree with the node + /// over which the corresponding iterator of @a otherListItem is iterating. + /// + /// For example, if @a otherListItem contains a LeafNode::ValueOnIter, + /// initialize this list's leaf iterator with the same LeafNode. + template + void initLevel(Index lvl, OtherIterListItemT& otherListItem) + { + if (lvl == Level) { + const NodeT* node = NULL; + otherListItem.getNode(lvl, node); + mIter = (node == NULL) ? IterT() : ITraits::begin(*const_cast(node)); + } else { + // Forward to one of the following list elements. + mNext.initLevel(lvl, otherListItem); + } + } + + /// Return The table offset of the iterator at level @a lvl of the tree. + Index pos(Index lvl) const { return (lvl == Level) ? mIter.pos() : mNext.pos(lvl); } + + /// Return @c true if the iterator at level @a lvl of the tree has not yet reached its end. + bool test(Index lvl) const { return (lvl == Level) ? mIter.test() : mNext.test(lvl); } + + /// Increment the iterator at level @a lvl of the tree. + bool next(Index lvl) { return (lvl == Level) ? mIter.next() : mNext.next(lvl); } + + /// @brief If the iterator at level @a lvl of the tree points to a child node, + /// initialize the next iterator in this list with that child node. + bool down(Index lvl) + { + if (lvl == Level && mPrev != NULL && mIter) { + if (ChildT* child = ITraits::template getChild(mIter)) { + mPrev->setIter(PrevItemT::ITraits::begin(*child)); + return true; + } + } + return (lvl > Level) ? mNext.down(lvl) : false; + } + + /// @brief Return the global coordinates of the voxel or tile to which the iterator + /// at level @a lvl of the tree is currently pointing. + Coord getCoord(Index lvl) const + { + return (lvl == Level) ? mIter.getCoord() : mNext.getCoord(lvl); + } + Index getChildDim(Index lvl) const + { + return (lvl == Level) ? NodeT::getChildDim() : mNext.getChildDim(lvl); + } + /// Return the number of (virtual) voxels spanned by a tile value or child node + Index64 getVoxelCount(Index lvl) const + { + return (lvl == Level) ? ChildT::NUM_VOXELS : mNext.getVoxelCount(lvl); + } + + /// Return @c true if the iterator at level @a lvl of the tree points to an active value. + bool isValueOn(Index lvl) const + { + return (lvl == Level) ? mIter.isValueOn() : mNext.isValueOn(lvl); + } + + /// Return the value to which the iterator at level @a lvl of the tree points. + const NCValueT& getValue(Index lvl) const + { + if (lvl == Level) return mIter.getValue(); + return mNext.getValue(lvl); + } + + /// @brief Set the value (to @a val) to which the iterator at level @a lvl + /// of the tree points and mark the value as active. + /// @note Not valid when @c IterT is a const iterator type + void setValue(Index lvl, const NCValueT& val) const + { + if (lvl == Level) mIter.setValue(val); else mNext.setValue(lvl, val); + } + /// @brief Set the value (to @a val) to which the iterator at level @a lvl of the tree + /// points and mark the value as active if @a on is @c true, or inactive otherwise. + /// @note Not valid when @c IterT is a const iterator type + void setValueOn(Index lvl, bool on = true) const + { + if (lvl == Level) mIter.setValueOn(on); else mNext.setValueOn(lvl, on); + } + /// @brief Mark the value to which the iterator at level @a lvl of the tree points + /// as inactive. + /// @note Not valid when @c IterT is a const iterator type + void setValueOff(Index lvl) const + { + if (lvl == Level) mIter.setValueOff(); else mNext.setValueOff(lvl); + } + + /// @brief Apply a functor to the item to which this iterator is pointing. + /// @note Not valid when @c IterT is a const iterator type + template + void modifyValue(Index lvl, const ModifyOp& op) const + { + if (lvl == Level) mIter.modifyValue(op); else mNext.modifyValue(lvl, op); + } + +private: + typedef typename boost::mpl::pop_front::type RestT; // NodeVecT minus its first item + typedef IterListItem NextItem; + + IterT mIter; + NextItem mNext; + PrevItemT* mPrev; +}; + + +/// The initial element of a compile-time linked list of iterators to nodes of different types +template +class IterListItem +{ +public: + /// The type of iterator stored in the previous list item + typedef typename PrevItemT::IterT PrevIterT; + /// The type of node (non-const) whose iterator is stored in this list item + typedef typename boost::mpl::front::type _NodeT; + /// The type of iterator stored in this list item (e.g., InternalNode::ValueOnCIter) + typedef typename IterTraits::template + NodeConverter<_NodeT>::Type IterT; + + /// The type of node (const or non-const) over which IterT iterates (e.g., const RootNode<...>) + typedef typename IterT::NodeType NodeT; + /// The type of the node with const qualifiers removed ("Non-Const") + typedef typename IterT::NonConstNodeType NCNodeT; + /// The type of value (with const qualifiers removed) to which the iterator points + typedef typename IterT::NonConstValueType NCValueT; + typedef IterTraits ITraits; + /// NodeT's level in its tree (0 = LeafNode) + static const Index Level = 0; + + IterListItem(PrevItemT*): mNext(this), mPrev(NULL) {} + + IterListItem(const IterListItem& other): mIter(other.mIter), mNext(other.mNext), mPrev(NULL) {} + IterListItem& operator=(const IterListItem& other) + { + if (&other != this) { + mIter = other.mIter; + mNext = other.mNext; + mPrev = NULL; + } + return *this; + } + + void updateBackPointers(PrevItemT* = NULL) { mPrev = NULL; mNext.updateBackPointers(this); } + + void setIter(const IterT& iter) { mIter = iter; } + template + void setIter(const OtherIterT& iter) { mNext.setIter(iter); } + + void getNode(Index lvl, NodeT*& node) const + { + node = (lvl == 0) ? mIter.getParentNode() : NULL; + } + template + void getNode(Index lvl, OtherNodeT*& node) const { mNext.getNode(lvl, node); } + + template + void initLevel(Index lvl, OtherIterListItemT& otherListItem) + { + if (lvl == 0) { + const NodeT* node = NULL; + otherListItem.getNode(lvl, node); + mIter = (node == NULL) ? IterT() : ITraits::begin(*const_cast(node)); + } else { + mNext.initLevel(lvl, otherListItem); + } + } + + Index pos(Index lvl) const { return (lvl == 0) ? mIter.pos() : mNext.pos(lvl); } + + bool test(Index lvl) const { return (lvl == 0) ? mIter.test() : mNext.test(lvl); } + + bool next(Index lvl) { return (lvl == 0) ? mIter.next() : mNext.next(lvl); } + + bool down(Index lvl) { return (lvl == 0) ? false : mNext.down(lvl); } + + Coord getCoord(Index lvl) const + { + return (lvl == 0) ? mIter.getCoord() : mNext.getCoord(lvl); + } + Index getChildDim(Index lvl) const + { + return (lvl == 0) ? NodeT::getChildDim() : mNext.getChildDim(lvl); + } + + Index64 getVoxelCount(Index lvl) const + { + return (lvl == 0) ? 1 : mNext.getVoxelCount(lvl); + } + + bool isValueOn(Index lvl) const + { + return (lvl == 0) ? mIter.isValueOn() : mNext.isValueOn(lvl); + } + + const NCValueT& getValue(Index lvl) const + { + if (lvl == 0) return mIter.getValue(); + return mNext.getValue(lvl); + } + + void setValue(Index lvl, const NCValueT& val) const + { + if (lvl == 0) mIter.setValue(val); else mNext.setValue(lvl, val); + } + void setValueOn(Index lvl, bool on = true) const + { + if (lvl == 0) mIter.setValueOn(on); else mNext.setValueOn(lvl, on); + } + void setValueOff(Index lvl) const + { + if (lvl == 0) mIter.setValueOff(); else mNext.setValueOff(lvl); + } + + template + void modifyValue(Index lvl, const ModifyOp& op) const + { + if (lvl == 0) mIter.modifyValue(op); else mNext.modifyValue(lvl, op); + } + +private: + typedef typename boost::mpl::pop_front::type RestT; // NodeVecT minus its first item + typedef IterListItem NextItem; + + IterT mIter; + NextItem mNext; + PrevItemT* mPrev; +}; + + +/// The final element of a compile-time linked list of iterators to nodes of different types +template +class IterListItem +{ +public: + typedef typename boost::mpl::front::type _NodeT; + /// The type of iterator stored in the previous list item + typedef typename PrevItemT::IterT PrevIterT; + /// The type of iterator stored in this list item (e.g., RootNode::ValueOnCIter) + typedef typename IterTraits::template + NodeConverter<_NodeT>::Type IterT; + + /// The type of node over which IterT iterates (e.g., const RootNode<...>) + typedef typename IterT::NodeType NodeT; + /// The type of the node with const qualifiers removed ("Non-Const") + typedef typename IterT::NonConstNodeType NCNodeT; + /// The type of value (with const qualifiers removed) to which the iterator points + typedef typename IterT::NonConstValueType NCValueT; + /// NodeT's child node type, with the same constness (e.g., const InternalNode<...>) + typedef typename CopyConstness::Type ChildT; + /// NodeT's child node type with const qualifiers removed + typedef typename CopyConstness::Type NCChildT; + typedef IterTraits ITraits; + /// NodeT's level in its tree (0 = LeafNode) + static const Index Level = _Level; + + IterListItem(PrevItemT* prev): mPrev(prev) {} + + IterListItem(const IterListItem& other): mIter(other.mIter), mPrev(NULL) {} + IterListItem& operator=(const IterListItem& other) + { + if (&other != this) { + mIter = other.mIter; + mPrev = NULL; ///< @note external call to updateBackPointers() required + } + return *this; + } + + void updateBackPointers(PrevItemT* prev) { mPrev = prev; } + + // The following method specializations differ from the default template + // implementations mainly in that they don't forward. + + void setIter(const IterT& iter) { mIter = iter; } + + void getNode(Index lvl, NodeT*& node) const + { + node = (lvl <= Level) ? mIter.getParentNode() : NULL; + } + + template + void initLevel(Index lvl, OtherIterListItemT& otherListItem) + { + if (lvl == Level) { + const NodeT* node = NULL; + otherListItem.getNode(lvl, node); + mIter = (node == NULL) ? IterT() : ITraits::begin(*const_cast(node)); + } + } + + Index pos(Index lvl) const { return (lvl == Level) ? mIter.pos() : Index(-1); } + + bool test(Index lvl) const { return (lvl == Level) ? mIter.test() : false; } + + bool next(Index lvl) { return (lvl == Level) ? mIter.next() : false; } + + bool down(Index lvl) + { + if (lvl == Level && mPrev != NULL && mIter) { + if (ChildT* child = ITraits::template getChild(mIter)) { + mPrev->setIter(PrevItemT::ITraits::begin(*child)); + return true; + } + } + return false; + } + + Coord getCoord(Index lvl) const { return (lvl == Level) ? mIter.getCoord() : Coord(); } + Index getChildDim(Index lvl) const { return (lvl == Level) ? NodeT::getChildDim() : 0; } + Index64 getVoxelCount(Index lvl) const { return (lvl == Level) ? ChildT::NUM_VOXELS : 0; } + + bool isValueOn(Index lvl) const { return (lvl == Level) ? mIter.isValueOn() : false; } + + const NCValueT& getValue(Index lvl) const + { + assert(lvl == Level); + (void)lvl; // avoid unused variable warning in optimized builds + return mIter.getValue(); + } + + void setValue(Index lvl, const NCValueT& val) const { if (lvl == Level) mIter.setValue(val); } + void setValueOn(Index lvl, bool on = true) const { if (lvl == Level) mIter.setValueOn(on); } + void setValueOff(Index lvl) const { if (lvl == Level) mIter.setValueOff(); } + + template + void modifyValue(Index lvl, const ModifyOp& op) const + { + if (lvl == Level) mIter.modifyValue(op); + } + +private: + IterT mIter; + PrevItemT* mPrev; +}; + + +//////////////////////////////////////// + + +//#define DEBUG_TREE_VALUE_ITERATOR + +/// @brief Base class for tree-traversal iterators over tile and voxel values +template +class TreeValueIteratorBase +{ +public: + typedef _TreeT TreeT; + typedef typename ValueIterT::NodeType NodeT; + typedef typename ValueIterT::NonConstValueType ValueT; + typedef typename NodeT::ChildOnCIter ChildOnIterT; + static const Index ROOT_LEVEL = NodeT::LEVEL; + BOOST_STATIC_ASSERT(ValueIterT::NodeType::LEVEL == ROOT_LEVEL); + static const Index LEAF_LEVEL = 0, ROOT_DEPTH = 0, LEAF_DEPTH = ROOT_LEVEL; + + TreeValueIteratorBase(TreeT&); + + TreeValueIteratorBase(const TreeValueIteratorBase& other); + TreeValueIteratorBase& operator=(const TreeValueIteratorBase& other); + + /// Specify the depth of the highest level of the tree to which to ascend (depth 0 = root). + void setMinDepth(Index minDepth); + /// Return the depth of the highest level of the tree to which this iterator ascends. + Index getMinDepth() const { return ROOT_LEVEL - Index(mMaxLevel); } + /// Specify the depth of the lowest level of the tree to which to descend (depth 0 = root). + void setMaxDepth(Index maxDepth); + /// Return the depth of the lowest level of the tree to which this iterator ascends. + Index getMaxDepth() const { return ROOT_LEVEL - Index(mMinLevel); } + + //@{ + /// Return @c true if this iterator is not yet exhausted. + bool test() const { return mValueIterList.test(mLevel); } + operator bool() const { return this->test(); } + //@} + + /// @brief Advance to the next tile or voxel value. + /// Return @c true if this iterator is not yet exhausted. + bool next(); + /// Advance to the next tile or voxel value. + TreeValueIteratorBase& operator++() { this->next(); return *this; } + + /// @brief Return the level in the tree (0 = leaf) of the node to which + /// this iterator is currently pointing. + Index getLevel() const { return mLevel; } + /// @brief Return the depth in the tree (0 = root) of the node to which + /// this iterator is currently pointing. + Index getDepth() const { return ROOT_LEVEL - mLevel; } + static Index getLeafDepth() { return LEAF_DEPTH; } + + /// @brief Return in @a node a pointer to the node over which this iterator is + /// currently iterating or one of that node's parents, as determined by @a NodeType. + /// @return a null pointer if @a NodeType specifies a node at a lower level + /// of the tree than that given by getLevel(). + template + void getNode(NodeType*& node) const { mValueIterList.getNode(mLevel, node); } + + /// @brief Return the global coordinates of the voxel or tile to which + /// this iterator is currently pointing. + Coord getCoord() const { return mValueIterList.getCoord(mLevel); } + /// @brief Return in @a bbox the axis-aligned bounding box of + /// the voxel or tile to which this iterator is currently pointing. + /// @return false if the bounding box is empty. + bool getBoundingBox(CoordBBox&) const; + /// @brief Return the axis-aligned bounding box of the voxel or tile to which + /// this iterator is currently pointing. + CoordBBox getBoundingBox() const { CoordBBox b; this->getBoundingBox(b); return b; } + + /// Return the number of (virtual) voxels corresponding to the value + Index64 getVoxelCount() const { return mValueIterList.getVoxelCount(mLevel);} + + /// Return @c true if this iterator is currently pointing to a (non-leaf) tile value. + bool isTileValue() const { return mLevel != 0 && this->test(); } + /// Return @c true if this iterator is currently pointing to a (leaf) voxel value. + bool isVoxelValue() const { return mLevel == 0 && this->test(); } + /// Return @c true if the value to which this iterator is currently pointing is active. + bool isValueOn() const { return mValueIterList.isValueOn(mLevel); } + + //@{ + /// Return the tile or voxel value to which this iterator is currently pointing. + const ValueT& getValue() const { return mValueIterList.getValue(mLevel); } + const ValueT& operator*() const { return this->getValue(); } + const ValueT* operator->() const { return &(this->operator*()); } + //@} + + /// @brief Change the tile or voxel value to which this iterator is currently pointing + /// and mark it as active. + void setValue(const ValueT& val) const { mValueIterList.setValue(mLevel, val); } + /// @brief Change the active/inactive state of the tile or voxel value to which + /// this iterator is currently pointing. + void setActiveState(bool on) const { mValueIterList.setValueOn(mLevel, on); } + /// Mark the tile or voxel value to which this iterator is currently pointing as inactive. + void setValueOff() const { mValueIterList.setValueOff(mLevel); } + + /// @brief Apply a functor to the item to which this iterator is pointing. + /// (Not valid for const iterators.) + /// @param op a functor of the form void op(ValueType&) const that modifies + /// its argument in place + /// @see Tree::modifyValue() + template + void modifyValue(const ModifyOp& op) const { mValueIterList.modifyValue(mLevel, op); } + + /// Return a pointer to the tree over which this iterator is iterating. + TreeT* getTree() const { return mTree; } + + /// Return a string (for debugging, mainly) describing this iterator's current state. + std::string summary() const; + +private: + bool advance(bool dontIncrement = false); + + typedef typename iter::InvertedTree::Type InvTreeT; + struct PrevChildItem { typedef ChildOnIterT IterT; }; + struct PrevValueItem { typedef ValueIterT IterT; }; + + IterListItem mChildIterList; + IterListItem mValueIterList; + Index mLevel; + int mMinLevel, mMaxLevel; + TreeT* mTree; +}; // class TreeValueIteratorBase + + +template +inline +TreeValueIteratorBase::TreeValueIteratorBase(TreeT& tree): + mChildIterList(NULL), + mValueIterList(NULL), + mLevel(ROOT_LEVEL), + mMinLevel(int(LEAF_LEVEL)), + mMaxLevel(int(ROOT_LEVEL)), + mTree(&tree) +{ + mChildIterList.setIter(IterTraits::begin(tree.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) +{ + Index + vPos = mValueIterList.pos(mLevel), + cPos = mChildIterList.pos(mLevel); + if (vPos == cPos && mChildIterList.test(mLevel)) { + /// @todo Once ValueOff iterators properly skip child pointers, remove this block. + mValueIterList.next(mLevel); + vPos = mValueIterList.pos(mLevel); + } + if (vPos < cPos) { + if (dontIncrement) return true; + if (mValueIterList.next(mLevel)) { + if (mValueIterList.pos(mLevel) == cPos && mChildIterList.test(mLevel)) { + /// @todo Once ValueOff iterators properly skip child pointers, remove this block. + mValueIterList.next(mLevel); + } + // If there is a next value and it precedes the next child, return. + if (mValueIterList.pos(mLevel) < cPos) return true; + } + } else { + // Advance to the next child, which may or may not precede the next value. + if (!dontIncrement) mChildIterList.next(mLevel); + } +#ifdef DEBUG_TREE_VALUE_ITERATOR + std::cout << "\n" << this->summary() << std::flush; +#endif + + // Descend to the lowest level at which the next value precedes the next child. + while (mChildIterList.pos(mLevel) < mValueIterList.pos(mLevel)) { +#ifdef ENABLE_TREE_VALUE_DEPTH_BOUND_OPTIMIZATION + if (int(mLevel) == mMinLevel) { + // If the current node lies at the lowest allowed level, none of its + // children can be visited, so advance its child iterator to the end. + /// @todo Consider adding methods to iterators to advance to the end + /// in one step, instead of by repeated increments. + while (mChildIterList.test(mLevel)) mChildIterList.next(mLevel); + } else +#endif + if (mChildIterList.down(mLevel)) { + --mLevel; // descend one level + mValueIterList.initLevel(mLevel, mChildIterList); + if (mValueIterList.pos(mLevel) == mChildIterList.pos(mLevel) + && mChildIterList.test(mLevel)) + { + /// @todo Once ValueOff iterators properly skip child pointers, remove this block. + mValueIterList.next(mLevel); + } + } else break; +#ifdef DEBUG_TREE_VALUE_ITERATOR + std::cout << "\n" << this->summary() << std::flush; +#endif + } + // Ascend to the nearest level at which one of the iterators is not yet exhausted. + while (!mChildIterList.test(mLevel) && !mValueIterList.test(mLevel)) { + if (mLevel == ROOT_LEVEL) return false; + ++mLevel; + mChildIterList.next(mLevel); + this->advance(/*dontIncrement=*/true); + } + return true; +} + + +template +inline bool +TreeValueIteratorBase::getBoundingBox(CoordBBox& bbox) const +{ + if (!this->test()) { + bbox = CoordBBox(); + return false; + } + bbox.min() = mValueIterList.getCoord(mLevel); + bbox.max() = bbox.min().offsetBy(mValueIterList.getChildDim(mLevel) - 1); + return true; +} + + +template +inline std::string +TreeValueIteratorBase::summary() const +{ + std::ostringstream ostr; + for (int lvl = int(ROOT_LEVEL); lvl >= 0 && lvl >= int(mLevel); --lvl) { + if (lvl == 0) ostr << "leaf"; + else if (lvl == int(ROOT_LEVEL)) ostr << "root"; + else ostr << "int" << (ROOT_LEVEL - lvl); + ostr << " v" << mValueIterList.pos(lvl) + << " c" << mChildIterList.pos(lvl); + if (lvl > int(mLevel)) ostr << " / "; + } + if (this->test() && mValueIterList.pos(mLevel) < mChildIterList.pos(mLevel)) { + if (mLevel == 0) { + ostr << " " << this->getCoord(); + } else { + ostr << " " << this->getBoundingBox(); + } + } + return ostr.str(); +} + + +//////////////////////////////////////// + + +/// @brief Base class for tree-traversal iterators over all nodes +template +class NodeIteratorBase +{ +public: + typedef _TreeT TreeT; + typedef RootChildOnIterT RootIterT; + typedef typename RootIterT::NodeType RootNodeT; + typedef typename RootIterT::NonConstNodeType NCRootNodeT; + static const Index ROOT_LEVEL = RootNodeT::LEVEL; + typedef typename iter::InvertedTree::Type InvTreeT; + static const Index LEAF_LEVEL = 0, ROOT_DEPTH = 0, LEAF_DEPTH = ROOT_LEVEL; + + typedef IterTraits RootIterTraits; + + NodeIteratorBase(); + NodeIteratorBase(TreeT&); + + NodeIteratorBase(const NodeIteratorBase& other); + NodeIteratorBase& operator=(const NodeIteratorBase& other); + + /// Specify the depth of the highest level of the tree to which to ascend (depth 0 = root). + void setMinDepth(Index minDepth); + /// Return the depth of the highest level of the tree to which this iterator ascends. + Index getMinDepth() const { return ROOT_LEVEL - Index(mMaxLevel); } + /// Specify the depth of the lowest level of the tree to which to descend (depth 0 = root). + void setMaxDepth(Index maxDepth); + /// Return the depth of the lowest level of the tree to which this iterator ascends. + Index getMaxDepth() const { return ROOT_LEVEL - Index(mMinLevel); } + + //@{ + /// Return @c true if this iterator is not yet exhausted. + bool test() const { return !mDone; } + operator bool() const { return this->test(); } + //@} + + /// @brief Advance to the next tile or voxel value. + /// @return @c true if this iterator is not yet exhausted. + bool next(); + /// Advance the iterator to the next leaf node. + void increment() { this->next(); } + NodeIteratorBase& operator++() { this->increment(); return *this; } + /// Increment the iterator n times. + void increment(Index n) { for (Index i = 0; i < n && this->next(); ++i) {} } + + /// @brief Return the level in the tree (0 = leaf) of the node to which + /// this iterator is currently pointing. + Index getLevel() const { return mLevel; } + /// @brief Return the depth in the tree (0 = root) of the node to which + /// this iterator is currently pointing. + Index getDepth() const { return ROOT_LEVEL - mLevel; } + static Index getLeafDepth() { return LEAF_DEPTH; } + + /// @brief Return the global coordinates of the voxel or tile to which + /// this iterator is currently pointing. + Coord getCoord() const; + /// @brief Return in @a bbox the axis-aligned bounding box of + /// the voxel or tile to which this iterator is currently pointing. + /// @return false if the bounding box is empty. + bool getBoundingBox(CoordBBox& bbox) const; + /// @brief Return the axis-aligned bounding box of the voxel or tile to which + /// this iterator is currently pointing. + CoordBBox getBoundingBox() const { CoordBBox b; this->getBoundingBox(b); return b; } + + /// @brief Return the node to which the iterator is pointing. + /// @note This iterator doesn't have the usual dereference operators (* and ->), + /// because they would have to be overloaded by the returned node type. + template + void getNode(NodeT*& node) const { node = NULL; mIterList.getNode(mLevel, node); } + + TreeT* getTree() const { return mTree; } + + std::string summary() const; + +private: + struct PrevItem { typedef RootIterT IterT; }; + + IterListItem mIterList; + Index mLevel; + int mMinLevel, mMaxLevel; + bool mDone; + TreeT* mTree; +}; // class NodeIteratorBase + + +template +inline +NodeIteratorBase::NodeIteratorBase(): + mIterList(NULL), + mLevel(ROOT_LEVEL), + mMinLevel(int(LEAF_LEVEL)), + mMaxLevel(int(ROOT_LEVEL)), + mDone(true), + mTree(NULL) +{ +} + + +template +inline +NodeIteratorBase::NodeIteratorBase(TreeT& tree): + mIterList(NULL), + mLevel(ROOT_LEVEL), + mMinLevel(int(LEAF_LEVEL)), + mMaxLevel(int(ROOT_LEVEL)), + mDone(false), + mTree(&tree) +{ + mIterList.setIter(RootIterTraits::begin(tree.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-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tree/Util.h b/openvdb_2_3_0_library/openvdb/tree/Util.h new file mode 100755 index 0000000..9adb096 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tree/Util.h @@ -0,0 +1,125 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file tree/Util.h + +#ifndef OPENVDB_TREE_UTIL_HAS_BEEN_INCLUDED +#define OPENVDB_TREE_UTIL_HAS_BEEN_INCLUDED + +#include // for isNegative and negative +#include // for Index typedef + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tree { + +/// @brief Helper class for use with Tree::pruneOp() to replace constant branches +/// (to within the provided tolerance) with more memory-efficient tiles +template +struct TolerancePrune +{ + TolerancePrune(const ValueType& tol): tolerance(tol) {} + + template + bool operator()(ChildType& child) + { + return (ChildType::LEVEL < TerminationLevel) ? false : this->isConstant(child); + } + + template + bool isConstant(ChildType& child) + { + child.pruneOp(*this); + return child.isConstant(value, state, tolerance); + } + + bool state; + ValueType value; + const ValueType tolerance; +}; + + +/// @brief Helper class for use with Tree::pruneOp() to replace inactive branches +/// with more memory-efficient inactive tiles with the provided value +/// @details This is more specialized but faster than a TolerancePrune. +template +struct InactivePrune +{ + InactivePrune(const ValueType& val): value(val) {} + + template + bool operator()(ChildType& child) const + { + child.pruneOp(*this); + return child.isInactive(); + } + + static const bool state = false; + const ValueType value; +}; + + +/// @brief Helper class for use with Tree::pruneOp() to prune any branches +/// whose values are all inactive and replace each with an inactive tile +/// whose value is equal in magnitude to the background value and whose sign +/// is equal to that of the first value encountered in the (inactive) child +/// +/// @details This operation is faster than a TolerancePrune and useful for +/// narrow-band level set applications where inactive values are limited +/// to either the inside or the outside value. +template +struct LevelSetPrune +{ + LevelSetPrune(const ValueType& background): outside(background) {} + + template + bool operator()(ChildType& child) + { + child.pruneOp(*this); + if (!child.isInactive()) return false; + value = math::isNegative(child.getFirstValue()) ? math::negative(outside) : outside; + return true; + } + + static const bool state = false; + const ValueType outside; + ValueType value; +}; + +} // namespace tree +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TREE_UTIL_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/tree/ValueAccessor.h b/openvdb_2_3_0_library/openvdb/tree/ValueAccessor.h new file mode 100755 index 0000000..46a240a --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/tree/ValueAccessor.h @@ -0,0 +1,2611 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file ValueAccessor.h +/// +/// When traversing a grid in a spatially coherent pattern (e.g., iterating +/// over neighboring voxels), request a @c ValueAccessor from the grid +/// (with Grid::getAccessor()) and use the accessor's @c getValue() and +/// @c setValue() methods. These will typically be significantly faster +/// than accessing voxels directly in the grid's tree. +/// +/// @par Example: +/// +/// @code +/// FloatGrid grid; +/// FloatGrid::Accessor acc = grid.getAccessor(); +/// // First access is slow: +/// acc.setValue(Coord(0, 0, 0), 100); +/// // Subsequent nearby accesses are fast, since the accessor now holds pointers +/// // to nodes that contain (0, 0, 0) along the path from the root of the grid's +/// // tree to the leaf: +/// acc.setValue(Coord(0, 0, 1), 100); +/// acc.getValue(Coord(0, 2, 0), 100); +/// // Slow, because the accessor must be repopulated: +/// acc.getValue(Coord(-1, -1, -1)); +/// // Fast: +/// acc.getValue(Coord(-1, -1, -2)); +/// acc.setValue(Coord(-1, -2, 0), -100); +/// @endcode + +#ifndef OPENVDB_TREE_VALUEACCESSOR_HAS_BEEN_INCLUDED +#define OPENVDB_TREE_VALUEACCESSOR_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tree { + +// Forward declarations of local classes that are not intended for general use +template class ValueAccessor0; +template class ValueAccessor1; +template class ValueAccessor2; +template class ValueAccessor3; +template class CacheItem; + + +/// @brief This base class for ValueAccessors manages registration of an accessor +/// with a tree so that the tree can automatically clear the accessor whenever +/// one of its nodes is deleted. +/// @internal A base class is needed because ValueAccessor is templated on both +/// a Tree type and a mutex type. The various instantiations of the template +/// are distinct, unrelated types, so they can't easily be stored in a container +/// such as the Tree's CacheRegistry. This base class, in contrast, is templated +/// only on the Tree type, so for any given Tree, only two distinct instantiations +/// are possible, ValueAccessorBase and ValueAccessorBase. +template +class ValueAccessorBase +{ +public: + static const bool IsConstTree = boost::is_const::value; + + ValueAccessorBase(TreeType& tree): mTree(&tree) { tree.attachAccessor(*this); } + + virtual ~ValueAccessorBase() { if (mTree) mTree->releaseAccessor(*this); } + + /// @brief Return a pointer to the tree associated with this accessor. + /// @details The pointer will be null only if the tree from which this accessor + /// was constructed was subsequently deleted (which generally leaves the + /// accessor in an unsafe state). + TreeType* getTree() const { return mTree; } + /// Return a reference to the tree associated with this accessor. + TreeType& tree() const { assert(mTree); return *mTree; } + + ValueAccessorBase(const ValueAccessorBase& other): mTree(other.mTree) + { + if (mTree) mTree->attachAccessor(*this); + } + + ValueAccessorBase& operator=(const ValueAccessorBase& other) + { + if (&other != this) { + if (mTree) mTree->releaseAccessor(*this); + mTree = other.mTree; + if (mTree) mTree->attachAccessor(*this); + } + return *this; + } + + virtual void clear() = 0; + +protected: + // Allow trees to deregister themselves. + template friend class Tree; + + virtual void release() { mTree = NULL; } + + TreeType* mTree; +}; // class ValueAccessorBase + + +//////////////////////////////////////// + + +/// When traversing a grid in a spatially coherent pattern (e.g., iterating +/// over neighboring voxels), request a @c ValueAccessor from the grid +/// (with Grid::getAccessor()) and use the accessor's @c getValue() and +/// @c setValue() methods. These will typically be significantly faster +/// than accessing voxels directly in the grid's tree. +/// +/// A ValueAccessor caches pointers to tree nodes along the path to a voxel (x, y, z). +/// A subsequent access to voxel (x', y', z') starts from the cached leaf node and +/// moves up until a cached node that encloses (x', y', z') is found, then traverses +/// down the tree from that node to a leaf, updating the cache with the new path. +/// This leads to significant acceleration of spatially-coherent accesses. +/// +/// @param _TreeType the type of the tree to be accessed [required] +/// @param CacheLevels the number of nodes to be cached, starting from the leaf level +/// and not including the root (i.e., CacheLevels < DEPTH), +/// and defaulting to all non-root nodes +/// @param MutexType the type of mutex to use (see note) +/// +/// @note If @c MutexType is a TBB-compatible mutex, then multiple threads may +/// safely access a single, shared accessor. However, it is highly recommended +/// that, instead, each thread be assigned its own, non-mutex-protected accessor. +template +class ValueAccessor: public ValueAccessorBase<_TreeType> +{ +public: + BOOST_STATIC_ASSERT(CacheLevels < _TreeType::DEPTH); + + typedef _TreeType TreeType; + typedef typename TreeType::RootNodeType RootNodeT; + typedef typename TreeType::LeafNodeType LeafNodeT; + typedef typename RootNodeT::ValueType ValueType; + typedef ValueAccessorBase BaseT; + typedef typename MutexType::scoped_lock LockT; + using BaseT::IsConstTree; + + ValueAccessor(TreeType& tree): BaseT(tree), mCache(*this) + { + mCache.insert(Coord(), &tree.root()); + } + + ValueAccessor(const ValueAccessor& other): BaseT(other), mCache(*this, other.mCache) {} + + ValueAccessor& operator=(const ValueAccessor& other) + { + if (&other != this) { + this->BaseT::operator=(other); + mCache.copy(*this, other.mCache); + } + return *this; + } + virtual ~ValueAccessor() {} + + /// Return the number of cache levels employed by this accessor. + static Index numCacheLevels() { return CacheLevels; } + + /// Return @c true if nodes along the path to the given voxel have been cached. + bool isCached(const Coord& xyz) const { LockT lock(mMutex); return mCache.isCached(xyz); } + + /// Return the value of the voxel at the given coordinates. + const ValueType& getValue(const Coord& xyz) const + { + LockT lock(mMutex); + return mCache.getValue(xyz); + } + + /// Return the active state of the voxel at the given coordinates. + bool isValueOn(const Coord& xyz) const { LockT lock(mMutex); return mCache.isValueOn(xyz); } + + /// Return the active state of the voxel as well as its value + bool probeValue(const Coord& xyz, ValueType& value) const + { + LockT lock(mMutex); + return mCache.probeValue(xyz,value); + } + + /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, + /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is + /// implicitly a background voxel). + int getValueDepth(const Coord& xyz) const + { + LockT lock(mMutex); + return mCache.getValueDepth(xyz); + } + + /// Return @c true if the value of voxel (x, y, z) resides at the leaf level + /// of the tree, i.e., if it is not a tile value. + bool isVoxel(const Coord& xyz) const { LockT lock(mMutex); return mCache.isVoxel(xyz); } + + //@{ + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValue(const Coord& xyz, const ValueType& value) + { + LockT lock(mMutex); + mCache.setValue(xyz, value); + } + void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } + //@} + + /// Set the value of the voxel at the given coordinate but don't change its active state. + void setValueOnly(const Coord& xyz, const ValueType& value) + { + LockT lock(mMutex); + mCache.setValueOnly(xyz, value); + } + + /// Set the value of the voxel at the given coordinates and mark the voxel + /// as active. [Experimental] + void newSetValue(const Coord& xyz, const ValueType& value) + { + LockT lock(mMutex); + mCache.newSetValue(xyz, value); + } + + /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. + void setValueOff(const Coord& xyz, const ValueType& value) + { + LockT lock(mMutex); + mCache.setValueOff(xyz, value); + } + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + /// @details See Tree::modifyValue() for details. + template + void modifyValue(const Coord& xyz, const ModifyOp& op) + { + LockT lock(mMutex); + mCache.modifyValue(xyz, op); + } + + /// @brief Apply a functor to the voxel at the given coordinates. + /// @details See Tree::modifyValueAndActiveState() for details. + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) + { + LockT lock(mMutex); + mCache.modifyValueAndActiveState(xyz, op); + } + + /// Set the active state of the voxel at the given coordinates but don't change its value. + void setActiveState(const Coord& xyz, bool on = true) + { + LockT lock(mMutex); + mCache.setActiveState(xyz, on); + } + /// Mark the voxel at the given coordinates as active but don't change its value. + void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } + /// Mark the voxel at the given coordinates as inactive but don't change its value. + void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } + + /// Return the cached node of type @a NodeType. [Mainly for internal use] + template + NodeType* getNode() + { + LockT lock(mMutex); + NodeType* node = NULL; + mCache.getNode(node); + return node; + } + + /// Cache the given node, which should lie along the path from the root node to + /// the node containing voxel (x, y, z). [Mainly for internal use] + template + void insertNode(const Coord& xyz, NodeType& node) + { + LockT lock(mMutex); + mCache.insert(xyz, &node); + } + + /// If a node of the given type exists in the cache, remove it, so that + /// isCached(xyz) returns @c false for any voxel (x, y, z) contained in + /// that node. [Mainly for internal use] + template + void eraseNode() { LockT lock(mMutex); NodeType* node = NULL; mCache.erase(node); } + + /// @brief Add the specified leaf to this tree, possibly creating a child branch + /// in the process. If the leaf node already exists, replace it. + void addLeaf(LeafNodeT* leaf) + { + LockT lock(mMutex); + mCache.addLeaf(leaf); + } + + /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), + /// possibly deleting existing nodes or creating new nodes in the process. + void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) + { + LockT lock(mMutex); + mCache.addTile(level, xyz, value, state); + } + + /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). + /// If no such node exists, create one, but preserve the values and + /// active states of all voxels. + /// @details Use this method to preallocate a static tree topology + /// over which to safely perform multithreaded processing. + LeafNodeT* touchLeaf(const Coord& xyz) + { + LockT lock(mMutex); + return mCache.touchLeaf(xyz); + } + + //@{ + /// @brief Return a pointer to the node of the specified type that contains + /// voxel (x, y, z), or NULL if no such node exists. + template + NodeT* probeNode(const Coord& xyz) + { + LockT lock(mMutex); + return mCache.template probeNode(xyz); + } + template + const NodeT* probeConstNode(const Coord& xyz) const + { + LockT lock(mMutex); + return mCache.template probeConstNode(xyz); + } + template + const NodeT* probeNode(const Coord& xyz) const + { + return this->template probeConstNode(xyz); + } + //@} + + //@{ + /// @brief Return a pointer to the leaf node that contains voxel (x, y, z), + /// or NULL if no such node exists. + LeafNodeT* probeLeaf(const Coord& xyz) + { + LockT lock(mMutex); + return mCache.probeLeaf(xyz); + } + const LeafNodeT* probeConstLeaf(const Coord& xyz) const + { + LockT lock(mMutex); + return mCache.probeConstLeaf(xyz); + } + const LeafNodeT* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } + //@} + + /// Remove all nodes from this cache, then reinsert the root node. + virtual void clear() + { + LockT lock(mMutex); + mCache.clear(); + if (this->mTree) mCache.insert(Coord(), &(this->mTree->root())); + } + +private: + // Allow nodes to insert themselves into the cache. + template friend class RootNode; + template friend class InternalNode; + template friend class LeafNode; + // Allow trees to deregister themselves. + template friend class Tree; + + /// Prevent this accessor from calling Tree::releaseCache() on a tree that + /// no longer exists. (Called by mTree when it is destroyed.) + virtual void release() + { + LockT lock(mMutex); + this->BaseT::release(); + mCache.clear(); + } + + /// Cache the given node, which should lie along the path from the root node to + /// the node containing voxel (x, y, z). + /// @note This operation is not mutex-protected and is intended to be called + /// only by nodes and only in the context of a getValue() or setValue() call. + template + void insert(const Coord& xyz, NodeType* node) { mCache.insert(xyz, node); } + + // Define a list of all tree node types from LeafNode to RootNode + typedef typename RootNodeT::NodeChainType InvTreeT; + // Remove all tree node types that are excluded from the cache + typedef typename boost::mpl::begin::type BeginT; + typedef typename boost::mpl::advance >::type FirstT; + typedef typename boost::mpl::find::type LastT; + typedef typename boost::mpl::erase::type SubtreeT; + typedef CacheItem::value==1> CacheItemT; + + // Private member data + mutable CacheItemT mCache; + mutable MutexType mMutex; + +}; // class ValueAccessor + + +/// @brief Template specialization of the ValueAccessor with no mutex and no cache levels +/// @details This specialization is provided mainly for benchmarking. +/// Accessors with caching will almost always be faster. +template +struct ValueAccessor: public ValueAccessor0 +{ + ValueAccessor(TreeType& tree): ValueAccessor0(tree) {} + ValueAccessor(const ValueAccessor& other): ValueAccessor0(other) {} + virtual ~ValueAccessor() {} +}; + + +/// Template specialization of the ValueAccessor with no mutex and one cache level +template +struct ValueAccessor: public ValueAccessor1 +{ + ValueAccessor(TreeType& tree): ValueAccessor1(tree) {} + ValueAccessor(const ValueAccessor& other): ValueAccessor1(other) {} + virtual ~ValueAccessor() {} +}; + + +/// Template specialization of the ValueAccessor with no mutex and two cache levels +template +struct ValueAccessor: public ValueAccessor2 +{ + ValueAccessor(TreeType& tree): ValueAccessor2(tree) {} + ValueAccessor(const ValueAccessor& other): ValueAccessor2(other) {} + virtual ~ValueAccessor() {} +}; + + +/// Template specialization of the ValueAccessor with no mutex and three cache levels +template +struct ValueAccessor: public ValueAccessor3 +{ + ValueAccessor(TreeType& tree): ValueAccessor3(tree) {} + ValueAccessor(const ValueAccessor& other): ValueAccessor3(other) {} + virtual ~ValueAccessor() {} +}; + + +//////////////////////////////////////// + + +/// @brief This accessor is thread-safe (at the cost of speed) for both reading and +/// writing to a tree. That is, multiple threads may safely access a single, +/// shared ValueAccessorRW. +/// +/// @warning Since the mutex-locking employed by the ValueAccessorRW +/// can seriously impair performance of multithreaded applications, it +/// is recommended that, instead, each thread be assigned its own +/// (non-mutex protected) accessor. +template +struct ValueAccessorRW: public ValueAccessor +{ + ValueAccessorRW(TreeType& tree) + : ValueAccessor(tree) + { + } +}; + + +//////////////////////////////////////// + + +// +// The classes below are for internal use and should rarely be used directly. +// + +// An element of a compile-time linked list of node pointers, ordered from LeafNode to RootNode +template +class CacheItem +{ +public: + typedef typename boost::mpl::front::type NodeType; + typedef typename NodeType::ValueType ValueType; + typedef typename NodeType::LeafNodeType LeafNodeType; + typedef std::numeric_limits CoordLimits; + + CacheItem(TreeCacheT& parent): + mParent(&parent), + mHash(CoordLimits::max()), + mNode(NULL), + mNext(parent) + { + } + + //@{ + /// Copy another CacheItem's node pointers and hash keys, but not its parent pointer. + CacheItem(TreeCacheT& parent, const CacheItem& other): + mParent(&parent), + mHash(other.mHash), + mNode(other.mNode), + mNext(parent, other.mNext) + { + } + + CacheItem& copy(TreeCacheT& parent, const CacheItem& other) + { + mParent = &parent; + mHash = other.mHash; + mNode = other.mNode; + mNext.copy(parent, other.mNext); + return *this; + } + //@} + + bool isCached(const Coord& xyz) const + { + return (this->isHashed(xyz) || mNext.isCached(xyz)); + } + + /// Cache the given node at this level. + void insert(const Coord& xyz, const NodeType* node) + { + mHash = (node != NULL) ? xyz & ~(NodeType::DIM-1) : Coord::max(); + mNode = node; + } + /// Forward the given node to another level of the cache. + template + void insert(const Coord& xyz, const OtherNodeType* node) { mNext.insert(xyz, node); } + + /// Erase the node at this level. + void erase(const NodeType*) { mHash = Coord::max(); mNode = NULL; } + /// Erase the node at another level of the cache. + template + void erase(const OtherNodeType* node) { mNext.erase(node); } + + /// Erase the nodes at this and lower levels of the cache. + void clear() { mHash = Coord::max(); mNode = NULL; mNext.clear(); } + + /// Return the cached node (if any) at this level. + void getNode(const NodeType*& node) const { node = mNode; } + void getNode(const NodeType*& node) { node = mNode; } + void getNode(NodeType*& node) + { + // This combination of a static assertion and a const_cast might not be elegant, + // but it is a lot simpler than specializing TreeCache for const Trees. + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + node = const_cast(mNode); + } + /// Forward the request to another level of the cache. + template + void getNode(OtherNodeType*& node) { mNext.getNode(node); } + + /// Return the value of the voxel at the given coordinates. + const ValueType& getValue(const Coord& xyz) + { + if (this->isHashed(xyz)) { + assert(mNode); + return mNode->getValueAndCache(xyz, *mParent); + } + return mNext.getValue(xyz); + } + + void addLeaf(LeafNodeType* leaf) + { + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + if (NodeType::LEVEL == 0) return; + if (this->isHashed(leaf->origin())) { + assert(mNode); + return const_cast(mNode)->addLeafAndCache(leaf, *mParent); + } + mNext.addLeaf(leaf); + } + + void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) + { + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + if (NodeType::LEVEL < level) return; + if (this->isHashed(xyz)) { + assert(mNode); + return const_cast(mNode)->addTileAndCache( + level, xyz, value, state, *mParent); + } + mNext.addTile(level, xyz, value, state); + } + + LeafNodeType* touchLeaf(const Coord& xyz) + { + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + if (this->isHashed(xyz)) { + assert(mNode); + return const_cast(mNode)->touchLeafAndCache(xyz, *mParent); + } + return mNext.touchLeaf(xyz); + } + + LeafNodeType* probeLeaf(const Coord& xyz) + { + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + if (this->isHashed(xyz)) { + assert(mNode); + return const_cast(mNode)->probeLeafAndCache(xyz, *mParent); + } + return mNext.probeLeaf(xyz); + } + + const LeafNodeType* probeConstLeaf(const Coord& xyz) + { + if (this->isHashed(xyz)) { + assert(mNode); + return mNode->probeConstLeafAndCache(xyz, *mParent); + } + return mNext.probeConstLeaf(xyz); + } + + template + NodeT* probeNode(const Coord& xyz) + { + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if (this->isHashed(xyz)) { + if ((boost::is_same::value)) { + assert(mNode); + return reinterpret_cast(const_cast(mNode)); + } + return const_cast(mNode)->template probeNodeAndCache(xyz, *mParent); + } + return mNext.template probeNode(xyz); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + + template + const NodeT* probeConstNode(const Coord& xyz) + { + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if (this->isHashed(xyz)) { + if ((boost::is_same::value)) { + assert(mNode); + return reinterpret_cast(mNode); + } + return mNode->template probeConstNodeAndCache(xyz, *mParent); + } + return mNext.template probeConstNode(xyz); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + + /// Return the active state of the voxel at the given coordinates. + bool isValueOn(const Coord& xyz) + { + if (this->isHashed(xyz)) { + assert(mNode); + return mNode->isValueOnAndCache(xyz, *mParent); + } + return mNext.isValueOn(xyz); + } + + /// Return the active state and value of the voxel at the given coordinates. + bool probeValue(const Coord& xyz, ValueType& value) + { + if (this->isHashed(xyz)) { + assert(mNode); + return mNode->probeValueAndCache(xyz, value, *mParent); + } + return mNext.probeValue(xyz, value); + } + + int getValueDepth(const Coord& xyz) + { + if (this->isHashed(xyz)) { + assert(mNode); + return static_cast(TreeCacheT::RootNodeT::LEVEL) - + static_cast(mNode->getValueLevelAndCache(xyz, *mParent)); + } else { + return mNext.getValueDepth(xyz); + } + } + + bool isVoxel(const Coord& xyz) + { + if (this->isHashed(xyz)) { + assert(mNode); + return mNode->getValueLevelAndCache(xyz, *mParent)==0; + } else { + return mNext.isVoxel(xyz); + } + } + + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValue(const Coord& xyz, const ValueType& value) + { + if (this->isHashed(xyz)) { + assert(mNode); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mNode)->setValueAndCache(xyz, value, *mParent); + } else { + mNext.setValue(xyz, value); + } + } + void setValueOnly(const Coord& xyz, const ValueType& value) + { + if (this->isHashed(xyz)) { + assert(mNode); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mNode)->setValueOnlyAndCache(xyz, value, *mParent); + } else { + mNext.setValueOnly(xyz, value); + } + } + void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + /// @details See Tree::modifyValue() for details. + template + void modifyValue(const Coord& xyz, const ModifyOp& op) + { + if (this->isHashed(xyz)) { + assert(mNode); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mNode)->modifyValueAndCache(xyz, op, *mParent); + } else { + mNext.modifyValue(xyz, op); + } + } + + /// @brief Apply a functor to the voxel at the given coordinates. + /// @details See Tree::modifyValueAndActiveState() for details. + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) + { + if (this->isHashed(xyz)) { + assert(mNode); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mNode)->modifyValueAndActiveStateAndCache(xyz, op, *mParent); + } else { + mNext.modifyValueAndActiveState(xyz, op); + } + } + + /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. + void setValueOff(const Coord& xyz, const ValueType& value) + { + if (this->isHashed(xyz)) { + assert(mNode); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mNode)->setValueOffAndCache(xyz, value, *mParent); + } else { + mNext.setValueOff(xyz, value); + } + } + + /// Set the active state of the voxel at the given coordinates. + void setActiveState(const Coord& xyz, bool on) + { + if (this->isHashed(xyz)) { + assert(mNode); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mNode)->setActiveStateAndCache(xyz, on, *mParent); + } else { + mNext.setActiveState(xyz, on); + } + } + +private: + CacheItem(const CacheItem&); + CacheItem& operator=(const CacheItem&); + + bool isHashed(const Coord& xyz) const + { + return (xyz[0] & ~Coord::ValueType(NodeType::DIM-1)) == mHash[0] + && (xyz[1] & ~Coord::ValueType(NodeType::DIM-1)) == mHash[1] + && (xyz[2] & ~Coord::ValueType(NodeType::DIM-1)) == mHash[2]; + } + + TreeCacheT* mParent; + Coord mHash; + const NodeType* mNode; + typedef typename boost::mpl::pop_front::type RestT; // NodeVecT minus its first item + CacheItem::value == 1> mNext; +};// end of CacheItem + + +/// The tail of a compile-time list of cached node pointers, ordered from LeafNode to RootNode +template +class CacheItem +{ +public: + typedef typename boost::mpl::front::type RootNodeType; + typedef typename RootNodeType::ValueType ValueType; + typedef typename RootNodeType::LeafNodeType LeafNodeType; + + CacheItem(TreeCacheT& parent): mParent(&parent), mRoot(NULL) {} + CacheItem(TreeCacheT& parent, const CacheItem& other): mParent(&parent), mRoot(other.mRoot) {} + + CacheItem& copy(TreeCacheT& parent, const CacheItem& other) + { + mParent = &parent; + mRoot = other.mRoot; + return *this; + } + + bool isCached(const Coord& xyz) const { return this->isHashed(xyz); } + + void insert(const Coord&, const RootNodeType* root) { mRoot = root; } + + // Needed for node types that are not cached + template + void insert(const Coord&, const OtherNodeType*) {} + + void erase(const RootNodeType*) { mRoot = NULL; } + + void clear() { mRoot = NULL; } + + void getNode(RootNodeType*& node) + { + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + node = const_cast(mRoot); + } + void getNode(const RootNodeType*& node) const { node = mRoot; } + + void addLeaf(LeafNodeType* leaf) + { + assert(mRoot); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mRoot)->addLeafAndCache(leaf, *mParent); + } + + void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) + { + assert(mRoot); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mRoot)->addTileAndCache(level, xyz, value, state, *mParent); + } + + LeafNodeType* touchLeaf(const Coord& xyz) + { + assert(mRoot); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + return const_cast(mRoot)->touchLeafAndCache(xyz, *mParent); + } + + LeafNodeType* probeLeaf(const Coord& xyz) + { + assert(mRoot); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + return const_cast(mRoot)->probeLeafAndCache(xyz, *mParent); + } + + const LeafNodeType* probeConstLeaf(const Coord& xyz) + { + assert(mRoot); + return mRoot->probeConstLeafAndCache(xyz, *mParent); + } + + template + NodeType* probeNode(const Coord& xyz) + { + assert(mRoot); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + return const_cast(mRoot)->template probeNodeAndCache(xyz, *mParent); + } + + template + const NodeType* probeConstNode(const Coord& xyz) + { + assert(mRoot); + return mRoot->template probeConstNodeAndCache(xyz, *mParent); + } + + int getValueDepth(const Coord& xyz) + { + assert(mRoot); + return mRoot->getValueDepthAndCache(xyz, *mParent); + } + bool isValueOn(const Coord& xyz) + { + assert(mRoot); + return mRoot->isValueOnAndCache(xyz, *mParent); + } + + bool probeValue(const Coord& xyz, ValueType& value) + { + assert(mRoot); + return mRoot->probeValueAndCache(xyz, value, *mParent); + } + bool isVoxel(const Coord& xyz) + { + assert(mRoot); + return mRoot->getValueDepthAndCache(xyz, *mParent) == + static_cast(RootNodeType::LEVEL); + } + const ValueType& getValue(const Coord& xyz) + { + assert(mRoot); + return mRoot->getValueAndCache(xyz, *mParent); + } + + void setValue(const Coord& xyz, const ValueType& value) + { + assert(mRoot); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mRoot)->setValueAndCache(xyz, value, *mParent); + } + void setValueOnly(const Coord& xyz, const ValueType& value) + { + assert(mRoot); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mRoot)->setValueOnlyAndCache(xyz, value, *mParent); + } + void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } + + template + void modifyValue(const Coord& xyz, const ModifyOp& op) + { + assert(mRoot); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mRoot)->modifyValueAndCache(xyz, op, *mParent); + } + + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) + { + assert(mRoot); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mRoot)->modifyValueAndActiveStateAndCache(xyz, op, *mParent); + } + + void setValueOff(const Coord& xyz, const ValueType& value) + { + assert(mRoot); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mRoot)->setValueOffAndCache(xyz, value, *mParent); + } + + void setActiveState(const Coord& xyz, bool on) + { + assert(mRoot); + BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); + const_cast(mRoot)->setActiveStateAndCache(xyz, on, *mParent); + } + +private: + CacheItem(const CacheItem&); + CacheItem& operator=(const CacheItem&); + + bool isHashed(const Coord&) const { return false; } + + TreeCacheT* mParent; + const RootNodeType* mRoot; +};// end of CacheItem specialized for RootNode + + +//////////////////////////////////////// + + +/// @brief ValueAccessor with no mutex and no node caching. +/// @details This specialization is provided mainly for benchmarking. +/// Accessors with caching will almost always be faster. +template +class ValueAccessor0: public ValueAccessorBase<_TreeType> +{ +public: + typedef _TreeType TreeType; + typedef typename TreeType::ValueType ValueType; + typedef typename TreeType::RootNodeType RootNodeT; + typedef typename TreeType::LeafNodeType LeafNodeT; + typedef ValueAccessorBase BaseT; + + ValueAccessor0(TreeType& tree): BaseT(tree) {} + + ValueAccessor0(const ValueAccessor0& other): BaseT(other) {} + + /// Return the number of cache levels employed by this accessor. + static Index numCacheLevels() { return 0; } + + ValueAccessor0& operator=(const ValueAccessor0& other) + { + if (&other != this) this->BaseT::operator=(other); + return *this; + } + + virtual ~ValueAccessor0() {} + + /// Return @c true if nodes along the path to the given voxel have been cached. + bool isCached(const Coord&) const { return false; } + + /// Return the value of the voxel at the given coordinates. + const ValueType& getValue(const Coord& xyz) const + { + assert(BaseT::mTree); + return BaseT::mTree->getValue(xyz); + } + + /// Return the active state of the voxel at the given coordinates. + bool isValueOn(const Coord& xyz) const + { + assert(BaseT::mTree); + return BaseT::mTree->isValueOn(xyz); + } + + /// Return the active state and, in @a value, the value of the voxel at the given coordinates. + bool probeValue(const Coord& xyz, ValueType& value) const + { + assert(BaseT::mTree); + return BaseT::mTree->probeValue(xyz, value); + } + + /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, + /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is + /// implicitly a background voxel). + int getValueDepth(const Coord& xyz) const + { + assert(BaseT::mTree); + return BaseT::mTree->getValueDepth(xyz); + } + + /// Return @c true if the value of voxel (x, y, z) resides at the leaf level + /// of the tree, i.e., if it is not a tile value. + bool isVoxel(const Coord& xyz) const + { + assert(BaseT::mTree); + return BaseT::mTree->getValueDepth(xyz) == static_cast(RootNodeT::LEVEL); + } + + //@{ + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValue(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + BaseT::mTree->setValue(xyz, value); + } + void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } + //@} + + /// Set the value of the voxel at the given coordinate but don't change its active state. + void setValueOnly(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + BaseT::mTree->setValueOnly(xyz, value); + } + + /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. + void setValueOff(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + BaseT::mTree->root().setValueOff(xyz, value); + } + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + /// @details See Tree::modifyValue() for details. + template + void modifyValue(const Coord& xyz, const ModifyOp& op) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + BaseT::mTree->modifyValue(xyz, op); + } + + /// @brief Apply a functor to the voxel at the given coordinates. + /// @details See Tree::modifyValueAndActiveState() for details. + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + BaseT::mTree->modifyValueAndActiveState(xyz, op); + } + + /// Set the active state of the voxel at the given coordinates but don't change its value. + void setActiveState(const Coord& xyz, bool on = true) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + BaseT::mTree->setActiveState(xyz, on); + } + /// Mark the voxel at the given coordinates as active but don't change its value. + void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } + /// Mark the voxel at the given coordinates as inactive but don't change its value. + void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } + + /// Return the cached node of type @a NodeType. [Mainly for internal use] + template NodeT* getNode() { return NULL; } + + /// Cache the given node, which should lie along the path from the root node to + /// the node containing voxel (x, y, z). [Mainly for internal use] + template void insertNode(const Coord&, NodeT&) {} + + /// @brief Add the specified leaf to this tree, possibly creating a child branch + /// in the process. If the leaf node already exists, replace it. + void addLeaf(LeafNodeT* leaf) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + BaseT::mTree->root().addLeaf(leaf); + } + + /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), + /// possibly deleting existing nodes or creating new nodes in the process. + void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + BaseT::mTree->root().addTile(level, xyz, value, state); + } + + /// If a node of the given type exists in the cache, remove it, so that + /// isCached(xyz) returns @c false for any voxel (x, y, z) contained in + /// that node. [Mainly for internal use] + template void eraseNode() {} + + LeafNodeT* touchLeaf(const Coord& xyz) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + return BaseT::mTree->touchLeaf(xyz); + } + + template + NodeT* probeNode(const Coord& xyz) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + return BaseT::mTree->template probeNode(xyz); + } + + template + const NodeT* probeConstNode(const Coord& xyz) const + { + assert(BaseT::mTree); + return BaseT::mTree->template probeConstNode(xyz); + } + + LeafNodeT* probeLeaf(const Coord& xyz) + { + return this->template probeNode(xyz); + } + + const LeafNodeT* probeConstLeaf(const Coord& xyz) const + { + return this->template probeConstNode(xyz); + } + + const LeafNodeT* probeLeaf(const Coord& xyz) const + { + return this->probeConstLeaf(xyz); + } + + /// Remove all nodes from this cache, then reinsert the root node. + virtual void clear() {} + +private: + // Allow trees to deregister themselves. + template friend class Tree; + + /// Prevent this accessor from calling Tree::releaseCache() on a tree that + /// no longer exists. (Called by mTree when it is destroyed.) + virtual void release() { this->BaseT::release(); } + +}; // ValueAccessor0 + + +/// @brief Value accessor with one level of node caching. +/// @details The node cache level is specified by L0 with the default value 0 +/// (defined in the forward declaration) corresponding to a LeafNode. +/// +/// @note This class is for experts only and should rarely be used +/// directly. Instead use ValueAccessor with its default template arguments. +template +class ValueAccessor1 : public ValueAccessorBase<_TreeType> +{ +public: + BOOST_STATIC_ASSERT(_TreeType::DEPTH >= 2); + BOOST_STATIC_ASSERT( L0 < _TreeType::RootNodeType::LEVEL ); + typedef _TreeType TreeType; + typedef typename TreeType::ValueType ValueType; + typedef typename TreeType::RootNodeType RootNodeT; + typedef typename TreeType::LeafNodeType LeafNodeT; + typedef ValueAccessorBase BaseT; + typedef typename RootNodeT::NodeChainType InvTreeT; + typedef typename boost::mpl::at >::type NodeT0; + + /// Constructor from a tree + ValueAccessor1(TreeType& tree) : BaseT(tree), mKey0(Coord::max()), mNode0(NULL) + { + } + + /// Copy constructor + ValueAccessor1(const ValueAccessor1& other) : BaseT(other) { this->copy(other); } + + /// Return the number of cache levels employed by this ValueAccessor + static Index numCacheLevels() { return 1; } + + /// Asignment operator + ValueAccessor1& operator=(const ValueAccessor1& other) + { + if (&other != this) { + this->BaseT::operator=(other); + this->copy(other); + } + return *this; + } + + /// Virtual destructor + virtual ~ValueAccessor1() {} + + /// Return @c true if any of the nodes along the path to the given + /// voxel have been cached. + bool isCached(const Coord& xyz) const + { + assert(BaseT::mTree); + return this->isHashed(xyz); + } + + /// Return the value of the voxel at the given coordinates. + const ValueType& getValue(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed(xyz)) { + assert(mNode0); + return mNode0->getValueAndCache(xyz, this->self()); + } + return BaseT::mTree->root().getValueAndCache(xyz, this->self()); + } + + /// Return the active state of the voxel at the given coordinates. + bool isValueOn(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed(xyz)) { + assert(mNode0); + return mNode0->isValueOnAndCache(xyz, this->self()); + } + return BaseT::mTree->root().isValueOnAndCache(xyz, this->self()); + } + + /// Return the active state of the voxel as well as its value + bool probeValue(const Coord& xyz, ValueType& value) const + { + assert(BaseT::mTree); + if (this->isHashed(xyz)) { + assert(mNode0); + return mNode0->probeValueAndCache(xyz, value, this->self()); + } + return BaseT::mTree->root().probeValueAndCache(xyz, value, this->self()); + } + + /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, + /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is + /// implicitly a background voxel). + int getValueDepth(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed(xyz)) { + assert(mNode0); + return RootNodeT::LEVEL - mNode0->getValueLevelAndCache(xyz, this->self()); + } + return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()); + } + + /// Return @c true if the value of voxel (x, y, z) resides at the leaf level + /// of the tree, i.e., if it is not a tile value. + bool isVoxel(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed(xyz)) { + assert(mNode0); + return mNode0->getValueLevelAndCache(xyz, this->self()) == 0; + } + return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()) == + static_cast(RootNodeT::LEVEL); + } + + //@{ + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValue(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed(xyz)) { + assert(mNode0); + const_cast(mNode0)->setValueAndCache(xyz, value, *this); + } else { + BaseT::mTree->root().setValueAndCache(xyz, value, *this); + } + } + void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } + //@} + + /// Set the value of the voxel at the given coordinate but preserves its active state. + void setValueOnly(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed(xyz)) { + assert(mNode0); + const_cast(mNode0)->setValueOnlyAndCache(xyz, value, *this); + } else { + BaseT::mTree->root().setValueOnlyAndCache(xyz, value, *this); + } + } + + /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. + void setValueOff(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed(xyz)) { + assert(mNode0); + const_cast(mNode0)->setValueOffAndCache(xyz, value, *this); + } else { + BaseT::mTree->root().setValueOffAndCache(xyz, value, *this); + } + } + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + /// @details See Tree::modifyValue() for details. + template + void modifyValue(const Coord& xyz, const ModifyOp& op) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed(xyz)) { + assert(mNode0); + const_cast(mNode0)->modifyValueAndCache(xyz, op, *this); + } else { + BaseT::mTree->root().modifyValueAndCache(xyz, op, *this); + } + } + + /// @brief Apply a functor to the voxel at the given coordinates. + /// @details See Tree::modifyValueAndActiveState() for details. + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed(xyz)) { + assert(mNode0); + const_cast(mNode0)->modifyValueAndActiveStateAndCache(xyz, op, *this); + } else { + BaseT::mTree->root().modifyValueAndActiveStateAndCache(xyz, op, *this); + } + } + + /// Set the active state of the voxel at the given coordinates but don't change its value. + void setActiveState(const Coord& xyz, bool on = true) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed(xyz)) { + assert(mNode0); + const_cast(mNode0)->setActiveStateAndCache(xyz, on, *this); + } else { + BaseT::mTree->root().setActiveStateAndCache(xyz, on, *this); + } + } + /// Mark the voxel at the given coordinates as active but don't change its value. + void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } + /// Mark the voxel at the given coordinates as inactive but don't change its value. + void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } + + /// Return the cached node of type @a NodeType. [Mainly for internal use] + template + NodeT* getNode() + { + const NodeT* node = NULL; + this->getNode(node); + return const_cast(node); + } + + /// Cache the given node, which should lie along the path from the root node to + /// the node containing voxel (x, y, z). [Mainly for internal use] + template + void insertNode(const Coord& xyz, NodeT& node) { this->insert(xyz, &node); } + + /// If a node of the given type exists in the cache, remove it, so that + /// isCached(xyz) returns @c false for any voxel (x, y, z) contained in + /// that node. [Mainly for internal use] + template + void eraseNode() + { + const NodeT* node = NULL; + this->eraseNode(node); + } + + /// @brief Add the specified leaf to this tree, possibly creating a child branch + /// in the process. If the leaf node already exists, replace it. + void addLeaf(LeafNodeT* leaf) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + BaseT::mTree->root().addLeaf(leaf); + } + + /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), + /// possibly deleting existing nodes or creating new nodes in the process. + void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + BaseT::mTree->root().addTile(level, xyz, value, state); + } + + /// @brief @return the leaf node that contains voxel (x, y, z) and + /// if it doesn't exist, create it, but preserve the values and + /// active states of all voxels. + /// + /// Use this method to preallocate a static tree topology over which to + /// safely perform multithreaded processing. + LeafNodeT* touchLeaf(const Coord& xyz) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed(xyz)) { + assert(mNode0); + return const_cast(mNode0)->touchLeafAndCache(xyz, *this); + } + return BaseT::mTree->root().touchLeafAndCache(xyz, *this); + } + + /// @brief @return a pointer to the node of the specified type that contains + /// voxel (x, y, z) and if it doesn't exist, return NULL. + template + NodeT* probeNode(const Coord& xyz) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if ((boost::is_same::value)) { + if (this->isHashed(xyz)) { + assert(mNode0); + return reinterpret_cast(const_cast(mNode0)); + } + return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); + } + return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + LeafNodeT* probeLeaf(const Coord& xyz) + { + return this->template probeNode(xyz); + } + + /// @brief @return a const pointer to the nodeof the specified type that contains + /// voxel (x, y, z) and if it doesn't exist, return NULL. + template + const NodeT* probeConstNode(const Coord& xyz) const + { + assert(BaseT::mTree); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if ((boost::is_same::value)) { + if (this->isHashed(xyz)) { + assert(mNode0); + return reinterpret_cast(mNode0); + } + return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); + } + return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + const LeafNodeT* probeConstLeaf(const Coord& xyz) const + { + return this->template probeConstNode(xyz); + } + const LeafNodeT* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } + + /// Remove all the cached nodes and invalidate the corresponding hash-keys. + virtual void clear() + { + mKey0 = Coord::max(); + mNode0 = NULL; + } + +private: + // Allow nodes to insert themselves into the cache. + template friend class RootNode; + template friend class InternalNode; + template friend class LeafNode; + // Allow trees to deregister themselves. + template friend class Tree; + + // This private method is merely for convenience. + inline ValueAccessor1& self() const { return const_cast(*this); } + + void getNode(const NodeT0*& node) { node = mNode0; } + void getNode(const RootNodeT*& node) + { + node = (BaseT::mTree ? &BaseT::mTree->root() : NULL); + } + template void getNode(const OtherNodeType*& node) { node = NULL; } + void eraseNode(const NodeT0*) { mKey0 = Coord::max(); mNode0 = NULL; } + template void eraseNode(const OtherNodeType*) {} + + /// Private copy method + inline void copy(const ValueAccessor1& other) + { + mKey0 = other.mKey0; + mNode0 = other.mNode0; + } + + /// Prevent this accessor from calling Tree::releaseCache() on a tree that + /// no longer exists. (Called by mTree when it is destroyed.) + virtual void release() + { + this->BaseT::release(); + this->clear(); + } + /// Cache the given node, which should lie along the path from the root node to + /// the node containing voxel (x, y, z). + /// @note This operation is not mutex-protected and is intended to be called + /// only by nodes and only in the context of a getValue() or setValue() call. + inline void insert(const Coord& xyz, const NodeT0* node) + { + assert(node); + mKey0 = xyz & ~(NodeT0::DIM-1); + mNode0 = node; + } + + /// No-op in case a tree traversal attemps to insert a node that + /// is not cached by the ValueAccessor + template inline void insert(const Coord&, const OtherNodeType*) {} + + inline bool isHashed(const Coord& xyz) const + { + return (xyz[0] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[0] + && (xyz[1] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[1] + && (xyz[2] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[2]; + } + mutable Coord mKey0; + mutable const NodeT0* mNode0; +}; // ValueAccessor1 + + +/// @brief Value accessor with two levels of node caching. +/// @details The node cache levels are specified by L0 and L1 +/// with the default values 0 and 1 (defined in the forward declaration) +/// corresponding to a LeafNode and its parent InternalNode. +/// +/// @note This class is for experts only and should rarely be used directly. +/// Instead use ValueAccessor with its default template arguments. +template +class ValueAccessor2 : public ValueAccessorBase<_TreeType> +{ +public: + BOOST_STATIC_ASSERT(_TreeType::DEPTH >= 3); + BOOST_STATIC_ASSERT( L0 < L1 && L1 < _TreeType::RootNodeType::LEVEL ); + typedef _TreeType TreeType; + typedef typename TreeType::ValueType ValueType; + typedef typename TreeType::RootNodeType RootNodeT; + typedef typename TreeType::LeafNodeType LeafNodeT; + typedef ValueAccessorBase BaseT; + typedef typename RootNodeT::NodeChainType InvTreeT; + typedef typename boost::mpl::at >::type NodeT0; + typedef typename boost::mpl::at >::type NodeT1; + + /// Constructor from a tree + ValueAccessor2(TreeType& tree) : BaseT(tree), + mKey0(Coord::max()), mNode0(NULL), + mKey1(Coord::max()), mNode1(NULL) {} + + /// Copy constructor + ValueAccessor2(const ValueAccessor2& other) : BaseT(other) { this->copy(other); } + + /// Return the number of cache levels employed by this ValueAccessor + static Index numCacheLevels() { return 2; } + + /// Asignment operator + ValueAccessor2& operator=(const ValueAccessor2& other) + { + if (&other != this) { + this->BaseT::operator=(other); + this->copy(other); + } + return *this; + } + + /// Virtual destructor + virtual ~ValueAccessor2() {} + + /// Return @c true if any of the nodes along the path to the given + /// voxel have been cached. + bool isCached(const Coord& xyz) const + { + assert(BaseT::mTree); + return this->isHashed1(xyz) || this->isHashed0(xyz); + } + + /// Return the value of the voxel at the given coordinates. + const ValueType& getValue(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return mNode0->getValueAndCache(xyz, this->self()); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return mNode1->getValueAndCache(xyz, this->self()); + } + return BaseT::mTree->root().getValueAndCache(xyz, this->self()); + } + + /// Return the active state of the voxel at the given coordinates. + bool isValueOn(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return mNode0->isValueOnAndCache(xyz, this->self()); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return mNode1->isValueOnAndCache(xyz, this->self()); + } + return BaseT::mTree->root().isValueOnAndCache(xyz, this->self()); + } + + /// Return the active state of the voxel as well as its value + bool probeValue(const Coord& xyz, ValueType& value) const + { + assert(BaseT::mTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return mNode0->probeValueAndCache(xyz, value, this->self()); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return mNode1->probeValueAndCache(xyz, value, this->self()); + } + return BaseT::mTree->root().probeValueAndCache(xyz, value, this->self()); + } + + /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, + /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is + /// implicitly a background voxel). + int getValueDepth(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return RootNodeT::LEVEL - mNode0->getValueLevelAndCache(xyz, this->self()); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return RootNodeT::LEVEL - mNode1->getValueLevelAndCache(xyz, this->self()); + } + return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()); + } + + /// Return @c true if the value of voxel (x, y, z) resides at the leaf level + /// of the tree, i.e., if it is not a tile value. + bool isVoxel(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return mNode0->getValueLevelAndCache(xyz, this->self())==0; + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return mNode1->getValueLevelAndCache(xyz, this->self())==0; + } + return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()) == + static_cast(RootNodeT::LEVEL); + } + + //@{ + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValue(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->setValueAndCache(xyz, value, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->setValueAndCache(xyz, value, *this); + } else { + BaseT::mTree->root().setValueAndCache(xyz, value, *this); + } + } + void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } + //@} + + /// Set the value of the voxel at the given coordinate but preserves its active state. + void setValueOnly(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->setValueOnlyAndCache(xyz, value, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->setValueOnlyAndCache(xyz, value, *this); + } else { + BaseT::mTree->root().setValueOnlyAndCache(xyz, value, *this); + } + } + + /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. + void setValueOff(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->setValueOffAndCache(xyz, value, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->setValueOffAndCache(xyz, value, *this); + } else { + BaseT::mTree->root().setValueOffAndCache(xyz, value, *this); + } + } + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + /// @details See Tree::modifyValue() for details. + template + void modifyValue(const Coord& xyz, const ModifyOp& op) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->modifyValueAndCache(xyz, op, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->modifyValueAndCache(xyz, op, *this); + } else { + BaseT::mTree->root().modifyValueAndCache(xyz, op, *this); + } + } + + /// @brief Apply a functor to the voxel at the given coordinates. + /// @details See Tree::modifyValueAndActiveState() for details. + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->modifyValueAndActiveStateAndCache(xyz, op, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->modifyValueAndActiveStateAndCache(xyz, op, *this); + } else { + BaseT::mTree->root().modifyValueAndActiveStateAndCache(xyz, op, *this); + } + } + + /// Set the active state of the voxel at the given coordinates without changing its value. + void setActiveState(const Coord& xyz, bool on = true) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->setActiveStateAndCache(xyz, on, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->setActiveStateAndCache(xyz, on, *this); + } else { + BaseT::mTree->root().setActiveStateAndCache(xyz, on, *this); + } + } + /// Mark the voxel at the given coordinates as active without changing its value. + void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } + /// Mark the voxel at the given coordinates as inactive without changing its value. + void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } + + /// Return the cached node of type @a NodeType. [Mainly for internal use] + template + NodeT* getNode() + { + const NodeT* node = NULL; + this->getNode(node); + return const_cast(node); + } + + /// Cache the given node, which should lie along the path from the root node to + /// the node containing voxel (x, y, z). [Mainly for internal use] + template + void insertNode(const Coord& xyz, NodeT& node) { this->insert(xyz, &node); } + + /// If a node of the given type exists in the cache, remove it, so that + /// isCached(xyz) returns @c false for any voxel (x, y, z) contained in + /// that node. [Mainly for internal use] + template + void eraseNode() + { + const NodeT* node = NULL; + this->eraseNode(node); + } + + /// @brief Add the specified leaf to this tree, possibly creating a child branch + /// in the process. If the leaf node already exists, replace it. + void addLeaf(LeafNodeT* leaf) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed1(leaf->origin())) { + assert(mNode1); + return const_cast(mNode1)->addLeafAndCache(leaf, *this); + } + BaseT::mTree->root().addLeafAndCache(leaf, *this); + } + + /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), + /// possibly deleting existing nodes or creating new nodes in the process. + void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed1(xyz)) { + assert(mNode1); + return const_cast(mNode1)->addTileAndCache(level, xyz, value, state, *this); + } + BaseT::mTree->root().addTileAndCache(level, xyz, value, state, *this); + } + + /// @brief @return the leaf node that contains voxel (x, y, z) and + /// if it doesn't exist, create it, but preserve the values and + /// active states of all voxels. + /// + /// Use this method to preallocate a static tree topology over which to + /// safely perform multithreaded processing. + LeafNodeT* touchLeaf(const Coord& xyz) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return const_cast(mNode0)->touchLeafAndCache(xyz, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return const_cast(mNode1)->touchLeafAndCache(xyz, *this); + } + return BaseT::mTree->root().touchLeafAndCache(xyz, *this); + } + /// @brief @return a pointer to the node of the specified type that contains + /// voxel (x, y, z) and if it doesn't exist, return NULL. + template + NodeT* probeNode(const Coord& xyz) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if ((boost::is_same::value)) { + if (this->isHashed0(xyz)) { + assert(mNode0); + return reinterpret_cast(const_cast(mNode0)); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return const_cast(mNode1)->template probeNodeAndCache(xyz, *this); + } + return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); + } else if ((boost::is_same::value)) { + if (this->isHashed1(xyz)) { + assert(mNode1); + return reinterpret_cast(const_cast(mNode1)); + } + return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); + } + return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + /// @brief @return a pointer to the leaf node that contains + /// voxel (x, y, z) and if it doesn't exist, return NULL. + LeafNodeT* probeLeaf(const Coord& xyz) { return this->template probeNode(xyz); } + + /// @brief @return a const pointer to the node of the specified type that contains + /// voxel (x, y, z) and if it doesn't exist, return NULL. + template + const NodeT* probeConstLeaf(const Coord& xyz) const + { + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if ((boost::is_same::value)) { + if (this->isHashed0(xyz)) { + assert(mNode0); + return reinterpret_cast(mNode0); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return mNode1->template probeConstNodeAndCache(xyz, this->self()); + } + return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); + } else if ((boost::is_same::value)) { + if (this->isHashed1(xyz)) { + assert(mNode1); + return reinterpret_cast(mNode1); + } + return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); + } + return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + /// @brief @return a const pointer to the leaf node that contains + /// voxel (x, y, z) and if it doesn't exist, return NULL. + const LeafNodeT* probeConstLeaf(const Coord& xyz) const + { + return this->template probeConstNode(xyz); + } + const LeafNodeT* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } + + /// @brief @return a const pointer to the node of the specified type that contains + /// voxel (x, y, z) and if it doesn't exist, return NULL. + template + const NodeT* probeConstNode(const Coord& xyz) const + { + assert(BaseT::mTree); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if ((boost::is_same::value)) { + if (this->isHashed0(xyz)) { + assert(mNode0); + return reinterpret_cast(mNode0); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return mNode1->template probeConstNodeAndCache(xyz, this->self()); + } + return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); + } else if ((boost::is_same::value)) { + if (this->isHashed1(xyz)) { + assert(mNode1); + return reinterpret_cast(mNode1); + } + return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); + } + return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + + /// Remove all the cached nodes and invalidate the corresponding hash-keys. + virtual void clear() + { + mKey0 = Coord::max(); + mNode0 = NULL; + mKey1 = Coord::max(); + mNode1 = NULL; + } + +private: + // Allow nodes to insert themselves into the cache. + template friend class RootNode; + template friend class InternalNode; + template friend class LeafNode; + // Allow trees to deregister themselves. + template friend class Tree; + + // This private method is merely for convenience. + inline ValueAccessor2& self() const { return const_cast(*this); } + + void getNode(const NodeT0*& node) { node = mNode0; } + void getNode(const NodeT1*& node) { node = mNode1; } + void getNode(const RootNodeT*& node) + { + node = (BaseT::mTree ? &BaseT::mTree->root() : NULL); + } + template void getNode(const OtherNodeType*& node) { node = NULL; } + + void eraseNode(const NodeT0*) { mKey0 = Coord::max(); mNode0 = NULL; } + void eraseNode(const NodeT1*) { mKey1 = Coord::max(); mNode1 = NULL; } + template void eraseNode(const OtherNodeType*) {} + + /// Private copy method + inline void copy(const ValueAccessor2& other) + { + mKey0 = other.mKey0; + mNode0 = other.mNode0; + mKey1 = other.mKey1; + mNode1 = other.mNode1; + } + + /// Prevent this accessor from calling Tree::releaseCache() on a tree that + /// no longer exists. (Called by mTree when it is destroyed.) + virtual void release() + { + this->BaseT::release(); + this->clear(); + } + + /// Cache the given node, which should lie along the path from the root node to + /// the node containing voxel (x, y, z). + /// @note This operation is not mutex-protected and is intended to be called + /// only by nodes and only in the context of a getValue() or setValue() call. + inline void insert(const Coord& xyz, const NodeT0* node) + { + assert(node); + mKey0 = xyz & ~(NodeT0::DIM-1); + mNode0 = node; + } + inline void insert(const Coord& xyz, const NodeT1* node) + { + assert(node); + mKey1 = xyz & ~(NodeT1::DIM-1); + mNode1 = node; + } + /// No-op in case a tree traversal attemps to insert a node that + /// is not cached by the ValueAccessor + template inline void insert(const Coord&, const NodeT*) {} + + inline bool isHashed0(const Coord& xyz) const + { + return (xyz[0] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[0] + && (xyz[1] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[1] + && (xyz[2] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[2]; + } + inline bool isHashed1(const Coord& xyz) const + { + return (xyz[0] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[0] + && (xyz[1] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[1] + && (xyz[2] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[2]; + } + mutable Coord mKey0; + mutable const NodeT0* mNode0; + mutable Coord mKey1; + mutable const NodeT1* mNode1; +}; // ValueAccessor2 + + +/// @brief Value accessor with three levels of node caching. +/// @details The node cache levels are specified by L0, L1, and L2 +/// with the default values 0, 1 and 2 (defined in the forward declaration) +/// corresponding to a LeafNode, its parent InternalNode, and its parent InternalNode. +/// Since the default configuration of all typed trees and grids, e.g., +/// FloatTree or FloatGrid, has a depth of four, this value accessor is the one +/// used by default. +/// +/// @note This class is for experts only and should rarely be used +/// directly. Instead use ValueAccessor with its default template arguments +template +class ValueAccessor3 : public ValueAccessorBase<_TreeType> +{ +public: + BOOST_STATIC_ASSERT(_TreeType::DEPTH >= 4); + BOOST_STATIC_ASSERT(L0 < L1 && L1 < L2 && L2 < _TreeType::RootNodeType::LEVEL); + typedef _TreeType TreeType; + typedef typename TreeType::ValueType ValueType; + typedef typename TreeType::RootNodeType RootNodeT; + typedef typename TreeType::LeafNodeType LeafNodeT; + typedef ValueAccessorBase BaseT; + typedef typename RootNodeT::NodeChainType InvTreeT; + typedef typename boost::mpl::at >::type NodeT0; + typedef typename boost::mpl::at >::type NodeT1; + typedef typename boost::mpl::at >::type NodeT2; + + /// Constructor from a tree + ValueAccessor3(TreeType& tree) : BaseT(tree), + mKey0(Coord::max()), mNode0(NULL), + mKey1(Coord::max()), mNode1(NULL), + mKey2(Coord::max()), mNode2(NULL) {} + + /// Copy constructor + ValueAccessor3(const ValueAccessor3& other) : BaseT(other) { this->copy(other); } + + /// Asignment operator + ValueAccessor3& operator=(const ValueAccessor3& other) + { + if (&other != this) { + this->BaseT::operator=(other); + this->copy(other); + } + return *this; + } + + /// Return the number of cache levels employed by this ValueAccessor + static Index numCacheLevels() { return 3; } + + /// Virtual destructor + virtual ~ValueAccessor3() {} + + /// Return @c true if any of the nodes along the path to the given + /// voxel have been cached. + bool isCached(const Coord& xyz) const + { + assert(BaseT::mTree); + return this->isHashed2(xyz) || this->isHashed1(xyz) || this->isHashed0(xyz); + } + + /// Return the value of the voxel at the given coordinates. + const ValueType& getValue(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return mNode0->getValueAndCache(xyz, this->self()); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return mNode1->getValueAndCache(xyz, this->self()); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + return mNode2->getValueAndCache(xyz, this->self()); + } + return BaseT::mTree->root().getValueAndCache(xyz, this->self()); + } + + /// Return the active state of the voxel at the given coordinates. + bool isValueOn(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return mNode0->isValueOnAndCache(xyz, this->self()); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return mNode1->isValueOnAndCache(xyz, this->self()); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + return mNode2->isValueOnAndCache(xyz, this->self()); + } + return BaseT::mTree->root().isValueOnAndCache(xyz, this->self()); + } + + /// Return the active state of the voxel as well as its value + bool probeValue(const Coord& xyz, ValueType& value) const + { + assert(BaseT::mTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return mNode0->probeValueAndCache(xyz, value, this->self()); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return mNode1->probeValueAndCache(xyz, value, this->self()); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + return mNode2->probeValueAndCache(xyz, value, this->self()); + } + return BaseT::mTree->root().probeValueAndCache(xyz, value, this->self()); + } + + /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, + /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is + /// implicitly a background voxel). + int getValueDepth(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return RootNodeT::LEVEL - mNode0->getValueLevelAndCache(xyz, this->self()); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return RootNodeT::LEVEL - mNode1->getValueLevelAndCache(xyz, this->self()); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + return RootNodeT::LEVEL - mNode2->getValueLevelAndCache(xyz, this->self()); + } + return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()); + } + + /// Return @c true if the value of voxel (x, y, z) resides at the leaf level + /// of the tree, i.e., if it is not a tile value. + bool isVoxel(const Coord& xyz) const + { + assert(BaseT::mTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return mNode0->getValueLevelAndCache(xyz, this->self())==0; + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return mNode1->getValueLevelAndCache(xyz, this->self())==0; + } else if (this->isHashed2(xyz)) { + assert(mNode2); + return mNode2->getValueLevelAndCache(xyz, this->self())==0; + } + return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()) == + static_cast(RootNodeT::LEVEL); + } + + //@{ + /// Set the value of the voxel at the given coordinates and mark the voxel as active. + void setValue(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->setValueAndCache(xyz, value, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->setValueAndCache(xyz, value, *this); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + const_cast(mNode2)->setValueAndCache(xyz, value, *this); + } else { + BaseT::mTree->root().setValueAndCache(xyz, value, *this); + } + } + void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } + //@} + + /// Set the value of the voxel at the given coordinate but preserves its active state. + void setValueOnly(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->setValueOnlyAndCache(xyz, value, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->setValueOnlyAndCache(xyz, value, *this); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + const_cast(mNode2)->setValueOnlyAndCache(xyz, value, *this); + } else { + BaseT::mTree->root().setValueOnlyAndCache(xyz, value, *this); + } + } + + /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. + void setValueOff(const Coord& xyz, const ValueType& value) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->setValueOffAndCache(xyz, value, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->setValueOffAndCache(xyz, value, *this); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + const_cast(mNode2)->setValueOffAndCache(xyz, value, *this); + } else { + BaseT::mTree->root().setValueOffAndCache(xyz, value, *this); + } + } + + /// @brief Apply a functor to the value of the voxel at the given coordinates + /// and mark the voxel as active. + /// @details See Tree::modifyValue() for details. + template + void modifyValue(const Coord& xyz, const ModifyOp& op) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->modifyValueAndCache(xyz, op, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->modifyValueAndCache(xyz, op, *this); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + const_cast(mNode2)->modifyValueAndCache(xyz, op, *this); + } else { + BaseT::mTree->root().modifyValueAndCache(xyz, op, *this); + } + } + + /// @brief Apply a functor to the voxel at the given coordinates. + /// @details See Tree::modifyValueAndActiveState() for details. + template + void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->modifyValueAndActiveStateAndCache(xyz, op, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->modifyValueAndActiveStateAndCache(xyz, op, *this); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + const_cast(mNode2)->modifyValueAndActiveStateAndCache(xyz, op, *this); + } else { + BaseT::mTree->root().modifyValueAndActiveStateAndCache(xyz, op, *this); + } + } + + /// Set the active state of the voxel at the given coordinates without changing its value. + void setActiveState(const Coord& xyz, bool on = true) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + const_cast(mNode0)->setActiveStateAndCache(xyz, on, *this); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + const_cast(mNode1)->setActiveStateAndCache(xyz, on, *this); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + const_cast(mNode2)->setActiveStateAndCache(xyz, on, *this); + } else { + BaseT::mTree->root().setActiveStateAndCache(xyz, on, *this); + } + } + /// Mark the voxel at the given coordinates as active without changing its value. + void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } + /// Mark the voxel at the given coordinates as inactive without changing its value. + void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } + + /// Return the cached node of type @a NodeType. [Mainly for internal use] + template + NodeT* getNode() + { + const NodeT* node = NULL; + this->getNode(node); + return const_cast(node); + } + + /// Cache the given node, which should lie along the path from the root node to + /// the node containing voxel (x, y, z). [Mainly for internal use] + template + void insertNode(const Coord& xyz, NodeT& node) { this->insert(xyz, &node); } + + /// If a node of the given type exists in the cache, remove it, so that + /// isCached(xyz) returns @c false for any voxel (x, y, z) contained in + /// that node. [Mainly for internal use] + template + void eraseNode() + { + const NodeT* node = NULL; + this->eraseNode(node); + } + + /// @brief Add the specified leaf to this tree, possibly creating a child branch + /// in the process. If the leaf node already exists, replace it. + void addLeaf(LeafNodeT* leaf) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed1(leaf->origin())) { + assert(mNode1); + return const_cast(mNode1)->addLeafAndCache(leaf, *this); + } else if (this->isHashed2(leaf->origin())) { + assert(mNode2); + return const_cast(mNode2)->addLeafAndCache(leaf, *this); + } + BaseT::mTree->root().addLeafAndCache(leaf, *this); + } + + /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), + /// possibly deleting existing nodes or creating new nodes in the process. + void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed1(xyz)) { + assert(mNode1); + return const_cast(mNode1)->addTileAndCache(level, xyz, value, state, *this); + } if (this->isHashed2(xyz)) { + assert(mNode2); + return const_cast(mNode2)->addTileAndCache(level, xyz, value, state, *this); + } + BaseT::mTree->root().addTileAndCache(level, xyz, value, state, *this); + } + + /// @brief @return the leaf node that contains voxel (x, y, z) and + /// if it doesn't exist, create it, but preserve the values and + /// active states of all voxels. + /// + /// Use this method to preallocate a static tree topology over which to + /// safely perform multithreaded processing. + LeafNodeT* touchLeaf(const Coord& xyz) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + if (this->isHashed0(xyz)) { + assert(mNode0); + return const_cast(mNode0); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return const_cast(mNode1)->touchLeafAndCache(xyz, *this); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + return const_cast(mNode2)->touchLeafAndCache(xyz, *this); + } + return BaseT::mTree->root().touchLeafAndCache(xyz, *this); + } + /// @brief @return a pointer to the node of the specified type that contains + /// voxel (x, y, z) and if it doesn't exist, return NULL. + template + NodeT* probeNode(const Coord& xyz) + { + assert(BaseT::mTree); + BOOST_STATIC_ASSERT(!BaseT::IsConstTree); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if ((boost::is_same::value)) { + if (this->isHashed0(xyz)) { + assert(mNode0); + return reinterpret_cast(const_cast(mNode0)); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return const_cast(mNode1)->template probeNodeAndCache(xyz, *this); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + return const_cast(mNode2)->template probeNodeAndCache(xyz, *this); + } + return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); + } else if ((boost::is_same::value)) { + if (this->isHashed1(xyz)) { + assert(mNode1); + return reinterpret_cast(const_cast(mNode1)); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + return const_cast(mNode2)->template probeNodeAndCache(xyz, *this); + } + return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); + } else if ((boost::is_same::value)) { + if (this->isHashed2(xyz)) { + assert(mNode2); + return reinterpret_cast(const_cast(mNode2)); + } + return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); + } + return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + /// @brief @return a pointer to the leaf node that contains + /// voxel (x, y, z) and if it doesn't exist, return NULL. + LeafNodeT* probeLeaf(const Coord& xyz) { return this->template probeNode(xyz); } + + /// @brief @return a const pointer to the node of the specified type that contains + /// voxel (x, y, z) and if it doesn't exist, return NULL. + template + const NodeT* probeConstNode(const Coord& xyz) const + { + assert(BaseT::mTree); + OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN + if ((boost::is_same::value)) { + if (this->isHashed0(xyz)) { + assert(mNode0); + return reinterpret_cast(mNode0); + } else if (this->isHashed1(xyz)) { + assert(mNode1); + return mNode1->template probeConstNodeAndCache(xyz, this->self()); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + return mNode2->template probeConstNodeAndCache(xyz, this->self()); + } + return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); + } else if ((boost::is_same::value)) { + if (this->isHashed1(xyz)) { + assert(mNode1); + return reinterpret_cast(mNode1); + } else if (this->isHashed2(xyz)) { + assert(mNode2); + return mNode2->template probeConstNodeAndCache(xyz, this->self()); + } + return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); + } else if ((boost::is_same::value)) { + if (this->isHashed2(xyz)) { + assert(mNode2); + return reinterpret_cast(mNode2); + } + return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); + } + return NULL; + OPENVDB_NO_UNREACHABLE_CODE_WARNING_END + } + /// @brief @return a const pointer to the leaf node that contains + /// voxel (x, y, z) and if it doesn't exist, return NULL. + const LeafNodeT* probeConstLeaf(const Coord& xyz) const + { + return this->template probeConstNode(xyz); + } + const LeafNodeT* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } + + /// Remove all the cached nodes and invalidate the corresponding hash-keys. + virtual void clear() + { + mKey0 = Coord::max(); + mNode0 = NULL; + mKey1 = Coord::max(); + mNode1 = NULL; + mKey2 = Coord::max(); + mNode2 = NULL; + } + +private: + // Allow nodes to insert themselves into the cache. + template friend class RootNode; + template friend class InternalNode; + template friend class LeafNode; + // Allow trees to deregister themselves. + template friend class Tree; + + // This private method is merely for convenience. + inline ValueAccessor3& self() const { return const_cast(*this); } + + /// Private copy method + inline void copy(const ValueAccessor3& other) + { + mKey0 = other.mKey0; + mNode0 = other.mNode0; + mKey1 = other.mKey1; + mNode1 = other.mNode1; + mKey2 = other.mKey2; + mNode2 = other.mNode2; + } + + /// Prevent this accessor from calling Tree::releaseCache() on a tree that + /// no longer exists. (Called by mTree when it is destroyed.) + virtual void release() + { + this->BaseT::release(); + this->clear(); + } + void getNode(const NodeT0*& node) { node = mNode0; } + void getNode(const NodeT1*& node) { node = mNode1; } + void getNode(const NodeT2*& node) { node = mNode2; } + void getNode(const RootNodeT*& node) + { + node = (BaseT::mTree ? &BaseT::mTree->root() : NULL); + } + template void getNode(const OtherNodeType*& node) { node = NULL; } + + void eraseNode(const NodeT0*) { mKey0 = Coord::max(); mNode0 = NULL; } + void eraseNode(const NodeT1*) { mKey1 = Coord::max(); mNode1 = NULL; } + void eraseNode(const NodeT2*) { mKey2 = Coord::max(); mNode2 = NULL; } + template void eraseNode(const OtherNodeType*) {} + + /// Cache the given node, which should lie along the path from the root node to + /// the node containing voxel (x, y, z). + /// @note This operation is not mutex-protected and is intended to be called + /// only by nodes and only in the context of a getValue() or setValue() call. + inline void insert(const Coord& xyz, const NodeT0* node) + { + assert(node); + mKey0 = xyz & ~(NodeT0::DIM-1); + mNode0 = node; + } + inline void insert(const Coord& xyz, const NodeT1* node) + { + assert(node); + mKey1 = xyz & ~(NodeT1::DIM-1); + mNode1 = node; + } + inline void insert(const Coord& xyz, const NodeT2* node) + { + assert(node); + mKey2 = xyz & ~(NodeT2::DIM-1); + mNode2 = node; + } + /// No-op in case a tree traversal attemps to insert a node that + /// is not cached by the ValueAccessor + template + inline void insert(const Coord&, const OtherNodeType*) + { + } + inline bool isHashed0(const Coord& xyz) const + { + return (xyz[0] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[0] + && (xyz[1] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[1] + && (xyz[2] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[2]; + } + inline bool isHashed1(const Coord& xyz) const + { + return (xyz[0] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[0] + && (xyz[1] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[1] + && (xyz[2] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[2]; + } + inline bool isHashed2(const Coord& xyz) const + { + return (xyz[0] & ~Coord::ValueType(NodeT2::DIM-1)) == mKey2[0] + && (xyz[1] & ~Coord::ValueType(NodeT2::DIM-1)) == mKey2[1] + && (xyz[2] & ~Coord::ValueType(NodeT2::DIM-1)) == mKey2[2]; + } + mutable Coord mKey0; + mutable const NodeT0* mNode0; + mutable Coord mKey1; + mutable const NodeT1* mNode1; + mutable Coord mKey2; + mutable const NodeT2* mNode2; +}; // ValueAccessor3 + +} // namespace tree +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TREE_VALUEACCESSOR_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestBBox.cc b/openvdb_2_3_0_library/openvdb/unittest/TestBBox.cc new file mode 100755 index 0000000..2f18cbd --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestBBox.cc @@ -0,0 +1,126 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +typedef float Real; + +class TestBBox: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestBBox); + CPPUNIT_TEST(testBBox); + CPPUNIT_TEST(testCenter); + CPPUNIT_TEST(testExtent); + CPPUNIT_TEST_SUITE_END(); + + void testBBox(); + void testCenter(); + void testExtent(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestBBox); + + +void +TestBBox::testBBox() +{ + typedef openvdb::Vec3R Vec3R; + typedef openvdb::math::BBox BBoxType; + + { + BBoxType B(Vec3R(1,1,1),Vec3R(2,2,2)); + + CPPUNIT_ASSERT(B.isSorted()); + CPPUNIT_ASSERT(B.isInside(Vec3R(1.5,2,2))); + CPPUNIT_ASSERT(!B.isInside(Vec3R(2,3,2))); + B.expand(Vec3R(3,3,3)); + CPPUNIT_ASSERT(B.isInside(Vec3R(3,3,3))); + } + + { + BBoxType B; + CPPUNIT_ASSERT(B.empty()); + const Vec3R expected(1); + B.expand(expected); + CPPUNIT_ASSERT_EQUAL(expected, B.min()); + CPPUNIT_ASSERT_EQUAL(expected, B.max()); + } +} + + +void +TestBBox::testCenter() +{ + using namespace openvdb::math; + + const Vec3 expected(1.5); + + BBox fbox(openvdb::Vec3R(1.0), openvdb::Vec3R(2.0)); + CPPUNIT_ASSERT_EQUAL(expected, fbox.getCenter()); + + BBox ibox(openvdb::Vec3i(1), openvdb::Vec3i(2)); + CPPUNIT_ASSERT_EQUAL(expected, ibox.getCenter()); + + openvdb::CoordBBox cbox(openvdb::Coord(1), openvdb::Coord(2)); + CPPUNIT_ASSERT_EQUAL(expected, cbox.getCenter()); +} + +void +TestBBox::testExtent() +{ + typedef openvdb::Vec3R Vec3R; + typedef openvdb::math::BBox BBoxType; + + { + BBoxType B(Vec3R(-20,0,1),Vec3R(2,2,2)); + CPPUNIT_ASSERT_EQUAL(size_t(2), B.minExtent()); + CPPUNIT_ASSERT_EQUAL(size_t(0), B.maxExtent()); + } + { + BBoxType B(Vec3R(1,0,1),Vec3R(2,21,20)); + CPPUNIT_ASSERT_EQUAL(size_t(0), B.minExtent()); + CPPUNIT_ASSERT_EQUAL(size_t(1), B.maxExtent()); + } + { + BBoxType B(Vec3R(1,0,1),Vec3R(3,1.5,20)); + CPPUNIT_ASSERT_EQUAL(size_t(1), B.minExtent()); + CPPUNIT_ASSERT_EQUAL(size_t(2), B.maxExtent()); + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestCoord.cc b/openvdb_2_3_0_library/openvdb/unittest/TestCoord.cc new file mode 100755 index 0000000..7291eae --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestCoord.cc @@ -0,0 +1,262 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include // for tbb::split + + +class TestCoord: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestCoord); + CPPUNIT_TEST(testCoord); + CPPUNIT_TEST(testConversion); + CPPUNIT_TEST(testIO); + CPPUNIT_TEST(testCoordBBox); + CPPUNIT_TEST_SUITE_END(); + + void testCoord(); + void testConversion(); + void testIO(); + void testCoordBBox(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestCoord); + + +void +TestCoord::testCoord() +{ + using openvdb::Coord; + + Coord xyz(-1, 2, 4); + Coord xyz2 = -xyz; + CPPUNIT_ASSERT_EQUAL(Coord(1, -2, -4), xyz2); + + xyz2 = -xyz2; + CPPUNIT_ASSERT_EQUAL(xyz, xyz2); + + xyz.setX(-xyz.x()); + CPPUNIT_ASSERT_EQUAL(Coord(1, 2, 4), xyz); + + xyz2 = xyz >> 1; + CPPUNIT_ASSERT_EQUAL(Coord(0, 1, 2), xyz2); + + xyz2 |= 1; + CPPUNIT_ASSERT_EQUAL(Coord(1, 1, 3), xyz2); + + CPPUNIT_ASSERT(xyz2 != xyz); + CPPUNIT_ASSERT(xyz2 < xyz); + CPPUNIT_ASSERT(xyz2 <= xyz); + + xyz2 -= xyz2; + CPPUNIT_ASSERT_EQUAL(Coord(), xyz2); + + xyz2.reset(0, 4, 4); + xyz2.offset(-1); + CPPUNIT_ASSERT_EQUAL(Coord(-1, 3, 3), xyz2); + + // xyz = (1, 2, 4), xyz2 = (-1, 3, 3) + CPPUNIT_ASSERT_EQUAL(Coord(-1, 2, 3), Coord::minComponent(xyz, xyz2)); + CPPUNIT_ASSERT_EQUAL(Coord(1, 3, 4), Coord::maxComponent(xyz, xyz2)); +} + + +void +TestCoord::testConversion() +{ + using openvdb::Coord; + + openvdb::Vec3I iv(1, 2, 4); + Coord xyz(iv); + CPPUNIT_ASSERT_EQUAL(Coord(1, 2, 4), xyz); + CPPUNIT_ASSERT_EQUAL(iv, xyz.asVec3I()); + CPPUNIT_ASSERT_EQUAL(openvdb::Vec3i(1, 2, 4), xyz.asVec3i()); + + iv = (xyz + iv) + xyz; + CPPUNIT_ASSERT_EQUAL(openvdb::Vec3I(3, 6, 12), iv); + iv = iv - xyz; + CPPUNIT_ASSERT_EQUAL(openvdb::Vec3I(2, 4, 8), iv); + + openvdb::Vec3s fv = xyz.asVec3s(); + CPPUNIT_ASSERT(openvdb::math::isExactlyEqual(openvdb::Vec3s(1, 2, 4), fv)); +} + + +void +TestCoord::testIO() +{ + using openvdb::Coord; + + Coord xyz(-1, 2, 4), xyz2; + + std::ostringstream os(std::ios_base::binary); + CPPUNIT_ASSERT_NO_THROW(xyz.write(os)); + + std::istringstream is(os.str(), std::ios_base::binary); + CPPUNIT_ASSERT_NO_THROW(xyz2.read(is)); + + CPPUNIT_ASSERT_EQUAL(xyz, xyz2); + + os.str(""); + os << xyz; + CPPUNIT_ASSERT_EQUAL(std::string("[-1, 2, 4]"), os.str()); +} + +void +TestCoord::testCoordBBox() +{ + {// Empty constructor + openvdb::CoordBBox b; + CPPUNIT_ASSERT_EQUAL(openvdb::Coord::max(), b.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord::min(), b.max()); + CPPUNIT_ASSERT(b.empty()); + } + {// Construct bbox from min and max + const openvdb::Coord min(-1,-2,30), max(20,30,55); + openvdb::CoordBBox b(min, max); + CPPUNIT_ASSERT_EQUAL(min, b.min()); + CPPUNIT_ASSERT_EQUAL(max, b.max()); + } + {// tbb::split constructor + const openvdb::Coord min(-1,-2,30), max(20,30,55); + openvdb::CoordBBox a(min, max), b(a, tbb::split()); + CPPUNIT_ASSERT_EQUAL(min, b.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(20, 14, 55), b.max()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-1, 15, 30), a.min()); + CPPUNIT_ASSERT_EQUAL(max, a.max()); + } + {// createCube + const openvdb::Coord min(0,8,16); + const openvdb::CoordBBox b = openvdb::CoordBBox::createCube(min, 8); + CPPUNIT_ASSERT_EQUAL(min, b.min()); + CPPUNIT_ASSERT_EQUAL(min + openvdb::Coord(8-1), b.max()); + } + {// inf + const openvdb::CoordBBox b = openvdb::CoordBBox::inf(); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord::min(), b.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord::max(), b.max()); + } + {// empty, hasVolume and volume + const openvdb::Coord c(1,2,3); + const openvdb::CoordBBox a(c, c), b(c, c.offsetBy(0,-1,0)); + CPPUNIT_ASSERT( a.hasVolume() && !a.empty()); + CPPUNIT_ASSERT(!b.hasVolume() && b.empty()); + CPPUNIT_ASSERT_EQUAL(uint64_t(1), a.volume()); + CPPUNIT_ASSERT_EQUAL(uint64_t(0), b.volume()); + } + {// volume and split constructor + const openvdb::Coord min(-1,-2,30), max(20,30,55); + const openvdb::CoordBBox bbox(min,max); + openvdb::CoordBBox a(bbox), b(a, tbb::split()); + CPPUNIT_ASSERT_EQUAL(bbox.volume(), a.volume() + b.volume()); + openvdb::CoordBBox c(b, tbb::split()); + CPPUNIT_ASSERT_EQUAL(bbox.volume(), a.volume() + b.volume() + c.volume()); + } + {// getCenter + const openvdb::Coord min(1,2,3), max(6,10,15); + const openvdb::CoordBBox b(min, max); + CPPUNIT_ASSERT_EQUAL(openvdb::Vec3d(3.5, 6.0, 9.0), b.getCenter()); + } + {// a volume that overflows Int32. + typedef openvdb::Int32 Int32; + Int32 maxInt32 = std::numeric_limits::max(); + const openvdb::Coord min(Int32(0), Int32(0), Int32(0)); + const openvdb::Coord max(maxInt32-Int32(2), Int32(2), Int32(2)); + + const openvdb::CoordBBox b(min, max); + uint64_t volume = UINT64_C(19327352814); + CPPUNIT_ASSERT_EQUAL(volume, b.volume()); + } + {// minExtent and maxExtent + const openvdb::Coord min(1,2,3); + { + const openvdb::Coord max = min + openvdb::Coord(1,2,3); + const openvdb::CoordBBox b(min, max); + CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 0); + CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 2); + } + { + const openvdb::Coord max = min + openvdb::Coord(1,3,2); + const openvdb::CoordBBox b(min, max); + CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 0); + CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 1); + } + { + const openvdb::Coord max = min + openvdb::Coord(2,1,3); + const openvdb::CoordBBox b(min, max); + CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 1); + CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 2); + } + { + const openvdb::Coord max = min + openvdb::Coord(2,3,1); + const openvdb::CoordBBox b(min, max); + CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 2); + CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 1); + } + { + const openvdb::Coord max = min + openvdb::Coord(3,1,2); + const openvdb::CoordBBox b(min, max); + CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 1); + CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 0); + } + { + const openvdb::Coord max = min + openvdb::Coord(3,2,1); + const openvdb::CoordBBox b(min, max); + CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 2); + CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 0); + } + } + + {//reset + openvdb::CoordBBox b; + CPPUNIT_ASSERT_EQUAL(openvdb::Coord::max(), b.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord::min(), b.max()); + CPPUNIT_ASSERT(b.empty()); + + const openvdb::Coord min(-1,-2,30), max(20,30,55); + b.reset(min, max); + CPPUNIT_ASSERT_EQUAL(min, b.min()); + CPPUNIT_ASSERT_EQUAL(max, b.max()); + CPPUNIT_ASSERT(!b.empty()); + + b.reset(); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord::max(), b.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord::min(), b.max()); + CPPUNIT_ASSERT(b.empty()); + } + +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestCpt.cc b/openvdb_2_3_0_library/openvdb/unittest/TestCpt.cc new file mode 100755 index 0000000..174cf01 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestCpt.cc @@ -0,0 +1,569 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include // for old GradientStencil +#include "util.h" // for unittest_util::makeSphere() + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + +class TestCpt: public CppUnit::TestFixture +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestCpt); + CPPUNIT_TEST(testCpt); // Cpt in World Space + CPPUNIT_TEST(testCptStencil); + CPPUNIT_TEST(testCptTool); // Cpt tool + CPPUNIT_TEST(testCptMaskedTool); + CPPUNIT_TEST(testOldStyleStencils); // old stencil impl + CPPUNIT_TEST_SUITE_END(); + + void testCpt(); + void testCptStencil(); + void testCptTool(); + void testCptMaskedTool(); + void testOldStyleStencils(); +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION(TestCpt); + +void +TestCpt::testCpt() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + + + { // unit voxel size tests + + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + const FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const Coord dim(64,64,64); + const Vec3f center(35.0, 30.0f, 40.0f); + const float radius=0;//point at {35,30,40} + unittest_util::makeSphere( + dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + + AccessorType inAccessor = grid->getConstAccessor(); + // this uses the gradient. Only test for a few maps, since the gradient is + // tested elsewhere + + Coord xyz(35,30,30); + + math::TranslationMap translate; + // Note the CPT::result is in continuous index space + Vec3f P = math::CPT::result(translate, inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + // CPT_RANGE::result is in the range of the map + // CPT_RANGE::result = map.applyMap(CPT::result()) + // for our tests, the map is an identity so in this special case + // the two versions of the Cpt should exactly agree + P = math::CPT_RANGE::result(translate, inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + xyz.reset(35,30,35); + + P = math::CPT::result(translate, inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + + P = math::CPT_RANGE::result(translate, inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + } + { + // NON-UNIT VOXEL SIZE + + double voxel_size = 0.5; + FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); + grid->setTransform(math::Transform::createLinearTransform(voxel_size)); + CPPUNIT_ASSERT(grid->empty()); + AccessorType inAccessor = grid->getConstAccessor(); + + const openvdb::Coord dim(32,32,32); + const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space + const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere + unittest_util::makeSphere( + dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!grid->empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); + + Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere + math::AffineMap affine(voxel_size*math::Mat3d::identity()); + + Vec3f P = math::CPT::result(affine, inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); + + + P = math::CPT_RANGE::result(affine, inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(10,P[2]); + + xyz.reset(12,16,10); + + P = math::CPT::result(affine, inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); + + + P = math::CPT_RANGE::result(affine, inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(6,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); + + } + { + // NON-UNIFORM SCALING + Vec3d voxel_sizes(0.5, 1, 0.5); + math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); + FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); + grid->setTransform(math::Transform::Ptr(new math::Transform(base_map))); + + CPPUNIT_ASSERT(grid->empty()); + AccessorType inAccessor = grid->getConstAccessor(); + + + const openvdb::Coord dim(32,32,32); + const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space + const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere + unittest_util::makeSphere( + dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!grid->empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); + + Coord ijk = grid->transform().worldToIndexNodeCentered(Vec3d(10,8,10)); + + //Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere + math::ScaleMap scale(voxel_sizes); + Vec3f P; + P = math::CPT::result(scale, inAccessor, ijk); + ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); + + + // world space result + P = math::CPT_RANGE::result(scale, inAccessor, ijk); + CPPUNIT_ASSERT_DOUBLES_EQUAL(16,P[0], 0.02 ); + CPPUNIT_ASSERT_DOUBLES_EQUAL(8, P[1], 0.02); + CPPUNIT_ASSERT_DOUBLES_EQUAL(10,P[2], 0.02); + + //xyz.reset(12,16,10); + ijk = grid->transform().worldToIndexNodeCentered(Vec3d(6,8,5)); + + P = math::CPT::result(scale, inAccessor, ijk); + ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); + + + P = math::CPT_RANGE::result(scale, inAccessor, ijk); + CPPUNIT_ASSERT_DOUBLES_EQUAL(6,P[0], 0.02); + CPPUNIT_ASSERT_DOUBLES_EQUAL(8,P[1], 0.02); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0,P[2], 0.02); + + } + +} + + +void +TestCpt::testCptStencil() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + + { // UNIT VOXEL TEST + + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + const FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const openvdb::Coord dim(64,64,64); + const openvdb::Vec3f center(35.0f ,30.0f, 40.0f); + const float radius=0.0f; + unittest_util::makeSphere( + dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + // this uses the gradient. Only test for a few maps, since the gradient is + // tested elsewhere + + math::SevenPointStencil sevenpt(*grid); + math::SecondOrderDenseStencil dense_2nd(*grid); + + + Coord xyz(35,30,30); + CPPUNIT_ASSERT(tree.isValueOn(xyz)); + + sevenpt.moveTo(xyz); + dense_2nd.moveTo(xyz); + + math::TranslationMap translate; + // Note the CPT::result is in continuous index space + Vec3f P = math::CPT::result(translate, sevenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + // CPT_RANGE::result_stencil is in the range of the map + // CPT_RANGE::result_stencil = map.applyMap(CPT::result_stencil()) + // for our tests, the map is an identity so in this special case + // the two versions of the Cpt should exactly agree + P = math::CPT_RANGE::result(translate, sevenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + xyz.reset(35,30,35); + + sevenpt.moveTo(xyz); + dense_2nd.moveTo(xyz); + + CPPUNIT_ASSERT(tree.isValueOn(xyz)); + + P = math::CPT::result(translate, sevenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + + P = math::CPT_RANGE::result(translate, sevenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + + xyz.reset(35,30,30); + + sevenpt.moveTo(xyz); + dense_2nd.moveTo(xyz); + + math::AffineMap affine; + + P = math::CPT::result(affine, dense_2nd); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + + P = math::CPT_RANGE::result(affine, dense_2nd); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + xyz.reset(35,30,35); + + sevenpt.moveTo(xyz); + dense_2nd.moveTo(xyz); + + CPPUNIT_ASSERT(tree.isValueOn(xyz)); + + P = math::CPT::result(affine, dense_2nd); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + CPPUNIT_ASSERT(tree.isValueOn(xyz)); + + P = math::CPT_RANGE::result(affine, dense_2nd); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + } + { + // NON-UNIT VOXEL SIZE + + double voxel_size = 0.5; + FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); + grid->setTransform(math::Transform::createLinearTransform(voxel_size)); + CPPUNIT_ASSERT(grid->empty()); + + const openvdb::Coord dim(32,32,32); + const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space + const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere + unittest_util::makeSphere( + dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!grid->empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); + + + math::SecondOrderDenseStencil dense_2nd(*grid); + + + Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere + math::AffineMap affine(voxel_size*math::Mat3d::identity()); + dense_2nd.moveTo(xyz); + + Vec3f P = math::CPT::result(affine, dense_2nd); + ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); + + + P = math::CPT_RANGE::result(affine, dense_2nd); + ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(10,P[2]); + + xyz.reset(12,16,10); + dense_2nd.moveTo(xyz); + + P = math::CPT::result(affine, dense_2nd); + ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); + + + P = math::CPT_RANGE::result(affine, dense_2nd); + ASSERT_DOUBLES_EXACTLY_EQUAL(6,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); + + } + { + // NON-UNIFORM SCALING + Vec3d voxel_sizes(0.5, 1, 0.5); + math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); + FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); + grid->setTransform(math::Transform::Ptr(new math::Transform(base_map))); + + CPPUNIT_ASSERT(grid->empty()); + + + const openvdb::Coord dim(32,32,32); + const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space + const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere + unittest_util::makeSphere( + dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!grid->empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); + + Coord ijk = grid->transform().worldToIndexNodeCentered(Vec3d(10,8,10)); + math::SevenPointStencil sevenpt(*grid); + + sevenpt.moveTo(ijk); + + //Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere + math::ScaleMap scale(voxel_sizes); + Vec3f P; + P = math::CPT::result(scale, sevenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); + + + // world space result + P = math::CPT_RANGE::result(scale, sevenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(16,P[0], 0.02 ); + CPPUNIT_ASSERT_DOUBLES_EQUAL(8, P[1], 0.02); + CPPUNIT_ASSERT_DOUBLES_EQUAL(10,P[2], 0.02); + + //xyz.reset(12,16,10); + ijk = grid->transform().worldToIndexNodeCentered(Vec3d(6,8,5)); + sevenpt.moveTo(ijk); + P = math::CPT::result(scale, sevenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); + + + P = math::CPT_RANGE::result(scale, sevenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(6,P[0], 0.02); + CPPUNIT_ASSERT_DOUBLES_EQUAL(8,P[1], 0.02); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0,P[2], 0.02); + + } + + +} + +void +TestCpt::testCptTool() +{ + using namespace openvdb; + + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + const FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const openvdb::Coord dim(64,64,64); + const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); + const float radius=0;//point at {35,30,40} + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + // run the tool + typedef openvdb::tools::Cpt FloatCpt; + FloatCpt cpt(*grid); + FloatCpt::OutGridType::Ptr cptGrid = + cpt.process(true/*threaded*/, false/*use world transform*/); + + FloatCpt::OutGridType::ConstAccessor cptAccessor = cptGrid->getConstAccessor(); + + Coord xyz(35,30,30); + CPPUNIT_ASSERT(tree.isValueOn(xyz)); + + Vec3f P = cptAccessor.getValue(xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); + + xyz.reset(35,30,35); + CPPUNIT_ASSERT(tree.isValueOn(xyz)); + + P = cptAccessor.getValue(xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); +} + +void +TestCpt::testCptMaskedTool() +{ + using namespace openvdb; + + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + const FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const openvdb::Coord dim(64,64,64); + const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); + const float radius=0;//point at {35,30,40} + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); + BoolGrid::Ptr maskGrid = BoolGrid::create(false); + maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); + + // run the tool + typedef openvdb::tools::Cpt FloatCpt; + FloatCpt cpt(*grid, *maskGrid); + FloatCpt::OutGridType::Ptr cptGrid = + cpt.process(true/*threaded*/, false/*use world transform*/); + + FloatCpt::OutGridType::ConstAccessor cptAccessor = cptGrid->getConstAccessor(); + + // inside the masked region + Coord xyz(35,30,30); + CPPUNIT_ASSERT(tree.isValueOn(xyz)); + + Vec3f P = cptAccessor.getValue(xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[0], P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[1], P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(center[2], P[2]); + + // outside the masked region + xyz.reset(42,42,42); + CPPUNIT_ASSERT(!cptAccessor.isValueOn(xyz)); +} + +void +TestCpt::testOldStyleStencils() +{ + using namespace openvdb; + + {// test of level set to sphere at (6,8,10) with R=10 and dx=0.5 + + FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); + grid->setTransform(math::Transform::createLinearTransform(/*voxel size=*/0.5)); + CPPUNIT_ASSERT(grid->empty()); + + const openvdb::Coord dim(32,32,32); + const openvdb::Vec3f center(6.0f,8.0f,10.0f);//i.e. (12,16,20) in index space + const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere + unittest_util::makeSphere( + dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!grid->empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); + math::GradStencil gs(*grid); + + Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere + gs.moveTo(xyz); + float dist = gs.getValue();//signed closest distance to sphere in world coordinates + Vec3f P = gs.cpt();//closes point to sphere in index space + ASSERT_DOUBLES_EXACTLY_EQUAL(dist,-6); + ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); + + xyz.reset(12,16,10);//i.e. (6,8,5) in world space or 15 world units inside the sphere + gs.moveTo(xyz); + dist = gs.getValue();//signed closest distance to sphere in world coordinates + P = gs.cpt();//closes point to sphere in index space + ASSERT_DOUBLES_EXACTLY_EQUAL(-5,dist); + ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL( 0,P[2]); + } +} + + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestCurl.cc b/openvdb_2_3_0_library/openvdb/unittest/TestCurl.cc new file mode 100755 index 0000000..50934da --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestCurl.cc @@ -0,0 +1,595 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/1e-6); + +namespace { +const int GRID_DIM = 10; +} + + +class TestCurl: public CppUnit::TestFixture +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestCurl); + CPPUNIT_TEST(testISCurl); // Gradient in Index Space + CPPUNIT_TEST(testISCurlStencil); + CPPUNIT_TEST(testWSCurl); // Gradient in World Space + CPPUNIT_TEST(testWSCurlStencil); + CPPUNIT_TEST(testCurlTool); // Gradient tool + CPPUNIT_TEST(testCurlMaskedTool); // Gradient tool + + CPPUNIT_TEST_SUITE_END(); + + void testISCurl(); + void testISCurlStencil(); + void testWSCurl(); + void testWSCurlStencil(); + void testCurlTool(); + void testCurlMaskedTool(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestCurl); + + +void +TestCurl::testCurlTool() +{ + using namespace openvdb; + + typedef VectorGrid::ConstAccessor Accessor; + + VectorGrid::Ptr inGrid = VectorGrid::create(); + const VectorTree& inTree = inGrid->tree(); + CPPUNIT_ASSERT(inTree.empty()); + + VectorGrid::Accessor inAccessor = inGrid->getAccessor(); + int dim = GRID_DIM; + for (int x = -dim; xactiveVoxelCount())); + + VectorGrid::ConstAccessor curlAccessor = curl_grid->getConstAccessor(); + --dim;//ignore boundary curl vectors + for (int x = -dim; x +#include +#include +#include +#include +#include +#ifdef BENCHMARK_TEST +#include "util.h" // for CpuTimer +#endif + + +class TestDense: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestDense); + + CPPUNIT_TEST(testDenseZYX); + CPPUNIT_TEST(testDenseXYZ); + + CPPUNIT_TEST(testCopyZYX); + CPPUNIT_TEST(testCopyXYZ); + + CPPUNIT_TEST(testCopyBoolZYX); + CPPUNIT_TEST(testCopyBoolXYZ); + + CPPUNIT_TEST(testCopyFromDenseWithOffsetZYX); + CPPUNIT_TEST(testCopyFromDenseWithOffsetXYZ); + + CPPUNIT_TEST(testDense2SparseZYX); + CPPUNIT_TEST(testDense2SparseXYZ); + + CPPUNIT_TEST(testDense2Sparse2ZYX); + CPPUNIT_TEST(testDense2Sparse2XYZ); + + CPPUNIT_TEST(testInvalidBBoxZYX); + CPPUNIT_TEST(testInvalidBBoxXYZ); + + CPPUNIT_TEST(testDense2Sparse2DenseZYX); + CPPUNIT_TEST(testDense2Sparse2DenseXYZ); + CPPUNIT_TEST_SUITE_END(); + + void testDenseZYX(); + void testDenseXYZ(); + + template + void testCopy(); + void testCopyZYX() { this->testCopy(); } + void testCopyXYZ() { this->testCopy(); } + + template + void testCopyBool(); + void testCopyBoolZYX() { this->testCopyBool(); } + void testCopyBoolXYZ() { this->testCopyBool(); } + + template + void testCopyFromDenseWithOffset(); + void testCopyFromDenseWithOffsetZYX() { + this->testCopyFromDenseWithOffset(); + } + void testCopyFromDenseWithOffsetXYZ() { + this->testCopyFromDenseWithOffset(); + } + + template + void testDense2Sparse(); + void testDense2SparseZYX() { this->testDense2Sparse(); } + void testDense2SparseXYZ() { this->testDense2Sparse(); } + + template + void testDense2Sparse2(); + void testDense2Sparse2ZYX() { this->testDense2Sparse2(); } + void testDense2Sparse2XYZ() { this->testDense2Sparse2(); } + + template + void testInvalidBBox(); + void testInvalidBBoxZYX() { this->testInvalidBBox(); } + void testInvalidBBoxXYZ() { this->testInvalidBBox(); } + + template + void testDense2Sparse2Dense(); + void testDense2Sparse2DenseZYX() { this->testDense2Sparse2Dense(); } + void testDense2Sparse2DenseXYZ() { this->testDense2Sparse2Dense(); } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestDense); + + +void +TestDense::testDenseZYX() +{ + const openvdb::CoordBBox bbox(openvdb::Coord(-40,-5, 6), + openvdb::Coord(-11, 7,22)); + openvdb::tools::Dense dense(bbox);//LayoutZYX is the default + + // Check Dense::valueCount + const int size = dense.valueCount(); + CPPUNIT_ASSERT_EQUAL(30*13*17, size); + + // Check Dense::fill(float) and Dense::getValue(size_t) + const float v = 0.234f; + dense.fill(v); + for (int i=0; i dense(bbox); + + // Check Dense::valueCount + const int size = dense.valueCount(); + CPPUNIT_ASSERT_EQUAL(30*13*17, size); + + // Check Dense::fill(float) and Dense::getValue(size_t) + const float v = 0.234f; + dense.fill(v); + for (int i=0; i > +class CheckDense +{ +public: + typedef typename TreeT::ValueType ValueT; + + CheckDense() : mTree(NULL), mDense(NULL) + { + CPPUNIT_ASSERT(DenseT::memoryLayout() == openvdb::tools::LayoutZYX || + DenseT::memoryLayout() == openvdb::tools::LayoutXYZ ); + } + + void check(const TreeT& tree, const DenseT& dense) + { + mTree = &tree; + mDense = &dense; + tbb::parallel_for(dense.bbox(), *this); + } + void operator()(const openvdb::CoordBBox& bbox) const + { + openvdb::tree::ValueAccessor acc(*mTree); + + if (DenseT::memoryLayout() == openvdb::tools::LayoutZYX) {//resolved at compiletime + for (openvdb::Coord P(bbox.min()); P[0] <= bbox.max()[0]; ++P[0]) { + for (P[1] = bbox.min()[1]; P[1] <= bbox.max()[1]; ++P[1]) { + for (P[2] = bbox.min()[2]; P[2] <= bbox.max()[2]; ++P[2]) { + CPPUNIT_ASSERT_DOUBLES_EQUAL(acc.getValue(P), mDense->getValue(P), + /*tolerance=*/0.0001); + } + } + } + } else { + for (openvdb::Coord P(bbox.min()); P[2] <= bbox.max()[2]; ++P[2]) { + for (P[1] = bbox.min()[1]; P[1] <= bbox.max()[1]; ++P[1]) { + for (P[0] = bbox.min()[0]; P[0] <= bbox.max()[0]; ++P[0]) { + CPPUNIT_ASSERT_DOUBLES_EQUAL(acc.getValue(P), mDense->getValue(P), + /*tolerance=*/0.0001); + } + } + } + } + } +private: + const TreeT* mTree; + const DenseT* mDense; +};// CheckDense + +template +void +TestDense::testCopy() +{ + using namespace openvdb; + + //std::cerr << "\nTesting testCopy with " + // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" + // << std::endl; + + typedef tools::Dense DenseT; + CheckDense checkDense; + const float radius = 10.0f, tolerance = 0.00001f; + const Vec3f center(0.0f); + // decrease the voxelSize to test larger grids +#ifdef BENCHMARK_TEST + const float voxelSize = 0.05f, width = 5.0f; +#else + const float voxelSize = 0.5f, width = 5.0f; +#endif + + // Create a VDB containing a level set of a sphere + FloatGrid::Ptr grid = + tools::createLevelSetSphere(radius, center, voxelSize, width); + FloatTree& tree0 = grid->tree(); + + // Create an empty dense grid + DenseT dense(grid->evalActiveVoxelBoundingBox()); +#ifdef BENCHMARK_TEST + std::cerr << "\nBBox = " << grid->evalActiveVoxelBoundingBox() << std::endl; +#endif + + {//check Dense::fill + dense.fill(voxelSize); +#ifndef BENCHMARK_TEST + checkDense.check(FloatTree(voxelSize), dense); +#endif + } + + {// parallel convert to dense +#ifdef BENCHMARK_TEST + unittest_util::CpuTimer ts; + ts.start("CopyToDense"); +#endif + tools::copyToDense(*grid, dense); +#ifdef BENCHMARK_TEST + ts.stop(); +#else + checkDense.check(tree0, dense); +#endif + } + + {// Parallel create from dense +#ifdef BENCHMARK_TEST + unittest_util::CpuTimer ts; + ts.start("CopyFromDense"); +#endif + FloatTree tree1(tree0.background()); + tools::copyFromDense(dense, tree1, tolerance); +#ifdef BENCHMARK_TEST + ts.stop(); +#else + checkDense.check(tree1, dense); +#endif + } +} + +template +void +TestDense::testCopyBool() +{ + using namespace openvdb; + + //std::cerr << "\nTesting testCopyBool with " + // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" + // << std::endl; + + const Coord bmin(-1), bmax(8); + const CoordBBox bbox(bmin, bmax); + + BoolGrid::Ptr grid = createGrid(false); + BoolGrid::ConstAccessor acc = grid->getConstAccessor(); + + typedef openvdb::tools::Dense DenseT; + DenseT dense(bbox); + dense.fill(false); + + // Start with sparse and dense grids both filled with false. + Coord xyz; + int &x = xyz[0], &y = xyz[1], &z = xyz[2]; + for (x = bmin.x(); x <= bmax.x(); ++x) { + for (y = bmin.y(); y <= bmax.y(); ++y) { + for (z = bmin.z(); z <= bmax.z(); ++z) { + CPPUNIT_ASSERT_EQUAL(false, dense.getValue(xyz)); + CPPUNIT_ASSERT_EQUAL(false, acc.getValue(xyz)); + } + } + } + + // Fill the dense grid with true. + dense.fill(true); + // Copy the contents of the dense grid to the sparse grid. + tools::copyFromDense(dense, *grid, /*tolerance=*/false); + + // Verify that both sparse and dense grids are now filled with true. + for (x = bmin.x(); x <= bmax.x(); ++x) { + for (y = bmin.y(); y <= bmax.y(); ++y) { + for (z = bmin.z(); z <= bmax.z(); ++z) { + CPPUNIT_ASSERT_EQUAL(true, dense.getValue(xyz)); + CPPUNIT_ASSERT_EQUAL(true, acc.getValue(xyz)); + } + } + } + + // Fill the dense grid with false. + dense.fill(false); + // Copy the contents (= true) of the sparse grid to the dense grid. + tools::copyToDense(*grid, dense); + + // Verify that the dense grid is now filled with true. + for (x = bmin.x(); x <= bmax.x(); ++x) { + for (y = bmin.y(); y <= bmax.y(); ++y) { + for (z = bmin.z(); z <= bmax.z(); ++z) { + CPPUNIT_ASSERT_EQUAL(true, dense.getValue(xyz)); + } + } + } +} + + +// Test copying from a dense grid to a sparse grid with various bounding boxes. +template +void +TestDense::testCopyFromDenseWithOffset() +{ + using namespace openvdb; + + //std::cerr << "\nTesting testCopyFromDenseWithOffset with " + // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" + // << std::endl; + + typedef openvdb::tools::Dense DenseT; + + const int DIM = 20, COUNT = DIM * DIM * DIM; + const float FOREGROUND = 99.0f, BACKGROUND = 5000.0f; + + const int OFFSET[] = { 1, -1, 1001, -1001 }; + for (int offsetIdx = 0; offsetIdx < 4; ++offsetIdx) { + + const int offset = OFFSET[offsetIdx]; + const CoordBBox bbox = CoordBBox::createCube(Coord(offset), DIM); + + DenseT dense(bbox, FOREGROUND); + CPPUNIT_ASSERT_EQUAL(bbox, dense.bbox()); + + FloatGrid grid(BACKGROUND); + tools::copyFromDense(dense, grid, /*tolerance=*/0.0); + + const CoordBBox gridBBox = grid.evalActiveVoxelBoundingBox(); + CPPUNIT_ASSERT_EQUAL(bbox, gridBBox); + CPPUNIT_ASSERT_EQUAL(COUNT, int(grid.activeVoxelCount())); + + FloatGrid::ConstAccessor acc = grid.getConstAccessor(); + for (int i = gridBBox.min()[0], ie = gridBBox.max()[0]; i < ie; ++i) { + for (int j = gridBBox.min()[1], je = gridBBox.max()[1]; j < je; ++j) { + for (int k = gridBBox.min()[2], ke = gridBBox.max()[2]; k < ke; ++k) { + const Coord ijk(i, j, k); + CPPUNIT_ASSERT_DOUBLES_EQUAL( + FOREGROUND, acc.getValue(ijk), /*tolerance=*/0.0); + CPPUNIT_ASSERT(acc.isValueOn(ijk)); + } + } + } + } +} + +template +void +TestDense::testDense2Sparse() +{ + // The following test revealed a bug in v2.0.0b2 + using namespace openvdb; + + //std::cerr << "\nTesting testDense2Sparse with " + // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" + // << std::endl; + + typedef tools::Dense DenseT; + + // Test Domain Resolution + size_t sizeX = 8; + size_t sizeY = 8; + size_t sizeZ = 9; + + // Define a dense grid + DenseT dense(Coord(sizeX, sizeY, sizeZ)); + const CoordBBox bboxD = dense.bbox(); + // std::cerr << "\nDense bbox" << bboxD << std::endl; + + // Verify that the CoordBBox is truely used as [inclusive, inclusive] + CPPUNIT_ASSERT(dense.valueCount() == sizeX * sizeY * sizeZ ); + + // Fill the dense grid with constant value 1. + dense.fill(1.0f); + + // Create two empty float grids + FloatGrid::Ptr gridS = FloatGrid::create(0.0f /*background*/); + FloatGrid::Ptr gridP = FloatGrid::create(0.0f /*background*/); + + // Convert in serial and parallel modes + tools::copyFromDense(dense, *gridS, /*tolerance*/0.0f, /*serial = */ true); + tools::copyFromDense(dense, *gridP, /*tolerance*/0.0f, /*serial = */ false); + + float minS, maxS; + float minP, maxP; + + gridS->evalMinMax(minS, maxS); + gridP->evalMinMax(minP, maxP); + + const float tolerance = 0.0001; + + CPPUNIT_ASSERT_DOUBLES_EQUAL(minS, minP, tolerance); + CPPUNIT_ASSERT_DOUBLES_EQUAL(maxS, maxP, tolerance); + CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), Index64(sizeX * sizeY * sizeZ)); + + const FloatTree& treeS = gridS->tree(); + const FloatTree& treeP = gridP->tree(); + + // Values in Test Domain are correct + for (Coord ijk(bboxD.min()); ijk[0] <= bboxD.max()[0]; ++ijk[0]) { + for (ijk[1] = bboxD.min()[1]; ijk[1] <= bboxD.max()[1]; ++ijk[1]) { + for (ijk[2] = bboxD.min()[2]; ijk[2] <= bboxD.max()[2]; ++ijk[2]) { + + const float expected = bboxD.isInside(ijk) ? 1.f : 0.f; + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, 1.f, tolerance); + + const float& vS = treeS.getValue(ijk); + const float& vP = treeP.getValue(ijk); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); + } + } + } + + CoordBBox bboxP = gridP->evalActiveVoxelBoundingBox(); + const Index64 voxelCountP = gridP->activeVoxelCount(); + //std::cerr << "\nParallel: bbox=" << bboxP << " voxels=" << voxelCountP << std::endl; + CPPUNIT_ASSERT( bboxP == bboxD ); + CPPUNIT_ASSERT_EQUAL( dense.valueCount(), voxelCountP); + + CoordBBox bboxS = gridS->evalActiveVoxelBoundingBox(); + const Index64 voxelCountS = gridS->activeVoxelCount(); + //std::cerr << "\nSerial: bbox=" << bboxS << " voxels=" << voxelCountS << std::endl; + CPPUNIT_ASSERT( bboxS == bboxD ); + CPPUNIT_ASSERT_EQUAL( dense.valueCount(), voxelCountS); + + // Topology + CPPUNIT_ASSERT( bboxS.isInside(bboxS) ); + CPPUNIT_ASSERT( bboxP.isInside(bboxP) ); + CPPUNIT_ASSERT( bboxS.isInside(bboxP) ); + CPPUNIT_ASSERT( bboxP.isInside(bboxS) ); + + /// Check that the two grids agree + for (Coord ijk(bboxS.min()); ijk[0] <= bboxS.max()[0]; ++ijk[0]) { + for (ijk[1] = bboxS.min()[1]; ijk[1] <= bboxS.max()[1]; ++ijk[1]) { + for (ijk[2] = bboxS.min()[2]; ijk[2] <= bboxS.max()[2]; ++ijk[2]) { + + const float& vS = treeS.getValue(ijk); + const float& vP = treeP.getValue(ijk); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(vS, vP, tolerance); + + // the value we should get based on the original domain + const float expected = bboxD.isInside(ijk) ? 1.f : 0.f; + + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); + } + } + } + + + // Verify the tree topology matches. + + CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), gridS->activeVoxelCount()); + CPPUNIT_ASSERT(gridP->evalActiveVoxelBoundingBox() == gridS->evalActiveVoxelBoundingBox()); + CPPUNIT_ASSERT(treeP.hasSameTopology(treeS) ); + +} + +template +void +TestDense::testDense2Sparse2() +{ + // The following tests copying a dense grid into a VDB tree with + // existing values outside the bbox of the dense grid. + + using namespace openvdb; + + //std::cerr << "\nTesting testDense2Sparse2 with " + // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" + // << std::endl; + + typedef tools::Dense DenseT; + + // Test Domain Resolution + size_t sizeX = 8; + size_t sizeY = 8; + size_t sizeZ = 9; + const Coord magicVoxel(sizeX, sizeY, sizeZ); + + // Define a dense grid + DenseT dense(Coord(sizeX, sizeY, sizeZ)); + const CoordBBox bboxD = dense.bbox(); + //std::cerr << "\nDense bbox" << bboxD << std::endl; + + // Verify that the CoordBBox is truely used as [inclusive, inclusive] + CPPUNIT_ASSERT(dense.valueCount() == sizeX * sizeY * sizeZ ); + + // Fill the dense grid with constant value 1. + dense.fill(1.0f); + + // Create two empty float grids + FloatGrid::Ptr gridS = FloatGrid::create(0.0f /*background*/); + FloatGrid::Ptr gridP = FloatGrid::create(0.0f /*background*/); + gridS->tree().setValue(magicVoxel, 5.0f); + gridP->tree().setValue(magicVoxel, 5.0f); + + // Convert in serial and parallel modes + tools::copyFromDense(dense, *gridS, /*tolerance*/0.0f, /*serial = */ true); + tools::copyFromDense(dense, *gridP, /*tolerance*/0.0f, /*serial = */ false); + + float minS, maxS; + float minP, maxP; + + gridS->evalMinMax(minS, maxS); + gridP->evalMinMax(minP, maxP); + + const float tolerance = 0.0001; + + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0f, minP, tolerance); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0f, minS, tolerance); + CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0f, maxP, tolerance); + CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0f, maxS, tolerance); + CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), Index64(1 + sizeX * sizeY * sizeZ)); + + const FloatTree& treeS = gridS->tree(); + const FloatTree& treeP = gridP->tree(); + + // Values in Test Domain are correct + for (Coord ijk(bboxD.min()); ijk[0] <= bboxD.max()[0]; ++ijk[0]) { + for (ijk[1] = bboxD.min()[1]; ijk[1] <= bboxD.max()[1]; ++ijk[1]) { + for (ijk[2] = bboxD.min()[2]; ijk[2] <= bboxD.max()[2]; ++ijk[2]) { + + const float expected = bboxD.isInside(ijk) ? 1.0f : 0.0f; + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, 1.0f, tolerance); + + const float& vS = treeS.getValue(ijk); + const float& vP = treeP.getValue(ijk); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); + } + } + } + + CoordBBox bboxP = gridP->evalActiveVoxelBoundingBox(); + const Index64 voxelCountP = gridP->activeVoxelCount(); + //std::cerr << "\nParallel: bbox=" << bboxP << " voxels=" << voxelCountP << std::endl; + CPPUNIT_ASSERT( bboxP != bboxD ); + CPPUNIT_ASSERT( bboxP == CoordBBox(Coord(0,0,0), magicVoxel) ); + CPPUNIT_ASSERT_EQUAL( dense.valueCount()+1, voxelCountP); + + CoordBBox bboxS = gridS->evalActiveVoxelBoundingBox(); + const Index64 voxelCountS = gridS->activeVoxelCount(); + //std::cerr << "\nSerial: bbox=" << bboxS << " voxels=" << voxelCountS << std::endl; + CPPUNIT_ASSERT( bboxS != bboxD ); + CPPUNIT_ASSERT( bboxS == CoordBBox(Coord(0,0,0), magicVoxel) ); + CPPUNIT_ASSERT_EQUAL( dense.valueCount()+1, voxelCountS); + + // Topology + CPPUNIT_ASSERT( bboxS.isInside(bboxS) ); + CPPUNIT_ASSERT( bboxP.isInside(bboxP) ); + CPPUNIT_ASSERT( bboxS.isInside(bboxP) ); + CPPUNIT_ASSERT( bboxP.isInside(bboxS) ); + + /// Check that the two grids agree + for (Coord ijk(bboxS.min()); ijk[0] <= bboxS.max()[0]; ++ijk[0]) { + for (ijk[1] = bboxS.min()[1]; ijk[1] <= bboxS.max()[1]; ++ijk[1]) { + for (ijk[2] = bboxS.min()[2]; ijk[2] <= bboxS.max()[2]; ++ijk[2]) { + + const float& vS = treeS.getValue(ijk); + const float& vP = treeP.getValue(ijk); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(vS, vP, tolerance); + + // the value we should get based on the original domain + const float expected = bboxD.isInside(ijk) ? 1.0f + : ijk == magicVoxel ? 5.0f : 0.0f; + + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); + } + } + } + + // Verify the tree topology matches. + + CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), gridS->activeVoxelCount()); + CPPUNIT_ASSERT(gridP->evalActiveVoxelBoundingBox() == gridS->evalActiveVoxelBoundingBox()); + CPPUNIT_ASSERT(treeP.hasSameTopology(treeS) ); + +} + +template +void +TestDense::testInvalidBBox() +{ + using namespace openvdb; + + //std::cerr << "\nTesting testInvalidBBox with " + // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" + // << std::endl; + + typedef tools::Dense DenseT; + const CoordBBox badBBox(Coord(1, 1, 1), Coord(-1, 2, 2)); + + CPPUNIT_ASSERT(badBBox.empty()); + CPPUNIT_ASSERT_THROW(DenseT dense(badBBox), ValueError); +} + +template +void +TestDense::testDense2Sparse2Dense() +{ + using namespace openvdb; + + //std::cerr << "\nTesting testDense2Sparse2Dense with " + // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" + // << std::endl; + + typedef tools::Dense DenseT; + + const CoordBBox bboxBig(Coord(-12, 7, -32), Coord(12, 14, -15)); + const CoordBBox bboxSmall(Coord(-10, 8, -31), Coord(10, 12, -20)); + + + // A larger bbox + CoordBBox bboxBigger = bboxBig; + bboxBigger.expand(Coord(10)); + + + // Small is in big + CPPUNIT_ASSERT(bboxBig.isInside(bboxSmall)); + + // Big is in Bigger + CPPUNIT_ASSERT(bboxBigger.isInside(bboxBig)); + + // Construct a small dense grid + DenseT denseSmall(bboxSmall, 0.f); + { + // insert non-const values + const int n = denseSmall.valueCount(); + float* d = denseSmall.data(); + for (int i = 0; i < n; ++i) { d[i] = i; } + } + // Construct large dense grid + DenseT denseBig(bboxBig, 0.f); + { + // insert non-const values + const int n = denseBig.valueCount(); + float* d = denseBig.data(); + for (int i = 0; i < n; ++i) { d[i] = i; } + } + + // Make a sparse grid to copy this data into + FloatGrid::Ptr grid = FloatGrid::create(3.3f /*background*/); + tools::copyFromDense(denseBig, *grid, /*tolerance*/0.0f, /*serial = */ true); + tools::copyFromDense(denseSmall, *grid, /*tolerance*/0.0f, /*serial = */ false); + + const FloatTree& tree = grid->tree(); + // + CPPUNIT_ASSERT_EQUAL(bboxBig.volume(), grid->activeVoxelCount()); + + // iterate over the Bigger + for (Coord ijk(bboxBigger.min()); ijk[0] <= bboxBigger.max()[0]; ++ijk[0]) { + for (ijk[1] = bboxBigger.min()[1]; ijk[1] <= bboxBigger.max()[1]; ++ijk[1]) { + for (ijk[2] = bboxBigger.min()[2]; ijk[2] <= bboxBigger.max()[2]; ++ijk[2]) { + + float expected = 3.3f; + if (bboxSmall.isInside(ijk)) { + expected = denseSmall.getValue(ijk); + } else if (bboxBig.isInside(ijk)) { + expected = denseBig.getValue(ijk); + } + + const float& value = tree.getValue(ijk); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, value, 0.0001); + + } + } + } + + // Convert to Dense in small bbox + { + DenseT denseSmall2(bboxSmall); + tools::copyToDense(*grid, denseSmall2, true /* serial */); + + // iterate over the Bigger + for (Coord ijk(bboxSmall.min()); ijk[0] <= bboxSmall.max()[0]; ++ijk[0]) { + for (ijk[1] = bboxSmall.min()[1]; ijk[1] <= bboxSmall.max()[1]; ++ijk[1]) { + for (ijk[2] = bboxSmall.min()[2]; ijk[2] <= bboxSmall.max()[2]; ++ijk[2]) { + + const float& expected = denseSmall.getValue(ijk); + const float& value = denseSmall2.getValue(ijk); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, value, 0.0001); + } + } + } + } + // Convert to Dense in large bbox + { + DenseT denseBig2(bboxBig); + + tools::copyToDense(*grid, denseBig2, false /* serial */); + // iterate over the Bigger + for (Coord ijk(bboxBig.min()); ijk[0] <= bboxBig.max()[0]; ++ijk[0]) { + for (ijk[1] = bboxBig.min()[1]; ijk[1] <= bboxBig.max()[1]; ++ijk[1]) { + for (ijk[2] = bboxBig.min()[2]; ijk[2] <= bboxBig.max()[2]; ++ijk[2]) { + + float expected = -1.f; // should never be this + if (bboxSmall.isInside(ijk)) { + expected = denseSmall.getValue(ijk); + } else if (bboxBig.isInside(ijk)) { + expected = denseBig.getValue(ijk); + } + const float& value = denseBig2.getValue(ijk); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, value, 0.0001); + } + } + } + } +} +#undef BENCHMARK_TEST + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestDenseSparseTools.cc b/openvdb_2_3_0_library/openvdb/unittest/TestDenseSparseTools.cc new file mode 100755 index 0000000..060e394 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestDenseSparseTools.cc @@ -0,0 +1,411 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestDivergence.cc b/openvdb_2_3_0_library/openvdb/unittest/TestDivergence.cc new file mode 100755 index 0000000..511a821 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestDivergence.cc @@ -0,0 +1,715 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + +namespace { +const int GRID_DIM = 10; +} + + +class TestDivergence: public CppUnit::TestFixture +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestDivergence); + CPPUNIT_TEST(testISDivergence); // Divergence in Index Space + CPPUNIT_TEST(testISDivergenceStencil); + CPPUNIT_TEST(testWSDivergence); // Divergence in World Space + CPPUNIT_TEST(testWSDivergenceStencil); + CPPUNIT_TEST(testDivergenceTool); // Divergence tool + CPPUNIT_TEST(testDivergenceMaskedTool); // Divergence tool + CPPUNIT_TEST(testStaggeredDivergence); + CPPUNIT_TEST_SUITE_END(); + + void testISDivergence(); + void testISDivergenceStencil(); + void testWSDivergence(); + void testWSDivergenceStencil(); + void testDivergenceTool(); + void testDivergenceMaskedTool(); + void testStaggeredDivergence(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestDivergence); + + +void +TestDivergence::testDivergenceTool() +{ + using namespace openvdb; + + typedef VectorGrid::ConstAccessor Accessor; + + VectorGrid::Ptr inGrid = VectorGrid::create(); + VectorTree& inTree = inGrid->tree(); + CPPUNIT_ASSERT(inTree.empty()); + + int dim = GRID_DIM; + for (int x = -dim; xactiveVoxelCount())); + + FloatGrid::ConstAccessor accessor = divGrid->getConstAccessor(); + --dim;//ignore boundary divergence + for (int x = -dim; xtree(); + CPPUNIT_ASSERT(inTree.empty()); + + int dim = GRID_DIM; + for (int x = -dim; xfill(maskBBox, true /*value*/, true /*activate*/); + + FloatGrid::Ptr divGrid = tools::divergence(*inGrid, *maskGrid); + CPPUNIT_ASSERT_EQUAL(math::Pow3(dim), int(divGrid->activeVoxelCount())); + + FloatGrid::ConstAccessor accessor = divGrid->getConstAccessor(); + --dim;//ignore boundary divergence + for (int x = -dim; xsetGridClass( GRID_STAGGERED ); + VectorTree& inTree = inGrid->tree(); + CPPUNIT_ASSERT(inTree.empty()); + + int dim = GRID_DIM; + for (int x = -dim; xactiveVoxelCount())); + + FloatGrid::ConstAccessor accessor = divGrid->getConstAccessor(); + --dim;//ignore boundary divergence + for (int x = -dim; xtree(); + CPPUNIT_ASSERT(inTree.empty()); + + int dim = GRID_DIM; + for (int x = -dim; xgetConstAccessor(); + CPPUNIT_ASSERT(!inTree.empty()); + CPPUNIT_ASSERT_EQUAL(math::Pow3(2*dim), int(inTree.activeVoxelCount())); + + --dim;//ignore boundary divergence + // test index space divergence + for (int x = -dim; x::result(inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + + d = math::ISDivergence::result(inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + + d = math::ISDivergence::result(inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + } + } + } + + --dim;//ignore boundary divergence + // test index space divergence + for (int x = -dim; x::result(inAccessor, xyz); + + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + d = math::ISDivergence::result(inAccessor, xyz); + + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + d = math::ISDivergence::result(inAccessor, xyz); + + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + } + } + } + + --dim;//ignore boundary divergence + // test index space divergence + for (int x = -dim; x::result(inAccessor, xyz); + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + + d = math::ISDivergence::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); + + d = math::ISDivergence::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); + } + } + } +} + + +void +TestDivergence::testISDivergenceStencil() +{ + using namespace openvdb; + + typedef VectorGrid::ConstAccessor Accessor; + + VectorGrid::Ptr inGrid = VectorGrid::create(); + VectorTree& inTree = inGrid->tree(); + CPPUNIT_ASSERT(inTree.empty()); + + int dim = GRID_DIM; + for (int x = -dim; x sevenpt(*inGrid); + math::ThirteenPointStencil thirteenpt(*inGrid); + math::NineteenPointStencil nineteenpt(*inGrid); + + --dim;//ignore boundary divergence + // test index space divergence + for (int x = -dim; x::result(sevenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + + d = math::ISDivergence::result(sevenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + + d = math::ISDivergence::result(sevenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + } + } + } + + --dim;//ignore boundary divergence + // test index space divergence + for (int x = -dim; x::result(thirteenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + + d = math::ISDivergence::result(thirteenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + + d = math::ISDivergence::result(thirteenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + } + } + } + + --dim;//ignore boundary divergence + // test index space divergence + for (int x = -dim; x::result(nineteenpt); + ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); + + d = math::ISDivergence::result(nineteenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); + + d = math::ISDivergence::result(nineteenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); + } + } + } +} + + +void +TestDivergence::testWSDivergence() +{ + using namespace openvdb; + + typedef VectorGrid::ConstAccessor Accessor; + + { // non-unit voxel size + double voxel_size = 0.5; + VectorGrid::Ptr inGrid = VectorGrid::create(); + inGrid->setTransform(math::Transform::createLinearTransform(voxel_size)); + + VectorTree& inTree = inGrid->tree(); + CPPUNIT_ASSERT(inTree.empty()); + + int dim = GRID_DIM; + for (int x = -dim; xindexToWorld(Vec3d(x,y,z)); + inTree.setValue(Coord(x,y,z), + VectorTree::ValueType(location.x(), location.y(), 0)); + } + } + } + + Accessor inAccessor = inGrid->getConstAccessor(); + CPPUNIT_ASSERT(!inTree.empty()); + CPPUNIT_ASSERT_EQUAL(math::Pow3(2*dim), int(inTree.activeVoxelCount())); + + --dim;//ignore boundary divergence + + // test with a map + // test with a map + math::AffineMap map(voxel_size*math::Mat3d::identity()); + math::UniformScaleMap uniform_map(voxel_size); + math::UniformScaleTranslateMap uniform_translate_map(voxel_size, Vec3d(0,0,0)); + + for (int x = -dim; x +#include +#include + +class TestDoubleMetadata : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestDoubleMetadata); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + void test(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestDoubleMetadata); + +void +TestDoubleMetadata::test() +{ + using namespace openvdb; + + Metadata::Ptr m(new DoubleMetadata(1.23)); + Metadata::Ptr m2 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); + + CPPUNIT_ASSERT(m->typeName().compare("double") == 0); + CPPUNIT_ASSERT(m2->typeName().compare("double") == 0); + + DoubleMetadata *s = dynamic_cast(m.get()); + //CPPUNIT_ASSERT(s->value() == 1.23); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.23,s->value(),0); + s->value() = 4.56; + //CPPUNIT_ASSERT(s->value() == 4.56); + CPPUNIT_ASSERT_DOUBLES_EQUAL(4.56,s->value(),0); + + m2->copy(*s); + + s = dynamic_cast(m2.get()); + //CPPUNIT_ASSERT(s->value() == 4.56); + CPPUNIT_ASSERT_DOUBLES_EQUAL(4.56,s->value(),0); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestExceptions.cc b/openvdb_2_3_0_library/openvdb/unittest/TestExceptions.cc new file mode 100755 index 0000000..7167de7 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestExceptions.cc @@ -0,0 +1,111 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include + + +class TestExceptions : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestExceptions); + CPPUNIT_TEST(testArithmeticError); + CPPUNIT_TEST(testIndexError); + CPPUNIT_TEST(testIoError); + CPPUNIT_TEST(testKeyError); + CPPUNIT_TEST(testLookupError); + CPPUNIT_TEST(testNotImplementedError); + CPPUNIT_TEST(testReferenceError); + CPPUNIT_TEST(testRuntimeError); + CPPUNIT_TEST(testTypeError); + CPPUNIT_TEST(testValueError); + CPPUNIT_TEST_SUITE_END(); + + void testArithmeticError() { testException(); } + void testIndexError() { testException(); } + void testIoError() { testException(); } + void testKeyError() { testException(); } + void testLookupError() { testException(); } + void testNotImplementedError() { testException(); } + void testReferenceError() { testException(); } + void testRuntimeError() { testException(); } + void testTypeError() { testException(); } + void testValueError() { testException(); } + +private: + template void testException(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestExceptions); + + +template struct ExceptionTraits +{ static std::string name() { return ""; } }; +template<> struct ExceptionTraits +{ static std::string name() { return "ArithmeticError"; } }; +template<> struct ExceptionTraits +{ static std::string name() { return "IndexError"; } }; +template<> struct ExceptionTraits +{ static std::string name() { return "IoError"; } }; +template<> struct ExceptionTraits +{ static std::string name() { return "KeyError"; } }; +template<> struct ExceptionTraits +{ static std::string name() { return "LookupError"; } }; +template<> struct ExceptionTraits +{ static std::string name() { return "NotImplementedError"; } }; +template<> struct ExceptionTraits +{ static std::string name() { return "ReferenceError"; } }; +template<> struct ExceptionTraits +{ static std::string name() { return "RuntimeError"; } }; +template<> struct ExceptionTraits +{ static std::string name() { return "TypeError"; } }; +template<> struct ExceptionTraits +{ static std::string name() { return "ValueError"; } }; + + +template +void +TestExceptions::testException() +{ + std::string ErrorMsg("Error message"); + + CPPUNIT_ASSERT_THROW(OPENVDB_THROW(ExceptionT, ErrorMsg), ExceptionT); + + try { + OPENVDB_THROW(ExceptionT, ErrorMsg); + } catch (openvdb::Exception& e) { + const std::string expectedMsg = ExceptionTraits::name() + ": " + ErrorMsg; + CPPUNIT_ASSERT_EQUAL(expectedMsg, std::string(e.what())); + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestFile.cc b/openvdb_2_3_0_library/openvdb/unittest/TestFile.cc new file mode 100755 index 0000000..673d584 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestFile.cc @@ -0,0 +1,2205 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include // for tbb::this_tbb_thread::sleep() +#include +#include +#include +#include +#include +#include +#include // for tools::sdfToFogVolume() +#include +#include +#include "util.h" // for unittest_util::makeSphere() +#include +#include +#include +#include // for stat() +#include +#ifndef _WIN32 +#include +#endif + + +class TestFile: public CppUnit::TestCase +{ +public: + virtual void setUp() {} + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestFile); + CPPUNIT_TEST(testHeader); + CPPUNIT_TEST(testWriteGrid); + CPPUNIT_TEST(testWriteMultipleGrids); + CPPUNIT_TEST(testWriteFloatAsHalf); + CPPUNIT_TEST(testWriteInstancedGrids); + CPPUNIT_TEST(testReadGridDescriptors); + CPPUNIT_TEST(testGridNaming); + CPPUNIT_TEST(testEmptyFile); + CPPUNIT_TEST(testEmptyGridIO); + CPPUNIT_TEST(testOpen); + CPPUNIT_TEST(testNonVdbOpen); + CPPUNIT_TEST(testGetMetadata); + CPPUNIT_TEST(testReadAll); + CPPUNIT_TEST(testWriteOpenFile); + CPPUNIT_TEST(testReadGridMetadata); + CPPUNIT_TEST(testReadGridPartial); + CPPUNIT_TEST(testReadGrid); + CPPUNIT_TEST(testMultipleBufferIO); + CPPUNIT_TEST(testHasGrid); + CPPUNIT_TEST(testNameIterator); + CPPUNIT_TEST(testReadOldFileFormat); + CPPUNIT_TEST(testCompression); + CPPUNIT_TEST(testAsync); + CPPUNIT_TEST_SUITE_END(); + + void testHeader(); + void testWriteGrid(); + void testWriteMultipleGrids(); + void testWriteFloatAsHalf(); + void testWriteInstancedGrids(); + void testReadGridDescriptors(); + void testGridNaming(); + void testEmptyFile(); + void testEmptyGridIO(); + void testOpen(); + void testNonVdbOpen(); + void testGetMetadata(); + void testReadAll(); + void testWriteOpenFile(); + void testReadGridMetadata(); + void testReadGridPartial(); + void testReadGrid(); + void testMultipleBufferIO(); + void testHasGrid(); + void testNameIterator(); + void testReadOldFileFormat(); + void testCompression(); + void testAsync(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestFile); + + +//////////////////////////////////////// + + +void +TestFile::testHeader() +{ + using namespace openvdb::io; + + File file("something.vdb2"); + + std::ostringstream ostr(std::ios_base::binary); + + file.writeHeader(ostr, /*seekable=*/true); + + std::string uuid_str=file.getUniqueTag(); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + bool unique=true; + CPPUNIT_ASSERT_NO_THROW(unique=file.readHeader(istr)); + + CPPUNIT_ASSERT(!unique);//reading same file again + + CPPUNIT_ASSERT_EQUAL(openvdb::OPENVDB_FILE_VERSION, file.fileVersion()); + CPPUNIT_ASSERT_EQUAL(openvdb::OPENVDB_LIBRARY_MAJOR_VERSION, file.libraryVersion().first); + CPPUNIT_ASSERT_EQUAL(openvdb::OPENVDB_LIBRARY_MINOR_VERSION, file.libraryVersion().second); + CPPUNIT_ASSERT_EQUAL(uuid_str, file.getUniqueTag()); + + //std::cerr << "\nuuid=" << uuid_str << std::endl; + + CPPUNIT_ASSERT(file.isIdentical(uuid_str)); + + remove("something.vdb2"); +} + + +void +TestFile::testWriteGrid() +{ + using namespace openvdb; + using namespace openvdb::io; + + typedef Int32Tree TreeType; + typedef Grid GridType; + + File file("something.vdb2"); + + std::ostringstream ostr(std::ios_base::binary); + + // Create a grid with transform. + math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); + + GridType::Ptr grid = createGrid(/*bg=*/1); + TreeType& tree = grid->tree(); + grid->setTransform(trans); + tree.setValue(Coord(10, 1, 2), 10); + tree.setValue(Coord(0, 0, 0), 5); + + // Add some metadata. + Metadata::clearRegistry(); + StringMetadata::registerType(); + const std::string meta0Val, meta1Val("Hello, world."); + Metadata::Ptr stringMetadata = Metadata::createMetadata(typeNameAsString()); + CPPUNIT_ASSERT(stringMetadata); + if (stringMetadata) { + grid->insertMeta("meta0", *stringMetadata); + grid->metaValue("meta0") = meta0Val; + grid->insertMeta("meta1", *stringMetadata); + grid->metaValue("meta1") = meta1Val; + } + + // Create the grid descriptor out of this grid. + GridDescriptor gd(Name("temperature"), grid->type()); + + // Write out the grid. + file.writeGrid(gd, grid, ostr, /*seekable=*/true); + + CPPUNIT_ASSERT(gd.getGridPos() != 0); + CPPUNIT_ASSERT(gd.getBlockPos() != 0); + CPPUNIT_ASSERT(gd.getEndPos() != 0); + + // Read in the grid descriptor. + GridDescriptor gd2; + std::istringstream istr(ostr.str(), std::ios_base::binary); + + // Since the input is only a fragment of a VDB file (in particular, + // it doesn't have a header), set the file format version number explicitly. + io::setCurrentVersion(istr); + + GridBase::Ptr gd2_grid; + CPPUNIT_ASSERT_THROW(gd2.read(istr), openvdb::LookupError); + + // Register the grid and the transform and the blocks. + GridBase::clearRegistry(); + GridType::registerGrid(); + + // Register transform maps + math::MapRegistry::clear(); + math::AffineMap::registerMap(); + math::ScaleMap::registerMap(); + math::UniformScaleMap::registerMap(); + math::TranslationMap::registerMap(); + math::ScaleTranslateMap::registerMap(); + math::UniformScaleTranslateMap::registerMap(); + math::NonlinearFrustumMap::registerMap(); + + istr.seekg(0, std::ios_base::beg); + CPPUNIT_ASSERT_NO_THROW(gd2_grid = gd2.read(istr)); + + CPPUNIT_ASSERT_EQUAL(gd.gridName(), gd2.gridName()); + CPPUNIT_ASSERT_EQUAL(GridType::gridType(), gd2_grid->type()); + CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), gd2.getGridPos()); + CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), gd2.getBlockPos()); + CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), gd2.getEndPos()); + + // Position the stream to beginning of the grid storage and read the grid. + gd2.seekToGrid(istr); + Archive::readGridCompression(istr); + gd2_grid->readMeta(istr); + gd2_grid->readTransform(istr); + gd2_grid->readTopology(istr); + + // Ensure that we have the same metadata. + CPPUNIT_ASSERT_EQUAL(grid->metaCount(), gd2_grid->metaCount()); + CPPUNIT_ASSERT((*gd2_grid)["meta0"]); + CPPUNIT_ASSERT((*gd2_grid)["meta1"]); + CPPUNIT_ASSERT_EQUAL(meta0Val, gd2_grid->metaValue("meta0")); + CPPUNIT_ASSERT_EQUAL(meta1Val, gd2_grid->metaValue("meta1")); + + // Ensure that we have the same topology and transform. + CPPUNIT_ASSERT_EQUAL( + grid->baseTree().leafCount(), gd2_grid->baseTree().leafCount()); + CPPUNIT_ASSERT_EQUAL( + grid->baseTree().nonLeafCount(), gd2_grid->baseTree().nonLeafCount()); + CPPUNIT_ASSERT_EQUAL( + grid->baseTree().treeDepth(), gd2_grid->baseTree().treeDepth()); + + //CPPUNIT_ASSERT_EQUAL(0.1, gd2_grid->getTransform()->getVoxelSizeX()); + //CPPUNIT_ASSERT_EQUAL(0.1, gd2_grid->getTransform()->getVoxelSizeY()); + //CPPUNIT_ASSERT_EQUAL(0.1, gd2_grid->getTransform()->getVoxelSizeZ()); + + // Read in the data blocks. + gd2.seekToBlocks(istr); + gd2_grid->readBuffers(istr); + TreeType::Ptr tree2 = boost::dynamic_pointer_cast(gd2_grid->baseTreePtr()); + CPPUNIT_ASSERT(tree2.get() != NULL); + CPPUNIT_ASSERT_EQUAL(10, tree2->getValue(Coord(10, 1, 2))); + CPPUNIT_ASSERT_EQUAL(5, tree2->getValue(Coord(0, 0, 0))); + + CPPUNIT_ASSERT_EQUAL(1, tree2->getValue(Coord(1000, 1000, 16000))); + // Clear registries. + GridBase::clearRegistry(); + Metadata::clearRegistry(); + math::MapRegistry::clear(); + + remove("something.vdb2"); +} + + +void +TestFile::testWriteMultipleGrids() +{ + using namespace openvdb; + using namespace openvdb::io; + + typedef Int32Tree TreeType; + typedef Grid GridType; + + File file("something.vdb2"); + + std::ostringstream ostr(std::ios_base::binary); + + // Create a grid with transform. + GridType::Ptr grid = createGrid(/*bg=*/1); + TreeType& tree = grid->tree(); + tree.setValue(Coord(10, 1, 2), 10); + tree.setValue(Coord(0, 0, 0), 5); + math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); + grid->setTransform(trans); + + GridType::Ptr grid2 = createGrid(/*bg=*/2); + TreeType& tree2 = grid2->tree(); + tree2.setValue(Coord(0, 0, 0), 10); + tree2.setValue(Coord(1000, 1000, 1000), 50); + math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.2); + grid2->setTransform(trans2); + + // Create the grid descriptor out of this grid. + GridDescriptor gd(Name("temperature"), grid->type()); + GridDescriptor gd2(Name("density"), grid2->type()); + + // Write out the grids. + file.writeGrid(gd, grid, ostr, /*seekable=*/true); + file.writeGrid(gd2, grid2, ostr, /*seekable=*/true); + + CPPUNIT_ASSERT(gd.getGridPos() != 0); + CPPUNIT_ASSERT(gd.getBlockPos() != 0); + CPPUNIT_ASSERT(gd.getEndPos() != 0); + + CPPUNIT_ASSERT(gd2.getGridPos() != 0); + CPPUNIT_ASSERT(gd2.getBlockPos() != 0); + CPPUNIT_ASSERT(gd2.getEndPos() != 0); + + // register the grid + GridBase::clearRegistry(); + GridType::registerGrid(); + + // register maps + math::MapRegistry::clear(); + math::AffineMap::registerMap(); + math::ScaleMap::registerMap(); + math::UniformScaleMap::registerMap(); + math::TranslationMap::registerMap(); + math::ScaleTranslateMap::registerMap(); + math::UniformScaleTranslateMap::registerMap(); + math::NonlinearFrustumMap::registerMap(); + + // Read in the first grid descriptor. + GridDescriptor gd_in; + std::istringstream istr(ostr.str(), std::ios_base::binary); + io::setCurrentVersion(istr); + + GridBase::Ptr gd_in_grid; + CPPUNIT_ASSERT_NO_THROW(gd_in_grid = gd_in.read(istr)); + + // Ensure read in the right values. + CPPUNIT_ASSERT_EQUAL(gd.gridName(), gd_in.gridName()); + CPPUNIT_ASSERT_EQUAL(GridType::gridType(), gd_in_grid->type()); + CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), gd_in.getGridPos()); + CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), gd_in.getBlockPos()); + CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), gd_in.getEndPos()); + + // Position the stream to beginning of the grid storage and read the grid. + gd_in.seekToGrid(istr); + Archive::readGridCompression(istr); + gd_in_grid->readMeta(istr); + gd_in_grid->readTransform(istr); + gd_in_grid->readTopology(istr); + + // Ensure that we have the same topology and transform. + CPPUNIT_ASSERT_EQUAL( + grid->baseTree().leafCount(), gd_in_grid->baseTree().leafCount()); + CPPUNIT_ASSERT_EQUAL( + grid->baseTree().nonLeafCount(), gd_in_grid->baseTree().nonLeafCount()); + CPPUNIT_ASSERT_EQUAL( + grid->baseTree().treeDepth(), gd_in_grid->baseTree().treeDepth()); + + // CPPUNIT_ASSERT_EQUAL(0.1, gd_in_grid->getTransform()->getVoxelSizeX()); + // CPPUNIT_ASSERT_EQUAL(0.1, gd_in_grid->getTransform()->getVoxelSizeY()); + // CPPUNIT_ASSERT_EQUAL(0.1, gd_in_grid->getTransform()->getVoxelSizeZ()); + + // Read in the data blocks. + gd_in.seekToBlocks(istr); + gd_in_grid->readBuffers(istr); + TreeType::Ptr grid_in = boost::dynamic_pointer_cast(gd_in_grid->baseTreePtr()); + CPPUNIT_ASSERT(grid_in.get() != NULL); + CPPUNIT_ASSERT_EQUAL(10, grid_in->getValue(Coord(10, 1, 2))); + CPPUNIT_ASSERT_EQUAL(5, grid_in->getValue(Coord(0, 0, 0))); + CPPUNIT_ASSERT_EQUAL(1, grid_in->getValue(Coord(1000, 1000, 16000))); + + ///////////////////////////////////////////////////////////////// + // Now read in the second grid descriptor. Make use of hte end offset. + /////////////////////////////////////////////////////////////// + + gd_in.seekToEnd(istr); + + GridDescriptor gd2_in; + GridBase::Ptr gd2_in_grid; + CPPUNIT_ASSERT_NO_THROW(gd2_in_grid = gd2_in.read(istr)); + + // Ensure that we read in the right values. + CPPUNIT_ASSERT_EQUAL(gd2.gridName(), gd2_in.gridName()); + CPPUNIT_ASSERT_EQUAL(TreeType::treeType(), gd2_in_grid->type()); + CPPUNIT_ASSERT_EQUAL(gd2.getGridPos(), gd2_in.getGridPos()); + CPPUNIT_ASSERT_EQUAL(gd2.getBlockPos(), gd2_in.getBlockPos()); + CPPUNIT_ASSERT_EQUAL(gd2.getEndPos(), gd2_in.getEndPos()); + + // Position the stream to beginning of the grid storage and read the grid. + gd2_in.seekToGrid(istr); + Archive::readGridCompression(istr); + gd2_in_grid->readMeta(istr); + gd2_in_grid->readTransform(istr); + gd2_in_grid->readTopology(istr); + + // Ensure that we have the same topology and transform. + CPPUNIT_ASSERT_EQUAL( + grid2->baseTree().leafCount(), gd2_in_grid->baseTree().leafCount()); + CPPUNIT_ASSERT_EQUAL( + grid2->baseTree().nonLeafCount(), gd2_in_grid->baseTree().nonLeafCount()); + CPPUNIT_ASSERT_EQUAL( + grid2->baseTree().treeDepth(), gd2_in_grid->baseTree().treeDepth()); + // CPPUNIT_ASSERT_EQUAL(0.2, gd2_in_grid->getTransform()->getVoxelSizeX()); + // CPPUNIT_ASSERT_EQUAL(0.2, gd2_in_grid->getTransform()->getVoxelSizeY()); + // CPPUNIT_ASSERT_EQUAL(0.2, gd2_in_grid->getTransform()->getVoxelSizeZ()); + + // Read in the data blocks. + gd2_in.seekToBlocks(istr); + gd2_in_grid->readBuffers(istr); + TreeType::Ptr grid2_in = + boost::dynamic_pointer_cast(gd2_in_grid->baseTreePtr()); + CPPUNIT_ASSERT(grid2_in.get() != NULL); + CPPUNIT_ASSERT_EQUAL(50, grid2_in->getValue(Coord(1000, 1000, 1000))); + CPPUNIT_ASSERT_EQUAL(10, grid2_in->getValue(Coord(0, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2, grid2_in->getValue(Coord(100000, 100000, 16000))); + + // Clear registries. + GridBase::clearRegistry(); + + math::MapRegistry::clear(); + remove("something.vdb2"); +} + + +void +TestFile::testWriteFloatAsHalf() +{ + using namespace openvdb; + using namespace openvdb::io; + + typedef Vec3STree TreeType; + typedef Grid GridType; + + // Register all grid types. + initialize(); + // Ensure that the registry is cleared on exit. + struct Local { static void uninitialize(char*) { openvdb::uninitialize(); } }; + boost::shared_ptr onExit((char*)(0), Local::uninitialize); + + // Create two test grids. + GridType::Ptr grid1 = createGrid(/*bg=*/Vec3s(1, 1, 1)); + TreeType& tree1 = grid1->tree(); + CPPUNIT_ASSERT(grid1.get() != NULL); + grid1->setTransform(math::Transform::createLinearTransform(0.1)); + grid1->setName("grid1"); + + GridType::Ptr grid2 = createGrid(/*bg=*/Vec3s(2, 2, 2)); + CPPUNIT_ASSERT(grid2.get() != NULL); + TreeType& tree2 = grid2->tree(); + grid2->setTransform(math::Transform::createLinearTransform(0.2)); + // Flag this grid for 16-bit float output. + grid2->setSaveFloatAsHalf(true); + grid2->setName("grid2"); + + for (int x = 0; x < 40; ++x) { + for (int y = 0; y < 40; ++y) { + for (int z = 0; z < 40; ++z) { + tree1.setValue(Coord(x, y, z), Vec3s(x, y, z)); + tree2.setValue(Coord(x, y, z), Vec3s(x, y, z)); + } + } + } + + GridPtrVec grids; + grids.push_back(grid1); + grids.push_back(grid2); + + const char* filename = "something.vdb2"; + { + // Write both grids to a file. + File vdbFile(filename); + vdbFile.write(grids); + } + { + // Verify that both grids can be read back successfully from the file. + File vdbFile(filename); + vdbFile.open(); + GridBase::Ptr + bgrid1 = vdbFile.readGrid("grid1"), + bgrid2 = vdbFile.readGrid("grid2"); + vdbFile.close(); + + CPPUNIT_ASSERT(bgrid1.get() != NULL); + CPPUNIT_ASSERT(bgrid1->isType()); + CPPUNIT_ASSERT(bgrid2.get() != NULL); + CPPUNIT_ASSERT(bgrid2->isType()); + + const TreeType& btree1 = boost::static_pointer_cast(bgrid1)->tree(); + CPPUNIT_ASSERT_EQUAL(Vec3s(10, 10, 10), btree1.getValue(Coord(10, 10, 10))); + const TreeType& btree2 = boost::static_pointer_cast(bgrid2)->tree(); + CPPUNIT_ASSERT_EQUAL(Vec3s(10, 10, 10), btree2.getValue(Coord(10, 10, 10))); + } +} + + +void +TestFile::testWriteInstancedGrids() +{ + using namespace openvdb; + + // Register data types. + openvdb::initialize(); + + // Create grids. + Int32Tree::Ptr tree1(new Int32Tree(1)); + FloatTree::Ptr tree2(new FloatTree(2.0)); + GridBase::Ptr + grid1 = createGrid(tree1), + grid2 = createGrid(tree1), // instance of grid1 + grid3 = createGrid(tree2), + grid4 = createGrid(tree2); // instance of grid3 + grid1->setName("density"); + grid2->setName("density_copy"); + // Leave grid3 and grid4 unnamed. + + // Create transforms. + math::Transform::Ptr trans1 = math::Transform::createLinearTransform(0.1); + math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); + grid1->setTransform(trans1); + grid2->setTransform(trans2); + grid3->setTransform(trans2); + grid4->setTransform(trans1); + + // Set some values. + tree1->setValue(Coord(0, 0, 0), 5); + tree1->setValue(Coord(100, 0, 0), 6); + tree2->setValue(Coord(0, 0, 0), 10); + tree2->setValue(Coord(0, 100, 0), 11); + + MetaMap::Ptr meta(new MetaMap); + meta->insertMeta("author", StringMetadata("Einstein")); + meta->insertMeta("year", Int32Metadata(2009)); + + GridPtrVecPtr grids(new GridPtrVec); + grids->push_back(grid1); + grids->push_back(grid2); + grids->push_back(grid3); + grids->push_back(grid4); + + // Write the grids to a file and then close the file. + const char* filename = "something.vdb2"; + boost::shared_ptr scopedFile(filename, ::remove); + { + io::File vdbFile(filename); + vdbFile.write(*grids, *meta); + } + meta.reset(); + + // Read the grids back in. + io::File file(filename); + file.open(); + grids = file.getGrids(); + meta = file.getMetadata(); + + // Verify the metadata. + CPPUNIT_ASSERT(meta.get() != NULL); + CPPUNIT_ASSERT_EQUAL(2, int(meta->metaCount())); + CPPUNIT_ASSERT_EQUAL(std::string("Einstein"), meta->metaValue("author")); + CPPUNIT_ASSERT_EQUAL(2009, meta->metaValue("year")); + + // Verify the grids. + CPPUNIT_ASSERT(grids.get() != NULL); + CPPUNIT_ASSERT_EQUAL(4, int(grids->size())); + + GridBase::Ptr grid = findGridByName(*grids, "density"); + CPPUNIT_ASSERT(grid.get() != NULL); + Int32Tree::Ptr density = gridPtrCast(grid)->treePtr(); + CPPUNIT_ASSERT(density.get() != NULL); + + grid.reset(); + grid = findGridByName(*grids, "density_copy"); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); + // Verify that "density_copy" is an instance of (i.e., shares a tree with) "density". + CPPUNIT_ASSERT_EQUAL(density, gridPtrCast(grid)->treePtr()); + + grid.reset(); + grid = findGridByName(*grids, ""); + CPPUNIT_ASSERT(grid.get() != NULL); + FloatTree::Ptr temperature = gridPtrCast(grid)->treePtr(); + CPPUNIT_ASSERT(temperature.get() != NULL); + + grid.reset(); + for (GridPtrVec::reverse_iterator it = grids->rbegin(); !grid && it != grids->rend(); ++it) { + // Search for the second unnamed grid starting from the end of the list. + if ((*it)->getName() == "") grid = *it; + } + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); + // Verify that the second unnamed grid is an instance of the first. + CPPUNIT_ASSERT_EQUAL(temperature, gridPtrCast(grid)->treePtr()); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(5, density->getValue(Coord(0, 0, 0)), /*tolerance=*/0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(6, density->getValue(Coord(100, 0, 0)), /*tolerance=*/0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(10, temperature->getValue(Coord(0, 0, 0)), /*tolerance=*/0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(11, temperature->getValue(Coord(0, 100, 0)), /*tolerance=*/0); + + // Reread with instancing disabled. + file.close(); + file.setInstancingEnabled(false); + file.open(); + grids = file.getGrids(); + CPPUNIT_ASSERT_EQUAL(4, int(grids->size())); + + grid = findGridByName(*grids, "density"); + CPPUNIT_ASSERT(grid.get() != NULL); + density = gridPtrCast(grid)->treePtr(); + CPPUNIT_ASSERT(density.get() != NULL); + grid = findGridByName(*grids, "density_copy"); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); + // Verify that "density_copy" is *not* an instance of "density". + CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr() != density); + + // Verify that the two unnamed grids are not instances of each other. + grid = findGridByName(*grids, ""); + CPPUNIT_ASSERT(grid.get() != NULL); + temperature = gridPtrCast(grid)->treePtr(); + CPPUNIT_ASSERT(temperature.get() != NULL); + grid.reset(); + for (GridPtrVec::reverse_iterator it = grids->rbegin(); !grid && it != grids->rend(); ++it) { + // Search for the second unnamed grid starting from the end of the list. + if ((*it)->getName() == "") grid = *it; + } + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); + CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr() != temperature); + + // Rewrite with instancing disabled, then reread with instancing enabled. + file.close(); + { + io::File vdbFile(filename); + vdbFile.setInstancingEnabled(false); + vdbFile.write(*grids, *meta); + } + file.setInstancingEnabled(true); + file.open(); + grids = file.getGrids(); + CPPUNIT_ASSERT_EQUAL(4, int(grids->size())); + + // Verify that "density_copy" is not an instance of "density". + grid = findGridByName(*grids, "density"); + CPPUNIT_ASSERT(grid.get() != NULL); + density = gridPtrCast(grid)->treePtr(); + CPPUNIT_ASSERT(density.get() != NULL); + grid = findGridByName(*grids, "density_copy"); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); + CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr() != density); + + // Verify that the two unnamed grids are not instances of each other. + grid = findGridByName(*grids, ""); + CPPUNIT_ASSERT(grid.get() != NULL); + temperature = gridPtrCast(grid)->treePtr(); + CPPUNIT_ASSERT(temperature.get() != NULL); + grid.reset(); + for (GridPtrVec::reverse_iterator it = grids->rbegin(); !grid && it != grids->rend(); ++it) { + // Search for the second unnamed grid starting from the end of the list. + if ((*it)->getName() == "") grid = *it; + } + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); + CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr() != temperature); +} + + +void +TestFile::testReadGridDescriptors() +{ + using namespace openvdb; + using namespace openvdb::io; + + typedef Int32Grid GridType; + typedef GridType::TreeType TreeType; + + File file("something.vdb2"); + + std::ostringstream ostr(std::ios_base::binary); + + // Create a grid with transform. + GridType::Ptr grid = createGrid(1); + TreeType& tree = grid->tree(); + tree.setValue(Coord(10, 1, 2), 10); + tree.setValue(Coord(0, 0, 0), 5); + math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); + grid->setTransform(trans); + + // Create another grid with transform. + GridType::Ptr grid2 = createGrid(2); + TreeType& tree2 = grid2->tree(); + tree2.setValue(Coord(0, 0, 0), 10); + tree2.setValue(Coord(1000, 1000, 1000), 50); + math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.2); + grid2->setTransform(trans2); + + // Create the grid descriptor out of this grid. + GridDescriptor gd(Name("temperature"), grid->type()); + GridDescriptor gd2(Name("density"), grid2->type()); + + // Write out the number of grids. + int32_t gridCount = 2; + ostr.write((char*)&gridCount, sizeof(int32_t)); + // Write out the grids. + file.writeGrid(gd, grid, ostr, /*seekable=*/true); + file.writeGrid(gd2, grid2, ostr, /*seekable=*/true); + + // Register the grid and the transform and the blocks. + GridBase::clearRegistry(); + GridType::registerGrid(); + // register maps + math::MapRegistry::clear(); + math::AffineMap::registerMap(); + math::ScaleMap::registerMap(); + math::UniformScaleMap::registerMap(); + math::TranslationMap::registerMap(); + math::ScaleTranslateMap::registerMap(); + math::UniformScaleTranslateMap::registerMap(); + math::NonlinearFrustumMap::registerMap(); + + // Read in the grid descriptors. + File file2("something.vdb2"); + std::istringstream istr(ostr.str(), std::ios_base::binary); + io::setCurrentVersion(istr); + file2.readGridDescriptors(istr); + + // Compare with the initial grid descriptors. + File::NameMapCIter it = file2.findDescriptor("temperature"); + CPPUNIT_ASSERT(it != file2.mGridDescriptors.end()); + GridDescriptor file2gd = it->second; + CPPUNIT_ASSERT_EQUAL(gd.gridName(), file2gd.gridName()); + CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), file2gd.getGridPos()); + CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), file2gd.getBlockPos()); + CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), file2gd.getEndPos()); + + it = file2.findDescriptor("density"); + CPPUNIT_ASSERT(it != file2.mGridDescriptors.end()); + file2gd = it->second; + CPPUNIT_ASSERT_EQUAL(gd2.gridName(), file2gd.gridName()); + CPPUNIT_ASSERT_EQUAL(gd2.getGridPos(), file2gd.getGridPos()); + CPPUNIT_ASSERT_EQUAL(gd2.getBlockPos(), file2gd.getBlockPos()); + CPPUNIT_ASSERT_EQUAL(gd2.getEndPos(), file2gd.getEndPos()); + + // Clear registries. + GridBase::clearRegistry(); + math::MapRegistry::clear(); + + remove("something.vdb2"); +} + + +void +TestFile::testGridNaming() +{ + using namespace openvdb; + using namespace openvdb::io; + + typedef Int32Tree TreeType; + typedef Grid GridType; + + // Register data types. + openvdb::initialize(); + + // Create several grids that share a single tree. + TreeType::Ptr tree(new TreeType(1)); + tree->setValue(Coord(10, 1, 2), 10); + tree->setValue(Coord(0, 0, 0), 5); + GridBase::Ptr + grid1 = openvdb::createGrid(tree), + grid2 = openvdb::createGrid(tree), + grid3 = openvdb::createGrid(tree); + + std::vector gridVec; + gridVec.push_back(grid1); + gridVec.push_back(grid2); + gridVec.push_back(grid3); + + // Give all grids the same name, but also some metadata to distinguish them. + for (int n = 0; n <= 2; ++n) { + gridVec[n]->setName("grid"); + gridVec[n]->insertMeta("index", Int32Metadata(n)); + } + + const char* filename = "/tmp/testGridNaming.vdb2"; + boost::shared_ptr scopedFile(filename, ::remove); + + // Test first with grid instancing disabled, then with instancing enabled. + for (int instancing = 0; instancing <= 1; ++instancing) { + { + // Write the grids out to a file. + File file(filename); + file.setInstancingEnabled(instancing); + file.write(gridVec); + } + + // Open the file for reading. + File file(filename); + file.setInstancingEnabled(instancing); + file.open(); + + int n = 0; + for (File::NameIterator i = file.beginName(), e = file.endName(); i != e; ++i, ++n) { + CPPUNIT_ASSERT(file.hasGrid(i.gridName())); + } + // Verify that the file contains three grids. + CPPUNIT_ASSERT_EQUAL(3, n); + + // Read each grid. + for (n = -1; n <= 2; ++n) { + openvdb::Name name("grid"); + + // On the first iteration, read the grid named "grid", then read "grid[0]" + // (which is synonymous with "grid"), then "grid[1]", then "grid[2]". + if (n >= 0) { + name = GridDescriptor::nameAsString(GridDescriptor::addSuffix(name, n)); + } + + CPPUNIT_ASSERT(file.hasGrid(name)); + + // Partially read the current grid. + GridBase::ConstPtr grid = file.readGridPartial(name); + CPPUNIT_ASSERT(grid.get() != NULL); + + // Verify that the grid is named "grid". + CPPUNIT_ASSERT_EQUAL(openvdb::Name("grid"), grid->getName()); + + CPPUNIT_ASSERT_EQUAL((n < 0 ? 0 : n), grid->metaValue("index")); + + // Fully read the current grid. + grid = file.readGrid(name); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT_EQUAL(openvdb::Name("grid"), grid->getName()); + CPPUNIT_ASSERT_EQUAL((n < 0 ? 0 : n), grid->metaValue("index")); + } + + // Read all three grids at once. + GridPtrVecPtr allGrids = file.getGrids(); + CPPUNIT_ASSERT(allGrids.get() != NULL); + CPPUNIT_ASSERT_EQUAL(3, int(allGrids->size())); + + GridBase::ConstPtr firstGrid; + std::vector indices; + for (GridPtrVecCIter i = allGrids->begin(), e = allGrids->end(); i != e; ++i) { + GridBase::ConstPtr grid = *i; + CPPUNIT_ASSERT(grid.get() != NULL); + + indices.push_back(grid->metaValue("index")); + + // If instancing is enabled, verify that all grids share the same tree. + if (instancing) { + if (!firstGrid) firstGrid = grid; + CPPUNIT_ASSERT_EQUAL(firstGrid->baseTreePtr(), grid->baseTreePtr()); + } + } + // Verify that three distinct grids were read, + // by examining their "index" metadata. + CPPUNIT_ASSERT_EQUAL(3, int(indices.size())); + std::sort(indices.begin(), indices.end()); + CPPUNIT_ASSERT_EQUAL(0, indices[0]); + CPPUNIT_ASSERT_EQUAL(1, indices[1]); + CPPUNIT_ASSERT_EQUAL(2, indices[2]); + } + + { + // Try writing and then reading a grid with a weird name + // that might conflict with the grid name indexing scheme. + const openvdb::Name weirdName("grid[4]"); + gridVec[0]->setName(weirdName); + { + File file(filename); + file.write(gridVec); + } + File file(filename); + file.open(); + + // Verify that the grid can be read and that its index is 0. + GridBase::ConstPtr grid = file.readGrid(weirdName); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT_EQUAL(weirdName, grid->getName()); + CPPUNIT_ASSERT_EQUAL(0, grid->metaValue("index")); + + // Verify that the other grids can still be read successfully. + grid = file.readGrid("grid[0]"); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT_EQUAL(openvdb::Name("grid"), grid->getName()); + // Because there are now only two grids named "grid", the one with + // index 1 is now "grid[0]". + CPPUNIT_ASSERT_EQUAL(1, grid->metaValue("index")); + + grid = file.readGrid("grid[1]"); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT_EQUAL(openvdb::Name("grid"), grid->getName()); + // Because there are now only two grids named "grid", the one with + // index 2 is now "grid[1]". + CPPUNIT_ASSERT_EQUAL(2, grid->metaValue("index")); + + // Verify that there is no longer a third grid named "grid". + CPPUNIT_ASSERT_THROW(file.readGrid("grid[2]"), openvdb::KeyError); + } +} + + +void +TestFile::testEmptyFile() +{ + using namespace openvdb; + using namespace openvdb::io; + + const char* filename = "/tmp/testEmptyFile.vdb2"; + boost::shared_ptr scopedFile(filename, ::remove); + + { + File file(filename); + file.write(GridPtrVec(), MetaMap()); + } + File file(filename); + file.open(); + + GridPtrVecPtr grids = file.getGrids(); + MetaMap::Ptr meta = file.getMetadata(); + + CPPUNIT_ASSERT(grids.get() != NULL); + CPPUNIT_ASSERT(grids->empty()); + + CPPUNIT_ASSERT(meta.get() != NULL); + CPPUNIT_ASSERT(meta->empty()); +} + + +void +TestFile::testEmptyGridIO() +{ + using namespace openvdb; + using namespace openvdb::io; + + typedef Int32Grid GridType; + typedef GridType::TreeType TreeType; + + const char* filename = "/tmp/something.vdb2"; + boost::shared_ptr scopedFile(filename, ::remove); + + File file(filename); + + std::ostringstream ostr(std::ios_base::binary); + + // Create a grid with transform. + GridType::Ptr grid = createGrid(/*bg=*/1); + math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); + grid->setTransform(trans); + + // Create another grid with transform. + math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.2); + GridType::Ptr grid2 = createGrid(/*bg=*/2); + grid2->setTransform(trans2); + + // Create the grid descriptor out of this grid. + GridDescriptor gd(Name("temperature"), grid->type()); + GridDescriptor gd2(Name("density"), grid2->type()); + + // Write out the number of grids. + int32_t gridCount = 2; + ostr.write((char*)&gridCount, sizeof(int32_t)); + // Write out the grids. + file.writeGrid(gd, grid, ostr, /*seekable=*/true); + file.writeGrid(gd2, grid2, ostr, /*seekable=*/true); + + // Ensure that the block offset and the end offsets are equivalent. + CPPUNIT_ASSERT_EQUAL(0, int(grid->baseTree().leafCount())); + CPPUNIT_ASSERT_EQUAL(0, int(grid2->baseTree().leafCount())); + CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), gd.getBlockPos()); + CPPUNIT_ASSERT_EQUAL(gd2.getEndPos(), gd2.getBlockPos()); + + // Register the grid and the transform and the blocks. + GridBase::clearRegistry(); + GridType::registerGrid(); + // register maps + math::MapRegistry::clear(); + math::AffineMap::registerMap(); + math::ScaleMap::registerMap(); + math::UniformScaleMap::registerMap(); + math::TranslationMap::registerMap(); + math::ScaleTranslateMap::registerMap(); + math::UniformScaleTranslateMap::registerMap(); + math::NonlinearFrustumMap::registerMap(); + + // Read in the grid descriptors. + File file2(filename); + std::istringstream istr(ostr.str(), std::ios_base::binary); + io::setCurrentVersion(istr); + file2.readGridDescriptors(istr); + + // Compare with the initial grid descriptors. + File::NameMapCIter it = file2.findDescriptor("temperature"); + CPPUNIT_ASSERT(it != file2.mGridDescriptors.end()); + GridDescriptor file2gd = it->second; + file2gd.seekToGrid(istr); + GridBase::Ptr gd_grid = GridBase::createGrid(file2gd.gridType()); + Archive::readGridCompression(istr); + gd_grid->readMeta(istr); + gd_grid->readTransform(istr); + gd_grid->readTopology(istr); + CPPUNIT_ASSERT_EQUAL(gd.gridName(), file2gd.gridName()); + CPPUNIT_ASSERT(gd_grid.get() != NULL); + CPPUNIT_ASSERT_EQUAL(0, int(gd_grid->baseTree().leafCount())); + //CPPUNIT_ASSERT_EQUAL(8, int(gd_grid->baseTree().nonLeafCount())); + CPPUNIT_ASSERT_EQUAL(4, int(gd_grid->baseTree().treeDepth())); + CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), file2gd.getGridPos()); + CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), file2gd.getBlockPos()); + CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), file2gd.getEndPos()); + + it = file2.findDescriptor("density"); + CPPUNIT_ASSERT(it != file2.mGridDescriptors.end()); + file2gd = it->second; + file2gd.seekToGrid(istr); + gd_grid = GridBase::createGrid(file2gd.gridType()); + Archive::readGridCompression(istr); + gd_grid->readMeta(istr); + gd_grid->readTransform(istr); + gd_grid->readTopology(istr); + CPPUNIT_ASSERT_EQUAL(gd2.gridName(), file2gd.gridName()); + CPPUNIT_ASSERT(gd_grid.get() != NULL); + CPPUNIT_ASSERT_EQUAL(0, int(gd_grid->baseTree().leafCount())); + //CPPUNIT_ASSERT_EQUAL(8, int(gd_grid->nonLeafCount())); + CPPUNIT_ASSERT_EQUAL(4, int(gd_grid->baseTree().treeDepth())); + CPPUNIT_ASSERT_EQUAL(gd2.getGridPos(), file2gd.getGridPos()); + CPPUNIT_ASSERT_EQUAL(gd2.getBlockPos(), file2gd.getBlockPos()); + CPPUNIT_ASSERT_EQUAL(gd2.getEndPos(), file2gd.getEndPos()); + + // Clear registries. + GridBase::clearRegistry(); + math::MapRegistry::clear(); +} + + +void +TestFile::testOpen() +{ + using namespace openvdb; + + typedef openvdb::FloatGrid FloatGrid; + typedef openvdb::Int32Grid IntGrid; + typedef FloatGrid::TreeType FloatTree; + typedef Int32Grid::TreeType IntTree; + + // Create a VDB to write. + + // Create grids + IntGrid::Ptr grid = createGrid(/*bg=*/1); + IntTree& tree = grid->tree(); + grid->setName("density"); + + FloatGrid::Ptr grid2 = createGrid(/*bg=*/2.0); + FloatTree& tree2 = grid2->tree(); + grid2->setName("temperature"); + + // Create transforms + math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); + math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); + grid->setTransform(trans); + grid2->setTransform(trans2); + + // Set some values + tree.setValue(Coord(0, 0, 0), 5); + tree.setValue(Coord(100, 0, 0), 6); + tree2.setValue(Coord(0, 0, 0), 10); + tree2.setValue(Coord(0, 100, 0), 11); + + MetaMap meta; + meta.insertMeta("author", StringMetadata("Einstein")); + meta.insertMeta("year", Int32Metadata(2009)); + + GridPtrVec grids; + grids.push_back(grid); + grids.push_back(grid2); + + CPPUNIT_ASSERT(findGridByName(grids, "density") == grid); + CPPUNIT_ASSERT(findGridByName(grids, "temperature") == grid2); + CPPUNIT_ASSERT(meta.metaValue("author") == "Einstein"); + CPPUNIT_ASSERT_EQUAL(2009, meta.metaValue("year")); + + // Register grid and transform. + GridBase::clearRegistry(); + IntGrid::registerGrid(); + FloatGrid::registerGrid(); + Metadata::clearRegistry(); + StringMetadata::registerType(); + Int32Metadata::registerType(); + // register maps + math::MapRegistry::clear(); + math::AffineMap::registerMap(); + math::ScaleMap::registerMap(); + math::UniformScaleMap::registerMap(); + math::TranslationMap::registerMap(); + math::ScaleTranslateMap::registerMap(); + math::UniformScaleTranslateMap::registerMap(); + math::NonlinearFrustumMap::registerMap(); + + // Write the vdb out to a file. + io::File vdbfile("something.vdb2"); + vdbfile.write(grids, meta); + + // Now we can read in the file. + CPPUNIT_ASSERT(!vdbfile.open());//opening the same file + //Can't open same file multiple times without cloasing + CPPUNIT_ASSERT_THROW(vdbfile.open(), openvdb::IoError); + vdbfile.close(); + CPPUNIT_ASSERT(!vdbfile.open());//opening the same file + + CPPUNIT_ASSERT(vdbfile.isOpen()); + CPPUNIT_ASSERT_EQUAL(OPENVDB_FILE_VERSION, vdbfile.fileVersion()); + CPPUNIT_ASSERT_EQUAL(OPENVDB_FILE_VERSION, io::getFormatVersion(vdbfile.mInStream)); + CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MAJOR_VERSION, vdbfile.libraryVersion().first); + CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MINOR_VERSION, vdbfile.libraryVersion().second); + CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MAJOR_VERSION, + io::getLibraryVersion(vdbfile.mInStream).first); + CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MINOR_VERSION, + io::getLibraryVersion(vdbfile.mInStream).second); + + // Ensure that we read in the vdb metadata. + CPPUNIT_ASSERT(vdbfile.getMetadata()); + CPPUNIT_ASSERT(vdbfile.getMetadata()->metaValue("author") == "Einstein"); + CPPUNIT_ASSERT_EQUAL(2009, vdbfile.getMetadata()->metaValue("year")); + + // Ensure we got the grid descriptors. + CPPUNIT_ASSERT_EQUAL(1, int(vdbfile.mGridDescriptors.count("density"))); + CPPUNIT_ASSERT_EQUAL(1, int(vdbfile.mGridDescriptors.count("temperature"))); + + io::File::NameMapCIter it = vdbfile.findDescriptor("density"); + CPPUNIT_ASSERT(it != vdbfile.mGridDescriptors.end()); + io::GridDescriptor gd = it->second; + CPPUNIT_ASSERT_EQUAL(IntTree::treeType(), gd.gridType()); + + it = vdbfile.findDescriptor("temperature"); + CPPUNIT_ASSERT(it != vdbfile.mGridDescriptors.end()); + gd = it->second; + CPPUNIT_ASSERT_EQUAL(FloatTree::treeType(), gd.gridType()); + + // Ensure we throw an error if there is no file. + io::File vdbfile2("somethingelses.vdb2"); + CPPUNIT_ASSERT_THROW(vdbfile2.open(), openvdb::IoError); + CPPUNIT_ASSERT(vdbfile2.mInStream.is_open() == false); + + // Clear registries. + GridBase::clearRegistry(); + Metadata::clearRegistry(); + math::MapRegistry::clear(); + + // Test closing the file. + vdbfile.close(); + CPPUNIT_ASSERT(vdbfile.isOpen() == false); + CPPUNIT_ASSERT(vdbfile.mMeta.get() == NULL); + CPPUNIT_ASSERT_EQUAL(0, int(vdbfile.mGridDescriptors.size())); + CPPUNIT_ASSERT(vdbfile.mInStream.is_open() == false); + + remove("something.vdb2"); +} + + +void +TestFile::testNonVdbOpen() +{ + std::ofstream file("dummy.vdb2", std::ios_base::binary); + + int64_t something = 1; + file.write((char*)&something, sizeof(int64_t)); + + file.close(); + + openvdb::io::File vdbfile("dummy.vdb2"); + CPPUNIT_ASSERT_THROW(vdbfile.open(), openvdb::IoError); + CPPUNIT_ASSERT(vdbfile.mInStream.is_open() == false); + + remove("dummy.vdb2"); +} + + +void +TestFile::testGetMetadata() +{ + using namespace openvdb; + + GridPtrVec grids; + MetaMap meta; + + meta.insertMeta("author", StringMetadata("Einstein")); + meta.insertMeta("year", Int32Metadata(2009)); + + // Adjust registry before writing. + Metadata::clearRegistry(); + StringMetadata::registerType(); + Int32Metadata::registerType(); + + // Write the vdb out to a file. + io::File vdbfile("something.vdb2"); + vdbfile.write(grids, meta); + + // Check if reading without opening the file + CPPUNIT_ASSERT_THROW(vdbfile.getMetadata(), openvdb::IoError); + + vdbfile.open(); + + MetaMap::Ptr meta2 = vdbfile.getMetadata(); + + CPPUNIT_ASSERT_EQUAL(2, int(meta2->metaCount())); + + CPPUNIT_ASSERT(meta2->metaValue("author") == "Einstein"); + CPPUNIT_ASSERT_EQUAL(2009, meta2->metaValue("year")); + + // Clear registry. + Metadata::clearRegistry(); + + remove("something.vdb2"); +} + + +void +TestFile::testReadAll() +{ + using namespace openvdb; + + typedef openvdb::FloatGrid FloatGrid; + typedef openvdb::Int32Grid IntGrid; + typedef FloatGrid::TreeType FloatTree; + typedef Int32Grid::TreeType IntTree; + + // Create a vdb to write. + + // Create grids + IntGrid::Ptr grid1 = createGrid(/*bg=*/1); + IntTree& tree = grid1->tree(); + grid1->setName("density"); + + FloatGrid::Ptr grid2 = createGrid(/*bg=*/2.0); + FloatTree& tree2 = grid2->tree(); + grid2->setName("temperature"); + + // Create transforms + math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); + math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); + grid1->setTransform(trans); + grid2->setTransform(trans2); + + // Set some values + tree.setValue(Coord(0, 0, 0), 5); + tree.setValue(Coord(100, 0, 0), 6); + tree2.setValue(Coord(0, 0, 0), 10); + tree2.setValue(Coord(0, 100, 0), 11); + + MetaMap meta; + meta.insertMeta("author", StringMetadata("Einstein")); + meta.insertMeta("year", Int32Metadata(2009)); + + GridPtrVec grids; + grids.push_back(grid1); + grids.push_back(grid2); + + // Register grid and transform. + openvdb::initialize(); + + // Write the vdb out to a file. + io::File vdbfile("something.vdb2"); + vdbfile.write(grids, meta); + + io::File vdbfile2("something.vdb2"); + CPPUNIT_ASSERT_THROW(vdbfile2.getGrids(), openvdb::IoError); + + vdbfile2.open(); + CPPUNIT_ASSERT(vdbfile2.isOpen()); + + GridPtrVecPtr grids2 = vdbfile2.getGrids(); + MetaMap::Ptr meta2 = vdbfile2.getMetadata(); + + // Ensure we have the metadata. + CPPUNIT_ASSERT_EQUAL(2, int(meta2->metaCount())); + CPPUNIT_ASSERT(meta2->metaValue("author") == "Einstein"); + CPPUNIT_ASSERT_EQUAL(2009, meta2->metaValue("year")); + + // Ensure we got the grids. + CPPUNIT_ASSERT_EQUAL(2, int(grids2->size())); + + GridBase::Ptr grid; + grid.reset(); + grid = findGridByName(*grids2, "density"); + CPPUNIT_ASSERT(grid.get() != NULL); + IntTree::Ptr density = gridPtrCast(grid)->treePtr(); + CPPUNIT_ASSERT(density.get() != NULL); + + grid.reset(); + grid = findGridByName(*grids2, "temperature"); + CPPUNIT_ASSERT(grid.get() != NULL); + FloatTree::Ptr temperature = gridPtrCast(grid)->treePtr(); + CPPUNIT_ASSERT(temperature.get() != NULL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(5, density->getValue(Coord(0, 0, 0)), /*tolerance=*/0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(6, density->getValue(Coord(100, 0, 0)), /*tolerance=*/0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(10, temperature->getValue(Coord(0, 0, 0)), /*tolerance=*/0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(11, temperature->getValue(Coord(0, 100, 0)), /*tolerance=*/0); + + // Clear registries. + GridBase::clearRegistry(); + Metadata::clearRegistry(); + math::MapRegistry::clear(); + + vdbfile2.close(); + + remove("something.vdb2"); +} + + +void +TestFile::testWriteOpenFile() +{ + using namespace openvdb; + + MetaMap::Ptr meta(new MetaMap); + meta->insertMeta("author", StringMetadata("Einstein")); + meta->insertMeta("year", Int32Metadata(2009)); + + // Register metadata + Metadata::clearRegistry(); + StringMetadata::registerType(); + Int32Metadata::registerType(); + + // Write the metadata out to a file. + io::File vdbfile("something.vdb2"); + vdbfile.write(GridPtrVec(), *meta); + + io::File vdbfile2("something.vdb2"); + CPPUNIT_ASSERT_THROW(vdbfile2.getGrids(), openvdb::IoError); + + vdbfile2.open(); + CPPUNIT_ASSERT(vdbfile2.isOpen()); + + GridPtrVecPtr grids = vdbfile2.getGrids(); + meta = vdbfile2.getMetadata(); + + // Ensure we have the metadata. + CPPUNIT_ASSERT(meta.get() != NULL); + CPPUNIT_ASSERT_EQUAL(2, int(meta->metaCount())); + CPPUNIT_ASSERT(meta->metaValue("author") == "Einstein"); + CPPUNIT_ASSERT_EQUAL(2009, meta->metaValue("year")); + + // Ensure we got the grids. + CPPUNIT_ASSERT(grids.get() != NULL); + CPPUNIT_ASSERT_EQUAL(0, int(grids->size())); + + // Cannot write an open file. + CPPUNIT_ASSERT_THROW(vdbfile2.write(*grids), openvdb::IoError); + + vdbfile2.close(); + + CPPUNIT_ASSERT_NO_THROW(vdbfile2.write(*grids)); + + // Clear registries. + Metadata::clearRegistry(); + + remove("something.vdb2"); +} + + +void +TestFile::testReadGridMetadata() +{ + using namespace openvdb; + + openvdb::initialize(); + + const char* filename = "/tmp/testReadGridMetadata.vdb2"; + boost::shared_ptr scopedFile(filename, ::remove); + + // Create grids + Int32Grid::Ptr igrid = createGrid(/*bg=*/1); + FloatGrid::Ptr fgrid = createGrid(/*bg=*/2.0); + + // Add metadata. + igrid->setName("igrid"); + igrid->insertMeta("author", StringMetadata("Einstein")); + igrid->insertMeta("year", Int32Metadata(2012)); + + fgrid->setName("fgrid"); + fgrid->insertMeta("author", StringMetadata("Einstein")); + fgrid->insertMeta("year", Int32Metadata(2012)); + + // Add transforms. + math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); + igrid->setTransform(trans); + fgrid->setTransform(trans); + + // Set some values. + igrid->tree().setValue(Coord(0, 0, 0), 5); + igrid->tree().setValue(Coord(100, 0, 0), 6); + fgrid->tree().setValue(Coord(0, 0, 0), 10); + fgrid->tree().setValue(Coord(0, 100, 0), 11); + + GridPtrVec srcGrids; + srcGrids.push_back(igrid); + srcGrids.push_back(fgrid); + std::map srcGridMap; + srcGridMap[igrid->getName()] = igrid; + srcGridMap[fgrid->getName()] = fgrid; + + enum { OUTPUT_TO_FILE = 0, OUTPUT_TO_STREAM = 1 }; + for (int outputMethod = OUTPUT_TO_FILE; outputMethod <= OUTPUT_TO_STREAM; ++outputMethod) + { + if (outputMethod == OUTPUT_TO_FILE) { + // Write the grids to a file. + io::File vdbfile(filename); + vdbfile.write(srcGrids); + } else { + // Stream the grids to a file (i.e., without file offsets). + std::ofstream ostrm(filename, std::ios_base::binary); + io::Stream(ostrm).write(srcGrids); + } + + // Read just the grid-level metadata from the file. + io::File vdbfile(filename); + + // Verify that reading from an unopened file generates an exception. + CPPUNIT_ASSERT_THROW(vdbfile.readGridMetadata("igrid"), openvdb::IoError); + CPPUNIT_ASSERT_THROW(vdbfile.readGridMetadata("noname"), openvdb::IoError); + CPPUNIT_ASSERT_THROW(vdbfile.readAllGridMetadata(), openvdb::IoError); + + vdbfile.open(); + + CPPUNIT_ASSERT(vdbfile.isOpen()); + + // Verify that reading a nonexistent grid generates an exception. + CPPUNIT_ASSERT_THROW(vdbfile.readGridMetadata("noname"), openvdb::KeyError); + + // Read all grids and store them in a list. + GridPtrVecPtr gridMetadata = vdbfile.readAllGridMetadata(); + CPPUNIT_ASSERT(gridMetadata.get() != NULL); + CPPUNIT_ASSERT_EQUAL(2, int(gridMetadata->size())); + + // Read individual grids and append them to the list. + GridBase::Ptr grid = vdbfile.readGridMetadata("igrid"); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT_EQUAL(std::string("igrid"), grid->getName()); + gridMetadata->push_back(grid); + + grid = vdbfile.readGridMetadata("fgrid"); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT_EQUAL(std::string("fgrid"), grid->getName()); + gridMetadata->push_back(grid); + + // Verify that the grids' metadata and transforms match the original grids'. + for (size_t i = 0, N = gridMetadata->size(); i < N; ++i) { + grid = (*gridMetadata)[i]; + + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT(grid->getName() == "igrid" || grid->getName() == "fgrid"); + CPPUNIT_ASSERT(grid->baseTreePtr().get() != NULL); + + // Since we didn't read the grid's topology, the tree should be empty. + CPPUNIT_ASSERT_EQUAL(0, int(grid->constBaseTreePtr()->leafCount())); + CPPUNIT_ASSERT_EQUAL(0, int(grid->constBaseTreePtr()->activeVoxelCount())); + + // Retrieve the source grid of the same name. + GridBase::ConstPtr srcGrid = srcGridMap[grid->getName()]; + + // Compare grid types and transforms. + CPPUNIT_ASSERT_EQUAL(srcGrid->type(), grid->type()); + CPPUNIT_ASSERT_EQUAL(srcGrid->transform(), grid->transform()); + + // Compare metadata, ignoring fields that were added when the file was written. + MetaMap::Ptr + statsMetadata = grid->getStatsMetadata(), + otherMetadata = grid->copyMeta(); // shallow copy + CPPUNIT_ASSERT(!statsMetadata->empty()); + statsMetadata->insertMeta(GridBase::META_FILE_COMPRESSION, StringMetadata("")); + for (MetaMap::ConstMetaIterator it = grid->beginMeta(), end = grid->endMeta(); + it != end; ++it) + { + // Keep all fields that exist in the source grid. + if ((*srcGrid)[it->first]) continue; + // Remove any remaining grid statistics fields. + if ((*statsMetadata)[it->first]) { + otherMetadata->removeMeta(it->first); + } + } + CPPUNIT_ASSERT_EQUAL(srcGrid->str(), otherMetadata->str()); + + const CoordBBox srcBBox = srcGrid->evalActiveVoxelBoundingBox(); + CPPUNIT_ASSERT_EQUAL(srcBBox.min().asVec3i(), grid->metaValue("file_bbox_min")); + CPPUNIT_ASSERT_EQUAL(srcBBox.max().asVec3i(), grid->metaValue("file_bbox_max")); + CPPUNIT_ASSERT_EQUAL(srcGrid->activeVoxelCount(), + Index64(grid->metaValue("file_voxel_count"))); + CPPUNIT_ASSERT_EQUAL(srcGrid->memUsage(), + Index64(grid->metaValue("file_mem_bytes"))); + } + } +} + + +void +TestFile::testReadGridPartial() +{ + using namespace openvdb; + + typedef openvdb::FloatGrid FloatGrid; + typedef openvdb::Int32Grid IntGrid; + typedef FloatGrid::TreeType FloatTree; + typedef Int32Grid::TreeType IntTree; + + // Create grids + IntGrid::Ptr grid = createGrid(/*bg=*/1); + IntTree& tree = grid->tree(); + grid->setName("density"); + + FloatGrid::Ptr grid2 = createGrid(/*bg=*/2.0); + FloatTree& tree2 = grid2->tree(); + grid2->setName("temperature"); + + // Create transforms + math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); + math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); + grid->setTransform(trans); + grid2->setTransform(trans2); + + // Set some values + tree.setValue(Coord(0, 0, 0), 5); + tree.setValue(Coord(100, 0, 0), 6); + tree2.setValue(Coord(0, 0, 0), 10); + tree2.setValue(Coord(0, 100, 0), 11); + + MetaMap meta; + meta.insertMeta("author", StringMetadata("Einstein")); + meta.insertMeta("year", Int32Metadata(2009)); + + GridPtrVec grids; + grids.push_back(grid); + grids.push_back(grid2); + + // Register grid and transform. + openvdb::initialize(); + + // Write the vdb out to a file. + io::File vdbfile("something.vdb2"); + vdbfile.write(grids, meta); + + io::File vdbfile2("something.vdb2"); + + CPPUNIT_ASSERT_THROW(vdbfile2.readGridPartial("density"), openvdb::IoError); + + vdbfile2.open(); + + CPPUNIT_ASSERT(vdbfile2.isOpen()); + + CPPUNIT_ASSERT_THROW(vdbfile2.readGridPartial("noname"), openvdb::KeyError); + + GridBase::ConstPtr density = vdbfile2.readGridPartial("density"); + + CPPUNIT_ASSERT(density.get() != NULL); + + IntTree::ConstPtr typedDensity = gridConstPtrCast(density)->treePtr(); + + CPPUNIT_ASSERT(typedDensity.get() != NULL); + + // the following should cause a compiler error. + // typedDensity->setValue(0, 0, 0, 0); + + // Clear registries. + GridBase::clearRegistry(); + Metadata::clearRegistry(); + math::MapRegistry::clear(); + + vdbfile2.close(); + + remove("something.vdb2"); +} + + +void +TestFile::testReadGrid() +{ + using namespace openvdb; + + typedef openvdb::FloatGrid FloatGrid; + typedef openvdb::Int32Grid IntGrid; + typedef FloatGrid::TreeType FloatTree; + typedef Int32Grid::TreeType IntTree; + + // Create a vdb to write. + + // Create grids + IntGrid::Ptr grid = createGrid(/*bg=*/1); + IntTree& tree = grid->tree(); + grid->setName("density"); + + FloatGrid::Ptr grid2 = createGrid(/*bg=*/2.0); + FloatTree& tree2 = grid2->tree(); + grid2->setName("temperature"); + + // Create transforms + math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); + math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); + grid->setTransform(trans); + grid2->setTransform(trans2); + + // Set some values + tree.setValue(Coord(0, 0, 0), 5); + tree.setValue(Coord(100, 0, 0), 6); + tree2.setValue(Coord(0, 0, 0), 10); + tree2.setValue(Coord(0, 100, 0), 11); + + MetaMap meta; + meta.insertMeta("author", StringMetadata("Einstein")); + meta.insertMeta("year", Int32Metadata(2009)); + + GridPtrVec grids; + grids.push_back(grid); + grids.push_back(grid2); + + // Register grid and transform. + openvdb::initialize(); + + // Write the vdb out to a file. + io::File vdbfile("something.vdb2"); + vdbfile.write(grids, meta); + + io::File vdbfile2("something.vdb2"); + + CPPUNIT_ASSERT_THROW(vdbfile2.readGridPartial("density"), openvdb::IoError); + + vdbfile2.open(); + + CPPUNIT_ASSERT(vdbfile2.isOpen()); + + CPPUNIT_ASSERT_THROW(vdbfile2.readGridPartial("noname"), openvdb::KeyError); + + // Get Temperature + GridBase::Ptr temperature = vdbfile2.readGrid("temperature"); + + CPPUNIT_ASSERT(temperature.get() != NULL); + + FloatTree::Ptr typedTemperature = gridPtrCast(temperature)->treePtr(); + + CPPUNIT_ASSERT(typedTemperature.get() != NULL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(10, typedTemperature->getValue(Coord(0, 0, 0)), 0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(11, typedTemperature->getValue(Coord(0, 100, 0)), 0); + + // Get Density + GridBase::Ptr density = vdbfile2.readGrid("density"); + + CPPUNIT_ASSERT(density.get() != NULL); + + IntTree::Ptr typedDensity = gridPtrCast(density)->treePtr(); + + CPPUNIT_ASSERT(typedDensity.get() != NULL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(5,typedDensity->getValue(Coord(0, 0, 0)), /*tolerance=*/0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(6,typedDensity->getValue(Coord(100, 0, 0)), /*tolerance=*/0); + + // Clear registries. + GridBase::clearRegistry(); + Metadata::clearRegistry(); + math::MapRegistry::clear(); + + vdbfile2.close(); + + remove("something.vdb2"); +} + + +void +TestFile::testMultipleBufferIO() +{ + using namespace openvdb; + using namespace openvdb::io; + + typedef Int32Grid GridType; + typedef GridType::TreeType TreeType; + + File file("something.vdb2"); + + std::ostringstream ostr(std::ios_base::binary); + + // Create a grid with transform. + GridType::Ptr grid = createGrid(/*bg=*/1); + TreeType& tree = grid->tree(); + grid->setName("temperature"); + math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); + grid->setTransform(trans); + tree.setValue(Coord(10, 1, 2), 10); + tree.setValue(Coord(0, 0, 0), 5); + + GridPtrVec grids; + grids.push_back(grid); + + // Register grid and transform. + openvdb::initialize(); + + // write the vdb to the file. + file.write(grids); + + // read into a different grid. + File file2("something.vdb2"); + file2.open(); + + GridBase::Ptr temperature = file2.readGrid("temperature"); + + CPPUNIT_ASSERT(temperature.get() != NULL); + + // Clear registries. + GridBase::clearRegistry(); + math::MapRegistry::clear(); + + remove("something.vdb2"); +} + + +void +TestFile::testHasGrid() +{ + using namespace openvdb; + using namespace openvdb::io; + + typedef openvdb::FloatGrid FloatGrid; + typedef openvdb::Int32Grid IntGrid; + typedef FloatGrid::TreeType FloatTree; + typedef Int32Grid::TreeType IntTree; + + // Create a vdb to write. + + // Create grids + IntGrid::Ptr grid = createGrid(/*bg=*/1); + IntTree& tree = grid->tree(); + grid->setName("density"); + + FloatGrid::Ptr grid2 = createGrid(/*bg=*/2.0); + FloatTree& tree2 = grid2->tree(); + grid2->setName("temperature"); + + // Create transforms + math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); + math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); + grid->setTransform(trans); + grid2->setTransform(trans2); + + // Set some values + tree.setValue(Coord(0, 0, 0), 5); + tree.setValue(Coord(100, 0, 0), 6); + tree2.setValue(Coord(0, 0, 0), 10); + tree2.setValue(Coord(0, 100, 0), 11); + + MetaMap meta; + meta.insertMeta("author", StringMetadata("Einstein")); + meta.insertMeta("year", Int32Metadata(2009)); + + GridPtrVec grids; + grids.push_back(grid); + grids.push_back(grid2); + + // Register grid and transform. + GridBase::clearRegistry(); + IntGrid::registerGrid(); + FloatGrid::registerGrid(); + Metadata::clearRegistry(); + StringMetadata::registerType(); + Int32Metadata::registerType(); + // register maps + math::MapRegistry::clear(); + math::AffineMap::registerMap(); + math::ScaleMap::registerMap(); + math::UniformScaleMap::registerMap(); + math::TranslationMap::registerMap(); + math::ScaleTranslateMap::registerMap(); + math::UniformScaleTranslateMap::registerMap(); + math::NonlinearFrustumMap::registerMap(); + + // Write the vdb out to a file. + io::File vdbfile("something.vdb2"); + vdbfile.write(grids, meta); + + io::File vdbfile2("something.vdb2"); + + CPPUNIT_ASSERT_THROW(vdbfile2.hasGrid("density"), openvdb::IoError); + + vdbfile2.open(); + + CPPUNIT_ASSERT(vdbfile2.hasGrid("density")); + CPPUNIT_ASSERT(vdbfile2.hasGrid("temperature")); + CPPUNIT_ASSERT(!vdbfile2.hasGrid("Temperature")); + CPPUNIT_ASSERT(!vdbfile2.hasGrid("densitY")); + + // Clear registries. + GridBase::clearRegistry(); + Metadata::clearRegistry(); + math::MapRegistry::clear(); + + vdbfile2.close(); + + remove("something.vdb2"); +} + + +void +TestFile::testNameIterator() +{ + using namespace openvdb; + using namespace openvdb::io; + + typedef openvdb::FloatGrid FloatGrid; + typedef openvdb::Int32Grid IntGrid; + typedef FloatGrid::TreeType FloatTree; + typedef Int32Grid::TreeType IntTree; + + // Create trees. + IntTree::Ptr itree(new IntTree(1)); + itree->setValue(Coord(0, 0, 0), 5); + itree->setValue(Coord(100, 0, 0), 6); + FloatTree::Ptr ftree(new FloatTree(2.0)); + ftree->setValue(Coord(0, 0, 0), 10.0); + ftree->setValue(Coord(0, 100, 0), 11.0); + + // Create grids. + GridPtrVec grids; + GridBase::Ptr grid = createGrid(itree); + grid->setName("density"); + grids.push_back(grid); + + grid = createGrid(ftree); + grid->setName("temperature"); + grids.push_back(grid); + + // Create two unnamed grids. + grids.push_back(createGrid(ftree)); + grids.push_back(createGrid(ftree)); + + // Create two grids with the same name. + grid = createGrid(ftree); + grid->setName("level_set"); + grids.push_back(grid); + grid = createGrid(ftree); + grid->setName("level_set"); + grids.push_back(grid); + + // Register types. + openvdb::initialize(); + + const char* filename = "/tmp/testNameIterator.vdb2"; + boost::shared_ptr scopedFile(filename, ::remove); + + // Write the grids out to a file. + { + io::File vdbfile(filename); + vdbfile.write(grids); + } + + io::File vdbfile(filename); + + // Verify that name iteration fails if the file is not open. + CPPUNIT_ASSERT_THROW(vdbfile.beginName(), openvdb::IoError); + + vdbfile.open(); + + // Names should appear in lexicographic order. + Name names[6] = { "[0]", "[1]", "density", "level_set", "level_set[1]", "temperature" }; + int count = 0; + for (io::File::NameIterator iter = vdbfile.beginName(); iter != vdbfile.endName(); ++iter) { + CPPUNIT_ASSERT_EQUAL(names[count], *iter); + CPPUNIT_ASSERT_EQUAL(names[count], iter.gridName()); + ++count; + grid = vdbfile.readGrid(*iter); + CPPUNIT_ASSERT(grid); + } + CPPUNIT_ASSERT_EQUAL(6, count); + + vdbfile.close(); +} + + +void +TestFile::testReadOldFileFormat() +{ + /// @todo Save some old-format (prior to OPENVDB_FILE_VERSION) .vdb2 files + /// to /work/rd/fx_tools/vdb_unittest/TestFile::testReadOldFileFormat/ + /// Verify that the files can still be read correctly. +} + + +void +TestFile::testCompression() +{ + using namespace openvdb; + using namespace openvdb::io; + + typedef openvdb::Int32Grid IntGrid; + + // Register types. + openvdb::initialize(); + + // Create reference grids. + IntGrid::Ptr intGrid = IntGrid::create(/*background=*/0); + intGrid->fill(CoordBBox(Coord(0), Coord(49)), /*value=*/999, /*active=*/true); + intGrid->fill(CoordBBox(Coord(6), Coord(43)), /*value=*/0, /*active=*/false); + 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 = "/tmp/testCompression.vdb2"; + boost::shared_ptr scopedFile(filename, ::remove); + + size_t uncompressedSize = 0; + { + // Write the grids out to a file with compression disabled. + io::File vdbfile(filename); + vdbfile.setCompressionFlags(io::COMPRESS_NONE); + vdbfile.write(grids); + vdbfile.close(); + + // Get the size of the file in bytes. + struct stat buf; + buf.st_size = 0; + CPPUNIT_ASSERT_EQUAL(0, ::stat(filename, &buf)); + uncompressedSize = buf.st_size; + } + + // Write the grids out with various combinations of compression options + // and verify that they can be read back successfully. + // Currently, only bits 0 and 1 have meaning as compression flags + // (see io/Compression.h), so the valid combinations range from 0x0 to 0x3. + for (uint32_t flags = 0x0; flags <= 0x3; ++flags) { + + if (flags != io::COMPRESS_NONE) { + io::File vdbfile(filename); + vdbfile.setCompressionFlags(flags); + vdbfile.write(grids); + vdbfile.close(); + } + if (flags != io::COMPRESS_NONE) { + // Verify that the compressed file is significantly smaller than + // the uncompressed file. + size_t compressedSize = 0; + struct stat buf; + buf.st_size = 0; + CPPUNIT_ASSERT_EQUAL(0, ::stat(filename, &buf)); + compressedSize = buf.st_size; + CPPUNIT_ASSERT(compressedSize < size_t(0.75 * uncompressedSize)); + } + { + // Verify that the grids can be read back successfully. + + io::File vdbfile(filename); + vdbfile.open(); + + GridPtrVecPtr inGrids = vdbfile.getGrids(); + CPPUNIT_ASSERT_EQUAL(3, int(inGrids->size())); + + // Verify that the original and input grids are equal. + { + const IntGrid::Ptr grid = gridPtrCast((*inGrids)[0]); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT_EQUAL(int(intGrid->getGridClass()), int(grid->getGridClass())); + + CPPUNIT_ASSERT(grid->tree().hasSameTopology(intGrid->tree())); + + CPPUNIT_ASSERT_EQUAL( + intGrid->tree().getValue(Coord(0)), + grid->tree().getValue(Coord(0))); + // Verify that 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() != NULL); + CPPUNIT_ASSERT_EQUAL(int(refGrid->getGridClass()), int(grid->getGridClass())); + + CPPUNIT_ASSERT(grid->tree().hasSameTopology(refGrid->tree())); + + FloatGrid::ConstAccessor refAcc = refGrid->getConstAccessor(); + for (FloatGrid::ValueAllCIter it = grid->cbeginValueAll(); it; ++it) { + CPPUNIT_ASSERT_EQUAL(refAcc.getValue(it.getCoord()), *it); + } + } + } + } +} + + +//////////////////////////////////////// + + +namespace { + +using namespace openvdb; + +struct TestAsyncHelper +{ + std::set ids; + std::map filenames; + size_t refFileSize; + bool verbose; + + TestAsyncHelper(size_t _refFileSize): refFileSize(_refFileSize), verbose(false) {} + + ~TestAsyncHelper() + { + // Remove output files. + for (std::map::iterator it = filenames.begin(); + it != filenames.end(); ++it) + { + ::remove(it->second.c_str()); + } + filenames.clear(); + ids.clear(); + } + + io::Queue::Notifier notifier() + { + return boost::bind(&TestAsyncHelper::validate, this, _1, _2); + } + + void insert(io::Queue::Id id, const std::string& filename) + { + ids.insert(id); + filenames[id] = filename; + if (verbose) std::cerr << "queued " << filename << " as task " << id << "\n"; + } + + void validate(io::Queue::Id id, io::Queue::Status status) + { + if (verbose) { + std::ostringstream ostr; + ostr << "task " << id; + switch (status) { + case io::Queue::UNKNOWN: ostr << " is unknown"; break; + case io::Queue::PENDING: ostr << " is pending"; break; + case io::Queue::SUCCEEDED: ostr << " succeeded"; break; + case io::Queue::FAILED: ostr << " failed"; break; + } + std::cerr << ostr.str() << "\n"; + } + + if (status == io::Queue::SUCCEEDED) { + // If the task completed successfully, verify that the output file's + // size matches the reference file's size. + struct stat buf; + buf.st_size = 0; + CPPUNIT_ASSERT_EQUAL(0, ::stat(filenames[id].c_str(), &buf)); + CPPUNIT_ASSERT_EQUAL(Index64(refFileSize), Index64(buf.st_size)); + } + + if (status == io::Queue::SUCCEEDED || status == io::Queue::FAILED) { + ids.erase(id); + } + } +}; // struct TestAsyncHelper + +} // unnamed namespace + + +void +TestFile::testAsync() +{ + using namespace openvdb; + + typedef openvdb::Int32Grid IntGrid; + + // Register types. + openvdb::initialize(); + + // Create a grid. + FloatGrid::Ptr lsGrid = createLevelSet(); + unittest_util::makeSphere(/*dim=*/Coord(100), /*ctr=*/Vec3f(50, 50, 50), /*r=*/20.0, + *lsGrid, unittest_util::SPHERE_SPARSE_NARROW_BAND); + + MetaMap fileMetadata; + fileMetadata.insertMeta("author", StringMetadata("Einstein")); + fileMetadata.insertMeta("year", Int32Metadata(2013)); + + GridPtrVec grids; + grids.push_back(lsGrid); + grids.push_back(lsGrid->deepCopy()); + grids.push_back(lsGrid->deepCopy()); + + size_t refFileSize = 0; + { + // Write a reference file without using asynchronous I/O. + const char* filename = "/tmp/testAsyncref.vdb"; + boost::shared_ptr scopedFile(filename, ::remove); + io::File f(filename); + f.write(grids, fileMetadata); + + // Record the size of the reference file. + struct stat buf; + buf.st_size = 0; + CPPUNIT_ASSERT_EQUAL(0, ::stat(filename, &buf)); + refFileSize = buf.st_size; + } + + { + // Output multiple files using asynchronous I/O. + // Use polling to get the status of the I/O tasks. + + TestAsyncHelper helper(refFileSize); + + io::Queue queue; + for (int i = 1; i < 10; ++i) { + std::ostringstream ostr; + ostr << "/tmp/testAsync." << i << ".vdb"; + const std::string filename = ostr.str(); + io::Queue::Id id = queue.write(grids, io::File(filename), fileMetadata); + helper.insert(id, filename); + } + + tbb::tick_count start = tbb::tick_count::now(); + while (!helper.ids.empty()) { + if ((tbb::tick_count::now() - start).seconds() > 60) break; // time out after 1 minute + + // Wait one second for tasks to complete. + tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(1.0/*sec*/)); + + // Poll each task in the pending map. + std::set ids = helper.ids; // iterate over a copy + for (std::set::iterator it = ids.begin(); it != ids.end(); ++it) { + const io::Queue::Id id = *it; + const io::Queue::Status status = queue.status(id); + helper.validate(id, status); + } + } + CPPUNIT_ASSERT(helper.ids.empty()); + CPPUNIT_ASSERT(queue.empty()); + } + { + // Output multiple files using asynchronous I/O. + // Use notifications to get the status of the I/O tasks. + + TestAsyncHelper helper(refFileSize); + + io::Queue queue(/*capacity=*/2); + queue.addNotifier(helper.notifier()); + + for (int i = 1; i < 10; ++i) { + std::ostringstream ostr; + ostr << "/tmp/testAsync" << i << ".vdb"; + const std::string filename = ostr.str(); + io::Queue::Id id = queue.write(grids, io::File(filename), fileMetadata); + helper.insert(id, filename); + } + while (!queue.empty()) { + tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(1.0/*sec*/)); + } + } + { + // Test queue timeout. + + io::Queue queue(/*capacity=*/1); + queue.setTimeout(0/*sec*/); + + boost::shared_ptr + scopedFile1("/tmp/testAsyncIOa.vdb", ::remove), + scopedFile2("/tmp/testAsyncIOb.vdb", ::remove); + std::ofstream + file1(scopedFile1.get()), + file2(scopedFile2.get()); + + queue.write(grids, io::Stream(file1)); + + // With the queue length restricted to 1 and the timeout to 0 seconds, + // the next write() call should time out immediately with an exception. + // (It is possible, though highly unlikely, for the previous task to complete + // in time for this write() to actually succeed.) + CPPUNIT_ASSERT_THROW(queue.write(grids, io::Stream(file2)), openvdb::RuntimeError); + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestFloatMetadata.cc b/openvdb_2_3_0_library/openvdb/unittest/TestFloatMetadata.cc new file mode 100755 index 0000000..733bb6d --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestFloatMetadata.cc @@ -0,0 +1,77 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + + +class TestFloatMetadata : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestFloatMetadata); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + void test(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestFloatMetadata); + +void +TestFloatMetadata::test() +{ + using namespace openvdb; + + Metadata::Ptr m(new FloatMetadata(1.0)); + Metadata::Ptr m2 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); + + CPPUNIT_ASSERT(m->typeName().compare("float") == 0); + CPPUNIT_ASSERT(m2->typeName().compare("float") == 0); + + FloatMetadata *s = dynamic_cast(m.get()); + //CPPUNIT_ASSERT(s->value() == 1.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0f,s->value(),0); + s->value() = 2.0; + //CPPUNIT_ASSERT(s->value() == 2.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,s->value(),0); + m2->copy(*s); + + s = dynamic_cast(m2.get()); + //CPPUNIT_ASSERT(s->value() == 2.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,s->value(),0); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestGradient.cc b/openvdb_2_3_0_library/openvdb/unittest/TestGradient.cc new file mode 100755 index 0000000..739a2b6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestGradient.cc @@ -0,0 +1,831 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include "util.h" // for unittest_util::makeSphere() + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + +class TestGradient: public CppUnit::TestFixture +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestGradient); + CPPUNIT_TEST(testISGradient); // Gradient in Index Space + CPPUNIT_TEST(testISGradientStencil); + CPPUNIT_TEST(testWSGradient); // Gradient in World Space + CPPUNIT_TEST(testWSGradientStencil); + CPPUNIT_TEST(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; + + typedef FloatGrid::ConstAccessor AccessorType; + FloatGrid::Ptr grid = createGrid(/*background=*/5.0); + FloatTree& tree = grid->tree(); + + const openvdb::Coord dim(64,64,64); + const openvdb::Vec3f center(35.0f ,30.0f, 40.0f); + const float radius=10.0f; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + const Coord xyz(10, 20, 30); + + + // Index Space Gradients: random access and stencil version + AccessorType inAccessor = grid->getConstAccessor(); + Vec3f result; + result = math::ISGradient::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.02); + + result = math::ISGradient::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.02); + + result = math::ISGradient::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); +} + + +void +TestGradient::testISGradientStencil() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + FloatGrid::Ptr grid = createGrid(/*background=*/5.0); + FloatTree& tree = grid->tree(); + + const openvdb::Coord dim(64,64,64); + const openvdb::Vec3f center(35.0f ,30.0f, 40.0f); + const float radius = 10.0f; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + const Coord xyz(10, 20, 30); + + + // Index Space Gradients: stencil version + Vec3f result; + // this stencil is large enough for all thie different schemes used + // in this test + math::NineteenPointStencil stencil(*grid); + stencil.moveTo(xyz); + + result = math::ISGradient::result(stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.02); + + result = math::ISGradient::result(stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.02); + + result = math::ISGradient::result(stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::ISGradient::result(stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + +} + + +void +TestGradient::testWSGradient() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + + double voxel_size = 0.5; + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + grid->setTransform(math::Transform::createLinearTransform(voxel_size)); + CPPUNIT_ASSERT(grid->empty()); + + const openvdb::Coord dim(32,32,32); + const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space + const float radius = 10.0f; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!grid->empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); + const Coord xyz(11, 17, 26); + + AccessorType inAccessor = grid->getConstAccessor(); + // try with a map + + // Index Space Gradients: stencil version + Vec3f result; + math::MapBase::Ptr rotated_map; + { + math::UniformScaleMap map(voxel_size); + result = math::Gradient::result( + map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + rotated_map = map.preRotate(1.5, math::X_AXIS); + // verify the new map is an affine map + CPPUNIT_ASSERT(rotated_map->type() == math::AffineMap::mapType()); + math::AffineMap::Ptr affine_map = + boost::static_pointer_cast(rotated_map); + // the gradient should have the same length even after rotation + result = math::Gradient::result( + *affine_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + result = math::Gradient::result( + *affine_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + { + math::UniformScaleTranslateMap map(voxel_size, Vec3d(0,0,0)); + result = math::Gradient::result( + map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + { + math::ScaleTranslateMap map(Vec3d(voxel_size, voxel_size, voxel_size), Vec3d(0,0,0)); + result = math::Gradient::result( + map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + + { + // this map has no scale, expect result/voxel_spaceing = 1 + math::TranslationMap map; + result = math::Gradient::result(map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(voxel_size, result.length(), /*tolerance=*/0.01); + } + + { + // test the GenericMap Grid interface + math::GenericMap generic_map(*grid); + result = math::Gradient::result( + generic_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + { + // test the GenericMap Transform interface + math::GenericMap generic_map(grid->transform()); + result = math::Gradient::result( + generic_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + { + // test the GenericMap Map interface + math::GenericMap generic_map(rotated_map); + result = math::Gradient::result( + generic_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + { + // test a map with non-uniform SCALING AND ROTATION + Vec3d voxel_sizes(0.25, 0.45, 0.75); + math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); + // apply rotation + rotated_map = base_map->preRotate(1.5, math::X_AXIS); + grid->setTransform(math::Transform::Ptr(new math::Transform(rotated_map))); + // remake the sphere + unittest_util::makeSphere( + dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + math::AffineMap::Ptr affine_map = + boost::static_pointer_cast(rotated_map); + + // math::ScaleMap map(voxel_sizes); + result = math::Gradient::result( + *affine_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + { + // test a map with non-uniform SCALING + Vec3d voxel_sizes(0.25, 0.45, 0.75); + math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); + grid->setTransform(math::Transform::Ptr(new math::Transform(base_map))); + // remake the sphere + unittest_util::makeSphere( + dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + math::ScaleMap::Ptr scale_map = + boost::static_pointer_cast(base_map); + + // math::ScaleMap map(voxel_sizes); + result = math::Gradient::result(*scale_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } +} + +void +TestGradient::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 = + boost::static_pointer_cast( + 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 = (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; + + typedef FloatGrid::ConstAccessor AccessorType; + + double voxel_size = 0.5; + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + grid->setTransform(math::Transform::createLinearTransform(voxel_size)); + CPPUNIT_ASSERT(grid->empty()); + + const openvdb::Coord dim(32,32,32); + const openvdb::Vec3f center(6.0f, 8.0f ,10.0f);//i.e. (12,16,20) in index space + const float radius = 10; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!grid->empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); + const Coord xyz(11, 17, 26); + + // try with a map + math::SevenPointStencil stencil(*grid); + stencil.moveTo(xyz); + + math::SecondOrderDenseStencil dense_2ndOrder(*grid); + dense_2ndOrder.moveTo(xyz); + + math::FourthOrderDenseStencil dense_4thOrder(*grid); + dense_4thOrder.moveTo(xyz); + + Vec3f result; + math::MapBase::Ptr rotated_map; + { + math::UniformScaleMap map(voxel_size); + result = math::Gradient::result( + map, stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + rotated_map = map.preRotate(1.5, math::X_AXIS); + // verify the new map is an affine map + CPPUNIT_ASSERT(rotated_map->type() == math::AffineMap::mapType()); + math::AffineMap::Ptr affine_map = + boost::static_pointer_cast(rotated_map); + // the gradient should have the same length even after rotation + + result = math::Gradient::result( + *affine_map, dense_2ndOrder); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + + result = math::Gradient::result( + *affine_map, dense_4thOrder); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + + { + math::UniformScaleTranslateMap map(voxel_size, Vec3d(0,0,0)); + + result = math::Gradient::result(map, stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + { + math::ScaleTranslateMap map(Vec3d(voxel_size, voxel_size, voxel_size), Vec3d(0,0,0)); + result = math::Gradient::result(map, stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + + { + math::TranslationMap map; + result = math::Gradient::result(map, stencil); + // value = 1 because the translation map assumes uniform spacing + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, result.length(), /*tolerance=*/0.01); + } + + { + // test the GenericMap Grid interface + math::GenericMap generic_map(*grid); + result = math::Gradient::result( + generic_map, dense_2ndOrder); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + { + // test the GenericMap Transform interface + math::GenericMap generic_map(grid->transform()); + result = math::Gradient::result( + generic_map, dense_2ndOrder); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + { + // test the GenericMap Map interface + math::GenericMap generic_map(rotated_map); + result = math::Gradient::result( + generic_map, dense_2ndOrder); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + { + // test a map with non-uniform SCALING AND ROTATION + Vec3d voxel_sizes(0.25, 0.45, 0.75); + math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); + // apply rotation + rotated_map = base_map->preRotate(1.5, math::X_AXIS); + grid->setTransform(math::Transform::Ptr(new math::Transform(rotated_map))); + // remake the sphere + unittest_util::makeSphere( + dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + math::AffineMap::Ptr affine_map = + boost::static_pointer_cast(rotated_map); + + stencil.moveTo(xyz); + result = math::Gradient::result(*affine_map, stencil); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } + { + // test a map with NON-UNIFORM SCALING + Vec3d voxel_sizes(0.5, 1.0, 0.75); + math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); + grid->setTransform(math::Transform::Ptr(new math::Transform(base_map))); + // remake the sphere + unittest_util::makeSphere( + dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + math::ScaleMap map(voxel_sizes); + dense_2ndOrder.moveTo(xyz); + + result = math::Gradient::result(map, dense_2ndOrder); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); + } +} + + +void +TestGradient::testWSGradientNormSqr() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + double voxel_size = 0.5; + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + grid->setTransform(math::Transform::createLinearTransform(voxel_size)); + CPPUNIT_ASSERT(grid->empty()); + + const openvdb::Coord dim(32,32,32); + const openvdb::Vec3f center(6.0f,8.0f,10.0f);//i.e. (12,16,20) in index space + const float radius = 10.0f; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!grid->empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); + const Coord xyz(11, 17, 26); + + AccessorType inAccessor = grid->getConstAccessor(); + + // test gradient in index and world space using the 7-pt stencil + math::UniformScaleMap uniform_scale(voxel_size); + FloatTree::ValueType normsqrd; + normsqrd = math::GradientNormSqrd::result( + uniform_scale, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.07); + + // test world space using the 13pt stencil + normsqrd = math::GradientNormSqrd::result( + uniform_scale, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.05); + + math::AffineMap affine(voxel_size*math::Mat3d::identity()); + normsqrd = math::GradientNormSqrd::result( + affine, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.07); + + normsqrd = math::GradientNormSqrd::result( + uniform_scale, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.05); +} + + +void +TestGradient::testWSGradientNormSqrStencil() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + double voxel_size = 0.5; + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + grid->setTransform(math::Transform::createLinearTransform(voxel_size)); + CPPUNIT_ASSERT(grid->empty()); + + const openvdb::Coord dim(32,32,32); + const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space + const float radius = 10.0f; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!grid->empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); + const Coord xyz(11, 17, 26); + + math::SevenPointStencil sevenpt(*grid); + sevenpt.moveTo(xyz); + + math::ThirteenPointStencil thirteenpt(*grid); + thirteenpt.moveTo(xyz); + + math::SecondOrderDenseStencil dense_2ndOrder(*grid); + dense_2ndOrder.moveTo(xyz); + + math::NineteenPointStencil nineteenpt(*grid); + nineteenpt.moveTo(xyz); + + // test gradient in index and world space using the 7-pt stencil + math::UniformScaleMap uniform_scale(voxel_size); + FloatTree::ValueType normsqrd; + normsqrd = math::GradientNormSqrd::result( + uniform_scale, sevenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.07); + + + // test gradient in index and world space using the 13pt stencil + normsqrd = math::GradientNormSqrd::result( + uniform_scale, thirteenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.05); + + math::AffineMap affine(voxel_size*math::Mat3d::identity()); + normsqrd = math::GradientNormSqrd::result( + affine, dense_2ndOrder); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.07); + + normsqrd = math::GradientNormSqrd::result( + uniform_scale, nineteenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.05); +} + + +void +TestGradient::testGradientTool() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + FloatGrid::Ptr grid = createGrid(/*background=*/5.0); + FloatTree& tree = grid->tree(); + + const openvdb::Coord dim(64, 64, 64); + const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); + const float radius = 10.0f; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + const Coord xyz(10, 20, 30); + + + Vec3SGrid::Ptr grad = tools::gradient(*grid); + CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(grad->activeVoxelCount())); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, grad->getConstAccessor().getValue(xyz).length(), + /*tolerance=*/0.01); +} + + +void +TestGradient::testGradientMaskedTool() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + FloatGrid::Ptr grid = createGrid(/*background=*/5.0); + FloatTree& tree = grid->tree(); + + const openvdb::Coord dim(64, 64, 64); + const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); + const float radius = 10.0f; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); + BoolGrid::Ptr maskGrid = BoolGrid::create(false); + maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); + + Vec3SGrid::Ptr grad = tools::gradient(*grid, *maskGrid); + {// outside the masked region + const Coord xyz(10, 20, 30); + CPPUNIT_ASSERT(!maskbbox.isInside(xyz)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, grad->getConstAccessor().getValue(xyz).length(), + /*tolerance=*/0.01); + } + {// inside the masked region + const Coord xyz(38, 35, 33); + CPPUNIT_ASSERT(maskbbox.isInside(xyz)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, grad->getConstAccessor().getValue(xyz).length(), + /*tolerance=*/0.01); + } +} + + +void +TestGradient::testIntersectsIsoValue() +{ + using namespace openvdb; + + {// test zero crossing in -x + FloatGrid grid(/*backgroundValue=*/5.0); + FloatTree& tree = grid.tree(); + Coord xyz(2,-5,60); + tree.setValue(xyz, 1.3f); + tree.setValue(xyz.offsetBy(-1,0,0), -2.0f); + math::SevenPointStencil stencil(grid); + stencil.moveTo(xyz); + CPPUNIT_ASSERT( stencil.intersects( )); + CPPUNIT_ASSERT( stencil.intersects( 0.0f)); + CPPUNIT_ASSERT( stencil.intersects( 2.0f)); + CPPUNIT_ASSERT(!stencil.intersects( 5.5f)); + CPPUNIT_ASSERT(!stencil.intersects(-2.5f)); + } + {// test zero crossing in +x + FloatGrid grid(/*backgroundValue=*/5.0); + FloatTree& tree = grid.tree(); + Coord xyz(2,-5,60); + tree.setValue(xyz, 1.3f); + tree.setValue(xyz.offsetBy(1,0,0), -2.0f); + math::SevenPointStencil stencil(grid); + stencil.moveTo(xyz); + CPPUNIT_ASSERT(stencil.intersects()); + } + {// test zero crossing in -y + FloatGrid grid(/*backgroundValue=*/5.0); + FloatTree& tree = grid.tree(); + Coord xyz(2,-5,60); + tree.setValue(xyz, 1.3f); + tree.setValue(xyz.offsetBy(0,-1,0), -2.0f); + math::SevenPointStencil stencil(grid); + stencil.moveTo(xyz); + CPPUNIT_ASSERT(stencil.intersects()); + } + {// test zero crossing in y + FloatGrid grid(/*backgroundValue=*/5.0); + FloatTree& tree = grid.tree(); + Coord xyz(2,-5,60); + tree.setValue(xyz, 1.3f); + tree.setValue(xyz.offsetBy(0,1,0), -2.0f); + math::SevenPointStencil stencil(grid); + stencil.moveTo(xyz); + CPPUNIT_ASSERT(stencil.intersects()); + } + {// test zero crossing in -z + FloatGrid grid(/*backgroundValue=*/5.0); + FloatTree& tree = grid.tree(); + Coord xyz(2,-5,60); + tree.setValue(xyz, 1.3f); + tree.setValue(xyz.offsetBy(0,0,-1), -2.0f); + math::SevenPointStencil stencil(grid); + stencil.moveTo(xyz); + CPPUNIT_ASSERT(stencil.intersects()); + } + {// test zero crossing in z + FloatGrid grid(/*backgroundValue=*/5.0); + FloatTree& tree = grid.tree(); + Coord xyz(2,-5,60); + tree.setValue(xyz, 1.3f); + tree.setValue(xyz.offsetBy(0,0,1), -2.0f); + math::SevenPointStencil stencil(grid); + stencil.moveTo(xyz); + CPPUNIT_ASSERT(stencil.intersects()); + } + {// test zero crossing in -x & z + FloatGrid grid(/*backgroundValue=*/5.0); + FloatTree& tree = grid.tree(); + Coord xyz(2,-5,60); + tree.setValue(xyz, 1.3f); + tree.setValue(xyz.offsetBy(-1,0,1), -2.0f); + math::SevenPointStencil stencil(grid); + stencil.moveTo(xyz); + CPPUNIT_ASSERT(!stencil.intersects()); + } + {// test zero multiple crossings + FloatGrid grid(/*backgroundValue=*/5.0); + FloatTree& tree = grid.tree(); + Coord xyz(2,-5,60); + tree.setValue(xyz, 1.3f); + tree.setValue(xyz.offsetBy(-1, 0, 1), -1.0f); + tree.setValue(xyz.offsetBy( 0, 0, 1), -2.0f); + tree.setValue(xyz.offsetBy( 0, 1, 0), -3.0f); + tree.setValue(xyz.offsetBy( 0, 0,-1), -2.0f); + math::SevenPointStencil stencil(grid); + stencil.moveTo(xyz); + CPPUNIT_ASSERT(stencil.intersects()); + } +} + + +void +TestGradient::testOldStyleStencils() +{ + using namespace openvdb; + + FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); + grid->setTransform(math::Transform::createLinearTransform(/*voxel size=*/0.5)); + CPPUNIT_ASSERT(grid->empty()); + + const openvdb::Coord dim(32,32,32); + const openvdb::Vec3f center(6.0f,8.0f,10.0f);//i.e. (12,16,20) in index space + const float radius=10.0f; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!grid->empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); + const Coord xyz(11, 17, 26); + + math::GradStencil gs(*grid); + gs.moveTo(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, gs.gradient().length(), /*tolerance=*/0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, gs.normSqGrad(), /*tolerance=*/0.10); + + math::WenoStencil ws(*grid); + ws.moveTo(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, ws.gradient().length(), /*tolerance=*/0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, ws.normSqGrad(), /*tolerance=*/0.01); + + math::CurvatureStencil cs(*grid); + cs.moveTo(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, cs.gradient().length(), /*tolerance=*/0.01); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestGrid.cc b/openvdb_2_3_0_library/openvdb/unittest/TestGrid.cc new file mode 100755 index 0000000..ec508b5 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestGrid.cc @@ -0,0 +1,336 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(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_SUITE_END(); + + void testGridRegistry(); + void testConstPtr(); + void testGetGrid(); + void testIsType(); + void testTransform(); + void testCopyGrid(); + void testValueConversion(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestGrid); + + +//////////////////////////////////////// + + +class ProxyTree: public openvdb::TreeBase +{ +public: + typedef int ValueType; + typedef void ValueAllCIter; + typedef void ValueAllIter; + typedef void ValueOffCIter; + typedef void ValueOffIter; + typedef void ValueOnCIter; + typedef void ValueOnIter; + typedef openvdb::TreeBase::Ptr TreeBasePtr; + typedef boost::shared_ptr Ptr; + typedef boost::shared_ptr ConstPtr; + + static const openvdb::Index DEPTH; + static const ValueType backg; + + ProxyTree() {} + ProxyTree(const ValueType&) {} + virtual ~ProxyTree() {} + + static const openvdb::Name& treeType() { static const openvdb::Name s("proxy"); return s; } + virtual const openvdb::Name& type() const { return treeType(); } + virtual openvdb::Name valueType() const { return "proxy"; } + const ValueType& background() const { return backg; } + + virtual TreeBasePtr copy() const { return TreeBasePtr(new ProxyTree(*this)); } + + virtual void readTopology(std::istream& is, bool = false) { is.seekg(0, std::ios::beg); } + virtual void writeTopology(std::ostream& os, bool = false) const { os.seekp(0); } + + virtual void readBuffers(std::istream& is, bool /*saveFloatAsHalf*/=false) { is.seekg(0); } + virtual void writeBuffers(std::ostream& os, bool /*saveFloatAsHalf*/=false) const + { os.seekp(0, std::ios::beg); } + + bool empty() const { return true; } + void clear() {} + void prune(const ValueType& = 0) {} + + virtual void getIndexRange(openvdb::CoordBBox&) const {} + virtual bool evalLeafBoundingBox(openvdb::CoordBBox& bbox) const + { bbox.min() = bbox.max() = openvdb::Coord(0, 0, 0); return false; } + virtual bool evalActiveVoxelBoundingBox(openvdb::CoordBBox& bbox) const + { bbox.min() = bbox.max() = openvdb::Coord(0, 0, 0); return false; } + virtual bool evalActiveVoxelDim(openvdb::Coord& dim) const + { dim = openvdb::Coord(0, 0, 0); return false; } + virtual bool evalLeafDim(openvdb::Coord& dim) const + { dim = openvdb::Coord(0, 0, 0); return false; } + + virtual openvdb::Index treeDepth() const { return 0; } + virtual openvdb::Index leafCount() const { return 0; } + virtual openvdb::Index nonLeafCount() const { return 0; } + virtual openvdb::Index64 activeVoxelCount() const { return 0UL; } + virtual openvdb::Index64 inactiveVoxelCount() const { return 0UL; } + virtual openvdb::Index64 activeLeafVoxelCount() const { return 0UL; } + virtual openvdb::Index64 inactiveLeafVoxelCount() const { return 0UL; } +}; + +const openvdb::Index ProxyTree::DEPTH = 0; +const ProxyTree::ValueType ProxyTree::backg = 0; + +typedef openvdb::Grid ProxyGrid; + + +//////////////////////////////////////// + +void +TestGrid::testGridRegistry() +{ + using namespace openvdb::tree; + + typedef Tree, 2> > > TreeType; + typedef openvdb::Grid GridType; + + openvdb::GridBase::clearRegistry(); + + CPPUNIT_ASSERT(!GridType::isRegistered()); + GridType::registerGrid(); + CPPUNIT_ASSERT(GridType::isRegistered()); + CPPUNIT_ASSERT_THROW(GridType::registerGrid(), openvdb::KeyError); + GridType::unregisterGrid(); + CPPUNIT_ASSERT(!GridType::isRegistered()); + CPPUNIT_ASSERT_NO_THROW(GridType::unregisterGrid()); + CPPUNIT_ASSERT(!GridType::isRegistered()); + CPPUNIT_ASSERT_NO_THROW(GridType::registerGrid()); + CPPUNIT_ASSERT(GridType::isRegistered()); + + openvdb::GridBase::clearRegistry(); +} + + +void +TestGrid::testConstPtr() +{ + using namespace openvdb; + + GridBase::ConstPtr constgrid = ProxyGrid::create(); + + CPPUNIT_ASSERT_EQUAL(Name("proxy"), constgrid->type()); +} + + +void +TestGrid::testGetGrid() +{ + using namespace openvdb; + + GridBase::Ptr grid = FloatGrid::create(/*bg=*/0.0); + GridBase::ConstPtr constGrid = grid; + + CPPUNIT_ASSERT(grid->baseTreePtr()); + + CPPUNIT_ASSERT(!gridPtrCast(grid)); + CPPUNIT_ASSERT(!gridPtrCast(grid)); + + CPPUNIT_ASSERT(gridConstPtrCast(constGrid)); + CPPUNIT_ASSERT(!gridConstPtrCast(constGrid)); +} + + +void +TestGrid::testIsType() +{ + using namespace openvdb; + + GridBase::Ptr grid = FloatGrid::create(); + CPPUNIT_ASSERT(grid->isType()); + CPPUNIT_ASSERT(!grid->isType()); +} + + +void +TestGrid::testTransform() +{ + ProxyGrid grid; + + // Verify that the grid has a valid default transform. + CPPUNIT_ASSERT(grid.transformPtr()); + + // Verify that a null transform pointer is not allowed. + CPPUNIT_ASSERT_THROW(grid.setTransform(openvdb::math::Transform::Ptr()), + openvdb::ValueError); + + grid.setTransform(openvdb::math::Transform::createLinearTransform()); + + CPPUNIT_ASSERT(grid.transformPtr()); + + // Verify that calling Transform-related Grid methods (Grid::voxelSize(), etc.) + // is the same as calling those methods on the Transform. + + CPPUNIT_ASSERT(grid.transform().voxelSize().eq(grid.voxelSize())); + CPPUNIT_ASSERT(grid.transform().voxelSize(openvdb::Vec3d(0.1, 0.2, 0.3)).eq( + grid.voxelSize(openvdb::Vec3d(0.1, 0.2, 0.3)))); + + CPPUNIT_ASSERT(grid.transform().indexToWorld(openvdb::Vec3d(0.1, 0.2, 0.3)).eq( + grid.indexToWorld(openvdb::Vec3d(0.1, 0.2, 0.3)))); + CPPUNIT_ASSERT(grid.transform().indexToWorld(openvdb::Coord(1, 2, 3)).eq( + grid.indexToWorld(openvdb::Coord(1, 2, 3)))); + CPPUNIT_ASSERT(grid.transform().worldToIndex(openvdb::Vec3d(0.1, 0.2, 0.3)).eq( + grid.worldToIndex(openvdb::Vec3d(0.1, 0.2, 0.3)))); +} + + +void +TestGrid::testCopyGrid() +{ + using namespace openvdb; + + // set up a grid + const float fillValue1=5.0f; + FloatGrid::Ptr grid1 = createGrid(/*bg=*/fillValue1); + FloatTree& tree1 = grid1->tree(); + tree1.setValue(Coord(-10,40,845), 3.456f); + tree1.setValue(Coord(1,-50,-8), 1.0f); + + // create a new grid, copying the first grid + GridBase::Ptr grid2 = grid1->deepCopy(); + + // cast down to the concrete type to query values + FloatTree& tree2 = gridPtrCast(grid2)->tree(); + + // compare topology + CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); + + // trees should be equal + ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue1, tree2.getValue(Coord(1,2,3))); + ASSERT_DOUBLES_EXACTLY_EQUAL(3.456f, tree2.getValue(Coord(-10,40,845))); + ASSERT_DOUBLES_EXACTLY_EQUAL(1.0f, tree2.getValue(Coord(1,-50,-8))); + + // change 1 value in tree2 + Coord changeCoord(1, -500, -8); + tree2.setValue(changeCoord, 1.0f); + + // topology should no longer match + CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); + + // query changed value and make sure it's different between trees + ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue1, tree1.getValue(changeCoord)); + ASSERT_DOUBLES_EXACTLY_EQUAL(1.0f, tree2.getValue(changeCoord)); +} + + +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. + typedef tree::Tree3::Type DTree23; + typedef Grid DGrid23; + CPPUNIT_ASSERT_THROW(DGrid23 d23grid(fgrid), openvdb::TypeError); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestGridBbox.cc b/openvdb_2_3_0_library/openvdb/unittest/TestGridBbox.cc new file mode 100755 index 0000000..3835a91 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestGridBbox.cc @@ -0,0 +1,114 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include + + +class TestGridBbox: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestGridBbox); + CPPUNIT_TEST(testLeafBbox); + CPPUNIT_TEST(testGridBbox); + CPPUNIT_TEST_SUITE_END(); + + void testLeafBbox(); + void testGridBbox(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestGridBbox); + + +//////////////////////////////////////// + + +void +TestGridBbox::testLeafBbox() +{ + openvdb::FloatTree tree(/*fillValue=*/256.0f); + + openvdb::CoordBBox bbox; + CPPUNIT_ASSERT(!tree.evalLeafBoundingBox(bbox)); + + // Add values to buffer zero. + tree.setValue(openvdb::Coord( 0, 9, 9), 2.0); + tree.setValue(openvdb::Coord(100, 35, 800), 2.5); + + // Coordinates in CoordBBox are inclusive! + CPPUNIT_ASSERT(tree.evalLeafBoundingBox(bbox)); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 8, 8), bbox.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(104-1, 40-1, 808-1), bbox.max()); + + // Test negative coordinates. + tree.setValue(openvdb::Coord(-100, -35, -800), 2.5); + + CPPUNIT_ASSERT(tree.evalLeafBoundingBox(bbox)); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-104, -40, -800), bbox.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(104-1, 40-1, 808-1), bbox.max()); +} + + +void +TestGridBbox::testGridBbox() +{ + openvdb::FloatTree tree(/*fillValue=*/256.0f); + + openvdb::CoordBBox bbox; + CPPUNIT_ASSERT(!tree.evalActiveVoxelBoundingBox(bbox)); + + // Add values to buffer zero. + tree.setValue(openvdb::Coord( 1, 0, 0), 1.5); + tree.setValue(openvdb::Coord( 0, 12, 8), 2.0); + tree.setValue(openvdb::Coord( 1, 35, 800), 2.5); + tree.setValue(openvdb::Coord(100, 0, 16), 3.0); + tree.setValue(openvdb::Coord( 1, 0, 16), 3.5); + + // Coordinates in CoordBBox are inclusive! + CPPUNIT_ASSERT(tree.evalActiveVoxelBoundingBox(bbox)); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord( 0, 0, 0), bbox.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(100, 35, 800), bbox.max()); + + // Test negative coordinates. + tree.setValue(openvdb::Coord(-100, -35, -800), 2.5); + + CPPUNIT_ASSERT(tree.evalActiveVoxelBoundingBox(bbox)); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-100, -35, -800), bbox.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(100, 35, 800), bbox.max()); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestGridDescriptor.cc b/openvdb_2_3_0_library/openvdb/unittest/TestGridDescriptor.cc new file mode 100755 index 0000000..18e86bb --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestGridDescriptor.cc @@ -0,0 +1,190 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + + +class TestGridDescriptor: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestGridDescriptor); + CPPUNIT_TEST(testIO); + CPPUNIT_TEST(testCopy); + CPPUNIT_TEST(testName); + CPPUNIT_TEST_SUITE_END(); + + void testIO(); + void testCopy(); + void testName(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestGridDescriptor); + + +void +TestGridDescriptor::testIO() +{ + using namespace openvdb::io; + using namespace openvdb; + + typedef FloatGrid GridType; + + GridDescriptor gd(GridDescriptor::addSuffix("temperature", 2), GridType::gridType()); + gd.setInstanceParentName("temperature_32bit"); + + gd.setGridPos(123); + gd.setBlockPos(234); + gd.setEndPos(567); + + // write out the gd. + std::ostringstream ostr(std::ios_base::binary); + + gd.writeHeader(ostr); + gd.writeStreamPos(ostr); + + // Read in the gd. + std::istringstream istr(ostr.str(), std::ios_base::binary); + + // Since the input is only a fragment of a VDB file (in particular, + // it doesn't have a header), set the file format version number explicitly. + io::setCurrentVersion(istr); + + GridDescriptor gd2; + + CPPUNIT_ASSERT_THROW(gd2.read(istr), openvdb::LookupError); + + // Register the grid. + GridBase::clearRegistry(); + GridType::registerGrid(); + + // seek back and read again. + istr.seekg(0, std::ios_base::beg); + GridBase::Ptr grid; + CPPUNIT_ASSERT_NO_THROW(grid = gd2.read(istr)); + + CPPUNIT_ASSERT_EQUAL(gd.gridName(), gd2.gridName()); + CPPUNIT_ASSERT_EQUAL(gd.uniqueName(), gd2.uniqueName()); + CPPUNIT_ASSERT_EQUAL(gd.gridType(), gd2.gridType()); + CPPUNIT_ASSERT_EQUAL(gd.instanceParentName(), gd2.instanceParentName()); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT_EQUAL(GridType::gridType(), grid->type()); + CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), gd2.getGridPos()); + CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), gd2.getBlockPos()); + CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), gd2.getEndPos()); + + // Clear the registry when we are done. + GridBase::clearRegistry(); +} + + +void +TestGridDescriptor::testCopy() +{ + using namespace openvdb::io; + using namespace openvdb; + + typedef FloatGrid GridType; + + GridDescriptor gd("temperature", GridType::gridType()); + gd.setInstanceParentName("temperature_32bit"); + + gd.setGridPos(123); + gd.setBlockPos(234); + gd.setEndPos(567); + + GridDescriptor gd2; + + // do the copy + gd2 = gd; + + CPPUNIT_ASSERT_EQUAL(gd.gridName(), gd2.gridName()); + CPPUNIT_ASSERT_EQUAL(gd.uniqueName(), gd2.uniqueName()); + CPPUNIT_ASSERT_EQUAL(gd.gridType(), gd2.gridType()); + CPPUNIT_ASSERT_EQUAL(gd.instanceParentName(), gd2.instanceParentName()); + CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), gd2.getGridPos()); + CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), gd2.getBlockPos()); + CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), gd2.getEndPos()); +} + + +void +TestGridDescriptor::testName() +{ + using openvdb::Name; + using openvdb::io::GridDescriptor; + + const std::string typ = openvdb::FloatGrid::gridType(); + + Name name("test"); + GridDescriptor gd(name, typ); + + // Verify that the grid name and the unique name are equivalent + // when the unique name has no suffix. + CPPUNIT_ASSERT_EQUAL(name, gd.gridName()); + CPPUNIT_ASSERT_EQUAL(name, gd.uniqueName()); + CPPUNIT_ASSERT_EQUAL(name, GridDescriptor::nameAsString(name)); + CPPUNIT_ASSERT_EQUAL(name, GridDescriptor::stripSuffix(name)); + + // Add a suffix. + name = GridDescriptor::addSuffix("test", 2); + gd = GridDescriptor(name, typ); + + // Verify that the grid name and the unique name differ + // when the unique name has a suffix. + CPPUNIT_ASSERT_EQUAL(name, gd.uniqueName()); + CPPUNIT_ASSERT(gd.gridName() != gd.uniqueName()); + CPPUNIT_ASSERT_EQUAL(GridDescriptor::stripSuffix(name), gd.gridName()); + CPPUNIT_ASSERT_EQUAL(Name("test[2]"), GridDescriptor::nameAsString(name)); + + // As above, but with a longer suffix + name = GridDescriptor::addSuffix("test", 13); + gd = GridDescriptor(name, typ); + + CPPUNIT_ASSERT_EQUAL(name, gd.uniqueName()); + CPPUNIT_ASSERT(gd.gridName() != gd.uniqueName()); + CPPUNIT_ASSERT_EQUAL(GridDescriptor::stripSuffix(name), gd.gridName()); + CPPUNIT_ASSERT_EQUAL(Name("test[13]"), GridDescriptor::nameAsString(name)); + + // Multiple suffixes aren't supported, but verify that + // they behave reasonably, at least. + name = GridDescriptor::addSuffix(name, 4); + gd = GridDescriptor(name, typ); + + CPPUNIT_ASSERT_EQUAL(name, gd.uniqueName()); + CPPUNIT_ASSERT(gd.gridName() != gd.uniqueName()); + CPPUNIT_ASSERT_EQUAL(GridDescriptor::stripSuffix(name), gd.gridName()); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestGridIO.cc b/openvdb_2_3_0_library/openvdb/unittest/TestGridIO.cc new file mode 100755 index 0000000..eecfea6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestGridIO.cc @@ -0,0 +1,235 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + + +class TestGridIO: public CppUnit::TestCase +{ +public: + typedef openvdb::tree::Tree< + openvdb::tree::RootNode< + openvdb::tree::InternalNode< + openvdb::tree::InternalNode< + openvdb::tree::InternalNode< + openvdb::tree::LeafNode, 3>, 4>, 5> > > + Float5432Tree; + typedef openvdb::Grid Float5432Grid; + + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestGridIO); + CPPUNIT_TEST(testReadAllBool); + CPPUNIT_TEST(testReadAllFloat); + CPPUNIT_TEST(testReadAllVec3S); + CPPUNIT_TEST(testReadAllFloat5432); + CPPUNIT_TEST(testReadAllHermite); + CPPUNIT_TEST_SUITE_END(); + + void testReadAllBool() { readAllTest(); } + void testReadAllFloat() { readAllTest(); } + void testReadAllVec3S() { readAllTest(); } + void testReadAllFloat5432() { Float5432Grid::registerGrid(); readAllTest(); } + void testReadAllHermite() { readAllTest(); } +private: + template void readAllTest(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestGridIO); + + +//////////////////////////////////////// + + +template +void +TestGridIO::readAllTest() +{ + using namespace openvdb; + + typedef typename GridType::TreeType TreeType; + typedef typename TreeType::Ptr TreePtr; + typedef typename TreeType::ValueType ValueT; + typedef typename TreeType::NodeCIter NodeCIter; + const ValueT zero = zeroVal(); + + // For each level of the tree, compute a bit mask for use in converting + // global coordinates to node origins for nodes at that level. + // That is, node_origin = global_coordinates & mask[node_level]. + std::vector mask; + TreeType::getNodeLog2Dims(mask); + const size_t height = mask.size(); + for (size_t i = 0; i < height; ++i) { + Index dim = 0; + for (size_t j = i; j < height; ++j) dim += mask[j]; + mask[i] = ~((1 << dim) - 1); + } + const Index childDim = 1 + ~(mask[0]); + + // Choose sample coordinate pairs (coord0, coord1) and (coord0, coord2) + // that are guaranteed to lie in different children of the root node + // (because they are separated by more than the child node dimension). + const Coord + coord0(0, 0, 0), + coord1(int(1.1 * childDim), 0, 0), + coord2(0, int(1.1 * childDim), 0); + + // Create trees. + TreePtr + tree1(new TreeType(zero + 1)), + tree2(new TreeType(zero + 2)); + + // Set some values. + tree1->setValue(coord0, zero + 5); + tree1->setValue(coord1, zero + 6); + tree2->setValue(coord0, zero + 10); + tree2->setValue(coord2, zero + 11); + + // Create grids with trees and assign transforms. + math::Transform::Ptr trans1(math::Transform::createLinearTransform(0.1)), + trans2(math::Transform::createLinearTransform(0.1)); + GridBase::Ptr grid1 = createGrid(tree1), grid2 = createGrid(tree2); + grid1->setTransform(trans1); + grid1->setName("density"); + grid2->setTransform(trans2); + grid2->setName("temperature"); + + OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN + CPPUNIT_ASSERT_EQUAL(ValueT(zero + 5), tree1->getValue(coord0)); + CPPUNIT_ASSERT_EQUAL(ValueT(zero + 6), tree1->getValue(coord1)); + CPPUNIT_ASSERT_EQUAL(ValueT(zero + 10), tree2->getValue(coord0)); + CPPUNIT_ASSERT_EQUAL(ValueT(zero + 11), tree2->getValue(coord2)); + OPENVDB_NO_FP_EQUALITY_WARNING_END + + // count[d] is the number of nodes already visited at depth d. + // There should be exactly two nodes at each depth (apart from the root). + std::vector count(height, 0); + + // Verify that tree1 has correct node origins. + for (NodeCIter iter = tree1->cbeginNode(); iter; ++iter) { + const Index depth = iter.getDepth(); + const Coord expected[2] = { + coord0 & mask[depth], // origin of the first node at this depth + coord1 & mask[depth] // origin of the second node at this depth + }; + CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); + ++count[depth]; + } + // Verify that tree2 has correct node origins. + count.assign(height, 0); // reset node counts + for (NodeCIter iter = tree2->cbeginNode(); iter; ++iter) { + const Index depth = iter.getDepth(); + const Coord expected[2] = { coord0 & mask[depth], coord2 & mask[depth] }; + CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); + ++count[depth]; + } + + MetaMap::Ptr meta(new MetaMap); + meta->insertMeta("author", StringMetadata("Einstein")); + meta->insertMeta("year", Int32Metadata(2009)); + + GridPtrVecPtr grids(new GridPtrVec); + grids->push_back(grid1); + grids->push_back(grid2); + + // Write grids and metadata out to a file. + { + io::File vdbfile("something.vdb2"); + vdbfile.write(*grids, *meta); + } + meta.reset(); + grids.reset(); + + io::File vdbfile("something.vdb2"); + CPPUNIT_ASSERT_THROW(vdbfile.getGrids(), openvdb::IoError); // file has not been opened + + // Read the grids back in. + vdbfile.open(); + CPPUNIT_ASSERT(vdbfile.isOpen()); + + grids = vdbfile.getGrids(); + meta = vdbfile.getMetadata(); + + // Ensure we have the metadata. + CPPUNIT_ASSERT(meta.get() != NULL); + CPPUNIT_ASSERT_EQUAL(2, int(meta->metaCount())); + CPPUNIT_ASSERT_EQUAL(std::string("Einstein"), meta->metaValue("author")); + CPPUNIT_ASSERT_EQUAL(2009, meta->metaValue("year")); + + // Ensure we got both grids. + CPPUNIT_ASSERT(grids.get() != NULL); + CPPUNIT_ASSERT_EQUAL(2, int(grids->size())); + + grid1.reset(); + grid1 = findGridByName(*grids, "density"); + CPPUNIT_ASSERT(grid1.get() != NULL); + TreePtr density = gridPtrCast(grid1)->treePtr(); + CPPUNIT_ASSERT(density.get() != NULL); + + grid2.reset(); + grid2 = findGridByName(*grids, "temperature"); + CPPUNIT_ASSERT(grid2.get() != NULL); + TreePtr temperature = gridPtrCast(grid2)->treePtr(); + CPPUNIT_ASSERT(temperature.get() != NULL); + + OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN + CPPUNIT_ASSERT_EQUAL(ValueT(zero + 5), density->getValue(coord0)); + CPPUNIT_ASSERT_EQUAL(ValueT(zero + 6), density->getValue(coord1)); + CPPUNIT_ASSERT_EQUAL(ValueT(zero + 10), temperature->getValue(coord0)); + CPPUNIT_ASSERT_EQUAL(ValueT(zero + 11), temperature->getValue(coord2)); + OPENVDB_NO_FP_EQUALITY_WARNING_END + + // Check if we got the correct node origins. + count.assign(height, 0); + for (NodeCIter iter = density->cbeginNode(); iter; ++iter) { + const Index depth = iter.getDepth(); + const Coord expected[2] = { coord0 & mask[depth], coord1 & mask[depth] }; + CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); + ++count[depth]; + } + count.assign(height, 0); + for (NodeCIter iter = temperature->cbeginNode(); iter; ++iter) { + const Index depth = iter.getDepth(); + const Coord expected[2] = { coord0 & mask[depth], coord2 & mask[depth] }; + CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); + ++count[depth]; + } + + vdbfile.close(); + + remove("something.vdb2"); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestGridTransformer.cc b/openvdb_2_3_0_library/openvdb/unittest/TestGridTransformer.cc new file mode 100755 index 0000000..d10bfd4 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestGridTransformer.cc @@ -0,0 +1,265 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + +class TestGridTransformer: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestGridTransformer); + CPPUNIT_TEST(testTransformBoolPoint); + CPPUNIT_TEST(testTransformFloatPoint); + CPPUNIT_TEST(testTransformFloatBox); + CPPUNIT_TEST(testTransformFloatQuadratic); + CPPUNIT_TEST(testTransformDoubleBox); + CPPUNIT_TEST(testTransformInt32Box); + CPPUNIT_TEST(testTransformInt64Box); + CPPUNIT_TEST(testTransformVec3SPoint); + CPPUNIT_TEST(testTransformVec3DBox); + CPPUNIT_TEST(testResampleToMatch); + CPPUNIT_TEST_SUITE_END(); + + void testTransformBoolPoint() + { transformGrid(); } + void testTransformFloatPoint() + { transformGrid(); } + void testTransformFloatBox() + { transformGrid(); } + void testTransformFloatQuadratic() + { transformGrid(); } + void testTransformDoubleBox() + { transformGrid(); } + void testTransformInt32Box() + { transformGrid(); } + void testTransformInt64Box() + { transformGrid(); } + void testTransformVec3SPoint() + { transformGrid(); } + void testTransformVec3DBox() + { transformGrid(); } + + void testResampleToMatch(); + +private: + template void transformGrid(); +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION(TestGridTransformer); + + +//////////////////////////////////////// + + +template +void +TestGridTransformer::transformGrid() +{ + using openvdb::Coord; + using openvdb::CoordBBox; + using openvdb::Vec3R; + + typedef typename GridType::ValueType ValueT; + + const int radius = Sampler::radius(); + const openvdb::Vec3R zeroVec(0, 0, 0), oneVec(1, 1, 1); + const ValueT + zero = openvdb::zeroVal(), + one = zero + 1, + two = one + 1, + background = one; + const bool transformTiles = true; + + // Create a sparse test grid comprising the eight corners of a 20 x 20 x 20 cube. + typename GridType::Ptr inGrid = GridType::create(background); + typename GridType::Accessor inAcc = inGrid->getAccessor(); + inAcc.setValue(Coord( 0, 0, 0), /*value=*/zero); + inAcc.setValue(Coord(20, 0, 0), zero); + inAcc.setValue(Coord( 0, 20, 0), zero); + inAcc.setValue(Coord( 0, 0, 20), zero); + inAcc.setValue(Coord(20, 0, 20), zero); + inAcc.setValue(Coord( 0, 20, 20), zero); + inAcc.setValue(Coord(20, 20, 0), zero); + inAcc.setValue(Coord(20, 20, 20), zero); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(8), inGrid->activeVoxelCount()); + + // For various combinations of scaling, rotation and translation... + for (int i = 0; i < 8; ++i) { + const openvdb::Vec3R + scale = i & 1 ? openvdb::Vec3R(10, 4, 7.5) : oneVec, + rotate = (i & 2 ? openvdb::Vec3R(30, 230, -190) : zeroVec) * (M_PI / 180), + translate = i & 4 ? openvdb::Vec3R(-5, 0, 10) : zeroVec, + pivot = i & 8 ? openvdb::Vec3R(0.5, 4, -3.3) : zeroVec; + openvdb::tools::GridTransformer transformer(pivot, scale, rotate, translate); + transformer.setTransformTiles(transformTiles); + + // Add a tile (either active or inactive) in the interior of the cube. + const bool tileIsActive = (i % 2); + inGrid->fill(CoordBBox(Coord(8), Coord(15)), two, tileIsActive); + if (tileIsActive) { + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(512 + 8), inGrid->activeVoxelCount()); + } else { + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(8), inGrid->activeVoxelCount()); + } + // Verify that a voxel outside the cube has the background value. + CPPUNIT_ASSERT(openvdb::math::isExactlyEqual(inAcc.getValue(Coord(21, 0, 0)), background)); + CPPUNIT_ASSERT_EQUAL(false, inAcc.isValueOn(Coord(21, 0, 0))); + // Verify that a voxel inside the cube has value two. + CPPUNIT_ASSERT(openvdb::math::isExactlyEqual(inAcc.getValue(Coord(12)), two)); + CPPUNIT_ASSERT_EQUAL(tileIsActive, inAcc.isValueOn(Coord(12))); + + // Verify that the bounding box of all active values is 20 x 20 x 20. + CoordBBox activeVoxelBBox = inGrid->evalActiveVoxelBoundingBox(); + CPPUNIT_ASSERT(!activeVoxelBBox.empty()); + const Coord imin = activeVoxelBBox.min(), imax = activeVoxelBBox.max(); + CPPUNIT_ASSERT_EQUAL(Coord(0), imin); + CPPUNIT_ASSERT_EQUAL(Coord(20), imax); + + // Transform the corners of the input grid's bounding box + // and compute the enclosing bounding box in the output grid. + const openvdb::Mat4R xform = transformer.getTransform(); + const Vec3R + inRMin(imin.x(), imin.y(), imin.z()), + inRMax(imax.x(), imax.y(), imax.z()); + Vec3R outRMin, outRMax; + outRMin = outRMax = inRMin * xform; + for (int j = 0; j < 8; ++j) { + Vec3R corner( + j & 1 ? inRMax.x() : inRMin.x(), + j & 2 ? inRMax.y() : inRMin.y(), + j & 4 ? inRMax.z() : inRMin.z()); + outRMin = openvdb::math::minComponent(outRMin, corner * xform); + outRMax = openvdb::math::maxComponent(outRMax, corner * xform); + } + + CoordBBox bbox( + Coord(openvdb::tools::local_util::floorVec3(outRMin) - radius), + Coord(openvdb::tools::local_util::ceilVec3(outRMax) + radius)); + + // Transform the test grid. + typename GridType::Ptr outGrid = GridType::create(background); + transformer.transformGrid(*inGrid, *outGrid); + outGrid->tree().prune(); + + // Verify that the bounding box of the transformed grid + // matches the transformed bounding box of the original grid. + + activeVoxelBBox = outGrid->evalActiveVoxelBoundingBox(); + CPPUNIT_ASSERT(!activeVoxelBBox.empty()); + const openvdb::Vec3i + omin = activeVoxelBBox.min().asVec3i(), + omax = activeVoxelBBox.max().asVec3i(); + const int bboxTolerance = 1; // allow for rounding +#if 0 + if (!omin.eq(bbox.min().asVec3i(), bboxTolerance) || + !omax.eq(bbox.max().asVec3i(), bboxTolerance)) + { + std::cerr << "\nS = " << scale << ", R = " << rotate + << ", T = " << translate << ", P = " << pivot << "\n" + << xform.transpose() << "\n" << "computed bbox = " << bbox + << "\nactual bbox = " << omin << " -> " << omax << "\n"; + } +#endif + CPPUNIT_ASSERT(omin.eq(bbox.min().asVec3i(), bboxTolerance)); + CPPUNIT_ASSERT(omax.eq(bbox.max().asVec3i(), bboxTolerance)); + + // Verify that (a voxel in) the interior of the cube was + // transformed correctly. + const Coord center = Coord::round(Vec3R(12) * xform); + const typename GridType::TreeType& outTree = outGrid->tree(); + CPPUNIT_ASSERT(openvdb::math::isExactlyEqual(transformTiles ? two : background, + outTree.getValue(center))); + if (transformTiles && tileIsActive) CPPUNIT_ASSERT(outTree.isValueOn(center)); + else CPPUNIT_ASSERT(!outTree.isValueOn(center)); + } +} + + +//////////////////////////////////////// + + +void +TestGridTransformer::testResampleToMatch() +{ + using namespace openvdb; + + // Create an input grid with an identity transform. + FloatGrid inGrid; + // Populate it with a 10 x 10 x 10 cube. + inGrid.fill(CoordBBox(Coord(5), Coord(14)), /*value=*/1.0); + CPPUNIT_ASSERT_EQUAL(1000, int(inGrid.activeVoxelCount())); + + {//test identity transform + FloatGrid outGrid; + CPPUNIT_ASSERT(outGrid.transform() == inGrid.transform()); + // Resample the input grid into the output grid using point sampling. + tools::resampleToMatch(inGrid, outGrid); + CPPUNIT_ASSERT_EQUAL(int(inGrid.activeVoxelCount()), int(outGrid.activeVoxelCount())); + for (openvdb::FloatTree::ValueOnCIter iter = inGrid.tree().cbeginValueOn(); iter; ++iter) { + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,outGrid.tree().getValue(iter.getCoord())); + } + // The output grid's transform should not have changed. + CPPUNIT_ASSERT(outGrid.transform() == inGrid.transform()); + } + + {//test nontrivial transform + // Create an output grid with a different transform. + math::Transform::Ptr xform = math::Transform::createLinearTransform(); + xform->preScale(Vec3d(2.0, 2.0, 1.0)); + FloatGrid outGrid; + outGrid.setTransform(xform); + CPPUNIT_ASSERT(outGrid.transform() != inGrid.transform()); + + // Resample the input grid into the output grid using point sampling. + tools::resampleToMatch(inGrid, outGrid); + + // The output grid's transform should not have changed. + CPPUNIT_ASSERT_EQUAL(*xform, outGrid.transform()); + + // The output grid should have half the resolution of the input grid in x and y + // and the same resolution in z. + CPPUNIT_ASSERT_EQUAL(250, int(outGrid.activeVoxelCount())); + CPPUNIT_ASSERT_EQUAL(Coord(5, 5, 10), outGrid.evalActiveVoxelDim()), + CPPUNIT_ASSERT_EQUAL(CoordBBox(Coord(3, 3, 5), Coord(7, 7, 14)), + outGrid.evalActiveVoxelBoundingBox()); + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestHermite.cc b/openvdb_2_3_0_library/openvdb/unittest/TestHermite.cc new file mode 100755 index 0000000..f07c342 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestHermite.cc @@ -0,0 +1,359 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +class TestHermite: public CppUnit::TestFixture +{ +public: + + CPPUNIT_TEST_SUITE(TestHermite); + CPPUNIT_TEST(testAccessors); + CPPUNIT_TEST(testComparisons); + CPPUNIT_TEST(testIO); + CPPUNIT_TEST_SUITE_END(); + + void testAccessors(); + void testComparisons(); + void testIO(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestHermite); + + +//////////////////////////////////////// + + +void +TestHermite::testAccessors() +{ + using namespace openvdb; + using namespace openvdb::math; + const double offsetTol = 0.001; + const double normalTol = 0.015; + + ////////// + + // Check initial values. + + Hermite hermite; + + CPPUNIT_ASSERT(!hermite); + CPPUNIT_ASSERT(!hermite.isInside()); + + CPPUNIT_ASSERT(!hermite.hasOffsetX()); + CPPUNIT_ASSERT(!hermite.hasOffsetY()); + CPPUNIT_ASSERT(!hermite.hasOffsetZ()); + + + ////////// + + // Check set & get + + // x + Vec3s n0(1.0, 0.0, 0.0); + hermite.setX(0.5f, n0); + CPPUNIT_ASSERT(hermite.hasOffsetX()); + CPPUNIT_ASSERT(!hermite.hasOffsetY()); + CPPUNIT_ASSERT(!hermite.hasOffsetZ()); + + float offset = hermite.getOffsetX(); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, offset, offsetTol); + + Vec3s n1 = hermite.getNormalX(); + CPPUNIT_ASSERT(n0.eq(n1, normalTol)); + + // y + n0 = Vec3s(0.0, 1.0, 0.0); + hermite.setY(0.3f, n0); + CPPUNIT_ASSERT(hermite.hasOffsetX()); + CPPUNIT_ASSERT(hermite.hasOffsetY()); + CPPUNIT_ASSERT(!hermite.hasOffsetZ()); + + offset = hermite.getOffsetY(); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.3, offset, offsetTol); + + n1 = hermite.getNormalY(); + CPPUNIT_ASSERT(n0.eq(n1, normalTol)); + + // z + n0 = Vec3s(0.0, 0.0, 1.0); + hermite.setZ(0.75f, n0); + CPPUNIT_ASSERT(hermite.hasOffsetX()); + CPPUNIT_ASSERT(hermite.hasOffsetY()); + CPPUNIT_ASSERT(hermite.hasOffsetZ()); + + offset = hermite.getOffsetZ(); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.75, offset, offsetTol); + + n1 = hermite.getNormalZ(); + CPPUNIT_ASSERT(n0.eq(n1, normalTol)); + + + ////////// + + // Check inside/outside state + + hermite.setIsInside(true); + CPPUNIT_ASSERT(hermite.isInside()); + + hermite.clear(); + + CPPUNIT_ASSERT(!hermite); + CPPUNIT_ASSERT(!hermite.isInside()); + + CPPUNIT_ASSERT(!hermite.hasOffsetX()); + CPPUNIT_ASSERT(!hermite.hasOffsetY()); + CPPUNIT_ASSERT(!hermite.hasOffsetZ()); + + n0 = Vec3s(0.0, 0.0, -1.0); + hermite.setZ(0.15f, n0); + CPPUNIT_ASSERT(!hermite.hasOffsetX()); + CPPUNIT_ASSERT(!hermite.hasOffsetY()); + CPPUNIT_ASSERT(hermite.hasOffsetZ()); + + offset = hermite.getOffsetZ(); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.15, offset, offsetTol); + + n1 = hermite.getNormalZ(); + CPPUNIT_ASSERT(n0.eq(n1, normalTol)); + + hermite.setIsInside(true); + CPPUNIT_ASSERT(hermite.isInside()); + + CPPUNIT_ASSERT(hermite); +} + + +//////////////////////////////////////// + + +void +TestHermite::testComparisons() +{ + using namespace openvdb; + using namespace openvdb::math; + const double offsetTol = 0.001; + const double normalTol = 0.015; + + + ////////// + + + Vec3s offsets(0.50, 0.82, 0.14); + Vec3s nX(1.0, 0.0, 0.0); + Vec3s nY(0.0, 1.0, 0.0); + Vec3s nZ(0.0, 0.0, 1.0); + + Hermite A, B; + + A.setX(offsets[0], nX); + A.setY(offsets[1], nY); + A.setZ(offsets[2], nZ); + A.setIsInside(true); + + B = A; + + CPPUNIT_ASSERT(B); + CPPUNIT_ASSERT(B == A); + CPPUNIT_ASSERT(!(B != A)); + CPPUNIT_ASSERT(B.isInside()); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[0], B.getOffsetX(), offsetTol); + CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[1], B.getOffsetY(), offsetTol); + CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[2], B.getOffsetZ(), offsetTol); + + CPPUNIT_ASSERT(B.getNormalX().eq(nX, normalTol)); + CPPUNIT_ASSERT(B.getNormalY().eq(nY, normalTol)); + CPPUNIT_ASSERT(B.getNormalZ().eq(nZ, normalTol)); + + CPPUNIT_ASSERT(!A.isLessX(B)); + CPPUNIT_ASSERT(!A.isLessY(B)); + CPPUNIT_ASSERT(!A.isLessZ(B)); + + CPPUNIT_ASSERT(!A.isGreaterX(B)); + CPPUNIT_ASSERT(!A.isGreaterY(B)); + CPPUNIT_ASSERT(!A.isGreaterZ(B)); + + B = -B; + + CPPUNIT_ASSERT(B); + CPPUNIT_ASSERT(B != A); + CPPUNIT_ASSERT(!B.isInside()); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[0], B.getOffsetX(), offsetTol); + CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[1], B.getOffsetY(), offsetTol); + CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[2], B.getOffsetZ(), offsetTol); + + CPPUNIT_ASSERT(B.getNormalX().eq(-nX, normalTol)); + CPPUNIT_ASSERT(B.getNormalY().eq(-nY, normalTol)); + CPPUNIT_ASSERT(B.getNormalZ().eq(-nZ, normalTol)); + + CPPUNIT_ASSERT(A.isLessX(B)); + CPPUNIT_ASSERT(A.isLessY(B)); + CPPUNIT_ASSERT(A.isLessZ(B)); + + CPPUNIT_ASSERT(!A.isGreaterX(B)); + CPPUNIT_ASSERT(!A.isGreaterY(B)); + CPPUNIT_ASSERT(!A.isGreaterZ(B)); + + + ////////// + + // min / max + + Hermite C = min(A, B); + + CPPUNIT_ASSERT(C); + CPPUNIT_ASSERT(C == A); + CPPUNIT_ASSERT(C != B); + + C = max(A, B); + + CPPUNIT_ASSERT(C); + CPPUNIT_ASSERT(C != A); + CPPUNIT_ASSERT(C == B); + + + A.clear(); + B.clear(); + C.clear(); + + A.setX(offsets[0], nX); + A.setY(offsets[1], nY); + A.setZ(offsets[2], nZ); + A.setIsInside(true); + + B.setX(offsets[2], nX); + B.setY(offsets[0], nY); + B.setZ(offsets[1], nZ); + B.setIsInside(true); + + C = max(A, B); + + CPPUNIT_ASSERT(C); + CPPUNIT_ASSERT(C != A); + CPPUNIT_ASSERT(C != B); + CPPUNIT_ASSERT(C.isGreaterX(A)); + CPPUNIT_ASSERT(C.isGreaterY(A)); + CPPUNIT_ASSERT(C.isGreaterZ(B)); + + C = min(A, B); + CPPUNIT_ASSERT(C); + CPPUNIT_ASSERT(C != A); + CPPUNIT_ASSERT(C != B); + CPPUNIT_ASSERT(C.isLessX(B)); + CPPUNIT_ASSERT(C.isLessY(B)); + CPPUNIT_ASSERT(C.isLessZ(A)); + + + A.clear(); + B.clear(); + C.clear(); + + + A.setY(offsets[1], nY); + A.setZ(offsets[2], nZ); + + B.setX(offsets[2], nX); + B.setY(offsets[0], nY); + + C = min(A, B); + + CPPUNIT_ASSERT(C); + CPPUNIT_ASSERT(C != A); + CPPUNIT_ASSERT(C != B); + + CPPUNIT_ASSERT(!C.hasOffsetX()); + CPPUNIT_ASSERT(C.hasOffsetY()); + CPPUNIT_ASSERT(!C.hasOffsetZ()); + + CPPUNIT_ASSERT(C.isLessY(A)); + + C = max(A, B); + + CPPUNIT_ASSERT(C); + CPPUNIT_ASSERT(C != A); + CPPUNIT_ASSERT(C != B); + + CPPUNIT_ASSERT(C.hasOffsetX()); + CPPUNIT_ASSERT(C.hasOffsetY()); + CPPUNIT_ASSERT(C.hasOffsetZ()); + + CPPUNIT_ASSERT(C.isGreaterX(A)); + CPPUNIT_ASSERT(C.isGreaterY(B)); + CPPUNIT_ASSERT(C.isGreaterZ(B)); +} + + +//////////////////////////////////////// + + +void +TestHermite::testIO() +{ + using namespace openvdb; + using namespace openvdb::math; + + std::stringstream + ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); + + Hermite A, B; + + A.setX(0.50f, Vec3s(1.0, 0.0, 0.0)); + A.setY(0.82f, Vec3s(0.0, 1.0, 0.0)); + A.setZ(0.14f, Vec3s(0.0, 0.0, 1.0)); + A.setIsInside(true); + + CPPUNIT_ASSERT(A); + CPPUNIT_ASSERT(!B); + + A.write(ss); + + B.read(ss); + + CPPUNIT_ASSERT(A); + CPPUNIT_ASSERT(B); + + CPPUNIT_ASSERT(A == B); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestInit.cc b/openvdb_2_3_0_library/openvdb/unittest/TestInit.cc new file mode 100755 index 0000000..ca74a88 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestInit.cc @@ -0,0 +1,122 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + + +class TestInit: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestInit); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + void test(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestInit); + + +void +TestInit::test() +{ + using namespace openvdb; + + initialize(); + + // data types + CPPUNIT_ASSERT(DoubleMetadata::isRegisteredType()); + CPPUNIT_ASSERT(FloatMetadata::isRegisteredType()); + CPPUNIT_ASSERT(Int32Metadata::isRegisteredType()); + CPPUNIT_ASSERT(Int64Metadata::isRegisteredType()); + CPPUNIT_ASSERT(StringMetadata::isRegisteredType()); + CPPUNIT_ASSERT(Vec2IMetadata::isRegisteredType()); + CPPUNIT_ASSERT(Vec2SMetadata::isRegisteredType()); + CPPUNIT_ASSERT(Vec2DMetadata::isRegisteredType()); + CPPUNIT_ASSERT(Vec3IMetadata::isRegisteredType()); + CPPUNIT_ASSERT(Vec3SMetadata::isRegisteredType()); + CPPUNIT_ASSERT(Vec3DMetadata::isRegisteredType()); + + // map types + CPPUNIT_ASSERT(math::AffineMap::isRegistered()); + CPPUNIT_ASSERT(math::UnitaryMap::isRegistered()); + CPPUNIT_ASSERT(math::ScaleMap::isRegistered()); + CPPUNIT_ASSERT(math::TranslationMap::isRegistered()); + CPPUNIT_ASSERT(math::ScaleTranslateMap::isRegistered()); + CPPUNIT_ASSERT(math::NonlinearFrustumMap::isRegistered()); + + // grid types + CPPUNIT_ASSERT(BoolGrid::isRegistered()); + CPPUNIT_ASSERT(FloatGrid::isRegistered()); + CPPUNIT_ASSERT(DoubleGrid::isRegistered()); + CPPUNIT_ASSERT(Int32Grid::isRegistered()); + CPPUNIT_ASSERT(Int64Grid::isRegistered()); + CPPUNIT_ASSERT(StringGrid::isRegistered()); + CPPUNIT_ASSERT(Vec3IGrid::isRegistered()); + CPPUNIT_ASSERT(Vec3SGrid::isRegistered()); + CPPUNIT_ASSERT(Vec3DGrid::isRegistered()); + + uninitialize(); + + CPPUNIT_ASSERT(!DoubleMetadata::isRegisteredType()); + CPPUNIT_ASSERT(!FloatMetadata::isRegisteredType()); + CPPUNIT_ASSERT(!Int32Metadata::isRegisteredType()); + CPPUNIT_ASSERT(!Int64Metadata::isRegisteredType()); + CPPUNIT_ASSERT(!StringMetadata::isRegisteredType()); + CPPUNIT_ASSERT(!Vec2IMetadata::isRegisteredType()); + CPPUNIT_ASSERT(!Vec2SMetadata::isRegisteredType()); + CPPUNIT_ASSERT(!Vec2DMetadata::isRegisteredType()); + CPPUNIT_ASSERT(!Vec3IMetadata::isRegisteredType()); + CPPUNIT_ASSERT(!Vec3SMetadata::isRegisteredType()); + CPPUNIT_ASSERT(!Vec3DMetadata::isRegisteredType()); + + CPPUNIT_ASSERT(!math::AffineMap::isRegistered()); + CPPUNIT_ASSERT(!math::UnitaryMap::isRegistered()); + CPPUNIT_ASSERT(!math::ScaleMap::isRegistered()); + CPPUNIT_ASSERT(!math::TranslationMap::isRegistered()); + CPPUNIT_ASSERT(!math::ScaleTranslateMap::isRegistered()); + CPPUNIT_ASSERT(!math::NonlinearFrustumMap::isRegistered()); + + CPPUNIT_ASSERT(!BoolGrid::isRegistered()); + CPPUNIT_ASSERT(!FloatGrid::isRegistered()); + CPPUNIT_ASSERT(!DoubleGrid::isRegistered()); + CPPUNIT_ASSERT(!Int32Grid::isRegistered()); + CPPUNIT_ASSERT(!Int64Grid::isRegistered()); + CPPUNIT_ASSERT(!StringGrid::isRegistered()); + CPPUNIT_ASSERT(!Vec3IGrid::isRegistered()); + CPPUNIT_ASSERT(!Vec3SGrid::isRegistered()); + CPPUNIT_ASSERT(!Vec3DGrid::isRegistered()); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestInt32Metadata.cc b/openvdb_2_3_0_library/openvdb/unittest/TestInt32Metadata.cc new file mode 100755 index 0000000..79b7075 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestInt32Metadata.cc @@ -0,0 +1,74 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +class TestInt32Metadata : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestInt32Metadata); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + void test(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestInt32Metadata); + +void +TestInt32Metadata::test() +{ + using namespace openvdb; + + Metadata::Ptr m(new Int32Metadata(123)); + Metadata::Ptr m2 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); + + CPPUNIT_ASSERT(m->typeName().compare("int32") == 0); + CPPUNIT_ASSERT(m2->typeName().compare("int32") == 0); + + Int32Metadata *s = dynamic_cast(m.get()); + CPPUNIT_ASSERT(s->value() == 123); + s->value() = 456; + CPPUNIT_ASSERT(s->value() == 456); + + m2->copy(*s); + + s = dynamic_cast(m2.get()); + CPPUNIT_ASSERT(s->value() == 456); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestInt64Metadata.cc b/openvdb_2_3_0_library/openvdb/unittest/TestInt64Metadata.cc new file mode 100755 index 0000000..9f7328e --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestInt64Metadata.cc @@ -0,0 +1,74 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +class TestInt64Metadata : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestInt64Metadata); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + void test(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestInt64Metadata); + +void +TestInt64Metadata::test() +{ + using namespace openvdb; + + Metadata::Ptr m(new Int64Metadata(123)); + Metadata::Ptr m2 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); + + CPPUNIT_ASSERT(m->typeName().compare("int64") == 0); + CPPUNIT_ASSERT(m2->typeName().compare("int64") == 0); + + Int64Metadata *s = dynamic_cast(m.get()); + CPPUNIT_ASSERT(s->value() == 123); + s->value() = 456; + CPPUNIT_ASSERT(s->value() == 456); + + m2->copy(*s); + + s = dynamic_cast(m2.get()); + CPPUNIT_ASSERT(s->value() == 456); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestInternalOrigin.cc b/openvdb_2_3_0_library/openvdb/unittest/TestInternalOrigin.cc new file mode 100755 index 0000000..4a36c4b --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestInternalOrigin.cc @@ -0,0 +1,104 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +class TestInternalOrigin: public CppUnit::TestCase +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestInternalOrigin); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + void test(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestInternalOrigin); + +void +TestInternalOrigin::test() +{ + std::set indices; + indices.insert(openvdb::Coord( 0, 0, 0)); + indices.insert(openvdb::Coord( 1, 0, 0)); + indices.insert(openvdb::Coord( 0,100, 8)); + indices.insert(openvdb::Coord(-9, 0, 8)); + indices.insert(openvdb::Coord(32, 0, 16)); + indices.insert(openvdb::Coord(33, -5, 16)); + indices.insert(openvdb::Coord(42,707,-35)); + indices.insert(openvdb::Coord(43, 17, 64)); + + typedef openvdb::tree::Tree4::Type FloatTree4; + FloatTree4 tree(0.0f); + std::set::iterator iter=indices.begin(); + for (int n=0; iter!=indices.end(); ++n, ++iter) tree.setValue(*iter,1+n*0.5f); + + openvdb::Coord C3, G; + typedef FloatTree4::RootNodeType Node0; + typedef Node0::ChildNodeType Node1; + typedef Node1::ChildNodeType Node2; + typedef Node2::LeafNodeType Node3; + for (Node0::ChildOnCIter iter0=tree.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-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestLaplacian.cc b/openvdb_2_3_0_library/openvdb/unittest/TestLaplacian.cc new file mode 100755 index 0000000..16d8b5b --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestLaplacian.cc @@ -0,0 +1,490 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include "util.h" // for unittest_util::makeSphere() + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + +class TestLaplacian: public CppUnit::TestFixture +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestLaplacian); + CPPUNIT_TEST(testISLaplacian); // Laplacian in Index Space + CPPUNIT_TEST(testISLaplacianStencil); + CPPUNIT_TEST(testWSLaplacian); // Laplacian in World Space + CPPUNIT_TEST(testWSLaplacianFrustum); // Laplacian in World Space + CPPUNIT_TEST(testWSLaplacianStencil); + CPPUNIT_TEST(testLaplacianTool); // Laplacian tool + CPPUNIT_TEST(testLaplacianMaskedTool); // Laplacian tool + CPPUNIT_TEST(testOldStyleStencils); // old stencil impl + CPPUNIT_TEST_SUITE_END(); + + void testISLaplacian(); + void testISLaplacianStencil(); + void testWSLaplacian(); + void testWSLaplacianFrustum(); + void testWSLaplacianStencil(); + void testLaplacianTool(); + void testLaplacianMaskedTool(); + void testOldStyleStencils(); +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION(TestLaplacian); + +void +TestLaplacian::testISLaplacian() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const Coord dim(64,64,64); + const Coord c(35,30,40); + const openvdb::Vec3f center(c[0],c[1],c[2]); + const float radius=0.0f;//point at {35,30,40} + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + Coord xyz(35,10,40); + + // Index Space Laplacian random access + AccessorType inAccessor = grid->getConstAccessor(); + FloatGrid::ValueType result; + result = math::ISLaplacian::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); + + result = math::ISLaplacian::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); + + result = math::ISLaplacian::result(inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); +} + + +void +TestLaplacian::testISLaplacianStencil() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const Coord dim(64,64,64); + const Coord c(35,30,40); + const openvdb::Vec3f center(c[0],c[1],c[2]); + const float radius=0;//point at {35,30,40} + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + Coord xyz(35,10,40); + + // Index Space Laplacian stencil access + FloatGrid::ValueType result; + + math::SevenPointStencil sevenpt(*grid); + sevenpt.moveTo(xyz); + result = math::ISLaplacian::result(sevenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); + + math::ThirteenPointStencil thirteenpt(*grid); + thirteenpt.moveTo(xyz); + result = math::ISLaplacian::result(thirteenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); + + math::NineteenPointStencil nineteenpt(*grid); + nineteenpt.moveTo(xyz); + result = math::ISLaplacian::result(nineteenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); +} + + +void +TestLaplacian::testWSLaplacian() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const Coord dim(64,64,64); + const Coord c(35,30,40); + const openvdb::Vec3f center(c[0],c[1],c[2]); + const float radius=0.0f;//point at {35,30,40} + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + Coord xyz(35,10,40); + + FloatGrid::ValueType result; + AccessorType inAccessor = grid->getConstAccessor(); + + // try with a map + math::UniformScaleMap map; + math::MapBase::Ptr rotated_map = map.preRotate(1.5, math::X_AXIS); + // verify the new map is an affine map + CPPUNIT_ASSERT(rotated_map->type() == math::AffineMap::mapType()); + math::AffineMap::Ptr affine_map = + boost::static_pointer_cast(rotated_map); + + // the laplacian is invariant to rotation + result = math::Laplacian::result(*affine_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + result = math::Laplacian::result(*affine_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + result = math::Laplacian::result(*affine_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + + // test uniform map + math::UniformScaleMap uniform; + + result = math::Laplacian::result(uniform, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + result = math::Laplacian::result(uniform, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + result = math::Laplacian::result(uniform, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + + // test the GenericMap Grid interface + { + math::GenericMap generic_map(*grid); + result = math::Laplacian::result(generic_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + + result = math::Laplacian::result(generic_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + } + { + // test the GenericMap Transform interface + math::GenericMap generic_map(grid->transform()); + result = math::Laplacian::result(generic_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + + } + { + // test the GenericMap Map interface + math::GenericMap generic_map(rotated_map); + result = math::Laplacian::result(generic_map, inAccessor, xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + } + +} + + +void +TestLaplacian::testWSLaplacianFrustum() +{ + using namespace openvdb; + + // Create a Frustum Map: + + openvdb::BBoxd bbox(Vec3d(0), Vec3d(100)); + math::NonlinearFrustumMap frustum(bbox, 1./6., 5); + /// frustum will have depth, far plane - near plane = 5 + /// the frustum has width 1 in the front and 6 in the back + + math::Vec3d trans(2,2,2); + math::NonlinearFrustumMap::Ptr map = + boost::static_pointer_cast( + frustum.preScale(Vec3d(10,10,10))->postTranslate(trans)); + + CPPUNIT_ASSERT(!map->hasUniformScale()); + + math::Vec3d result; + result = map->voxelSize(); + + CPPUNIT_ASSERT( math::isApproxEqual(result.x(), 0.1)); + CPPUNIT_ASSERT( math::isApproxEqual(result.y(), 0.1)); + CPPUNIT_ASSERT( math::isApproxEqual(result.z(), 0.5, 0.0001)); + + + // Create a tree + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/0.0); + FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + // Load cos(x)sin(y)cos(z) + Coord ijk(10,10,10); + for (Int32& i=ijk.x(); i < 20; ++i) { + for (Int32& j=ijk.y(); j < 20; ++j) { + for (Int32& k=ijk.z(); k < 20; ++k) { + // world space image of the ijk coord + const Vec3d ws = map->applyMap(ijk.asVec3d()); + const float value = cos( ws.x() ) * sin( ws.y()) * cos(ws.z()); + tree.setValue(ijk, value); + } + } + } + + const Coord testloc(16,16,16); + float test_result + = math::Laplacian::result(*map, tree, testloc); + float expected_result = -3.f * tree.getValue(testloc); + + // The exact solution of Laplacian( cos(x)sin(y)cos(z) ) = -3 cos(x) sin(y) cos(z) + + CPPUNIT_ASSERT( math::isApproxEqual(test_result, expected_result, /*tolerance=*/0.02f) ); + +} + + +void +TestLaplacian::testWSLaplacianStencil() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const Coord dim(64,64,64); + const Coord c(35,30,40); + const openvdb::Vec3f center(c[0],c[1],c[2]); + const float radius=0.0f;//point at {35,30,40} + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + Coord xyz(35,10,40); + + FloatGrid::ValueType result; + + // try with a map + math::UniformScaleMap map; + math::MapBase::Ptr rotated_map = map.preRotate(1.5, math::X_AXIS); + // verify the new map is an affine map + CPPUNIT_ASSERT(rotated_map->type() == math::AffineMap::mapType()); + math::AffineMap::Ptr affine_map = + boost::static_pointer_cast(rotated_map); + + // the laplacian is invariant to rotation + math::SevenPointStencil sevenpt(*grid); + math::ThirteenPointStencil thirteenpt(*grid); + math::NineteenPointStencil nineteenpt(*grid); + math::SecondOrderDenseStencil dense_2nd(*grid); + math::FourthOrderDenseStencil dense_4th(*grid); + math::SixthOrderDenseStencil dense_6th(*grid); + sevenpt.moveTo(xyz); + thirteenpt.moveTo(xyz); + nineteenpt.moveTo(xyz); + dense_2nd.moveTo(xyz); + dense_4th.moveTo(xyz); + dense_6th.moveTo(xyz); + + result = math::Laplacian::result(*affine_map, dense_2nd); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + result = math::Laplacian::result(*affine_map, dense_4th); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + result = math::Laplacian::result(*affine_map, dense_6th); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + + // test uniform map + math::UniformScaleMap uniform; + + result = math::Laplacian::result(uniform, sevenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + result = math::Laplacian::result(uniform, thirteenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + result = math::Laplacian::result(uniform, nineteenpt); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + + // test the GenericMap Grid interface + { + math::GenericMap generic_map(*grid); + result = math::Laplacian::result(generic_map, dense_2nd); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + + result = math::Laplacian::result(generic_map, dense_4th); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + } + { + // test the GenericMap Transform interface + math::GenericMap generic_map(grid->transform()); + result = math::Laplacian::result(generic_map, dense_2nd); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + + } + { + // test the GenericMap Map interface + math::GenericMap generic_map(rotated_map); + result = math::Laplacian::result(generic_map, dense_2nd); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); + } +} + + +void +TestLaplacian::testOldStyleStencils() +{ + using namespace openvdb; + + FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); + grid->setTransform(math::Transform::createLinearTransform(/*voxel size=*/0.5)); + CPPUNIT_ASSERT(grid->empty()); + + const Coord dim(32, 32, 32); + const Coord c(35,30,40); + const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space + const float radius=10.0f; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!grid->empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); + + math::GradStencil gs(*grid); + math::WenoStencil ws(*grid); + math::CurvatureStencil cs(*grid); + + Coord xyz(20,16,20);//i.e. 8 voxel or 4 world units away from the center + gs.moveTo(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/4.0, gs.laplacian(), 0.01);// 2/distance from center + + ws.moveTo(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/4.0, ws.laplacian(), 0.01);// 2/distance from center + + cs.moveTo(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/4.0, cs.laplacian(), 0.01);// 2/distance from center + + xyz.reset(12,16,10);//i.e. 10 voxel or 5 world units away from the center + gs.moveTo(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/5.0, gs.laplacian(), 0.01);// 2/distance from center + + ws.moveTo(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/5.0, ws.laplacian(), 0.01);// 2/distance from center + + cs.moveTo(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/5.0, cs.laplacian(), 0.01);// 2/distance from center +} + + +void +TestLaplacian::testLaplacianTool() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const Coord dim(64, 64, 64); + const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); + const float radius=0.0f; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + FloatGrid::Ptr lap = tools::laplacian(*grid); + CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(lap->activeVoxelCount())); + + Coord xyz(35,30,30); + + CPPUNIT_ASSERT_DOUBLES_EQUAL( + 2.0/10.0, lap->getConstAccessor().getValue(xyz), 0.01);// 2/distance from center + + xyz.reset(35,10,40); + + CPPUNIT_ASSERT_DOUBLES_EQUAL( + 2.0/20.0, lap->getConstAccessor().getValue(xyz),0.01);// 2/distance from center +} + +void +TestLaplacian::testLaplacianMaskedTool() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); + FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const Coord dim(64, 64, 64); + const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); + const float radius=0.0f; + unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); + BoolGrid::Ptr maskGrid = BoolGrid::create(false); + maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); + + + FloatGrid::Ptr lap = tools::laplacian(*grid, *maskGrid); + + {// outside the masked region + Coord xyz(34,30,30); + + CPPUNIT_ASSERT(!maskbbox.isInside(xyz)); + CPPUNIT_ASSERT_DOUBLES_EQUAL( + 0, lap->getConstAccessor().getValue(xyz), 0.01);// 2/distance from center + + xyz.reset(35,10,40); + + CPPUNIT_ASSERT_DOUBLES_EQUAL( + 0, lap->getConstAccessor().getValue(xyz),0.01);// 2/distance from center + } + + {// inside the masked region + Coord xyz(35,30,30); + + CPPUNIT_ASSERT(maskbbox.isInside(xyz)); + CPPUNIT_ASSERT_DOUBLES_EQUAL( + 2.0/10.0, lap->getConstAccessor().getValue(xyz), 0.01);// 2/distance from center + + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestLeaf.cc b/openvdb_2_3_0_library/openvdb/unittest/TestLeaf.cc new file mode 100755 index 0000000..085816e --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestLeaf.cc @@ -0,0 +1,346 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +class TestLeaf: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestLeaf); + CPPUNIT_TEST(testBuffer); + CPPUNIT_TEST(testGetValue); + CPPUNIT_TEST(testSetValue); + CPPUNIT_TEST(testIsValueSet); + CPPUNIT_TEST(testProbeValue); + CPPUNIT_TEST(testIterators); + CPPUNIT_TEST(testEquivalence); + CPPUNIT_TEST(testGetOrigin); + CPPUNIT_TEST(testIteratorGetCoord); + CPPUNIT_TEST(testNegativeIndexing); + CPPUNIT_TEST(testSignedFloodFill); + CPPUNIT_TEST_SUITE_END(); + + void testBuffer(); + void testGetValue(); + void testSetValue(); + void testIsValueSet(); + void testProbeValue(); + void testIterators(); + void testEquivalence(); + void testGetOrigin(); + void testIteratorGetCoord(); + void testNegativeIndexing(); + void testSignedFloodFill(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestLeaf); + +typedef openvdb::tree::LeafNode LeafType; +typedef LeafType::Buffer BufferType; +using openvdb::Index; + +void +TestLeaf::testBuffer() +{ + {// access + BufferType buf; + + for (Index i = 0; i < BufferType::size(); ++i) { + buf.mData[i] = i; + CPPUNIT_ASSERT(buf[i] == buf.mData[i]); + } + for (Index i = 0; i < BufferType::size(); ++i) { + buf[i] = i; + CPPUNIT_ASSERT_EQUAL(int(i), buf[i]); + } + } + + {// swap + BufferType buf0, buf1, buf2; + + int *buf0Data = buf0.mData; + int *buf1Data = buf1.mData; + + for (Index i = 0; i < BufferType::size(); ++i) { + buf0[i] = i; + buf1[i] = i * 2; + } + + buf0.swap(buf1); + + CPPUNIT_ASSERT(buf0.mData == buf1Data); + CPPUNIT_ASSERT(buf1.mData == buf0Data); + + buf1.swap(buf0); + + CPPUNIT_ASSERT(buf0.mData == buf0Data); + CPPUNIT_ASSERT(buf1.mData == buf1Data); + + buf0.swap(buf2); + + CPPUNIT_ASSERT(buf2.mData == buf0Data); + + buf2.swap(buf0); + + CPPUNIT_ASSERT(buf0.mData == buf0Data); + } + +} + +void +TestLeaf::testGetValue() +{ + LeafType leaf(openvdb::Coord(0, 0, 0)); + + leaf.mBuffer[0] = 2; + leaf.mBuffer[1] = 3; + leaf.mBuffer[2] = 4; + leaf.mBuffer[65] = 10; + + CPPUNIT_ASSERT_EQUAL(2, leaf.getValue(openvdb::Coord(0, 0, 0))); + CPPUNIT_ASSERT_EQUAL(3, leaf.getValue(openvdb::Coord(0, 0, 1))); + CPPUNIT_ASSERT_EQUAL(4, leaf.getValue(openvdb::Coord(0, 0, 2))); + + CPPUNIT_ASSERT_EQUAL(10, leaf.getValue(openvdb::Coord(1, 0, 1))); +} + +void +TestLeaf::testSetValue() +{ + LeafType leaf(openvdb::Coord(0, 0, 0), 3); + + openvdb::Coord xyz(0, 0, 0); + leaf.setValueOn(xyz, 10); + CPPUNIT_ASSERT_EQUAL(10, leaf.getValue(xyz)); + + xyz.reset(7, 7, 7); + leaf.setValueOn(xyz, 7); + CPPUNIT_ASSERT_EQUAL(7, leaf.getValue(xyz)); + leaf.setValueOnly(xyz, 10); + CPPUNIT_ASSERT_EQUAL(10, leaf.getValue(xyz)); + + xyz.reset(2, 3, 6); + leaf.setValueOn(xyz, 236); + CPPUNIT_ASSERT_EQUAL(236, leaf.getValue(xyz)); + + leaf.setValueOff(xyz, 1); + CPPUNIT_ASSERT_EQUAL(1, leaf.getValue(xyz)); + CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); +} + +void +TestLeaf::testIsValueSet() +{ + LeafType leaf(openvdb::Coord(0, 0, 0)); + leaf.setValueOn(openvdb::Coord(1, 5, 7), 10); + + CPPUNIT_ASSERT(leaf.isValueOn(openvdb::Coord(1, 5, 7))); + + CPPUNIT_ASSERT(!leaf.isValueOn(openvdb::Coord(0, 5, 7))); + CPPUNIT_ASSERT(!leaf.isValueOn(openvdb::Coord(1, 6, 7))); + CPPUNIT_ASSERT(!leaf.isValueOn(openvdb::Coord(0, 5, 6))); +} + +void +TestLeaf::testProbeValue() +{ + LeafType leaf(openvdb::Coord(0, 0, 0)); + leaf.setValueOn(openvdb::Coord(1, 6, 5), 10); + + LeafType::ValueType val; + CPPUNIT_ASSERT(leaf.probeValue(openvdb::Coord(1, 6, 5), val)); + CPPUNIT_ASSERT(!leaf.probeValue(openvdb::Coord(1, 6, 4), val)); +} + +void +TestLeaf::testIterators() +{ + LeafType leaf(openvdb::Coord(0, 0, 0), 2); + leaf.setValueOn(openvdb::Coord(1, 2, 3), -3); + leaf.setValueOn(openvdb::Coord(5, 2, 3), 4); + LeafType::ValueType sum = 0; + for (LeafType::ValueOnIter iter = leaf.beginValueOn(); iter; ++iter) sum += *iter; + CPPUNIT_ASSERT_EQUAL((-3 + 4), sum); +} + +void +TestLeaf::testEquivalence() +{ + LeafType leaf( openvdb::Coord(0, 0, 0), 2); + LeafType leaf2(openvdb::Coord(0, 0, 0), 3); + + CPPUNIT_ASSERT(leaf != leaf2); + + for(openvdb::Index32 i = 0; i < LeafType::size(); ++i) { + leaf.setValueOnly(i, i); + leaf2.setValueOnly(i, i); + } + CPPUNIT_ASSERT(leaf == leaf2); + + // set some values. + leaf.setValueOn(openvdb::Coord(0, 0, 0), 1); + leaf.setValueOn(openvdb::Coord(0, 1, 0), 1); + leaf.setValueOn(openvdb::Coord(1, 1, 0), 1); + leaf.setValueOn(openvdb::Coord(1, 1, 2), 1); + + leaf2.setValueOn(openvdb::Coord(0, 0, 0), 1); + leaf2.setValueOn(openvdb::Coord(0, 1, 0), 1); + leaf2.setValueOn(openvdb::Coord(1, 1, 0), 1); + leaf2.setValueOn(openvdb::Coord(1, 1, 2), 1); + + CPPUNIT_ASSERT(leaf == leaf2); + + leaf2.setValueOn(openvdb::Coord(0, 0, 1), 1); + + CPPUNIT_ASSERT(leaf != leaf2); + + leaf2.setValueOff(openvdb::Coord(0, 0, 1), 1); + + CPPUNIT_ASSERT(leaf == leaf2); +} + +void +TestLeaf::testGetOrigin() +{ + { + LeafType leaf(openvdb::Coord(1, 0, 0), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(0, 0, 0), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(8, 0, 0), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(8, 1, 0), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(1024, 1, 3), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(128*8, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(1023, 1, 3), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(127*8, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(512, 512, 512), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(512, 512, 512), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(2, 52, 515), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 48, 512), leaf.origin()); + } +} + +void +TestLeaf::testIteratorGetCoord() +{ + using namespace openvdb; + + LeafType leaf(openvdb::Coord(8, 8, 0), 2); + + CPPUNIT_ASSERT_EQUAL(Coord(8, 8, 0), leaf.origin()); + + leaf.setValueOn(Coord(1, 2, 3), -3); + leaf.setValueOn(Coord(5, 2, 3), 4); + + LeafType::ValueOnIter iter = leaf.beginValueOn(); + Coord xyz = iter.getCoord(); + CPPUNIT_ASSERT_EQUAL(Coord(9, 10, 3), xyz); + + ++iter; + xyz = iter.getCoord(); + CPPUNIT_ASSERT_EQUAL(Coord(13, 10, 3), xyz); +} + +void +TestLeaf::testNegativeIndexing() +{ + using namespace openvdb; + + LeafType leaf(openvdb::Coord(-9, -2, -8), 1); + + CPPUNIT_ASSERT_EQUAL(Coord(-16, -8, -8), leaf.origin()); + + leaf.setValueOn(Coord(1, 2, 3), -3); + leaf.setValueOn(Coord(5, 2, 3), 4); + + CPPUNIT_ASSERT_EQUAL(-3, leaf.getValue(Coord(1, 2, 3))); + CPPUNIT_ASSERT_EQUAL(4, leaf.getValue(Coord(5, 2, 3))); + + LeafType::ValueOnIter iter = leaf.beginValueOn(); + Coord xyz = iter.getCoord(); + CPPUNIT_ASSERT_EQUAL(Coord(-15, -6, -5), xyz); + + ++iter; + xyz = iter.getCoord(); + CPPUNIT_ASSERT_EQUAL(Coord(-11, -6, -5), xyz); +} + +void +TestLeaf::testSignedFloodFill() +{ + using namespace openvdb; + + const int fill0=5, fill1=-fill0; + int D=LeafType::dim(), C=D/2; + Coord origin(0,0,0), left(0,0,C-1), right(0,0,C); + LeafType leaf(origin,fill0); + for (int i=0; i +#include +#include +#include +#include +#include +#include "util.h" // for unittest_util::makeSphere() + + +class TestLeafBool: public CppUnit::TestCase +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestLeafBool); + CPPUNIT_TEST(testGetValue); + CPPUNIT_TEST(testSetValue); + CPPUNIT_TEST(testProbeValue); + CPPUNIT_TEST(testIterators); + CPPUNIT_TEST(testIteratorGetCoord); + CPPUNIT_TEST(testEquivalence); + CPPUNIT_TEST(testGetOrigin); + CPPUNIT_TEST(testNegativeIndexing); + CPPUNIT_TEST(testIO); + CPPUNIT_TEST(testTopologyCopy); + CPPUNIT_TEST(testMerge); + CPPUNIT_TEST(testCombine); + CPPUNIT_TEST(testBoolTree); + //CPPUNIT_TEST(testFilter); + CPPUNIT_TEST_SUITE_END(); + + void testGetValue(); + void testSetValue(); + void testProbeValue(); + void testIterators(); + void testEquivalence(); + void testGetOrigin(); + void testIteratorGetCoord(); + void testNegativeIndexing(); + void testIO(); + void testTopologyCopy(); + void testMerge(); + void testCombine(); + void testBoolTree(); + //void testFilter(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafBool); + +typedef openvdb::tree::LeafNode LeafType; + + +//////////////////////////////////////// + + +void +TestLeafBool::testGetValue() +{ + { + LeafType leaf(openvdb::Coord(0, 0, 0), /*background=*/false); + for (openvdb::Index n = 0; n < leaf.numValues(); ++n) { + CPPUNIT_ASSERT_EQUAL(false, leaf.getValue(leaf.offsetToLocalCoord(n))); + } + } + { + LeafType leaf(openvdb::Coord(0, 0, 0), /*background=*/true); + for (openvdb::Index n = 0; n < leaf.numValues(); ++n) { + CPPUNIT_ASSERT_EQUAL(true, leaf.getValue(leaf.offsetToLocalCoord(n))); + } + } +} + + +void +TestLeafBool::testSetValue() +{ + LeafType leaf(openvdb::Coord(0, 0, 0), false); + + openvdb::Coord xyz(0, 0, 0); + CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); + leaf.setValueOn(xyz); + CPPUNIT_ASSERT(leaf.isValueOn(xyz)); + + xyz.reset(7, 7, 7); + CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); + leaf.setValueOn(xyz); + CPPUNIT_ASSERT(leaf.isValueOn(xyz)); + leaf.setValueOn(xyz, /*value=*/true); // value argument should be ignored + CPPUNIT_ASSERT(leaf.isValueOn(xyz)); + leaf.setValueOn(xyz, /*value=*/false); // value argument should be ignored + CPPUNIT_ASSERT(leaf.isValueOn(xyz)); + + leaf.setValueOff(xyz); + CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); + + xyz.reset(2, 3, 6); + leaf.setValueOn(xyz); + CPPUNIT_ASSERT(leaf.isValueOn(xyz)); + + leaf.setValueOff(xyz); + CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); +} + + +void +TestLeafBool::testProbeValue() +{ + LeafType leaf(openvdb::Coord(0, 0, 0)); + leaf.setValueOn(openvdb::Coord(1, 6, 5)); + + bool val; + CPPUNIT_ASSERT(leaf.probeValue(openvdb::Coord(1, 6, 5), val)); + CPPUNIT_ASSERT(!leaf.probeValue(openvdb::Coord(1, 6, 4), val)); +} + + +void +TestLeafBool::testIterators() +{ + LeafType leaf(openvdb::Coord(0, 0, 0)); + leaf.setValueOn(openvdb::Coord(1, 2, 3)); + leaf.setValueOn(openvdb::Coord(5, 2, 3)); + openvdb::Coord sum; + for (LeafType::ValueOnIter iter = leaf.beginValueOn(); iter; ++iter) { + sum += iter.getCoord(); + } + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(1 + 5, 2 + 2, 3 + 3), sum); + + openvdb::Index count = 0; + for (LeafType::ValueOffIter iter = leaf.beginValueOff(); iter; ++iter, ++count); + CPPUNIT_ASSERT_EQUAL(leaf.numValues() - 2, count); + + count = 0; + for (LeafType::ValueAllIter iter = leaf.beginValueAll(); iter; ++iter, ++count); + CPPUNIT_ASSERT_EQUAL(leaf.numValues(), count); + + count = 0; + for (LeafType::ChildOnIter iter = leaf.beginChildOn(); iter; ++iter, ++count); + CPPUNIT_ASSERT_EQUAL(openvdb::Index(0), count); + + count = 0; + for (LeafType::ChildOffIter iter = leaf.beginChildOff(); iter; ++iter, ++count); + CPPUNIT_ASSERT_EQUAL(openvdb::Index(0), count); + + count = 0; + for (LeafType::ChildAllIter iter = leaf.beginChildAll(); iter; ++iter, ++count); + CPPUNIT_ASSERT_EQUAL(leaf.numValues(), count); +} + + +void +TestLeafBool::testIteratorGetCoord() +{ + using namespace openvdb; + + LeafType leaf(openvdb::Coord(8, 8, 0)); + + CPPUNIT_ASSERT_EQUAL(Coord(8, 8, 0), leaf.origin()); + + leaf.setValueOn(Coord(1, 2, 3), -3); + leaf.setValueOn(Coord(5, 2, 3), 4); + + LeafType::ValueOnIter iter = leaf.beginValueOn(); + Coord xyz = iter.getCoord(); + CPPUNIT_ASSERT_EQUAL(Coord(9, 10, 3), xyz); + + ++iter; + xyz = iter.getCoord(); + CPPUNIT_ASSERT_EQUAL(Coord(13, 10, 3), xyz); +} + + +void +TestLeafBool::testEquivalence() +{ + using openvdb::CoordBBox; + using openvdb::Coord; + + LeafType leaf(Coord(0, 0, 0), false); // false and inactive + LeafType leaf2(Coord(0, 0, 0), true); // true and inactive + + CPPUNIT_ASSERT(leaf != leaf2); + + leaf.fill(CoordBBox(Coord(0), Coord(LeafType::DIM - 1)), true, /*active=*/false); + CPPUNIT_ASSERT(leaf == leaf2); // true and inactive + + leaf.setValuesOn(); // true and active + + leaf2.fill(CoordBBox(Coord(0), Coord(LeafType::DIM - 1)), false); // false and active + CPPUNIT_ASSERT(leaf != leaf2); + + leaf.negate(); // false and active + CPPUNIT_ASSERT(leaf == leaf2); + + // Set some values. + leaf.setValueOn(Coord(0, 0, 0), true); + leaf.setValueOn(Coord(0, 1, 0), true); + leaf.setValueOn(Coord(1, 1, 0), true); + leaf.setValueOn(Coord(1, 1, 2), true); + + leaf2.setValueOn(Coord(0, 0, 0), true); + leaf2.setValueOn(Coord(0, 1, 0), true); + leaf2.setValueOn(Coord(1, 1, 0), true); + leaf2.setValueOn(Coord(1, 1, 2), true); + + CPPUNIT_ASSERT(leaf == leaf2); + + leaf2.setValueOn(Coord(0, 0, 1), true); + + CPPUNIT_ASSERT(leaf != leaf2); + + leaf2.setValueOff(Coord(0, 0, 1), false); + + CPPUNIT_ASSERT(leaf != leaf2); + + leaf2.setValueOn(Coord(0, 0, 1)); + + CPPUNIT_ASSERT(leaf == leaf2); +} + + +void +TestLeafBool::testGetOrigin() +{ + { + LeafType leaf(openvdb::Coord(1, 0, 0), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(0, 0, 0), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(8, 0, 0), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(8, 1, 0), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(1024, 1, 3), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(128*8, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(1023, 1, 3), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(127*8, 0, 0), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(512, 512, 512), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(512, 512, 512), leaf.origin()); + } + { + LeafType leaf(openvdb::Coord(2, 52, 515), 1); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 48, 512), leaf.origin()); + } +} + + +void +TestLeafBool::testNegativeIndexing() +{ + using namespace openvdb; + + LeafType leaf(openvdb::Coord(-9, -2, -8)); + + CPPUNIT_ASSERT_EQUAL(Coord(-16, -8, -8), leaf.origin()); + + leaf.setValueOn(Coord(1, 2, 3)); + leaf.setValueOn(Coord(5, 2, 3)); + + CPPUNIT_ASSERT(leaf.isValueOn(Coord(1, 2, 3))); + CPPUNIT_ASSERT(leaf.isValueOn(Coord(5, 2, 3))); + + LeafType::ValueOnIter iter = leaf.beginValueOn(); + Coord xyz = iter.getCoord(); + CPPUNIT_ASSERT_EQUAL(Coord(-15, -6, -5), xyz); + + ++iter; + xyz = iter.getCoord(); + CPPUNIT_ASSERT_EQUAL(Coord(-11, -6, -5), xyz); +} + + +void +TestLeafBool::testIO() +{ + LeafType leaf(openvdb::Coord(1, 3, 5)); + const openvdb::Coord origin = leaf.origin(); + + leaf.setValueOn(openvdb::Coord(0, 1, 0)); + leaf.setValueOn(openvdb::Coord(1, 0, 0)); + + std::ostringstream ostr(std::ios_base::binary); + + leaf.writeBuffers(ostr); + + leaf.setValueOff(openvdb::Coord(0, 1, 0)); + leaf.setValueOn(openvdb::Coord(0, 1, 1)); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + // Since the input stream doesn't include a VDB header with file format version info, + // tag the input stream explicitly with the current version number. + openvdb::io::setCurrentVersion(istr); + + leaf.readBuffers(istr); + + CPPUNIT_ASSERT_EQUAL(origin, leaf.origin()); + + CPPUNIT_ASSERT(leaf.isValueOn(openvdb::Coord(0, 1, 0))); + CPPUNIT_ASSERT(leaf.isValueOn(openvdb::Coord(1, 0, 0))); + + CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); +} + + +void +TestLeafBool::testTopologyCopy() +{ + using openvdb::Coord; + + // LeafNode having the same Log2Dim as LeafType + typedef LeafType::ValueConverter::Type FloatLeafType; + + FloatLeafType fleaf(Coord(10, 20, 30), /*background=*/-1.0); + std::set coords; + for (openvdb::Index n = 0; n < fleaf.numValues(); n += 10) { + Coord xyz = fleaf.offsetToGlobalCoord(n); + fleaf.setValueOn(xyz, n); + coords.insert(xyz); + } + + LeafType leaf(fleaf, openvdb::TopologyCopy()); + CPPUNIT_ASSERT_EQUAL(fleaf.onVoxelCount(), leaf.onVoxelCount()); + + CPPUNIT_ASSERT(leaf.hasSameTopology(&fleaf)); + + for (LeafType::ValueOnIter iter = leaf.beginValueOn(); iter; ++iter) { + coords.erase(iter.getCoord()); + } + CPPUNIT_ASSERT(coords.empty()); +} + + +void +TestLeafBool::testMerge() +{ + LeafType leaf(openvdb::Coord(0, 0, 0)); + for (openvdb::Index n = 0; n < leaf.numValues(); n += 10) { + leaf.setValueOn(n); + } + CPPUNIT_ASSERT(!leaf.isValueMaskOn()); + CPPUNIT_ASSERT(!leaf.isValueMaskOff()); + bool val = false, active = false; + CPPUNIT_ASSERT(!leaf.isConstant(val, active)); + + LeafType leaf2(leaf); + leaf2.getValueMask().toggle(); + CPPUNIT_ASSERT(!leaf2.isValueMaskOn()); + CPPUNIT_ASSERT(!leaf2.isValueMaskOff()); + val = active = false; + CPPUNIT_ASSERT(!leaf2.isConstant(val, active)); + + leaf.merge(leaf2); + CPPUNIT_ASSERT(leaf.isValueMaskOn()); + CPPUNIT_ASSERT(!leaf.isValueMaskOff()); + val = active = false; + CPPUNIT_ASSERT(leaf.isConstant(val, active)); + CPPUNIT_ASSERT(active); +} + + +void +TestLeafBool::testCombine() +{ + struct Local { + static void op(openvdb::CombineArgs& args) { + args.setResult(false); // result should be ignored + args.setResultIsActive(args.aIsActive() ^ args.bIsActive()); + } + }; + + LeafType leaf(openvdb::Coord(0, 0, 0)); + for (openvdb::Index n = 0; n < leaf.numValues(); n += 10) { + leaf.setValueOn(n); + } + CPPUNIT_ASSERT(!leaf.isValueMaskOn()); + CPPUNIT_ASSERT(!leaf.isValueMaskOff()); + const LeafType::NodeMaskType savedMask = leaf.getValueMask(); + OPENVDB_LOG_DEBUG_RUNTIME(leaf.str()); + + LeafType leaf2(leaf); + for (openvdb::Index n = 0; n < leaf.numValues(); n += 4) { + leaf2.setValueOn(n); + } + CPPUNIT_ASSERT(!leaf2.isValueMaskOn()); + CPPUNIT_ASSERT(!leaf2.isValueMaskOff()); + OPENVDB_LOG_DEBUG_RUNTIME(leaf2.str()); + + leaf.combine(leaf2, Local::op); + OPENVDB_LOG_DEBUG_RUNTIME(leaf.str()); + + CPPUNIT_ASSERT(leaf.getValueMask() == (savedMask ^ leaf2.getValueMask())); +} + + +void +TestLeafBool::testBoolTree() +{ + using namespace openvdb; + +#if 0 + FloatGrid::Ptr inGrid; + FloatTree::Ptr inTree; + { + //io::File vdbFile("/work/rd/fx_tools/vdb_unittest/TestGridCombine::testCsg/large1.vdb2"); + io::File vdbFile("/hosts/whitestar/usr/pic1/VDB/bunny_0256.vdb2"); + vdbFile.open(); + inGrid = gridPtrCast(vdbFile.readGrid("LevelSet")); + CPPUNIT_ASSERT(inGrid.get() != NULL); + inTree = inGrid->treePtr(); + CPPUNIT_ASSERT(inTree.get() != NULL); + } +#else + FloatGrid::Ptr inGrid = FloatGrid::create(); + CPPUNIT_ASSERT(inGrid.get() != NULL); + FloatTree& inTree = inGrid->tree(); + inGrid->setName("LevelSet"); + + unittest_util::makeSphere(/*dim =*/Coord(128), + /*center=*/Vec3f(0, 0, 0), + /*radius=*/5, + *inGrid, unittest_util::SPHERE_DENSE); +#endif + + const Index64 + floatTreeMem = inTree.memUsage(), + floatTreeLeafCount = inTree.leafCount(), + floatTreeVoxelCount = inTree.activeVoxelCount(); + + TreeBase::Ptr outTree(new BoolTree(inTree, false, true, TopologyCopy())); + CPPUNIT_ASSERT(outTree.get() != NULL); + + BoolGrid::Ptr outGrid = BoolGrid::create(*inGrid); // copy transform and metadata + outGrid->setTree(outTree); + outGrid->setName("Boolean"); + + const Index64 + boolTreeMem = outTree->memUsage(), + boolTreeLeafCount = outTree->leafCount(), + boolTreeVoxelCount = outTree->activeVoxelCount(); + +#if 0 + GridPtrVec grids; + grids.push_back(inGrid); + grids.push_back(outGrid); + io::File vdbFile("/tmp/bool_tree.vdb2"); + vdbFile.write(grids); + vdbFile.close(); +#endif + + CPPUNIT_ASSERT_EQUAL(floatTreeLeafCount, boolTreeLeafCount); + CPPUNIT_ASSERT_EQUAL(floatTreeVoxelCount, boolTreeVoxelCount); + + //std::cerr << "\nboolTree mem=" << boolTreeMem << " bytes" << std::endl; + //std::cerr << "floatTree mem=" << floatTreeMem << " bytes" << std::endl; + + // Considering only voxel buffer memory usage, the BoolTree would be expected + // to use (2 mask bits/voxel / ((32 value bits + 1 mask bit)/voxel)) = ~1/16 + // as much memory as the FloatTree. Considering total memory usage, verify that + // the BoolTree is no more than 1/10 the size of the FloatTree. + CPPUNIT_ASSERT(boolTreeMem * 10 <= floatTreeMem); +} + + +// void +// TestLeafBool::testFilter() +// { +// using namespace openvdb; + +// BoolGrid::Ptr grid = BoolGrid::create(); +// CPPUNIT_ASSERT(grid.get() != NULL); +// BoolTree::Ptr tree = grid->treePtr(); +// CPPUNIT_ASSERT(tree.get() != NULL); +// grid->setName("filtered"); + +// unittest_util::makeSphere(/*dim=*/Coord(32), +// /*ctr=*/Vec3f(0, 0, 0), +// /*radius=*/10, +// *grid, unittest_util::SPHERE_DENSE); + +// BoolTree::Ptr copyOfTree(new BoolTree(*tree)); +// BoolGrid::Ptr copyOfGrid = BoolGrid::create(copyOfTree); +// copyOfGrid->setName("original"); + +// tools::Filter filter(*grid); +// filter.offset(1); + +// #if 0 +// GridPtrVec grids; +// grids.push_back(copyOfGrid); +// grids.push_back(grid); +// io::File vdbFile("/tmp/TestLeafBool::testFilter.vdb2"); +// vdbFile.write(grids); +// vdbFile.close(); +// #endif + +// // Verify that offsetting all active voxels by 1 (true) has no effect, +// // since the active voxels were all true to begin with. +// CPPUNIT_ASSERT(tree->hasSameTopology(*copyOfTree)); +// } + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestLeafIO.cc b/openvdb_2_3_0_library/openvdb/unittest/TestLeafIO.cc new file mode 100755 index 0000000..61b7417 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestLeafIO.cc @@ -0,0 +1,172 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include // for toupper() +#include +#include + +// CPPUNIT_TEST_SUITE() invokes CPPUNIT_TESTNAMER_DECL() to generate a suite name +// from the FixtureType. But if FixtureType is a templated type, the generated name +// can become long and messy. This macro overrides the normal naming logic, +// instead invoking FixtureType::testSuiteName(), which should be a static member +// function that returns a std::string containing the suite name for the specific +// template instantiation. +#undef CPPUNIT_TESTNAMER_DECL +#define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ + CPPUNIT_NS::TestNamer variableName( FixtureType::testSuiteName() ) + + +template +class TestLeafIO: public CppUnit::TestCase +{ +public: + static std::string testSuiteName() + { + std::string name = openvdb::typeNameAsString(); + if (!name.empty()) name[0] = ::toupper(name[0]); + return "TestLeafIO" + name; + } + + CPPUNIT_TEST_SUITE(TestLeafIO); + CPPUNIT_TEST(testBuffer); + CPPUNIT_TEST_SUITE_END(); + + void testBuffer(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); + + +template +void +TestLeafIO::testBuffer() +{ + openvdb::tree::LeafNode leaf(openvdb::Coord(0, 0, 0)); + + leaf.setValueOn(openvdb::Coord(0, 1, 0), T(1)); + leaf.setValueOn(openvdb::Coord(1, 0, 0), T(1)); + + std::ostringstream ostr(std::ios_base::binary); + + leaf.writeBuffers(ostr); + + leaf.setValueOn(openvdb::Coord(0, 1, 0), T(0)); + leaf.setValueOn(openvdb::Coord(0, 1, 1), T(1)); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + // Since the input stream doesn't include a VDB header with file format version info, + // tag the input stream explicitly with the current version number. + openvdb::io::setCurrentVersion(istr); + + leaf.readBuffers(istr); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1), leaf.getValue(openvdb::Coord(0, 1, 0)), /*tolerance=*/0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1), leaf.getValue(openvdb::Coord(1, 0, 0)), /*tolerance=*/0); + + CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); +} + + +template<> +void +TestLeafIO::testBuffer() +{ + openvdb::tree::LeafNode + leaf(openvdb::Coord(0, 0, 0), std::string()); + + leaf.setValueOn(openvdb::Coord(0, 1, 0), std::string("test")); + leaf.setValueOn(openvdb::Coord(1, 0, 0), std::string("test")); + + std::ostringstream ostr(std::ios_base::binary); + + leaf.writeBuffers(ostr); + + leaf.setValueOn(openvdb::Coord(0, 1, 0), std::string("douche")); + leaf.setValueOn(openvdb::Coord(0, 1, 1), std::string("douche")); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + // Since the input stream doesn't include a VDB header with file format version info, + // tag the input stream explicitly with the current version number. + openvdb::io::setCurrentVersion(istr); + + leaf.readBuffers(istr); + + CPPUNIT_ASSERT_EQUAL(std::string("test"), leaf.getValue(openvdb::Coord(0, 1, 0))); + CPPUNIT_ASSERT_EQUAL(std::string("test"), leaf.getValue(openvdb::Coord(1, 0, 0))); + + CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); +} + + +template<> +void +TestLeafIO::testBuffer() +{ + openvdb::tree::LeafNode leaf(openvdb::Coord(0, 0, 0)); + + leaf.setValueOn(openvdb::Coord(0, 1, 0), openvdb::Vec3R(1, 1, 1)); + leaf.setValueOn(openvdb::Coord(1, 0, 0), openvdb::Vec3R(1, 1, 1)); + + std::ostringstream ostr(std::ios_base::binary); + + leaf.writeBuffers(ostr); + + leaf.setValueOn(openvdb::Coord(0, 1, 0), openvdb::Vec3R(0, 0, 0)); + leaf.setValueOn(openvdb::Coord(0, 1, 1), openvdb::Vec3R(1, 1, 1)); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + // Since the input stream doesn't include a VDB header with file format version info, + // tag the input stream explicitly with the current version number. + openvdb::io::setCurrentVersion(istr); + + leaf.readBuffers(istr); + + CPPUNIT_ASSERT(leaf.getValue(openvdb::Coord(0, 1, 0)) == openvdb::Vec3R(1, 1, 1)); + CPPUNIT_ASSERT(leaf.getValue(openvdb::Coord(1, 0, 0)) == openvdb::Vec3R(1, 1, 1)); + + CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestLeafOrigin.cc b/openvdb_2_3_0_library/openvdb/unittest/TestLeafOrigin.cc new file mode 100755 index 0000000..72406c4 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestLeafOrigin.cc @@ -0,0 +1,138 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + + +class TestLeafOrigin: public CppUnit::TestCase +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestLeafOrigin); + CPPUNIT_TEST(test); + CPPUNIT_TEST(test2Values); + CPPUNIT_TEST(testGetValue); + CPPUNIT_TEST_SUITE_END(); + + void test(); + void test2Values(); + void testGetValue(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafOrigin); + + +//////////////////////////////////////// + + +void +TestLeafOrigin::test() +{ + using namespace openvdb; + + std::set indices; + indices.insert(Coord( 0, 0, 0)); + indices.insert(Coord( 1, 0, 0)); + indices.insert(Coord( 0, 100, 8)); + indices.insert(Coord(-9, 0, 8)); + indices.insert(Coord(32, 0, 16)); + indices.insert(Coord(33, -5, 16)); + indices.insert(Coord(42, 17, 35)); + indices.insert(Coord(43, 17, 64)); + + FloatTree tree(/*bg=*/256.0); + std::set::iterator iter = indices.begin(); + for ( ; iter != indices.end(); ++iter) tree.setValue(*iter, 1.0); + + for (FloatTree::LeafCIter leafIter = tree.cbeginLeaf(); leafIter; ++leafIter) { + const Int32 mask = ~((1 << leafIter->log2dim()) - 1); + const Coord leafOrigin = leafIter->origin(); + for (FloatTree::LeafNodeType::ValueOnCIter valIter = leafIter->cbeginValueOn(); + valIter; ++valIter) + { + Coord xyz = valIter.getCoord(); + CPPUNIT_ASSERT_EQUAL(leafOrigin, xyz & mask); + + iter = indices.find(xyz); + CPPUNIT_ASSERT(iter != indices.end()); + indices.erase(iter); + } + } + CPPUNIT_ASSERT(indices.empty()); +} + + +void +TestLeafOrigin::test2Values() +{ + using namespace openvdb; + + FloatGrid::Ptr grid = createGrid(/*bg=*/1.0f); + FloatTree& tree = grid->tree(); + + tree.setValue(Coord(0, 0, 0), 5); + tree.setValue(Coord(100, 0, 0), 6); + + grid->setTransform(math::Transform::createLinearTransform(0.1)); + + FloatTree::LeafCIter iter = tree.cbeginLeaf(); + CPPUNIT_ASSERT_EQUAL(Coord(0, 0, 0), iter->origin()); + ++iter; + CPPUNIT_ASSERT_EQUAL(Coord(96, 0, 0), iter->origin()); +} + +void +TestLeafOrigin::testGetValue() +{ + const openvdb::Coord c0(0,-10,0), c1(100,13,0); + const float v0=5.0f, v1=6.0f, v2=1.0f; + openvdb::FloatTree::Ptr tree(new openvdb::FloatTree(v2)); + + tree->setValue(c0, v0); + tree->setValue(c1, v1); + + openvdb::FloatTree::LeafCIter iter = tree->cbeginLeaf(); + CPPUNIT_ASSERT_EQUAL(v0, iter->getValue(c0)); + ++iter; + CPPUNIT_ASSERT_EQUAL(v1, iter->getValue(c1)); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestLevelSetRayIntersector.cc b/openvdb_2_3_0_library/openvdb/unittest/TestLevelSetRayIntersector.cc new file mode 100755 index 0000000..ffe6dbd --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestLevelSetRayIntersector.cc @@ -0,0 +1,416 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +/// @author Ken Museth + +// Uncomment to enable statistics of ray-intersections +//#define STATS_TEST + +#include +#include +#include +#include +#include +#include +#include +#include +#include // for Film +#ifdef STATS_TEST +//only needed for statistics +#include +#include "util.h"//for CpuTimer +#include +#endif + + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + +#define ASSERT_DOUBLES_APPROX_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/1.e-6); + + +class TestLevelSetRayIntersector : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestLevelSetRayIntersector); + CPPUNIT_TEST(tests); + +#ifdef STATS_TEST + CPPUNIT_TEST(stats); +#endif + + CPPUNIT_TEST_SUITE_END(); + + void tests(); +#ifdef STATS_TEST + void stats(); +#endif +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestLevelSetRayIntersector); + +void +TestLevelSetRayIntersector::tests() +{ + using namespace openvdb; + typedef math::Ray RayT; + typedef RayT::Vec3Type Vec3T; + + {// voxel intersection against a level set sphere + const float r = 5.0f; + const Vec3f c(20.0f, 0.0f, 0.0f); + const float s = 0.5f, w = 2.0f; + + FloatGrid::Ptr ls = tools::createLevelSetSphere(r, c, s, w); + + tools::LevelSetRayIntersector lsri(*ls); + + const Vec3T dir(1.0, 0.0, 0.0); + const Vec3T eye(2.0, 0.0, 0.0); + const RayT ray(eye, dir); + //std::cerr << ray << std::endl; + Vec3T xyz(0); + 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("/tmp/sphere_serial"); + stats.print("First hit"); + hist.print("First hit"); + } +} +#endif // STATS_TEST + +#undef STATS_TEST + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestLevelSetUtil.cc b/openvdb_2_3_0_library/openvdb/unittest/TestLevelSetUtil.cc new file mode 100755 index 0000000..3d79f2b --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestLevelSetUtil.cc @@ -0,0 +1,102 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include +#include + +class TestLevelSetUtil: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestLevelSetUtil); + CPPUNIT_TEST(testMinMaxVoxel); + CPPUNIT_TEST(testLevelSetToFogVolume); + CPPUNIT_TEST_SUITE_END(); + + void testMinMaxVoxel(); + void testRelativeIsoOffset(); + void testLevelSetToFogVolume(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestLevelSetUtil); + + +//////////////////////////////////////// + + +void +TestLevelSetUtil::testMinMaxVoxel() +{ + + openvdb::FloatTree myTree(std::numeric_limits::max()); + + openvdb::tree::ValueAccessor acc(myTree); + + for (int i = -9; i < 10; ++i) { + for (int j = -9; j < 10; ++j) { + acc.setValue(openvdb::Coord(i,j,0), static_cast(j)); + } + } + + openvdb::tree::LeafManager leafs(myTree); + + openvdb::tools::MinMaxVoxel minmax(leafs); + minmax.runParallel(); + + CPPUNIT_ASSERT(!(minmax.minVoxel() < -9.0)); + CPPUNIT_ASSERT(!(minmax.maxVoxel() > 9.0)); +} + +void +TestLevelSetUtil::testLevelSetToFogVolume() +{ + openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(10.0); + + grid->fill(openvdb::CoordBBox(openvdb::Coord(-100), openvdb::Coord(100)), 9.0); + grid->fill(openvdb::CoordBBox(openvdb::Coord(-50), openvdb::Coord(50)), -9.0); + + + openvdb::tools::sdfToFogVolume(*grid); + + CPPUNIT_ASSERT(grid->background() < 1e-7); + + openvdb::FloatGrid::ValueOnIter iter = grid->beginValueOn(); + for (; iter; ++iter) { + CPPUNIT_ASSERT(iter.getValue() > 0.0); + CPPUNIT_ASSERT(std::abs(iter.getValue() - 1.0) < 1e-7); + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestLinearInterp.cc b/openvdb_2_3_0_library/openvdb/unittest/TestLinearInterp.cc new file mode 100755 index 0000000..d3fefe3 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestLinearInterp.cc @@ -0,0 +1,1003 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +// CPPUNIT_TEST_SUITE() invokes CPPUNIT_TESTNAMER_DECL() to generate a suite name +// from the FixtureType. But if FixtureType is a templated type, the generated name +// can become long and messy. This macro overrides the normal naming logic, +// instead invoking FixtureType::testSuiteName(), which should be a static member +// function that returns a std::string containing the suite name for the specific +// template instantiation. +#undef CPPUNIT_TESTNAMER_DECL +#define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ + CPPUNIT_NS::TestNamer variableName( FixtureType::testSuiteName() ) + +namespace { +// Absolute tolerance for floating-point equality comparisons +const double TOLERANCE = 1.e-6; +} + +template +class TestLinearInterp: public CppUnit::TestCase +{ +public: + static std::string testSuiteName() + { + std::string name = openvdb::typeNameAsString(); + if (!name.empty()) name[0] = ::toupper(name[0]); + return "TestLinearInterp" + name; + } + + CPPUNIT_TEST_SUITE(TestLinearInterp); + CPPUNIT_TEST(test); + CPPUNIT_TEST(testTree); + CPPUNIT_TEST(testAccessor); + CPPUNIT_TEST(testConstantValues); + CPPUNIT_TEST(testFillValues); + CPPUNIT_TEST(testNegativeIndices); + CPPUNIT_TEST_SUITE_END(); + + void test(); + void testTree(); + void testAccessor(); + void testConstantValues(); + void testFillValues(); + void testNegativeIndices(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestLinearInterp); +CPPUNIT_TEST_SUITE_REGISTRATION(TestLinearInterp); +CPPUNIT_TEST_SUITE_REGISTRATION(TestLinearInterp); + + +template +void +TestLinearInterp::test() +{ + typename GridType::TreeType TreeType; + float fillValue = 256.0f; + + GridType grid(fillValue); + typename GridType::TreeType& tree = grid.tree(); + + tree.setValue(openvdb::Coord(10, 10, 10), 1.0); + + tree.setValue(openvdb::Coord(11, 10, 10), 2.0); + tree.setValue(openvdb::Coord(11, 11, 10), 2.0); + tree.setValue(openvdb::Coord(10, 11, 10), 2.0); + tree.setValue(openvdb::Coord( 9, 11, 10), 2.0); + tree.setValue(openvdb::Coord( 9, 10, 10), 2.0); + tree.setValue(openvdb::Coord( 9, 9, 10), 2.0); + tree.setValue(openvdb::Coord(10, 9, 10), 2.0); + tree.setValue(openvdb::Coord(11, 9, 10), 2.0); + + tree.setValue(openvdb::Coord(10, 10, 11), 3.0); + tree.setValue(openvdb::Coord(11, 10, 11), 3.0); + tree.setValue(openvdb::Coord(11, 11, 11), 3.0); + tree.setValue(openvdb::Coord(10, 11, 11), 3.0); + tree.setValue(openvdb::Coord( 9, 11, 11), 3.0); + tree.setValue(openvdb::Coord( 9, 10, 11), 3.0); + tree.setValue(openvdb::Coord( 9, 9, 11), 3.0); + tree.setValue(openvdb::Coord(10, 9, 11), 3.0); + tree.setValue(openvdb::Coord(11, 9, 11), 3.0); + + tree.setValue(openvdb::Coord(10, 10, 9), 4.0); + tree.setValue(openvdb::Coord(11, 10, 9), 4.0); + tree.setValue(openvdb::Coord(11, 11, 9), 4.0); + tree.setValue(openvdb::Coord(10, 11, 9), 4.0); + tree.setValue(openvdb::Coord( 9, 11, 9), 4.0); + tree.setValue(openvdb::Coord( 9, 10, 9), 4.0); + tree.setValue(openvdb::Coord( 9, 9, 9), 4.0); + tree.setValue(openvdb::Coord(10, 9, 9), 4.0); + tree.setValue(openvdb::Coord(11, 9, 9), 4.0); + + // transform used for worldspace interpolation) + openvdb::tools::GridSampler + interpolator(grid); + //openvdb::tools::LinearInterp interpolator(*tree); + + typename GridType::ValueType val = + interpolator.sampleVoxel(10.5, 10.5, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.0, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(11.0, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(11.0, 11.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(11.0, 11.0, 11.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(9.0, 11.0, 9.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(9.0, 10.0, 9.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.1, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.8, 10.8, 10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.1, 10.8, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.8, 10.1, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.5, 10.1, 10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.5, 10.8, 10.1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); +} + + +template<> +void +TestLinearInterp::test() +{ + using namespace openvdb; + + Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); + + Vec3SGrid grid(fillValue); + Vec3STree& tree = grid.tree(); + + tree.setValue(openvdb::Coord(10, 10, 10), Vec3s(1.0, 1.0, 1.0)); + + tree.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); + + tree.setValue(openvdb::Coord(10, 10, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(11, 10, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(11, 11, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(10, 11, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord( 9, 11, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord( 9, 10, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord( 9, 9, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(10, 9, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(11, 9, 11), Vec3s(3.0, 3.0, 3.0)); + + tree.setValue(openvdb::Coord(10, 10, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(11, 10, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(11, 11, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(10, 11, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord( 9, 11, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord( 9, 10, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord( 9, 9, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(10, 9, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(11, 9, 9), Vec3s(4.0, 4.0, 4.0)); + + openvdb::tools::GridSampler + interpolator(grid); + + //openvdb::tools::LinearInterp interpolator(*tree); + + Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.375, 2.375, 2.375))); + + val = interpolator.sampleVoxel(10.0, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(1.0, 1.0, 1.0))); + + val = interpolator.sampleVoxel(11.0, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(11.0, 11.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(11.0, 11.0, 11.0); + CPPUNIT_ASSERT(val.eq(Vec3s(3.0, 3.0, 3.0))); + + val = interpolator.sampleVoxel(9.0, 11.0, 9.0); + CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); + + val = interpolator.sampleVoxel(9.0, 10.0, 9.0); + CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); + + val = interpolator.sampleVoxel(10.1, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(1.1, 1.1, 1.1))); + + val = interpolator.sampleVoxel(10.8, 10.8, 10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(2.792, 2.792, 2.792))); + + val = interpolator.sampleVoxel(10.1, 10.8, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); + + val = interpolator.sampleVoxel(10.8, 10.1, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); + + val = interpolator.sampleVoxel(10.5, 10.1, 10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(2.71, 2.71, 2.71))); + + val = interpolator.sampleVoxel(10.5, 10.8, 10.1); + CPPUNIT_ASSERT(val.eq(Vec3s(2.01, 2.01, 2.01))); +} + +template +void +TestLinearInterp::testTree() +{ + float fillValue = 256.0f; + typedef typename GridType::TreeType TreeType; + TreeType tree(fillValue); + + tree.setValue(openvdb::Coord(10, 10, 10), 1.0); + + tree.setValue(openvdb::Coord(11, 10, 10), 2.0); + tree.setValue(openvdb::Coord(11, 11, 10), 2.0); + tree.setValue(openvdb::Coord(10, 11, 10), 2.0); + tree.setValue(openvdb::Coord( 9, 11, 10), 2.0); + tree.setValue(openvdb::Coord( 9, 10, 10), 2.0); + tree.setValue(openvdb::Coord( 9, 9, 10), 2.0); + tree.setValue(openvdb::Coord(10, 9, 10), 2.0); + tree.setValue(openvdb::Coord(11, 9, 10), 2.0); + + tree.setValue(openvdb::Coord(10, 10, 11), 3.0); + tree.setValue(openvdb::Coord(11, 10, 11), 3.0); + tree.setValue(openvdb::Coord(11, 11, 11), 3.0); + tree.setValue(openvdb::Coord(10, 11, 11), 3.0); + tree.setValue(openvdb::Coord( 9, 11, 11), 3.0); + tree.setValue(openvdb::Coord( 9, 10, 11), 3.0); + tree.setValue(openvdb::Coord( 9, 9, 11), 3.0); + tree.setValue(openvdb::Coord(10, 9, 11), 3.0); + tree.setValue(openvdb::Coord(11, 9, 11), 3.0); + + tree.setValue(openvdb::Coord(10, 10, 9), 4.0); + tree.setValue(openvdb::Coord(11, 10, 9), 4.0); + tree.setValue(openvdb::Coord(11, 11, 9), 4.0); + tree.setValue(openvdb::Coord(10, 11, 9), 4.0); + tree.setValue(openvdb::Coord( 9, 11, 9), 4.0); + tree.setValue(openvdb::Coord( 9, 10, 9), 4.0); + tree.setValue(openvdb::Coord( 9, 9, 9), 4.0); + tree.setValue(openvdb::Coord(10, 9, 9), 4.0); + tree.setValue(openvdb::Coord(11, 9, 9), 4.0); + + // transform used for worldspace interpolation) + openvdb::tools::GridSampler + interpolator(tree, openvdb::math::Transform()); + + typename GridType::ValueType val = + interpolator.sampleVoxel(10.5, 10.5, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.0, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(11.0, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(11.0, 11.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(11.0, 11.0, 11.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(9.0, 11.0, 9.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(9.0, 10.0, 9.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.1, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.8, 10.8, 10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.1, 10.8, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.8, 10.1, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.5, 10.1, 10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.5, 10.8, 10.1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); +} + + +template<> +void +TestLinearInterp::testTree() +{ + using namespace openvdb; + + Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); + + Vec3STree tree(fillValue); + + tree.setValue(openvdb::Coord(10, 10, 10), Vec3s(1.0, 1.0, 1.0)); + + tree.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); + + tree.setValue(openvdb::Coord(10, 10, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(11, 10, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(11, 11, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(10, 11, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord( 9, 11, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord( 9, 10, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord( 9, 9, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(10, 9, 11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(11, 9, 11), Vec3s(3.0, 3.0, 3.0)); + + tree.setValue(openvdb::Coord(10, 10, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(11, 10, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(11, 11, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(10, 11, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord( 9, 11, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord( 9, 10, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord( 9, 9, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(10, 9, 9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(11, 9, 9), Vec3s(4.0, 4.0, 4.0)); + + openvdb::tools::GridSampler + interpolator(tree, openvdb::math::Transform()); + + //openvdb::tools::LinearInterp interpolator(*tree); + + Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.375, 2.375, 2.375))); + + val = interpolator.sampleVoxel(10.0, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(1.0, 1.0, 1.0))); + + val = interpolator.sampleVoxel(11.0, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(11.0, 11.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(11.0, 11.0, 11.0); + CPPUNIT_ASSERT(val.eq(Vec3s(3.0, 3.0, 3.0))); + + val = interpolator.sampleVoxel(9.0, 11.0, 9.0); + CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); + + val = interpolator.sampleVoxel(9.0, 10.0, 9.0); + CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); + + val = interpolator.sampleVoxel(10.1, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(1.1, 1.1, 1.1))); + + val = interpolator.sampleVoxel(10.8, 10.8, 10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(2.792, 2.792, 2.792))); + + val = interpolator.sampleVoxel(10.1, 10.8, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); + + val = interpolator.sampleVoxel(10.8, 10.1, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); + + val = interpolator.sampleVoxel(10.5, 10.1, 10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(2.71, 2.71, 2.71))); + + val = interpolator.sampleVoxel(10.5, 10.8, 10.1); + CPPUNIT_ASSERT(val.eq(Vec3s(2.01, 2.01, 2.01))); +} + +template +void +TestLinearInterp::testAccessor() +{ + float fillValue = 256.0f; + + GridType grid(fillValue); + typedef typename GridType::Accessor AccessorType; + + AccessorType acc = grid.getAccessor(); + + acc.setValue(openvdb::Coord(10, 10, 10), 1.0); + + acc.setValue(openvdb::Coord(11, 10, 10), 2.0); + acc.setValue(openvdb::Coord(11, 11, 10), 2.0); + acc.setValue(openvdb::Coord(10, 11, 10), 2.0); + acc.setValue(openvdb::Coord( 9, 11, 10), 2.0); + acc.setValue(openvdb::Coord( 9, 10, 10), 2.0); + acc.setValue(openvdb::Coord( 9, 9, 10), 2.0); + acc.setValue(openvdb::Coord(10, 9, 10), 2.0); + acc.setValue(openvdb::Coord(11, 9, 10), 2.0); + + acc.setValue(openvdb::Coord(10, 10, 11), 3.0); + acc.setValue(openvdb::Coord(11, 10, 11), 3.0); + acc.setValue(openvdb::Coord(11, 11, 11), 3.0); + acc.setValue(openvdb::Coord(10, 11, 11), 3.0); + acc.setValue(openvdb::Coord( 9, 11, 11), 3.0); + acc.setValue(openvdb::Coord( 9, 10, 11), 3.0); + acc.setValue(openvdb::Coord( 9, 9, 11), 3.0); + acc.setValue(openvdb::Coord(10, 9, 11), 3.0); + acc.setValue(openvdb::Coord(11, 9, 11), 3.0); + + acc.setValue(openvdb::Coord(10, 10, 9), 4.0); + acc.setValue(openvdb::Coord(11, 10, 9), 4.0); + acc.setValue(openvdb::Coord(11, 11, 9), 4.0); + acc.setValue(openvdb::Coord(10, 11, 9), 4.0); + acc.setValue(openvdb::Coord( 9, 11, 9), 4.0); + acc.setValue(openvdb::Coord( 9, 10, 9), 4.0); + acc.setValue(openvdb::Coord( 9, 9, 9), 4.0); + acc.setValue(openvdb::Coord(10, 9, 9), 4.0); + acc.setValue(openvdb::Coord(11, 9, 9), 4.0); + + // transform used for worldspace interpolation) + openvdb::tools::GridSampler + interpolator(acc, grid.transform()); + + typename GridType::ValueType val = + interpolator.sampleVoxel(10.5, 10.5, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.0, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(11.0, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(11.0, 11.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(11.0, 11.0, 11.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(9.0, 11.0, 9.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(9.0, 10.0, 9.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.1, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.8, 10.8, 10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.1, 10.8, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.8, 10.1, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.5, 10.1, 10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.5, 10.8, 10.1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); +} + + +template<> +void +TestLinearInterp::testAccessor() +{ + using namespace openvdb; + + Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); + + Vec3SGrid grid(fillValue); + typedef Vec3SGrid::Accessor AccessorType; + AccessorType acc = grid.getAccessor(); + + acc.setValue(openvdb::Coord(10, 10, 10), Vec3s(1.0, 1.0, 1.0)); + + acc.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); + acc.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); + acc.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); + acc.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); + acc.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); + acc.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); + acc.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); + acc.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); + + acc.setValue(openvdb::Coord(10, 10, 11), Vec3s(3.0, 3.0, 3.0)); + acc.setValue(openvdb::Coord(11, 10, 11), Vec3s(3.0, 3.0, 3.0)); + acc.setValue(openvdb::Coord(11, 11, 11), Vec3s(3.0, 3.0, 3.0)); + acc.setValue(openvdb::Coord(10, 11, 11), Vec3s(3.0, 3.0, 3.0)); + acc.setValue(openvdb::Coord( 9, 11, 11), Vec3s(3.0, 3.0, 3.0)); + acc.setValue(openvdb::Coord( 9, 10, 11), Vec3s(3.0, 3.0, 3.0)); + acc.setValue(openvdb::Coord( 9, 9, 11), Vec3s(3.0, 3.0, 3.0)); + acc.setValue(openvdb::Coord(10, 9, 11), Vec3s(3.0, 3.0, 3.0)); + acc.setValue(openvdb::Coord(11, 9, 11), Vec3s(3.0, 3.0, 3.0)); + + acc.setValue(openvdb::Coord(10, 10, 9), Vec3s(4.0, 4.0, 4.0)); + acc.setValue(openvdb::Coord(11, 10, 9), Vec3s(4.0, 4.0, 4.0)); + acc.setValue(openvdb::Coord(11, 11, 9), Vec3s(4.0, 4.0, 4.0)); + acc.setValue(openvdb::Coord(10, 11, 9), Vec3s(4.0, 4.0, 4.0)); + acc.setValue(openvdb::Coord( 9, 11, 9), Vec3s(4.0, 4.0, 4.0)); + acc.setValue(openvdb::Coord( 9, 10, 9), Vec3s(4.0, 4.0, 4.0)); + acc.setValue(openvdb::Coord( 9, 9, 9), Vec3s(4.0, 4.0, 4.0)); + acc.setValue(openvdb::Coord(10, 9, 9), Vec3s(4.0, 4.0, 4.0)); + acc.setValue(openvdb::Coord(11, 9, 9), Vec3s(4.0, 4.0, 4.0)); + + openvdb::tools::GridSampler + interpolator(acc, grid.transform()); + + Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.375, 2.375, 2.375))); + + val = interpolator.sampleVoxel(10.0, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(1.0, 1.0, 1.0))); + + val = interpolator.sampleVoxel(11.0, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(11.0, 11.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(11.0, 11.0, 11.0); + CPPUNIT_ASSERT(val.eq(Vec3s(3.0, 3.0, 3.0))); + + val = interpolator.sampleVoxel(9.0, 11.0, 9.0); + CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); + + val = interpolator.sampleVoxel(9.0, 10.0, 9.0); + CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); + + val = interpolator.sampleVoxel(10.1, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(1.1, 1.1, 1.1))); + + val = interpolator.sampleVoxel(10.8, 10.8, 10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(2.792, 2.792, 2.792))); + + val = interpolator.sampleVoxel(10.1, 10.8, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); + + val = interpolator.sampleVoxel(10.8, 10.1, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); + + val = interpolator.sampleVoxel(10.5, 10.1, 10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(2.71, 2.71, 2.71))); + + val = interpolator.sampleVoxel(10.5, 10.8, 10.1); + CPPUNIT_ASSERT(val.eq(Vec3s(2.01, 2.01, 2.01))); +} + +template +void +TestLinearInterp::testConstantValues() +{ + typedef typename GridType::TreeType TreeType; + float fillValue = 256.0f; + + GridType grid(fillValue); + TreeType& tree = grid.tree(); + + // Add values to buffer zero. + tree.setValue(openvdb::Coord(10, 10, 10), 2.0); + + tree.setValue(openvdb::Coord(11, 10, 10), 2.0); + tree.setValue(openvdb::Coord(11, 11, 10), 2.0); + tree.setValue(openvdb::Coord(10, 11, 10), 2.0); + tree.setValue(openvdb::Coord( 9, 11, 10), 2.0); + tree.setValue(openvdb::Coord( 9, 10, 10), 2.0); + tree.setValue(openvdb::Coord( 9, 9, 10), 2.0); + tree.setValue(openvdb::Coord(10, 9, 10), 2.0); + tree.setValue(openvdb::Coord(11, 9, 10), 2.0); + + tree.setValue(openvdb::Coord(10, 10, 11), 2.0); + tree.setValue(openvdb::Coord(11, 10, 11), 2.0); + tree.setValue(openvdb::Coord(11, 11, 11), 2.0); + tree.setValue(openvdb::Coord(10, 11, 11), 2.0); + tree.setValue(openvdb::Coord( 9, 11, 11), 2.0); + tree.setValue(openvdb::Coord( 9, 10, 11), 2.0); + tree.setValue(openvdb::Coord( 9, 9, 11), 2.0); + tree.setValue(openvdb::Coord(10, 9, 11), 2.0); + tree.setValue(openvdb::Coord(11, 9, 11), 2.0); + + tree.setValue(openvdb::Coord(10, 10, 9), 2.0); + tree.setValue(openvdb::Coord(11, 10, 9), 2.0); + tree.setValue(openvdb::Coord(11, 11, 9), 2.0); + tree.setValue(openvdb::Coord(10, 11, 9), 2.0); + tree.setValue(openvdb::Coord( 9, 11, 9), 2.0); + tree.setValue(openvdb::Coord( 9, 10, 9), 2.0); + tree.setValue(openvdb::Coord( 9, 9, 9), 2.0); + tree.setValue(openvdb::Coord(10, 9, 9), 2.0); + tree.setValue(openvdb::Coord(11, 9, 9), 2.0); + + openvdb::tools::GridSampler interpolator(grid); + //openvdb::tools::LinearInterp interpolator(*tree); + + typename GridType::ValueType val = + interpolator.sampleVoxel(10.5, 10.5, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.0, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.1, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.8, 10.8, 10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.1, 10.8, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.8, 10.1, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.5, 10.1, 10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.5, 10.8, 10.1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); +} + + +template<> +void +TestLinearInterp::testConstantValues() +{ + using namespace openvdb; + + Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); + + Vec3SGrid grid(fillValue); + Vec3STree& tree = grid.tree(); + + // Add values to buffer zero. + tree.setValue(openvdb::Coord(10, 10, 10), Vec3s(2.0, 2.0, 2.0)); + + tree.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); + + tree.setValue(openvdb::Coord(10, 10, 11), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 10, 11), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 11, 11), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(10, 11, 11), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 11, 11), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 10, 11), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 9, 11), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(10, 9, 11), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 9, 11), Vec3s(2.0, 2.0, 2.0)); + + tree.setValue(openvdb::Coord(10, 10, 9), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 10, 9), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 11, 9), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(10, 11, 9), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 11, 9), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 10, 9), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( 9, 9, 9), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(10, 9, 9), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(11, 9, 9), Vec3s(2.0, 2.0, 2.0)); + + openvdb::tools::GridSampler interpolator(grid); + //openvdb::tools::LinearInterp interpolator(*tree); + + Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(10.0, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(10.1, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(10.8, 10.8, 10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(10.1, 10.8, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(10.8, 10.1, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(10.5, 10.1, 10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(10.5, 10.8, 10.1); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); +} + + +template +void +TestLinearInterp::testFillValues() +{ + typedef typename GridType::TreeType TreeType; + float fillValue = 256.0f; + + GridType grid(fillValue); + //typename GridType::TreeType& tree = grid.tree(); + + openvdb::tools::GridSampler + interpolator(grid); + //openvdb::tools::LinearInterp interpolator(*tree); + + typename GridType::ValueType val = + interpolator.sampleVoxel(10.5, 10.5, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.0, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.1, 10.0, 10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.8, 10.8, 10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.1, 10.8, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.8, 10.1, 10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.5, 10.1, 10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(10.5, 10.8, 10.1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); +} + + +template<> +void +TestLinearInterp::testFillValues() +{ + using namespace openvdb; + + Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); + + Vec3SGrid grid(fillValue); + //Vec3STree& tree = grid.tree(); + + openvdb::tools::GridSampler + interpolator(grid); + //openvdb::tools::LinearInterp interpolator(*tree); + + Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); + + val = interpolator.sampleVoxel(10.0, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); + + val = interpolator.sampleVoxel(10.1, 10.0, 10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); + + val = interpolator.sampleVoxel(10.8, 10.8, 10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); + + val = interpolator.sampleVoxel(10.1, 10.8, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); + + val = interpolator.sampleVoxel(10.8, 10.1, 10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); + + val = interpolator.sampleVoxel(10.5, 10.1, 10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); + + val = interpolator.sampleVoxel(10.5, 10.8, 10.1); + CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); +} + + +template +void +TestLinearInterp::testNegativeIndices() +{ + typedef typename GridType::TreeType TreeType; + float fillValue = 256.0f; + + GridType grid(fillValue); + TreeType& tree = grid.tree(); + + tree.setValue(openvdb::Coord(-10, -10, -10), 1.0); + + tree.setValue(openvdb::Coord(-11, -10, -10), 2.0); + tree.setValue(openvdb::Coord(-11, -11, -10), 2.0); + tree.setValue(openvdb::Coord(-10, -11, -10), 2.0); + tree.setValue(openvdb::Coord( -9, -11, -10), 2.0); + tree.setValue(openvdb::Coord( -9, -10, -10), 2.0); + tree.setValue(openvdb::Coord( -9, -9, -10), 2.0); + tree.setValue(openvdb::Coord(-10, -9, -10), 2.0); + tree.setValue(openvdb::Coord(-11, -9, -10), 2.0); + + tree.setValue(openvdb::Coord(-10, -10, -11), 3.0); + tree.setValue(openvdb::Coord(-11, -10, -11), 3.0); + tree.setValue(openvdb::Coord(-11, -11, -11), 3.0); + tree.setValue(openvdb::Coord(-10, -11, -11), 3.0); + tree.setValue(openvdb::Coord( -9, -11, -11), 3.0); + tree.setValue(openvdb::Coord( -9, -10, -11), 3.0); + tree.setValue(openvdb::Coord( -9, -9, -11), 3.0); + tree.setValue(openvdb::Coord(-10, -9, -11), 3.0); + tree.setValue(openvdb::Coord(-11, -9, -11), 3.0); + + tree.setValue(openvdb::Coord(-10, -10, -9), 4.0); + tree.setValue(openvdb::Coord(-11, -10, -9), 4.0); + tree.setValue(openvdb::Coord(-11, -11, -9), 4.0); + tree.setValue(openvdb::Coord(-10, -11, -9), 4.0); + tree.setValue(openvdb::Coord( -9, -11, -9), 4.0); + tree.setValue(openvdb::Coord( -9, -10, -9), 4.0); + tree.setValue(openvdb::Coord( -9, -9, -9), 4.0); + tree.setValue(openvdb::Coord(-10, -9, -9), 4.0); + tree.setValue(openvdb::Coord(-11, -9, -9), 4.0); + + //openvdb::tools::LinearInterp interpolator(*tree); + openvdb::tools::GridSampler interpolator(grid); + + typename GridType::ValueType val = + interpolator.sampleVoxel(-10.5, -10.5, -10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); + + val = interpolator.sampleVoxel(-10.0, -10.0, -10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(-11.0, -10.0, -10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(-11.0, -11.0, -10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(-11.0, -11.0, -11.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(-9.0, -11.0, -9.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(-9.0, -10.0, -9.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); + + val = interpolator.sampleVoxel(-10.1, -10.0, -10.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); + + val = interpolator.sampleVoxel(-10.8, -10.8, -10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); + + val = interpolator.sampleVoxel(-10.1, -10.8, -10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); + + val = interpolator.sampleVoxel(-10.8, -10.1, -10.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); + + val = interpolator.sampleVoxel(-10.5, -10.1, -10.8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); + + val = interpolator.sampleVoxel(-10.5, -10.8, -10.1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); +} + + +template<> +void +TestLinearInterp::testNegativeIndices() +{ + using namespace openvdb; + + Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); + + Vec3SGrid grid(fillValue); + Vec3STree& tree = grid.tree(); + + tree.setValue(openvdb::Coord(-10, -10, -10), Vec3s(1.0, 1.0, 1.0)); + + tree.setValue(openvdb::Coord(-11, -10, -10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(-11, -11, -10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(-10, -11, -10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( -9, -11, -10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( -9, -10, -10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord( -9, -9, -10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(-10, -9, -10), Vec3s(2.0, 2.0, 2.0)); + tree.setValue(openvdb::Coord(-11, -9, -10), Vec3s(2.0, 2.0, 2.0)); + + tree.setValue(openvdb::Coord(-10, -10, -11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(-11, -10, -11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(-11, -11, -11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(-10, -11, -11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord( -9, -11, -11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord( -9, -10, -11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord( -9, -9, -11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(-10, -9, -11), Vec3s(3.0, 3.0, 3.0)); + tree.setValue(openvdb::Coord(-11, -9, -11), Vec3s(3.0, 3.0, 3.0)); + + tree.setValue(openvdb::Coord(-10, -10, -9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(-11, -10, -9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(-11, -11, -9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(-10, -11, -9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord( -9, -11, -9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord( -9, -10, -9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord( -9, -9, -9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(-10, -9, -9), Vec3s(4.0, 4.0, 4.0)); + tree.setValue(openvdb::Coord(-11, -9, -9), Vec3s(4.0, 4.0, 4.0)); + + openvdb::tools::GridSampler interpolator(grid); + //openvdb::tools::LinearInterp interpolator(*tree); + + Vec3SGrid::ValueType val = interpolator.sampleVoxel(-10.5, -10.5, -10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.375, 2.375, 2.375))); + + val = interpolator.sampleVoxel(-10.0, -10.0, -10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(1.0, 1.0, 1.0))); + + val = interpolator.sampleVoxel(-11.0, -10.0, -10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(-11.0, -11.0, -10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); + + val = interpolator.sampleVoxel(-11.0, -11.0, -11.0); + CPPUNIT_ASSERT(val.eq(Vec3s(3.0, 3.0, 3.0))); + + val = interpolator.sampleVoxel(-9.0, -11.0, -9.0); + CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); + + val = interpolator.sampleVoxel(-9.0, -10.0, -9.0); + CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); + + val = interpolator.sampleVoxel(-10.1, -10.0, -10.0); + CPPUNIT_ASSERT(val.eq(Vec3s(1.1, 1.1, 1.1))); + + val = interpolator.sampleVoxel(-10.8, -10.8, -10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(2.792, 2.792, 2.792))); + + val = interpolator.sampleVoxel(-10.1, -10.8, -10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); + + val = interpolator.sampleVoxel(-10.8, -10.1, -10.5); + CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); + + val = interpolator.sampleVoxel(-10.5, -10.1, -10.8); + CPPUNIT_ASSERT(val.eq(Vec3s(2.71, 2.71, 2.71))); + + val = interpolator.sampleVoxel(-10.5, -10.8, -10.1); + CPPUNIT_ASSERT(val.eq(Vec3s(2.01, 2.01, 2.01))); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestMaps.cc b/openvdb_2_3_0_library/openvdb/unittest/TestMaps.cc new file mode 100755 index 0000000..016211d --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestMaps.cc @@ -0,0 +1,834 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 == singularInv ); + + } + { + Mat4d m = Mat4d::identity(); + m[0][1] = 1; + + // should give true inverse, since this matrix has det=1 + Mat4d minv = approxInverse(m); + + Mat4d prod = m * minv; + CPPUNIT_ASSERT( prod.eq( Mat4d::identity() ) ); + } + { + Mat4d m = Mat4d::identity(); + m[0][1] = 1; + m[1][1] = 0; + // should give true inverse, since this matrix has det=1 + Mat4d minv = approxInverse(m); + + Mat4d expected = Mat4d::zero(); + expected[3][3] = 1; + CPPUNIT_ASSERT( minv.eq(expected ) ); + } + + +} + + +void +TestMaps::testUniformScale() +{ + using namespace openvdb::math; + + AffineMap map; + + CPPUNIT_ASSERT(map.hasUniformScale()); + + // Apply uniform scale: should still have square voxels + map.accumPreScale(Vec3d(2, 2, 2)); + + CPPUNIT_ASSERT(map.hasUniformScale()); + + // Apply a rotation, should still have squaure voxels. + map.accumPostRotation(X_AXIS, 2.5); + + CPPUNIT_ASSERT(map.hasUniformScale()); + + // non uniform scaling will stretch the voxels + map.accumPostScale(Vec3d(1, 3, 1) ); + + CPPUNIT_ASSERT(!map.hasUniformScale()); +} + +void +TestMaps::testTranslation() +{ + using namespace openvdb::math; + + double TOL = 1e-7; + + TranslationMap::Ptr translation(new TranslationMap(Vec3d(1,1,1))); + CPPUNIT_ASSERT(is_linear::value); + + TranslationMap another_translation(Vec3d(1,1,1)); + CPPUNIT_ASSERT(another_translation == *translation); + + TranslationMap::Ptr translate_by_two(new TranslationMap(Vec3d(2,2,2))); + + CPPUNIT_ASSERT(*translate_by_two != *translation); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(translate_by_two->determinant(), 1, TOL); + + CPPUNIT_ASSERT(translate_by_two->hasUniformScale()); + + /// apply the map forward + Vec3d unit(1,0,0); + Vec3d result = translate_by_two->applyMap(unit); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), 3, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), 2, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), 2, TOL); + + /// invert the map + result = translate_by_two->applyInverseMap(result); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), 1, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), 0, TOL); + + /// Inverse Jacobian Transpose + result = translate_by_two->applyIJT(result); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), 1, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), 0, TOL); + + /// Jacobian Transpose + result = translate_by_two->applyJT(translate_by_two->applyIJT(unit)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); + + + MapBase::Ptr inverse = translation->inverseMap(); + CPPUNIT_ASSERT(inverse->type() == TranslationMap::mapType()); + // apply the map forward and the inverse map back + result = inverse->applyMap(translation->applyMap(unit)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), 1, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), 0, TOL); + + +} + +void +TestMaps::testScaleDefault() +{ + using namespace openvdb::math; + + double TOL = 1e-7; + + // testing default constructor + // should be the identity + ScaleMap::Ptr scale(new ScaleMap()); + Vec3d unit(1, 1, 1); + + Vec3d result = scale->applyMap(unit); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(0), result(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(1), result(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(2), result(2), TOL); + + result = scale->applyInverseMap(unit); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(0), result(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(1), result(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(2), result(2), TOL); + + + MapBase::Ptr inverse = scale->inverseMap(); + CPPUNIT_ASSERT(inverse->type() == ScaleMap::mapType()); + // apply the map forward and the inverse map back + result = inverse->applyMap(scale->applyMap(unit)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); + + +} + +void +TestMaps::testRotation() +{ + using namespace openvdb::math; + + double TOL = 1e-7; + + double pi = 4.*atan(1.); + UnitaryMap::Ptr rotation(new UnitaryMap(Vec3d(1,0,0), pi/2)); + + CPPUNIT_ASSERT(is_linear::value); + + UnitaryMap another_rotation(Vec3d(1,0,0), pi/2.); + CPPUNIT_ASSERT(another_rotation == *rotation); + + UnitaryMap::Ptr rotation_two(new UnitaryMap(Vec3d(1,0,0), pi/4.)); + + CPPUNIT_ASSERT(*rotation_two != *rotation); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(rotation->determinant(), 1, TOL); + + CPPUNIT_ASSERT(rotation_two->hasUniformScale()); + + /// apply the map forward + Vec3d unit(0,1,0); + Vec3d result = rotation->applyMap(unit); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(2), TOL); + + /// invert the map + result = rotation->applyInverseMap(result); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(2), TOL); + + /// Inverse Jacobian Transpose + result = rotation_two->applyIJT(result); // rotate backwards + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(sqrt(2.)/2, result(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(sqrt(2.)/2, result(2), TOL); + + /// Jacobian Transpose + result = rotation_two->applyJT(rotation_two->applyIJT(unit)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); + + + // Test inverse map + MapBase::Ptr inverse = rotation->inverseMap(); + CPPUNIT_ASSERT(inverse->type() == UnitaryMap::mapType()); + // apply the map forward and the inverse map back + result = inverse->applyMap(rotation->applyMap(unit)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); +} + + +void +TestMaps::testScaleTranslate() +{ + using namespace openvdb::math; + + double TOL = 1e-7; + + CPPUNIT_ASSERT(is_linear::value); + + TranslationMap::Ptr translation(new TranslationMap(Vec3d(1,1,1))); + ScaleMap::Ptr scale(new ScaleMap(Vec3d(1,2,3))); + + ScaleTranslateMap::Ptr scaleAndTranslate( + new ScaleTranslateMap(*scale, *translation)); + + TranslationMap translate_by_two(Vec3d(2,2,2)); + ScaleTranslateMap another_scaleAndTranslate(*scale, translate_by_two); + + CPPUNIT_ASSERT(another_scaleAndTranslate != *scaleAndTranslate); + + CPPUNIT_ASSERT(!scaleAndTranslate->hasUniformScale()); + //CPPUNIT_ASSERT_DOUBLES_EQUAL(scaleAndTranslate->determinant(), 6, TOL); + + /// apply the map forward + Vec3d unit(1,0,0); + Vec3d result = scaleAndTranslate->applyMap(unit); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2, result(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(2), TOL); + + /// invert the map + result = scaleAndTranslate->applyInverseMap(result); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(2), TOL); + + /// Inverse Jacobian Transpose + result = Vec3d(0,2,0); + result = scaleAndTranslate->applyIJT(result ); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(2), TOL); + + + /// Jacobian Transpose + result = scaleAndTranslate->applyJT(scaleAndTranslate->applyIJT(unit)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); + + + // Test inverse map + MapBase::Ptr inverse = scaleAndTranslate->inverseMap(); + CPPUNIT_ASSERT(inverse->type() == ScaleTranslateMap::mapType()); + // apply the map forward and the inverse map back + result = inverse->applyMap(scaleAndTranslate->applyMap(unit)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); + +} + + +void +TestMaps::testUniformScaleTranslate() +{ + using namespace openvdb::math; + + double TOL = 1e-7; + + CPPUNIT_ASSERT(is_linear::value); + CPPUNIT_ASSERT(is_linear::value); + + TranslationMap::Ptr translation(new TranslationMap(Vec3d(1,1,1))); + UniformScaleMap::Ptr scale(new UniformScaleMap(2)); + + UniformScaleTranslateMap::Ptr scaleAndTranslate( + new UniformScaleTranslateMap(*scale, *translation)); + + TranslationMap translate_by_two(Vec3d(2,2,2)); + UniformScaleTranslateMap another_scaleAndTranslate(*scale, translate_by_two); + + CPPUNIT_ASSERT(another_scaleAndTranslate != *scaleAndTranslate); + CPPUNIT_ASSERT(scaleAndTranslate->hasUniformScale()); + //CPPUNIT_ASSERT_DOUBLES_EQUAL(scaleAndTranslate->determinant(), 6, TOL); + + /// apply the map forward + Vec3d unit(1,0,0); + Vec3d result = scaleAndTranslate->applyMap(unit); + CPPUNIT_ASSERT_DOUBLES_EQUAL(3, result(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(2), TOL); + + /// invert the map + result = scaleAndTranslate->applyInverseMap(result); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(2), TOL); + + /// Inverse Jacobian Transpose + result = Vec3d(0,2,0); + result = scaleAndTranslate->applyIJT(result ); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(2), TOL); + + + /// Jacobian Transpose + result = scaleAndTranslate->applyJT(scaleAndTranslate->applyIJT(unit)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); + + + + // Test inverse map + MapBase::Ptr inverse = scaleAndTranslate->inverseMap(); + CPPUNIT_ASSERT(inverse->type() == UniformScaleTranslateMap::mapType()); + // apply the map forward and the inverse map back + result = inverse->applyMap(scaleAndTranslate->applyMap(unit)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); + +} + + +void +TestMaps::testDecomposition() +{ + using namespace openvdb::math; + + //double TOL = 1e-7; + + CPPUNIT_ASSERT(is_linear::value); + CPPUNIT_ASSERT(is_linear::value); + CPPUNIT_ASSERT(is_linear::value); + CPPUNIT_ASSERT(is_linear::value); + + Mat4d matrix(Mat4d::identity()); + Vec3d input_translation(0,0,1); + matrix.setTranslation(input_translation); + + + matrix(0,0) = 1.8930039; + matrix(1,0) = -0.120080537; + matrix(2,0) = -0.497615212; + + matrix(0,1) = -0.120080537; + matrix(1,1) = 2.643265436; + matrix(2,1) = 0.6176957495; + + matrix(0,2) = -0.497615212; + matrix(1,2) = 0.6176957495; + matrix(2,2) = 1.4637305884; + + FullyDecomposedMap::Ptr decomp = createFullyDecomposedMap(matrix); + + /// the singular values + const Vec3& singular_values = + decomp->firstMap().firstMap().secondMap().getScale(); + /// expected values + Vec3d expected_values(2, 3, 1); + + CPPUNIT_ASSERT( isApproxEqual(singular_values, expected_values) ); + + const Vec3& the_translation = decomp->secondMap().secondMap().getTranslation(); + CPPUNIT_ASSERT( isApproxEqual(the_translation, input_translation)); +} + + +void +TestMaps::testFrustum() +{ + using namespace openvdb::math; + + openvdb::BBoxd bbox(Vec3d(0), Vec3d(100)); + NonlinearFrustumMap frustum(bbox, 1./6., 5); + /// frustum will have depth, far plane - near plane = 5 + /// the frustum has width 1 in the front and 6 in the back + + Vec3d trans(2,2,2); + NonlinearFrustumMap::Ptr map = + boost::static_pointer_cast( + frustum.preScale(Vec3d(10,10,10))->postTranslate(trans)); + + CPPUNIT_ASSERT(!map->hasUniformScale()); + + Vec3d result; + result = map->voxelSize(); + + CPPUNIT_ASSERT( isApproxEqual(result.x(), 0.1)); + CPPUNIT_ASSERT( isApproxEqual(result.y(), 0.1)); + CPPUNIT_ASSERT( isApproxEqual(result.z(), 0.5, 0.0001)); + //--------- Front face + Vec3d corner(0,0,0); + result = map->applyMap(corner); + CPPUNIT_ASSERT(isApproxEqual(result, Vec3d(-5, -5, 0) + trans)); + + corner = Vec3d(100,0,0); + result = map->applyMap(corner); + CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(5, -5, 0) + trans)); + + corner = Vec3d(0,100,0); + result = map->applyMap(corner); + CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(-5, 5, 0) + trans)); + + corner = Vec3d(100,100,0); + result = map->applyMap(corner); + CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(5, 5, 0) + trans)); + + //--------- Back face + corner = Vec3d(0,0,100); + result = map->applyMap(corner); + CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(-30, -30, 50) + trans)); // 10*(5/2 + 1/2) = 30 + + corner = Vec3d(100,0,100); + result = map->applyMap(corner); + CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(30, -30, 50) + trans)); + + corner = Vec3d(0,100,100); + result = map->applyMap(corner); + CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(-30, 30, 50) + trans)); + + corner = Vec3d(100,100,100); + result = map->applyMap(corner); + CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(30, 30, 50) + trans)); + + + // invert a single corner + result = map->applyInverseMap(Vec3d(30,30,50) + trans); + CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(100, 100, 100))); + + CPPUNIT_ASSERT(map->hasSimpleAffine()); + + /// create a frustum from from camera type information + + // the location of the camera + Vec3d position(100,10,1); + // the direction the camera is pointing + Vec3d direction(0,1,1); + direction.normalize(); + + // the up-direction for the camera + Vec3d up(10,3,-3); + + // distance from camera to near-plane measured in the direction 'direction' + double z_near = 100.; + // depth of frustum to far-plane to near-plane + double depth = 500.; + //aspect ratio of frustum: width/height + double aspect = 2; + + // voxel count in frustum. the y_count = x_count / aspect + Coord::ValueType x_count = 500; + Coord::ValueType z_count = 5000; + + + NonlinearFrustumMap frustumMap_from_camera( + position, direction, up, aspect, z_near, depth, x_count, z_count); + Vec3d center; + // find the center of the near plane and make sure it is in the correct place + center = Vec3d(0,0,0); + center += frustumMap_from_camera.applyMap(Vec3d(0,0,0)); + center += frustumMap_from_camera.applyMap(Vec3d(500,0,0)); + center += frustumMap_from_camera.applyMap(Vec3d(0,250,0)); + center += frustumMap_from_camera.applyMap(Vec3d(500,250,0)); + center = center /4.; + CPPUNIT_ASSERT( isApproxEqual(center, position + z_near * direction)); + // find the center of the far plane and make sure it is in the correct place + center = Vec3d(0,0,0); + center += frustumMap_from_camera.applyMap(Vec3d( 0, 0,5000)); + center += frustumMap_from_camera.applyMap(Vec3d(500, 0,5000)); + center += frustumMap_from_camera.applyMap(Vec3d( 0,250,5000)); + center += frustumMap_from_camera.applyMap(Vec3d(500,250,5000)); + center = center /4.; + CPPUNIT_ASSERT( isApproxEqual(center, position + (z_near+depth) * direction)); + // check that the frustum has the correct heigh on the near plane + Vec3d corner1 = frustumMap_from_camera.applyMap(Vec3d(0,0,0)); + Vec3d corner2 = frustumMap_from_camera.applyMap(Vec3d(0,250,0)); + Vec3d side = corner2-corner1; + CPPUNIT_ASSERT( isApproxEqual( side.length(), 2 * up.length())); + // check that the frustum is correctly oriented w.r.t up + side.normalize(); + CPPUNIT_ASSERT( isApproxEqual( side * (up.length()), up)); + // check that the linear map inside the frustum is a simple affine map (i.e. has no shear) + CPPUNIT_ASSERT(frustumMap_from_camera.hasSimpleAffine()); +} + + +void +TestMaps::testCalcBoundingBox() +{ + using namespace openvdb::math; + + openvdb::BBoxd world_bbox(Vec3d(0,0,0), Vec3d(1,1,1)); + openvdb::BBoxd voxel_bbox; + openvdb::BBoxd expected; + { + AffineMap affine; + affine.accumPreScale(Vec3d(2,2,2)); + + openvdb::util::calculateBounds(affine, world_bbox, voxel_bbox); + + expected = openvdb::BBoxd(Vec3d(0,0,0), Vec3d(0.5, 0.5, 0.5)); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); + + affine.accumPostTranslation(Vec3d(1,1,1)); + openvdb::util::calculateBounds(affine, world_bbox, voxel_bbox); + expected = openvdb::BBoxd(Vec3d(-0.5,-0.5,-0.5), Vec3d(0, 0, 0)); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); + } + { + AffineMap affine; + affine.accumPreScale(Vec3d(2,2,2)); + affine.accumPostTranslation(Vec3d(1,1,1)); + // test a sphere: + Vec3d center(0,0,0); + double radius = 10; + + openvdb::util::calculateBounds(affine, center, radius, voxel_bbox); + expected = openvdb::BBoxd(Vec3d(-5.5,-5.5,-5.5), Vec3d(4.5, 4.5, 4.5)); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); + } + { + AffineMap affine; + affine.accumPreScale(Vec3d(2,2,2)); + double pi = 4.*atan(1.); + affine.accumPreRotation(X_AXIS, pi/4.); + Vec3d center(0,0,0); + double radius = 10; + + openvdb::util::calculateBounds(affine, center, radius, voxel_bbox); + expected = openvdb::BBoxd(Vec3d(-5,-5,-5), Vec3d(5, 5, 5)); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); + } + { + AffineMap affine; + affine.accumPreScale(Vec3d(2,1,1)); + double pi = 4.*atan(1.); + affine.accumPreRotation(X_AXIS, pi/4.); + Vec3d center(0,0,0); + double radius = 10; + + openvdb::util::calculateBounds(affine, center, radius, voxel_bbox); + expected = openvdb::BBoxd(Vec3d(-5,-10,-10), Vec3d(5, 10, 10)); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); + } + { + AffineMap affine; + affine.accumPreScale(Vec3d(2,1,1)); + double pi = 4.*atan(1.); + affine.accumPreRotation(X_AXIS, pi/4.); + affine.accumPostTranslation(Vec3d(1,1,1)); + Vec3d center(1,1,1); + double radius = 10; + + openvdb::util::calculateBounds(affine, center, radius, voxel_bbox); + expected = openvdb::BBoxd(Vec3d(-5,-10,-10), Vec3d(5, 10, 10)); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); + CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); + } + { + openvdb::BBoxd bbox(Vec3d(0), Vec3d(100)); + NonlinearFrustumMap frustum(bbox, 2, 5); + NonlinearFrustumMap::Ptr map = + boost::static_pointer_cast( + frustum.preScale(Vec3d(2,2,2))); + Vec3d center(20,20,10); + double radius(1); + + openvdb::util::calculateBounds(*map, center, radius, voxel_bbox); + } +} +void +TestMaps::testJacobians() +{ + using namespace openvdb::math; + const double TOL = 1e-7; + { + AffineMap affine; + + const int n = 10; + const double dtheta = M_PI / n; + + const Vec3d test(1,2,3); + const Vec3d origin(0,0,0); + + for (int i = 0; i < n; ++i) { + double theta = i * dtheta; + + affine.accumPostRotation(X_AXIS, theta); + + Vec3d result = affine.applyJacobian(test); + Vec3d expected = affine.applyMap(test) - affine.applyMap(origin); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), expected(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), expected(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), expected(2), TOL); + + Vec3d tmp = affine.applyInverseJacobian(result); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); + } + } + + { + UniformScaleMap scale(3); + const Vec3d test(1,2,3); + const Vec3d origin(0,0,0); + + + Vec3d result = scale.applyJacobian(test); + Vec3d expected = scale.applyMap(test) - scale.applyMap(origin); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), expected(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), expected(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), expected(2), TOL); + + Vec3d tmp = scale.applyInverseJacobian(result); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); + } + + { + ScaleMap scale(Vec3d(1,2,3)); + const Vec3d test(1,2,3); + const Vec3d origin(0,0,0); + + + Vec3d result = scale.applyJacobian(test); + Vec3d expected = scale.applyMap(test) - scale.applyMap(origin); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), expected(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), expected(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), expected(2), TOL); + + Vec3d tmp = scale.applyInverseJacobian(result); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); + } + { + TranslationMap map(Vec3d(1,2,3)); + const Vec3d test(1,2,3); + const Vec3d origin(0,0,0); + + + Vec3d result = map.applyJacobian(test); + Vec3d expected = map.applyMap(test) - map.applyMap(origin); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), expected(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), expected(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), expected(2), TOL); + + Vec3d tmp = map.applyInverseJacobian(result); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); + } + { + ScaleTranslateMap map(Vec3d(1,2,3), Vec3d(3,5,4)); + const Vec3d test(1,2,3); + const Vec3d origin(0,0,0); + + + Vec3d result = map.applyJacobian(test); + Vec3d expected = map.applyMap(test) - map.applyMap(origin); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), expected(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), expected(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), expected(2), TOL); + + Vec3d tmp = map.applyInverseJacobian(result); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); + } + { + + openvdb::BBoxd bbox(Vec3d(0), Vec3d(100)); + NonlinearFrustumMap frustum(bbox, 1./6., 5); + /// frustum will have depth, far plane - near plane = 5 + /// the frustum has width 1 in the front and 6 in the back + + Vec3d trans(2,2,2); + NonlinearFrustumMap::Ptr map = + boost::static_pointer_cast( + frustum.preScale(Vec3d(10,10,10))->postTranslate(trans)); + + + const Vec3d test(1,2,3); + const Vec3d origin(0, 0, 0); + + // these two drop down to just the linear part + Vec3d lresult = map->applyJacobian(test); + Vec3d ltmp = map->applyInverseJacobian(lresult); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(ltmp(0), test(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(ltmp(1), test(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(ltmp(2), test(2), TOL); + + Vec3d isloc(4,5,6); + // these two drop down to just the linear part + Vec3d result = map->applyJacobian(test, isloc); + Vec3d tmp = map->applyInverseJacobian(result, isloc); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); + + + + } + + +} + + + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestMat4Metadata.cc b/openvdb_2_3_0_library/openvdb/unittest/TestMat4Metadata.cc new file mode 100755 index 0000000..7b95001 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestMat4Metadata.cc @@ -0,0 +1,131 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +class TestMat4Metadata : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestMat4Metadata); + CPPUNIT_TEST(testMat4s); + CPPUNIT_TEST(testMat4d); + CPPUNIT_TEST_SUITE_END(); + + void testMat4s(); + void testMat4d(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMat4Metadata); + +void +TestMat4Metadata::testMat4s() +{ + using namespace openvdb; + + Metadata::Ptr m(new Mat4SMetadata(openvdb::math::Mat4s(1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f))); + Metadata::Ptr m3 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast( m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m3.get()) != 0); + + CPPUNIT_ASSERT( m->typeName().compare("mat4s") == 0); + CPPUNIT_ASSERT(m3->typeName().compare("mat4s") == 0); + + Mat4SMetadata *s = dynamic_cast(m.get()); + CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4s(1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f)); + s->value() = openvdb::math::Mat4s(3.0f, 3.0f, 3.0f, 3.0f, + 3.0f, 3.0f, 3.0f, 3.0f, + 3.0f, 3.0f, 3.0f, 3.0f, + 3.0f, 3.0f, 3.0f, 3.0f); + CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4s(3.0f, 3.0f, 3.0f, 3.0f, + 3.0f, 3.0f, 3.0f, 3.0f, + 3.0f, 3.0f, 3.0f, 3.0f, + 3.0f, 3.0f, 3.0f, 3.0f)); + + m3->copy(*s); + + s = dynamic_cast(m3.get()); + CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4s(3.0f, 3.0f, 3.0f, 3.0f, + 3.0f, 3.0f, 3.0f, 3.0f, + 3.0f, 3.0f, 3.0f, 3.0f, + 3.0f, 3.0f, 3.0f, 3.0f)); +} + +void +TestMat4Metadata::testMat4d() +{ + using namespace openvdb; + + Metadata::Ptr m(new Mat4DMetadata(openvdb::math::Mat4d(1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0))); + Metadata::Ptr m3 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast( m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m3.get()) != 0); + + CPPUNIT_ASSERT( m->typeName().compare("mat4d") == 0); + CPPUNIT_ASSERT(m3->typeName().compare("mat4d") == 0); + + Mat4DMetadata *s = dynamic_cast(m.get()); + CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4d(1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0)); + s->value() = openvdb::math::Mat4d(3.0, 3.0, 3.0, 3.0, + 3.0, 3.0, 3.0, 3.0, + 3.0, 3.0, 3.0, 3.0, + 3.0, 3.0, 3.0, 3.0); + CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4d(3.0, 3.0, 3.0, 3.0, + 3.0, 3.0, 3.0, 3.0, + 3.0, 3.0, 3.0, 3.0, + 3.0, 3.0, 3.0, 3.0)); + + m3->copy(*s); + + s = dynamic_cast(m3.get()); + CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4d(3.0, 3.0, 3.0, 3.0, + 3.0, 3.0, 3.0, 3.0, + 3.0, 3.0, 3.0, 3.0, + 3.0, 3.0, 3.0, 3.0)); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestMath.cc b/openvdb_2_3_0_library/openvdb/unittest/TestMath.cc new file mode 100755 index 0000000..6263514 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestMath.cc @@ -0,0 +1,221 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + + +class TestMath: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestMath); + CPPUNIT_TEST(testAll); + CPPUNIT_TEST(testRandomInt); + CPPUNIT_TEST(testRandom01); + CPPUNIT_TEST(testMinMaxIndex); + CPPUNIT_TEST_SUITE_END(); + + void testAll(); + void testRandomInt(); + void testRandom01(); + void testMinMaxIndex(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMath); + + +// This suite of tests obviously needs to be expanded! +void +TestMath::testAll() +{ + using namespace openvdb; + + {// Sign + CPPUNIT_ASSERT_EQUAL(math::Sign( 3 ), 1); + CPPUNIT_ASSERT_EQUAL(math::Sign(-1.0 ),-1); + CPPUNIT_ASSERT_EQUAL(math::Sign( 0.0f), 0); + } + {// SignChange + CPPUNIT_ASSERT( math::SignChange( -1, 1)); + CPPUNIT_ASSERT(!math::SignChange( 0.0f, 0.5f)); + CPPUNIT_ASSERT( math::SignChange( 0.0f,-0.5f)); + CPPUNIT_ASSERT( math::SignChange(-0.1, 0.0001)); + + } + {// isApproxZero + CPPUNIT_ASSERT( math::isApproxZero( 0.0f)); + CPPUNIT_ASSERT(!math::isApproxZero( 9.0e-6f)); + CPPUNIT_ASSERT(!math::isApproxZero(-9.0e-6f)); + CPPUNIT_ASSERT( math::isApproxZero( 9.0e-9f)); + CPPUNIT_ASSERT( math::isApproxZero(-9.0e-9f)); + CPPUNIT_ASSERT( math::isApproxZero( 0.01, 0.1)); + } + {// Cbrt + const double a = math::Cbrt(3.0); + CPPUNIT_ASSERT(math::isApproxEqual(a*a*a, 3.0, 1e-6)); + } +} + + +void +TestMath::testRandomInt() +{ + using openvdb::math::RandomInt; + + int imin = -3, imax = 11; + RandomInt rnd(/*seed=*/42, imin, imax); + + // Generate a sequence of random integers and verify that they all fall + // in the interval [imin, imax]. + std::vector seq(100); + for (int i = 0; i < 100; ++i) { + seq[i] = rnd(); + CPPUNIT_ASSERT(seq[i] >= imin); + CPPUNIT_ASSERT(seq[i] <= imax); + } + + // Verify that generators with the same seed produce the same sequence. + rnd = RandomInt(42, imin, imax); + for (int i = 0; i < 100; ++i) { + int r = rnd(); + CPPUNIT_ASSERT_EQUAL(seq[i], r); + } + + // Verify that generators with different seeds produce different sequences. + rnd = RandomInt(101, imin, imax); + std::vector newSeq(100); + for (int i = 0; i < 100; ++i) newSeq[i] = rnd(); + CPPUNIT_ASSERT(newSeq != seq); + + // Temporarily change the range. + imin = -5; imax = 6; + for (int i = 0; i < 100; ++i) { + int r = rnd(imin, imax); + CPPUNIT_ASSERT(r >= imin); + CPPUNIT_ASSERT(r <= imax); + } + // Verify that the range change was temporary. + imin = -3; imax = 11; + for (int i = 0; i < 100; ++i) { + int r = rnd(); + CPPUNIT_ASSERT(r >= imin); + CPPUNIT_ASSERT(r <= imax); + } + + // Permanently change the range. + imin = -5; imax = 6; + rnd.setRange(imin, imax); + for (int i = 0; i < 100; ++i) { + int r = rnd(); + CPPUNIT_ASSERT(r >= imin); + CPPUNIT_ASSERT(r <= imax); + } + + // Verify that it is OK to specify imin > imax (they are automatically swapped). + imin = 5; imax = -6; + rnd.setRange(imin, imax); + + rnd = RandomInt(42, imin, imax); +} + + +void +TestMath::testRandom01() +{ + using openvdb::math::Random01; + using openvdb::math::isApproxEqual; + + Random01 rnd(/*seed=*/42); + + // Generate a sequence of random numbers and verify that they all fall + // in the interval [0, 1). + std::vector seq(100); + for (int i = 0; i < 100; ++i) { + seq[i] = rnd(); + CPPUNIT_ASSERT(seq[i] >= 0.0); + CPPUNIT_ASSERT(seq[i] < 1.0); + } + + // Verify that generators with the same seed produce the same sequence. + rnd = Random01(42); + for (int i = 0; i < 100; ++i) { + CPPUNIT_ASSERT_DOUBLES_EQUAL(seq[i], rnd(), /*tolerance=*/1.0e-6); + } + + // Verify that generators with different seeds produce different sequences. + rnd = Random01(101); + bool allEqual = true; + for (int i = 0; allEqual && i < 100; ++i) { + if (!isApproxEqual(rnd(), seq[i])) allEqual = false; + } + CPPUNIT_ASSERT(!allEqual); +} + +void +TestMath::testMinMaxIndex() +{ + const openvdb::Vec3R a(-1, 2, 0); + CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MinIndex(a)); + CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MaxIndex(a)); + const openvdb::Vec3R b(-1, -2, 0); + CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MinIndex(b)); + CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(b)); + const openvdb::Vec3R c(5, 2, 1); + CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(c)); + CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MaxIndex(c)); + const openvdb::Vec3R d(0, 0, 1); + CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MinIndex(d)); + CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(d)); + const openvdb::Vec3R e(1, 0, 0); + CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(e)); + CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MaxIndex(e)); + const openvdb::Vec3R f(0, 1, 0); + CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(f)); + CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MaxIndex(f)); + const openvdb::Vec3R g(1, 1, 0); + CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(g)); + CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MaxIndex(g)); + const openvdb::Vec3R h(1, 0, 1); + CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MinIndex(h)); + CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(h)); + const openvdb::Vec3R i(0, 1, 1); + CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MinIndex(i)); + CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(i)); + const openvdb::Vec3R j(1, 1, 1); + CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(j)); + CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(j)); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestMeanCurvature.cc b/openvdb_2_3_0_library/openvdb/unittest/TestMeanCurvature.cc new file mode 100755 index 0000000..322f072 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestMeanCurvature.cc @@ -0,0 +1,742 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include "util.h" // for unittest_util::makeSphere() + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + +class TestMeanCurvature: public CppUnit::TestFixture +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestMeanCurvature); + CPPUNIT_TEST(testISMeanCurvature); // MeanCurvature in Index Space + CPPUNIT_TEST(testISMeanCurvatureStencil); + CPPUNIT_TEST(testWSMeanCurvature); // MeanCurvature in World Space + CPPUNIT_TEST(testWSMeanCurvatureStencil); + CPPUNIT_TEST(testMeanCurvatureTool); // MeanCurvature tool + CPPUNIT_TEST(testMeanCurvatureMaskedTool); // MeanCurvature tool + CPPUNIT_TEST(testOldStyleStencils); // old stencil impl - deprecate + + CPPUNIT_TEST_SUITE_END(); + + void testISMeanCurvature(); + void testISMeanCurvatureStencil(); + void testWSMeanCurvature(); + void testWSMeanCurvatureStencil(); + void testMeanCurvatureTool(); + void testMeanCurvatureMaskedTool(); + void testOldStyleStencils(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMeanCurvature); + + +void +TestMeanCurvature::testISMeanCurvature() +{ + using namespace openvdb; + + typedef FloatGrid::ConstAccessor AccessorType; + + FloatGrid::Ptr grid = createGrid(/*background=*/5.0); + FloatTree& tree = grid->tree(); + 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-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestMeshToVolume.cc b/openvdb_2_3_0_library/openvdb/unittest/TestMeshToVolume.cc new file mode 100755 index 0000000..c97dad7 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestMeshToVolume.cc @@ -0,0 +1,378 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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(testVoxelizer); + CPPUNIT_TEST(testPrimitiveVoxelRatio); + CPPUNIT_TEST(testIntersectingVoxelCleaner); + CPPUNIT_TEST(testShellVoxelCleaner); + CPPUNIT_TEST(testConversion); + CPPUNIT_TEST_SUITE_END(); + + void testUtils(); + void testVoxelizer(); + void testPrimitiveVoxelRatio(); + void testIntersectingVoxelCleaner(); + void testShellVoxelCleaner(); + void testConversion(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMeshToVolume); + + +//////////////////////////////////////// + + +void +TestMeshToVolume::testUtils() +{ + /// Test nearestCoord + openvdb::Vec3d xyz(0.7, 2.2, -2.7); + openvdb::Coord ijk = openvdb::util::nearestCoord(xyz); + CPPUNIT_ASSERT(ijk[0] == 0 && ijk[1] == 2 && ijk[2] == -3); + + xyz = openvdb::Vec3d(-22.1, 4.6, 202.34); + ijk = openvdb::util::nearestCoord(xyz); + CPPUNIT_ASSERT(ijk[0] == -23 && ijk[1] == 4 && ijk[2] == 202); + + /// Test the coordinate offset table for neghbouring voxels + openvdb::Coord sum(0, 0, 0); + + unsigned int pX = 0, pY = 0, pZ = 0, mX = 0, mY = 0, mZ = 0; + + for (unsigned int i = 0; i < 26; ++i) { + ijk = openvdb::util::COORD_OFFSETS[i]; + sum += ijk; + + if (ijk[0] == 1) ++pX; + else if (ijk[0] == -1) ++mX; + + if (ijk[1] == 1) ++pY; + else if (ijk[1] == -1) ++mY; + + if (ijk[2] == 1) ++pZ; + else if (ijk[2] == -1) ++mZ; + } + + CPPUNIT_ASSERT(sum == openvdb::Coord(0, 0, 0)); + + CPPUNIT_ASSERT( pX == 9); + CPPUNIT_ASSERT( pY == 9); + CPPUNIT_ASSERT( pZ == 9); + CPPUNIT_ASSERT( mX == 9); + CPPUNIT_ASSERT( mY == 9); + CPPUNIT_ASSERT( mZ == 9); +} + + +void +TestMeshToVolume::testVoxelizer() +{ + std::vector pointList; + std::vector polygonList; + + typedef openvdb::tools::internal::MeshVoxelizer MeshVoxelizer; + + // CASE 1: One triangle + + pointList.push_back(openvdb::Vec3s(0.0, 0.0, 0.0)); + pointList.push_back(openvdb::Vec3s(0.0, 0.0, 3.0)); + pointList.push_back(openvdb::Vec3s(0.0, 3.0, 0.0)); + + polygonList.push_back(openvdb::Vec4I(0, 1, 2, openvdb::util::INVALID_IDX)); + + { + MeshVoxelizer voxelizer(pointList, polygonList); + voxelizer.run(); + + // Check for mesh intersecting voxels + CPPUNIT_ASSERT(13== voxelizer.intersectionTree().activeVoxelCount()); + + // topologically unique voxels. + CPPUNIT_ASSERT(99 == voxelizer.sqrDistTree().activeVoxelCount()); + CPPUNIT_ASSERT(99 == voxelizer.primIndexTree().activeVoxelCount()); + } + + // CASE 2: Two triangles + + pointList.push_back(openvdb::Vec3s(0.0, 3.0, 3.0)); + polygonList.push_back(openvdb::Vec4I(1, 3, 2, openvdb::util::INVALID_IDX)); + + { + MeshVoxelizer voxelizer(pointList, polygonList); + voxelizer.run(); + + // Check for mesh intersecting voxels + CPPUNIT_ASSERT(16 == voxelizer.intersectionTree().activeVoxelCount()); + + // topologically unique voxels. + CPPUNIT_ASSERT(108 == voxelizer.sqrDistTree().activeVoxelCount()); + CPPUNIT_ASSERT(108 == voxelizer.primIndexTree().activeVoxelCount()); + } + + // CASE 3: One quad + + polygonList.clear(); + polygonList.push_back(openvdb::Vec4I(0, 1, 3, 2)); + + { + MeshVoxelizer voxelizer(pointList, polygonList); + voxelizer.run(); + + // Check for mesh intersecting voxels + CPPUNIT_ASSERT(16 == voxelizer.intersectionTree().activeVoxelCount()); + + // topologically unique voxels. + CPPUNIT_ASSERT(108 == voxelizer.sqrDistTree().activeVoxelCount()); + CPPUNIT_ASSERT(108 == voxelizer.primIndexTree().activeVoxelCount()); + } + + // CASE 4: Two triangles and one quad + + pointList.push_back(openvdb::Vec3s(0.0, 0.0, 6.0)); + pointList.push_back(openvdb::Vec3s(0.0, 3.0, 6.0)); + + polygonList.clear(); + polygonList.push_back(openvdb::Vec4I(0, 1, 2, openvdb::util::INVALID_IDX)); + polygonList.push_back(openvdb::Vec4I(1, 3, 2, openvdb::util::INVALID_IDX)); + polygonList.push_back(openvdb::Vec4I(1, 4, 5, 3)); + + { + MeshVoxelizer voxelizer(pointList, polygonList); + voxelizer.run(); + + // Check for 28 mesh intersecting voxels + CPPUNIT_ASSERT(28 == voxelizer.intersectionTree().activeVoxelCount()); + + // 154 topologically unique voxels. + CPPUNIT_ASSERT(162 == voxelizer.sqrDistTree().activeVoxelCount()); + CPPUNIT_ASSERT(162 == voxelizer.primIndexTree().activeVoxelCount()); + } +} + + +void +TestMeshToVolume::testPrimitiveVoxelRatio() +{ + std::vector pointList; + std::vector polygonList; + + // Create one big triangle + pointList.push_back(openvdb::Vec3s(0.0, 0.0, 0.0)); + pointList.push_back(openvdb::Vec3s(0.0, 0.0, 250.0)); + pointList.push_back(openvdb::Vec3s(0.0, 100.0, 0.0)); + + polygonList.push_back(openvdb::Vec4I(0, 1, 2, openvdb::util::INVALID_IDX)); + + openvdb::tools::internal::MeshVoxelizer + voxelizer(pointList, polygonList); + + voxelizer.run(); + + CPPUNIT_ASSERT(0 != voxelizer.intersectionTree().activeVoxelCount()); +} + + +void +TestMeshToVolume::testIntersectingVoxelCleaner() +{ + // Empty tree's + + openvdb::FloatTree distTree(std::numeric_limits::max()); + openvdb::BoolTree intersectionTree(false); + openvdb::Int32Tree indexTree(openvdb::util::INVALID_IDX); + + openvdb::tree::ValueAccessor distAcc(distTree); + openvdb::tree::ValueAccessor intersectionAcc(intersectionTree); + openvdb::tree::ValueAccessor indexAcc(indexTree); + + // Add a row of intersecting voxels surrounded by both positive and negative distance values. + for (int i = 0; i < 10; ++i) { + for (int j = -1; j < 2; ++j) { + distAcc.setValue(openvdb::Coord(i,j,0), (float)j); + indexAcc.setValue(openvdb::Coord(i,j,0), 10); + } + intersectionAcc.setValue(openvdb::Coord(i,0,0), 1); + } + + unsigned int numSDFVoxels = distTree.activeVoxelCount(); + unsigned int numIVoxels = intersectionTree.activeVoxelCount(); + unsigned int numCPVoxels = indexTree.activeVoxelCount(); + + { + openvdb::tree::LeafManager leafs(intersectionTree); + + openvdb::tools::internal::IntersectingVoxelCleaner + cleaner(distTree, indexTree, intersectionTree, leafs); + + cleaner.run(); + } + + CPPUNIT_ASSERT(numSDFVoxels == distTree.activeVoxelCount()); + CPPUNIT_ASSERT(numIVoxels == intersectionTree.activeVoxelCount()); + CPPUNIT_ASSERT(numCPVoxels == indexTree.activeVoxelCount()); + + + // Add a row of intersecting voxels that are not surrounded by any positive distance values. + for (int i = 0; i < 10; ++i) { + for (int j = -1; j < 2; ++j) { + distAcc.setValue(openvdb::Coord(i,j,0), -1.0); + indexAcc.setValue(openvdb::Coord(i,j,0), 10); + } + intersectionAcc.setValue(openvdb::Coord(i,0,0), 1); + } + + numIVoxels = 0; + + { + openvdb::tree::LeafManager leafs(intersectionTree); + + openvdb::tools::internal::IntersectingVoxelCleaner + cleaner(distTree, indexTree, intersectionTree, leafs); + + cleaner.run(); + } + + CPPUNIT_ASSERT(numSDFVoxels == distTree.activeVoxelCount()); + CPPUNIT_ASSERT(numIVoxels == intersectionTree.activeVoxelCount()); + CPPUNIT_ASSERT(numCPVoxels == indexTree.activeVoxelCount()); +} + + +void +TestMeshToVolume::testShellVoxelCleaner() +{ + // Empty tree's + + openvdb::FloatTree distTree(std::numeric_limits::max()); + openvdb::BoolTree intersectionTree(false); + openvdb::Int32Tree indexTree(openvdb::util::INVALID_IDX); + + openvdb::tree::ValueAccessor distAcc(distTree); + openvdb::tree::ValueAccessor intersectionAcc(intersectionTree); + openvdb::tree::ValueAccessor indexAcc(indexTree); + + /// Add a row of intersecting voxels surrounded by negative distance values. + for (int i = 0; i < 10; ++i) { + for (int j = -1; j < 2; ++j) { + distAcc.setValue(openvdb::Coord(i,j,0), -1.0); + indexAcc.setValue(openvdb::Coord(i,j,0), 10); + } + intersectionAcc.setValue(openvdb::Coord(i,0,0), 1); + } + + unsigned int numSDFVoxels = distTree.activeVoxelCount(); + unsigned int numIVoxels = intersectionTree.activeVoxelCount(); + unsigned int numCPVoxels = indexTree.activeVoxelCount(); + + { + openvdb::tree::LeafManager leafs(distTree); + + openvdb::tools::internal::ShellVoxelCleaner + cleaner(distTree, leafs, indexTree, intersectionTree); + + cleaner.run(); + } + + CPPUNIT_ASSERT(numSDFVoxels == distTree.activeVoxelCount()); + CPPUNIT_ASSERT(numIVoxels == intersectionTree.activeVoxelCount()); + CPPUNIT_ASSERT(numCPVoxels == indexTree.activeVoxelCount()); + + intersectionTree.clear(); + + { + openvdb::tree::LeafManager leafs(distTree); + + openvdb::tools::internal::ShellVoxelCleaner + cleaner(distTree, leafs, indexTree, intersectionTree); + + cleaner.run(); + } + + CPPUNIT_ASSERT(0 == distTree.activeVoxelCount()); + CPPUNIT_ASSERT(0 == intersectionTree.activeVoxelCount()); + CPPUNIT_ASSERT(0 == indexTree.activeVoxelCount());; +} + + +void +TestMeshToVolume::testConversion() +{ + using namespace openvdb; + + std::vector points; + std::vector quads; + + // cube vertices + points.push_back(Vec3s(2, 2, 2)); // 0 6--------7 + points.push_back(Vec3s(5, 2, 2)); // 1 /| /| + points.push_back(Vec3s(2, 5, 2)); // 2 2--------3 | + points.push_back(Vec3s(5, 5, 2)); // 3 | | | | + points.push_back(Vec3s(2, 2, 5)); // 4 | 4----- |-5 + points.push_back(Vec3s(5, 2, 5)); // 5 |/ |/ + points.push_back(Vec3s(2, 5, 5)); // 6 0--------1 + points.push_back(Vec3s(5, 5, 5)); // 7 + + // cube faces + quads.push_back(Vec4I(0, 1, 3, 2)); // front + quads.push_back(Vec4I(5, 4, 6, 7)); // back + quads.push_back(Vec4I(0, 2, 6, 4)); // left + quads.push_back(Vec4I(1, 5, 7, 3)); // right + quads.push_back(Vec4I(2, 3, 7, 6)); // top + quads.push_back(Vec4I(0, 4, 5, 1)); // bottom + + FloatGrid::Ptr grid = tools::meshToLevelSet( + *math::Transform::createLinearTransform(), points, quads); + + //io::File("/tmp/cube.vdb").write(GridPtrVec(1, grid)); + + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT_EQUAL(int(GRID_LEVEL_SET), int(grid->getGridClass())); + CPPUNIT_ASSERT_EQUAL(1, int(grid->baseTree().leafCount())); + /// @todo validate output +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestMetaMap.cc b/openvdb_2_3_0_library/openvdb/unittest/TestMetaMap.cc new file mode 100755 index 0000000..bea28e3 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestMetaMap.cc @@ -0,0 +1,317 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +class TestMetaMap: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestMetaMap); + CPPUNIT_TEST(testInsert); + CPPUNIT_TEST(testRemove); + CPPUNIT_TEST(testGetMetadata); + CPPUNIT_TEST(testIO); + CPPUNIT_TEST(testEmptyIO); + CPPUNIT_TEST(testCopyConstructor); + CPPUNIT_TEST(testCopyConstructorEmpty); + CPPUNIT_TEST(testAssignment); + CPPUNIT_TEST_SUITE_END(); + + void testInsert(); + void testRemove(); + void testGetMetadata(); + void testIO(); + void testEmptyIO(); + void testCopyConstructor(); + void testCopyConstructorEmpty(); + void testAssignment(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMetaMap); + +void +TestMetaMap::testInsert() +{ + using namespace openvdb; + + MetaMap meta; + meta.insertMeta("meta1", StringMetadata("testing")); + meta.insertMeta("meta2", Int32Metadata(20)); + meta.insertMeta("meta3", FloatMetadata(2.0)); + + MetaMap::MetaIterator iter = meta.beginMeta(); + int i = 1; + for( ; iter != meta.endMeta(); ++iter, ++i) { + if(i == 1) { + CPPUNIT_ASSERT(iter->first.compare("meta1") == 0); + std::string val = meta.metaValue("meta1"); + CPPUNIT_ASSERT(val == "testing"); + } else if(i == 2) { + CPPUNIT_ASSERT(iter->first.compare("meta2") == 0); + int32_t val = meta.metaValue("meta2"); + CPPUNIT_ASSERT(val == 20); + } else if(i == 3) { + CPPUNIT_ASSERT(iter->first.compare("meta3") == 0); + float val = meta.metaValue("meta3"); + //CPPUNIT_ASSERT(val == 2.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,val,0); + } + } +} + +void +TestMetaMap::testRemove() +{ + using namespace openvdb; + + MetaMap meta; + meta.insertMeta("meta1", StringMetadata("testing")); + meta.insertMeta("meta2", Int32Metadata(20)); + meta.insertMeta("meta3", FloatMetadata(2.0)); + + meta.removeMeta("meta2"); + + MetaMap::MetaIterator iter = meta.beginMeta(); + int i = 1; + for( ; iter != meta.endMeta(); ++iter, ++i) { + if(i == 1) { + CPPUNIT_ASSERT(iter->first.compare("meta1") == 0); + std::string val = meta.metaValue("meta1"); + CPPUNIT_ASSERT(val == "testing"); + } else if(i == 2) { + CPPUNIT_ASSERT(iter->first.compare("meta3") == 0); + float val = meta.metaValue("meta3"); + //CPPUNIT_ASSERT(val == 2.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,val,0); + } + } + + meta.removeMeta("meta1"); + + iter = meta.beginMeta(); + for( ; iter != meta.endMeta(); ++iter, ++i) { + CPPUNIT_ASSERT(iter->first.compare("meta3") == 0); + float val = meta.metaValue("meta3"); + //CPPUNIT_ASSERT(val == 2.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,val,0); + } + + meta.removeMeta("meta3"); + + CPPUNIT_ASSERT(meta.empty()); +} + +void +TestMetaMap::testGetMetadata() +{ + using namespace openvdb; + + MetaMap meta; + meta.insertMeta("meta1", StringMetadata("testing")); + meta.insertMeta("meta2", Int32Metadata(20)); + meta.insertMeta("meta3", DoubleMetadata(2.0)); + + Metadata::Ptr metadata = meta["meta2"]; + CPPUNIT_ASSERT(metadata); + CPPUNIT_ASSERT(metadata->typeName().compare("int32") == 0); + + DoubleMetadata::Ptr dm = meta.getMetadata("meta3"); + //CPPUNIT_ASSERT(dm->value() == 2.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0,dm->value(),0); + + const DoubleMetadata::Ptr cdm = meta.getMetadata("meta3"); + //CPPUNIT_ASSERT(dm->value() == 2.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0,cdm->value(),0); + + CPPUNIT_ASSERT(meta.getMetadata("meta2") == NULL); + + CPPUNIT_ASSERT_THROW(meta.metaValue("meta3"), + openvdb::TypeError); + + CPPUNIT_ASSERT_THROW(meta.metaValue("meta5"), + openvdb::LookupError); +} + +void +TestMetaMap::testIO() +{ + using namespace openvdb; + + Metadata::clearRegistry(); + + // Write some metadata using unregistered types. + MetaMap meta; + meta.insertMeta("meta1", StringMetadata("testing")); + meta.insertMeta("meta2", Int32Metadata(20)); + meta.insertMeta("meta3", DoubleMetadata(2.0)); + std::ostringstream ostr(std::ios_base::binary); + meta.writeMeta(ostr); + + // Verify that reading metadata of unregistered types is possible, + // though the values cannot be retrieved. + MetaMap meta2; + std::istringstream istr(ostr.str(), std::ios_base::binary); + CPPUNIT_ASSERT_NO_THROW(meta2.readMeta(istr)); + CPPUNIT_ASSERT(meta2.empty()); + + // Register just one of the three types, then reread and verify that + // the value of the registered type can be retrieved. + Int32Metadata::registerType(); + istr.seekg(0, std::ios_base::beg); + CPPUNIT_ASSERT_NO_THROW(meta2.readMeta(istr)); + CPPUNIT_ASSERT_EQUAL(size_t(1), meta2.metaCount()); + CPPUNIT_ASSERT_EQUAL(meta.metaValue("meta2"), meta2.metaValue("meta2")); + + // Register the remaining types. + StringMetadata::registerType(); + DoubleMetadata::registerType(); + + // Now seek to beginning and read again. + istr.seekg(0, std::ios_base::beg); + meta2.clearMetadata(); + + CPPUNIT_ASSERT_NO_THROW(meta2.readMeta(istr)); + CPPUNIT_ASSERT_EQUAL(meta.metaCount(), meta2.metaCount()); + + std::string val = meta.metaValue("meta1"); + std::string val2 = meta2.metaValue("meta1"); + CPPUNIT_ASSERT_EQUAL(0, val.compare(val2)); + + int intval = meta.metaValue("meta2"); + int intval2 = meta2.metaValue("meta2"); + CPPUNIT_ASSERT_EQUAL(intval, intval2); + + double dval = meta.metaValue("meta3"); + double dval2 = meta2.metaValue("meta3"); + CPPUNIT_ASSERT_DOUBLES_EQUAL(dval, dval2,0); + + // Clear the registry once the test is done. + Metadata::clearRegistry(); +} + +void +TestMetaMap::testEmptyIO() +{ + using namespace openvdb; + + MetaMap meta; + + // Write out an empty metadata + std::ostringstream ostr(std::ios_base::binary); + + // Read in the metadata; + MetaMap meta2; + std::istringstream istr(ostr.str(), std::ios_base::binary); + CPPUNIT_ASSERT_NO_THROW(meta2.readMeta(istr)); + + CPPUNIT_ASSERT(meta2.metaCount() == 0); +} + +void +TestMetaMap::testCopyConstructor() +{ + using namespace openvdb; + + MetaMap meta; + meta.insertMeta("meta1", StringMetadata("testing")); + meta.insertMeta("meta2", Int32Metadata(20)); + meta.insertMeta("meta3", FloatMetadata(2.0)); + + // copy constructor + MetaMap meta2(meta); + + CPPUNIT_ASSERT(meta.metaCount() == meta2.metaCount()); + + std::string str = meta.metaValue("meta1"); + std::string str2 = meta2.metaValue("meta1"); + CPPUNIT_ASSERT(str == str2); + + CPPUNIT_ASSERT(meta.metaValue("meta2") == + meta2.metaValue("meta2")); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(meta.metaValue("meta3"), + meta2.metaValue("meta3"),0); + //CPPUNIT_ASSERT(meta.metaValue("meta3") == + // meta2.metaValue("meta3")); +} + + +void +TestMetaMap::testCopyConstructorEmpty() +{ + using namespace openvdb; + + MetaMap meta; + + MetaMap meta2(meta); + + CPPUNIT_ASSERT(meta.metaCount() == 0); + CPPUNIT_ASSERT(meta2.metaCount() == meta.metaCount()); +} + + +void +TestMetaMap::testAssignment() +{ + using namespace openvdb; + + // Populate a map with data. + MetaMap meta; + meta.insertMeta("meta1", StringMetadata("testing")); + meta.insertMeta("meta2", Int32Metadata(20)); + meta.insertMeta("meta3", FloatMetadata(2.0)); + + // Create an empty map. + MetaMap meta2; + CPPUNIT_ASSERT(meta2.empty()); + + // Copy the first map to the second. + meta2 = meta; + CPPUNIT_ASSERT_EQUAL(meta.metaCount(), meta2.metaCount()); + + // Verify that the contents of the two maps are the same. + CPPUNIT_ASSERT_EQUAL( + meta.metaValue("meta1"), meta2.metaValue("meta1")); + CPPUNIT_ASSERT_EQUAL(meta.metaValue("meta2"), meta2.metaValue("meta2")); + CPPUNIT_ASSERT_DOUBLES_EQUAL( + meta.metaValue("meta3"), meta2.metaValue("meta3"), /*tolerance=*/0); + + // Verify that changing one map doesn't affect the other. + meta.insertMeta("meta1", StringMetadata("changed")); + std::string str = meta.metaValue("meta1"); + CPPUNIT_ASSERT_EQUAL(std::string("testing"), meta2.metaValue("meta1")); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestMetadata.cc b/openvdb_2_3_0_library/openvdb/unittest/TestMetadata.cc new file mode 100755 index 0000000..3931733 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestMetadata.cc @@ -0,0 +1,127 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +class TestMetadata : public CppUnit::TestCase +{ +public: + virtual void setUp() { openvdb::Metadata::clearRegistry(); } + virtual void tearDown() { openvdb::Metadata::clearRegistry(); } + + CPPUNIT_TEST_SUITE(TestMetadata); + CPPUNIT_TEST(testMetadataRegistry); + CPPUNIT_TEST(testMetadataAsBool); + CPPUNIT_TEST_SUITE_END(); + + void testMetadataRegistry(); + void testMetadataAsBool(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadata); + +void +TestMetadata::testMetadataRegistry() +{ + using namespace openvdb; + + Int32Metadata::registerType(); + + StringMetadata strMetadata; + + CPPUNIT_ASSERT(!Metadata::isRegisteredType(strMetadata.typeName())); + + StringMetadata::registerType(); + + CPPUNIT_ASSERT(Metadata::isRegisteredType(strMetadata.typeName())); + CPPUNIT_ASSERT( + Metadata::isRegisteredType(Int32Metadata::staticTypeName())); + + Metadata::Ptr stringMetadata = + Metadata::createMetadata(strMetadata.typeName()); + + CPPUNIT_ASSERT(stringMetadata->typeName() == strMetadata.typeName()); + + StringMetadata::unregisterType(); + + CPPUNIT_ASSERT_THROW(Metadata::createMetadata(strMetadata.typeName()), + openvdb::LookupError); +} + +void +TestMetadata::testMetadataAsBool() +{ + using namespace openvdb; + + { + FloatMetadata meta(0.0); + CPPUNIT_ASSERT(!meta.asBool()); + meta.setValue(1.0); + CPPUNIT_ASSERT(meta.asBool()); + meta.setValue(-1.0); + CPPUNIT_ASSERT(meta.asBool()); + meta.setValue(999.0); + CPPUNIT_ASSERT(meta.asBool()); + } + { + Int32Metadata meta(0); + CPPUNIT_ASSERT(!meta.asBool()); + meta.setValue(1); + CPPUNIT_ASSERT(meta.asBool()); + meta.setValue(-1); + CPPUNIT_ASSERT(meta.asBool()); + meta.setValue(999); + CPPUNIT_ASSERT(meta.asBool()); + } + { + StringMetadata meta(""); + CPPUNIT_ASSERT(!meta.asBool()); + meta.setValue("abc"); + CPPUNIT_ASSERT(meta.asBool()); + } + { + Vec3IMetadata meta(Vec3i(0)); + CPPUNIT_ASSERT(!meta.asBool()); + meta.setValue(Vec3i(-1, 0, 1)); + CPPUNIT_ASSERT(meta.asBool()); + } + { + Vec3SMetadata meta(Vec3s(0.0)); + CPPUNIT_ASSERT(!meta.asBool()); + meta.setValue(Vec3s(-1.0, 0.0, 1.0)); + CPPUNIT_ASSERT(meta.asBool()); + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestMetadataIO.cc b/openvdb_2_3_0_library/openvdb/unittest/TestMetadataIO.cc new file mode 100755 index 0000000..ebc9254 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestMetadataIO.cc @@ -0,0 +1,264 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +// CPPUNIT_TEST_SUITE() invokes CPPUNIT_TESTNAMER_DECL() to generate a suite name +// from the FixtureType. But if FixtureType is a templated type, the generated name +// can become long and messy. This macro overrides the normal naming logic, +// instead invoking FixtureType::testSuiteName(), which should be a static member +// function that returns a std::string containing the suite name for the specific +// template instantiation. +#undef CPPUNIT_TESTNAMER_DECL +#define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ + CPPUNIT_NS::TestNamer variableName( FixtureType::testSuiteName() ) + +template +class TestMetadataIO: public CppUnit::TestCase +{ +public: + static std::string testSuiteName() + { + std::string name = openvdb::typeNameAsString(); + if (!name.empty()) name[0] = ::toupper(name[0]); + return "TestMetadataIO" + name; + } + + CPPUNIT_TEST_SUITE(TestMetadataIO); + CPPUNIT_TEST(test); + CPPUNIT_TEST(testMultiple); + CPPUNIT_TEST_SUITE_END(); + + void test(); + void testMultiple(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); +CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); + + +template +void +TestMetadataIO::test() +{ + using namespace openvdb; + + TypedMetadata m(1); + + std::ostringstream ostr(std::ios_base::binary); + + m.write(ostr); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + TypedMetadata tm; + tm.read(istr); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1),tm.value(),0); + //CPPUNIT_ASSERT(tm.value() == T(1)); +} + + +template +void +TestMetadataIO::testMultiple() +{ + using namespace openvdb; + + TypedMetadata m(1); + TypedMetadata m2(2); + + std::ostringstream ostr(std::ios_base::binary); + + m.write(ostr); + m2.write(ostr); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + TypedMetadata tm, tm2; + tm.read(istr); + tm2.read(istr); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1),tm.value(),0); + //CPPUNIT_ASSERT(tm.value() == T(1)); + CPPUNIT_ASSERT_DOUBLES_EQUAL(T(2),tm2.value(),0); + //CPPUNIT_ASSERT(tm2.value() == T(2)); +} + + +template<> +void +TestMetadataIO::test() +{ + using namespace openvdb; + + TypedMetadata m("test"); + + std::ostringstream ostr(std::ios_base::binary); + + m.write(ostr); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + TypedMetadata tm; + tm.read(istr); + + CPPUNIT_ASSERT(tm.value() == "test"); +} + + +template<> +void +TestMetadataIO::testMultiple() +{ + using namespace openvdb; + + TypedMetadata m("test"); + TypedMetadata m2("test2"); + + std::ostringstream ostr(std::ios_base::binary); + + m.write(ostr); + m2.write(ostr); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + TypedMetadata tm, tm2; + tm.read(istr); + tm2.read(istr); + + CPPUNIT_ASSERT(tm.value() == "test"); + CPPUNIT_ASSERT(tm2.value() == "test2"); +} + + +template<> +void +TestMetadataIO::test() +{ + using namespace openvdb; + + TypedMetadata m(Vec3R(1, 2, 3)); + + std::ostringstream ostr(std::ios_base::binary); + + m.write(ostr); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + TypedMetadata tm; + tm.read(istr); + + CPPUNIT_ASSERT(tm.value() == Vec3R(1, 2, 3)); +} + + +template<> +void +TestMetadataIO::testMultiple() +{ + using namespace openvdb; + + TypedMetadata m(Vec3R(1, 2, 3)); + TypedMetadata m2(Vec3R(4, 5, 6)); + + std::ostringstream ostr(std::ios_base::binary); + + m.write(ostr); + m2.write(ostr); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + TypedMetadata tm, tm2; + tm.read(istr); + tm2.read(istr); + + CPPUNIT_ASSERT(tm.value() == Vec3R(1, 2, 3)); + CPPUNIT_ASSERT(tm2.value() == Vec3R(4, 5, 6)); +} + + +template<> +void +TestMetadataIO::test() +{ + using namespace openvdb; + + TypedMetadata m(Vec2i(1, 2)); + + std::ostringstream ostr(std::ios_base::binary); + + m.write(ostr); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + TypedMetadata tm; + tm.read(istr); + + CPPUNIT_ASSERT(tm.value() == Vec2i(1, 2)); +} + + +template<> +void +TestMetadataIO::testMultiple() +{ + using namespace openvdb; + + TypedMetadata m(Vec2i(1, 2)); + TypedMetadata m2(Vec2i(3, 4)); + + std::ostringstream ostr(std::ios_base::binary); + + m.write(ostr); + m2.write(ostr); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + TypedMetadata tm, tm2; + tm.read(istr); + tm2.read(istr); + + CPPUNIT_ASSERT(tm.value() == Vec2i(1, 2)); + CPPUNIT_ASSERT(tm2.value() == Vec2i(3, 4)); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestName.cc b/openvdb_2_3_0_library/openvdb/unittest/TestName.cc new file mode 100755 index 0000000..9c3f464 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestName.cc @@ -0,0 +1,112 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +class TestName : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestName); + CPPUNIT_TEST(test); + CPPUNIT_TEST(testIO); + CPPUNIT_TEST(testMultipleIO); + CPPUNIT_TEST_SUITE_END(); + + void test(); + void testIO(); + void testMultipleIO(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestName); + +void +TestName::test() +{ + using namespace openvdb; + + Name name; + Name name2("something"); + Name name3 = std::string("something2"); + name = "something"; + + CPPUNIT_ASSERT(name == name2); + CPPUNIT_ASSERT(name != name3); + CPPUNIT_ASSERT(name != Name("testing")); + CPPUNIT_ASSERT(name == Name("something")); +} + +void +TestName::testIO() +{ + using namespace openvdb; + + Name name("some name that i made up"); + + std::ostringstream ostr(std::ios_base::binary); + + openvdb::writeString(ostr, name); + + name = "some other name"; + + CPPUNIT_ASSERT(name == Name("some other name")); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + name = openvdb::readString(istr); + + CPPUNIT_ASSERT(name == Name("some name that i made up")); +} + +void +TestName::testMultipleIO() +{ + using namespace openvdb; + + Name name("some name that i made up"); + Name name2("something else"); + + std::ostringstream ostr(std::ios_base::binary); + + openvdb::writeString(ostr, name); + openvdb::writeString(ostr, name2); + + std::istringstream istr(ostr.str(), std::ios_base::binary); + + Name n = openvdb::readString(istr), n2 = openvdb::readString(istr); + + CPPUNIT_ASSERT(name == n); + CPPUNIT_ASSERT(name2 == n2); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestNodeIterator.cc b/openvdb_2_3_0_library/openvdb/unittest/TestNodeIterator.cc new file mode 100755 index 0000000..2c5dd22 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestNodeIterator.cc @@ -0,0 +1,406 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include + + +class TestNodeIterator: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestNodeIterator); + CPPUNIT_TEST(testEmpty); + CPPUNIT_TEST(testSinglePositive); + CPPUNIT_TEST(testSingleNegative); + CPPUNIT_TEST(testMultipleBlocks); + CPPUNIT_TEST(testDepthBounds); + CPPUNIT_TEST_SUITE_END(); + + void testEmpty(); + void testSinglePositive(); + void testSingleNegative(); + void testMultipleBlocks(); + void testDepthBounds(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestNodeIterator); + +namespace { +typedef openvdb::tree::Tree4::Type Tree323f; +} + + +//////////////////////////////////////// + + +void +TestNodeIterator::testEmpty() +{ + Tree323f tree(/*fillValue=*/256.0f); + { + Tree323f::NodeCIter iter(tree); + CPPUNIT_ASSERT(!iter.next()); + } + { + tree.setValue(openvdb::Coord(8, 16, 24), 10.f); + Tree323f::NodeIter iter(tree); // non-const + CPPUNIT_ASSERT(iter); + + // Try modifying the tree through a non-const iterator. + Tree323f::RootNodeType* root = NULL; + iter.getNode(root); + CPPUNIT_ASSERT(root != NULL); + root->clear(); + + // Verify that the tree is now empty. + iter = Tree323f::NodeIter(tree); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT(!iter.next()); + } +} + + +void +TestNodeIterator::testSinglePositive() +{ + { + Tree323f tree(/*fillValue=*/256.0f); + + tree.setValue(openvdb::Coord(8, 16, 24), 10.f); + + Tree323f::NodeCIter iter(tree); + + CPPUNIT_ASSERT(Tree323f::LeafNodeType::DIM == 8); + + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(0U, iter.getDepth()); + CPPUNIT_ASSERT_EQUAL(tree.treeDepth(), 1 + iter.getLevel()); + openvdb::CoordBBox range, bbox; + tree.getIndexRange(range); + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL(bbox.min(), range.min()); + CPPUNIT_ASSERT_EQUAL(bbox.max(), range.max()); + + // Descend to the depth-1 internal node with bounding box + // (0, 0, 0) -> (255, 255, 255) containing voxel (8, 16, 24). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0), bbox.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord((1 << (3 + 2 + 3)) - 1), bbox.max()); + + // Descend to the depth-2 internal node with bounding box + // (0, 0, 0) -> (31, 31, 31) containing voxel (8, 16, 24). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0), bbox.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord((1 << (2 + 3)) - 1), bbox.max()); + + // Descend to the leaf node with bounding box (8, 16, 24) -> (15, 23, 31) + // containing voxel (8, 16, 24). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); + iter.getBoundingBox(bbox); + range.min().reset(8, 16, 24); + range.max() = range.min().offsetBy((1 << 3) - 1); // add leaf node size + CPPUNIT_ASSERT_EQUAL(range.min(), bbox.min()); + CPPUNIT_ASSERT_EQUAL(range.max(), bbox.max()); + + iter.next(); + CPPUNIT_ASSERT(!iter); + } + { + Tree323f tree(/*fillValue=*/256.0f); + + tree.setValue(openvdb::Coord(129), 10.f); + + Tree323f::NodeCIter iter(tree); + + CPPUNIT_ASSERT(Tree323f::LeafNodeType::DIM == 8); + + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(0U, iter.getDepth()); + CPPUNIT_ASSERT_EQUAL(tree.treeDepth(), 1 + iter.getLevel()); + openvdb::CoordBBox range, bbox; + tree.getIndexRange(range); + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL(bbox.min(), range.min()); + CPPUNIT_ASSERT_EQUAL(bbox.max(), range.max()); + + // Descend to the depth-1 internal node with bounding box + // (0, 0, 0) -> (255, 255, 255) containing voxel (129, 129, 129). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0), bbox.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord((1 << (3 + 2 + 3)) - 1), bbox.max()); + + // Descend to the depth-2 internal node with bounding box + // (128, 128, 128) -> (159, 159, 159) containing voxel (129, 129, 129). + // (128 is the nearest multiple of 32 less than 129.) + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); + iter.getBoundingBox(bbox); + range.min().reset(128, 128, 128); + CPPUNIT_ASSERT_EQUAL(range.min(), bbox.min()); + CPPUNIT_ASSERT_EQUAL(range.min().offsetBy((1 << (2 + 3)) - 1), bbox.max()); + + // Descend to the leaf node with bounding box + // (128, 128, 128) -> (135, 135, 135) containing voxel (129, 129, 129). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); + iter.getBoundingBox(bbox); + range.max() = range.min().offsetBy((1 << 3) - 1); // add leaf node size + CPPUNIT_ASSERT_EQUAL(range.min(), bbox.min()); + CPPUNIT_ASSERT_EQUAL(range.max(), bbox.max()); + + iter.next(); + CPPUNIT_ASSERT(!iter); + } +} + + +void +TestNodeIterator::testSingleNegative() +{ + Tree323f tree(/*fillValue=*/256.0f); + + tree.setValue(openvdb::Coord(-1), 10.f); + + Tree323f::NodeCIter iter(tree); + + CPPUNIT_ASSERT(Tree323f::LeafNodeType::DIM == 8); + + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(0U, iter.getDepth()); + CPPUNIT_ASSERT_EQUAL(tree.treeDepth(), 1 + iter.getLevel()); + openvdb::CoordBBox range, bbox; + tree.getIndexRange(range); + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL(bbox.min(), range.min()); + CPPUNIT_ASSERT_EQUAL(bbox.max(), range.max()); + + // Descend to the depth-1 internal node with bounding box + // (-256, -256, -256) -> (-1, -1, -1) containing voxel (-1, -1, -1). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-(1 << (3 + 2 + 3))), bbox.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-1), bbox.max()); + + // Descend to the depth-2 internal node with bounding box + // (-32, -32, -32) -> (-1, -1, -1) containing voxel (-1, -1, -1). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-(1 << (2 + 3))), bbox.min()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-1), bbox.max()); + + // Descend to the leaf node with bounding box (-8, -8, -8) -> (-1, -1, -1) + // containing voxel (-1, -1, -1). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); + iter.getBoundingBox(bbox); + range.max().reset(-1, -1, -1); + range.min() = range.max().offsetBy(-((1 << 3) - 1)); // add leaf node size + CPPUNIT_ASSERT_EQUAL(range.min(), bbox.min()); + CPPUNIT_ASSERT_EQUAL(range.max(), bbox.max()); + + iter.next(); + CPPUNIT_ASSERT(!iter); +} + + +void +TestNodeIterator::testMultipleBlocks() +{ + Tree323f tree(/*fillValue=*/256.0f); + + tree.setValue(openvdb::Coord(-1), 10.f); + tree.setValue(openvdb::Coord(129), 10.f); + + Tree323f::NodeCIter iter(tree); + + CPPUNIT_ASSERT(Tree323f::LeafNodeType::DIM == 8); + + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(0U, iter.getDepth()); + CPPUNIT_ASSERT_EQUAL(tree.treeDepth(), 1 + iter.getLevel()); + + // Descend to the depth-1 internal node with bounding box + // (-256, -256, -256) -> (-1, -1, -1) containing voxel (-1, -1, -1). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); + + // Descend to the depth-2 internal node with bounding box + // (-32, -32, -32) -> (-1, -1, -1) containing voxel (-1, -1, -1). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); + + // Descend to the leaf node with bounding box (-8, -8, -8) -> (-1, -1, -1) + // containing voxel (-1, -1, -1). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); + openvdb::Coord expectedMin, expectedMax(-1, -1, -1); + expectedMin = expectedMax.offsetBy(-((1 << 3) - 1)); // add leaf node size + openvdb::CoordBBox bbox; + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL(expectedMin, bbox.min()); + CPPUNIT_ASSERT_EQUAL(expectedMax, bbox.max()); + + // Ascend to the depth-1 internal node with bounding box (0, 0, 0) -> (255, 255, 255) + // containing voxel (129, 129, 129). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); + + // Descend to the depth-2 internal node with bounding box + // (128, 128, 128) -> (159, 159, 159) containing voxel (129, 129, 129). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); + + // Descend to the leaf node with bounding box (128, 128, 128) -> (135, 135, 135) + // containing voxel (129, 129, 129). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); + expectedMin.reset(128, 128, 128); + expectedMax = expectedMin.offsetBy((1 << 3) - 1); // add leaf node size + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL(expectedMin, bbox.min()); + CPPUNIT_ASSERT_EQUAL(expectedMax, bbox.max()); + + iter.next(); + CPPUNIT_ASSERT(!iter); +} + + +void +TestNodeIterator::testDepthBounds() +{ + Tree323f tree(/*fillValue=*/256.0f); + + tree.setValue(openvdb::Coord(-1), 10.f); + tree.setValue(openvdb::Coord(129), 10.f); + + { + // Iterate over internal nodes only. + Tree323f::NodeCIter iter(tree); + iter.setMaxDepth(2); + iter.setMinDepth(1); + + // Begin at the depth-1 internal node with bounding box + // (-256, -256, -256) -> (-1, -1, -1) containing voxel (-1, -1, -1). + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); + + // Descend to the depth-2 internal node with bounding box + // (-32, -32, -32) -> (-1, -1, -1) containing voxel (-1, -1, -1). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); + + // Skipping the leaf node, ascend to the depth-1 internal node with bounding box + // (0, 0, 0) -> (255, 255, 255) containing voxel (129, 129, 129). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); + + // Descend to the depth-2 internal node with bounding box + // (128, 128, 128) -> (159, 159, 159) containing voxel (129, 129, 129). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); + + // Verify that no internal nodes remain unvisited. + iter.next(); + CPPUNIT_ASSERT(!iter); + } + { + // Iterate over depth-1 internal nodes only. + Tree323f::NodeCIter iter(tree); + iter.setMaxDepth(1); + iter.setMinDepth(1); + + // Begin at the depth-1 internal node with bounding box + // (-256, -256, -256) -> (-1, -1, -1) containing voxel (-1, -1, -1). + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); + + // Skip to the depth-1 internal node with bounding box + // (0, 0, 0) -> (255, 255, 255) containing voxel (129, 129, 129). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); + + // Verify that no depth-1 nodes remain unvisited. + iter.next(); + CPPUNIT_ASSERT(!iter); + } + { + // Iterate over leaf nodes only. + Tree323f::NodeCIter iter = tree.cbeginNode(); + iter.setMaxDepth(3); + iter.setMinDepth(3); + + // Begin at the leaf node with bounding box (-8, -8, -8) -> (-1, -1, -1) + // containing voxel (-1, -1, -1). + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); + + // Skip to the leaf node with bounding box (128, 128, 128) -> (135, 135, 135) + // containing voxel (129, 129, 129). + iter.next(); + CPPUNIT_ASSERT(iter); + CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); + + // Verify that no leaf nodes remain unvisited. + iter.next(); + CPPUNIT_ASSERT(!iter); + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestNodeMask.cc b/openvdb_2_3_0_library/openvdb/unittest/TestNodeMask.cc new file mode 100755 index 0000000..cb9a092 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestNodeMask.cc @@ -0,0 +1,172 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +using openvdb::Index; + +template void TestAll(); + +class TestNodeMask: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestNodeMask); + CPPUNIT_TEST(testAll4); + CPPUNIT_TEST(testAll3); + CPPUNIT_TEST(testAll2); + CPPUNIT_TEST(testAll1); + CPPUNIT_TEST_SUITE_END(); + + void testAll4() { TestAll >(); } + void testAll3() { TestAll >(); } + void testAll2() { TestAll >(); } + void testAll1() { TestAll >(); } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestNodeMask); + +template +void TestAll() +{ + CPPUNIT_ASSERT(MaskType::memUsage() == MaskType::SIZE/8); + const Index SIZE = MaskType::SIZE > 512 ? 512 : MaskType::SIZE; + + {// default constructor + MaskType m;//all bits are off + for (Index i=0; i +#include +#include +#include +#include +#include +#include + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + + +class TestParticlesToLevelSet: public CppUnit::TestFixture +{ +public: + virtual void setUp() {openvdb::initialize();} + virtual void tearDown() {openvdb::uninitialize();} + + void writeGrid(openvdb::GridBase::Ptr grid, std::string fileName) const + { + std::cout << "\nWriting \""<setName("TestParticlesToLevelSet"); + openvdb::GridPtrVec grids; + grids.push_back(grid); + openvdb::io::File file("/tmp/" + fileName + ".vdb"); + file.write(grids); + file.close(); + } + + CPPUNIT_TEST_SUITE(TestParticlesToLevelSet); + CPPUNIT_TEST(testMyParticleList); + CPPUNIT_TEST(testRasterizeSpheres); + CPPUNIT_TEST(testRasterizeSpheresAndId); + CPPUNIT_TEST(testRasterizeTrails); + CPPUNIT_TEST(testRasterizeTrailsAndId); + CPPUNIT_TEST_SUITE_END(); + + void testMyParticleList(); + void testRasterizeSpheres(); + void testRasterizeSpheresAndId(); + void testRasterizeTrails(); + void testRasterizeTrailsAndId(); +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION(TestParticlesToLevelSet); + +class MyParticleList +{ +protected: + struct MyParticle { + openvdb::Vec3R p, v; + openvdb::Real r; + }; + openvdb::Real mRadiusScale; + openvdb::Real mVelocityScale; + std::vector mParticleList; +public: + MyParticleList(openvdb::Real rScale=1, openvdb::Real vScale=1) + : mRadiusScale(rScale), mVelocityScale(vScale) {} + void add(const openvdb::Vec3R &p, const openvdb::Real &r, + const openvdb::Vec3R &v=openvdb::Vec3R(0,0,0)) + { + MyParticle pa; + pa.p = p; + pa.r = r; + pa.v = v; + mParticleList.push_back(pa); + } + /// @return coordinate bbox in the space of the specified transfrom + openvdb::CoordBBox getBBox(const openvdb::GridBase& grid) { + openvdb::CoordBBox bbox; + openvdb::Coord &min= bbox.min(), &max = bbox.max(); + openvdb::Vec3R pos; + openvdb::Real rad, invDx = 1/grid.voxelSize()[0]; + for (size_t n=0, e=this->size(); ngetPosRad(n, pos, rad); + const openvdb::Vec3d xyz = grid.worldToIndex(pos); + const openvdb::Real r = rad * invDx; + for (int i=0; i<3; ++i) { + min[i] = openvdb::math::Min(min[i], openvdb::math::Floor(xyz[i] - r)); + max[i] = openvdb::math::Max(max[i], openvdb::math::Ceil( xyz[i] + r)); + } + } + return bbox; + } + //typedef int AttributeType; + // The methods below are only required for the unit-tests + openvdb::Vec3R pos(int n) const {return mParticleList[n].p;} + openvdb::Vec3R vel(int n) const {return mVelocityScale*mParticleList[n].v;} + openvdb::Real radius(int n) const {return mRadiusScale*mParticleList[n].r;} + + ////////////////////////////////////////////////////////////////////////////// + /// The methods below are the only ones required by tools::ParticleToLevelSet + /// @note We return by value since the radius and velocities are modified + /// by the scaling factors! Also these methods are all assumed to + /// be thread-safe. + + /// Return the total number of particles in list. + /// Always required! + size_t size() const { return mParticleList.size(); } + + /// Get the world space position of n'th particle. + /// Required by ParticledToLevelSet::rasterizeSphere(*this,radius). + void getPos(size_t n, openvdb::Vec3R&pos) const { pos = mParticleList[n].p; } + + + void getPosRad(size_t n, openvdb::Vec3R& pos, openvdb::Real& rad) const { + pos = mParticleList[n].p; + rad = mRadiusScale*mParticleList[n].r; + } + void getPosRadVel(size_t n, openvdb::Vec3R& pos, openvdb::Real& rad, openvdb::Vec3R& vel) const { + pos = mParticleList[n].p; + rad = mRadiusScale*mParticleList[n].r; + vel = mVelocityScale*mParticleList[n].v; + } + // The method below is only required for attribute transfer + void getAtt(size_t n, openvdb::Index32& att) const { att = n; } +}; + + +void +TestParticlesToLevelSet::testMyParticleList() +{ + MyParticleList pa; + CPPUNIT_ASSERT_EQUAL(0, int(pa.size())); + pa.add(openvdb::Vec3R(10,10,10), 2, openvdb::Vec3R(1,0,0)); + CPPUNIT_ASSERT_EQUAL(1, int(pa.size())); + ASSERT_DOUBLES_EXACTLY_EQUAL(10, pa.pos(0)[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(10, pa.pos(0)[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(10, pa.pos(0)[2]); + ASSERT_DOUBLES_EXACTLY_EQUAL(1 , pa.vel(0)[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(0)[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(0)[2]); + ASSERT_DOUBLES_EXACTLY_EQUAL(2 , pa.radius(0)); + pa.add(openvdb::Vec3R(20,20,20), 3); + CPPUNIT_ASSERT_EQUAL(2, int(pa.size())); + ASSERT_DOUBLES_EXACTLY_EQUAL(20, pa.pos(1)[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(20, pa.pos(1)[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(20, pa.pos(1)[2]); + ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(1)[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(1)[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(1)[2]); + ASSERT_DOUBLES_EXACTLY_EQUAL(3 , pa.radius(1)); + + const float voxelSize = 0.5f, halfWidth = 4.0f; + openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); + openvdb::CoordBBox bbox = pa.getBBox(*ls); + ASSERT_DOUBLES_EXACTLY_EQUAL((10-2)/voxelSize, bbox.min()[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL((10-2)/voxelSize, bbox.min()[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL((10-2)/voxelSize, bbox.min()[2]); + ASSERT_DOUBLES_EXACTLY_EQUAL((20+3)/voxelSize, bbox.max()[0]); + ASSERT_DOUBLES_EXACTLY_EQUAL((20+3)/voxelSize, bbox.max()[1]); + ASSERT_DOUBLES_EXACTLY_EQUAL((20+3)/voxelSize, bbox.max()[2]); +} + + +void +TestParticlesToLevelSet::testRasterizeSpheres() +{ + MyParticleList pa; + pa.add(openvdb::Vec3R(10,10,10), 2); + pa.add(openvdb::Vec3R(20,20,20), 2); + // testing CSG + pa.add(openvdb::Vec3R(31.0,31,31), 5); + pa.add(openvdb::Vec3R(31.5,31,31), 5); + pa.add(openvdb::Vec3R(32.0,31,31), 5); + pa.add(openvdb::Vec3R(32.5,31,31), 5); + pa.add(openvdb::Vec3R(33.0,31,31), 5); + pa.add(openvdb::Vec3R(33.5,31,31), 5); + pa.add(openvdb::Vec3R(34.0,31,31), 5); + pa.add(openvdb::Vec3R(34.5,31,31), 5); + pa.add(openvdb::Vec3R(35.0,31,31), 5); + pa.add(openvdb::Vec3R(35.5,31,31), 5); + pa.add(openvdb::Vec3R(36.0,31,31), 5); + CPPUNIT_ASSERT_EQUAL(13, int(pa.size())); + + const float voxelSize = 1.0f, halfWidth = 2.0f; + openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); + openvdb::tools::ParticlesToLevelSet raster(*ls); + + raster.setGrainSize(1);//a value of zero disables threading + raster.rasterizeSpheres(pa); + raster.finalize(); + //openvdb::FloatGrid::Ptr ls = raster.getSdfGrid(); + + //ls->tree().print(std::cout,4); + //this->writeGrid(ls, "testRasterizeSpheres"); + + ASSERT_DOUBLES_EXACTLY_EQUAL(halfWidth * voxelSize, + ls->tree().getValue(openvdb::Coord( 0, 0, 0))); + + ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord( 6,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord( 7,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord( 8,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord( 9,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(10,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(11,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(12,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(13,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(14,10,10))); + + ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,16,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,17,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,18,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,19,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(20,20,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,21,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,22,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,23,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,24,20))); + {// full but slow test of all voxels + openvdb::CoordBBox bbox = pa.getBBox(*ls); + bbox.expand(static_cast(halfWidth)+1); + openvdb::Index64 count=0; + const float outside = ls->background(), inside = -outside; + const openvdb::Coord &min=bbox.min(), &max=bbox.max(); + for (openvdb::Coord ijk=min; ijk[0]indexToWorld(ijk.asVec3d()); + double dist = (xyz-pa.pos(0)).length()-pa.radius(0); + for (int i=1, s=pa.size(); itree().getValue(ijk); + if (dist >= outside) { + CPPUNIT_ASSERT_DOUBLES_EQUAL(outside, val, 0.0001); + CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); + } else if( dist <= inside ) { + CPPUNIT_ASSERT_DOUBLES_EQUAL(inside, val, 0.0001); + CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); + } else { + CPPUNIT_ASSERT_DOUBLES_EQUAL( dist, val, 0.0001); + CPPUNIT_ASSERT(ls->tree().isValueOn(ijk)); + ++count; + } + } + } + } + //std::cerr << "\nExpected active voxel count = " << count + // << ", actual active voxle count = " + // << ls->activeVoxelCount() << std::endl; + CPPUNIT_ASSERT_EQUAL(count, ls->activeVoxelCount()); + } +} + + +void +TestParticlesToLevelSet::testRasterizeSpheresAndId() +{ + MyParticleList pa(0.5f); + pa.add(openvdb::Vec3R(10,10,10), 4); + pa.add(openvdb::Vec3R(20,20,20), 4); + // testing CSG + pa.add(openvdb::Vec3R(31.0,31,31),10); + pa.add(openvdb::Vec3R(31.5,31,31),10); + pa.add(openvdb::Vec3R(32.0,31,31),10); + pa.add(openvdb::Vec3R(32.5,31,31),10); + pa.add(openvdb::Vec3R(33.0,31,31),10); + pa.add(openvdb::Vec3R(33.5,31,31),10); + pa.add(openvdb::Vec3R(34.0,31,31),10); + pa.add(openvdb::Vec3R(34.5,31,31),10); + pa.add(openvdb::Vec3R(35.0,31,31),10); + pa.add(openvdb::Vec3R(35.5,31,31),10); + pa.add(openvdb::Vec3R(36.0,31,31),10); + CPPUNIT_ASSERT_EQUAL(13, int(pa.size())); + + typedef openvdb::tools::ParticlesToLevelSet RasterT; + const float voxelSize = 1.0f, halfWidth = 2.0f; + openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); + + RasterT raster(*ls); + raster.setGrainSize(1);//a value of zero disables threading + raster.rasterizeSpheres(pa); + raster.finalize(); + const RasterT::AttGridType::Ptr id = raster.attributeGrid(); + + int minVal = std::numeric_limits::max(), maxVal = -minVal; + for (RasterT::AttGridType::ValueOnCIter i=id->cbeginValueOn(); i; ++i) { + minVal = openvdb::math::Min(minVal, int(*i)); + maxVal = openvdb::math::Max(maxVal, int(*i)); + } + CPPUNIT_ASSERT_EQUAL(0 , minVal); + CPPUNIT_ASSERT_EQUAL(12, maxVal); + + //grid.tree().print(std::cout,4); + //id->print(std::cout,4); + //this->writeGrid(ls, "testRasterizeSpheres"); + + ASSERT_DOUBLES_EXACTLY_EQUAL(halfWidth * voxelSize, + ls->tree().getValue(openvdb::Coord( 0, 0, 0))); + + ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord( 6,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord( 7,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord( 8,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord( 9,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(10,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(11,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(12,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(13,10,10))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(14,10,10))); + + ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,16,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,17,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,18,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,19,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(20,20,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,21,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,22,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,23,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,24,20))); + + {// full but slow test of all voxels + openvdb::CoordBBox bbox = pa.getBBox(*ls); + bbox.expand(static_cast(halfWidth)+1); + openvdb::Index64 count = 0; + const float outside = ls->background(), inside = -outside; + const openvdb::Coord &min=bbox.min(), &max=bbox.max(); + for (openvdb::Coord ijk=min; ijk[0]indexToWorld(ijk.asVec3d()); + double dist = (xyz-pa.pos(0)).length()-pa.radius(0); + openvdb::Index32 k =0; + for (int i=1, s=pa.size(); itree().getValue(ijk); + openvdb::Index32 m = id->tree().getValue(ijk); + if (dist >= outside) { + CPPUNIT_ASSERT_DOUBLES_EQUAL(outside, val, 0.0001); + CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); + //CPPUNIT_ASSERT_EQUAL(openvdb::util::INVALID_IDX, m); + CPPUNIT_ASSERT(id->tree().isValueOff(ijk)); + } else if( dist <= inside ) { + CPPUNIT_ASSERT_DOUBLES_EQUAL(inside, val, 0.0001); + CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); + //CPPUNIT_ASSERT_EQUAL(openvdb::util::INVALID_IDX, m); + CPPUNIT_ASSERT(id->tree().isValueOff(ijk)); + } else { + CPPUNIT_ASSERT_DOUBLES_EQUAL( dist, val, 0.0001); + CPPUNIT_ASSERT(ls->tree().isValueOn(ijk)); + CPPUNIT_ASSERT_EQUAL(k, m); + CPPUNIT_ASSERT(id->tree().isValueOn(ijk)); + ++count; + } + } + } + } + //std::cerr << "\nExpected active voxel count = " << count + // << ", actual active voxle count = " + // << ls->activeVoxelCount() << std::endl; + CPPUNIT_ASSERT_EQUAL(count, ls->activeVoxelCount()); + } +} + + +/// This is not really a conventional unit-test since the result of +/// the tests are written to a file and need to be visually verified! +void +TestParticlesToLevelSet::testRasterizeTrails() +{ + const float voxelSize = 1.0f, halfWidth = 2.0f; + openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); + + MyParticleList pa(1,5); + + // This particle radius = 1 < 1.5 i.e. it's below the Nyquist frequency and hence ignored + pa.add(openvdb::Vec3R( 0, 0, 0), 1, openvdb::Vec3R( 0, 1, 0)); + pa.add(openvdb::Vec3R(-10,-10,-10), 2, openvdb::Vec3R( 2, 0, 0)); + pa.add(openvdb::Vec3R( 10, 10, 10), 3, openvdb::Vec3R( 0, 1, 0)); + pa.add(openvdb::Vec3R( 0, 0, 0), 6, openvdb::Vec3R( 0, 0,-5)); + pa.add(openvdb::Vec3R( 20, 0, 0), 2, openvdb::Vec3R( 0, 0, 0)); + + openvdb::tools::ParticlesToLevelSet raster(*ls); + raster.rasterizeTrails(pa, 0.75);//scale offset between two instances + + //ls->tree().print(std::cout, 4); + //this->writeGrid(ls, "testRasterizeTrails"); +} + + +void +TestParticlesToLevelSet::testRasterizeTrailsAndId() +{ + MyParticleList pa(1,5); + + // This particle radius = 1 < 1.5 i.e. it's below the Nyquist frequency and hence ignored + pa.add(openvdb::Vec3R( 0, 0, 0), 1, openvdb::Vec3R( 0, 1, 0)); + pa.add(openvdb::Vec3R(-10,-10,-10), 2, openvdb::Vec3R( 2, 0, 0)); + pa.add(openvdb::Vec3R( 10, 10, 10), 3, openvdb::Vec3R( 0, 1, 0)); + pa.add(openvdb::Vec3R( 0, 0, 0), 6, openvdb::Vec3R( 0, 0,-5)); + + typedef openvdb::tools::ParticlesToLevelSet RasterT; + const float voxelSize = 1.0f, halfWidth = 2.0f; + openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); + RasterT raster(*ls); + raster.rasterizeTrails(pa, 0.75);//scale offset between two instances + raster.finalize(); + const RasterT::AttGridType::Ptr id = raster.attributeGrid(); + CPPUNIT_ASSERT(!ls->empty()); + CPPUNIT_ASSERT(!id->empty()); + CPPUNIT_ASSERT_EQUAL(ls->activeVoxelCount(),id->activeVoxelCount()); + + int min = std::numeric_limits::max(), max = -min; + for (RasterT::AttGridType::ValueOnCIter i=id->cbeginValueOn(); i; ++i) { + min = openvdb::math::Min(min, int(*i)); + max = openvdb::math::Max(max, int(*i)); + } + CPPUNIT_ASSERT_EQUAL(1, min);//first particle is ignored because of its small rdadius! + CPPUNIT_ASSERT_EQUAL(3, max); + + //ls->tree().print(std::cout, 4); + //this->writeGrid(ls, "testRasterizeTrails"); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestPrePostAPI.cc b/openvdb_2_3_0_library/openvdb/unittest/TestPrePostAPI.cc new file mode 100755 index 0000000..3a2ba7f --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestPrePostAPI.cc @@ -0,0 +1,711 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + + +class TestPrePostAPI: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestPrePostAPI); + + CPPUNIT_TEST(testMat4); + CPPUNIT_TEST(testMat4Rotate); + CPPUNIT_TEST(testMat4Scale); + CPPUNIT_TEST(testMat4Shear); + CPPUNIT_TEST(testMaps); + CPPUNIT_TEST(testLinearTransform); + CPPUNIT_TEST(testFrustumTransform); + + CPPUNIT_TEST_SUITE_END(); + + void testMat4(); + void testMat4Rotate(); + void testMat4Scale(); + void testMat4Shear(); + void testMaps(); + void testLinearTransform(); + void testFrustumTransform(); + //void testIsType(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestPrePostAPI); + + +void +TestPrePostAPI::testMat4() +{ + using namespace openvdb::math; + + double TOL = 1e-7; + + + Mat4d m = Mat4d::identity(); + Mat4d minv = Mat4d::identity(); + + // create matrix with pre-API + // Translate Shear Rotate Translate Scale matrix + m.preScale(Vec3d(1, 2, 3)); + m.preTranslate(Vec3d(2, 3, 4)); + m.preRotate(X_AXIS, 20); + m.preShear(X_AXIS, Y_AXIS, 2); + m.preTranslate(Vec3d(2, 2, 2)); + + // create inverse using the post-API + minv.postScale(Vec3d(1.f, 1.f/2.f, 1.f/3.f)); + minv.postTranslate(-Vec3d(2, 3, 4)); + minv.postRotate(X_AXIS,-20); + minv.postShear(X_AXIS, Y_AXIS, -2); + minv.postTranslate(-Vec3d(2, 2, 2)); + + Mat4d mtest = minv * m; + + // verify that the results is an identity + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][0], 1, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][1], 1, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][2], 1, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][2], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][3], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][2], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][3], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][3], 0, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][2], 0, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][3], 1, TOL); +} + + +void +TestPrePostAPI::testMat4Rotate() +{ + using namespace openvdb::math; + + double TOL = 1e-7; + + Mat4d rx, ry, rz; + const double angle1 = 20. * M_PI / 180.; + const double angle2 = 64. * M_PI / 180.; + const double angle3 = 125. *M_PI / 180.; + rx.setToRotation(Vec3d(1,0,0), angle1); + ry.setToRotation(Vec3d(0,1,0), angle2); + rz.setToRotation(Vec3d(0,0,1), angle3); + + Mat4d shear = Mat4d::identity(); + shear.setToShear(X_AXIS, Z_AXIS, 2.0); + shear.preShear(Y_AXIS, X_AXIS, 3.0); + shear.preTranslate(Vec3d(2,4,1)); + + const Mat4d preResult = rz*ry*rx*shear; + Mat4d mpre = shear; + mpre.preRotate(X_AXIS, angle1); + mpre.preRotate(Y_AXIS, angle2); + mpre.preRotate(Z_AXIS, angle3); + + CPPUNIT_ASSERT( mpre.eq(preResult, TOL) ); + + const Mat4d postResult = shear*rx*ry*rz; + Mat4d mpost = shear; + mpost.postRotate(X_AXIS, angle1); + mpost.postRotate(Y_AXIS, angle2); + mpost.postRotate(Z_AXIS, angle3); + + CPPUNIT_ASSERT( mpost.eq(postResult, TOL) ); + + CPPUNIT_ASSERT( !mpost.eq(mpre, TOL)); + +} + + +void +TestPrePostAPI::testMat4Scale() +{ + using namespace openvdb::math; + + double TOL = 1e-7; + + Mat4d mpre, mpost; + double* pre = mpre.asPointer(); + double* post = mpost.asPointer(); + for (int i = 0; i < 16; ++i) { + pre[i] = double(i); + post[i] = double(i); + } + + Mat4d scale = Mat4d::identity(); + scale.setToScale(Vec3d(2, 3, 5.5)); + Mat4d preResult = scale * mpre; + Mat4d postResult = mpost * scale; + + mpre.preScale(Vec3d(2, 3, 5.5)); + mpost.postScale(Vec3d(2, 3, 5.5)); + + CPPUNIT_ASSERT( mpre.eq(preResult, TOL) ); + CPPUNIT_ASSERT( mpost.eq(postResult, TOL) ); +} + + +void +TestPrePostAPI::testMat4Shear() +{ + using namespace openvdb::math; + + double TOL = 1e-7; + + Mat4d mpre, mpost; + double* pre = mpre.asPointer(); + double* post = mpost.asPointer(); + for (int i = 0; i < 16; ++i) { + pre[i] = double(i); + post[i] = double(i); + } + + Mat4d shear = Mat4d::identity(); + shear.setToShear(X_AXIS, Z_AXIS, 13.); + Mat4d preResult = shear * mpre; + Mat4d postResult = mpost * shear; + + mpre.preShear(X_AXIS, Z_AXIS, 13.); + mpost.postShear(X_AXIS, Z_AXIS, 13.); + + CPPUNIT_ASSERT( mpre.eq(preResult, TOL) ); + CPPUNIT_ASSERT( mpost.eq(postResult, TOL) ); +} + + +void +TestPrePostAPI::testMaps() +{ + using namespace openvdb::math; + + double TOL = 1e-7; + + { // pre translate + UniformScaleMap usm; + UniformScaleTranslateMap ustm; + ScaleMap sm; + ScaleTranslateMap stm; + AffineMap am; + + const Vec3d trans(1,2,3); + Mat4d correct = Mat4d::identity(); + correct.preTranslate(trans); + { + MapBase::Ptr base = usm.preTranslate(trans); + Mat4d result = (base->getAffineMap())->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = ustm.preTranslate(trans)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = sm.preTranslate(trans)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = stm.preTranslate(trans)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = am.preTranslate(trans)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + } + { // post translate + UniformScaleMap usm; + UniformScaleTranslateMap ustm; + ScaleMap sm; + ScaleTranslateMap stm; + AffineMap am; + + const Vec3d trans(1,2,3); + Mat4d correct = Mat4d::identity(); + correct.postTranslate(trans); + { + const Mat4d result = usm.postTranslate(trans)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = ustm.postTranslate(trans)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = sm.postTranslate(trans)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = stm.postTranslate(trans)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = am.postTranslate(trans)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + } + { // pre scale + UniformScaleMap usm; + UniformScaleTranslateMap ustm; + ScaleMap sm; + ScaleTranslateMap stm; + AffineMap am; + + const Vec3d scale(1,2,3); + Mat4d correct = Mat4d::identity(); + correct.preScale(scale); + { + const Mat4d result = usm.preScale(scale)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = ustm.preScale(scale)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = sm.preScale(scale)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = stm.preScale(scale)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = am.preScale(scale)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + } + { // post scale + UniformScaleMap usm; + UniformScaleTranslateMap ustm; + ScaleMap sm; + ScaleTranslateMap stm; + AffineMap am; + + const Vec3d scale(1,2,3); + Mat4d correct = Mat4d::identity(); + correct.postScale(scale); + { + const Mat4d result = usm.postScale(scale)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = ustm.postScale(scale)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = sm.postScale(scale)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = stm.postScale(scale)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = am.postScale(scale)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + } + { // pre shear + UniformScaleMap usm; + UniformScaleTranslateMap ustm; + ScaleMap sm; + ScaleTranslateMap stm; + AffineMap am; + + Mat4d correct = Mat4d::identity(); + correct.preShear(X_AXIS, Z_AXIS, 13.); + { + const Mat4d result = usm.preShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = ustm.preShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = sm.preShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = stm.preShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = am.preShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + } + { // post shear + UniformScaleMap usm; + UniformScaleTranslateMap ustm; + ScaleMap sm; + ScaleTranslateMap stm; + AffineMap am; + + Mat4d correct = Mat4d::identity(); + correct.postShear(X_AXIS, Z_AXIS, 13.); + { + const Mat4d result = usm.postShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = ustm.postShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = sm.postShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = stm.postShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = am.postShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + } + { // pre rotate + const double angle1 = 20. * M_PI / 180.; + UniformScaleMap usm; + UniformScaleTranslateMap ustm; + ScaleMap sm; + ScaleTranslateMap stm; + AffineMap am; + + Mat4d correct = Mat4d::identity(); + correct.preRotate(X_AXIS, angle1); + { + const Mat4d result = usm.preRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = ustm.preRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = sm.preRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = stm.preRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = am.preRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + } + { // post rotate + const double angle1 = 20. * M_PI / 180.; + UniformScaleMap usm; + UniformScaleTranslateMap ustm; + ScaleMap sm; + ScaleTranslateMap stm; + AffineMap am; + + Mat4d correct = Mat4d::identity(); + correct.postRotate(X_AXIS, angle1); + { + const Mat4d result = usm.postRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = ustm.postRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = sm.postRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = stm.postRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + { + const Mat4d result = am.postRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); + CPPUNIT_ASSERT( correct.eq(result, TOL)); + } + } +} + + +void +TestPrePostAPI::testLinearTransform() +{ + using namespace openvdb::math; + + double TOL = 1e-7; + { + Transform::Ptr t = Transform::createLinearTransform(1.f); + Transform::Ptr tinv = Transform::createLinearTransform(1.f); + + // create matrix with pre-API + // Translate Shear Rotate Translate Scale matrix + t->preScale(Vec3d(1, 2, 3)); + t->preTranslate(Vec3d(2, 3, 4)); + t->preRotate(20); + t->preShear(2, X_AXIS, Y_AXIS); + t->preTranslate(Vec3d(2, 2, 2)); + + // create inverse using the post-API + tinv->postScale(Vec3d(1.f, 1.f/2.f, 1.f/3.f)); + tinv->postTranslate(-Vec3d(2, 3, 4)); + tinv->postRotate(-20); + tinv->postShear(-2, X_AXIS, Y_AXIS); + tinv->postTranslate(-Vec3d(2, 2, 2)); + + + // test this by verifying that equvilent interal matrix + // represenations are inverses + Mat4d m = t->baseMap()->getAffineMap()->getMat4(); + Mat4d minv = tinv->baseMap()->getAffineMap()->getMat4(); + + Mat4d mtest = minv * m; + + // verify that the results is an identity + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][0], 1, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][1], 1, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][2], 1, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][2], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][3], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][2], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][3], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][3], 0, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][2], 0, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][3], 1, TOL); + } + + { + Transform::Ptr t = Transform::createLinearTransform(1.f); + + Mat4d m = Mat4d::identity(); + + // create matrix with pre-API + // Translate Shear Rotate Translate Scale matrix + m.preScale(Vec3d(1, 2, 3)); + m.preTranslate(Vec3d(2, 3, 4)); + m.preRotate(X_AXIS, 20); + m.preShear(X_AXIS, Y_AXIS, 2); + m.preTranslate(Vec3d(2, 2, 2)); + + t->preScale(Vec3d(1,2,3)); + t->preMult(m); + t->postMult(m); + + Mat4d minv = Mat4d::identity(); + + // create inverse using the post-API + minv.postScale(Vec3d(1.f, 1.f/2.f, 1.f/3.f)); + minv.postTranslate(-Vec3d(2, 3, 4)); + minv.postRotate(X_AXIS,-20); + minv.postShear(X_AXIS, Y_AXIS, -2); + minv.postTranslate(-Vec3d(2, 2, 2)); + + t->preMult(minv); + t->postMult(minv); + + Mat4d mtest = t->baseMap()->getAffineMap()->getMat4(); + + + // verify that the results is the scale + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][0], 1, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][1], 2, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][2], 3, 1e-6); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][2], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][3], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][2], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][3], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][3], 0, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][0], 0, 1e-6); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][1], 0, 1e-6); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][2], 0, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][3], 1, TOL); + } + + +} + + +void +TestPrePostAPI::testFrustumTransform() +{ + using namespace openvdb::math; + + typedef BBox BBoxd; + + double TOL = 1e-7; + { + + BBoxd bbox(Vec3d(-5,-5,0), Vec3d(5,5,10)); + Transform::Ptr t = Transform::createFrustumTransform(bbox, /* taper*/ 1, /*depth*/10, /* voxel size */1.f); + Transform::Ptr tinv = Transform::createFrustumTransform(bbox, /* taper*/ 1, /*depth*/10, /* voxel size */1.f); + + + // create matrix with pre-API + // Translate Shear Rotate Translate Scale matrix + t->preScale(Vec3d(1, 2, 3)); + t->preTranslate(Vec3d(2, 3, 4)); + t->preRotate(20); + t->preShear(2, X_AXIS, Y_AXIS); + t->preTranslate(Vec3d(2, 2, 2)); + + // create inverse using the post-API + tinv->postScale(Vec3d(1.f, 1.f/2.f, 1.f/3.f)); + tinv->postTranslate(-Vec3d(2, 3, 4)); + tinv->postRotate(-20); + tinv->postShear(-2, X_AXIS, Y_AXIS); + tinv->postTranslate(-Vec3d(2, 2, 2)); + + + // test this by verifying that equvilent interal matrix + // represenations are inverses + NonlinearFrustumMap::Ptr frustum = boost::static_pointer_cast( t->baseMap() ); + NonlinearFrustumMap::Ptr frustuminv = boost::static_pointer_cast( tinv->baseMap() ); + + Mat4d m = frustum->secondMap().getMat4(); + Mat4d minv = frustuminv->secondMap().getMat4(); + + Mat4d mtest = minv * m; + + // verify that the results is an identity + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][0], 1, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][1], 1, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][2], 1, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][2], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][3], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][2], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][3], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][3], 0, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][2], 0, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][3], 1, TOL); + } + + { + + BBoxd bbox(Vec3d(-5,-5,0), Vec3d(5,5,10)); + Transform::Ptr t = Transform::createFrustumTransform(bbox, /* taper*/ 1, /*depth*/10, /* voxel size */1.f); + + + Mat4d m = Mat4d::identity(); + + // create matrix with pre-API + // Translate Shear Rotate Translate Scale matrix + m.preScale(Vec3d(1, 2, 3)); + m.preTranslate(Vec3d(2, 3, 4)); + m.preRotate(X_AXIS, 20); + m.preShear(X_AXIS, Y_AXIS, 2); + m.preTranslate(Vec3d(2, 2, 2)); + + t->preScale(Vec3d(1,2,3)); + t->preMult(m); + t->postMult(m); + + Mat4d minv = Mat4d::identity(); + + // create inverse using the post-API + minv.postScale(Vec3d(1.f, 1.f/2.f, 1.f/3.f)); + minv.postTranslate(-Vec3d(2, 3, 4)); + minv.postRotate(X_AXIS,-20); + minv.postShear(X_AXIS, Y_AXIS, -2); + minv.postTranslate(-Vec3d(2, 2, 2)); + + t->preMult(minv); + t->postMult(minv); + + NonlinearFrustumMap::Ptr frustum = boost::static_pointer_cast( t->baseMap() ); + Mat4d mtest = frustum->secondMap().getMat4(); + + // verify that the results is the scale + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][0], 1, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][1], 2, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][2], 3, 1e-6); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][2], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][3], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][2], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][3], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][0], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][1], 0, TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][3], 0, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][0], 0, 1e-6); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][1], 0, 1e-6); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][2], 0, TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][3], 1, TOL); + } + + +} + + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestQuadraticInterp.cc b/openvdb_2_3_0_library/openvdb/unittest/TestQuadraticInterp.cc new file mode 100755 index 0000000..a02f71d --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestQuadraticInterp.cc @@ -0,0 +1,361 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file TestQuadraticInterp.cc + +#include +#include +#include +#include + +// CPPUNIT_TEST_SUITE() invokes CPPUNIT_TESTNAMER_DECL() to generate a suite name +// from the FixtureType. But if FixtureType is a templated type, the generated name +// can become long and messy. This macro overrides the normal naming logic, +// instead invoking FixtureType::testSuiteName(), which should be a static member +// function that returns a std::string containing the suite name for the specific +// template instantiation. +#undef CPPUNIT_TESTNAMER_DECL +#define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ + CPPUNIT_NS::TestNamer variableName( FixtureType::testSuiteName() ) + + +namespace { +// Absolute tolerance for floating-point equality comparisons +const double TOLERANCE = 1.0e-5; +} + + +//////////////////////////////////////// + + +template +class TestQuadraticInterp: public CppUnit::TestCase +{ +public: + typedef typename GridType::ValueType ValueT; + typedef typename GridType::Ptr GridPtr; + struct TestVal { float x, y, z; ValueT expected; }; + + static std::string testSuiteName() + { + std::string name = openvdb::typeNameAsString(); + if (!name.empty()) name[0] = ::toupper(name[0]); + return "TestQuadraticInterp" + name; + } + + CPPUNIT_TEST_SUITE(TestQuadraticInterp); + CPPUNIT_TEST(test); + CPPUNIT_TEST(testConstantValues); + CPPUNIT_TEST(testFillValues); + CPPUNIT_TEST(testNegativeIndices); + CPPUNIT_TEST_SUITE_END(); + + void test(); + void testConstantValues(); + void testFillValues(); + void testNegativeIndices(); + +private: + void executeTest(const GridPtr&, const TestVal*, size_t numVals) const; + + /// Initialize an arbitrary ValueType from a scalar. + static inline ValueT constValue(double d) { return ValueT(d); } + + /// Compare two numeric values for equality within an absolute tolerance. + static inline bool relEq(const ValueT& v1, const ValueT& v2) + { return fabs(v1 - v2) <= TOLERANCE; } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestQuadraticInterp); +CPPUNIT_TEST_SUITE_REGISTRATION(TestQuadraticInterp); +CPPUNIT_TEST_SUITE_REGISTRATION(TestQuadraticInterp); + + +//////////////////////////////////////// + + +/// Specialization for Vec3s grids +template<> +inline openvdb::Vec3s +TestQuadraticInterp::constValue(double d) +{ + return openvdb::Vec3s(d, d, d); +} + +/// Specialization for Vec3s grids +template<> +inline bool +TestQuadraticInterp::relEq( + const openvdb::Vec3s& v1, const openvdb::Vec3s& v2) +{ + return v1.eq(v2, TOLERANCE); +} + + +/// Sample the given tree at various locations and assert if +/// any of the sampled values don't match the expected values. +template +void +TestQuadraticInterp::executeTest(const GridPtr& grid, + const TestVal* testVals, size_t numVals) const +{ + openvdb::tools::GridSampler interpolator(*grid); + //openvdb::tools::QuadraticInterp interpolator(*tree); + + for (size_t i = 0; i < numVals; ++i) { + const TestVal& val = testVals[i]; + const ValueT actual = interpolator.sampleVoxel(val.x, val.y, val.z); + if (!relEq(val.expected, actual)) { + std::ostringstream ostr; + ostr << std::setprecision(10) + << "sampleVoxel(" << val.x << ", " << val.y << ", " << val.z + << "): expected " << val.expected << ", got " << actual; + CPPUNIT_FAIL(ostr.str()); + } + } +} + + +template +void +TestQuadraticInterp::test() +{ + const ValueT + one = constValue(1), + two = constValue(2), + three = constValue(3), + four = constValue(4), + fillValue = constValue(256); + + GridPtr grid(new GridType(fillValue)); + typename GridType::TreeType& tree = grid->tree(); + + tree.setValue(openvdb::Coord(10, 10, 10), one); + + tree.setValue(openvdb::Coord(11, 10, 10), two); + tree.setValue(openvdb::Coord(11, 11, 10), two); + tree.setValue(openvdb::Coord(10, 11, 10), two); + tree.setValue(openvdb::Coord( 9, 11, 10), two); + tree.setValue(openvdb::Coord( 9, 10, 10), two); + tree.setValue(openvdb::Coord( 9, 9, 10), two); + tree.setValue(openvdb::Coord(10, 9, 10), two); + tree.setValue(openvdb::Coord(11, 9, 10), two); + + tree.setValue(openvdb::Coord(10, 10, 11), three); + tree.setValue(openvdb::Coord(11, 10, 11), three); + tree.setValue(openvdb::Coord(11, 11, 11), three); + tree.setValue(openvdb::Coord(10, 11, 11), three); + tree.setValue(openvdb::Coord( 9, 11, 11), three); + tree.setValue(openvdb::Coord( 9, 10, 11), three); + tree.setValue(openvdb::Coord( 9, 9, 11), three); + tree.setValue(openvdb::Coord(10, 9, 11), three); + tree.setValue(openvdb::Coord(11, 9, 11), three); + + tree.setValue(openvdb::Coord(10, 10, 9), four); + tree.setValue(openvdb::Coord(11, 10, 9), four); + tree.setValue(openvdb::Coord(11, 11, 9), four); + tree.setValue(openvdb::Coord(10, 11, 9), four); + tree.setValue(openvdb::Coord( 9, 11, 9), four); + tree.setValue(openvdb::Coord( 9, 10, 9), four); + tree.setValue(openvdb::Coord( 9, 9, 9), four); + tree.setValue(openvdb::Coord(10, 9, 9), four); + tree.setValue(openvdb::Coord(11, 9, 9), four); + + const TestVal testVals[] = { + { 10.5, 10.5, 10.5, constValue(1.703125) }, + { 10.0, 10.0, 10.0, one }, + { 11.0, 10.0, 10.0, two }, + { 11.0, 11.0, 10.0, two }, + { 11.0, 11.0, 11.0, three }, + { 9.0, 11.0, 9.0, four }, + { 9.0, 10.0, 9.0, four }, + { 10.1, 10.0, 10.0, constValue(1.01) }, + { 10.8, 10.8, 10.8, constValue(2.513344) }, + { 10.1, 10.8, 10.5, constValue(1.8577) }, + { 10.8, 10.1, 10.5, constValue(1.8577) }, + { 10.5, 10.1, 10.8, constValue(2.2927) }, + { 10.5, 10.8, 10.1, constValue(1.6977) }, + }; + const size_t numVals = sizeof(testVals) / sizeof(TestVal); + + executeTest(grid, testVals, numVals); +} + + +template +void +TestQuadraticInterp::testConstantValues() +{ + const ValueT + two = constValue(2), + fillValue = constValue(256); + + GridPtr grid(new GridType(fillValue)); + typename GridType::TreeType& tree = grid->tree(); + + tree.setValue(openvdb::Coord(10, 10, 10), two); + + tree.setValue(openvdb::Coord(11, 10, 10), two); + tree.setValue(openvdb::Coord(11, 11, 10), two); + tree.setValue(openvdb::Coord(10, 11, 10), two); + tree.setValue(openvdb::Coord( 9, 11, 10), two); + tree.setValue(openvdb::Coord( 9, 10, 10), two); + tree.setValue(openvdb::Coord( 9, 9, 10), two); + tree.setValue(openvdb::Coord(10, 9, 10), two); + tree.setValue(openvdb::Coord(11, 9, 10), two); + + tree.setValue(openvdb::Coord(10, 10, 11), two); + tree.setValue(openvdb::Coord(11, 10, 11), two); + tree.setValue(openvdb::Coord(11, 11, 11), two); + tree.setValue(openvdb::Coord(10, 11, 11), two); + tree.setValue(openvdb::Coord( 9, 11, 11), two); + tree.setValue(openvdb::Coord( 9, 10, 11), two); + tree.setValue(openvdb::Coord( 9, 9, 11), two); + tree.setValue(openvdb::Coord(10, 9, 11), two); + tree.setValue(openvdb::Coord(11, 9, 11), two); + + tree.setValue(openvdb::Coord(10, 10, 9), two); + tree.setValue(openvdb::Coord(11, 10, 9), two); + tree.setValue(openvdb::Coord(11, 11, 9), two); + tree.setValue(openvdb::Coord(10, 11, 9), two); + tree.setValue(openvdb::Coord( 9, 11, 9), two); + tree.setValue(openvdb::Coord( 9, 10, 9), two); + tree.setValue(openvdb::Coord( 9, 9, 9), two); + tree.setValue(openvdb::Coord(10, 9, 9), two); + tree.setValue(openvdb::Coord(11, 9, 9), two); + + const TestVal testVals[] = { + { 10.5, 10.5, 10.5, two }, + { 10.0, 10.0, 10.0, two }, + { 10.1, 10.0, 10.0, two }, + { 10.8, 10.8, 10.8, two }, + { 10.1, 10.8, 10.5, two }, + { 10.8, 10.1, 10.5, two }, + { 10.5, 10.1, 10.8, two }, + { 10.5, 10.8, 10.1, two } + }; + const size_t numVals = sizeof(testVals) / sizeof(TestVal); + + executeTest(grid, testVals, numVals); +} + + +template +void +TestQuadraticInterp::testFillValues() +{ + const ValueT fillValue = constValue(256); + + GridPtr grid(new GridType(fillValue)); + + const TestVal testVals[] = { + { 10.5, 10.5, 10.5, fillValue }, + { 10.0, 10.0, 10.0, fillValue }, + { 10.1, 10.0, 10.0, fillValue }, + { 10.8, 10.8, 10.8, fillValue }, + { 10.1, 10.8, 10.5, fillValue }, + { 10.8, 10.1, 10.5, fillValue }, + { 10.5, 10.1, 10.8, fillValue }, + { 10.5, 10.8, 10.1, fillValue } + }; + const size_t numVals = sizeof(testVals) / sizeof(TestVal); + + executeTest(grid, testVals, numVals); +} + + +template +void +TestQuadraticInterp::testNegativeIndices() +{ + const ValueT + one = constValue(1), + two = constValue(2), + three = constValue(3), + four = constValue(4), + fillValue = constValue(256); + + GridPtr grid(new GridType(fillValue)); + typename GridType::TreeType& tree = grid->tree(); + + tree.setValue(openvdb::Coord(-10, -10, -10), one); + + tree.setValue(openvdb::Coord(-11, -10, -10), two); + tree.setValue(openvdb::Coord(-11, -11, -10), two); + tree.setValue(openvdb::Coord(-10, -11, -10), two); + tree.setValue(openvdb::Coord( -9, -11, -10), two); + tree.setValue(openvdb::Coord( -9, -10, -10), two); + tree.setValue(openvdb::Coord( -9, -9, -10), two); + tree.setValue(openvdb::Coord(-10, -9, -10), two); + tree.setValue(openvdb::Coord(-11, -9, -10), two); + + tree.setValue(openvdb::Coord(-10, -10, -11), three); + tree.setValue(openvdb::Coord(-11, -10, -11), three); + tree.setValue(openvdb::Coord(-11, -11, -11), three); + tree.setValue(openvdb::Coord(-10, -11, -11), three); + tree.setValue(openvdb::Coord( -9, -11, -11), three); + tree.setValue(openvdb::Coord( -9, -10, -11), three); + tree.setValue(openvdb::Coord( -9, -9, -11), three); + tree.setValue(openvdb::Coord(-10, -9, -11), three); + tree.setValue(openvdb::Coord(-11, -9, -11), three); + + tree.setValue(openvdb::Coord(-10, -10, -9), four); + tree.setValue(openvdb::Coord(-11, -10, -9), four); + tree.setValue(openvdb::Coord(-11, -11, -9), four); + tree.setValue(openvdb::Coord(-10, -11, -9), four); + tree.setValue(openvdb::Coord( -9, -11, -9), four); + tree.setValue(openvdb::Coord( -9, -10, -9), four); + tree.setValue(openvdb::Coord( -9, -9, -9), four); + tree.setValue(openvdb::Coord(-10, -9, -9), four); + tree.setValue(openvdb::Coord(-11, -9, -9), four); + + const TestVal testVals[] = { + { -10.5, -10.5, -10.5, constValue(-104.75586) }, + { -10.0, -10.0, -10.0, one }, + { -11.0, -10.0, -10.0, two }, + { -11.0, -11.0, -10.0, two }, + { -11.0, -11.0, -11.0, three }, + { -9.0, -11.0, -9.0, four }, + { -9.0, -10.0, -9.0, four }, + { -10.1, -10.0, -10.0, constValue(-10.28504) }, + { -10.8, -10.8, -10.8, constValue(-62.84878) }, + { -10.1, -10.8, -10.5, constValue(-65.68951) }, + { -10.8, -10.1, -10.5, constValue(-65.68951) }, + { -10.5, -10.1, -10.8, constValue(-65.40736) }, + { -10.5, -10.8, -10.1, constValue(-66.30510) }, + }; + const size_t numVals = sizeof(testVals) / sizeof(TestVal); + + executeTest(grid, testVals, numVals); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestQuantizedUnitVec.cc b/openvdb_2_3_0_library/openvdb/unittest/TestQuantizedUnitVec.cc new file mode 100755 index 0000000..30159aa --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestQuantizedUnitVec.cc @@ -0,0 +1,177 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class TestQuantizedUnitVec: public CppUnit::TestFixture +{ +public: + CPPUNIT_TEST_SUITE(TestQuantizedUnitVec); + CPPUNIT_TEST(testQuantization); + CPPUNIT_TEST_SUITE_END(); + + void testQuantization(); + +private: + // Generate a random number in the range [0, 1]. + double randNumber() { return double(rand()) / (double(RAND_MAX) + 1.0); } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestQuantizedUnitVec); + + +//////////////////////////////////////// + + +namespace { +const uint16_t + MASK_XSIGN = 0x8000, // 1000000000000000 + MASK_YSIGN = 0x4000, // 0100000000000000 + MASK_ZSIGN = 0x2000; // 0010000000000000 +} + + +//////////////////////////////////////// + + +void +TestQuantizedUnitVec::testQuantization() +{ + using namespace openvdb; + using namespace openvdb::math; + + // + // Check sign bits + // + Vec3s unitVec = Vec3s(-1.0, -1.0, -1.0); + unitVec.normalize(); + + uint16_t quantizedVec = QuantizedUnitVec::pack(unitVec); + + CPPUNIT_ASSERT((quantizedVec & MASK_XSIGN)); + CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); + CPPUNIT_ASSERT((quantizedVec & MASK_ZSIGN)); + + unitVec[0] = -unitVec[0]; + unitVec[2] = -unitVec[2]; + quantizedVec = QuantizedUnitVec::pack(unitVec); + + CPPUNIT_ASSERT(!(quantizedVec & MASK_XSIGN)); + CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); + CPPUNIT_ASSERT(!(quantizedVec & MASK_ZSIGN)); + + unitVec[1] = -unitVec[1]; + quantizedVec = QuantizedUnitVec::pack(unitVec); + + CPPUNIT_ASSERT(!(quantizedVec & MASK_XSIGN)); + CPPUNIT_ASSERT(!(quantizedVec & MASK_YSIGN)); + CPPUNIT_ASSERT(!(quantizedVec & MASK_ZSIGN)); + + QuantizedUnitVec::flipSignBits(quantizedVec); + + CPPUNIT_ASSERT((quantizedVec & MASK_XSIGN)); + CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); + CPPUNIT_ASSERT((quantizedVec & MASK_ZSIGN)); + + unitVec[2] = -unitVec[2]; + quantizedVec = QuantizedUnitVec::pack(unitVec); + QuantizedUnitVec::flipSignBits(quantizedVec); + + CPPUNIT_ASSERT((quantizedVec & MASK_XSIGN)); + CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); + CPPUNIT_ASSERT(!(quantizedVec & MASK_ZSIGN)); + + // + // Check conversion error + // + const double tol = 0.015; // component error tolerance + + const int numNormals = 40000; + + + // init + srand(0); + const int n = int(std::sqrt(double(numNormals))); + const double xScale = (2.0 * M_PI) / double(n); + const double yScale = M_PI / double(n); + + double x, y, theta, phi; + Vec3s n0, n1; + + // generate random normals, by uniformly distributing points on a unit-sphere. + + // loop over a [0 to n) x [0 to n) grid. + for (int a = 0; a < n; ++a) { + for (int b = 0; b < n; ++b) { + + // jitter, move to random pos. inside the current cell + x = double(a) + randNumber(); + y = double(b) + randNumber(); + + // remap to a lat/long map + theta = y * yScale; // [0 to PI] + phi = x * xScale; // [0 to 2PI] + + // convert to cartesian coordinates on a unit sphere. + // spherical coordinate triplet (r=1, theta, phi) + n0[0] = float(std::sin(theta)*std::cos(phi)); + n0[1] = float(std::sin(theta)*std::sin(phi)); + n0[2] = float(std::cos(theta)); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(n0.length(), 1.0, 1e-6); + + n1 = QuantizedUnitVec::unpack(QuantizedUnitVec::pack(n0)); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(n1.length(), 1.0, 1e-6); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(n0[0], n1[0], tol); + CPPUNIT_ASSERT_DOUBLES_EQUAL(n0[1], n1[1], tol); + CPPUNIT_ASSERT_DOUBLES_EQUAL(n0[2], n1[2], tol); + + float sumDiff = std::abs(n0[0] - n1[0]) + std::abs(n0[1] - n1[1]) + + std::abs(n0[2] - n1[2]); + + CPPUNIT_ASSERT(sumDiff < (2.0 * tol)); + } + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestQuat.cc b/openvdb_2_3_0_library/openvdb/unittest/TestQuat.cc new file mode 100755 index 0000000..fd88fe6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestQuat.cc @@ -0,0 +1,313 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +using namespace openvdb::math; + +class TestQuat: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE( TestQuat ); + CPPUNIT_TEST( testConstructor ); + CPPUNIT_TEST( testAxisAngle ); + CPPUNIT_TEST( testOpPlus ); + CPPUNIT_TEST( testOpMinus ); + CPPUNIT_TEST( testOpMultiply ); + CPPUNIT_TEST( testInvert ); + CPPUNIT_TEST( testEulerAngles ); + CPPUNIT_TEST_SUITE_END(); + + void testConstructor(); + void testAxisAngle(); + void testOpPlus(); + void testOpMinus(); + void testOpMultiply(); + void testInvert(); + void testEulerAngles(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestQuat); + + +void +TestQuat::testConstructor() +{ + { + Quat qq(1.23, 2.34, 3.45, 4.56); + CPPUNIT_ASSERT( isExactlyEqual(qq.x(), 1.23f) ); + CPPUNIT_ASSERT( isExactlyEqual(qq.y(), 2.34f) ); + CPPUNIT_ASSERT( isExactlyEqual(qq.z(), 3.45f) ); + CPPUNIT_ASSERT( isExactlyEqual(qq.w(), 4.56f) ); + } + + { + float a[] = {1.23, 2.34, 3.45, 4.56}; + Quat qq(a); + CPPUNIT_ASSERT( isExactlyEqual(qq.x(), 1.23f) ); + CPPUNIT_ASSERT( isExactlyEqual(qq.y(), 2.34f) ); + CPPUNIT_ASSERT( isExactlyEqual(qq.z(), 3.45f) ); + CPPUNIT_ASSERT( isExactlyEqual(qq.w(), 4.56f) ); + } +} + + +void +TestQuat::testAxisAngle() +{ + float TOL = 1e-6; + + Quat q1(1 , 2 , 3 , 4 ); + Quat q2(1.2, 2.3, 3.4, 4.5); + + Vec3s v(1, 2, 3); + v.normalize(); + float a = M_PI/4; + + Quat q(v,a); + float b = q.angle(); + Vec3s vv = q.axis(); + + CPPUNIT_ASSERT( isApproxEqual(a, b, TOL) ); + CPPUNIT_ASSERT( v.eq(vv, TOL) ); + + q1.setAxisAngle(v,a); + b = q1.angle(); + vv = q1.axis(); + CPPUNIT_ASSERT( isApproxEqual(a, b, TOL) ); + CPPUNIT_ASSERT( v.eq(vv, TOL) ); +} + + +void +TestQuat::testOpPlus() +{ + Quat q1(1 , 2 , 3 , 4 ); + Quat q2(1.2, 2.3, 3.4, 4.5); + + Quat q = q1 + q2; + + float + x=q1.x()+q2.x(), y=q1.y()+q2.y(), z=q1.z()+q2.z(), w=q1.w()+q2.w(); + CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); + CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); + CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); + CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); + + q = q1; + q += q2; + CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); + CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); + CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); + CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); + + q.add(q1,q2); + CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); + CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); + CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); + CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); +} + + +void +TestQuat::testOpMinus() +{ + Quat q1(1 , 2 , 3 , 4 ); + Quat q2(1.2, 2.3, 3.4, 4.5); + + Quat q = q1 - q2; + + float + x=q1.x()-q2.x(), y=q1.y()-q2.y(), z=q1.z()-q2.z(), w=q1.w()-q2.w(); + CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); + CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); + CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); + CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); + + q = q1; + q -= q2; + CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); + CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); + CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); + CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); + + q.sub(q1,q2); + CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); + CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); + CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); + CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); +} + + +void +TestQuat::testOpMultiply() +{ + Quat q1(1 , 2 , 3 , 4 ); + Quat q2(1.2, 2.3, 3.4, 4.5); + + Quat q = q1 * 1.5f; + + CPPUNIT_ASSERT( isExactlyEqual(q.x(), float(1.5f)*q1.x()) ); + CPPUNIT_ASSERT( isExactlyEqual(q.y(), float(1.5f)*q1.y()) ); + CPPUNIT_ASSERT( isExactlyEqual(q.z(), float(1.5f)*q1.z()) ); + CPPUNIT_ASSERT( isExactlyEqual(q.w(), float(1.5f)*q1.w()) ); + + q = q1; + q *= 1.5f; + CPPUNIT_ASSERT( isExactlyEqual(q.x(), float(1.5f)*q1.x()) ); + CPPUNIT_ASSERT( isExactlyEqual(q.y(), float(1.5f)*q1.y()) ); + CPPUNIT_ASSERT( isExactlyEqual(q.z(), float(1.5f)*q1.z()) ); + CPPUNIT_ASSERT( isExactlyEqual(q.w(), float(1.5f)*q1.w()) ); + + q.scale(1.5f, q1); + CPPUNIT_ASSERT( isExactlyEqual(q.x(), float(1.5f)*q1.x()) ); + CPPUNIT_ASSERT( isExactlyEqual(q.y(), float(1.5f)*q1.y()) ); + CPPUNIT_ASSERT( isExactlyEqual(q.z(), float(1.5f)*q1.z()) ); + CPPUNIT_ASSERT( isExactlyEqual(q.w(), float(1.5f)*q1.w()) ); +} + + +void +TestQuat::testInvert() +{ + float TOL = 1e-6; + + Quat q1(1 , 2 , 3 , 4 ); + Quat q2(1.2, 2.3, 3.4, 4.5); + + + q1 = q2; + q2 = q2.inverse(); + + Quat q = q1*q2; + + CPPUNIT_ASSERT( q.eq( Quat(0,0,0,1), TOL ) ); + + q1.normalize(); + q2 = q1.conjugate(); + q = q1*q2; + CPPUNIT_ASSERT( q.eq( Quat(0,0,0,1), TOL ) ); +} + + +void +TestQuat::testEulerAngles() +{ + + { + double TOL = 1e-7; + + Mat4d rx, ry, rz; + const double angle1 = 20. * M_PI / 180.; + const double angle2 = 64. * M_PI / 180.; + const double angle3 = 125. *M_PI / 180.; + rx.setToRotation(Vec3d(1,0,0), angle1); + ry.setToRotation(Vec3d(0,1,0), angle2); + rz.setToRotation(Vec3d(0,0,1), angle3); + + Mat4d r = rx * ry * rz; + + const Quat rot(r.getMat3()); + Vec3d result = rot.eulerAngles(ZYX_ROTATION); + + rx.setToRotation(Vec3d(1,0,0), result[0]); + ry.setToRotation(Vec3d(0,1,0), result[1]); + rz.setToRotation(Vec3d(0,0,1), result[2]); + + Mat4d rtest = rx * ry * rz; + + CPPUNIT_ASSERT(r.eq(rtest, TOL)); + } + + { + double TOL = 1e-7; + + Mat4d rx, ry, rz; + const double angle1 = 20. * M_PI / 180.; + const double angle2 = 64. * M_PI / 180.; + const double angle3 = 125. *M_PI / 180.; + rx.setToRotation(Vec3d(1,0,0), angle1); + ry.setToRotation(Vec3d(0,1,0), angle2); + rz.setToRotation(Vec3d(0,0,1), angle3); + + Mat4d r = rz * ry * rx; + + const Quat rot(r.getMat3()); + Vec3d result = rot.eulerAngles(XYZ_ROTATION); + + rx.setToRotation(Vec3d(1,0,0), result[0]); + ry.setToRotation(Vec3d(0,1,0), result[1]); + rz.setToRotation(Vec3d(0,0,1), result[2]); + + Mat4d rtest = rz * ry * rx; + + CPPUNIT_ASSERT(r.eq(rtest, TOL)); + } + + { + double TOL = 1e-7; + + Mat4d rx, ry, rz; + const double angle1 = 20. * M_PI / 180.; + const double angle2 = 64. * M_PI / 180.; + const double angle3 = 125. *M_PI / 180.; + rx.setToRotation(Vec3d(1,0,0), angle1); + ry.setToRotation(Vec3d(0,1,0), angle2); + rz.setToRotation(Vec3d(0,0,1), angle3); + + Mat4d r = rz * rx * ry; + + const Quat rot(r.getMat3()); + Vec3d result = rot.eulerAngles(YXZ_ROTATION); + + rx.setToRotation(Vec3d(1,0,0), result[0]); + ry.setToRotation(Vec3d(0,1,0), result[1]); + rz.setToRotation(Vec3d(0,0,1), result[2]); + + Mat4d rtest = rz * rx * ry; + + CPPUNIT_ASSERT(r.eq(rtest, TOL)); + } + + { + const Quat rot(X_AXIS, 1.0); + Vec3s result = rot.eulerAngles(XZY_ROTATION); + CPPUNIT_ASSERT_EQUAL(result, Vec3s(1,0,0)); + } + +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestRay.cc b/openvdb_2_3_0_library/openvdb/unittest/TestRay.cc new file mode 100755 index 0000000..fe7a4f8 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestRay.cc @@ -0,0 +1,494 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 // for ISGradient +#include +#include +#include + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + + +class TestStats: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestStats); + CPPUNIT_TEST(testStats); + CPPUNIT_TEST(testGridStats); + CPPUNIT_TEST(testGridHistogram); + CPPUNIT_TEST(testGridOperatorStats); + CPPUNIT_TEST_SUITE_END(); + + void testStats(); + void testGridStats(); + void testGridHistogram(); + void testGridOperatorStats(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestStats); + + +void +TestStats::testStats() +{ + {// trivial test + openvdb::math::Stats s; + s.add(0); + s.add(1); + CPPUNIT_ASSERT_EQUAL(2, int(s.size())); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.min(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, s.max(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, s.mean(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.25, s.variance(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, s.stdDev(), 0.000001); + //s.print("test"); + } + {// non-trivial test + openvdb::math::Stats s; + const int data[5]={600, 470, 170, 430, 300}; + for (int i=0; i<5; ++i) s.add(data[i]); + double sum = 0.0; + for (int i=0; i<5; ++i) sum += data[i]; + const double mean = sum/5.0; + sum = 0.0; + for (int i=0; i<5; ++i) sum += (data[i]-mean)*(data[i]-mean); + const double var = sum/5.0; + CPPUNIT_ASSERT_EQUAL(5, int(s.size())); + CPPUNIT_ASSERT_DOUBLES_EQUAL(data[2], s.min(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(data[0], s.max(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mean, s.mean(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(var, s.variance(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(sqrt(var), s.stdDev(), 0.000001); + //s.print("test"); + } + {// non-trivial test of Stats::add(Stats) + openvdb::math::Stats s, t; + const int data[5]={600, 470, 170, 430, 300}; + for (int i=0; i<3; ++i) s.add(data[i]); + for (int i=3; i<5; ++i) t.add(data[i]); + s.add(t); + double sum = 0.0; + for (int i=0; i<5; ++i) sum += data[i]; + const double mean = sum/5.0; + sum = 0.0; + for (int i=0; i<5; ++i) sum += (data[i]-mean)*(data[i]-mean); + const double var = sum/5.0; + CPPUNIT_ASSERT_EQUAL(5, int(s.size())); + CPPUNIT_ASSERT_DOUBLES_EQUAL(data[2], s.min(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(data[0], s.max(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mean, s.mean(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(var, s.variance(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(sqrt(var), s.stdDev(), 0.000001); + //s.print("test"); + } + {// Trivial test of Stats::add(value, n) + openvdb::math::Stats s; + const double val = 3.45; + const uint64_t n = 57; + s.add(val, 57); + CPPUNIT_ASSERT_EQUAL(n, s.size()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(val, s.min(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(val, s.max(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(val, s.mean(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.variance(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.stdDev(), 0.000001); + } + {// Test 1 of Stats::add(value), Stats::add(value, n) and Stats::add(Stats) + openvdb::math::Stats s, t; + const double val1 = 1.0, val2 = 3.0, sum = val1 + val2; + const uint64_t n1 = 1, n2 =1; + s.add(val1, n1); + CPPUNIT_ASSERT_EQUAL(uint64_t(n1), s.size()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(val1, s.min(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(val1, s.max(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(val1, s.mean(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.variance(), 0.000001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.stdDev(), 0.000001); + for (uint64_t i=0; i= h.min(j) && data[i] < h.max(j)) bin[j]++; + } + for (int i=0; i<5; ++i) CPPUNIT_ASSERT_EQUAL(bin[i],int(h.count(i))); + //h.print("test"); + } + {//Test print of Histogram + openvdb::math::Stats s; + const int N=500000; + for (int i=0; i GradT; + if (it.isVoxelValue()) { + stats.add(GradT::result(acc, it.getCoord()).length()); + } else { + openvdb::CoordBBox bbox = it.getBoundingBox(); + openvdb::Coord xyz; + int &x = xyz[0], &y = xyz[1], &z = xyz[2]; + for (x = bbox.min()[0]; x <= bbox.max()[0]; ++x) { + for (y = bbox.min()[1]; y <= bbox.max()[1]; ++y) { + for (z = bbox.min()[2]; z <= bbox.max()[2]; ++z) { + stats.add(GradT::result(acc, xyz).length()); + } + } + } + } + } +}; + +} // unnamed namespace + +void +TestStats::testGridStats() +{ + using namespace openvdb; + + const int DIM = 109; + { + const float background = 0.0; + FloatGrid grid(background); + { + // Compute active value statistics for a grid with a single active voxel. + grid.tree().setValue(Coord(0), /*value=*/42.0); + math::Stats stats = tools::statistics(grid.cbeginValueOn()); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, stats.min(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, stats.max(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, stats.mean(), /*tolerance=*/1.0e-8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, stats.variance(), /*tolerance=*/1.0e-8); + + // Compute inactive value statistics for a grid with only background voxels. + grid.tree().setValueOff(Coord(0), background); + stats = tools::statistics(grid.cbeginValueOff()); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(background, stats.min(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(background, stats.max(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(background, stats.mean(), /*tolerance=*/1.0e-8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, stats.variance(), /*tolerance=*/1.0e-8); + } + + // Compute active value statistics for a grid with two active voxel populations + // of the same size but two different values. + grid.fill(CoordBBox::createCube(Coord(0), DIM), /*value=*/1.0); + grid.fill(CoordBBox::createCube(Coord(-300), DIM), /*value=*/-3.0); + + CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), grid.activeVoxelCount()); + + for (int threaded = 0; threaded <= 1; ++threaded) { + math::Stats stats = tools::statistics(grid.cbeginValueOn(), threaded); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(-3.0), stats.min(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.max(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(-1.0), stats.mean(), /*tolerance=*/1.0e-8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(4.0), stats.variance(), /*tolerance=*/1.0e-8); + } + + // Compute active value statistics for just the positive values. + for (int threaded = 0; threaded <= 1; ++threaded) { + struct Local { + static void addIfPositive(const FloatGrid::ValueOnCIter& it, math::Stats& stats) + { + const float f = *it; + if (f > 0.0) { + if (it.isVoxelValue()) stats.add(f); + else stats.add(f, it.getVoxelCount()); + } + } + }; + math::Stats stats = + tools::statistics(grid.cbeginValueOn(), &Local::addIfPositive, threaded); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.min(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.max(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.mean(), /*tolerance=*/1.0e-8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), stats.variance(), /*tolerance=*/1.0e-8); + } + + // Compute active value statistics for the first-order gradient. + for (int threaded = 0; threaded <= 1; ++threaded) { + // First, using a custom ValueOp... + math::Stats stats = tools::statistics(grid.cbeginValueOn(), GradOp(grid), threaded); + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), stats.min(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL( + double(9.0 + 9.0 + 9.0), stats.max() * stats.max(), /*tol=*/1.0e-3); + // max gradient is (dx, dy, dz) = (-3 - 0, -3 - 0, -3 - 0) + + // ...then using tools::opStatistics(). + typedef math::ISOpMagnitude > MathOp; + stats = tools::opStatistics(grid.cbeginValueOn(), MathOp(), threaded); + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), stats.min(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL( + double(9.0 + 9.0 + 9.0), stats.max() * stats.max(), /*tolerance=*/1.0e-3); + // max gradient is (dx, dy, dz) = (-3 - 0, -3 - 0, -3 - 0) + } + } + { + const Vec3s background(0.0); + Vec3SGrid grid(background); + + // Compute active vector magnitude statistics for a vector-valued grid + // with two active voxel populations of the same size but two different values. + grid.fill(CoordBBox::createCube(Coord(0), DIM), Vec3s(3.0, 0.0, 4.0)); // length = 5 + grid.fill(CoordBBox::createCube(Coord(-300), DIM), Vec3s(1.0, 2.0, 2.0)); // length = 3 + + CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), grid.activeVoxelCount()); + + for (int threaded = 0; threaded <= 1; ++threaded) { + math::Stats stats = tools::statistics(grid.cbeginValueOn(), threaded); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(3.0), stats.min(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(5.0), stats.max(), /*tolerance=*/0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(4.0), stats.mean(), /*tolerance=*/1.0e-8); + CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.variance(), /*tolerance=*/1.0e-8); + } + } +} + + +namespace { + +template +inline void +doTestGridOperatorStats(const GridT& grid, const OpT& op) +{ + openvdb::math::Stats serialStats = + openvdb::tools::opStatistics(grid.cbeginValueOn(), op, /*threaded=*/false); + + openvdb::math::Stats parallelStats = + openvdb::tools::opStatistics(grid.cbeginValueOn(), op, /*threaded=*/true); + + // Verify that the results from threaded and serial runs are equivalent. + CPPUNIT_ASSERT_EQUAL(serialStats.size(), parallelStats.size()); + ASSERT_DOUBLES_EXACTLY_EQUAL(serialStats.min(), parallelStats.min()); + ASSERT_DOUBLES_EXACTLY_EQUAL(serialStats.max(), parallelStats.max()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(serialStats.mean(), parallelStats.mean(), /*tolerance=*/1.0e-6); + CPPUNIT_ASSERT_DOUBLES_EQUAL(serialStats.variance(), parallelStats.variance(), 1.0e-6); +} + +} + +void +TestStats::testGridOperatorStats() +{ + using namespace openvdb; + + typedef math::UniformScaleMap MapType; + MapType map; + + const int DIM = 109; + { + // Test operations on a scalar grid. + const float background = 0.0; + FloatGrid grid(background); + grid.fill(CoordBBox::createCube(Coord(0), DIM), /*value=*/1.0); + grid.fill(CoordBBox::createCube(Coord(-300), DIM), /*value=*/-3.0); + + { // Magnitude of gradient computed via first-order differencing + typedef math::MapAdapter, MapType>, double> OpT; + doTestGridOperatorStats(grid, OpT(map)); + } + { // Magnitude of index-space gradient computed via first-order differencing + typedef math::ISOpMagnitude > OpT; + doTestGridOperatorStats(grid, OpT()); + } + { // Laplacian of index-space gradient computed via second-order central differencing + typedef math::ISLaplacian OpT; + doTestGridOperatorStats(grid, OpT()); + } + } + { + // Test operations on a vector grid. + const Vec3s background(0.0); + Vec3SGrid grid(background); + grid.fill(CoordBBox::createCube(Coord(0), DIM), Vec3s(3.0, 0.0, 4.0)); // length = 5 + grid.fill(CoordBBox::createCube(Coord(-300), DIM), Vec3s(1.0, 2.0, 2.0)); // length = 3 + + { // Divergence computed via first-order differencing + typedef math::MapAdapter, double> OpT; + doTestGridOperatorStats(grid, OpT(map)); + } + { // Magnitude of curl computed via first-order differencing + typedef math::MapAdapter, MapType>, double> OpT; + doTestGridOperatorStats(grid, OpT(map)); + } + { // Magnitude of index-space curl computed via first-order differencing + typedef math::ISOpMagnitude > OpT; + doTestGridOperatorStats(grid, OpT()); + } + } +} + + +void +TestStats::testGridHistogram() +{ + using namespace openvdb; + + const int DIM = 109; + { + const float background = 0.0; + FloatGrid grid(background); + { + const double value = 42.0; + + // Compute a histogram of the active values of a grid with a single active voxel. + grid.tree().setValue(Coord(0), value); + math::Histogram hist = tools::histogram(grid.cbeginValueOn(), + /*min=*/0.0, /*max=*/100.0); + + for (size_t i = 0, N = hist.numBins(); i < N; ++i) { + uint64_t expected = ((hist.min(i) <= value && value <= hist.max(i)) ? 1 : 0); + CPPUNIT_ASSERT_EQUAL(expected, hist.count(i)); + } + } + + // Compute a histogram of the active values of a grid with two + // active voxel populations of the same size but two different values. + grid.fill(CoordBBox::createCube(Coord(0), DIM), /*value=*/1.0); + grid.fill(CoordBBox::createCube(Coord(-300), DIM), /*value=*/3.0); + + CPPUNIT_ASSERT_EQUAL(uint64_t(2 * DIM * DIM * DIM), grid.activeVoxelCount()); + + for (int threaded = 0; threaded <= 1; ++threaded) { + math::Histogram hist = tools::histogram(grid.cbeginValueOn(), + /*min=*/0.0, /*max=*/10.0, /*numBins=*/9, threaded); + + CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), hist.size()); + for (size_t i = 0, N = hist.numBins(); i < N; ++i) { + if (i == 0 || i == 2) { + CPPUNIT_ASSERT_EQUAL(uint64_t(DIM * DIM * DIM), hist.count(i)); + } else { + CPPUNIT_ASSERT_EQUAL(uint64_t(0), hist.count(i)); + } + } + } + } + { + const Vec3s background(0.0); + Vec3SGrid grid(background); + + // Compute a histogram of vector magnitudes of the active values of a + // vector-valued grid with two active voxel populations of the same size + // but two different values. + grid.fill(CoordBBox::createCube(Coord(0), DIM), Vec3s(3.0, 0.0, 4.0)); // length = 5 + grid.fill(CoordBBox::createCube(Coord(-300), DIM), Vec3s(1.0, 2.0, 2.0)); // length = 3 + + CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), grid.activeVoxelCount()); + + for (int threaded = 0; threaded <= 1; ++threaded) { + math::Histogram hist = tools::histogram(grid.cbeginValueOn(), + /*min=*/0.0, /*max=*/10.0, /*numBins=*/9, threaded); + + CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), hist.size()); + for (size_t i = 0, N = hist.numBins(); i < N; ++i) { + if (i == 2 || i == 4) { + CPPUNIT_ASSERT_EQUAL(uint64_t(DIM * DIM * DIM), hist.count(i)); + } else { + CPPUNIT_ASSERT_EQUAL(uint64_t(0), hist.count(i)); + } + } + } + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestStream.cc b/openvdb_2_3_0_library/openvdb/unittest/TestStream.cc new file mode 100755 index 0000000..7606131 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestStream.cc @@ -0,0 +1,276 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(a, b) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((a), (b), /*tolerance=*/0.0); + + +class TestStream: public CppUnit::TestCase +{ +public: + virtual void setUp(); + virtual void tearDown(); + + CPPUNIT_TEST_SUITE(TestStream); + CPPUNIT_TEST(testWrite); + CPPUNIT_TEST(testRead); + CPPUNIT_TEST(testFileReadFromStream); + CPPUNIT_TEST_SUITE_END(); + + void testWrite(); + void testRead(); + void testFileReadFromStream(); + +private: + static openvdb::GridPtrVecPtr createTestGrids(openvdb::MetaMap::Ptr&); + static void verifyTestGrids(openvdb::GridPtrVecPtr, openvdb::MetaMap::Ptr); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestStream); + + +//////////////////////////////////////// + + +void +TestStream::setUp() +{ + openvdb::uninitialize(); + + openvdb::Int32Grid::registerGrid(); + openvdb::FloatGrid::registerGrid(); + + openvdb::StringMetadata::registerType(); + openvdb::Int32Metadata::registerType(); + openvdb::Int64Metadata::registerType(); + openvdb::Vec3IMetadata::registerType(); + + // Register maps + openvdb::math::MapRegistry::clear(); + openvdb::math::AffineMap::registerMap(); + openvdb::math::ScaleMap::registerMap(); + openvdb::math::UniformScaleMap::registerMap(); + openvdb::math::TranslationMap::registerMap(); + openvdb::math::ScaleTranslateMap::registerMap(); + openvdb::math::UniformScaleTranslateMap::registerMap(); + openvdb::math::NonlinearFrustumMap::registerMap(); +} + + +void +TestStream::tearDown() +{ + openvdb::uninitialize(); +} + + +//////////////////////////////////////// + + +openvdb::GridPtrVecPtr +TestStream::createTestGrids(openvdb::MetaMap::Ptr& metadata) +{ + using namespace openvdb; + + // Create trees + Int32Tree::Ptr tree1(new Int32Tree(1)); + FloatTree::Ptr tree2(new FloatTree(2.0)); + + // Set some values + tree1->setValue(Coord(0, 0, 0), 5); + tree1->setValue(Coord(100, 0, 0), 6); + tree2->setValue(Coord(0, 0, 0), 10); + tree2->setValue(Coord(0, 100, 0), 11); + + // Create grids + GridBase::Ptr + grid1 = createGrid(tree1), + grid2 = createGrid(tree1), // instance of grid1 + grid3 = createGrid(tree2); + grid1->setName("density"); + grid2->setName("density_copy"); + grid3->setName("temperature"); + + // Create transforms + math::Transform::Ptr trans1 = math::Transform::createLinearTransform(0.1); + math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); + grid1->setTransform(trans1); + grid2->setTransform(trans2); + grid3->setTransform(trans2); + + metadata.reset(new MetaMap); + metadata->insertMeta("author", StringMetadata("Einstein")); + metadata->insertMeta("year", Int32Metadata(2009)); + + GridPtrVecPtr grids(new GridPtrVec); + grids->push_back(grid1); + grids->push_back(grid2); + grids->push_back(grid3); + + return grids; +} + + +void +TestStream::verifyTestGrids(openvdb::GridPtrVecPtr grids, openvdb::MetaMap::Ptr meta) +{ + using namespace openvdb; + + CPPUNIT_ASSERT(grids.get() != NULL); + CPPUNIT_ASSERT(meta.get() != NULL); + + // Verify the metadata. + CPPUNIT_ASSERT_EQUAL(2, int(meta->metaCount())); + CPPUNIT_ASSERT_EQUAL(std::string("Einstein"), meta->metaValue("author")); + CPPUNIT_ASSERT_EQUAL(2009, meta->metaValue("year")); + + // Verify the grids. + CPPUNIT_ASSERT_EQUAL(3, int(grids->size())); + + GridBase::Ptr grid = findGridByName(*grids, "density"); + CPPUNIT_ASSERT(grid.get() != NULL); + Int32Tree::Ptr density = gridPtrCast(grid)->treePtr(); + CPPUNIT_ASSERT(density.get() != NULL); + + grid.reset(); + grid = findGridByName(*grids, "density_copy"); + CPPUNIT_ASSERT(grid.get() != NULL); + CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); + // Verify that "density_copy" is an instance of (i.e., shares a tree with) "density". + CPPUNIT_ASSERT_EQUAL(density, gridPtrCast(grid)->treePtr()); + + grid.reset(); + grid = findGridByName(*grids, "temperature"); + CPPUNIT_ASSERT(grid.get() != NULL); + FloatTree::Ptr temperature = gridPtrCast(grid)->treePtr(); + CPPUNIT_ASSERT(temperature.get() != NULL); + + ASSERT_DOUBLES_EXACTLY_EQUAL(5, density->getValue(Coord(0, 0, 0))); + ASSERT_DOUBLES_EXACTLY_EQUAL(6, density->getValue(Coord(100, 0, 0))); + ASSERT_DOUBLES_EXACTLY_EQUAL(10, temperature->getValue(Coord(0, 0, 0))); + ASSERT_DOUBLES_EXACTLY_EQUAL(11, temperature->getValue(Coord(0, 100, 0))); +} + + +//////////////////////////////////////// + + +void +TestStream::testWrite() +{ + using namespace openvdb; + + // Create test grids and stream them to a string. + MetaMap::Ptr meta; + GridPtrVecPtr grids = createTestGrids(meta); + std::ostringstream ostr(std::ios_base::binary); + io::Stream(ostr).write(*grids, *meta); + //std::ofstream file("debug.vdb2", std::ios_base::binary); + //file << ostr.str(); + + // Stream the grids back in. + std::istringstream is(ostr.str(), std::ios_base::binary); + io::Stream strm(is); + meta = strm.getMetadata(); + grids = strm.getGrids(); + + verifyTestGrids(grids, meta); +} + + +void +TestStream::testRead() +{ + using namespace openvdb; + + // Create test grids and write them to a file. + MetaMap::Ptr meta; + GridPtrVecPtr grids = createTestGrids(meta); + const char* filename = "something.vdb2"; + io::File(filename).write(*grids, *meta); + boost::shared_ptr scopedFile(filename, ::remove); + + // Stream the grids back in. + std::ifstream is(filename, std::ios_base::binary); + io::Stream strm(is); + meta = strm.getMetadata(); + grids = strm.getGrids(); + + verifyTestGrids(grids, meta); +} + + +/// Stream grids to a file using io::Stream, then read the file back using io::File. +void +TestStream::testFileReadFromStream() +{ + using namespace openvdb; + + MetaMap::Ptr meta; + GridPtrVecPtr grids; + + // Create test grids and stream them to a file (and then close the file). + const char* filename = "something.vdb2"; + boost::shared_ptr scopedFile(filename, ::remove); + { + std::ofstream os(filename, std::ios_base::binary); + grids = createTestGrids(meta); + io::Stream(os).write(*grids, *meta); + } + + // Read the grids back in. + io::File file(filename); + CPPUNIT_ASSERT(file.inputHasGridOffsets()); + CPPUNIT_ASSERT_THROW(file.getGrids(), IoError); + + file.open(); + meta = file.getMetadata(); + grids = file.getGrids(); + + CPPUNIT_ASSERT(!file.inputHasGridOffsets()); + CPPUNIT_ASSERT(meta.get() != NULL); + CPPUNIT_ASSERT(grids.get() != NULL); + CPPUNIT_ASSERT(!grids->empty()); + + verifyTestGrids(grids, meta); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestStringMetadata.cc b/openvdb_2_3_0_library/openvdb/unittest/TestStringMetadata.cc new file mode 100755 index 0000000..8092e11 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestStringMetadata.cc @@ -0,0 +1,74 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +class TestStringMetadata : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestStringMetadata); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + void test(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestStringMetadata); + +void +TestStringMetadata::test() +{ + using namespace openvdb; + + Metadata::Ptr m(new StringMetadata("testing")); + Metadata::Ptr m2 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); + + CPPUNIT_ASSERT(m->typeName().compare("string") == 0); + CPPUNIT_ASSERT(m2->typeName().compare("string") == 0); + + StringMetadata *s = dynamic_cast(m.get()); + CPPUNIT_ASSERT(s->value().compare("testing") == 0); + s->value() = "testing2"; + CPPUNIT_ASSERT(s->value().compare("testing2") == 0); + + m2->copy(*s); + + s = dynamic_cast(m2.get()); + CPPUNIT_ASSERT(s->value().compare("testing2") == 0); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestTools.cc b/openvdb_2_3_0_library/openvdb/unittest/TestTools.cc new file mode 100755 index 0000000..63db9e3 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestTools.cc @@ -0,0 +1,1767 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 "util.h" // for unittest_util::makeSphere() + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + +class TestTools: public CppUnit::TestFixture +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestTools); + CPPUNIT_TEST(testDilateVoxels); + CPPUNIT_TEST(testErodeVoxels); + CPPUNIT_TEST(testActivate); + CPPUNIT_TEST(testFilter); + CPPUNIT_TEST(testFloatApply); + CPPUNIT_TEST(testLevelSetSphere); + CPPUNIT_TEST(testLevelSetAdvect); + CPPUNIT_TEST(testLevelSetMeasure); + CPPUNIT_TEST(testLevelSetMorph); + CPPUNIT_TEST(testMagnitude); + CPPUNIT_TEST(testMaskedMagnitude); + CPPUNIT_TEST(testNormalize); + CPPUNIT_TEST(testMaskedNormalize); + CPPUNIT_TEST(testPointAdvect); + CPPUNIT_TEST(testPointScatter); + CPPUNIT_TEST(testTransformValues); + CPPUNIT_TEST(testVectorApply); + CPPUNIT_TEST(testAccumulate); + CPPUNIT_TEST(testUtil); + CPPUNIT_TEST(testVectorTransformer); + + CPPUNIT_TEST_SUITE_END(); + + void testDilateVoxels(); + void testErodeVoxels(); + void testActivate(); + void testFilter(); + void testFloatApply(); + void testLevelSetSphere(); + void testLevelSetAdvect(); + void testLevelSetMeasure(); + void testLevelSetMorph(); + void testMagnitude(); + void testMaskedMagnitude(); + void testNormalize(); + void testMaskedNormalize(); + void testPointAdvect(); + void testPointScatter(); + void testTransformValues(); + void testVectorApply(); + void testAccumulate(); + void testUtil(); + 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 << "/usr/pic1/tmp/" << name << "_" << mVersion << "_" << mFrame << ".vdb"; + openvdb::io::File file(ostr.str()); + openvdb::GridPtrVec grids; + grids.push_back(mGrid); + file.write(grids); + std::cerr << "\nWrote \"" << ostr.str() << "\" with time = " + << time << " after CFL-iterations = " << n << std::endl; + ++mFrame; + } + +private: + int mFrame, mVersion; + typename GridT::Ptr mGrid; +}; + +} // unnamed namespace +#endif + + +void +TestTools::testDilateVoxels() +{ + using openvdb::CoordBBox; + using openvdb::Coord; + using openvdb::Index32; + using openvdb::Index64; + + typedef openvdb::tree::Tree4::Type Tree543f; + + Tree543f::Ptr tree(new Tree543f); + tree->setBackground(/*background=*/5.0); + CPPUNIT_ASSERT(tree->empty()); + + const openvdb::Index leafDim = Tree543f::LeafNodeType::DIM; + CPPUNIT_ASSERT_EQUAL(1 << 3, int(leafDim)); + + { + // Set and dilate a single voxel at the center of a leaf node. + tree->clear(); + tree->setValue(Coord(leafDim >> 1), 1.0); + CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); + openvdb::tools::dilateVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(Index64(7), tree->activeVoxelCount()); + } + { + // Create an active, leaf node-sized tile. + tree->clear(); + tree->fill(CoordBBox(Coord(0), Coord(leafDim - 1)), 1.0); + CPPUNIT_ASSERT_EQUAL(Index32(0), tree->leafCount()); + CPPUNIT_ASSERT_EQUAL(Index64(leafDim * leafDim * leafDim), tree->activeVoxelCount()); + + tree->setValue(Coord(leafDim, leafDim - 1, leafDim - 1), 1.0); + CPPUNIT_ASSERT_EQUAL(Index64(leafDim * leafDim * leafDim + 1), + tree->activeVoxelCount()); + + openvdb::tools::dilateVoxels(*tree); + + CPPUNIT_ASSERT_EQUAL(Index64(leafDim * leafDim * leafDim + 1 + 5), + tree->activeVoxelCount()); + } + { + // Set and dilate a single voxel at each of the eight corners of a leaf node. + for (int i = 0; i < 8; ++i) { + tree->clear(); + + openvdb::Coord xyz( + i & 1 ? leafDim - 1 : 0, + i & 2 ? leafDim - 1 : 0, + i & 4 ? leafDim - 1 : 0); + tree->setValue(xyz, 1.0); + CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); + + openvdb::tools::dilateVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(Index64(7), tree->activeVoxelCount()); + } + } + { + tree->clear(); + tree->setValue(Coord(0), 1.0); + tree->setValue(Coord( 1, 0, 0), 1.0); + tree->setValue(Coord(-1, 0, 0), 1.0); + CPPUNIT_ASSERT_EQUAL(Index64(3), tree->activeVoxelCount()); + openvdb::tools::dilateVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(Index64(17), tree->activeVoxelCount()); + } + { + struct Info { int activeVoxelCount, leafCount, nonLeafCount; }; + Info iterInfo[11] = { + { 1, 1, 3 }, + { 7, 1, 3 }, + { 25, 1, 3 }, + { 63, 1, 3 }, + { 129, 4, 3 }, + { 231, 7, 9 }, + { 377, 7, 9 }, + { 575, 7, 9 }, + { 833, 10, 9 }, + { 1159, 16, 9 }, + { 1561, 19, 15 }, + }; + + // Perform repeated dilations, starting with a single voxel. + tree->clear(); + tree->setValue(Coord(leafDim >> 1), 1.0); + for (int i = 0; i < 11; ++i) { + CPPUNIT_ASSERT_EQUAL(iterInfo[i].activeVoxelCount, int(tree->activeVoxelCount())); + CPPUNIT_ASSERT_EQUAL(iterInfo[i].leafCount, int(tree->leafCount())); + CPPUNIT_ASSERT_EQUAL(iterInfo[i].nonLeafCount, int(tree->nonLeafCount())); + + openvdb::tools::dilateVoxels(*tree); + } + } + + {// dialte a narrow band of a sphere + typedef openvdb::Grid GridType; + GridType grid(tree->background()); + unittest_util::makeSphere(/*dim=*/openvdb::Coord(64, 64, 64), + /*center=*/openvdb::Vec3f(0, 0, 0), + /*radius=*/20, grid, /*dx=*/1.0f, + unittest_util::SPHERE_DENSE_NARROW_BAND); + const openvdb::Index64 count = grid.tree().activeVoxelCount(); + openvdb::tools::dilateVoxels(grid.tree()); + CPPUNIT_ASSERT(grid.tree().activeVoxelCount() > count); + } + + {// dilate a fog volume of a sphere + typedef openvdb::Grid GridType; + GridType grid(tree->background()); + unittest_util::makeSphere(/*dim=*/openvdb::Coord(64, 64, 64), + /*center=*/openvdb::Vec3f(0, 0, 0), + /*radius=*/20, grid, /*dx=*/1.0f, + unittest_util::SPHERE_DENSE_NARROW_BAND); + openvdb::tools::sdfToFogVolume(grid); + const openvdb::Index64 count = grid.tree().activeVoxelCount(); + //std::cerr << "\nBefore: active voxel count = " << count << std::endl; + //grid.print(std::cerr,5); + openvdb::tools::dilateVoxels(grid.tree()); + CPPUNIT_ASSERT(grid.tree().activeVoxelCount() > count); + //std::cerr << "\nAfter: active voxel count = " << grid.tree().activeVoxelCount() << std::endl; + } +// {// Test a grid from a file that has proven to be challenging +// openvdb::initialize(); +// openvdb::io::File file("/usr/home/kmuseth/Data/vdb/dilation.vdb"); +// file.open(); +// openvdb::GridBase::Ptr baseGrid = file.readGrid(file.beginName().gridName()); +// file.close(); +// openvdb::FloatGrid::Ptr grid = openvdb::gridPtrCast(baseGrid); +// const openvdb::Index64 count = grid->tree().activeVoxelCount(); +// //std::cerr << "\nBefore: active voxel count = " << count << std::endl; +// //grid->print(std::cerr,5); +// openvdb::tools::dilateVoxels(grid->tree()); +// CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); +// //std::cerr << "\nAfter: active voxel count = " << grid->tree().activeVoxelCount() << std::endl; +// } +} + +void +TestTools::testErodeVoxels() +{ + using openvdb::CoordBBox; + using openvdb::Coord; + using openvdb::Index32; + using openvdb::Index64; + + typedef openvdb::tree::Tree4::Type TreeType; + + TreeType::Ptr tree(new TreeType); + tree->setBackground(/*background=*/5.0); + CPPUNIT_ASSERT(tree->empty()); + + const int leafDim = TreeType::LeafNodeType::DIM; + CPPUNIT_ASSERT_EQUAL(1 << 3, leafDim); + + { + // Set, dilate and erode a single voxel at the center of a leaf node. + tree->clear(); + CPPUNIT_ASSERT_EQUAL(0, int(tree->activeVoxelCount())); + + tree->setValue(Coord(leafDim >> 1), 1.0); + CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); + + openvdb::tools::dilateVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(7, int(tree->activeVoxelCount())); + + openvdb::tools::erodeVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); + + openvdb::tools::erodeVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(0, int(tree->activeVoxelCount())); + } + { + // Create an active, leaf node-sized tile. + tree->clear(); + tree->fill(CoordBBox(Coord(0), Coord(leafDim - 1)), 1.0); + CPPUNIT_ASSERT_EQUAL(0, int(tree->leafCount())); + CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim, int(tree->activeVoxelCount())); + + tree->setValue(Coord(leafDim, leafDim - 1, leafDim - 1), 1.0); + CPPUNIT_ASSERT_EQUAL(1, int(tree->leafCount())); + CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim + 1,int(tree->activeVoxelCount())); + + openvdb::tools::dilateVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(3, int(tree->leafCount())); + CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim + 1 + 5,int(tree->activeVoxelCount())); + + openvdb::tools::erodeVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(1, int(tree->leafCount())); + CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim + 1, int(tree->activeVoxelCount())); + } + { + // Set and dilate a single voxel at each of the eight corners of a leaf node. + for (int i = 0; i < 8; ++i) { + tree->clear(); + + openvdb::Coord xyz( + i & 1 ? leafDim - 1 : 0, + i & 2 ? leafDim - 1 : 0, + i & 4 ? leafDim - 1 : 0); + tree->setValue(xyz, 1.0); + CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); + + openvdb::tools::dilateVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(7, int(tree->activeVoxelCount())); + + openvdb::tools::erodeVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); + } + } + { + // Set three active voxels and dilate and erode + tree->clear(); + tree->setValue(Coord(0), 1.0); + tree->setValue(Coord( 1, 0, 0), 1.0); + tree->setValue(Coord(-1, 0, 0), 1.0); + CPPUNIT_ASSERT_EQUAL(3, int(tree->activeVoxelCount())); + + openvdb::tools::dilateVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(17, int(tree->activeVoxelCount())); + + openvdb::tools::erodeVoxels(*tree); + CPPUNIT_ASSERT_EQUAL(3, int(tree->activeVoxelCount())); + } + { + struct Info { + void test(TreeType::Ptr 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 + typedef openvdb::Grid GridType; + GridType grid(tree->background()); + unittest_util::makeSphere(/*dim=*/openvdb::Coord(64, 64, 64), + /*center=*/openvdb::Vec3f(0, 0, 0), + /*radius=*/20, grid, /*dx=*/1.0f, + unittest_util::SPHERE_DENSE_NARROW_BAND); + const openvdb::Index64 count = grid.tree().activeVoxelCount(); + openvdb::tools::erodeVoxels(grid.tree()); + CPPUNIT_ASSERT(grid.tree().activeVoxelCount() < count); + } + + {// erode a fog volume of a sphere + typedef openvdb::Grid GridType; + GridType grid(tree->background()); + unittest_util::makeSphere(/*dim=*/openvdb::Coord(64, 64, 64), + /*center=*/openvdb::Vec3f(0, 0, 0), + /*radius=*/20, grid, /*dx=*/1.0f, + unittest_util::SPHERE_DENSE_NARROW_BAND); + openvdb::tools::sdfToFogVolume(grid); + const openvdb::Index64 count = grid.tree().activeVoxelCount(); + openvdb::tools::erodeVoxels(grid.tree()); + CPPUNIT_ASSERT(grid.tree().activeVoxelCount() < count); + } +} + + +void +TestTools::testActivate() +{ + using namespace openvdb; + + const Vec3s background(0.0, -1.0, 1.0), foreground(42.0); + + Vec3STree tree(background); + + const CoordBBox bbox1(Coord(-200), Coord(-181)), bbox2(Coord(51), Coord(373)); + + // Set some non-background active voxels. + tree.fill(bbox1, Vec3s(0.0), /*active=*/true); + + // Mark some background voxels as active. + tree.fill(bbox2, background, /*active=*/true); + CPPUNIT_ASSERT_EQUAL(bbox2.volume() + bbox1.volume(), tree.activeVoxelCount()); + + // Deactivate all voxels with the background value. + tools::deactivate(tree, background, /*tolerance=*/Vec3s(1.0e-6)); + // Verify that there are no longer any active voxels with the background value. + CPPUNIT_ASSERT_EQUAL(bbox1.volume(), tree.activeVoxelCount()); + + // Set some voxels to the foreground value but leave them inactive. + tree.fill(bbox2, foreground, /*active=*/false); + // Verify that there are no active voxels with the background value. + CPPUNIT_ASSERT_EQUAL(bbox1.volume(), tree.activeVoxelCount()); + + // Activate all voxels with the foreground value. + tools::activate(tree, foreground); + // Verify that the expected number of voxels are active. + CPPUNIT_ASSERT_EQUAL(bbox1.volume() + bbox2.volume(), tree.activeVoxelCount()); +} + + +void +TestTools::testFilter() +{ + openvdb::FloatGrid::Ptr referenceGrid = openvdb::FloatGrid::create(/*background=*/5.0); + + const openvdb::Coord dim(40); + const openvdb::Vec3f center(25.0f, 20.0f, 20.0f); + const float radius = 10.0f; + unittest_util::makeSphere( + dim, center, radius, *referenceGrid, unittest_util::SPHERE_DENSE); + const openvdb::FloatTree& sphere = referenceGrid->tree(); + + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(sphere.activeVoxelCount())); + openvdb::Coord xyz; + + {// test Filter::offsetFilter + openvdb::FloatGrid::Ptr grid = referenceGrid->deepCopy(); + openvdb::FloatTree& tree = grid->tree(); + openvdb::tools::Filter filter(*grid); + const float offset = 2.34f; + filter.setGrainSize(0);//i.e. disable threading + filter.offset(offset); + for (int x=0; x0.0001f) std::cerr << " failed at " << xyz << std::endl; + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0f, delta, /*tolerance=*/0.0001); + } + } + } + filter.setGrainSize(1);//i.e. enable threading + filter.offset(-offset);//default is multi-threaded + for (int x=0; x0.0001f) std::cerr << " failed at " << xyz << std::endl; + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0f, delta, /*tolerance=*/0.0001); + } + } + } + //std::cerr << "Successfully completed TestTools::testFilter offset test" << std::endl; + } + {// test Filter::median + openvdb::FloatGrid::Ptr filteredGrid = referenceGrid->deepCopy(); + openvdb::FloatTree& filteredTree = filteredGrid->tree(); + const int width = 2; + openvdb::math::DenseStencil stencil(*referenceGrid, width); + openvdb::tools::Filter filter(*filteredGrid); + filter.median(width, /*interations=*/1); + std::vector tmp; + for (int x=0; xdeepCopy(); + openvdb::FloatTree& filteredTree = filteredGrid->tree(); + const int width = 2; + openvdb::math::DenseStencil stencil(*referenceGrid, width); + openvdb::tools::Filter filter(*filteredGrid); + filter.mean(width, /*interations=*/1); + for (int x=0; x(radius, center, voxelSize, width); + + /// Also test ultra slow makeSphere in unittest/util.h + openvdb::FloatGrid::Ptr grid2 = openvdb::createLevelSet(voxelSize, width); + unittest_util::makeSphere( + openvdb::Coord(dim), center, radius, *grid2, unittest_util::SPHERE_SPARSE_NARROW_BAND); + + const float outside = grid1->background(), inside = -outside; + for (int i=0; itree().getValue(openvdb::Coord(i,j,k)); + const float val2 = grid2->tree().getValue(openvdb::Coord(i,j,k)); + if (dist > outside) { + CPPUNIT_ASSERT_DOUBLES_EQUAL( outside, val1, 0.0001); + CPPUNIT_ASSERT_DOUBLES_EQUAL( outside, val2, 0.0001); + } else if (dist < inside) { + CPPUNIT_ASSERT_DOUBLES_EQUAL( inside, val1, 0.0001); + CPPUNIT_ASSERT_DOUBLES_EQUAL( inside, val2, 0.0001); + } else { + CPPUNIT_ASSERT_DOUBLES_EQUAL( dist, val1, 0.0001); + CPPUNIT_ASSERT_DOUBLES_EQUAL( dist, val2, 0.0001); + } + } + } + } + + CPPUNIT_ASSERT_EQUAL(grid1->activeVoxelCount(), grid2->activeVoxelCount()); +} + +void +TestTools::testLevelSetAdvect() +{ + // Uncomment sections below to run this (time-consuming) test + /* + const int dim = 64;//256 + const openvdb::Vec3f center(0.35f, 0.35f, 0.35f); + const float radius = 0.15f, voxelSize = 1.0f/(dim-1); + + typedef openvdb::FloatGrid GridT; + typedef openvdb::Vec3fGrid VectT; + + */ + /* + {//test tracker + GridT::Ptr grid = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); + typedef openvdb::tools::LevelSetTracker TrackerT; + TrackerT tracker(*grid); + tracker.setSpatialScheme(openvdb::math::HJWENO5_BIAS); + tracker.setTemporalScheme(openvdb::math::TVD_RK1); + + FrameWriter fw(dim, grid); fw("Tracker",0, 0); + //for (float t = 0, dt = 0.005f; !grid->empty() && t < 3.0f; t += dt) { + // fw("Enright", t + dt, advect.advect(t, t + dt)); + //} + for (float t = 0, dt = 0.5f; !grid->empty() && t < 1.0f; t += dt) { + tracker.track(); + fw("Tracker", 0, 0); + } + } + */ + /* + {//test EnrightField + GridT::Ptr grid = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); + typedef openvdb::tools::EnrightField FieldT; + FieldT field; + + typedef openvdb::tools::LevelSetAdvection AdvectT; + AdvectT advect(*grid, field); + advect.setSpatialScheme(openvdb::math::HJWENO5_BIAS); + advect.setTemporalScheme(openvdb::math::TVD_RK2); + advect.setTrackerSpatialScheme(openvdb::math::HJWENO5_BIAS); + advect.setTrackerTemporalScheme(openvdb::math::TVD_RK1); + + FrameWriter fw(dim, grid); fw("Enright",0, 0); + //for (float t = 0, dt = 0.005f; !grid->empty() && t < 3.0f; t += dt) { + // fw("Enright", t + dt, advect.advect(t, t + dt)); + //} + for (float t = 0, dt = 0.5f; !grid->empty() && t < 1.0f; t += dt) { + fw("Enright", t + dt, advect.advect(t, t + dt)); + } + } + */ + /* + {// test DiscreteGrid - Aligned + GridT::Ptr grid = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); + VectT vect(openvdb::Vec3f(1,0,0)); + typedef openvdb::tools::DiscreteField FieldT; + FieldT field(vect); + typedef openvdb::tools::LevelSetAdvection AdvectT; + AdvectT advect(*grid, field); + advect.setSpatialScheme(openvdb::math::HJWENO5_BIAS); + advect.setTemporalScheme(openvdb::math::TVD_RK2); + + FrameWriter fw(dim, grid); fw("Aligned",0, 0); + //for (float t = 0, dt = 0.005f; !grid->empty() && t < 3.0f; t += dt) { + // fw("Aligned", t + dt, advect.advect(t, t + dt)); + //} + for (float t = 0, dt = 0.5f; !grid->empty() && t < 1.0f; t += dt) { + fw("Aligned", t + dt, advect.advect(t, t + dt)); + } + } + */ + /* + {// test DiscreteGrid - Transformed + GridT::Ptr grid = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); + VectT vect(openvdb::Vec3f(0,0,0)); + VectT::Accessor acc = vect.getAccessor(); + for (openvdb::Coord ijk(0); ijk[0] FieldT; + FieldT field(vect); + typedef openvdb::tools::LevelSetAdvection AdvectT; + AdvectT advect(*grid, field); + advect.setSpatialScheme(openvdb::math::HJWENO5_BIAS); + advect.setTemporalScheme(openvdb::math::TVD_RK2); + + FrameWriter fw(dim, grid); fw("Xformed",0, 0); + //for (float t = 0, dt = 0.005f; !grid->empty() && t < 3.0f; t += dt) { + // fw("Xformed", t + dt, advect.advect(t, t + dt)); + //} + for (float t = 0, dt = 0.5f; !grid->empty() && t < 1.0f; t += dt) { + fw("Xformed", t + dt, advect.advect(t, t + dt)); + } + } + */ +}//testLevelSetAdvect + + +//////////////////////////////////////// + +void +TestTools::testLevelSetMorph() +{ + typedef openvdb::FloatGrid GridT; + {//test morphing overlapping but aligned spheres + const int dim = 64; + const openvdb::Vec3f C1(0.35f, 0.35f, 0.35f), C2(0.4f, 0.4f, 0.4f); + const float radius = 0.15f, voxelSize = 1.0f/(dim-1); + + GridT::Ptr source = openvdb::tools::createLevelSetSphere(radius, C1, voxelSize); + GridT::Ptr target = openvdb::tools::createLevelSetSphere(radius, C2, voxelSize); + + typedef openvdb::tools::LevelSetMorphing MorphT; + MorphT morph(*source, *target); + morph.setSpatialScheme(openvdb::math::HJWENO5_BIAS); + morph.setTemporalScheme(openvdb::math::TVD_RK3); + morph.setTrackerSpatialScheme(openvdb::math::HJWENO5_BIAS); + morph.setTrackerTemporalScheme(openvdb::math::TVD_RK2); + + const std::string name("SphereToSphere"); + //FrameWriter fw(dim, source); + //fw(name, 0.0f, 0); + //unittest_util::CpuTimer timer; + const float tMax = 0.05f/voxelSize; + //std::cerr << "\nt-max = " << tMax << std::endl; + //timer.start("\nMorphing"); + for (float t = 0, dt = 0.1f; !source->empty() && t < tMax; t += dt) { + morph.advect(t, t + dt); + //fw(name, t + dt, morph.advect(t, t + dt)); + } + // timer.stop(); + + const float invDx = 1.0f/voxelSize; + openvdb::math::Stats s; + for (GridT::ValueOnCIter it = source->tree().cbeginValueOn(); it; ++it) { + s.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); + } + for (GridT::ValueOnCIter it = target->tree().cbeginValueOn(); it; ++it) { + s.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); + } + //s.print("Morph"); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.min(), 0.50); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.max(), 0.50); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.avg(), 0.02); + /* + openvdb::math::Histogram h(s, 30); + for (GridT::ValueOnCIter it = source->tree().cbeginValueOn(); it; ++it) { + h.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); + } + for (GridT::ValueOnCIter it = target->tree().cbeginValueOn(); it; ++it) { + h.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); + } + h.print("Morph"); + */ + } + /* + // Uncomment sections below to run this (very time-consuming) test + {//test morphing between the bunny and the buddha models loaded from files + unittest_util::CpuTimer timer; + openvdb::initialize();//required whenever I/O of OpenVDB files is performed! + openvdb::io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/bunny.vdb"); + sourceFile.open(); + GridT::Ptr source = openvdb::gridPtrCast(sourceFile.getGrids()->at(0)); + + openvdb::io::File targetFile("/usr/pic1/Data/OpenVDB/LevelSetModels/buddha.vdb"); + targetFile.open(); + GridT::Ptr target = openvdb::gridPtrCast(targetFile.getGrids()->at(0)); + + typedef openvdb::tools::LevelSetMorphing MorphT; + MorphT morph(*source, *target); + morph.setSpatialScheme(openvdb::math::FIRST_BIAS); + //morph.setSpatialScheme(openvdb::math::HJWENO5_BIAS); + morph.setTemporalScheme(openvdb::math::TVD_RK2); + morph.setTrackerSpatialScheme(openvdb::math::FIRST_BIAS); + //morph.setTrackerSpatialScheme(openvdb::math::HJWENO5_BIAS); + morph.setTrackerTemporalScheme(openvdb::math::TVD_RK2); + + const std::string name("Bunny2Buddha"); + FrameWriter fw(1, source); + fw(name, 0.0f, 0); + for (float t = 0, dt = 1.0f; !source->empty() && t < 300.0f; t += dt) { + timer.start("Morphing"); + const int cflCount = morph.advect(t, t + dt); + timer.stop(); + fw(name, t + dt, cflCount); + } + } + */ + /* + // Uncomment sections below to run this (very time-consuming) test + {//test morphing between the dragon and the teapot models loaded from files + unittest_util::CpuTimer timer; + openvdb::initialize();//required whenever I/O of OpenVDB files is performed! + openvdb::io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/dragon.vdb"); + sourceFile.open(); + GridT::Ptr source = openvdb::gridPtrCast(sourceFile.getGrids()->at(0)); + + openvdb::io::File targetFile("/usr/pic1/Data/OpenVDB/LevelSetModels/utahteapot.vdb"); + targetFile.open(); + GridT::Ptr target = openvdb::gridPtrCast(targetFile.getGrids()->at(0)); + + typedef openvdb::tools::LevelSetMorphing MorphT; + MorphT morph(*source, *target); + morph.setSpatialScheme(openvdb::math::FIRST_BIAS); + //morph.setSpatialScheme(openvdb::math::HJWENO5_BIAS); + morph.setTemporalScheme(openvdb::math::TVD_RK2); + //morph.setTrackerSpatialScheme(openvdb::math::HJWENO5_BIAS); + morph.setTrackerSpatialScheme(openvdb::math::FIRST_BIAS); + morph.setTrackerTemporalScheme(openvdb::math::TVD_RK2); + + const std::string name("Dragon2Teapot"); + FrameWriter fw(5, source); + fw(name, 0.0f, 0); + for (float t = 0, dt = 0.4f; !source->empty() && t < 110.0f; t += dt) { + timer.start("Morphing"); + const int cflCount = morph.advect(t, t + dt); + timer.stop(); + fw(name, t + dt, cflCount); + } + } + + */ +}//testLevelSetMorph + +//////////////////////////////////////// + +void +TestTools::testLevelSetMeasure() +{ + const double percentage = 0.1/100.0;//i.e. 0.1% + typedef openvdb::FloatGrid GridT; + const int dim = 256; + openvdb::Real a, v, c, area, volume, curv; + + // First sphere + openvdb::Vec3f C(0.35f, 0.35f, 0.35f); + openvdb::Real r = 0.15, voxelSize = 1.0/(dim-1); + const openvdb::Real Pi = boost::math::constants::pi(); + GridT::Ptr sphere = openvdb::tools::createLevelSetSphere(r, C, voxelSize); + + typedef openvdb::tools::LevelSetMeasure MeasureT; + MeasureT m(*sphere); + + /// Test area and volume of sphere in world units + m.measure(a, v); + area = 4*Pi*r*r; + volume = 4.0/3.0*Pi*r*r*r; + //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; + //std::cerr << "\nVolume of sphere = " << volume << " " << v << std::endl; + // Test accuracy of computed measures to within 0.1% of the exact measure. + CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); + CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); + + // Test all measures of sphere in world units + m.measure(a, v, c); + area = 4*Pi*r*r; + volume = 4.0/3.0*Pi*r*r*r; + curv = 1.0/r; + //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; + //std::cerr << "Volume of sphere = " << volume << " " << v << std::endl; + //std::cerr << "Avg mean curvature of sphere = " << curv << " " << c << std::endl; + // Test accuracy of computed measures to within 0.1% of the exact measure. + CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); + CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); + CPPUNIT_ASSERT_DOUBLES_EQUAL(curv, c, percentage*curv); + + // Test all measures of sphere in index units + m.measure(a, v, c, false); + r /= voxelSize; + area = 4*Pi*r*r; + volume = 4.0/3.0*Pi*r*r*r; + curv = 1.0/r; + //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; + //std::cerr << "Volume of sphere = " << volume << " " << v << std::endl; + //std::cerr << "Avg mean curvature of sphere = " << curv << " " << c << std::endl; + // Test accuracy of computed measures to within 0.1% of the exact measure. + CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); + CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); + CPPUNIT_ASSERT_DOUBLES_EQUAL(curv, c, percentage*curv); + + // Second sphere + C = openvdb::Vec3f(5.4f, 6.4f, 8.4f); + r = 0.57f; + sphere = openvdb::tools::createLevelSetSphere(r, C, voxelSize); + m.reinit(*sphere); + + // Test all measures of sphere in world units + m.measure(a, v, c); + area = 4*Pi*r*r; + volume = 4.0/3.0*Pi*r*r*r; + curv = 1.0/r; + //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; + //std::cerr << "Volume of sphere = " << volume << " " << v << std::endl; + //std::cerr << "Avg mean curvature of sphere = " << curv << " " << c << std::endl; + // Test accuracy of computed measures to within 0.1% of the exact measure. + CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); + CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); + CPPUNIT_ASSERT_DOUBLES_EQUAL(curv, c, percentage*curv); + CPPUNIT_ASSERT_DOUBLES_EQUAL(area, openvdb::tools::levelSetArea(*sphere), percentage*area); + CPPUNIT_ASSERT_DOUBLES_EQUAL(volume,openvdb::tools::levelSetVolume(*sphere),percentage*volume); + + // Test all measures of sphere in index units + m.measure(a, v, c, false); + r /= voxelSize; + area = 4*Pi*r*r; + volume = 4.0/3.0*Pi*r*r*r; + curv = 1.0/r; + //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; + //std::cerr << "Volume of sphere = " << volume << " " << v << std::endl; + //std::cerr << "Avg mean curvature of sphere = " << curv << " " << c << std::endl; + // Test accuracy of computed measures to within 0.1% of the exact measure. + CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); + CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); + CPPUNIT_ASSERT_DOUBLES_EQUAL(curv, c, percentage*curv); + CPPUNIT_ASSERT_DOUBLES_EQUAL(area, openvdb::tools::levelSetArea(*sphere,false), + percentage*area); + CPPUNIT_ASSERT_DOUBLES_EQUAL(volume,openvdb::tools::levelSetVolume(*sphere,false), + percentage*volume); + + // Read level set from file + /* + unittest_util::CpuTimer timer; + openvdb::initialize();//required whenever I/O of OpenVDB files is performed! + openvdb::io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/venusstatue.vdb"); + sourceFile.open(); + GridT::Ptr model = openvdb::gridPtrCast(sourceFile.getGrids()->at(0)); + m.reinit(*model); + + //m.setGrainSize(1); + timer.start("\nParallel measure of area and volume"); + m.measure(a, v, false); + timer.stop(); + std::cerr << "Model: area = " << a << ", volume = " << v << std::endl; + + timer.start("\nParallel measure of area, volume and curvature"); + m.measure(a, v, c, false); + timer.stop(); + std::cerr << "Model: area = " << a << ", volume = " << v + << ", average curvature = " << c << std::endl; + + m.setGrainSize(0); + timer.start("\nSerial measure of area and volume"); + m.measure(a, v, false); + timer.stop(); + std::cerr << "Model: area = " << a << ", volume = " << v << std::endl; + + timer.start("\nSerial measure of area, volume and curvature"); + m.measure(a, v, c, false); + timer.stop(); + std::cerr << "Model: area = " << a << ", volume = " << v + << ", average curvature = " << c << std::endl; + */ +}//testLevelSetMeasure + +void +TestTools::testMagnitude() +{ + openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(/*background=*/5.0); + openvdb::FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const openvdb::Coord dim(64,64,64); + const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); + const float radius=0.0f; + unittest_util::makeSphere(dim,center,radius,*grid, + unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + openvdb::VectorGrid::Ptr gradGrid = openvdb::tools::gradient(*grid); + CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(gradGrid->activeVoxelCount())); + + openvdb::FloatGrid::Ptr mag = openvdb::tools::magnitude(*gradGrid); + CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(mag->activeVoxelCount())); + + openvdb::FloatGrid::ConstAccessor accessor = mag->getConstAccessor(); + + openvdb::Coord xyz(35,30,30); + float v = accessor.getValue(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, v, 0.01); + + xyz.reset(35,10,40); + v = accessor.getValue(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, v, 0.01); +} + + +void +TestTools::testMaskedMagnitude() +{ + openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(/*background=*/5.0); + openvdb::FloatTree& tree = grid->tree(); + CPPUNIT_ASSERT(tree.empty()); + + const openvdb::Coord dim(64,64,64); + const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); + const float radius=0.0f; + unittest_util::makeSphere(dim,center,radius,*grid, + unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT(!tree.empty()); + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + + openvdb::VectorGrid::Ptr gradGrid = openvdb::tools::gradient(*grid); + CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(gradGrid->activeVoxelCount())); + + + // create a masking grid + + const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); + openvdb::BoolGrid::Ptr maskGrid = openvdb::BoolGrid::create(false); + maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); + + // compute the magnitude in masked region + openvdb::FloatGrid::Ptr mag = openvdb::tools::magnitude(*gradGrid, *maskGrid); + + openvdb::FloatGrid::ConstAccessor accessor = mag->getConstAccessor(); + + // test in the masked region + openvdb::Coord xyz(35,30,30); + CPPUNIT_ASSERT(maskbbox.isInside(xyz)); + float v = accessor.getValue(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, v, 0.01); + + // test outside the masked region + xyz.reset(35,10,40); + CPPUNIT_ASSERT(!maskbbox.isInside(xyz)); + v = accessor.getValue(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, v, 0.01); +} + + +void +TestTools::testNormalize() +{ + openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(5.0); + openvdb::FloatTree& tree = grid->tree(); + + const openvdb::Coord dim(64,64,64); + const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); + const float radius=10.0f; + unittest_util::makeSphere( + dim,center,radius,*grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + openvdb::Coord xyz(10, 20, 30); + + openvdb::VectorGrid::Ptr grad = openvdb::tools::gradient(*grid); + + typedef openvdb::VectorGrid::ValueType Vec3Type; + + typedef openvdb::VectorGrid::ValueOnIter ValueIter; + + struct Local { + static inline Vec3Type op(const Vec3Type &x) { return x * 2.0f; } + static inline void visit(const ValueIter& it) { it.setValue(op(*it)); } + }; + + openvdb::tools::foreach(grad->beginValueOn(), Local::visit, true); + + openvdb::VectorGrid::ConstAccessor accessor = grad->getConstAccessor(); + + xyz = openvdb::Coord(35,10,40); + Vec3Type v = accessor.getValue(xyz); + //std::cerr << "\nPassed testNormalize(" << xyz << ")=" << v.length() << std::endl; + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0,v.length(),0.001); + openvdb::VectorGrid::Ptr norm = openvdb::tools::normalize(*grad); + + accessor = norm->getConstAccessor(); + v = accessor.getValue(xyz); + //std::cerr << "\nPassed testNormalize(" << xyz << ")=" << v.length() << std::endl; + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, v.length(), 0.0001); +} + + +void +TestTools::testMaskedNormalize() +{ + openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(5.0); + openvdb::FloatTree& tree = grid->tree(); + + const openvdb::Coord dim(64,64,64); + const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); + const float radius=10.0f; + unittest_util::makeSphere( + dim,center,radius,*grid, unittest_util::SPHERE_DENSE); + + CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); + openvdb::Coord xyz(10, 20, 30); + + openvdb::VectorGrid::Ptr grad = openvdb::tools::gradient(*grid); + + typedef openvdb::VectorGrid::ValueType Vec3Type; + + typedef openvdb::VectorGrid::ValueOnIter ValueIter; + + struct Local { + static inline Vec3Type op(const Vec3Type &x) { return x * 2.0f; } + static inline void visit(const ValueIter& it) { it.setValue(op(*it)); } + }; + + openvdb::tools::foreach(grad->beginValueOn(), Local::visit, true); + + openvdb::VectorGrid::ConstAccessor accessor = grad->getConstAccessor(); + + xyz = openvdb::Coord(35,10,40); + Vec3Type v = accessor.getValue(xyz); + + // create a masking grid + + const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); + openvdb::BoolGrid::Ptr maskGrid = openvdb::BoolGrid::create(false); + maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0,v.length(),0.001); + + // compute the normalized valued in the masked region + openvdb::VectorGrid::Ptr norm = openvdb::tools::normalize(*grad, *maskGrid); + + accessor = norm->getConstAccessor(); + { // outside the masked region + CPPUNIT_ASSERT(!maskbbox.isInside(xyz)); + v = accessor.getValue(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, v.length(), 0.0001); + } + { // inside the masked region + xyz.reset(35, 30, 30); + v = accessor.getValue(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, v.length(), 0.0001); + } +} + +//////////////////////////////////////// + + +void +TestTools::testPointAdvect() +{ + { + // Setup: Advect a number of points in a uniform velocity field (1,1,1). + // over a time dt=1 with each of the 4 different advection schemes. + // Points initialized at latice points. + // + // Uses: FloatTree (velocity), collocated sampling, advection + // + // Expected: All advection schemes will have the same result. Each point will + // be advanced to a new latice point. The i-th point will be at (i+1,i+1,i+1) + // + + const size_t numPoints = 2000000; + + // create a uniform velocity field in SINGLE PRECISION + const openvdb::Vec3f velocityBackground(1, 1, 1); + openvdb::Vec3fGrid::Ptr velocityGrid = openvdb::Vec3fGrid::create(velocityBackground); + + // using all the default template arguments + openvdb::tools::PointAdvect<> advectionTool(*velocityGrid); + + // create points + std::vector pointList(numPoints); /// larger than the tbb chunk size + for (size_t i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3f(i, i, i); + + for (unsigned int order = 1; order < 5; ++order) { + // check all four time integrations schemes + // construct an advection tool. By default the number of cpt iterations is zero + advectionTool.setIntegrationOrder(order); + advectionTool.advect(pointList, /*dt=*/1.0, /*iterations=*/1); + + // check locations + for (size_t i = 0; i < numPoints; i++) { + openvdb::Vec3f expected(i + 1, i + 1 , i + 1); + CPPUNIT_ASSERT_EQUAL(expected, pointList[i]); + } + // reset values + for (size_t i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3f(i, i, i); + } + + } + + { + // Setup: Advect a number of points in a uniform velocity field (1,1,1). + // over a time dt=1 with each of the 4 different advection schemes. + // And then project the point location onto the x-y plane + // Points initialized at latice points. + // + // Uses: DoubleTree (velocity), staggered sampling, constraint projection, advection + // + // Expected: All advection schemes will have the same result. Modes 1-4: Each point will + // be advanced to a new latice point and projected to x-y plane. + // The i-th point will be at (i+1,i+1,0). For mode 0 (no advection), i-th point + // will be found at (i, i, 0) + + const size_t numPoints = 4; + + // create a uniform velocity field in DOUBLE PRECISION + const openvdb::Vec3d velocityBackground(1, 1, 1); + openvdb::Vec3dGrid::Ptr velocityGrid = openvdb::Vec3dGrid::create(velocityBackground); + + // create a simple (horizontal) constraint field valid for a + // (-10,10)x(-10,10)x(-10,10) + const openvdb::Vec3d cptBackground(0, 0, 0); + openvdb::Vec3dGrid::Ptr cptGrid = openvdb::Vec3dGrid::create(cptBackground); + openvdb::Vec3dTree& cptTree = cptGrid->tree(); + + // create points + std::vector pointList(numPoints); + for (unsigned int i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3d(i, i, i); + + // Initialize the constraint field in a [-10,10]x[-10,10]x[-10,10] box + // this test will only work if the points remain in the box + openvdb::Coord ijk(0, 0, 0); + for (int i = -10; i < 11; i++) { + ijk.setX(i); + for (int j = -10; j < 11; j++) { + ijk.setY(j); + for (int k = -10; k < 11; k++) { + ijk.setZ(k); + // set the value as projection onto the x-y plane + cptTree.setValue(ijk, openvdb::Vec3d(i, j, 0)); + } + } + } + + // construct an advection tool. By default the number of cpt iterations is zero + openvdb::tools::ConstrainedPointAdvect, true> constrainedAdvectionTool(*velocityGrid, *cptGrid, 0); + constrainedAdvectionTool.setThreaded(false); + + // change the number of constraint interation from default 0 to 5 + constrainedAdvectionTool.setConstraintIterations(5); + + // test the pure-projection mode (order = 0) + constrainedAdvectionTool.setIntegrationOrder(0); + + // change the number of constraint interation (from 0 to 5) + constrainedAdvectionTool.setConstraintIterations(5); + + constrainedAdvectionTool.advect(pointList, /*dt=*/1.0, /*iterations=*/1); + + // check locations + for (unsigned int i = 0; i < numPoints; i++) { + openvdb::Vec3d expected(i, i, 0); // location (i, i, i) projected on to x-y plane + for (int n=0; n<3; ++n) { + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected[n], pointList[i][n], /*tolerance=*/1e-6); + } + } + + // reset values + for (unsigned int i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3d(i, i, i); + + // test all four time integrations schemes + for (unsigned int order = 1; order < 5; ++order) { + + constrainedAdvectionTool.setIntegrationOrder(order); + + constrainedAdvectionTool.advect(pointList, /*dt=*/1.0, /*iterations=*/1); + + // check locations + for (unsigned int i = 0; i < numPoints; i++) { + openvdb::Vec3d expected(i+1, i+1, 0); // location (i,i,i) projected onto x-y plane + for (int n=0; n<3; ++n) { + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected[n], pointList[i][n], /*tolerance=*/1e-6); + } + } + // reset values + for (unsigned int i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3d(i, i, i); + } + } +} + + +//////////////////////////////////////// + + +namespace { + + struct PointList + { + struct Point { double x,y,z; }; + std::vector list; + void add(const openvdb::Vec3d &p) { Point q={p[0],p[1],p[2]}; list.push_back(q); } + }; +} + + +void +TestTools::testPointScatter() +{ + typedef openvdb::FloatGrid GridType; + const openvdb::Coord dim(64, 64, 64); + const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); + const float radius = 20.0; + typedef boost::mt11213b RandGen; + RandGen mtRand; + + GridType::Ptr grid = GridType::create(/*background=*/2.0); + unittest_util::makeSphere(dim, center, radius, *grid, + unittest_util::SPHERE_DENSE_NARROW_BAND); + + { + const int pointCount = 1000; + PointList points; + openvdb::tools::UniformPointScatter scatter(points, pointCount, mtRand); + scatter.operator()(*grid); + CPPUNIT_ASSERT_EQUAL( pointCount, scatter.getPointCount() ); + CPPUNIT_ASSERT_EQUAL( pointCount, int(points.list.size()) ); + } + { + const float density = 1.0f;//per volume = per voxel since voxel size = 1 + PointList points; + openvdb::tools::UniformPointScatter scatter(points, density, mtRand); + scatter.operator()(*grid); + CPPUNIT_ASSERT_EQUAL( int(scatter.getVoxelCount()), scatter.getPointCount() ); + CPPUNIT_ASSERT_EQUAL( int(scatter.getVoxelCount()), int(points.list.size()) ); + } + { + const float density = 1.0f;//per volume = per voxel since voxel size = 1 + PointList points; + openvdb::tools::NonUniformPointScatter scatter(points, density, mtRand); + scatter.operator()(*grid); + CPPUNIT_ASSERT( int(scatter.getVoxelCount()) < scatter.getPointCount() ); + CPPUNIT_ASSERT_EQUAL( scatter.getPointCount(), int(points.list.size()) ); + } +} + + +//////////////////////////////////////// + + +void +TestTools::testFloatApply() +{ + typedef openvdb::FloatTree::ValueOnIter ValueIter; + + struct Local { + static inline float op(float x) { return x * 2.0; } + static inline void visit(const ValueIter& it) { it.setValue(op(*it)); } + }; + + const float background = 1.0; + openvdb::FloatTree tree(background); + + const int MIN = -1000, MAX = 1000, STEP = 50; + openvdb::Coord xyz; + for (int z = MIN; z < MAX; z += STEP) { + xyz.setZ(z); + for (int y = MIN; y < MAX; y += STEP) { + xyz.setY(y); + for (int x = MIN; x < MAX; x += STEP) { + xyz.setX(x); + tree.setValue(xyz, x + y + z); + } + } + } + /// @todo set some tile values + + openvdb::tools::foreach(tree.begin(), Local::visit, /*threaded=*/true); + + float expected = Local::op(background); + //CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, tree.background(), /*tolerance=*/0.0); + //expected = Local::op(-background); + //CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, -tree.background(), /*tolerance=*/0.0); + + for (openvdb::FloatTree::ValueOnCIter it = tree.cbeginValueOn(); it; ++it) { + xyz = it.getCoord(); + expected = Local::op(xyz[0] + xyz[1] + xyz[2]); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, it.getValue(), /*tolerance=*/0.0); + } +} + + +//////////////////////////////////////// + + +namespace { + +template +struct MatMul { + openvdb::math::Mat3s 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() +{ + typedef openvdb::VectorTree::ValueOnIter ValueIter; + + const openvdb::Vec3s background(1, 1, 1); + openvdb::VectorTree tree(background); + + const int MIN = -1000, MAX = 1000, STEP = 80; + openvdb::Coord xyz; + for (int z = MIN; z < MAX; z += STEP) { + xyz.setZ(z); + for (int y = MIN; y < MAX; y += STEP) { + xyz.setY(y); + for (int x = MIN; x < MAX; x += STEP) { + xyz.setX(x); + tree.setValue(xyz, openvdb::Vec3s(x, y, z)); + } + } + } + /// @todo set some tile values + + MatMul op(openvdb::math::Mat3s(1, 2, 3, -1, -2, -3, 3, 2, 1)); + openvdb::tools::foreach(tree.beginValueOn(), op, /*threaded=*/true); + + openvdb::Vec3s expected; + for (openvdb::VectorTree::ValueOnCIter it = tree.cbeginValueOn(); it; ++it) { + xyz = it.getCoord(); + expected = op.xform(openvdb::Vec3s(xyz[0], xyz[1], xyz[2])); + CPPUNIT_ASSERT_EQUAL(expected, it.getValue()); + } +} + + +//////////////////////////////////////// + + +namespace { + +struct AccumSum { + int64_t sum; int joins; + AccumSum(): sum(0), joins(0) {} + void operator()(const openvdb::Int32Tree::ValueOnCIter& it) + { + if (it.isVoxelValue()) sum += *it; + else sum += (*it) * it.getVoxelCount(); + } + void join(AccumSum& other) { sum += other.sum; joins += 1 + other.joins; } +}; + + +struct AccumLeafVoxelCount { + typedef openvdb::tree::LeafManager::LeafRange LeafRange; + openvdb::Index64 count; + AccumLeafVoxelCount(): count(0) {} + void operator()(const LeafRange::Iterator& it) { count += it->onVoxelCount(); } + void join(AccumLeafVoxelCount& other) { count += other.count; } +}; + +} + + +void +TestTools::testAccumulate() +{ + using namespace openvdb; + + const int value = 2; + Int32Tree tree(/*background=*/0); + tree.fill(CoordBBox::createCube(Coord(0), 198), value, /*active=*/true); + + const int64_t expected = tree.activeVoxelCount() * value; + { + AccumSum op; + tools::accumulate(tree.cbeginValueOn(), op, /*threaded=*/false); + CPPUNIT_ASSERT_EQUAL(expected, op.sum); + CPPUNIT_ASSERT_EQUAL(0, op.joins); + } + { + AccumSum op; + tools::accumulate(tree.cbeginValueOn(), op, /*threaded=*/true); + CPPUNIT_ASSERT_EQUAL(expected, op.sum); + } + { + AccumLeafVoxelCount op; + tree::LeafManager mgr(tree); + tools::accumulate(mgr.leafRange().begin(), op, /*threaded=*/true); + CPPUNIT_ASSERT_EQUAL(tree.activeLeafVoxelCount(), op.count); + } +} + + +//////////////////////////////////////// + + +namespace { + +template +struct FloatToVec +{ + typedef typename InIterT::ValueT ValueT; + typedef typename openvdb::tree::ValueAccessor Accessor; + + // Transform a scalar value into a vector value. + static openvdb::Vec3s toVec(const ValueT& v) { return openvdb::Vec3s(v, v*2, v*3); } + + FloatToVec() { numTiles = 0; } + + void operator()(const InIterT& it, Accessor& acc) + { + if (it.isVoxelValue()) { // set a single voxel + acc.setValue(it.getCoord(), toVec(*it)); + } else { // fill an entire tile + numTiles.fetch_and_increment(); + openvdb::CoordBBox bbox; + it.getBoundingBox(bbox); + acc.tree().fill(bbox, toVec(*it)); + } + } + + tbb::atomic numTiles; +}; + +} + + +void +TestTools::testTransformValues() +{ + using openvdb::CoordBBox; + using openvdb::Coord; + using openvdb::Vec3s; + + typedef openvdb::tree::Tree4::Type Tree323f; + typedef openvdb::tree::Tree4::Type Tree323v; + + const float background = 1.0; + Tree323f ftree(background); + + const int MIN = -1000, MAX = 1000, STEP = 80; + Coord xyz; + for (int z = MIN; z < MAX; z += STEP) { + xyz.setZ(z); + for (int y = MIN; y < MAX; y += STEP) { + xyz.setY(y); + for (int x = MIN; x < MAX; x += STEP) { + xyz.setX(x); + ftree.setValue(xyz, x + y + z); + } + } + } + // Set some tile values. + ftree.fill(CoordBBox(Coord(1024), Coord(1024 + 8 - 1)), 3 * 1024); // level-1 tile + ftree.fill(CoordBBox(Coord(2048), Coord(2048 + 32 - 1)), 3 * 2048); // level-2 tile + ftree.fill(CoordBBox(Coord(3072), Coord(3072 + 256 - 1)), 3 * 3072); // level-3 tile + + for (int shareOp = 0; shareOp <= 1; ++shareOp) { + FloatToVec op; + Tree323v vtree; + openvdb::tools::transformValues(ftree.cbeginValueOn(), vtree, op, + /*threaded=*/true, shareOp); + + // The tile count is accurate only if the functor is shared. Otherwise, + // it is initialized to zero in the main thread and never changed. + CPPUNIT_ASSERT_EQUAL(shareOp ? 3 : 0, int(op.numTiles)); + + Vec3s expected; + for (Tree323v::ValueOnCIter it = vtree.cbeginValueOn(); it; ++it) { + xyz = it.getCoord(); + expected = op.toVec(xyz[0] + xyz[1] + xyz[2]); + CPPUNIT_ASSERT_EQUAL(expected, it.getValue()); + } + // Check values inside the tiles. + CPPUNIT_ASSERT_EQUAL(op.toVec(3 * 1024), vtree.getValue(Coord(1024 + 4))); + CPPUNIT_ASSERT_EQUAL(op.toVec(3 * 2048), vtree.getValue(Coord(2048 + 16))); + CPPUNIT_ASSERT_EQUAL(op.toVec(3 * 3072), vtree.getValue(Coord(3072 + 128))); + } +} + + +//////////////////////////////////////// + + +void +TestTools::testUtil() +{ + using openvdb::CoordBBox; + using openvdb::Coord; + using openvdb::Vec3s; + + typedef openvdb::tree::Tree4::Type CharTree; + + // Test boolean operators + CharTree treeA(false), treeB(false); + + treeA.fill(CoordBBox(Coord(-10), Coord(10)), true); + treeA.voxelizeActiveTiles(); + + treeB.fill(CoordBBox(Coord(-10), Coord(10)), true); + treeB.voxelizeActiveTiles(); + + const size_t voxelCountA = treeA.activeVoxelCount(); + const size_t voxelCountB = treeB.activeVoxelCount(); + + CPPUNIT_ASSERT_EQUAL(voxelCountA, voxelCountB); + + CharTree::Ptr tree = openvdb::util::leafTopologyDifference(treeA, treeB); + CPPUNIT_ASSERT(tree->activeVoxelCount() == 0); + + tree = openvdb::util::leafTopologyIntersection(treeA, treeB); + CPPUNIT_ASSERT(tree->activeVoxelCount() == voxelCountA); + + treeA.fill(CoordBBox(Coord(-10), Coord(22)), true); + treeA.voxelizeActiveTiles(); + + const size_t voxelCount = treeA.activeVoxelCount(); + + tree = openvdb::util::leafTopologyDifference(treeA, treeB); + CPPUNIT_ASSERT(tree->activeVoxelCount() == (voxelCount - voxelCountA)); + + tree = openvdb::util::leafTopologyIntersection(treeA, treeB); + CPPUNIT_ASSERT(tree->activeVoxelCount() == voxelCountA); +} + + +//////////////////////////////////////// + + +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); + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestTransform.cc b/openvdb_2_3_0_library/openvdb/unittest/TestTransform.cc new file mode 100755 index 0000000..a5af0f8 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestTransform.cc @@ -0,0 +1,522 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + + +class TestTransform: public CppUnit::TestCase +{ +public: + virtual void setUp(); + virtual void tearDown(); + + CPPUNIT_TEST_SUITE(TestTransform); + CPPUNIT_TEST(testLinearTransform); + CPPUNIT_TEST(testTransformEquality); + CPPUNIT_TEST(testBackwardCompatibility); + CPPUNIT_TEST(testIsIdentity); + CPPUNIT_TEST_SUITE_END(); + + void testLinearTransform(); + void testTransformEquality(); + void testBackwardCompatibility(); + void testIsIdentity(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestTransform); + + +//////////////////////////////////////// + + +void +TestTransform::setUp() +{ + openvdb::math::MapRegistry::clear(); + openvdb::math::AffineMap::registerMap(); + openvdb::math::ScaleMap::registerMap(); + openvdb::math::UniformScaleMap::registerMap(); + openvdb::math::TranslationMap::registerMap(); + openvdb::math::ScaleTranslateMap::registerMap(); + openvdb::math::UniformScaleTranslateMap::registerMap(); +} + + +void +TestTransform::tearDown() +{ + openvdb::math::MapRegistry::clear(); +} + + +////openvdb:://////////////////////////////////// + + +void +TestTransform::testLinearTransform() +{ + using namespace openvdb; + double TOL = 1e-7; + + // Test: Scaling + math::Transform::Ptr t = math::Transform::createLinearTransform(0.5); + + Vec3R voxelSize = t->voxelSize(); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[2], TOL); + + CPPUNIT_ASSERT(t->hasUniformScale()); + + // world to index space + Vec3R xyz(-1.0, 2.0, 4.0); + xyz = t->worldToIndex(xyz); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-2.0, xyz[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 4.0, xyz[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 8.0, xyz[2], TOL); + + xyz = Vec3R(-0.7, 2.4, 4.7); + + // cell centered conversion + Coord ijk = t->worldToIndexCellCentered(xyz); + CPPUNIT_ASSERT_EQUAL(Coord(-1, 5, 9), ijk); + + // node centrered conversion + ijk = t->worldToIndexNodeCentered(xyz); + CPPUNIT_ASSERT_EQUAL(Coord(-2, 4, 9), ijk); + + // index to world space + ijk = Coord(4, 2, -8); + xyz = t->indexToWorld(ijk); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 2.0, xyz[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-4.0, xyz[2], TOL); + + // I/O test + { + std::stringstream + ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); + + t->write(ss); + + t = math::Transform::createLinearTransform(); + + // Since we wrote only a fragment of a VDB file (in particular, we didn't + // write the header), set the file format version number explicitly. + io::setCurrentVersion(ss); + + t->read(ss); + } + + // check map type + CPPUNIT_ASSERT_EQUAL(math::UniformScaleMap::mapType(), t->baseMap()->type()); + + voxelSize = t->voxelSize(); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[2], TOL); + + ////////// + + // Test: Scale, translation & rotation + t = math::Transform::createLinearTransform(2.0); + + // rotate, 180 deg, (produces a diagonal matrix that can be simplified into a scale map) + // with diagonal -2, 2, -2 + const double PI = std::atan(1.0)*4; + t->preRotate(PI, math::Y_AXIS); + + // this is just a rotation so it will have uniform scale + CPPUNIT_ASSERT(t->hasUniformScale()); + + CPPUNIT_ASSERT_EQUAL(math::ScaleMap::mapType(), t->baseMap()->type()); + + voxelSize = t->voxelSize(); + xyz = t->worldToIndex(Vec3R(-2.0, -2.0, -2.0)); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[2], TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[2], TOL); + + // translate + t->postTranslate(Vec3d(1.0, 0.0, 1.0)); + + CPPUNIT_ASSERT_EQUAL(math::ScaleTranslateMap::mapType(), t->baseMap()->type()); + + voxelSize = t->voxelSize(); + xyz = t->worldToIndex(Vec3R(-2.0, -2.0, -2.0)); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[2], TOL); + + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[2], TOL); + + + // I/O test + { + std::stringstream + ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); + + t->write(ss); + + t = math::Transform::createLinearTransform(); + + // Since we wrote only a fragment of a VDB file (in particular, we didn't + // write the header), set the file format version number explicitly. + io::setCurrentVersion(ss); + + t->read(ss); + } + + // check map type + CPPUNIT_ASSERT_EQUAL(math::ScaleTranslateMap::mapType(), t->baseMap()->type()); + + voxelSize = t->voxelSize(); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[2], TOL); + + xyz = t->worldToIndex(Vec3R(-2.0, -2.0, -2.0)); + + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[2], TOL); + + // new transform + t = math::Transform::createLinearTransform(1.0); + + // rotate 90 deg + t->preRotate( std::atan(1.0) * 2 , math::Y_AXIS); + + // check map type + CPPUNIT_ASSERT_EQUAL(math::AffineMap::mapType(), t->baseMap()->type()); + + xyz = t->worldToIndex(Vec3R(1.0, 1.0, 1.0)); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[2], TOL); + + // I/O test + { + std::stringstream + ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); + + t->write(ss); + + t = math::Transform::createLinearTransform(); + + CPPUNIT_ASSERT_EQUAL(math::UniformScaleMap::mapType(), t->baseMap()->type()); + + xyz = t->worldToIndex(Vec3R(1.0, 1.0, 1.0)); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, xyz[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, xyz[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, xyz[2], TOL); + + // Since we wrote only a fragment of a VDB file (in particular, we didn't + // write the header), set the file format version number explicitly. + io::setCurrentVersion(ss); + + t->read(ss); + } + + // check map type + CPPUNIT_ASSERT_EQUAL(math::AffineMap::mapType(), t->baseMap()->type()); + + xyz = t->worldToIndex(Vec3R(1.0, 1.0, 1.0)); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[2], TOL); +} + + +//////////////////////////////////////// + +void +TestTransform::testTransformEquality() +{ + using namespace openvdb; + + // maps created in different ways may be equivalent + math::Transform::Ptr t1 = math::Transform::createLinearTransform(0.5); + math::Mat4d mat = math::Mat4d::identity(); + mat.preScale(math::Vec3d(0.5, 0.5, 0.5)); + math::Transform::Ptr t2 = math::Transform::createLinearTransform(mat); + + CPPUNIT_ASSERT( *t1 == *t2); + + // test that the auto-convert to the simplest form worked + CPPUNIT_ASSERT( t1->mapType() == t2->mapType()); + + + mat.preScale(math::Vec3d(1., 1., .4)); + math::Transform::Ptr t3 = math::Transform::createLinearTransform(mat); + + CPPUNIT_ASSERT( *t1 != *t3); + + // test equality between different but equivalent maps + math::UniformScaleTranslateMap::Ptr ustmap( new math::UniformScaleTranslateMap(1.0, math::Vec3d(0,0,0))); + math::Transform::Ptr t4( new math::Transform( ustmap) ); + CPPUNIT_ASSERT( t4->baseMap()->isType() ); + math::Transform::Ptr t5( new math::Transform); // constructs with a scale map + CPPUNIT_ASSERT( t5->baseMap()->isType() ); + + CPPUNIT_ASSERT( *t5 == *t4); + + CPPUNIT_ASSERT( t5->mapType() != t4->mapType() ); + + // test inequatlity of two maps of the same type + math::UniformScaleTranslateMap::Ptr ustmap2( new math::UniformScaleTranslateMap(1.0, math::Vec3d(1,0,0))); + math::Transform::Ptr t6( new math::Transform( ustmap2) ); + CPPUNIT_ASSERT( t6->baseMap()->isType() ); + CPPUNIT_ASSERT( *t6 != *t4); + + // test comparison of linear to nonlinear map + openvdb::BBoxd bbox(math::Vec3d(0), math::Vec3d(100)); + math::Transform::Ptr frustum = math::Transform::createFrustumTransform(bbox, 0.25, 10); + + CPPUNIT_ASSERT( *frustum != *t1 ); + + +} +//////////////////////////////////////// + +void +TestTransform::testBackwardCompatibility() +{ + using namespace openvdb; + double TOL = 1e-7; + + // Register maps + math::MapRegistry::clear(); + math::AffineMap::registerMap(); + math::ScaleMap::registerMap(); + math::TranslationMap::registerMap(); + math::ScaleTranslateMap::registerMap(); + + std::stringstream + ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); + + + ////////// + + // Construct and write out an old transform that gets converted + // into a ScaleMap on read. + + // First write the old transform type name + writeString(ss, Name("LinearTransform")); + + // Second write the old transform's base class membes. + Coord tmpMin(0), tmpMax(1); + ss.write(reinterpret_cast(&tmpMin), sizeof(Coord::ValueType) * 3); + ss.write(reinterpret_cast(&tmpMax), sizeof(Coord::ValueType) * 3); + + // Last write out the old linear transform's members + math::Mat4d tmpLocalToWorld = math::Mat4d::identity(), + tmpWorldToLocal = math::Mat4d::identity(), + tmpVoxelToLocal = math::Mat4d::identity(), + tmpLocalToVoxel = math::Mat4d::identity(); + + tmpVoxelToLocal.preScale(math::Vec3d(0.5, 0.5, 0.5)); + + tmpLocalToWorld.write(ss); + tmpWorldToLocal.write(ss); + tmpVoxelToLocal.write(ss); + tmpLocalToVoxel.write(ss); + + // Read in the old transform and converting it to the new map based implementation. + + math::Transform::Ptr t = math::Transform::createLinearTransform(1.0); + + t->read(ss); + + // check map type + CPPUNIT_ASSERT_EQUAL(math::UniformScaleMap::mapType(), t->baseMap()->type()); + + Vec3d voxelSize = t->voxelSize(); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[2], TOL); + + Vec3d xyz = t->worldToIndex(Vec3d(-1.0, 2.0, 4.0)); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(-2.0, xyz[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 4.0, xyz[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 8.0, xyz[2], TOL); + + + ////////// + + // Construct and write out an old transform that gets converted + // into a ScaleTranslateMap on read. + + ss.clear(); + writeString(ss, Name("LinearTransform")); + ss.write((char*)&tmpMin, sizeof(Coord::ValueType) * 3); + ss.write((char*)&tmpMax, sizeof(Coord::ValueType) * 3); + tmpLocalToWorld = math::Mat4d::identity(), + tmpWorldToLocal = math::Mat4d::identity(), + tmpVoxelToLocal = math::Mat4d::identity(), + tmpLocalToVoxel = math::Mat4d::identity(); + + tmpVoxelToLocal.preScale(math::Vec3d(2.0, 2.0, 2.0)); + tmpLocalToWorld.setTranslation(math::Vec3d(1.0, 0.0, 1.0)); + + tmpLocalToWorld.write(ss); + tmpWorldToLocal.write(ss); + tmpVoxelToLocal.write(ss); + tmpLocalToVoxel.write(ss); + + // Read in the old transform and converting it to the new map based implementation. + + t = math::Transform::createLinearTransform(); // rest transform + t->read(ss); + + CPPUNIT_ASSERT_EQUAL(math::UniformScaleTranslateMap::mapType(), t->baseMap()->type()); + + voxelSize = t->voxelSize(); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[2], TOL); + + xyz = t->worldToIndex(Vec3d(1.0, 1.0, 1.0)); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, xyz[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, xyz[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, xyz[2], TOL); + + + ////////// + + // Construct and write out an old transform that gets converted + // into a AffineMap on read. + + ss.clear(); + writeString(ss, Name("LinearTransform")); + ss.write((char*)&tmpMin, sizeof(Coord::ValueType) * 3); + ss.write((char*)&tmpMax, sizeof(Coord::ValueType) * 3); + tmpLocalToWorld = math::Mat4d::identity(), + tmpWorldToLocal = math::Mat4d::identity(), + tmpVoxelToLocal = math::Mat4d::identity(), + tmpLocalToVoxel = math::Mat4d::identity(); + + tmpVoxelToLocal.preScale(math::Vec3d(1.0, 1.0, 1.0)); + tmpLocalToWorld.preRotate( math::Y_AXIS, std::atan(1.0) * 2); + + tmpLocalToWorld.write(ss); + tmpWorldToLocal.write(ss); + tmpVoxelToLocal.write(ss); + tmpLocalToVoxel.write(ss); + + // Read in the old transform and converting it to the new map based implementation. + + t = math::Transform::createLinearTransform(); // rest transform + t->read(ss); + + CPPUNIT_ASSERT_EQUAL(math::AffineMap::mapType(), t->baseMap()->type()); + + voxelSize = t->voxelSize(); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, voxelSize[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, voxelSize[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, voxelSize[2], TOL); + + xyz = t->worldToIndex(Vec3d(1.0, 1.0, 1.0)); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[0], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[1], TOL); + CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[2], TOL); +} + + +void +TestTransform::testIsIdentity() +{ + using namespace openvdb; + math::Transform::Ptr t = math::Transform::createLinearTransform(1.0); + + CPPUNIT_ASSERT(t->isIdentity()); + + t->preScale(Vec3d(2,2,2)); + + CPPUNIT_ASSERT(!t->isIdentity()); + + t->preScale(Vec3d(0.5,0.5,0.5)); + CPPUNIT_ASSERT(t->isIdentity()); + + BBoxd bbox(math::Vec3d(-5,-5,0), Vec3d(5,5,10)); + math::Transform::Ptr f = math::Transform::createFrustumTransform(bbox, + /*taper*/ 1, + /*depth*/ 1, + /*voxel size*/ 1); + f->preScale(Vec3d(10,10,10)); + + CPPUNIT_ASSERT(f->isIdentity()); + + // rotate by PI/2 + f->postRotate(std::atan(1.0)*2, math::Y_AXIS); + CPPUNIT_ASSERT(!f->isIdentity()); + + f->postRotate(std::atan(1.0)*6, math::Y_AXIS); + CPPUNIT_ASSERT(f->isIdentity()); +} + +//////////////////////////////////////// + + +/// @todo Test the new frustum transform. +/* +void +TestTransform::testNonlinearTransform() +{ + using namespace openvdb; + double TOL = 1e-7; +} +*/ + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestTree.cc b/openvdb_2_3_0_library/openvdb/unittest/TestTree.cc new file mode 100755 index 0000000..9d4d665 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestTree.cc @@ -0,0 +1,2534 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include // for remove() +#include +#include +#include +#include +#include +#include // for tools::setValueOnMin(), et al. +#include +#include +#include // for io::RealToHalf +#include // for Abs() +#include +#include "util.h" // for unittest_util::makeSphere() + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + + +typedef float ValueType; +typedef openvdb::tree::LeafNode LeafNodeType; +typedef openvdb::tree::InternalNode InternalNodeType1; +typedef openvdb::tree::InternalNode InternalNodeType2; +typedef openvdb::tree::RootNode RootNodeType; + + +class TestTree: public CppUnit::TestFixture +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestTree); + CPPUNIT_TEST(testBackground); + CPPUNIT_TEST(testHalf); + CPPUNIT_TEST(testValues); + CPPUNIT_TEST(testSetValue); + CPPUNIT_TEST(testSetValueOnly); + CPPUNIT_TEST(testEvalMinMax); + CPPUNIT_TEST(testResize); + CPPUNIT_TEST(testHasSameTopology); + CPPUNIT_TEST(testTopologyCopy); + CPPUNIT_TEST(testIterators); + CPPUNIT_TEST(testIO); + CPPUNIT_TEST(testNegativeIndexing); + CPPUNIT_TEST(testDeepCopy); + CPPUNIT_TEST(testMerge); + CPPUNIT_TEST(testVoxelizeActiveTiles); + CPPUNIT_TEST(testTopologyUnion); + CPPUNIT_TEST(testTopologyIntersection); + CPPUNIT_TEST(testTopologyDifference); + CPPUNIT_TEST(testSignedFloodFill); + CPPUNIT_TEST(testPruneInactive); + CPPUNIT_TEST(testPruneLevelSet); + CPPUNIT_TEST(testTouchLeaf); + CPPUNIT_TEST(testProbeLeaf); + CPPUNIT_TEST(testAddLeaf); + CPPUNIT_TEST(testAddTile); + CPPUNIT_TEST(testLeafManager); + CPPUNIT_TEST(testProcessBBox); + CPPUNIT_TEST(testStealNode); + CPPUNIT_TEST_SUITE_END(); + + void testBackground(); + void testHalf(); + void testValues(); + void testSetValue(); + void testSetValueOnly(); + void testEvalMinMax(); + void testResize(); + void testHasSameTopology(); + void testTopologyCopy(); + void testIterators(); + void testIO(); + void testNegativeIndexing(); + void testDeepCopy(); + void testMerge(); + void testVoxelizeActiveTiles(); + void testTopologyUnion(); + void testTopologyIntersection(); + void testTopologyDifference(); + void testSignedFloodFill(); + void testPruneLevelSet(); + void testPruneInactive(); + void testTouchLeaf(); + void testProbeLeaf(); + void testAddLeaf(); + void testAddTile(); + void testLeafManager(); + void testProcessBBox(); + void testStealNode(); + +private: + template void testWriteHalf(); + template void doTestMerge(openvdb::MergePolicy); +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION(TestTree); + + +void +TestTree::testBackground() +{ + const ValueType background = 5.0f; + RootNodeType root_node(background); + CPPUNIT_ASSERT(root_node.getLevel()==3); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, root_node.getValue(openvdb::Coord(5,10,20))); + const ValueType newBackground = 10.0f; + root_node.setBackground(newBackground); + ASSERT_DOUBLES_EXACTLY_EQUAL(newBackground, root_node.getValue(openvdb::Coord(5,10,20))); +} + + +void +TestTree::testHalf() +{ + testWriteHalf(); + testWriteHalf(); + testWriteHalf(); + testWriteHalf(); + testWriteHalf(); + testWriteHalf(); + + // Verify that non-floating-point grids are saved correctly. + testWriteHalf(); + testWriteHalf(); + testWriteHalf(); +} + + +template +void +TestTree::testWriteHalf() +{ + typedef openvdb::Grid GridType; + typedef typename TreeType::ValueType ValueT; + ValueT background = openvdb::zeroVal(); + background += 5; + GridType grid(background); + + unittest_util::makeSphere(openvdb::Coord(64, 64, 64), + openvdb::Vec3f(35, 30, 40), + /*radius=*/10, grid, + /*dx=*/1.0f, unittest_util::SPHERE_DENSE); + CPPUNIT_ASSERT(!grid.tree().empty()); + + // Write grid blocks in both float and half formats. + std::ostringstream outFull(std::ios_base::binary); + grid.setSaveFloatAsHalf(false); + grid.writeBuffers(outFull); + outFull.flush(); + const size_t fullBytes = outFull.str().size(); + CPPUNIT_ASSERT_MESSAGE("wrote empty full float buffers", fullBytes > 0); + + std::ostringstream outHalf(std::ios_base::binary); + grid.setSaveFloatAsHalf(true); + grid.writeBuffers(outHalf); + outHalf.flush(); + const size_t halfBytes = outHalf.str().size(); + CPPUNIT_ASSERT_MESSAGE("wrote empty half float buffers", halfBytes > 0); + + if (openvdb::io::RealToHalf::isReal) { + // Verify that the half float file is "significantly smaller" than the full float file. + std::ostringstream ostr; + ostr << "half float buffers not significantly smaller than full float (" + << halfBytes << " vs. " << fullBytes << " bytes)"; + CPPUNIT_ASSERT_MESSAGE(ostr.str(), halfBytes < size_t(0.75 * fullBytes)); + } else { + // For non-real data types, "half float" and "full float" file sizes should be the same. + CPPUNIT_ASSERT_MESSAGE("full float and half float file sizes differ for data of type " + + std::string(openvdb::typeNameAsString()), halfBytes == fullBytes); + } + + // Read back the half float data (converting back to full float in the process), + // then write it out again in half float format. Verify that the resulting file + // is identical to the original half float file. + { + openvdb::Grid gridCopy(grid); + gridCopy.setSaveFloatAsHalf(true); + std::istringstream is(outHalf.str(), std::ios_base::binary); + + // Since the input stream doesn't include a VDB header with file format version info, + // tag the input stream explicitly with the current version number. + openvdb::io::setCurrentVersion(is); + + gridCopy.readBuffers(is); + + std::ostringstream outDiff(std::ios_base::binary); + gridCopy.writeBuffers(outDiff); + outDiff.flush(); + + CPPUNIT_ASSERT_MESSAGE("half-from-full and half-from-half buffers differ", + outHalf.str() == outDiff.str()); + } +} + + +void +TestTree::testValues() +{ + ValueType background=5.0f; + + { + const openvdb::Coord c0(5,10,20), c1(50000,20000,30000); + RootNodeType root_node(background); + const float v0=0.234f, v1=4.5678f; + CPPUNIT_ASSERT(root_node.empty()); + ASSERT_DOUBLES_EXACTLY_EQUAL(root_node.getValue(c0), background); + ASSERT_DOUBLES_EXACTLY_EQUAL(root_node.getValue(c1), background); + root_node.setValueOn(c0, v0); + root_node.setValueOn(c1, v1); + ASSERT_DOUBLES_EXACTLY_EQUAL(v0,root_node.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(v1,root_node.getValue(c1)); + int count=0; + for (int i =0; i<256; ++i) { + for (int j=0; j<256; ++j) { + for (int k=0; k<256; ++k) { + if (root_node.getValue(openvdb::Coord(i,j,k))<1.0f) ++count; + } + } + } + CPPUNIT_ASSERT(count == 1); + } + + { + const openvdb::Coord min(-30,-25,-60), max(60,80,100); + const openvdb::Coord c0(-5,-10,-20), c1(50,20,90), c2(59,67,89); + const float v0=0.234f, v1=4.5678f, v2=-5.673f; + RootNodeType root_node(background); + CPPUNIT_ASSERT(root_node.empty()); + ASSERT_DOUBLES_EXACTLY_EQUAL(background,root_node.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background,root_node.getValue(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background,root_node.getValue(c2)); + root_node.setValueOn(c0, v0); + root_node.setValueOn(c1, v1); + root_node.setValueOn(c2, v2); + ASSERT_DOUBLES_EXACTLY_EQUAL(v0,root_node.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(v1,root_node.getValue(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(v2,root_node.getValue(c2)); + int count=0; + for (int i =min[0]; i +void +evalMinMaxTest() +{ + typedef typename TreeT::ValueType ValueT; + + struct Local { + static bool isEqual(const ValueT& a, const ValueT& b) { + using namespace openvdb; // for operator>() + return !(math::Abs(a - b) > zeroVal()); + } + }; + + const ValueT + zero = openvdb::zeroVal(), + minusTwo = zero + (-2), + plusTwo = zero + 2, + five = zero + 5; + + TreeT tree(/*background=*/five); + + // No set voxels (defaults to min = max = zero) + ValueT minVal = five, maxVal = five; + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT(Local::isEqual(minVal, zero)); + CPPUNIT_ASSERT(Local::isEqual(maxVal, zero)); + + // Only one set voxel + tree.setValue(openvdb::Coord(0, 0, 0), minusTwo); + minVal = maxVal = five; + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT(Local::isEqual(minVal, minusTwo)); + CPPUNIT_ASSERT(Local::isEqual(maxVal, minusTwo)); + + // Multiple set voxels, single value + tree.setValue(openvdb::Coord(10, 10, 10), minusTwo); + minVal = maxVal = five; + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT(Local::isEqual(minVal, minusTwo)); + CPPUNIT_ASSERT(Local::isEqual(maxVal, minusTwo)); + + // Multiple set voxels, multiple values + tree.setValue(openvdb::Coord(10, 10, 10), plusTwo); + tree.setValue(openvdb::Coord(-10, -10, -10), zero); + minVal = maxVal = five; + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT(Local::isEqual(minVal, minusTwo)); + CPPUNIT_ASSERT(Local::isEqual(maxVal, plusTwo)); +} + +/// Specialization for boolean trees +template<> +void +evalMinMaxTest() +{ + openvdb::BoolTree tree(/*background=*/false); + + // No set voxels (defaults to min = max = zero) + bool minVal = true, maxVal = false; + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT_EQUAL(false, minVal); + CPPUNIT_ASSERT_EQUAL(false, maxVal); + + // Only one set voxel + tree.setValue(openvdb::Coord(0, 0, 0), true); + minVal = maxVal = false; + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT_EQUAL(true, minVal); + CPPUNIT_ASSERT_EQUAL(true, maxVal); + + // Multiple set voxels, single value + tree.setValue(openvdb::Coord(-10, -10, -10), true); + minVal = maxVal = false; + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT_EQUAL(true, minVal); + CPPUNIT_ASSERT_EQUAL(true, maxVal); + + // Multiple set voxels, multiple values + tree.setValue(openvdb::Coord(10, 10, 10), false); + minVal = true; maxVal = false; + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT_EQUAL(false, minVal); + CPPUNIT_ASSERT_EQUAL(true, maxVal); +} + +/// Specialization for string trees +template<> +void +evalMinMaxTest() +{ + const std::string + echidna("echidna"), loris("loris"), pangolin("pangolin"); + + openvdb::StringTree tree(/*background=*/loris); + + // No set voxels (defaults to min = max = zero) + std::string minVal, maxVal; + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT_EQUAL(std::string(), minVal); + CPPUNIT_ASSERT_EQUAL(std::string(), maxVal); + + // Only one set voxel + tree.setValue(openvdb::Coord(0, 0, 0), pangolin); + minVal.clear(); maxVal.clear(); + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT_EQUAL(pangolin, minVal); + CPPUNIT_ASSERT_EQUAL(pangolin, maxVal); + + // Multiple set voxels, single value + tree.setValue(openvdb::Coord(-10, -10, -10), pangolin); + minVal.clear(); maxVal.clear(); + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT_EQUAL(pangolin, minVal); + CPPUNIT_ASSERT_EQUAL(pangolin, maxVal); + + // Multiple set voxels, multiple values + tree.setValue(openvdb::Coord(10, 10, 10), echidna); + minVal.clear(); maxVal.clear(); + tree.evalMinMax(minVal, maxVal); + CPPUNIT_ASSERT_EQUAL(echidna, minVal); + CPPUNIT_ASSERT_EQUAL(pangolin, maxVal); +} + +} // unnamed namespace + +void +TestTree::testEvalMinMax() +{ + evalMinMaxTest(); + evalMinMaxTest(); + evalMinMaxTest(); + evalMinMaxTest(); + evalMinMaxTest(); + evalMinMaxTest(); +} + + +void +TestTree::testResize() +{ + ValueType background=5.0f; + //use this when resize is implemented + RootNodeType root_node(background); + CPPUNIT_ASSERT(root_node.getLevel()==3); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, root_node.getValue(openvdb::Coord(5,10,20))); + //fprintf(stdout,"Root grid dim=(%i,%i,%i)\n", + // root_node.getGridDim(0), root_node.getGridDim(1), root_node.getGridDim(2)); + root_node.setValueOn(openvdb::Coord(5,10,20),0.234f); + ASSERT_DOUBLES_EXACTLY_EQUAL(root_node.getValue(openvdb::Coord(5,10,20)) , 0.234f); + root_node.setValueOn(openvdb::Coord(500,200,300),4.5678f); + ASSERT_DOUBLES_EXACTLY_EQUAL(root_node.getValue(openvdb::Coord(500,200,300)) , 4.5678f); + { + ValueType sum=0.0f; + for (RootNodeType::ChildOnIter root_iter = root_node.beginChildOn(); + root_iter.test(); ++root_iter) + { + for (InternalNodeType2::ChildOnIter internal_iter2 = root_iter->beginChildOn(); + internal_iter2.test(); ++internal_iter2) + { + for (InternalNodeType1::ChildOnIter internal_iter1 = + internal_iter2->beginChildOn(); internal_iter1.test(); ++internal_iter1) + { + for (LeafNodeType::ValueOnIter block_iter = + internal_iter1->beginValueOn(); block_iter.test(); ++block_iter) + { + sum += *block_iter; + } + } + } + } + ASSERT_DOUBLES_EXACTLY_EQUAL(sum, (0.234f + 4.5678f)); + } + + CPPUNIT_ASSERT(root_node.getLevel()==3); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, root_node.getValue(openvdb::Coord(5,11,20))); + { + ValueType sum=0.0f; + for (RootNodeType::ChildOnIter root_iter = root_node.beginChildOn(); + root_iter.test(); ++root_iter) + { + for (InternalNodeType2::ChildOnIter internal_iter2 = root_iter->beginChildOn(); + internal_iter2.test(); ++internal_iter2) + { + for (InternalNodeType1::ChildOnIter internal_iter1 = + internal_iter2->beginChildOn(); internal_iter1.test(); ++internal_iter1) + { + for (LeafNodeType::ValueOnIter block_iter = + internal_iter1->beginValueOn(); block_iter.test(); ++block_iter) + { + sum += *block_iter; + } + } + } + } + ASSERT_DOUBLES_EXACTLY_EQUAL(sum, (0.234f + 4.5678f)); + } + +} + + +void +TestTree::testHasSameTopology() +{ + // Test using trees of the same type. + { + const float background1=5.0f; + openvdb::FloatTree tree1(background1); + + const float background2=6.0f; + openvdb::FloatTree tree2(background2); + + CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); + + tree1.setValue(openvdb::Coord(-10,40,845),3.456f); + CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); + + tree2.setValue(openvdb::Coord(-10,40,845),-3.456f); + CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); + + tree1.setValue(openvdb::Coord(1,-500,-8), 1.0f); + CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); + + tree2.setValue(openvdb::Coord(1,-500,-8),1.0f); + CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); + } + // Test using trees of different types. + { + const float background1=5.0f; + openvdb::FloatTree tree1(background1); + + const openvdb::Vec3f background2(1.0f,3.4f,6.0f); + openvdb::Vec3fTree tree2(background2); + + CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); + + tree1.setValue(openvdb::Coord(-10,40,845),3.456f); + CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); + + tree2.setValue(openvdb::Coord(-10,40,845),openvdb::Vec3f(1.0f,2.0f,-3.0f)); + CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); + + tree1.setValue(openvdb::Coord(1,-500,-8), 1.0f); + CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); + + tree2.setValue(openvdb::Coord(1,-500,-8),openvdb::Vec3f(1.0f,2.0f,-3.0f)); + CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); + } +} + + +void +TestTree::testTopologyCopy() +{ + // Test using trees of the same type. + { + const float background1=5.0f; + openvdb::FloatTree tree1(background1); + tree1.setValue(openvdb::Coord(-10,40,845),3.456f); + tree1.setValue(openvdb::Coord(1,-50,-8), 1.0f); + + const float background2=6.0f, setValue2=3.0f; + openvdb::FloatTree tree2(tree1,background2,setValue2,openvdb::TopologyCopy()); + + CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); + + ASSERT_DOUBLES_EXACTLY_EQUAL(background2, tree2.getValue(openvdb::Coord(1,2,3))); + ASSERT_DOUBLES_EXACTLY_EQUAL(setValue2, tree2.getValue(openvdb::Coord(-10,40,845))); + ASSERT_DOUBLES_EXACTLY_EQUAL(setValue2, tree2.getValue(openvdb::Coord(1,-50,-8))); + + tree1.setValue(openvdb::Coord(1,-500,-8), 1.0f); + CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); + + tree2.setValue(openvdb::Coord(1,-500,-8),1.0f); + CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); + } + // Test using trees of different types. + { + const openvdb::Vec3f background1(1.0f,3.4f,6.0f); + openvdb::Vec3fTree tree1(background1); + tree1.setValue(openvdb::Coord(-10,40,845),openvdb::Vec3f(3.456f,-2.3f,5.6f)); + tree1.setValue(openvdb::Coord(1,-50,-8), openvdb::Vec3f(1.0f,3.0f,4.5f)); + + const float background2=6.0f, setValue2=3.0f; + openvdb::FloatTree tree2(tree1,background2,setValue2,openvdb::TopologyCopy()); + + CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); + + ASSERT_DOUBLES_EXACTLY_EQUAL(background2, tree2.getValue(openvdb::Coord(1,2,3))); + ASSERT_DOUBLES_EXACTLY_EQUAL(setValue2, tree2.getValue(openvdb::Coord(-10,40,845))); + ASSERT_DOUBLES_EXACTLY_EQUAL(setValue2, tree2.getValue(openvdb::Coord(1,-50,-8))); + + tree1.setValue(openvdb::Coord(1,-500,-8), openvdb::Vec3f(1.0f,0.0f,-3.0f)); + CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); + + tree2.setValue(openvdb::Coord(1,-500,-8), 1.0f); + CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); + CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); + } +} + + +void +TestTree::testIterators() +{ + ValueType background=5.0f; + RootNodeType root_node(background); + root_node.setValueOn(openvdb::Coord(5,10,20),0.234f); + root_node.setValueOn(openvdb::Coord(50000,20000,30000),4.5678f); + { + ValueType sum=0.0f; + for (RootNodeType::ChildOnIter root_iter = root_node.beginChildOn(); + root_iter.test(); ++root_iter) + { + for (InternalNodeType2::ChildOnIter internal_iter2 = root_iter->beginChildOn(); + internal_iter2.test(); ++internal_iter2) + { + for (InternalNodeType1::ChildOnIter internal_iter1 = + internal_iter2->beginChildOn(); internal_iter1.test(); ++internal_iter1) + { + for (LeafNodeType::ValueOnIter block_iter = + internal_iter1->beginValueOn(); block_iter.test(); ++block_iter) + { + sum += *block_iter; + } + } + } + } + ASSERT_DOUBLES_EXACTLY_EQUAL((0.234f + 4.5678f), sum); + } + { + // As above, but using dense iterators. + ValueType sum = 0.0f, val = 0.0f; + for (RootNodeType::ChildAllIter rootIter = root_node.beginChildAll(); + rootIter.test(); ++rootIter) + { + if (!rootIter.isChildNode()) continue; + + for (InternalNodeType2::ChildAllIter internalIter2 = + rootIter.probeChild(val)->beginChildAll(); internalIter2; ++internalIter2) + { + if (!internalIter2.isChildNode()) continue; + + for (InternalNodeType1::ChildAllIter internalIter1 = + internalIter2.probeChild(val)->beginChildAll(); internalIter1; ++internalIter1) + { + if (!internalIter1.isChildNode()) continue; + + for (LeafNodeType::ValueOnIter leafIter = + internalIter1.probeChild(val)->beginValueOn(); leafIter; ++leafIter) + { + sum += *leafIter; + } + } + } + } + ASSERT_DOUBLES_EXACTLY_EQUAL((0.234f + 4.5678f), sum); + } + { + ValueType v_sum=0.0f; + openvdb::Coord xyz0, xyz1, xyz2, xyz3, xyzSum(0, 0, 0); + for (RootNodeType::ChildOnIter root_iter = root_node.beginChildOn(); + root_iter.test(); ++root_iter) + { + root_iter.getCoord(xyz3); + for (InternalNodeType2::ChildOnIter internal_iter2 = root_iter->beginChildOn(); + internal_iter2.test(); ++internal_iter2) + { + internal_iter2.getCoord(xyz2); + xyz2 = xyz2 - internal_iter2.parent().origin(); + for (InternalNodeType1::ChildOnIter internal_iter1 = + internal_iter2->beginChildOn(); internal_iter1.test(); ++internal_iter1) + { + internal_iter1.getCoord(xyz1); + xyz1 = xyz1 - internal_iter1.parent().origin(); + for (LeafNodeType::ValueOnIter block_iter = + internal_iter1->beginValueOn(); block_iter.test(); ++block_iter) + { + block_iter.getCoord(xyz0); + xyz0 = xyz0 - block_iter.parent().origin(); + v_sum += *block_iter; + xyzSum = xyzSum + xyz0 + xyz1 + xyz2 + xyz3; + } + } + } + } + ASSERT_DOUBLES_EXACTLY_EQUAL((0.234f + 4.5678f), v_sum); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(5 + 50000, 10 + 20000, 20 + 30000), xyzSum); + } +} + + +void +TestTree::testIO() +{ + const char* filename = "/tmp/test.dbg"; + boost::shared_ptr scopedFile(filename, ::remove); + { + ValueType background=5.0f; + RootNodeType root_node(background); + root_node.setValueOn(openvdb::Coord(5,10,20),0.234f); + root_node.setValueOn(openvdb::Coord(50000,20000,30000),4.5678f); + + std::ofstream os(filename, std::ios_base::binary); + root_node.writeTopology(os); + root_node.writeBuffers(os); + CPPUNIT_ASSERT(!os.fail()); + } + { + ValueType background=2.0f; + RootNodeType root_node(background); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, root_node.getValue(openvdb::Coord(5,10,20))); + { + std::ifstream is(filename, std::ios_base::binary); + // Since the test file doesn't include a VDB header with file format version info, + // tag the input stream explicitly with the current version number. + openvdb::io::setCurrentVersion(is); + root_node.readTopology(is); + root_node.readBuffers(is); + CPPUNIT_ASSERT(!is.fail()); + } + + ASSERT_DOUBLES_EXACTLY_EQUAL(0.234f, root_node.getValue(openvdb::Coord(5,10,20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(5.0f, root_node.getValue(openvdb::Coord(5,11,20))); + ValueType sum=0.0f; + for (RootNodeType::ChildOnIter root_iter = root_node.beginChildOn(); + root_iter.test(); ++root_iter) + { + for (InternalNodeType2::ChildOnIter internal_iter2 = root_iter->beginChildOn(); + internal_iter2.test(); ++internal_iter2) + { + for (InternalNodeType1::ChildOnIter internal_iter1 = + internal_iter2->beginChildOn(); internal_iter1.test(); ++internal_iter1) + { + for (LeafNodeType::ValueOnIter block_iter = + internal_iter1->beginValueOn(); block_iter.test(); ++block_iter) + { + sum += *block_iter; + } + } + } + } + ASSERT_DOUBLES_EXACTLY_EQUAL(sum, (0.234f + 4.5678f)); + } +} + + +void +TestTree::testNegativeIndexing() +{ + ValueType background=5.0f; + openvdb::FloatTree tree(background); + CPPUNIT_ASSERT(tree.empty()); + ASSERT_DOUBLES_EXACTLY_EQUAL(tree.getValue(openvdb::Coord(5,-10,20)), background); + ASSERT_DOUBLES_EXACTLY_EQUAL(tree.getValue(openvdb::Coord(-5000,2000,3000)), background); + tree.setValue(openvdb::Coord( 5, 10, 20),0.0f); + tree.setValue(openvdb::Coord(-5, 10, 20),0.1f); + tree.setValue(openvdb::Coord( 5,-10, 20),0.2f); + tree.setValue(openvdb::Coord( 5, 10,-20),0.3f); + tree.setValue(openvdb::Coord(-5,-10, 20),0.4f); + tree.setValue(openvdb::Coord(-5, 10,-20),0.5f); + tree.setValue(openvdb::Coord( 5,-10,-20),0.6f); + tree.setValue(openvdb::Coord(-5,-10,-20),0.7f); + tree.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); + tree.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); + tree.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.0f, tree.getValue(openvdb::Coord( 5, 10, 20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.1f, tree.getValue(openvdb::Coord(-5, 10, 20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.2f, tree.getValue(openvdb::Coord( 5,-10, 20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.3f, tree.getValue(openvdb::Coord( 5, 10,-20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.4f, tree.getValue(openvdb::Coord(-5,-10, 20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.5f, tree.getValue(openvdb::Coord(-5, 10,-20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.6f, tree.getValue(openvdb::Coord( 5,-10,-20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.7f, tree.getValue(openvdb::Coord(-5,-10,-20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord(-5000, 2000,-3000))); + ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord( 5000,-2000,-3000))); + ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord(-5000,-2000, 3000))); + int count=0; + for (int i =-25; i<25; ++i) { + for (int j=-25; j<25; ++j) { + for (int k=-25; k<25; ++k) { + if (tree.getValue(openvdb::Coord(i,j,k))<1.0f) { + //fprintf(stderr,"(%i,%i,%i)=%f\n",i,j,k,tree.getValue(openvdb::Coord(i,j,k))); + ++count; + } + } + } + } + CPPUNIT_ASSERT(count == 8); + int count2 = 0; + openvdb::Coord xyz; + for (openvdb::FloatTree::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { + ++count2; + xyz = iter.getCoord(); + //std::cerr << xyz << " = " << *iter << "\n"; + } + CPPUNIT_ASSERT(count2 == 11); + CPPUNIT_ASSERT(tree.activeVoxelCount() == 11); + { + count2 = 0; + for (openvdb::FloatTree::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { + ++count2; + xyz = iter.getCoord(); + //std::cerr << xyz << " = " << *iter << "\n"; + } + CPPUNIT_ASSERT(count2 == 11); + CPPUNIT_ASSERT(tree.activeVoxelCount() == 11); + } +} + + +void +TestTree::testDeepCopy() +{ + // set up a tree + const float fillValue1=5.0f; + openvdb::FloatTree tree1(fillValue1); + tree1.setValue(openvdb::Coord(-10,40,845), 3.456f); + tree1.setValue(openvdb::Coord(1,-50,-8), 1.0f); + + // make a deep copy of the tree + openvdb::TreeBase::Ptr newTree = tree1.copy(); + + // cast down to the concrete type to query values + openvdb::FloatTree *pTree2 = dynamic_cast(newTree.get()); + + // compare topology + CPPUNIT_ASSERT(tree1.hasSameTopology(*pTree2)); + CPPUNIT_ASSERT(pTree2->hasSameTopology(tree1)); + + // trees should be equal + ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue1, pTree2->getValue(openvdb::Coord(1,2,3))); + ASSERT_DOUBLES_EXACTLY_EQUAL(3.456f, pTree2->getValue(openvdb::Coord(-10,40,845))); + ASSERT_DOUBLES_EXACTLY_EQUAL(1.0f, pTree2->getValue(openvdb::Coord(1,-50,-8))); + + // change 1 value in tree2 + openvdb::Coord changeCoord(1, -500, -8); + pTree2->setValue(changeCoord, 1.0f); + + // topology should no longer match + CPPUNIT_ASSERT(!tree1.hasSameTopology(*pTree2)); + CPPUNIT_ASSERT(!pTree2->hasSameTopology(tree1)); + + // query changed value and make sure it's different between trees + ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue1, tree1.getValue(changeCoord)); + ASSERT_DOUBLES_EXACTLY_EQUAL(1.0f, pTree2->getValue(changeCoord)); +} + + +void +TestTree::testMerge() +{ + ValueType background=5.0f; + openvdb::FloatTree tree0(background), tree1(background), tree2(background); + CPPUNIT_ASSERT(tree2.empty()); + tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); + tree0.setValue(openvdb::Coord(-5, 10, 20),0.1f); + tree0.setValue(openvdb::Coord( 5,-10, 20),0.2f); + tree0.setValue(openvdb::Coord( 5, 10,-20),0.3f); + tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); + tree1.setValue(openvdb::Coord(-5, 10, 20),0.1f); + tree1.setValue(openvdb::Coord( 5,-10, 20),0.2f); + tree1.setValue(openvdb::Coord( 5, 10,-20),0.3f); + + tree0.setValue(openvdb::Coord(-5,-10, 20),0.4f); + tree0.setValue(openvdb::Coord(-5, 10,-20),0.5f); + tree0.setValue(openvdb::Coord( 5,-10,-20),0.6f); + tree0.setValue(openvdb::Coord(-5,-10,-20),0.7f); + tree0.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); + tree0.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); + tree0.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); + tree2.setValue(openvdb::Coord(-5,-10, 20),0.4f); + tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); + tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); + tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); + tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); + tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); + tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); + + CPPUNIT_ASSERT(tree0.leafCount()!=tree1.leafCount()); + CPPUNIT_ASSERT(tree0.leafCount()!=tree2.leafCount()); + + CPPUNIT_ASSERT(!tree2.empty()); + tree1.merge(tree2, openvdb::MERGE_ACTIVE_STATES); + CPPUNIT_ASSERT(tree2.empty()); + CPPUNIT_ASSERT(tree0.leafCount()==tree1.leafCount()); + CPPUNIT_ASSERT(tree0.nonLeafCount()==tree1.nonLeafCount()); + CPPUNIT_ASSERT(tree0.activeLeafVoxelCount()==tree1.activeLeafVoxelCount()); + CPPUNIT_ASSERT(tree0.inactiveLeafVoxelCount()==tree1.inactiveLeafVoxelCount()); + CPPUNIT_ASSERT(tree0.activeVoxelCount()==tree1.activeVoxelCount()); + CPPUNIT_ASSERT(tree0.inactiveVoxelCount()==tree1.inactiveVoxelCount()); + + for (openvdb::FloatTree::ValueOnCIter iter0 = tree0.cbeginValueOn(); iter0; ++iter0) { + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter0,tree1.getValue(iter0.getCoord())); + } + + // Test active tile support. + { + using namespace openvdb; + FloatTree treeA(/*background*/0.0), treeB(/*background*/0.0); + + treeA.fill(CoordBBox(Coord(16,16,16), Coord(31,31,31)), /*value*/1.0); + treeB.fill(CoordBBox(Coord(0,0,0), Coord(15,15,15)), /*value*/1.0); + + CPPUNIT_ASSERT_EQUAL(4096, int(treeA.activeVoxelCount())); + CPPUNIT_ASSERT_EQUAL(4096, int(treeB.activeVoxelCount())); + + treeA.merge(treeB, MERGE_ACTIVE_STATES); + + CPPUNIT_ASSERT_EQUAL(8192, int(treeA.activeVoxelCount())); + CPPUNIT_ASSERT_EQUAL(0, int(treeB.activeVoxelCount())); + } + + doTestMerge(openvdb::MERGE_NODES); + doTestMerge(openvdb::MERGE_ACTIVE_STATES); + doTestMerge(openvdb::MERGE_ACTIVE_STATES_AND_NODES); + + doTestMerge(openvdb::MERGE_NODES); + doTestMerge(openvdb::MERGE_ACTIVE_STATES); + doTestMerge(openvdb::MERGE_ACTIVE_STATES_AND_NODES); +} + + +template +void +TestTree::doTestMerge(openvdb::MergePolicy policy) +{ + using namespace openvdb; + + TreeType treeA, treeB; + + typedef typename TreeType::RootNodeType RootT; + typedef typename TreeType::LeafNodeType LeafT; + + const typename TreeType::ValueType val(1); + const int + depth = static_cast(treeA.treeDepth()), + leafDim = static_cast(LeafT::dim()), + leafSize = static_cast(LeafT::size()); + // Coords that are in a different top-level branch than (0, 0, 0) + const Coord pos(static_cast(RootT::getChildDim())); + + treeA.setValueOff(pos, val); + treeA.setValueOff(-pos, val); + + treeB.setValueOff(Coord(0), val); + treeB.fill(CoordBBox(pos, pos.offsetBy(leafDim - 1)), val, /*active=*/true); + treeB.setValueOn(-pos, val); + + // treeA treeB . + // . + // R R . + // / \ /|\ . + // I I I I I . + // / \ / | \ . + // I I I I I . + // / \ / | on x SIZE . + // L L L L . + // off off on off . + + CPPUNIT_ASSERT_EQUAL(0, int(treeA.activeVoxelCount())); + CPPUNIT_ASSERT_EQUAL(leafSize + 1, int(treeB.activeVoxelCount())); + CPPUNIT_ASSERT_EQUAL(2, int(treeA.leafCount())); + CPPUNIT_ASSERT_EQUAL(2, int(treeB.leafCount())); + CPPUNIT_ASSERT_EQUAL(2*(depth-2)+1, int(treeA.nonLeafCount())); // 2 branches (II+II+R) + CPPUNIT_ASSERT_EQUAL(3*(depth-2)+1, int(treeB.nonLeafCount())); // 3 branches (II+II+II+R) + + treeA.merge(treeB, policy); + + // MERGE_NODES MERGE_ACTIVE_STATES MERGE_ACTIVE_STATES_AND_NODES . + // . + // R R R . + // /|\ /|\ /|\ . + // I I I I I I I I I . + // / | \ / | \ / | \ . + // I I I I I I I I I . + // / | \ / | on x SIZE / | \ . + // L L L L L L L L . + // off off off on off on off on x SIZE . + + switch (policy) { + case MERGE_NODES: + CPPUNIT_ASSERT_EQUAL(0, int(treeA.activeVoxelCount())); + CPPUNIT_ASSERT_EQUAL(2 + 1, int(treeA.leafCount())); // 1 leaf node stolen from B + CPPUNIT_ASSERT_EQUAL(3*(depth-2)+1, int(treeA.nonLeafCount())); // 3 branches (II+II+II+R) + break; + case MERGE_ACTIVE_STATES: + CPPUNIT_ASSERT_EQUAL(2, int(treeA.leafCount())); // 1 leaf stolen, 1 replaced with tile + CPPUNIT_ASSERT_EQUAL(3*(depth-2)+1, int(treeA.nonLeafCount())); // 3 branches (II+II+II+R) + CPPUNIT_ASSERT_EQUAL(leafSize + 1, int(treeA.activeVoxelCount())); + break; + case MERGE_ACTIVE_STATES_AND_NODES: + CPPUNIT_ASSERT_EQUAL(2 + 1, int(treeA.leafCount())); // 1 leaf node stolen from B + CPPUNIT_ASSERT_EQUAL(3*(depth-2)+1, int(treeA.nonLeafCount())); // 3 branches (II+II+II+R) + CPPUNIT_ASSERT_EQUAL(leafSize + 1, int(treeA.activeVoxelCount())); + break; + } + CPPUNIT_ASSERT(treeB.empty()); +} + + +void +TestTree::testVoxelizeActiveTiles() +{ + using openvdb::CoordBBox; + using openvdb::Coord; + // Use a small custom tree so we don't run out of memory when + // tiles are converted to dense leafs :) + typedef openvdb::tree::Tree4::Type MyTree; + float background=5.0f; + const Coord xyz[] = {Coord(-1,-2,-3),Coord( 1, 2, 3)}; + //check two leaf nodes and two tiles at each level 1, 2 and 3 + const int tile_size[4]={0, 1<<2, 1<<(2*2), 1<<(3*2)}; + for (int level=0; level<=3; ++level) { + + MyTree tree(background); + CPPUNIT_ASSERT_EQUAL(-1,tree.getValueDepth(xyz[0])); + CPPUNIT_ASSERT_EQUAL(-1,tree.getValueDepth(xyz[1])); + + if (level==0) { + tree.setValue(xyz[0], 1.0f); + tree.setValue(xyz[1], 1.0f); + } else { + const int n = tile_size[level]; + tree.fill(CoordBBox::createCube(Coord(-n,-n,-n), n), 1.0f, true); + tree.fill(CoordBBox::createCube(Coord( 0, 0, 0), n), 1.0f, true); + } + + CPPUNIT_ASSERT_EQUAL(3-level,tree.getValueDepth(xyz[0])); + CPPUNIT_ASSERT_EQUAL(3-level,tree.getValueDepth(xyz[1])); + + tree.voxelizeActiveTiles(); + + CPPUNIT_ASSERT_EQUAL(3 ,tree.getValueDepth(xyz[0])); + CPPUNIT_ASSERT_EQUAL(3 ,tree.getValueDepth(xyz[1])); + } +} + + +void +TestTree::testTopologyUnion() +{ + {//super simple test with only two active values + const ValueType background=0.0f; + openvdb::FloatTree tree0(background), tree1(background); + tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); + tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); + openvdb::FloatTree tree2(tree1); + + tree1.topologyUnion(tree0); + + for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { + CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); + } + for (openvdb::FloatTree::ValueOnCIter iter = tree2.cbeginValueOn(); iter; ++iter) { + CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); + } + for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree2.getValue(iter.getCoord())); + } + } + {// test using setValue + ValueType background=5.0f; + openvdb::FloatTree tree0(background), tree1(background), tree2(background); + CPPUNIT_ASSERT(tree2.empty()); + // tree0 = tree1.topologyUnion(tree2) + tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); + tree0.setValue(openvdb::Coord(-5, 10, 20),0.1f); + tree0.setValue(openvdb::Coord( 5,-10, 20),0.2f); + tree0.setValue(openvdb::Coord( 5, 10,-20),0.3f); + tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); + tree1.setValue(openvdb::Coord(-5, 10, 20),0.1f); + tree1.setValue(openvdb::Coord( 5,-10, 20),0.2f); + tree1.setValue(openvdb::Coord( 5, 10,-20),0.3f); + + tree0.setValue(openvdb::Coord(-5,-10, 20),background); + tree0.setValue(openvdb::Coord(-5, 10,-20),background); + tree0.setValue(openvdb::Coord( 5,-10,-20),background); + tree0.setValue(openvdb::Coord(-5,-10,-20),background); + tree0.setValue(openvdb::Coord(-5000, 2000,-3000),background); + tree0.setValue(openvdb::Coord( 5000,-2000,-3000),background); + tree0.setValue(openvdb::Coord(-5000,-2000, 3000),background); + tree2.setValue(openvdb::Coord(-5,-10, 20),0.4f); + tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); + tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); + tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); + tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); + tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); + tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); + + // tree3 has the same topology as tree2 but a different value type + const openvdb::Vec3f background2(1.0f,3.4f,6.0f), vec_val(3.1f,5.3f,-9.5f); + openvdb::Vec3fTree tree3(background2); + for (openvdb::FloatTree::ValueOnCIter iter2 = tree2.cbeginValueOn(); iter2; ++iter2) { + tree3.setValue(iter2.getCoord(), vec_val); + } + + CPPUNIT_ASSERT(tree0.leafCount()!=tree1.leafCount()); + CPPUNIT_ASSERT(tree0.leafCount()!=tree2.leafCount()); + CPPUNIT_ASSERT(tree0.leafCount()!=tree3.leafCount()); + + CPPUNIT_ASSERT(!tree2.empty()); + CPPUNIT_ASSERT(!tree3.empty()); + openvdb::FloatTree tree1_copy(tree1); + + //tree1.topologyUnion(tree2);//should make tree1 = tree0 + tree1.topologyUnion(tree3);//should make tree1 = tree0 + + CPPUNIT_ASSERT(tree0.leafCount()==tree1.leafCount()); + CPPUNIT_ASSERT(tree0.nonLeafCount()==tree1.nonLeafCount()); + CPPUNIT_ASSERT(tree0.activeLeafVoxelCount()==tree1.activeLeafVoxelCount()); + CPPUNIT_ASSERT(tree0.inactiveLeafVoxelCount()==tree1.inactiveLeafVoxelCount()); + CPPUNIT_ASSERT(tree0.activeVoxelCount()==tree1.activeVoxelCount()); + CPPUNIT_ASSERT(tree0.inactiveVoxelCount()==tree1.inactiveVoxelCount()); + + CPPUNIT_ASSERT(tree1.hasSameTopology(tree0)); + CPPUNIT_ASSERT(tree0.hasSameTopology(tree1)); + + for (openvdb::FloatTree::ValueOnCIter iter2 = tree2.cbeginValueOn(); iter2; ++iter2) { + CPPUNIT_ASSERT(tree1.isValueOn(iter2.getCoord())); + } + for (openvdb::FloatTree::ValueOnCIter iter1 = tree1.cbeginValueOn(); iter1; ++iter1) { + CPPUNIT_ASSERT(tree0.isValueOn(iter1.getCoord())); + } + for (openvdb::FloatTree::ValueOnCIter iter0 = tree0.cbeginValueOn(); iter0; ++iter0) { + CPPUNIT_ASSERT(tree1.isValueOn(iter0.getCoord())); + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter0,tree1.getValue(iter0.getCoord())); + } + for (openvdb::FloatTree::ValueOnCIter iter = tree1_copy.cbeginValueOn(); iter; ++iter) { + CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(iter.getCoord())); + } + for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { + const openvdb::Coord p = iter.getCoord(); + CPPUNIT_ASSERT(tree3.isValueOn(p) || tree1_copy.isValueOn(p)); + } + } + { + ValueType background=5.0f; + openvdb::FloatTree tree0(background), tree1(background), tree2(background); + CPPUNIT_ASSERT(tree2.empty()); + // tree0 = tree1.topologyUnion(tree2) + tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); + tree0.setValue(openvdb::Coord(-5, 10, 20),0.1f); + tree0.setValue(openvdb::Coord( 5,-10, 20),0.2f); + tree0.setValue(openvdb::Coord( 5, 10,-20),0.3f); + tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); + tree1.setValue(openvdb::Coord(-5, 10, 20),0.1f); + tree1.setValue(openvdb::Coord( 5,-10, 20),0.2f); + tree1.setValue(openvdb::Coord( 5, 10,-20),0.3f); + + tree0.setValue(openvdb::Coord(-5,-10, 20),background); + tree0.setValue(openvdb::Coord(-5, 10,-20),background); + tree0.setValue(openvdb::Coord( 5,-10,-20),background); + tree0.setValue(openvdb::Coord(-5,-10,-20),background); + tree0.setValue(openvdb::Coord(-5000, 2000,-3000),background); + tree0.setValue(openvdb::Coord( 5000,-2000,-3000),background); + tree0.setValue(openvdb::Coord(-5000,-2000, 3000),background); + tree2.setValue(openvdb::Coord(-5,-10, 20),0.4f); + tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); + tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); + tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); + tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); + tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); + tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); + + // tree3 has the same topology as tree2 but a different value type + const openvdb::Vec3f background2(1.0f,3.4f,6.0f), vec_val(3.1f,5.3f,-9.5f); + openvdb::Vec3fTree tree3(background2); + + for (openvdb::FloatTree::ValueOnCIter iter2 = tree2.cbeginValueOn(); iter2; ++iter2) { + tree3.setValue(iter2.getCoord(), vec_val); + } + + openvdb::FloatTree tree4(tree1);//tree4 = tree1 + openvdb::FloatTree tree5(tree1);//tree5 = tree1 + + tree1.topologyUnion(tree3);//should make tree1 = tree0 + + CPPUNIT_ASSERT(tree1.hasSameTopology(tree0)); + + for (openvdb::Vec3fTree::ValueOnCIter iter3 = tree3.cbeginValueOn(); iter3; ++iter3) { + tree4.setValueOn(iter3.getCoord()); + const openvdb::Coord p = iter3.getCoord(); + ASSERT_DOUBLES_EXACTLY_EQUAL(tree1.getValue(p),tree5.getValue(p)); + ASSERT_DOUBLES_EXACTLY_EQUAL(tree4.getValue(p),tree5.getValue(p)); + } + + CPPUNIT_ASSERT(tree4.hasSameTopology(tree0)); + + for (openvdb::FloatTree::ValueOnCIter iter4 = tree4.cbeginValueOn(); iter4; ++iter4) { + const openvdb::Coord p = iter4.getCoord(); + ASSERT_DOUBLES_EXACTLY_EQUAL(tree0.getValue(p),tree5.getValue(p)); + ASSERT_DOUBLES_EXACTLY_EQUAL(tree1.getValue(p),tree5.getValue(p)); + ASSERT_DOUBLES_EXACTLY_EQUAL(tree4.getValue(p),tree5.getValue(p)); + } + + for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { + const openvdb::Coord p = iter.getCoord(); + CPPUNIT_ASSERT(tree3.isValueOn(p) || tree4.isValueOn(p)); + } + } + {// test overlapping spheres + const float background=5.0f, R0=10.0f, R1=5.6f; + const openvdb::Vec3f C0(35.0f, 30.0f, 40.0f), C1(22.3f, 30.5f, 31.0f); + const openvdb::Coord dim(32, 32, 32); + openvdb::FloatGrid grid0(background); + openvdb::FloatGrid grid1(background); + unittest_util::makeSphere(dim, C0, R0, grid0, + 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); + unittest_util::makeSphere(dim, C1, R1, grid1, + 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); + openvdb::FloatTree& tree0 = grid0.tree(); + openvdb::FloatTree& tree1 = grid1.tree(); + openvdb::FloatTree tree0_copy(tree0); + + tree0.topologyUnion(tree1); + + const openvdb::Index64 n0 = tree0_copy.activeVoxelCount(); + const openvdb::Index64 n = tree0.activeVoxelCount(); + const openvdb::Index64 n1 = tree1.activeVoxelCount(); + + //fprintf(stderr,"Union of spheres: n=%i, n0=%i n1=%i n0+n1=%i\n",n,n0,n1, n0+n1); + + CPPUNIT_ASSERT( n > n0 ); + CPPUNIT_ASSERT( n > n1 ); + CPPUNIT_ASSERT( n < n0 + n1 ); + + for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { + const openvdb::Coord p = iter.getCoord(); + CPPUNIT_ASSERT(tree0.isValueOn(p)); + ASSERT_DOUBLES_EXACTLY_EQUAL(tree0.getValue(p), tree0_copy.getValue(p)); + } + for (openvdb::FloatTree::ValueOnCIter iter = tree0_copy.cbeginValueOn(); iter; ++iter) { + const openvdb::Coord p = iter.getCoord(); + CPPUNIT_ASSERT(tree0.isValueOn(p)); + ASSERT_DOUBLES_EXACTLY_EQUAL(tree0.getValue(p), *iter); + } + } + +}// testTopologyUnion + +void +TestTree::testTopologyIntersection() +{ + {//no overlapping voxels + const ValueType background=0.0f; + openvdb::FloatTree tree0(background), tree1(background); + tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); + tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree0.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree1.activeVoxelCount()); + + tree1.topologyIntersection(tree0); + + CPPUNIT_ASSERT_EQUAL(tree1.activeVoxelCount(), openvdb::Index64(0)); + CPPUNIT_ASSERT(!tree1.empty()); + tree1.pruneInactive(); + CPPUNIT_ASSERT(tree1.empty()); + } + {//two overlapping voxels + const ValueType background=0.0f; + openvdb::FloatTree tree0(background), tree1(background); + tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); + + tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); + tree1.setValue(openvdb::Coord( 500, 300, 200), 1.0f); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); + + tree1.topologyIntersection(tree0); + + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); + CPPUNIT_ASSERT(!tree1.empty()); + tree1.pruneInactive(); + CPPUNIT_ASSERT(!tree1.empty()); + } + {//4 overlapping voxels + const ValueType background=0.0f; + openvdb::FloatTree tree0(background), tree1(background); + tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); + tree0.setValue(openvdb::Coord( 400, 30, 20), 2.0f); + tree0.setValue(openvdb::Coord( 8, 11, 11), 3.0f); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); + + tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); + tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); + tree1.setValue(openvdb::Coord( 8, 11, 11), 6.0f); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); + + tree1.topologyIntersection(tree0); + + CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); + CPPUNIT_ASSERT(!tree1.empty()); + tree1.pruneInactive(); + CPPUNIT_ASSERT(!tree1.empty()); + CPPUNIT_ASSERT_EQUAL( openvdb::Index32(2), tree1.leafCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); + } + {//passive tile + const ValueType background=0.0f; + const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; + openvdb::FloatTree tree0(background), tree1(background); + tree0.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, false); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(0), tree0.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); + + tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); + tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); + tree1.setValue(openvdb::Coord( dim, 11, 11), 6.0f); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); + + tree1.topologyIntersection(tree0); + + CPPUNIT_ASSERT_EQUAL( openvdb::Index32(0), tree1.leafCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(0), tree1.activeVoxelCount() ); + CPPUNIT_ASSERT(tree1.empty()); + } + {//active tile + const ValueType background=0.0f; + const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; + openvdb::FloatTree tree0(background), tree1(background); + tree1.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, true); + CPPUNIT_ASSERT_EQUAL(dim*dim*dim, tree1.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree1.leafCount() ); + + tree0.setValue(openvdb::Coord( 500, 301, 200), 4.0f); + tree0.setValue(openvdb::Coord( 400, 30, 20), 5.0f); + tree0.setValue(openvdb::Coord( dim, 11, 11), 6.0f); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); + + tree1.topologyIntersection(tree0); + + CPPUNIT_ASSERT_EQUAL( openvdb::Index32(2), tree1.leafCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); + CPPUNIT_ASSERT(!tree1.empty()); + tree1.pruneInactive(); + CPPUNIT_ASSERT(!tree1.empty()); + } + {// use tree with different voxel type + ValueType background=5.0f; + openvdb::FloatTree tree0(background), tree1(background), tree2(background); + CPPUNIT_ASSERT(tree2.empty()); + // tree0 = tree1.topologyIntersection(tree2) + tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); + tree0.setValue(openvdb::Coord(-5, 10,-20),0.1f); + tree0.setValue(openvdb::Coord( 5,-10,-20),0.2f); + tree0.setValue(openvdb::Coord(-5,-10,-20),0.3f); + + tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); + tree1.setValue(openvdb::Coord(-5, 10,-20),0.1f); + tree1.setValue(openvdb::Coord( 5,-10,-20),0.2f); + tree1.setValue(openvdb::Coord(-5,-10,-20),0.3f); + + tree2.setValue(openvdb::Coord( 5, 10, 20),0.4f); + tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); + tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); + tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); + + tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); + tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); + tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); + + openvdb::FloatTree tree1_copy(tree1); + + // tree3 has the same topology as tree2 but a different value type + const openvdb::Vec3f background2(1.0f,3.4f,6.0f), vec_val(3.1f,5.3f,-9.5f); + openvdb::Vec3fTree tree3(background2); + for (openvdb::FloatTree::ValueOnCIter iter = tree2.cbeginValueOn(); iter; ++iter) { + tree3.setValue(iter.getCoord(), vec_val); + } + + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree0.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree1.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree2.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree3.leafCount()); + + + //tree1.topologyInterection(tree2);//should make tree1 = tree0 + tree1.topologyIntersection(tree3);//should make tree1 = tree0 + + CPPUNIT_ASSERT(tree0.leafCount()==tree1.leafCount()); + CPPUNIT_ASSERT(tree0.nonLeafCount()==tree1.nonLeafCount()); + CPPUNIT_ASSERT(tree0.activeLeafVoxelCount()==tree1.activeLeafVoxelCount()); + CPPUNIT_ASSERT(tree0.inactiveLeafVoxelCount()==tree1.inactiveLeafVoxelCount()); + CPPUNIT_ASSERT(tree0.activeVoxelCount()==tree1.activeVoxelCount()); + CPPUNIT_ASSERT(tree0.inactiveVoxelCount()==tree1.inactiveVoxelCount()); + CPPUNIT_ASSERT(tree1.hasSameTopology(tree0)); + CPPUNIT_ASSERT(tree0.hasSameTopology(tree1)); + + for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { + const openvdb::Coord p = iter.getCoord(); + CPPUNIT_ASSERT(tree1.isValueOn(p)); + CPPUNIT_ASSERT(tree2.isValueOn(p)); + CPPUNIT_ASSERT(tree3.isValueOn(p)); + CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(p)); + } + for (openvdb::FloatTree::ValueOnCIter iter = tree1_copy.cbeginValueOn(); iter; ++iter) { + CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(iter.getCoord())); + } + for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { + const openvdb::Coord p = iter.getCoord(); + CPPUNIT_ASSERT(tree0.isValueOn(p)); + CPPUNIT_ASSERT(tree2.isValueOn(p)); + CPPUNIT_ASSERT(tree3.isValueOn(p)); + CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree0.getValue(p)); + } + } + + {// test overlapping spheres + const float background=5.0f, R0=10.0f, R1=5.6f; + const openvdb::Vec3f C0(35.0f, 30.0f, 40.0f), C1(22.3f, 30.5f, 31.0f); + const openvdb::Coord dim(32, 32, 32); + openvdb::FloatGrid grid0(background); + openvdb::FloatGrid grid1(background); + unittest_util::makeSphere(dim, C0, R0, grid0, + 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); + unittest_util::makeSphere(dim, C1, R1, grid1, + 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); + openvdb::FloatTree& tree0 = grid0.tree(); + openvdb::FloatTree& tree1 = grid1.tree(); + openvdb::FloatTree tree0_copy(tree0); + + tree0.topologyIntersection(tree1); + + const openvdb::Index64 n0 = tree0_copy.activeVoxelCount(); + const openvdb::Index64 n = tree0.activeVoxelCount(); + const openvdb::Index64 n1 = tree1.activeVoxelCount(); + + //fprintf(stderr,"Intersection of spheres: n=%i, n0=%i n1=%i n0+n1=%i\n",n,n0,n1, n0+n1); + + CPPUNIT_ASSERT( n < n0 ); + CPPUNIT_ASSERT( n < n1 ); + + for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { + const openvdb::Coord p = iter.getCoord(); + CPPUNIT_ASSERT(tree1.isValueOn(p)); + CPPUNIT_ASSERT(tree0_copy.isValueOn(p)); + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter, tree0_copy.getValue(p)); + } + } + + {// Test based on boolean grids + openvdb::CoordBBox big( openvdb::Coord(-9), openvdb::Coord(10)); + openvdb::CoordBBox small(openvdb::Coord( 1), openvdb::Coord(10)); + + openvdb::BoolGrid::Ptr gridBig = openvdb::BoolGrid::create(false); + gridBig->fill(big, true/*value*/, true /*make active*/); + CPPUNIT_ASSERT_EQUAL(8, int(gridBig->tree().activeTileCount())); + CPPUNIT_ASSERT_EQUAL((20 * 20 * 20), int(gridBig->activeVoxelCount())); + + openvdb::BoolGrid::Ptr gridSmall = openvdb::BoolGrid::create(false); + gridSmall->fill(small, true/*value*/, true /*make active*/); + CPPUNIT_ASSERT_EQUAL(0, int(gridSmall->tree().activeTileCount())); + CPPUNIT_ASSERT_EQUAL((10 * 10 * 10), int(gridSmall->activeVoxelCount())); + + // change the topology of gridBig by intersecting with gridSmall + gridBig->topologyIntersection(*gridSmall); + + // Should be unchanged + CPPUNIT_ASSERT_EQUAL(0, int(gridSmall->tree().activeTileCount())); + CPPUNIT_ASSERT_EQUAL((10 * 10 * 10), int(gridSmall->activeVoxelCount())); + + // In this case the interesection should be exactly "small" + CPPUNIT_ASSERT_EQUAL(0, int(gridBig->tree().activeTileCount())); + CPPUNIT_ASSERT_EQUAL((10 * 10 * 10), int(gridBig->activeVoxelCount())); + + } + +}// testTopologyIntersection + +void +TestTree::testTopologyDifference() +{ + {//no overlapping voxels + const ValueType background=0.0f; + openvdb::FloatTree tree0(background), tree1(background); + tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); + tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree0.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree1.activeVoxelCount()); + + tree1.topologyDifference(tree0); + + CPPUNIT_ASSERT_EQUAL(tree1.activeVoxelCount(), openvdb::Index64(1)); + CPPUNIT_ASSERT(!tree1.empty()); + tree1.pruneInactive(); + CPPUNIT_ASSERT(!tree1.empty()); + } + {//two overlapping voxels + const ValueType background=0.0f; + openvdb::FloatTree tree0(background), tree1(background); + tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); + + tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); + tree1.setValue(openvdb::Coord( 500, 300, 200), 1.0f); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); + + CPPUNIT_ASSERT( tree0.isValueOn(openvdb::Coord( 500, 300, 200))); + CPPUNIT_ASSERT( tree1.isValueOn(openvdb::Coord( 500, 300, 200))); + CPPUNIT_ASSERT( tree1.isValueOn(openvdb::Coord( 8, 11, 11))); + + tree1.topologyDifference(tree0); + + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); + CPPUNIT_ASSERT( tree0.isValueOn(openvdb::Coord( 500, 300, 200))); + CPPUNIT_ASSERT(!tree1.isValueOn(openvdb::Coord( 500, 300, 200))); + CPPUNIT_ASSERT( tree1.isValueOn(openvdb::Coord( 8, 11, 11))); + + CPPUNIT_ASSERT(!tree1.empty()); + tree1.pruneInactive(); + CPPUNIT_ASSERT(!tree1.empty()); + } + {//4 overlapping voxels + const ValueType background=0.0f; + openvdb::FloatTree tree0(background), tree1(background); + tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); + tree0.setValue(openvdb::Coord( 400, 30, 20), 2.0f); + tree0.setValue(openvdb::Coord( 8, 11, 11), 3.0f); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); + + tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); + tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); + tree1.setValue(openvdb::Coord( 8, 11, 11), 6.0f); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); + + tree1.topologyDifference(tree0); + + CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); + CPPUNIT_ASSERT(!tree1.empty()); + tree1.pruneInactive(); + CPPUNIT_ASSERT(!tree1.empty()); + CPPUNIT_ASSERT_EQUAL( openvdb::Index32(1), tree1.leafCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); + } + {//passive tile + const ValueType background=0.0f; + const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; + openvdb::FloatTree tree0(background), tree1(background); + tree0.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, false); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(0), tree0.activeVoxelCount()); + CPPUNIT_ASSERT(!tree0.hasActiveTiles()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(0), tree0.root().onTileCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); + + tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); + tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); + tree1.setValue(openvdb::Coord( dim, 11, 11), 6.0f); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); + CPPUNIT_ASSERT(!tree1.hasActiveTiles()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); + + tree1.topologyDifference(tree0); + + CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(3), tree1.activeVoxelCount() ); + CPPUNIT_ASSERT(!tree1.empty()); + tree1.pruneInactive(); + CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(3), tree1.activeVoxelCount() ); + CPPUNIT_ASSERT(!tree1.empty()); + } + {//active tile + const ValueType background=0.0f; + const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; + openvdb::FloatTree tree0(background), tree1(background); + tree1.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, true); + CPPUNIT_ASSERT_EQUAL(dim*dim*dim, tree1.activeVoxelCount()); + CPPUNIT_ASSERT(tree1.hasActiveTiles()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree1.root().onTileCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); + + tree0.setValue(openvdb::Coord( 500, 301, 200), 4.0f); + tree0.setValue(openvdb::Coord( 400, 30, 20), 5.0f); + tree0.setValue(openvdb::Coord( int(dim), 11, 11), 6.0f); + CPPUNIT_ASSERT(!tree0.hasActiveTiles()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); + CPPUNIT_ASSERT( tree0.isValueOn(openvdb::Coord( int(dim), 11, 11))); + CPPUNIT_ASSERT(!tree1.isValueOn(openvdb::Coord( int(dim), 11, 11))); + + tree1.topologyDifference(tree0); + + CPPUNIT_ASSERT(tree1.root().onTileCount() > 1); + CPPUNIT_ASSERT_EQUAL( dim*dim*dim - 2, tree1.activeVoxelCount() ); + CPPUNIT_ASSERT(!tree1.empty()); + tree1.pruneInactive(); + CPPUNIT_ASSERT_EQUAL( dim*dim*dim - 2, tree1.activeVoxelCount() ); + CPPUNIT_ASSERT(!tree1.empty()); + } + {//active tile + const ValueType background=0.0f; + const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; + openvdb::FloatTree tree0(background), tree1(background); + tree1.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, true); + CPPUNIT_ASSERT_EQUAL(dim*dim*dim, tree1.activeVoxelCount()); + CPPUNIT_ASSERT(tree1.hasActiveTiles()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree1.root().onTileCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); + + tree0.setValue(openvdb::Coord( 500, 301, 200), 4.0f); + tree0.setValue(openvdb::Coord( 400, 30, 20), 5.0f); + tree0.setValue(openvdb::Coord( dim, 11, 11), 6.0f); + CPPUNIT_ASSERT(!tree0.hasActiveTiles()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); + + tree0.topologyDifference(tree1); + + CPPUNIT_ASSERT_EQUAL( openvdb::Index32(1), tree0.leafCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); + CPPUNIT_ASSERT(!tree0.empty()); + tree0.pruneInactive(); + CPPUNIT_ASSERT_EQUAL( openvdb::Index32(1), tree0.leafCount() ); + CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); + CPPUNIT_ASSERT(!tree1.empty()); + } + {// use tree with different voxel type + ValueType background=5.0f; + openvdb::FloatTree tree0(background), tree1(background), tree2(background); + CPPUNIT_ASSERT(tree2.empty()); + // tree0 = tree1.topologyIntersection(tree2) + tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); + tree0.setValue(openvdb::Coord(-5, 10,-20),0.1f); + tree0.setValue(openvdb::Coord( 5,-10,-20),0.2f); + tree0.setValue(openvdb::Coord(-5,-10,-20),0.3f); + + tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); + tree1.setValue(openvdb::Coord(-5, 10,-20),0.1f); + tree1.setValue(openvdb::Coord( 5,-10,-20),0.2f); + tree1.setValue(openvdb::Coord(-5,-10,-20),0.3f); + + tree2.setValue(openvdb::Coord( 5, 10, 20),0.4f); + tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); + tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); + tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); + + tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); + tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); + tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); + + openvdb::FloatTree tree1_copy(tree1); + + // tree3 has the same topology as tree2 but a different value type + const openvdb::Vec3f background2(1.0f,3.4f,6.0f), vec_val(3.1f,5.3f,-9.5f); + openvdb::Vec3fTree tree3(background2); + for (openvdb::FloatTree::ValueOnCIter iter = tree2.cbeginValueOn(); iter; ++iter) { + tree3.setValue(iter.getCoord(), vec_val); + } + + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree0.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree1.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree2.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree3.leafCount()); + + + //tree1.topologyInterection(tree2);//should make tree1 = tree0 + tree1.topologyIntersection(tree3);//should make tree1 = tree0 + + CPPUNIT_ASSERT(tree0.leafCount()==tree1.leafCount()); + CPPUNIT_ASSERT(tree0.nonLeafCount()==tree1.nonLeafCount()); + CPPUNIT_ASSERT(tree0.activeLeafVoxelCount()==tree1.activeLeafVoxelCount()); + CPPUNIT_ASSERT(tree0.inactiveLeafVoxelCount()==tree1.inactiveLeafVoxelCount()); + CPPUNIT_ASSERT(tree0.activeVoxelCount()==tree1.activeVoxelCount()); + CPPUNIT_ASSERT(tree0.inactiveVoxelCount()==tree1.inactiveVoxelCount()); + CPPUNIT_ASSERT(tree1.hasSameTopology(tree0)); + CPPUNIT_ASSERT(tree0.hasSameTopology(tree1)); + + for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { + const openvdb::Coord p = iter.getCoord(); + CPPUNIT_ASSERT(tree1.isValueOn(p)); + CPPUNIT_ASSERT(tree2.isValueOn(p)); + CPPUNIT_ASSERT(tree3.isValueOn(p)); + CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(p)); + } + for (openvdb::FloatTree::ValueOnCIter iter = tree1_copy.cbeginValueOn(); iter; ++iter) { + CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(iter.getCoord())); + } + for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { + const openvdb::Coord p = iter.getCoord(); + CPPUNIT_ASSERT(tree0.isValueOn(p)); + CPPUNIT_ASSERT(tree2.isValueOn(p)); + CPPUNIT_ASSERT(tree3.isValueOn(p)); + CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree0.getValue(p)); + } + } + {// test overlapping spheres + const float background=5.0f, R0=10.0f, R1=5.6f; + const openvdb::Vec3f C0(35.0f, 30.0f, 40.0f), C1(22.3f, 30.5f, 31.0f); + const openvdb::Coord dim(32, 32, 32); + openvdb::FloatGrid grid0(background); + openvdb::FloatGrid grid1(background); + unittest_util::makeSphere(dim, C0, R0, grid0, + 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); + unittest_util::makeSphere(dim, C1, R1, grid1, + 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); + openvdb::FloatTree& tree0 = grid0.tree(); + openvdb::FloatTree& tree1 = grid1.tree(); + openvdb::FloatTree tree0_copy(tree0); + + tree0.topologyDifference(tree1); + + const openvdb::Index64 n0 = tree0_copy.activeVoxelCount(); + const openvdb::Index64 n = tree0.activeVoxelCount(); + + CPPUNIT_ASSERT( n < n0 ); + + for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { + const openvdb::Coord p = iter.getCoord(); + CPPUNIT_ASSERT(tree1.isValueOff(p)); + CPPUNIT_ASSERT(tree0_copy.isValueOn(p)); + ASSERT_DOUBLES_EXACTLY_EQUAL(*iter, tree0_copy.getValue(p)); + } + } +}// testTopologyDifference + +void +TestTree::testSignedFloodFill() +{ + // Use a custom tree configuration to ensure we flood-fill at all levels! + typedef openvdb::tree::LeafNode LeafT;//4^3 + typedef openvdb::tree::InternalNode InternalT;//4^3 + typedef openvdb::tree::RootNode RootT;// child nodes are 16^3 + typedef openvdb::tree::Tree TreeT; + + const float outside = 2.0f, inside = -outside, radius = 20.0f; + openvdb::Grid::Ptr grid = openvdb::Grid::create(outside); + TreeT& tree = grid->tree(); + const RootT& root = tree.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(C[0], C[1], C[2]); + openvdb::Coord xyz; + for (xyz[0]=0; xyz[0]transform().indexToWorld(xyz); + const float dist = (p-center).length() - radius; + if (fabs(dist) > outside) continue; + acc.setValue(xyz, dist); + } + } + } + // Check narrow band with incorrect background + const size_t size_before = root.getTableSize(); + CPPUNIT_ASSERT(size_before>0); + CPPUNIT_ASSERT(!tree.isValueOn(C)); + ASSERT_DOUBLES_EXACTLY_EQUAL(outside,tree.getValue(C)); + for (xyz[0]=0; xyz[0]transform().indexToWorld(xyz); + const float dist = (p-center).length() - radius; + const float val = acc.getValue(xyz); + if (dist < inside) { + ASSERT_DOUBLES_EXACTLY_EQUAL( val, outside); + } else if (dist>outside) { + ASSERT_DOUBLES_EXACTLY_EQUAL( val, outside); + } else { + ASSERT_DOUBLES_EXACTLY_EQUAL( val, dist ); + } + } + } + } + + CPPUNIT_ASSERT(tree.getValueDepth(C) == -1);//i.e. background value + tree.signedFloodFill(); + CPPUNIT_ASSERT(tree.getValueDepth(C) == 0);//added inside tile to root + + // Check narrow band with correct background + for (xyz[0]=0; xyz[0]transform().indexToWorld(xyz); + const float dist = (p-center).length() - radius; + const float val = acc.getValue(xyz); + if (dist < inside) { + ASSERT_DOUBLES_EXACTLY_EQUAL( val, inside); + } else if (dist>outside) { + ASSERT_DOUBLES_EXACTLY_EQUAL( val, outside); + } else { + ASSERT_DOUBLES_EXACTLY_EQUAL( val, dist ); + } + } + } + } + + CPPUNIT_ASSERT(root.getTableSize()>size_before);//added inside root tiles + CPPUNIT_ASSERT(!tree.isValueOn(C)); + ASSERT_DOUBLES_EXACTLY_EQUAL(inside,tree.getValue(C)); +} + + +void +TestTree::testPruneInactive() +{ + using openvdb::Coord; + using openvdb::Index32; + using openvdb::Index64; + + float background = 5.0; + openvdb::FloatTree tree(background); + + // Verify that the newly-constructed tree is empty and that pruning it has no effect. + CPPUNIT_ASSERT(tree.empty()); + tree.prune(); + CPPUNIT_ASSERT(tree.empty()); + tree.pruneInactive(background); + CPPUNIT_ASSERT(tree.empty()); + + // Set some active values. + tree.setValue(Coord(-5, 10, 20), 0.1); + tree.setValue(Coord(-5,-10, 20), 0.4); + tree.setValue(Coord(-5, 10,-20), 0.5); + tree.setValue(Coord(-5,-10,-20), 0.7); + tree.setValue(Coord( 5, 10, 20), 0.0); + tree.setValue(Coord( 5,-10, 20), 0.2); + tree.setValue(Coord( 5,-10,-20), 0.6); + tree.setValue(Coord( 5, 10,-20), 0.3); + // Verify that the tree has the expected numbers of active voxels and leaf nodes. + CPPUNIT_ASSERT_EQUAL(Index64(8), tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); + + // Verify that prune() has no effect, since the values are all different. + tree.prune(); + CPPUNIT_ASSERT_EQUAL(Index64(8), tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); + // Verify that pruneInactive() has no effect, since the values are active. + tree.pruneInactive(background); + CPPUNIT_ASSERT_EQUAL(Index64(8), tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); + + // Make some of the active values inactive, without changing their values. + tree.setValueOff(Coord(-5, 10, 20)); + tree.setValueOff(Coord(-5,-10, 20)); + tree.setValueOff(Coord(-5, 10,-20)); + tree.setValueOff(Coord(-5,-10,-20)); + CPPUNIT_ASSERT_EQUAL(Index64(4), tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); + // Verify that prune() has no effect, since the values are still different. + tree.prune(); + CPPUNIT_ASSERT_EQUAL(Index64(4), tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); + // Verify that pruneInactive() prunes the nodes containing only inactive voxels. + tree.pruneInactive(background); + CPPUNIT_ASSERT_EQUAL(Index64(4), tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Index32(4), tree.leafCount()); + + // Make all of the active values inactive, without changing their values. + tree.setValueOff(Coord( 5, 10, 20)); + tree.setValueOff(Coord( 5,-10, 20)); + tree.setValueOff(Coord( 5,-10,-20)); + tree.setValueOff(Coord( 5, 10,-20)); + CPPUNIT_ASSERT_EQUAL(Index64(0), tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Index32(4), tree.leafCount()); + // Verify that prune() has no effect, since the values are still different. + tree.prune(); + CPPUNIT_ASSERT_EQUAL(Index64(0), tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Index32(4), tree.leafCount()); + // Verify that pruneInactive() prunes all of the remaining leaf nodes. + tree.pruneInactive(background); + CPPUNIT_ASSERT(tree.empty()); +} + + +void +TestTree::testPruneLevelSet() +{ + const float background=10.0f, R=5.6f; + const openvdb::Vec3f C(12.3f, 15.5f, 10.0f); + const openvdb::Coord dim(32, 32, 32); + openvdb::FloatGrid grid(background); + unittest_util::makeSphere(dim, C, R, grid, + 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); + openvdb::FloatTree& tree = grid.tree(); + + openvdb::Index64 count = 0; + openvdb::Coord xyz; + for (xyz[0]=0; xyz[0]beginValueOn(); vIter; ++vIter) { + if (fabs(*vIter)setValueOff(vIter.pos(), *vIter > 0.0f ? background : -background); + ++removed; + } + } + // The following version is slower since it employs + // FloatTree::ValueOnIter that visits both tiles and voxels and + // also uses random acceess to set the voxels off. + /* + for (openvdb::FloatTree::ValueOnIter i = tree.beginValueOn(); i; ++i) { + if (fabs(*i) 0.0f ? background : -background); + ++removed2; + } + */ + + CPPUNIT_ASSERT_EQUAL(leafCount, tree.leafCount()); + //std::cerr << "Leaf count=" << tree.leafCount() << std::endl; + CPPUNIT_ASSERT_EQUAL(tree.activeVoxelCount(), count-removed); + CPPUNIT_ASSERT_EQUAL(tree.activeLeafVoxelCount(), count-removed); + + tree.pruneLevelSet(); + + CPPUNIT_ASSERT(tree.leafCount() < leafCount); + //std::cerr << "Leaf count=" << tree.leafCount() << std::endl; + CPPUNIT_ASSERT_EQUAL(tree.activeVoxelCount(), count-removed); + CPPUNIT_ASSERT_EQUAL(tree.activeLeafVoxelCount(), count-removed); + + openvdb::FloatTree::ValueOnCIter i = tree.cbeginValueOn(); + for (; i; ++i) CPPUNIT_ASSERT( *i < new_width); + + for (xyz[0]=0; xyz[0]getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); + CPPUNIT_ASSERT(tree->touchLeaf(xyz)!=NULL); + CPPUNIT_ASSERT_EQUAL( 3, tree->getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); + CPPUNIT_ASSERT(!tree->isValueOn(xyz)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree->getValue(xyz)); + } + {// test accessor + openvdb::FloatTree::Ptr tree(new openvdb::FloatTree(background)); + openvdb::tree::ValueAccessor acc(*tree); + CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); + CPPUNIT_ASSERT(acc.touchLeaf(xyz)!=NULL); + CPPUNIT_ASSERT_EQUAL( 3, tree->getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); + CPPUNIT_ASSERT(!acc.isValueOn(xyz)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, acc.getValue(xyz)); + } +} + + +void +TestTree::testProbeLeaf() +{ + const float background=10.0f, value = 2.0f; + const openvdb::Coord xyz(-20,30,10); + {// test Tree::probeLeaf + openvdb::FloatTree::Ptr tree(new openvdb::FloatTree(background)); + CPPUNIT_ASSERT_EQUAL(-1, tree->getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); + CPPUNIT_ASSERT(tree->probeLeaf(xyz)==NULL); + CPPUNIT_ASSERT_EQUAL(-1, tree->getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); + tree->setValue(xyz, value); + CPPUNIT_ASSERT_EQUAL( 3, tree->getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); + CPPUNIT_ASSERT(tree->probeLeaf(xyz)!=NULL); + CPPUNIT_ASSERT_EQUAL( 3, tree->getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); + CPPUNIT_ASSERT(tree->isValueOn(xyz)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree->getValue(xyz)); + } + {// test Tree::probeConstLeaf + const openvdb::FloatTree tree1(background); + CPPUNIT_ASSERT_EQUAL(-1, tree1.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 0, int(tree1.leafCount())); + CPPUNIT_ASSERT(tree1.probeConstLeaf(xyz)==NULL); + CPPUNIT_ASSERT_EQUAL(-1, tree1.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 0, int(tree1.leafCount())); + openvdb::FloatTree tmp(tree1); + tmp.setValue(xyz, value); + const openvdb::FloatTree tree2(tmp); + CPPUNIT_ASSERT_EQUAL( 3, tree2.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 1, int(tree2.leafCount())); + CPPUNIT_ASSERT(tree2.probeConstLeaf(xyz)!=NULL); + CPPUNIT_ASSERT_EQUAL( 3, tree2.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 1, int(tree2.leafCount())); + CPPUNIT_ASSERT(tree2.isValueOn(xyz)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree2.getValue(xyz)); + } + {// test ValueAccessor::probeLeaf + openvdb::FloatTree::Ptr tree(new openvdb::FloatTree(background)); + openvdb::tree::ValueAccessor acc(*tree); + CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); + CPPUNIT_ASSERT(acc.probeLeaf(xyz)==NULL); + CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); + acc.setValue(xyz, value); + CPPUNIT_ASSERT_EQUAL( 3, acc.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); + CPPUNIT_ASSERT(acc.probeLeaf(xyz)!=NULL); + CPPUNIT_ASSERT_EQUAL( 3, acc.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); + CPPUNIT_ASSERT(acc.isValueOn(xyz)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(xyz)); + } + {// test ValueAccessor::probeConstLeaf + const openvdb::FloatTree tree1(background); + openvdb::tree::ValueAccessor acc1(tree1); + CPPUNIT_ASSERT_EQUAL(-1, acc1.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 0, int(tree1.leafCount())); + CPPUNIT_ASSERT(acc1.probeConstLeaf(xyz)==NULL); + CPPUNIT_ASSERT_EQUAL(-1, acc1.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 0, int(tree1.leafCount())); + openvdb::FloatTree tmp(tree1); + tmp.setValue(xyz, value); + const openvdb::FloatTree tree2(tmp); + openvdb::tree::ValueAccessor acc2(tree2); + CPPUNIT_ASSERT_EQUAL( 3, acc2.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 1, int(tree2.leafCount())); + CPPUNIT_ASSERT(acc2.probeConstLeaf(xyz)!=NULL); + CPPUNIT_ASSERT_EQUAL( 3, acc2.getValueDepth(xyz)); + CPPUNIT_ASSERT_EQUAL( 1, int(tree2.leafCount())); + CPPUNIT_ASSERT(acc2.isValueOn(xyz)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc2.getValue(xyz)); + } +} + + +void +TestTree::testAddLeaf() +{ + using namespace openvdb; + + typedef FloatTree::LeafNodeType LeafT; + + const Coord ijk(100); + FloatGrid grid; + FloatTree& tree = grid.tree(); + + tree.setValue(ijk, 5.0); + const LeafT* oldLeaf = tree.probeLeaf(ijk); + CPPUNIT_ASSERT(oldLeaf != NULL); + ASSERT_DOUBLES_EXACTLY_EQUAL(5.0, oldLeaf->getValue(ijk)); + + LeafT* newLeaf = new LeafT; + newLeaf->setOrigin(oldLeaf->origin()); + newLeaf->fill(3.0); + + tree.addLeaf(*newLeaf); + CPPUNIT_ASSERT_EQUAL(newLeaf, tree.probeLeaf(ijk)); + ASSERT_DOUBLES_EXACTLY_EQUAL(3.0, tree.getValue(ijk)); +} + + +void +TestTree::testAddTile() +{ + using namespace openvdb; + + const Coord ijk(100); + FloatGrid grid; + FloatTree& tree = grid.tree(); + + tree.setValue(ijk, 5.0); + CPPUNIT_ASSERT(tree.probeLeaf(ijk) != NULL); + + const Index lvl = FloatTree::DEPTH >> 1; + if (lvl > 0) tree.addTile(lvl,ijk, 3.0, /*active=*/true); + else tree.addTile(1,ijk, 3.0, /*active=*/true); + + CPPUNIT_ASSERT(tree.probeLeaf(ijk) == NULL); + ASSERT_DOUBLES_EXACTLY_EQUAL(3.0, tree.getValue(ijk)); +} + + +struct BBoxOp +{ + std::vector bbox; + std::vector level; + + // This method is required by Tree::visitActiveBBox + // Since it will return false if LEVEL==0 it will never descent to + // the active voxels. In other words the smallest BBoxes + // correspond to LeafNodes or active tiles at LEVEL=1 + template + inline bool descent() { return LEVEL>0; } + + // This method is required by Tree::visitActiveBBox + template + inline void operator()(const openvdb::CoordBBox &_bbox) { + bbox.push_back(_bbox); + level.push_back(LEVEL); + } +}; + +void +TestTree::testProcessBBox() +{ + using openvdb::Coord; + using openvdb::CoordBBox; + //check two leaf nodes and two tiles at each level 1, 2 and 3 + const int size[4]={1<<3, 1<<3, 1<<(3+4), 1<<(3+4+5)}; + for (int level=0; level<=3; ++level) { + openvdb::FloatTree tree; + const int n = size[level]; + const CoordBBox bbox[]={CoordBBox::createCube(Coord(-n,-n,-n), n), + CoordBBox::createCube(Coord( 0, 0, 0), n)}; + if (level==0) { + tree.setValue(Coord(-1,-2,-3), 1.0f); + tree.setValue(Coord( 1, 2, 3), 1.0f); + } else { + tree.fill(bbox[0], 1.0f, true); + tree.fill(bbox[1], 1.0f, true); + } + BBoxOp op; + tree.visitActiveBBox(op); + CPPUNIT_ASSERT_EQUAL(2, int(op.bbox.size())); + + for (int i=0; i<2; ++i) { + //std::cerr <<"\nLevel="< +#include +#include +#include +#include "util.h" // for unittest_util::makeSphere() + +#define TEST_CSG_VERBOSE 0 + +#if TEST_CSG_VERBOSE +#include // Timer +#endif + +namespace { +typedef openvdb::tree::Tree4::Type Float433Tree; +typedef openvdb::Grid Float433Grid; +} + + +class TestTreeCombine: public CppUnit::TestFixture +{ +public: + virtual void setUp() { openvdb::initialize(); Float433Grid::registerGrid(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestTreeCombine); + CPPUNIT_TEST(testCombine); + CPPUNIT_TEST(testCombine2); + CPPUNIT_TEST(testCompMax); + CPPUNIT_TEST(testCompMin); + CPPUNIT_TEST(testCompSum); + CPPUNIT_TEST(testCompProd); + CPPUNIT_TEST(testCompDiv); + CPPUNIT_TEST(testCompReplace); + CPPUNIT_TEST(testBoolTree); +#ifdef DWA_OPENVDB + CPPUNIT_TEST(testCsg); +#endif + CPPUNIT_TEST_SUITE_END(); + + void testCombine(); + void testCombine2(); + void testCompMax(); + void testCompMin(); + void testCompSum(); + void testCompProd(); + void testCompDiv(); + void testCompReplace(); + void testBoolTree(); + void testCsg(); + +private: + template + void testComp(const TreeComp&, const ValueComp&); + + template + void testCompRepl(); + + template + typename TreeT::Ptr + visitCsg(const TreeT& a, const TreeT& b, const TreeT& ref, const VisitorT&); + +#if TEST_CSG_VERBOSE + struct Timer { + tbb::tick_count t; + Timer(): t(tbb::tick_count::now()) {} + void start() { t = tbb::tick_count::now(); } + double stop() { return (tbb::tick_count::now() - t).seconds(); }; + }; +#endif +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION(TestTreeCombine); + + +//////////////////////////////////////// + + +namespace { +namespace Local { + +template +struct OrderDependentCombineOp { + OrderDependentCombineOp() {} + void operator()(const ValueT& a, const ValueT& b, ValueT& result) const { + result = a + 100 * b; // result is order-dependent on A and B + } +}; + +/// Test Tree::combine(), which takes a functor that accepts three arguments +/// (the a, b and result values). +template +void combine(TreeT& a, TreeT& b) +{ + a.combine(b, OrderDependentCombineOp()); +} + +/// Test Tree::combineExtended(), which takes a functor that accepts a single +/// CombineArgs argument, in which the functor can return a computed active state +/// for the output value. +template +void extendedCombine(TreeT& a, TreeT& b) +{ + typedef typename TreeT::ValueType ValueT; + struct ArgsOp { + static void order(openvdb::CombineArgs& args) { + // The result is order-dependent on A and B. + args.setResult(args.a() + 100 * args.b()); + args.setResultIsActive(args.aIsActive() || args.bIsActive()); + } + }; + a.combineExtended(b, ArgsOp::order); +} + +template void compMax(TreeT& a, TreeT& b) { openvdb::tools::compMax(a, b); } +template void compMin(TreeT& a, TreeT& b) { openvdb::tools::compMin(a, b); } +template void compSum(TreeT& a, TreeT& b) { openvdb::tools::compSum(a, b); } +template void compMul(TreeT& a, TreeT& b) { openvdb::tools::compMul(a, b); }\ +template void compDiv(TreeT& a, TreeT& b) { openvdb::tools::compDiv(a, b); }\ + +float orderf(float a, float b) { return a + 100 * b; } +float maxf(float a, float b) { return std::max(a, b); } +float minf(float a, float b) { return std::min(a, b); } +float sumf(float a, float b) { return a + b; } +float mulf(float a, float b) { return a * b; } +float divf(float a, float b) { return a / b; } + +openvdb::Vec3f orderv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a + 100 * b; } +openvdb::Vec3f maxv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { + const float aMag = a.lengthSqr(), bMag = b.lengthSqr(); + return (aMag > bMag ? a : (bMag > aMag ? b : std::max(a, b))); +} +openvdb::Vec3f minv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { + const float aMag = a.lengthSqr(), bMag = b.lengthSqr(); + return (aMag < bMag ? a : (bMag < aMag ? b : std::min(a, b))); +} +openvdb::Vec3f sumv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a + b; } +openvdb::Vec3f mulv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a * b; } +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::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.5 * (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 + Timer timer; + timer.start(); +#endif + + const std::string testDir("/work/rd/fx_tools/vdb_unittest/TestGridCombine::testCsg/"); + smallTree1 = Local::readFile(testDir + "small1.vdb2 LevelSet"); + CPPUNIT_ASSERT(smallTree1.get() != NULL); + smallTree2 = Local::readFile(testDir + "small2.vdb2 Cylinder"); + CPPUNIT_ASSERT(smallTree2.get() != NULL); + largeTree1 = Local::readFile(testDir + "large1.vdb2 LevelSet"); + CPPUNIT_ASSERT(largeTree1.get() != NULL); + largeTree2 = Local::readFile(testDir + "large2.vdb2 LevelSet"); + CPPUNIT_ASSERT(largeTree2.get() != NULL); + +#if TEST_CSG_VERBOSE + std::cerr << "file read: " << timer.stop() << " sec\n"; +#endif + +#if TEST_CSG_VERBOSE + std::cerr << "\n\n"; +#endif + refTree = Local::readFile(testDir + "small_union.vdb2"); + outTree = visitCsg(*smallTree1, *smallTree2, *refTree, Local::visitorUnion); + //Local::writeFile(outTree, "/tmp/small_union_out.vdb2"); + refTree = Local::readFile(testDir + "large_union.vdb2"); + outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorUnion); + //Local::writeFile(outTree, "/tmp/large_union_out.vdb2"); + +#if TEST_CSG_VERBOSE + std::cerr << "\n\n"; +#endif + refTree = Local::readFile(testDir + "small_intersection.vdb2"); + outTree = visitCsg(*smallTree1, *smallTree2, *refTree, Local::visitorIntersect); + //Local::writeFile(outTree, "/tmp/small_intersection_out.vdb2"); + refTree = Local::readFile(testDir + "large_intersection.vdb2"); + outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorIntersect); + //Local::writeFile(outTree, "/tmp/large_intersection_out.vdb2"); + +#if TEST_CSG_VERBOSE + std::cerr << "\n\n"; +#endif + refTree = Local::readFile(testDir + "small_difference.vdb2"); + outTree = visitCsg(*smallTree1, *smallTree2, *refTree, Local::visitorDiff); + //Local::writeFile(outTree, "/tmp/small_difference_out.vdb2"); + refTree = Local::readFile(testDir + "large_difference.vdb2"); + outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorDiff); + //Local::writeFile(outTree, "/tmp/large_difference_out.vdb2"); +} + + +template +typename TreeT::Ptr +TestTreeCombine::visitCsg(const TreeT& aInputTree, const TreeT& bInputTree, + const TreeT& refTree, const VisitorT& visitor) +{ + typedef typename TreeT::Ptr TreePtr; + +#if TEST_CSG_VERBOSE + Timer timer; + + timer.start(); +#endif + TreePtr aTree(new TreeT(aInputTree)); + TreeT bTree(bInputTree); +#if TEST_CSG_VERBOSE + std::cerr << "deep copy: " << timer.stop() << " sec\n"; +#endif + +#if (TEST_CSG_VERBOSE > 1) + std::cerr << "\nA grid:\n"; + aTree->print(std::cerr, /*verbose=*/3); + std::cerr << "\nB grid:\n"; + bTree.print(std::cerr, /*verbose=*/3); + std::cerr << "\nExpected:\n"; + refTree.print(std::cerr, /*verbose=*/3); + std::cerr << "\n"; +#endif + + // Compute the CSG combination of the two grids. +#if TEST_CSG_VERBOSE + timer.start(); +#endif + visitor(*aTree, bTree); +#if TEST_CSG_VERBOSE + std::cerr << "combine: " << timer.stop() << " sec\n"; +#endif +#if (TEST_CSG_VERBOSE > 1) + std::cerr << "\nActual:\n"; + aTree->print(std::cerr, /*verbose=*/3); +#endif + + std::ostringstream aInfo, refInfo; + aTree->print(aInfo, /*verbose=*/3); + refTree.print(refInfo, /*verbose=*/3); + + CPPUNIT_ASSERT_EQUAL(refInfo.str(), aInfo.str()); + + CPPUNIT_ASSERT(aTree->hasSameTopology(refTree)); + + return aTree; +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestTreeGetSetValues.cc b/openvdb_2_3_0_library/openvdb/unittest/TestTreeGetSetValues.cc new file mode 100755 index 0000000..4093745 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestTreeGetSetValues.cc @@ -0,0 +1,465 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include // for tools::setValueOnMin() et al. + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + + +class TestTreeGetSetValues: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestTreeGetSetValues); + CPPUNIT_TEST(testGetValues); + CPPUNIT_TEST(testSetValues); + CPPUNIT_TEST(testUnsetValues); + CPPUNIT_TEST(testFill); + CPPUNIT_TEST(testSetActiveStates); + CPPUNIT_TEST(testHasActiveTiles); + CPPUNIT_TEST_SUITE_END(); + + void testGetBackground(); + void testGetValues(); + void testSetValues(); + void testUnsetValues(); + void testFill(); + void testSetActiveStates(); + void testHasActiveTiles(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestTreeGetSetValues); + +namespace { +typedef openvdb::tree::Tree4::Type Tree323f; // 8^3 x 4^3 x 8^3 +} + + +void +TestTreeGetSetValues::testGetBackground() +{ + const float background = 256.0f; + Tree323f tree(background); + + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.background()); +} + + +void +TestTreeGetSetValues::testGetValues() +{ + Tree323f tree(/*background=*/256.0f); + + tree.setValue(openvdb::Coord(0, 0, 0), 1.0); + tree.setValue(openvdb::Coord(1, 0, 0), 1.5); + tree.setValue(openvdb::Coord(0, 0, 8), 2.0); + tree.setValue(openvdb::Coord(1, 0, 8), 2.5); + tree.setValue(openvdb::Coord(0, 0, 16), 3.0); + tree.setValue(openvdb::Coord(1, 0, 16), 3.5); + tree.setValue(openvdb::Coord(0, 0, 24), 4.0); + tree.setValue(openvdb::Coord(1, 0, 24), 4.5); + + ASSERT_DOUBLES_EXACTLY_EQUAL(1.0, tree.getValue(openvdb::Coord(0, 0, 0))); + ASSERT_DOUBLES_EXACTLY_EQUAL(1.5, tree.getValue(openvdb::Coord(1, 0, 0))); + ASSERT_DOUBLES_EXACTLY_EQUAL(2.0, tree.getValue(openvdb::Coord(0, 0, 8))); + ASSERT_DOUBLES_EXACTLY_EQUAL(2.5, tree.getValue(openvdb::Coord(1, 0, 8))); + ASSERT_DOUBLES_EXACTLY_EQUAL(3.0, tree.getValue(openvdb::Coord(0, 0, 16))); + ASSERT_DOUBLES_EXACTLY_EQUAL(3.5, tree.getValue(openvdb::Coord(1, 0, 16))); + ASSERT_DOUBLES_EXACTLY_EQUAL(4.0, tree.getValue(openvdb::Coord(0, 0, 24))); + ASSERT_DOUBLES_EXACTLY_EQUAL(4.5, tree.getValue(openvdb::Coord(1, 0, 24))); +} + + +void +TestTreeGetSetValues::testSetValues() +{ + using namespace openvdb; + + const float background = 256.0; + Tree323f tree(background); + + for (int activeTile = 0; activeTile < 2; ++activeTile) { + if (activeTile) tree.fill(CoordBBox(Coord(0), Coord(31)), background, /*active=*/true); + + tree.setValue(openvdb::Coord(0, 0, 0), 1.0); + tree.setValue(openvdb::Coord(1, 0, 0), 1.5); + tree.setValue(openvdb::Coord(0, 0, 8), 2.0); + tree.setValue(openvdb::Coord(1, 0, 8), 2.5); + tree.setValue(openvdb::Coord(0, 0, 16), 3.0); + tree.setValue(openvdb::Coord(1, 0, 16), 3.5); + tree.setValue(openvdb::Coord(0, 0, 24), 4.0); + tree.setValue(openvdb::Coord(1, 0, 24), 4.5); + + const int expectedActiveCount = (!activeTile ? 8 : 32 * 32 * 32); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); + + float val = 1.0; + for (Tree323f::LeafCIter iter = tree.cbeginLeaf(); iter; ++iter) { + ASSERT_DOUBLES_EXACTLY_EQUAL(val, iter->getValue(openvdb::Coord(0, 0, 0))); + ASSERT_DOUBLES_EXACTLY_EQUAL(val+0.5, iter->getValue(openvdb::Coord(1, 0, 0))); + val = val + 1.0; + } + } +} + + +void +TestTreeGetSetValues::testUnsetValues() +{ + using namespace openvdb; + + const float background = 256.0; + Tree323f tree(background); + + for (int activeTile = 0; activeTile < 2; ++activeTile) { + if (activeTile) tree.fill(CoordBBox(Coord(0), Coord(31)), background, /*active=*/true); + + Coord setCoords[8] = { + Coord(0, 0, 0), + Coord(1, 0, 0), + Coord(0, 0, 8), + Coord(1, 0, 8), + Coord(0, 0, 16), + Coord(1, 0, 16), + Coord(0, 0, 24), + Coord(1, 0, 24) + }; + + for (int i = 0; i < 8; ++i) { + tree.setValue(setCoords[i], 1.0); + } + const int expectedActiveCount = (!activeTile ? 8 : 32 * 32 * 32); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); + + // Unset some voxels. + for (int i = 0; i < 8; i += 2) { + tree.setValueOff(setCoords[i]); + } + CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 4, int(tree.activeVoxelCount())); + + // Unset some voxels, but change their values. + for (int i = 0; i < 8; i += 2) { + tree.setValueOff(setCoords[i], background); + } + CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 4, int(tree.activeVoxelCount())); + for (int i = 0; i < 8; i += 2) { + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(setCoords[i])); + } + } +} + + +void +TestTreeGetSetValues::testFill() +{ + using openvdb::CoordBBox; + using openvdb::Coord; + + const float background = 256.0; + Tree323f tree(background); + + // Fill from (-2,-2,-2) to (2,2,2) with active value 2. + tree.fill(CoordBBox(Coord(-2), Coord(2)), 2.0); + Coord xyz, xyzMin = Coord::max(), xyzMax = Coord::min(); + for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { + xyz = iter.getCoord(); + xyzMin = std::min(xyzMin, xyz); + xyzMax = std::max(xyz, xyzMax); + ASSERT_DOUBLES_EXACTLY_EQUAL(2.0, *iter); + } + CPPUNIT_ASSERT_EQUAL(openvdb::Index64(5*5*5), tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); + CPPUNIT_ASSERT_EQUAL(Coord( 2), xyzMax); + + // Fill from (1,1,1) to (3,3,3) with active value 3. + tree.fill(CoordBBox(Coord(1), Coord(3)), 3.0); + xyzMin = Coord::max(); xyzMax = Coord::min(); + for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { + xyz = iter.getCoord(); + xyzMin = std::min(xyzMin, xyz); + xyzMax = std::max(xyz, xyzMax); + const float expectedValue = (xyz[0] >= 1 && xyz[1] >= 1 && xyz[2] >= 1 + && xyz[0] <= 3 && xyz[1] <= 3 && xyz[2] <= 3) ? 3.0 : 2.0; + ASSERT_DOUBLES_EXACTLY_EQUAL(expectedValue, *iter); + } + openvdb::Index64 expectedCount = + 5*5*5 // (-2,-2,-2) to (2,2,2) + + 3*3*3 // (1,1,1) to (3,3,3) + - 2*2*2; // (1,1,1) to (2,2,2) overlap + CPPUNIT_ASSERT_EQUAL(expectedCount, tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); + CPPUNIT_ASSERT_EQUAL(Coord( 3), xyzMax); + + // Fill from (10,10,10) to (20,20,20) with active value 10. + tree.fill(CoordBBox(Coord(10), Coord(20)), 10.0); + xyzMin = Coord::max(); xyzMax = Coord::min(); + for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { + xyz = iter.getCoord(); + xyzMin = std::min(xyzMin, xyz); + xyzMax = std::max(xyz, xyzMax); + float expectedValue = 2.0; + if (xyz[0] >= 1 && xyz[1] >= 1 && xyz[2] >= 1 + && xyz[0] <= 3 && xyz[1] <= 3 && xyz[2] <= 3) + { + expectedValue = 3.0; + } else if (xyz[0] >= 10 && xyz[1] >= 10 && xyz[2] >= 10 + && xyz[0] <= 20 && xyz[1] <= 20 && xyz[2] <= 20) + { + expectedValue = 10.0; + } + ASSERT_DOUBLES_EXACTLY_EQUAL(expectedValue, *iter); + } + expectedCount = + 5*5*5 // (-2,-2,-2) to (2,2,2) + + 3*3*3 // (1,1,1) to (3,3,3) + - 2*2*2 // (1,1,1) to (2,2,2) overlap + + 11*11*11; // (10,10,10) to (20,20,20) + CPPUNIT_ASSERT_EQUAL(expectedCount, tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); + CPPUNIT_ASSERT_EQUAL(Coord(20), xyzMax); + + // "Undo" previous fill from (10,10,10) to (20,20,20). + tree.fill(CoordBBox(Coord(10), Coord(20)), background, /*active=*/false); + xyzMin = Coord::max(); xyzMax = Coord::min(); + for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { + xyz = iter.getCoord(); + xyzMin = std::min(xyzMin, xyz); + xyzMax = std::max(xyz, xyzMax); + const float expectedValue = (xyz[0] >= 1 && xyz[1] >= 1 && xyz[2] >= 1 + && xyz[0] <= 3 && xyz[1] <= 3 && xyz[2] <= 3) ? 3.0 : 2.0; + ASSERT_DOUBLES_EXACTLY_EQUAL(expectedValue, *iter); + } + expectedCount = + 5*5*5 // (-2,-2,-2) to (2,2,2) + + 3*3*3 // (1,1,1) to (3,3,3) + - 2*2*2; // (1,1,1) to (2,2,2) overlap + CPPUNIT_ASSERT_EQUAL(expectedCount, tree.activeVoxelCount()); + CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); + CPPUNIT_ASSERT_EQUAL(Coord( 3), xyzMax); + + // The following tests assume a [3,2,3] tree configuration. + + tree.clear(); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node + + // Partially fill a single leaf node. + tree.fill(CoordBBox(Coord(8), Coord(14)), 0.0); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); + + // Completely fill the leaf node, replacing it with a tile. + tree.fill(CoordBBox(Coord(8), Coord(15)), 0.0); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); + + { + const int activeVoxelCount = int(tree.activeVoxelCount()); + + // Fill a single voxel of the tile with a different (active) value. + tree.fill(CoordBBox(Coord(10), Coord(10)), 1.0); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); + CPPUNIT_ASSERT_EQUAL(activeVoxelCount, int(tree.activeVoxelCount())); + // Fill the voxel with an inactive value. + tree.fill(CoordBBox(Coord(10), Coord(10)), 1.0, /*active=*/false); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); + CPPUNIT_ASSERT_EQUAL(activeVoxelCount - 1, int(tree.activeVoxelCount())); + + // Completely fill the leaf node, replacing it with a tile again. + tree.fill(CoordBBox(Coord(8), Coord(15)), 0.0); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); + } + + // Expand by one voxel, creating seven neighboring leaf nodes. + tree.fill(CoordBBox(Coord(8), Coord(16)), 0.0); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); + + // Completely fill the internal node containing the tile, replacing it with + // a tile at the next level of the tree. + tree.fill(CoordBBox(Coord(0), Coord(31)), 0.0); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2), tree.nonLeafCount()); + + // Expand by one voxel, creating a layer of leaf nodes on three faces. + tree.fill(CoordBBox(Coord(0), Coord(32)), 0.0); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(5*5 + 4*5 + 4*4), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2 + 7), tree.nonLeafCount()); // +7 internal nodes + + // Completely fill the second-level internal node, replacing it with a root-level tile. + tree.fill(CoordBBox(Coord(0), Coord(255)), 0.0); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); + + // Repeat, filling with an inactive value. + + tree.clear(); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node + + // Partially fill a single leaf node. + tree.fill(CoordBBox(Coord(8), Coord(14)), 0.0, /*active=*/false); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); + + // Completely fill the leaf node, replacing it with a tile. + tree.fill(CoordBBox(Coord(8), Coord(15)), 0.0, /*active=*/false); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); + + // Expand by one voxel, creating seven neighboring leaf nodes. + tree.fill(CoordBBox(Coord(8), Coord(16)), 0.0, /*active=*/false); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); + + // Completely fill the internal node containing the tile, replacing it with + // a tile at the next level of the tree. + tree.fill(CoordBBox(Coord(0), Coord(31)), 0.0, /*active=*/false); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2), tree.nonLeafCount()); + + // Expand by one voxel, creating a layer of leaf nodes on three faces. + tree.fill(CoordBBox(Coord(0), Coord(32)), 0.0, /*active=*/false); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(5*5 + 4*5 + 4*4), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2 + 7), tree.nonLeafCount()); // +7 internal nodes + + // Completely fill the second-level internal node, replacing it with a root-level tile. + tree.fill(CoordBBox(Coord(0), Coord(255)), 0.0, /*active=*/false); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); + + tree.clear(); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node + + // Partially fill a region with the background value. + tree.fill(CoordBBox(Coord(27), Coord(254)), background, /*active=*/false); + // Confirm that after pruning, the tree is empty. + tree.prune(); + CPPUNIT_ASSERT(tree.empty()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); + CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node +} + + +// Verify that setting voxels inside active tiles works correctly. +// In particular, it should preserve the active states of surrounding voxels. +void +TestTreeGetSetValues::testSetActiveStates() +{ + using namespace openvdb; + + const float background = 256.0; + Tree323f tree(background); + + const Coord xyz(10); + const float val = 42.0; + const int expectedActiveCount = 32 * 32 * 32; + +#define RESET_TREE() \ + tree.fill(CoordBBox(Coord(0), Coord(31)), background, /*active=*/true) // create an active tile + + RESET_TREE(); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); + + tree.setValueOff(xyz); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 1, int(tree.activeVoxelCount())); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); + + RESET_TREE(); + tree.setValueOn(xyz); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); + + RESET_TREE(); + tree.setValueOff(xyz, val); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 1, int(tree.activeVoxelCount())); + ASSERT_DOUBLES_EXACTLY_EQUAL(val, tree.getValue(xyz)); + + RESET_TREE(); + tree.setActiveState(xyz, true); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); + + RESET_TREE(); + tree.setActiveState(xyz, false); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 1, int(tree.activeVoxelCount())); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); + + RESET_TREE(); + tree.setValueOn(xyz, val); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); + ASSERT_DOUBLES_EXACTLY_EQUAL(val, tree.getValue(xyz)); + + RESET_TREE(); + tools::setValueOnMin(tree, xyz, val); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); + ASSERT_DOUBLES_EXACTLY_EQUAL(std::min(val, background), tree.getValue(xyz)); + + RESET_TREE(); + tools::setValueOnMax(tree, xyz, val); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); + ASSERT_DOUBLES_EXACTLY_EQUAL(std::max(val, background), tree.getValue(xyz)); + + RESET_TREE(); + tools::setValueOnSum(tree, xyz, val); + CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); + ASSERT_DOUBLES_EXACTLY_EQUAL(val + background, tree.getValue(xyz)); + +#undef RESET_TREE +} + + +void +TestTreeGetSetValues::testHasActiveTiles() +{ + Tree323f tree(/*background=*/256.0f); + + CPPUNIT_ASSERT(!tree.hasActiveTiles()); + + // Fill from (-2,-2,-2) to (2,2,2) with active value 2. + tree.fill(openvdb::CoordBBox(openvdb::Coord(-2), openvdb::Coord(2)), 2.0f); + CPPUNIT_ASSERT(!tree.hasActiveTiles()); + + // Fill from (-200,-200,-200) to (-4,-4,-4) with active value 3. + tree.fill(openvdb::CoordBBox(openvdb::Coord(-200), openvdb::Coord(-4)), 3.0f); + CPPUNIT_ASSERT(tree.hasActiveTiles()); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestTreeIterators.cc b/openvdb_2_3_0_library/openvdb/unittest/TestTreeIterators.cc new file mode 100755 index 0000000..55cddb9 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestTreeIterators.cc @@ -0,0 +1,587 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0) + + +class TestTreeIterators: public CppUnit::TestCase +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestTreeIterators); + CPPUNIT_TEST(testLeafIterator); + CPPUNIT_TEST(testEmptyLeafIterator); + CPPUNIT_TEST(testOnlyNegative); + CPPUNIT_TEST(testValueAllIterator); + CPPUNIT_TEST(testValueOnIterator); + CPPUNIT_TEST(testValueOffIterator); + CPPUNIT_TEST(testModifyValue); + CPPUNIT_TEST(testDepthBounds); + CPPUNIT_TEST_SUITE_END(); + + void testLeafIterator(); + void testEmptyLeafIterator(); + void testOnlyNegative(); + void testValueAllIterator(); + void testValueOnIterator(); + void testValueOffIterator(); + void testModifyValue(); + void testDepthBounds(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestTreeIterators); + + +typedef openvdb::FloatTree TreeType; + + +void +TestTreeIterators::testLeafIterator() +{ + const float fillValue = 256.0f; + + TreeType tree(fillValue); + + tree.setValue(openvdb::Coord(0, 0, 0), 1.0); + tree.setValue(openvdb::Coord(1, 0, 0), 1.5); + tree.setValue(openvdb::Coord(0, 0, 8), 2.0); + tree.setValue(openvdb::Coord(1, 0, 8), 2.5); + tree.setValue(openvdb::Coord(0, 0, 16), 3.0); + tree.setValue(openvdb::Coord(1, 0, 16), 3.5); + tree.setValue(openvdb::Coord(0, 0, 24), 4.0); + tree.setValue(openvdb::Coord(1, 0, 24), 4.5); + + float val = 1.0; + for (TreeType::LeafCIter iter = tree.cbeginLeaf(); iter; ++iter) { + const TreeType::LeafNodeType* leaf = iter.getLeaf(); + CPPUNIT_ASSERT(leaf != NULL); + ASSERT_DOUBLES_EXACTLY_EQUAL(val, leaf->getValue(openvdb::Coord(0, 0, 0))); + ASSERT_DOUBLES_EXACTLY_EQUAL(val + 0.5, iter->getValue(openvdb::Coord(1, 0, 0))); + ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue, iter->getValue(openvdb::Coord(1, 1, 1))); + val = val + 1.0; + } +} + + +// Test the leaf iterator over a tree without any leaf nodes. +void +TestTreeIterators::testEmptyLeafIterator() +{ + using namespace openvdb; + + TreeType tree(/*fillValue=*/256.0); + + std::vector dims; + tree.getNodeLog2Dims(dims); + CPPUNIT_ASSERT_EQUAL(4, int(dims.size())); + + // Start with an iterator over an empty tree. + TreeType::LeafCIter iter = tree.cbeginLeaf(); + CPPUNIT_ASSERT(!iter); + + // Using sparse fill, add internal nodes but no leaf nodes to the tree. + + // Fill the region subsumed by a level-2 internal node (assuming a four-level tree). + Index log2Sum = dims[1] + dims[2] + dims[3]; + CoordBBox bbox(Coord(0), Coord((1 << log2Sum) - 1)); + tree.fill(bbox, /*value=*/1.0); + iter = tree.cbeginLeaf(); + CPPUNIT_ASSERT(!iter); + + // Fill the region subsumed by a level-1 internal node. + log2Sum = dims[2] + dims[3]; + bbox.reset(Coord(0), Coord((1 << log2Sum) - 1)); + tree.fill(bbox, /*value=*/2.0); + iter = tree.cbeginLeaf(); + CPPUNIT_ASSERT(!iter); +} + + +void +TestTreeIterators::testOnlyNegative() +{ + using openvdb::Index64; + + const float fillValue = 5.0f; + + TreeType tree(fillValue); + + CPPUNIT_ASSERT(tree.empty()); + ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue, tree.getValue(openvdb::Coord(5, -10, 20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue, tree.getValue(openvdb::Coord(-500, 200, 300))); + + tree.setValue(openvdb::Coord(-5, 10, 20), 0.1f); + tree.setValue(openvdb::Coord( 5, -10, 20), 0.2f); + tree.setValue(openvdb::Coord( 5, 10, -20), 0.3f); + tree.setValue(openvdb::Coord(-5, -10, 20), 0.4f); + tree.setValue(openvdb::Coord(-5, 10, -20), 0.5f); + tree.setValue(openvdb::Coord( 5, -10, -20), 0.6f); + tree.setValue(openvdb::Coord(-5, -10, -20), 0.7f); + tree.setValue(openvdb::Coord(-500, 200, -300), 4.5678f); + tree.setValue(openvdb::Coord( 500, -200, -300), 4.5678f); + tree.setValue(openvdb::Coord(-500, -200, 300), 4.5678f); + + ASSERT_DOUBLES_EXACTLY_EQUAL(0.1f, tree.getValue(openvdb::Coord(-5, 10, 20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.2f, tree.getValue(openvdb::Coord( 5, -10, 20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.3f, tree.getValue(openvdb::Coord( 5, 10, -20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.4f, tree.getValue(openvdb::Coord(-5, -10, 20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.5f, tree.getValue(openvdb::Coord(-5, 10, -20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.6f, tree.getValue(openvdb::Coord( 5, -10, -20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.7f, tree.getValue(openvdb::Coord(-5, -10, -20))); + ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord(-500, 200, -300))); + ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord( 500, -200, -300))); + ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord(-500, -200, 300))); + + int count = 0; + for (int i = -25; i < 25; ++i) { + for (int j = -25; j < 25; ++j) { + for (int k = -25; k < 25; ++k) { + if (tree.getValue(openvdb::Coord(i, j, k)) < 1.0f) { + //fprintf(stderr, "(%i, %i, %i) = %f\n", + // i, j, k, tree.getValue(openvdb::Coord(i, j, k))); + ++count; + } + } + } + } + CPPUNIT_ASSERT_EQUAL(7, count); + + openvdb::Coord xyz; + int count2 = 0; + for (TreeType::ValueOnCIter iter = tree.cbeginValueOn();iter; ++iter) { + ++count2; + xyz = iter.getCoord(); + //std::cerr << xyz << " = " << *iter << "\n"; + } + CPPUNIT_ASSERT_EQUAL(10, count2); + CPPUNIT_ASSERT_EQUAL(Index64(10), tree.activeVoxelCount()); +} + + +void +TestTreeIterators::testValueAllIterator() +{ + const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; + + typedef openvdb::tree::Tree4::Type Tree323f; + + typedef Tree323f::RootNodeType RootT; + typedef RootT::ChildNodeType Int1T; + typedef Int1T::ChildNodeType Int2T; + typedef Int2T::ChildNodeType LeafT; + + Tree323f tree(/*fillValue=*/256.0f); + tree.setValue(openvdb::Coord(4), 0.0f); + tree.setValue(openvdb::Coord(-4), -1.0f); + + const size_t expectedNumOff = + 2 * ((1 << (3 * DIM2)) - 1) // 2 8x8x8 InternalNodes - 1 child pointer each + + 2 * ((1 << (3 * DIM1)) - 1) // 2 4x4x4 InternalNodes - 1 child pointer each + + 2 * ((1 << (3 * DIM0)) - 1); // 2 8x8x8 LeafNodes - 1 active value each + + { + Tree323f::ValueAllIter iter = tree.beginValueAll(); + CPPUNIT_ASSERT(iter.test()); + + // Read all tile and voxel values through a non-const value iterator. + size_t numOn = 0, numOff = 0; + for ( ; iter; ++iter) { + CPPUNIT_ASSERT(iter.getLevel() <= 3); + const openvdb::Index iterLevel = iter.getLevel(); + for (openvdb::Index lvl = 0; lvl <= 3; ++lvl) { + RootT* root; Int1T* int1; Int2T* int2; LeafT* leaf; + iter.getNode(root); CPPUNIT_ASSERT(root != NULL); + iter.getNode(int1); CPPUNIT_ASSERT(iterLevel < 3 ? int1 != NULL: int1 == NULL); + iter.getNode(int2); CPPUNIT_ASSERT(iterLevel < 2 ? int2 != NULL: int2 == NULL); + iter.getNode(leaf); CPPUNIT_ASSERT(iterLevel < 1 ? leaf != NULL: leaf == NULL); + } + + if (iter.isValueOn()) { + ++numOn; + const float f = iter.getValue(); + if (openvdb::math::isZero(f)) { + CPPUNIT_ASSERT(iter.getCoord() == openvdb::Coord(4)); + CPPUNIT_ASSERT(iter.isVoxelValue()); + } else { + ASSERT_DOUBLES_EXACTLY_EQUAL(-1.0f, f); + CPPUNIT_ASSERT(iter.getCoord() == openvdb::Coord(-4)); + CPPUNIT_ASSERT(iter.isVoxelValue()); + } + } else { + ++numOff; + + // For every tenth inactive value, check that the size of + // the tile or voxel is as expected. + if (numOff % 10 == 0) { + const int dim[4] = { + 1, 1 << DIM0, 1 << (DIM1 + DIM0), 1 << (DIM2 + DIM1 + DIM0) + }; + const int lvl = iter.getLevel(); + CPPUNIT_ASSERT(lvl < 4); + openvdb::CoordBBox bbox; + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL( + bbox.extents(), openvdb::Coord(dim[lvl], dim[lvl], dim[lvl])); + } + } + } + CPPUNIT_ASSERT_EQUAL(2, int(numOn)); + CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); + } + { + Tree323f::ValueAllCIter iter = tree.cbeginValueAll(); + CPPUNIT_ASSERT(iter.test()); + + // Read all tile and voxel values through a const value iterator. + size_t numOn = 0, numOff = 0; + for ( ; iter.test(); iter.next()) { + if (iter.isValueOn()) ++numOn; else ++numOff; + } + CPPUNIT_ASSERT_EQUAL(2, int(numOn)); + CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); + } + { + Tree323f::ValueAllIter iter = tree.beginValueAll(); + CPPUNIT_ASSERT(iter.test()); + + // Read all tile and voxel values through a non-const value iterator + // and overwrite all active values. + size_t numOn = 0, numOff = 0; + for ( ; iter; ++iter) { + if (iter.isValueOn()) { + iter.setValue(iter.getValue() - 5); + ++numOn; + } else { + ++numOff; + } + } + CPPUNIT_ASSERT_EQUAL(2, int(numOn)); + CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); + } +} + + +void +TestTreeIterators::testValueOnIterator() +{ + typedef openvdb::tree::Tree4::Type Tree323f; + + Tree323f tree(/*fillValue=*/256.0f); + + { + Tree323f::ValueOnIter iter = tree.beginValueOn(); + CPPUNIT_ASSERT(!iter.test()); // empty tree + } + + const int STEP = 8/*100*/, NUM_STEPS = 10; + for (int i = 0; i < NUM_STEPS; ++i) { + tree.setValue(openvdb::Coord(STEP * i), 0.0f); + } + + { + Tree323f::ValueOnIter iter = tree.beginValueOn(); + CPPUNIT_ASSERT(iter.test()); + + // Read all active tile and voxel values through a non-const value iterator. + size_t numOn = 0; + for ( ; iter; ++iter) { + CPPUNIT_ASSERT(iter.isVoxelValue()); + CPPUNIT_ASSERT(iter.isValueOn()); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.0f, iter.getValue()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(STEP * numOn), iter.getCoord()); + ++numOn; + } + CPPUNIT_ASSERT_EQUAL(NUM_STEPS, int(numOn)); + } + { + Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); + CPPUNIT_ASSERT(iter.test()); + + // Read all active tile and voxel values through a const value iterator. + size_t numOn = 0; + for ( ; iter.test(); iter.next()) { + CPPUNIT_ASSERT(iter.isVoxelValue()); + CPPUNIT_ASSERT(iter.isValueOn()); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.0f, iter.getValue()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(STEP * numOn), iter.getCoord()); + ++numOn; + } + CPPUNIT_ASSERT_EQUAL(NUM_STEPS, int(numOn)); + } + { + Tree323f::ValueOnIter iter = tree.beginValueOn(); + CPPUNIT_ASSERT(iter.test()); + + // Read all active tile and voxel values through a non-const value iterator + // and overwrite the values. + size_t numOn = 0; + for ( ; iter; ++iter) { + CPPUNIT_ASSERT(iter.isVoxelValue()); + CPPUNIT_ASSERT(iter.isValueOn()); + ASSERT_DOUBLES_EXACTLY_EQUAL(0.0f, iter.getValue()); + iter.setValue(5.0f); + ASSERT_DOUBLES_EXACTLY_EQUAL(5.0f, iter.getValue()); + CPPUNIT_ASSERT_EQUAL(openvdb::Coord(STEP * numOn), iter.getCoord()); + ++numOn; + } + CPPUNIT_ASSERT_EQUAL(NUM_STEPS, int(numOn)); + } +} + + +void +TestTreeIterators::testValueOffIterator() +{ + const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; + + typedef openvdb::tree::Tree4::Type Tree323f; + + Tree323f tree(/*fillValue=*/256.0f); + tree.setValue(openvdb::Coord(4), 0.0f); + tree.setValue(openvdb::Coord(-4), -1.0f); + + const size_t expectedNumOff = + 2 * ((1 << (3 * DIM2)) - 1) // 2 8x8x8 InternalNodes - 1 child pointer each + + 2 * ((1 << (3 * DIM1)) - 1) // 2 4x4x4 InternalNodes - 1 child pointer each + + 2 * ((1 << (3 * DIM0)) - 1); // 2 8x8x8 LeafNodes - 1 active value each + + { + Tree323f::ValueOffIter iter = tree.beginValueOff(); + CPPUNIT_ASSERT(iter.test()); + + // Read all inactive tile and voxel values through a non-const value iterator. + size_t numOff = 0; + for ( ; iter; ++iter) { + CPPUNIT_ASSERT(!iter.isValueOn()); + ++numOff; + // For every tenth inactive value, check that the size of + // the tile or voxel is as expected. + if (numOff % 10 == 0) { + const int dim[4] = { + 1, 1 << DIM0, 1 << (DIM1 + DIM0), 1 << (DIM2 + DIM1 + DIM0) + }; + const int lvl = iter.getLevel(); + CPPUNIT_ASSERT(lvl < 4); + openvdb::CoordBBox bbox; + iter.getBoundingBox(bbox); + CPPUNIT_ASSERT_EQUAL(bbox.extents(), openvdb::Coord(dim[lvl], dim[lvl], dim[lvl])); + } + } + CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); + } + { + Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); + CPPUNIT_ASSERT(iter.test()); + + // Read all inactive tile and voxel values through a const value iterator. + size_t numOff = 0; + for ( ; iter.test(); iter.next(), ++numOff) { + CPPUNIT_ASSERT(!iter.isValueOn()); + } + CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); + } + { + Tree323f::ValueOffIter iter = tree.beginValueOff(); + CPPUNIT_ASSERT(iter.test()); + + // Read all inactive tile and voxel values through a non-const value iterator + // and overwrite the values. + size_t numOff = 0; + for ( ; iter; ++iter, ++numOff) { + iter.setValue(iter.getValue() - 5); + iter.setValueOff(); + } + for (iter = tree.beginValueOff(); iter; ++iter, --numOff); + CPPUNIT_ASSERT_EQUAL(size_t(0), numOff); + } +} + + +void +TestTreeIterators::testModifyValue() +{ + using openvdb::Coord; + + const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; + { + typedef openvdb::tree::Tree4::Type IntTree323f; + + IntTree323f tree(/*background=*/256); + tree.addTile(/*level=*/3, Coord(-1), /*value=*/ 4, /*active=*/true); + tree.addTile(/*level=*/2, Coord(1 << (DIM0 + DIM1)), /*value=*/-3, /*active=*/true); + tree.addTile(/*level=*/1, Coord(1 << DIM0), /*value=*/ 2, /*active=*/true); + tree.addTile(/*level=*/0, Coord(0), /*value=*/-1, /*active=*/true); + + struct Local { static inline void negate(int32_t& n) { n = -n; } }; + + for (IntTree323f::ValueAllIter iter = tree.beginValueAll(); iter; ++iter) { + iter.modifyValue(Local::negate); + } + + for (IntTree323f::ValueAllCIter iter = tree.cbeginValueAll(); iter; ++iter) { + const int32_t val = *iter; + if (val < 0) CPPUNIT_ASSERT((-val) % 2 == 0); // negative values are even + else CPPUNIT_ASSERT(val % 2 == 1); // positive values are odd + } + + // Because modifying values through a const iterator is not allowed, + // uncommenting the following line should result in a static assertion failure: + //tree.cbeginValueOn().modifyValue(Local::negate); + } + { + typedef openvdb::tree::Tree4::Type BoolTree323f; + + BoolTree323f tree; + tree.addTile(/*level=*/3, Coord(-1), /*value=*/false, /*active=*/true); + tree.addTile(/*level=*/2, Coord(1 << (DIM0 + DIM1)), /*value=*/ true, /*active=*/true); + tree.addTile(/*level=*/1, Coord(1 << DIM0), /*value=*/false, /*active=*/true); + tree.addTile(/*level=*/0, Coord(0), /*value=*/ true, /*active=*/true); + + struct Local { static inline void negate(bool& b) { b = !b; } }; + + for (BoolTree323f::ValueAllIter iter = tree.beginValueAll(); iter; ++iter) { + iter.modifyValue(Local::negate); + } + + CPPUNIT_ASSERT(!tree.getValue(Coord(0))); + CPPUNIT_ASSERT( tree.getValue(Coord(1 << DIM0))); + CPPUNIT_ASSERT(!tree.getValue(Coord(1 << (DIM0 + DIM1)))); + CPPUNIT_ASSERT( tree.getValue(Coord(-1))); + + // Because modifying values through a const iterator is not allowed, + // uncommenting the following line should result in a static assertion failure: + //tree.cbeginValueOn().modifyValue(Local::negate); + } + { + typedef openvdb::tree::Tree4::Type StringTree323f; + + StringTree323f tree(/*background=*/""); + tree.addTile(/*level=*/3, Coord(-1), /*value=*/"abc", /*active=*/true); + tree.addTile(/*level=*/2, Coord(1 << (DIM0 + DIM1)), /*value=*/"abc", /*active=*/true); + tree.addTile(/*level=*/1, Coord(1 << DIM0), /*value=*/"abc", /*active=*/true); + tree.addTile(/*level=*/0, Coord(0), /*value=*/"abc", /*active=*/true); + + struct Local { static inline void op(std::string& s) { s.append("def"); } }; + + for (StringTree323f::ValueOnIter iter = tree.beginValueOn(); iter; ++iter) { + iter.modifyValue(Local::op); + } + + const std::string expectedVal("abcdef"); + for (StringTree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { + CPPUNIT_ASSERT_EQUAL(expectedVal, *iter); + } + for (StringTree323f::ValueOffCIter iter = tree.cbeginValueOff(); iter; ++iter) { + CPPUNIT_ASSERT((*iter).empty()); + } + } +} + + +void +TestTreeIterators::testDepthBounds() +{ + const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; + + typedef openvdb::tree::Tree4::Type Tree323f; + + Tree323f tree(/*fillValue=*/256.0f); + tree.setValue(openvdb::Coord(4), 0.0f); + tree.setValue(openvdb::Coord(-4), -1.0f); + + const size_t + numDepth1 = 2 * ((1 << (3 * DIM2)) - 1), // 2 8x8x8 InternalNodes - 1 child pointer each + numDepth2 = 2 * ((1 << (3 * DIM1)) - 1), // 2 4x4x4 InternalNodes - 1 child pointer each + numDepth3 = 2 * ((1 << (3 * DIM0)) - 1), // 2 8x8x8 LeafNodes - 1 active value each + expectedNumOff = numDepth1 + numDepth2 + numDepth3; + + { + Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); + CPPUNIT_ASSERT(iter.test()); + + // Read all inactive tile and voxel values through a non-const value iterator. + size_t numOff = 0; + for ( ; iter; ++iter) { + CPPUNIT_ASSERT(!iter.isValueOn()); + ++numOff; + } + CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); + } + { + // Repeat, setting the minimum iterator depth to 2. + Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); + CPPUNIT_ASSERT(iter.test()); + + iter.setMinDepth(2); + CPPUNIT_ASSERT(iter.test()); + + size_t numOff = 0; + for ( ; iter; ++iter) { + CPPUNIT_ASSERT(!iter.isValueOn()); + ++numOff; + const int depth = iter.getDepth(); + CPPUNIT_ASSERT(depth > 1); + } + CPPUNIT_ASSERT_EQUAL(expectedNumOff - numDepth1, numOff); + } + { + // Repeat, setting the minimum and maximum depths to 2. + Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); + CPPUNIT_ASSERT(iter.test()); + + iter.setMinDepth(2); + CPPUNIT_ASSERT(iter.test()); + + iter.setMaxDepth(2); + CPPUNIT_ASSERT(iter.test()); + + size_t numOff = 0; + for ( ; iter; ++iter) { + CPPUNIT_ASSERT(!iter.isValueOn()); + ++numOff; + const int depth = iter.getDepth(); + CPPUNIT_ASSERT_EQUAL(2, depth); + } + CPPUNIT_ASSERT_EQUAL(expectedNumOff - numDepth1 - numDepth3, numOff); + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestTreeVisitor.cc b/openvdb_2_3_0_library/openvdb/unittest/TestTreeVisitor.cc new file mode 100755 index 0000000..aa2d9c6 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestTreeVisitor.cc @@ -0,0 +1,376 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 (it != mNodes.end()) ? it->second.size() : 0; + } + openvdb::Index nonLeafCount() const + { + openvdb::Index count = 1; // root node + for (NodeMap::const_iterator i = mNodes.begin(), e = mNodes.end(); i != e; ++i) { + if (i->first != 0) count += i->second.size(); + } + return count; + } + + bool usedOnlyConstIterators() const + { + return (mConstIterUseCount > 0 && mNonConstIterUseCount == 0); + } + bool usedOnlyNonConstIterators() const + { + return (mConstIterUseCount == 0 && mNonConstIterUseCount > 0); + } + +private: + template + void insertChild(const ChildT* child) + { + if (child != NULL) { + const openvdb::Index level = child->getLevel(); + if (!mSkipLeafNodes || level > 0) { + mNodes[level].insert(child); + } + } + } + + void incrementIterUseCount(bool isConst) + { + if (isConst) ++mConstIterUseCount; else ++mNonConstIterUseCount; + } + + bool mSkipLeafNodes; + NodeMap mNodes; + int mNonConstIterUseCount, mConstIterUseCount; +}; + +/// Specialization for LeafNode iterators, whose ChildNodeType is void +/// (therefore can't call child->getLevel()) +template<> inline void Visitor::insertChild(const void*) {} + +} // unnamed namespace + + +template +void +TestTreeVisitor::visitTree() +{ + TreeT tree = createTestTree(); + { + // Traverse the tree, accumulating node counts. + Visitor visitor; + const_cast(tree).visit(visitor); + + CPPUNIT_ASSERT(visitor.usedOnlyConstIterators()); + CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.leafCount()); + CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.nonLeafCount()); + } + { + // Traverse the tree, accumulating node counts as above, + // but using non-const iterators. + Visitor visitor; + tree.visit(visitor); + + CPPUNIT_ASSERT(visitor.usedOnlyNonConstIterators()); + CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.leafCount()); + CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.nonLeafCount()); + } + { + // Traverse the tree, accumulating counts of non-leaf nodes only. + Visitor visitor; + visitor.setSkipLeafNodes(true); + const_cast(tree).visit(visitor); + + CPPUNIT_ASSERT(visitor.usedOnlyConstIterators()); + CPPUNIT_ASSERT_EQUAL(0U, visitor.leafCount()); // leaf nodes were skipped + CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.nonLeafCount()); + } +} + + +//////////////////////////////////////// + + +namespace { + +/// Two-tree visitor that accumulates node counts +class Visitor2 +{ +public: + typedef std::map > NodeMap; + + Visitor2() { reset(); } + + void reset() + { + mSkipALeafNodes = mSkipBLeafNodes = false; + mANodeCount.clear(); + mBNodeCount.clear(); + } + + void setSkipALeafNodes(bool b) { mSkipALeafNodes = b; } + void setSkipBLeafNodes(bool b) { mSkipBLeafNodes = b; } + + openvdb::Index aLeafCount() const { return leafCount(/*useA=*/true); } + openvdb::Index bLeafCount() const { return leafCount(/*useA=*/false); } + openvdb::Index aNonLeafCount() const { return nonLeafCount(/*useA=*/true); } + openvdb::Index bNonLeafCount() const { return nonLeafCount(/*useA=*/false); } + + template + int operator()(AIterT& aIter, BIterT& bIter) + { + CPPUNIT_ASSERT(aIter.getParentNode() != NULL); + CPPUNIT_ASSERT(bIter.getParentNode() != NULL); + + typename AIterT::NodeType& aNode = aIter.parent(); + typename BIterT::NodeType& bNode = bIter.parent(); + + const openvdb::Index aLevel = aNode.getLevel(), bLevel = bNode.getLevel(); + mANodeCount[aLevel].insert(&aNode); + mBNodeCount[bLevel].insert(&bNode); + + int skipBranch = 0; + if (aLevel == 1 && mSkipALeafNodes) skipBranch = (skipBranch | 1); + if (bLevel == 1 && mSkipBLeafNodes) skipBranch = (skipBranch | 2); + return skipBranch; + } + +private: + openvdb::Index leafCount(bool useA) const + { + const NodeMap& theMap = (useA ? mANodeCount : mBNodeCount); + NodeMap::const_iterator it = theMap.find(0); + if (it != theMap.end()) return it->second.size(); + return 0; + } + openvdb::Index nonLeafCount(bool useA) const + { + openvdb::Index count = 0; + const NodeMap& theMap = (useA ? mANodeCount : mBNodeCount); + for (NodeMap::const_iterator i = theMap.begin(), e = theMap.end(); i != e; ++i) { + if (i->first != 0) count += i->second.size(); + } + return count; + } + + bool mSkipALeafNodes, mSkipBLeafNodes; + NodeMap mANodeCount, mBNodeCount; +}; + +} // unnamed namespace + + +void +TestTreeVisitor::testVisit2Trees() +{ + typedef openvdb::FloatTree TreeT; + typedef openvdb::VectorTree Tree2T; + typedef TreeT::ValueType ValueT; + typedef Tree2T::ValueType Value2T; + + // Create a test tree. + TreeT tree = createTestTree(); + // Create another test tree of a different type but with the same topology. + Tree2T tree2 = createTestTree(); + + // Traverse both trees. + Visitor2 visitor; + tree.visit2(tree2, visitor); + + //CPPUNIT_ASSERT(visitor.usedOnlyConstIterators()); + CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.aLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.bLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.aNonLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.bNonLeafCount()); + + visitor.reset(); + + // Change the topology of the first tree. + tree.setValue(openvdb::Coord(-200, -200, -200), openvdb::zeroVal()); + + // Traverse both trees. + tree.visit2(tree2, visitor); + + CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.aLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.bLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.aNonLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.bNonLeafCount()); + + visitor.reset(); + + // Traverse the two trees in the opposite order. + tree2.visit2(tree, visitor); + + CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.aLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.bLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.aNonLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.bNonLeafCount()); + + // Repeat, skipping leaf nodes of tree2. + visitor.reset(); + visitor.setSkipALeafNodes(true); + tree2.visit2(tree, visitor); + + CPPUNIT_ASSERT_EQUAL(0U, visitor.aLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.bLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.aNonLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.bNonLeafCount()); + + // Repeat, skipping leaf nodes of tree. + visitor.reset(); + visitor.setSkipBLeafNodes(true); + tree2.visit2(tree, visitor); + + CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.aLeafCount()); + CPPUNIT_ASSERT_EQUAL(0U, visitor.bLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.aNonLeafCount()); + CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.bNonLeafCount()); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestValueAccessor.cc b/openvdb_2_3_0_library/openvdb/unittest/TestValueAccessor.cc new file mode 100755 index 0000000..3f9a9c5 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestValueAccessor.cc @@ -0,0 +1,549 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + + +typedef float ValueType; +typedef openvdb::tree::Tree< + openvdb::tree::RootNode< + openvdb::tree::LeafNode > > Tree2Type; +typedef openvdb::tree::Tree< + openvdb::tree::RootNode< + openvdb::tree::InternalNode< + openvdb::tree::LeafNode, 4> > > Tree3Type; +typedef openvdb::tree::Tree4::Type Tree4Type; +typedef openvdb::tree::Tree< + openvdb::tree::RootNode< + openvdb::tree::InternalNode< + openvdb::tree::InternalNode< + openvdb::tree::InternalNode< + openvdb::tree::LeafNode, 4>, 5>, 5> > > Tree5Type; +typedef Tree4Type TreeType; + + +using namespace openvdb::tree; + +class TestValueAccessor: public CppUnit::TestFixture +{ +public: + virtual void setUp() { openvdb::initialize(); } + virtual void tearDown() { openvdb::uninitialize(); } + + CPPUNIT_TEST_SUITE(TestValueAccessor); + + CPPUNIT_TEST(testTree2Accessor); + CPPUNIT_TEST(testTree2AccessorRW); + CPPUNIT_TEST(testTree2ConstAccessor); + CPPUNIT_TEST(testTree2ConstAccessorRW); + + CPPUNIT_TEST(testTree3Accessor); + CPPUNIT_TEST(testTree3AccessorRW); + CPPUNIT_TEST(testTree3ConstAccessor); + CPPUNIT_TEST(testTree3ConstAccessorRW); + + CPPUNIT_TEST(testTree4Accessor); + CPPUNIT_TEST(testTree4AccessorRW); + CPPUNIT_TEST(testTree4ConstAccessor); + CPPUNIT_TEST(testTree4ConstAccessorRW); + + CPPUNIT_TEST(testTree5Accessor); + CPPUNIT_TEST(testTree5AccessorRW); + CPPUNIT_TEST(testTree5ConstAccessor); + CPPUNIT_TEST(testTree5ConstAccessorRW); + + CPPUNIT_TEST(testTree3Accessor2); + CPPUNIT_TEST(testTree3ConstAccessor2); + CPPUNIT_TEST(testTree4Accessor2); + CPPUNIT_TEST(testTree4ConstAccessor2); + CPPUNIT_TEST(testTree4Accessor1); + CPPUNIT_TEST(testTree4ConstAccessor1); + CPPUNIT_TEST(testTree4Accessor0); + CPPUNIT_TEST(testTree4ConstAccessor0); + CPPUNIT_TEST(testTree5Accessor2); + CPPUNIT_TEST(testTree5ConstAccessor2); + CPPUNIT_TEST(testTree4Accessor12);//cache node level 2 + CPPUNIT_TEST(testTree5Accessor213);//cache node level 1 and 3 + + CPPUNIT_TEST(testMultithreadedAccessor); + CPPUNIT_TEST(testAccessorRegistration); + CPPUNIT_TEST(testGetNode); + + CPPUNIT_TEST_SUITE_END(); + // cache all node levels + void testTree2Accessor() { accessorTest >(); } + void testTree2AccessorRW() { accessorTest >(); } + void testTree2ConstAccessor() { constAccessorTest >(); } + void testTree2ConstAccessorRW() { constAccessorTest >(); } + // cache all node levels + void testTree3Accessor() { accessorTest >(); } + void testTree3AccessorRW() { accessorTest >(); } + void testTree3ConstAccessor() { constAccessorTest >(); } + void testTree3ConstAccessorRW() { constAccessorTest >(); } + // cache all node levels + void testTree4Accessor() { accessorTest >(); } + void testTree4AccessorRW() { accessorTest >(); } + void testTree4ConstAccessor() { constAccessorTest >(); } + void testTree4ConstAccessorRW() { constAccessorTest >(); } + // cache all node levels + void testTree5Accessor() { accessorTest >(); } + void testTree5AccessorRW() { accessorTest >(); } + void testTree5ConstAccessor() { constAccessorTest >(); } + void testTree5ConstAccessorRW() { constAccessorTest >(); } + + // Test odd combinations of trees and ValueAccessors + // cache node level 0 and 1 + void testTree3Accessor2() { accessorTest >(); } + void testTree3ConstAccessor2() { constAccessorTest >(); } + void testTree4Accessor2() { accessorTest >(); } + void testTree4ConstAccessor2() { constAccessorTest >(); } + void testTree5Accessor2() { accessorTest >(); } + void testTree5ConstAccessor2() { constAccessorTest >(); } + // only cache leaf level + void testTree4Accessor1() { accessorTest >(); } + void testTree4ConstAccessor1() { constAccessorTest >(); } + // disable node caching + void testTree4Accessor0() { accessorTest >(); } + void testTree4ConstAccessor0() { constAccessorTest >(); } + //cache node level 2 + void testTree4Accessor12() { accessorTest >(); } + //cache node level 1 and 3 + void testTree5Accessor213() { accessorTest >(); } + + void testMultithreadedAccessor(); + void testAccessorRegistration(); + void testGetNode(); + +private: + template void accessorTest(); + template void constAccessorTest(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestValueAccessor); + + +//////////////////////////////////////// + + +namespace { + +struct Plus +{ + float addend; + Plus(float f): addend(f) {} + inline void operator()(float& f) const { f += addend; } + inline void operator()(float& f, bool& b) const { f += addend; b = false; } +}; + +} + + +template +void +TestValueAccessor::accessorTest() +{ + typedef typename AccessorT::TreeType TreeType; + const int leafDepth = int(TreeType::DEPTH) - 1; + // subtract one because getValueDepth() returns 0 for values at the root + + const ValueType background = 5.0f, value = -9.345f; + const openvdb::Coord c0(5, 10, 20), c1(500000, 200000, 300000); + + { + TreeType tree(background); + CPPUNIT_ASSERT(!tree.isValueOn(c0)); + CPPUNIT_ASSERT(!tree.isValueOn(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); + tree.setValue(c0, value); + CPPUNIT_ASSERT(tree.isValueOn(c0)); + CPPUNIT_ASSERT(!tree.isValueOn(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); + } + { + TreeType tree(background); + AccessorT acc(tree); + ValueType v; + + CPPUNIT_ASSERT(!tree.isValueOn(c0)); + CPPUNIT_ASSERT(!tree.isValueOn(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); + CPPUNIT_ASSERT(!acc.isCached(c0)); + CPPUNIT_ASSERT(!acc.isCached(c1)); + CPPUNIT_ASSERT(!acc.probeValue(c0,v)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); + CPPUNIT_ASSERT(!acc.probeValue(c1,v)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); + CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c0)); + CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); + CPPUNIT_ASSERT(!acc.isVoxel(c0)); + CPPUNIT_ASSERT(!acc.isVoxel(c1)); + + acc.setValue(c0, value); + + CPPUNIT_ASSERT(tree.isValueOn(c0)); + CPPUNIT_ASSERT(!tree.isValueOn(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); + CPPUNIT_ASSERT(acc.probeValue(c0,v)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, v); + CPPUNIT_ASSERT(!acc.probeValue(c1,v)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); + CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); // leaf-level voxel value + CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); // background value + CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(openvdb::Coord(7, 10, 20))); + const int depth = leafDepth == 1 ? -1 : leafDepth - 1; + CPPUNIT_ASSERT_EQUAL(depth, acc.getValueDepth(openvdb::Coord(8, 10, 20))); + CPPUNIT_ASSERT( acc.isVoxel(c0)); // leaf-level voxel value + CPPUNIT_ASSERT(!acc.isVoxel(c1)); + CPPUNIT_ASSERT( acc.isVoxel(openvdb::Coord(7, 10, 20))); + CPPUNIT_ASSERT(!acc.isVoxel(openvdb::Coord(8, 10, 20))); + + ASSERT_DOUBLES_EXACTLY_EQUAL(background, acc.getValue(c1)); + CPPUNIT_ASSERT(!acc.isCached(c1)); // uncached background value + CPPUNIT_ASSERT(!acc.isValueOn(c1)); // inactive background value + ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); + CPPUNIT_ASSERT( + (acc.numCacheLevels()>0) == acc.isCached(c0)); // active, leaf-level voxel value + CPPUNIT_ASSERT(acc.isValueOn(c0)); + + acc.setValue(c1, value); + + CPPUNIT_ASSERT(acc.isValueOn(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c1)); + CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c1)); + CPPUNIT_ASSERT(!acc.isCached(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); + CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); + CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); + CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); + CPPUNIT_ASSERT(acc.isVoxel(c0)); + CPPUNIT_ASSERT(acc.isVoxel(c1)); + + tree.setValueOff(c1); + + ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c1)); + CPPUNIT_ASSERT(!acc.isCached(c0)); + CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); + CPPUNIT_ASSERT( acc.isValueOn(c0)); + CPPUNIT_ASSERT(!acc.isValueOn(c1)); + + acc.setValueOn(c1); + + CPPUNIT_ASSERT(!acc.isCached(c0)); + CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); + CPPUNIT_ASSERT( acc.isValueOn(c0)); + CPPUNIT_ASSERT( acc.isValueOn(c1)); + + acc.modifyValueAndActiveState(c1, Plus(-value)); // subtract value & mark inactive + CPPUNIT_ASSERT(!acc.isValueOn(c1)); + + acc.modifyValue(c1, Plus(-value)); // subtract value again & mark active + + CPPUNIT_ASSERT(acc.isValueOn(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(-value, tree.getValue(c1)); + CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(-value, acc.getValue(c1)); + CPPUNIT_ASSERT(!acc.isCached(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); + CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); + CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); + CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); + CPPUNIT_ASSERT(acc.isVoxel(c0)); + CPPUNIT_ASSERT(acc.isVoxel(c1)); + + acc.setValueOnly(c1, 3*value); + + CPPUNIT_ASSERT(acc.isValueOn(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(3*value, tree.getValue(c1)); + CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(3*value, acc.getValue(c1)); + CPPUNIT_ASSERT(!acc.isCached(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); + CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); + CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); + CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); + CPPUNIT_ASSERT(acc.isVoxel(c0)); + CPPUNIT_ASSERT(acc.isVoxel(c1)); + + acc.clear(); + CPPUNIT_ASSERT(!acc.isCached(c0)); + CPPUNIT_ASSERT(!acc.isCached(c1)); + } +} + + +template +void +TestValueAccessor::constAccessorTest() +{ + typedef typename boost::remove_const::type TreeType; + const int leafDepth = int(TreeType::DEPTH) - 1; + // subtract one because getValueDepth() returns 0 for values at the root + + const ValueType background = 5.0f, value = -9.345f; + const openvdb::Coord c0(5, 10, 20), c1(500000, 200000, 300000); + ValueType v; + + TreeType tree(background); + AccessorT acc(tree); + + CPPUNIT_ASSERT(!tree.isValueOn(c0)); + CPPUNIT_ASSERT(!tree.isValueOn(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); + CPPUNIT_ASSERT(!acc.isCached(c0)); + CPPUNIT_ASSERT(!acc.isCached(c1)); + CPPUNIT_ASSERT(!acc.probeValue(c0,v)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); + CPPUNIT_ASSERT(!acc.probeValue(c1,v)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); + CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c0)); + CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); + CPPUNIT_ASSERT(!acc.isVoxel(c0)); + CPPUNIT_ASSERT(!acc.isVoxel(c1)); + + tree.setValue(c0, value); + + CPPUNIT_ASSERT(tree.isValueOn(c0)); + CPPUNIT_ASSERT(!tree.isValueOn(c1)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, acc.getValue(c1)); + CPPUNIT_ASSERT(!acc.isCached(c1)); + CPPUNIT_ASSERT(!acc.isCached(c0)); + CPPUNIT_ASSERT(acc.isValueOn(c0)); + CPPUNIT_ASSERT(!acc.isValueOn(c1)); + CPPUNIT_ASSERT(acc.probeValue(c0,v)); + ASSERT_DOUBLES_EXACTLY_EQUAL(value, v); + CPPUNIT_ASSERT(!acc.probeValue(c1,v)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); + CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); + CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); + CPPUNIT_ASSERT( acc.isVoxel(c0)); + CPPUNIT_ASSERT(!acc.isVoxel(c1)); + + ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); + CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); + ASSERT_DOUBLES_EXACTLY_EQUAL(background, acc.getValue(c1)); + CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); + CPPUNIT_ASSERT(!acc.isCached(c1)); + CPPUNIT_ASSERT(acc.isValueOn(c0)); + CPPUNIT_ASSERT(!acc.isValueOn(c1)); + + tree.setValue(c1, value); + + ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c1)); + CPPUNIT_ASSERT(!acc.isCached(c0)); + CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); + CPPUNIT_ASSERT(acc.isValueOn(c0)); + CPPUNIT_ASSERT(acc.isValueOn(c1)); + CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); + CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); + CPPUNIT_ASSERT(acc.isVoxel(c0)); + CPPUNIT_ASSERT(acc.isVoxel(c1)); + + // The next two lines should not compile, because the acc references a const tree: + //acc.setValue(c1, value); + //acc.setValueOff(c1); + + acc.clear(); + CPPUNIT_ASSERT(!acc.isCached(c0)); + CPPUNIT_ASSERT(!acc.isCached(c1)); +} + + +void +TestValueAccessor::testMultithreadedAccessor() +{ +#define MAX_COORD 5000 + + typedef openvdb::tree::ValueAccessorRW AccessorT; + // Substituting the following typedef typically results in assertion failures: + //typedef openvdb::tree::ValueAccessor AccessorT; + + // Task to perform multiple reads through a shared accessor + struct ReadTask: public tbb::task { + AccessorT& acc; + ReadTask(AccessorT& c): acc(c) {} + tbb::task* execute() + { + for (int i = -MAX_COORD; i < MAX_COORD; ++i) { + ASSERT_DOUBLES_EXACTLY_EQUAL(double(i), acc.getValue(openvdb::Coord(i))); + } + return NULL; + } + }; + // Task to perform multiple writes through a shared accessor + struct WriteTask: public tbb::task { + AccessorT& acc; + WriteTask(AccessorT& c): acc(c) {} + tbb::task* execute() + { + for (int i = -MAX_COORD; i < MAX_COORD; ++i) { + float f = acc.getValue(openvdb::Coord(i)); + ASSERT_DOUBLES_EXACTLY_EQUAL(float(i), f); + acc.setValue(openvdb::Coord(i), i); + ASSERT_DOUBLES_EXACTLY_EQUAL(float(i), acc.getValue(openvdb::Coord(i))); + } + return NULL; + } + }; + // Parent task to spawn multiple parallel read and write tasks + struct RootTask: public tbb::task { + AccessorT& acc; + RootTask(AccessorT& c): acc(c) {} + tbb::task* execute() + { + ReadTask* r[3]; WriteTask* w[3]; + for (int i = 0; i < 3; ++i) { + r[i] = new(allocate_child()) ReadTask(acc); + w[i] = new(allocate_child()) WriteTask(acc); + } + set_ref_count(6 /*children*/ + 1 /*wait*/); + for (int i = 0; i < 3; ++i) { + spawn(*r[i]); spawn(*w[i]); + } + wait_for_all(); + return NULL; + } + }; + + Tree4Type tree(/*background=*/0.5); + AccessorT acc(tree); + // Populate the tree. + for (int i = -MAX_COORD; i < MAX_COORD; ++i) { + acc.setValue(openvdb::Coord(i), i); + } + + // Run multiple read and write tasks in parallel. + RootTask& root = *new(tbb::task::allocate_root()) RootTask(acc); + tbb::task::spawn_root_and_wait(root); + +#undef MAX_COORD +} + + +void +TestValueAccessor::testAccessorRegistration() +{ + using openvdb::Index; + + const float background = 5.0f, value = -9.345f; + const openvdb::Coord c0(5, 10, 20); + + openvdb::FloatTree::Ptr tree(new openvdb::FloatTree(background)); + openvdb::tree::ValueAccessor acc(*tree); + + // Set a single leaf voxel via the accessor and verify that + // the cache is populated. + acc.setValue(c0, value); + CPPUNIT_ASSERT_EQUAL(Index(1), tree->leafCount()); + CPPUNIT_ASSERT_EQUAL(tree->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. + 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-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestVec2Metadata.cc b/openvdb_2_3_0_library/openvdb/unittest/TestVec2Metadata.cc new file mode 100755 index 0000000..505bf74 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestVec2Metadata.cc @@ -0,0 +1,128 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +class TestVec2Metadata : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestVec2Metadata); + CPPUNIT_TEST(testVec2i); + CPPUNIT_TEST(testVec2s); + CPPUNIT_TEST(testVec2d); + CPPUNIT_TEST_SUITE_END(); + + void testVec2i(); + void testVec2s(); + void testVec2d(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestVec2Metadata); + +void +TestVec2Metadata::testVec2i() +{ + using namespace openvdb; + + Metadata::Ptr m(new Vec2IMetadata(openvdb::Vec2i(1, 1))); + Metadata::Ptr m2 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); + + CPPUNIT_ASSERT(m->typeName().compare("vec2i") == 0); + CPPUNIT_ASSERT(m2->typeName().compare("vec2i") == 0); + + Vec2IMetadata *s = dynamic_cast(m.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec2i(1, 1)); + s->value() = openvdb::Vec2i(2, 2); + CPPUNIT_ASSERT(s->value() == openvdb::Vec2i(2, 2)); + + m2->copy(*s); + + s = dynamic_cast(m2.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec2i(2, 2)); +} + +void +TestVec2Metadata::testVec2s() +{ + using namespace openvdb; + + Metadata::Ptr m(new Vec2SMetadata(openvdb::Vec2s(1, 1))); + Metadata::Ptr m2 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); + + CPPUNIT_ASSERT(m->typeName().compare("vec2s") == 0); + CPPUNIT_ASSERT(m2->typeName().compare("vec2s") == 0); + + Vec2SMetadata *s = dynamic_cast(m.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec2s(1, 1)); + s->value() = openvdb::Vec2s(2, 2); + CPPUNIT_ASSERT(s->value() == openvdb::Vec2s(2, 2)); + + m2->copy(*s); + + s = dynamic_cast(m2.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec2s(2, 2)); +} + +void +TestVec2Metadata::testVec2d() +{ + using namespace openvdb; + + Metadata::Ptr m(new Vec2DMetadata(openvdb::Vec2d(1, 1))); + Metadata::Ptr m2 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); + + CPPUNIT_ASSERT(m->typeName().compare("vec2d") == 0); + CPPUNIT_ASSERT(m2->typeName().compare("vec2d") == 0); + + Vec2DMetadata *s = dynamic_cast(m.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec2d(1, 1)); + s->value() = openvdb::Vec2d(2, 2); + CPPUNIT_ASSERT(s->value() == openvdb::Vec2d(2, 2)); + + m2->copy(*s); + + s = dynamic_cast(m2.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec2d(2, 2)); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestVec3Metadata.cc b/openvdb_2_3_0_library/openvdb/unittest/TestVec3Metadata.cc new file mode 100755 index 0000000..b19e435 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestVec3Metadata.cc @@ -0,0 +1,128 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +class TestVec3Metadata : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestVec3Metadata); + CPPUNIT_TEST(testVec3i); + CPPUNIT_TEST(testVec3s); + CPPUNIT_TEST(testVec3d); + CPPUNIT_TEST_SUITE_END(); + + void testVec3i(); + void testVec3s(); + void testVec3d(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestVec3Metadata); + +void +TestVec3Metadata::testVec3i() +{ + using namespace openvdb; + + Metadata::Ptr m(new Vec3IMetadata(openvdb::Vec3i(1, 1, 1))); + Metadata::Ptr m3 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast( m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m3.get()) != 0); + + CPPUNIT_ASSERT( m->typeName().compare("vec3i") == 0); + CPPUNIT_ASSERT(m3->typeName().compare("vec3i") == 0); + + Vec3IMetadata *s = dynamic_cast(m.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec3i(1, 1, 1)); + s->value() = openvdb::Vec3i(3, 3, 3); + CPPUNIT_ASSERT(s->value() == openvdb::Vec3i(3, 3, 3)); + + m3->copy(*s); + + s = dynamic_cast(m3.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec3i(3, 3, 3)); +} + +void +TestVec3Metadata::testVec3s() +{ + using namespace openvdb; + + Metadata::Ptr m(new Vec3SMetadata(openvdb::Vec3s(1, 1, 1))); + Metadata::Ptr m3 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast( m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m3.get()) != 0); + + CPPUNIT_ASSERT( m->typeName().compare("vec3s") == 0); + CPPUNIT_ASSERT(m3->typeName().compare("vec3s") == 0); + + Vec3SMetadata *s = dynamic_cast(m.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec3s(1, 1, 1)); + s->value() = openvdb::Vec3s(3, 3, 3); + CPPUNIT_ASSERT(s->value() == openvdb::Vec3s(3, 3, 3)); + + m3->copy(*s); + + s = dynamic_cast(m3.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec3s(3, 3, 3)); +} + +void +TestVec3Metadata::testVec3d() +{ + using namespace openvdb; + + Metadata::Ptr m(new Vec3DMetadata(openvdb::Vec3d(1, 1, 1))); + Metadata::Ptr m3 = m->copy(); + + CPPUNIT_ASSERT(dynamic_cast( m.get()) != 0); + CPPUNIT_ASSERT(dynamic_cast(m3.get()) != 0); + + CPPUNIT_ASSERT( m->typeName().compare("vec3d") == 0); + CPPUNIT_ASSERT(m3->typeName().compare("vec3d") == 0); + + Vec3DMetadata *s = dynamic_cast(m.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec3d(1, 1, 1)); + s->value() = openvdb::Vec3d(3, 3, 3); + CPPUNIT_ASSERT(s->value() == openvdb::Vec3d(3, 3, 3)); + + m3->copy(*s); + + s = dynamic_cast(m3.get()); + CPPUNIT_ASSERT(s->value() == openvdb::Vec3d(3, 3, 3)); +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestVolumeRayIntersector.cc b/openvdb_2_3_0_library/openvdb/unittest/TestVolumeRayIntersector.cc new file mode 100755 index 0000000..a5b495c --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestVolumeRayIntersector.cc @@ -0,0 +1,252 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +/// @author Ken Museth + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); + +#define ASSERT_DOUBLES_APPROX_EQUAL(expected, actual) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/1.e-6); + + +class TestVolumeRayIntersector : public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestVolumeRayIntersector); + CPPUNIT_TEST(testAll); + CPPUNIT_TEST_SUITE_END(); + + void testAll(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestVolumeRayIntersector); + +void +TestVolumeRayIntersector::testAll() +{ + using namespace openvdb; + typedef math::Ray RayT; + typedef RayT::Vec3Type Vec3T; + + {//one single leaf node + FloatGrid grid(0.0f); + + grid.tree().setValue(Coord(0,0,0), 1.0f); + grid.tree().setValue(Coord(7,7,7), 1.0f); + + const Vec3T dir( 1.0, 0.0, 0.0); + const Vec3T eye(-1.0, 0.0, 0.0); + const RayT ray(eye, dir);//ray in index space + tools::VolumeRayIntersector inter(grid); + CPPUNIT_ASSERT(inter.setIndexRay(ray)); + double t0=0, t1=0; + CPPUNIT_ASSERT(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); + } + + {// 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)); + } +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/TestVolumeToMesh.cc b/openvdb_2_3_0_library/openvdb/unittest/TestVolumeToMesh.cc new file mode 100755 index 0000000..748793c --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/TestVolumeToMesh.cc @@ -0,0 +1,141 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include + +#include + +class TestVolumeToMesh: public CppUnit::TestCase +{ +public: + CPPUNIT_TEST_SUITE(TestVolumeToMesh); + CPPUNIT_TEST(testAuxData); + CPPUNIT_TEST(testConversion); + CPPUNIT_TEST_SUITE_END(); + + void testAuxData(); + void testConversion(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestVolumeToMesh); + + +//////////////////////////////////////// + + +void +TestVolumeToMesh::testAuxData() +{ + typedef openvdb::tree::Tree4::Type Tree543f; + Tree543f::Ptr tree(new Tree543f(0)); + + // create one voxel with 3 upwind edges (that have a sign change) + tree->setValue(openvdb::Coord(0,0,0), -1); + tree->setValue(openvdb::Coord(1,0,0), 1); + tree->setValue(openvdb::Coord(0,1,0), 1); + tree->setValue(openvdb::Coord(0,0,1), 1); + + typedef openvdb::tree::LeafManager LeafManager; + + LeafManager leafs(*tree); + + CPPUNIT_ASSERT(openvdb::tools::internal::needsActiveVoxePadding(leafs, 0.0, 1.0)); + + openvdb::tools::internal::SignData op(*tree, leafs, 0.0); + op.run(); + + CPPUNIT_ASSERT(op.signTree()->activeVoxelCount() == 1); + CPPUNIT_ASSERT(op.signTree()->activeVoxelCount() == op.idxTree()->activeVoxelCount()); + + + int flags = int(op.signTree()->getValue(openvdb::Coord(0,0,0))); + + CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::INSIDE)); + CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::EDGES)); + CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::XEDGE)); + CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::YEDGE)); + CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::ZEDGE)); + + + tree->setValueOff(openvdb::Coord(0,0,1), -1); + op.run(); + + CPPUNIT_ASSERT(op.signTree()->activeVoxelCount() == 1); + CPPUNIT_ASSERT(op.signTree()->activeVoxelCount() == op.idxTree()->activeVoxelCount()); + + flags = int(op.signTree()->getValue(openvdb::Coord(0,0,0))); + + CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::INSIDE)); + CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::EDGES)); + CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::XEDGE)); + CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::YEDGE)); + CPPUNIT_ASSERT(!bool(flags & openvdb::tools::internal::ZEDGE)); +} + + +void +TestVolumeToMesh::testConversion() +{ + using namespace openvdb; + + typedef tree::Tree4::Type Tree543f; + typedef Grid GridType; + + GridType::Ptr grid = createGrid(/*background=*/1); + + grid->fill(CoordBBox(Coord(0), Coord(7)), 0.0); + grid->fill(CoordBBox(Coord(1), Coord(6)), -1.0); + + std::vector points; + std::vector quads; + std::vector triangles; + + openvdb::tools::volumeToMesh(*grid, points, quads); + CPPUNIT_ASSERT(points.size() >= 4); + CPPUNIT_ASSERT(!quads.empty()); + /// @todo validate output + + points.clear(); + quads.clear(); + triangles.clear(); + + tools::volumeToMesh(*grid, points, triangles, quads, /*isovalue=*/0.01, /*adaptivity=*/0); + CPPUNIT_ASSERT(points.size() >= 3); + CPPUNIT_ASSERT(!triangles.empty() || !quads.empty()); + /// @todo validate output +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/main.cc b/openvdb_2_3_0_library/openvdb/unittest/main.cc new file mode 100755 index 0000000..156d262 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/main.cc @@ -0,0 +1,202 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifdef DWA_OPENVDB + +#include +#include + +#else + +#include // for exit() +#include // for strrchr() +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef OPENVDB_USE_LOG4CPLUS +#include +#include +#include +#endif +#ifdef _WIN32 +#include +#else +#include // for getopt(), optarg +#endif + + +static void +dump(CppUnit::Test* test) +{ + if (test == NULL) { + std::cerr << "Error: no tests found\n"; + return; + } + + std::cout << test->getName() << std::endl; + for (int i = 0; i < test->getChildTestCount(); i++) { + dump(test->getChildTestAt(i)); + } +} + + +int +run(int argc, char* argv[]) +{ + int verbose = 0; + std::string tests; + int c = -1; + while ((c = getopt(argc, argv, "lt:v")) != -1) { + switch (c) { + case 'l': + { + dump(CppUnit::TestFactoryRegistry::getRegistry().makeTest()); + return EXIT_SUCCESS; + } + case 'v': verbose = 1; break; + case 't': if (optarg) tests = optarg; break; + default: + { + const char* prog = argv[0]; + if (const char* ptr = ::strrchr(prog, '/')) prog = ptr + 1; + std::cerr << +"Usage: " << prog << " [options]\n" << +"Which: runs OpenVDB library unit tests\n" << +"Options:\n" << +" -l list all available tests\n" << +" -t test specific suite or test to run, e.g., \"-t TestGrid\"\n" << +" or \"-t TestGrid::testGetGrid\" (default: run all tests)\n" << +" -v verbose output\n"; +#ifdef OPENVDB_USE_LOG4CPLUS + std::cerr << "\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 + return EXIT_FAILURE; + } + } + } + + try { + CppUnit::TestFactoryRegistry& registry = + CppUnit::TestFactoryRegistry::getRegistry(); + + CppUnit::TestRunner runner; + runner.addTest(registry.makeTest()); + + CppUnit::TestResult controller; + + CppUnit::TestResultCollector result; + controller.addListener(&result); + + CppUnit::TextTestProgressListener progress; + CppUnit::BriefTestProgressListener vProgress; + if (verbose) { + controller.addListener(&vProgress); + } else { + controller.addListener(&progress); + } + + runner.run(controller, tests); + + CppUnit::CompilerOutputter outputter(&result, std::cerr); + outputter.write(); + + return result.wasSuccessful() ? EXIT_SUCCESS : EXIT_FAILURE; + + } catch (std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} +#endif + + +int +main(int argc, char *argv[]) +{ +#ifdef DWA_OPENVDB + + // Disable logging by default ("-quiet") unless overridden + // with "-debug" or "-info". + bool quiet = false; + { + std::vector args(argv, argv + argc); + int numArgs = int(args.size()); + logging_base::Config config(numArgs, &args[0]); + quiet = (!config.useInfo() && !config.useDebug()); + } + char* quietArg = "-quiet"; + std::vector args(argv, argv + argc); + if (quiet) args.insert(++args.begin(), quietArg); + int numArgs = int(args.size()); + + logging_base::Config config(numArgs, &args[0]); + logging_base::configure(config); + + return pdevunit::run(numArgs, &args[0]); + +#else // ifndef DWA_OPENVDB + +#ifndef OPENVDB_USE_LOG4CPLUS + return run(argc, argv); +#else + log4cplus::BasicConfigurator::doConfigure(); + + std::vector args; + args.push_back(argv[0]); + + log4cplus::Logger log = log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("main")); + log.setLogLevel(log4cplus::FATAL_LOG_LEVEL); + for (int i = 1; i < argc; ++i) { + char* arg = argv[i]; + if (std::string("-info") == arg) log.setLogLevel(log4cplus::INFO_LOG_LEVEL); + else if (std::string("-warn") == arg) log.setLogLevel(log4cplus::WARN_LOG_LEVEL); + else if (std::string("-error") == arg) log.setLogLevel(log4cplus::ERROR_LOG_LEVEL); + else if (std::string("-debug") == arg) log.setLogLevel(log4cplus::DEBUG_LOG_LEVEL); + else args.push_back(arg); + } + + return run(int(args.size()), &args[0]); +#endif // OPENVDB_USE_LOG4CPLUS + +#endif // DWA_OPENVDB +} + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/unittest/util.h b/openvdb_2_3_0_library/openvdb/unittest/util.h new file mode 100755 index 0000000..12b2286 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/unittest/util.h @@ -0,0 +1,156 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_UNITTEST_UTIL_HAS_BEEN_INCLUDED +#define OPENVDB_UNITTEST_UTIL_HAS_BEEN_INCLUDED + +#include +#include +#include + +namespace unittest_util { + +enum SphereMode { SPHERE_DENSE, SPHERE_DENSE_NARROW_BAND, SPHERE_SPARSE_NARROW_BAND }; + +/// @brief Generates the signed distance to a sphere located at @a center +/// and with a specified @a radius (both in world coordinates). Only voxels +/// in the domain [0,0,0] -> @a dim are considered. Also note that the +/// level set is either dense, dense narrow-band or sparse narrow-band. +/// +/// @note This method is VERY SLOW and should only be used for debugging purposes! +/// However it works for any transform and even with open level sets. +/// A faster approch for closed narrow band generation is to only set voxels +/// sparsely and then use grid::signedFloodFill to defined the sign +/// of the background values and tiles! This is implemented in openvdb/tools/LevelSetSphere.h +template +inline void +makeSphere(const openvdb::Coord& dim, const openvdb::Vec3f& center, float radius, + GridType& grid, SphereMode mode) +{ + typedef typename GridType::ValueType ValueT; + const ValueT + zero = openvdb::zeroVal(), + outside = grid.background(), + inside = -outside; + + typename GridType::Accessor acc = grid.getAccessor(); + openvdb::Coord xyz; + for (xyz[0]=0; xyz[0] +inline void +makeSphere(const openvdb::Coord& dim, const openvdb::Vec3f& center, + float radius, openvdb::BoolGrid& grid, SphereMode) +{ + openvdb::BoolGrid::Accessor acc = grid.getAccessor(); + openvdb::Coord xyz; + for (xyz[0]=0; xyz[0] +inline void +makeSphere(const openvdb::Coord& dim, const openvdb::Vec3f& center, float radius, + GridType &grid, float dx, SphereMode mode) +{ + grid.setTransform(openvdb::math::Transform::createLinearTransform(/*voxel size=*/dx)); + makeSphere(dim, center, radius, grid, mode); +} + +// @todo makePlane + + +//////////////////////////////////////// + + +class CpuTimer +{ +public: + CpuTimer() {} + + void start(const std::string& msg = std::string("Task")) + { + std::cerr << msg << " ... "; + mT0 = tbb::tick_count::now(); + } + + void stop() const + { + tbb::tick_count t1 = tbb::tick_count::now(); + std::ostringstream ostr; + ostr << "completed in " << std::setprecision(3) << (t1 - mT0).seconds() << " sec\n"; + std::cerr << ostr.str(); + } + +private: + tbb::tick_count mT0; +};// CpuTimer + +} // namespace unittest_util + +#endif // OPENVDB_UNITTEST_UTIL_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/util/Formats.cc b/openvdb_2_3_0_library/openvdb/util/Formats.cc new file mode 100755 index 0000000..3d7b4cb --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/util/Formats.cc @@ -0,0 +1,121 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Formats.h" + +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace util { + +int +printBytes(std::ostream& os, uint64_t bytes, + const std::string& head, const std::string& tail, + bool exact, int width, int precision) +{ + const uint64_t one = 1; + int group = 0; + + // Write to a string stream so that I/O manipulators like + // std::setprecision() don't alter the output stream. + std::ostringstream ostr; + ostr << head; + ostr << std::setprecision(precision) << std::setiosflags(std::ios::fixed); + if (bytes >> 40) { + ostr << std::setw(width) << (bytes / double(one << 40)) << " TB"; + group = 4; + } else if (bytes >> 30) { + ostr << std::setw(width) << (bytes / double(one << 30)) << " GB"; + group = 3; + } else if (bytes >> 20) { + ostr << std::setw(width) << (bytes / double(one << 20)) << " MB"; + group = 2; + } else if (bytes >> 10) { + ostr << std::setw(width) << (bytes / double(one << 10)) << " KB"; + group = 1; + } else { + ostr << std::setw(width) << bytes << " Bytes"; + } + if (exact && group) ostr << " (" << bytes << " Bytes)"; + ostr << tail; + + os << ostr.str(); + + return group; +} + + +int +printNumber(std::ostream& os, uint64_t number, + const std::string& head, const std::string& tail, + bool exact, int width, int precision) +{ + int group = 0; + + // Write to a string stream so that I/O manipulators like + // std::setprecision() don't alter the output stream. + std::ostringstream ostr; + ostr << head; + ostr << std::setprecision(precision) << std::setiosflags(std::ios::fixed); + if (number / UINT64_C(1000000000000)) { + ostr << std::setw(width) << (number / 1000000000000.0) << " trillion"; + group = 4; + } else if (number / UINT64_C(1000000000)) { + ostr << std::setw(width) << (number / 1000000000.0) << " billion"; + group = 3; + } else if (number / UINT64_C(1000000)) { + ostr << std::setw(width) << (number / 1000000.0) << " million"; + group = 2; + } else if (number / UINT64_C(1000)) { + ostr << std::setw(width) << (number / 1000.0) << " thousand"; + group = 1; + } else { + ostr << std::setw(width) << number; + } + if (exact && group) ostr << " (" << number << ")"; + ostr << tail; + + os << ostr.str(); + + return group; +} + +} // namespace util +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/util/Formats.h b/openvdb_2_3_0_library/openvdb/util/Formats.h new file mode 100755 index 0000000..9a685c3 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/util/Formats.h @@ -0,0 +1,140 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @author Ken Museth +/// +/// @file Formats.h +/// +/// @brief Utility routines to output nicely-formatted numeric values + + +#ifndef OPENVDB_UTIL_FORMATS_HAS_BEEN_INCLUDED +#define OPENVDB_UTIL_FORMATS_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace util { + +/// Output a byte count with the correct binary suffix (KB, MB, GB or TB). +/// @param os the output stream +/// @param bytes the byte count to be output +/// @param head a string to be output before the numeric text +/// @param tail a string to be output after the numeric text +/// @param exact if true, also output the unmodified count, e.g., "4.6 KB (4620 Bytes)" +/// @param width a fixed width for the numeric text +/// @param precision the number of digits after the decimal point +/// @return 0, 1, 2, 3 or 4, denoting the order of magnitude of the count. +OPENVDB_API int +printBytes(std::ostream& os, uint64_t bytes, + const std::string& head = "", + const std::string& tail = "\n", + bool exact = false, int width = 8, int precision = 3); + +/// Output a number with the correct SI suffix (thousand, million, billion or trillion) +/// @param os the output stream +/// @param number the number to be output +/// @param head a string to be output before the numeric text +/// @param tail a string to be output after the numeric text +/// @param exact if true, also output the unmodified count, e.g., "4.6 Thousand (4620)" +/// @param width a fixed width for the numeric text +/// @param precision the number of digits after the decimal point +/// @return 0, 1, 2, 3 or 4, denoting the order of magnitude of the number. +OPENVDB_API int +printNumber(std::ostream& os, uint64_t number, + const std::string& head = "", + const std::string& tail = "\n", + bool exact = true, int width = 8, int precision = 3); + + +//////////////////////////////////////// + + +/// @brief I/O manipulator that formats integer values with thousands separators +template +class FormattedInt +{ +public: + static char sep() { return ','; } + + FormattedInt(IntT n): mInt(n) {} + + std::ostream& put(std::ostream& os) const + { + // Convert the integer to a string. + std::ostringstream ostr; + ostr << mInt; + std::string s = ostr.str(); + // Prefix the string with spaces if its length is not a multiple of three. + size_t padding = (s.size() % 3) ? 3 - (s.size() % 3) : 0; + s = std::string(padding, ' ') + s; + // Construct a new string in which groups of three digits are followed + // by a separator character. + ostr.str(""); + for (size_t i = 0, N = s.size(); i < N; ) { + ostr << s[i]; + ++i; + if (i >= padding && i % 3 == 0 && i < s.size()) { + ostr << sep(); + } + } + // Remove any padding that was added and output the string. + s = ostr.str(); + os << s.substr(padding, s.size()); + return os; + } + +private: + IntT mInt; +}; + +template +std::ostream& operator<<(std::ostream& os, const FormattedInt& n) { return n.put(os); } + +/// @return an I/O manipulator that formats the given integer value for output to a stream. +template +FormattedInt formattedInt(IntT n) { return FormattedInt(n); } + +} // namespace util +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_UTIL_FORMATS_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/util/MapsUtil.h b/openvdb_2_3_0_library/openvdb/util/MapsUtil.h new file mode 100755 index 0000000..e8faa38 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/util/MapsUtil.h @@ -0,0 +1,321 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file MapsUtil.h + +#ifndef OPENVDB_UTIL_MAPSUTIL_HAS_BEEN_INCLUDED +#define OPENVDB_UTIL_MAPSUTIL_HAS_BEEN_INCLUDED + +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace util { + +// Utility methods for calculating bounding boxes + +/// @brief Calculate an axis-aligned bounding box in the given map's domain +/// (e.g., index space) from an axis-aligned bounding box in its range +/// (e.g., world space) +template +inline void +calculateBounds(const MapType& map, const BBoxd& in, BBoxd& out) +{ + const Vec3d& min = in.min(); + const Vec3d& max = in.max(); + + // the pre-image of the 8 corners of the box + Vec3d corners[8]; + corners[0] = in.min();; + corners[1] = Vec3d(min(0), min(1), min(2)); + corners[2] = Vec3d(max(0), max(1), min(2)); + corners[3] = Vec3d(min(0), max(1), min(2)); + corners[4] = Vec3d(min(0), min(1), max(2)); + corners[5] = Vec3d(max(0), min(1), max(2)); + corners[6] = max; + corners[7] = Vec3d(min(0), max(1), max(2)); + + Vec3d pre_image; + Vec3d& out_min = out.min(); + Vec3d& out_max = out.max(); + out_min = map.applyInverseMap(corners[0]); + out_max = min; + for (int i = 1; i < 8; ++i) { + pre_image = map.applyInverseMap(corners[i]); + for (int j = 0; j < 3; ++j) { + out_min(j) = std::min( out_min(j), pre_image(j)); + out_max(j) = std::max( out_max(j), pre_image(j)); + } + } +} + + +/// @brief Calculate an axis-aligned bounding box in the given map's domain +/// from a spherical bounding box in its range. +template +inline void +calculateBounds(const MapType& map, const Vec3d& center, const Real radius, BBoxd& out) +{ + // On return, out gives a bounding box in continuous index space + // that encloses the sphere. + // + // the image of a sphere under the inverse of the linearMap will be an ellipsoid. + + if (math::is_linear::value) { + // I want to find extrema for three functions f(x', y', z') = x', or = y', or = z' + // with the constraint that g = (x-xo)^2 + (y-yo)^2 + (z-zo)^2 = r^2. + // Where the point x,y,z is the image of x',y',z' + // Solve: \lambda Grad(g) = Grad(f) and g = r^2. + // Note: here (x,y,z) is the image of (x',y',z'), and the gradient + // is w.r.t the (') space. + // + // This can be solved exactly: e_a^T (x' -xo') =\pm r\sqrt(e_a^T J^(-1)J^(-T)e_a) + // where e_a is one of the three unit vectors. - djh. + + /// find the image of the center of the sphere + Vec3d center_pre_image = map.applyInverseMap(center); + + std::vector coordinate_units; + coordinate_units.push_back(Vec3d(1,0,0)); + coordinate_units.push_back(Vec3d(0,1,0)); + coordinate_units.push_back(Vec3d(0,0,1)); + + Vec3d& out_min = out.min(); + Vec3d& out_max = out.max(); + for (int direction = 0; direction < 3; ++direction) { + Vec3d temp = map.applyIJT(coordinate_units[direction]); + double offset = + radius * sqrt(temp.x()*temp.x() + temp.y()*temp.y() + temp.z()*temp.z()); + out_min(direction) = center_pre_image(direction) - offset; + out_max(direction) = center_pre_image(direction) + offset; + } + + } else { + // This is some unknown map type. In this case, we form an axis-aligned + // bounding box for the sphere in world space and find the pre-images of + // the corners in index space. From these corners we compute an axis-aligned + // bounding box in index space. + BBoxd bounding_box(center - radius*Vec3d(1,1,1), center + radius*Vec3d(1,1,1)); + calculateBounds(map, bounding_box, out); + } +} + + +namespace { // anonymous namespace for this helper function + +/// @brief Find the intersection of a line passing through the point +/// \f$ (x=0, z=-1/g)\f$ with the circle \f$ (x-xo)^2 + (z-zo)^2 = r^2 \f$ +/// at a point tangent to the circle. +/// @return 0 if the focal point (0, -1/g) is inside the circle, +/// 1 if the focal point touches the circle, or 2 when both points are found. +inline int +findTangentPoints(const double g, const double xo, const double zo, + const double r, double& xp, double& zp, double& xm, double& zm) +{ + double x2 = xo * xo; + double r2 = r * r; + double xd = g * xo; + double xd2 = xd*xd; + double zd = g * zo + 1.; + double zd2 = zd*zd; + double rd2 = r2*g*g; + + double distA = xd2 + zd2; + double distB = distA - rd2; + + if (distB > 0) { + double discriminate = sqrt(distB); + + xp = xo - xo*rd2/distA + r * zd *discriminate / distA; + xm = xo - xo*rd2/distA - r * zd *discriminate / distA; + + zp = (zo*zd2 + zd*g*(x2 - r2) - xo*xo*g - r*xd*discriminate) / distA; + zm = (zo*zd2 + zd*g*(x2 - r2) - xo*xo*g + r*xd*discriminate) / distA; + + return 2; + + } if (0 >= distB && distB >= -1e-9) { + // the circle touches the focal point (x=0, z = -1/g) + xp = 0; xm = 0; + zp = -1/g; zm = -1/g; + + return 1; + } + + return 0; +} + +} // end anonymous namespace + + +/// @brief Calculate an axis-aligned bounding box in index space +/// from a spherical bounding box in world space. +/// @note This specialization is optimized for a frustum map +template<> +inline void +calculateBounds(const math::NonlinearFrustumMap& frustum, + const Vec3d& center, const Real radius, BBoxd& out) +{ + // The frustum is a nonlinear map followed by a uniform scale, rotation, translation. + // First we invert the translation, rotation and scale to find the spherical pre-image + // of the sphere in "local" coordinates where the frustum is aligned with the near plane + // on the z=0 plane and the "camera" is located at (x=0, y=0, z=-1/g). + + // check that the internal map has no shear. + const math::AffineMap& secondMap = frustum.secondMap(); + // test if the linear part has shear or non-uniform scaling + if (!frustum.hasSimpleAffine()) { + + // In this case, we form an axis-aligned bounding box for sphere in world space + // and find the pre_images of the corners in voxel space. From these corners we + // compute an axis-algined bounding box in voxel-spae + BBoxd bounding_box(center - radius*Vec3d(1,1,1), center + radius*Vec3d(1,1,1)); + calculateBounds(frustum, bounding_box, out); + return; + } + + // for convenience + Vec3d& out_min = out.min(); + Vec3d& out_max = out.max(); + + Vec3d centerLS = secondMap.applyInverseMap(center); + Vec3d voxelSize = secondMap.voxelSize(); + + // all the voxels have the same size since we know this is a simple affine map + double radiusLS = radius / voxelSize(0); + + double gamma = frustum.getGamma(); + double xp; + double zp; + double xm; + double zm; + int soln_number; + + // the bounding box in index space for the points in the frustum + const BBoxd& bbox = frustum.getBBox(); + // initialize min and max + const double x_min = bbox.min().x(); + const double y_min = bbox.min().y(); + const double z_min = bbox.min().z(); + + const double x_max = bbox.max().x(); + const double y_max = bbox.max().y(); + const double z_max = bbox.max().z(); + + out_min.x() = x_min; + out_max.x() = x_max; + out_min.y() = y_min; + out_max.y() = y_max; + + Vec3d extreme; + Vec3d extreme2; + Vec3d pre_image; + // find the x-range + soln_number = findTangentPoints(gamma, centerLS.x(), centerLS.z(), radiusLS, xp, zp, xm, zm); + if (soln_number == 2) { + extreme.x() = xp; + extreme.y() = centerLS.y(); + extreme.z() = zp; + + // location in world space of the tangent point + extreme2 = secondMap.applyMap(extreme); + // convert back to voxel space + pre_image = frustum.applyInverseMap(extreme2); + out_max.x() = std::max(x_min, std::min(x_max, pre_image.x())); + + 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-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/util/Name.h b/openvdb_2_3_0_library/openvdb/util/Name.h new file mode 100755 index 0000000..51cf919 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/util/Name.h @@ -0,0 +1,72 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_UTIL_NAME_HAS_BEEN_INCLUDED +#define OPENVDB_UTIL_NAME_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +typedef std::string Name; + +inline Name +readString(std::istream& is) +{ + uint32_t size; + is.read(reinterpret_cast(&size), sizeof(uint32_t)); + std::string buffer(size, ' '); + is.read(&buffer[0], size); + return buffer; +} + + +inline void +writeString(std::ostream& os, const Name& name) +{ + uint32_t size = uint32_t(name.size()); + os.write(reinterpret_cast(&size), sizeof(uint32_t)); + os.write(&name[0], size); +} + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_UTIL_NAME_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/util/NodeMasks.h b/openvdb_2_3_0_library/openvdb/util/NodeMasks.h new file mode 100755 index 0000000..88862f7 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/util/NodeMasks.h @@ -0,0 +1,1278 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @author Ken Museth +/// +/// @file NodeMasks.h + +#ifndef OPENVDB_UTIL_NODEMASKS_HAS_BEEN_INCLUDED +#define OPENVDB_UTIL_NODEMASKS_HAS_BEEN_INCLUDED + +#include +#include +#include // for cout +#include +//#include +//#include // for ffs + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace util { + +/// Return the number of on bits in the given 8-bit value. +inline Index32 +CountOn(Byte v) +{ + // Simple LUT: + static const Byte numBits[256] = { +# define B2(n) n, n+1, n+1, n+2 +# define B4(n) B2(n), B2(n+1), B2(n+1), B2(n+2) +# define B6(n) B4(n), B4(n+1), B4(n+1), B4(n+2) + B6(0), B6(1), B6(1), B6(2) + }; + return numBits[v]; + + // Sequentially clear least significant bits + //Index32 c; + //for (c = 0; v; c++) v &= v - 0x01U; + //return c; + + // This version is only fast on CPUs with fast "%" and "*" operations + //return (v * UINT64_C(0x200040008001) & UINT64_C(0x111111111111111)) % 0xF; +} +/// Return the number of off bits in the given 8-bit value. +inline Index32 CountOff(Byte v) { return CountOn(~v); } + +/// Return the number of on bits in the given 32-bit value. +inline Index32 +CountOn(Index32 v) +{ + v = v - ((v >> 1) & 0x55555555U); + v = (v & 0x33333333U) + ((v >> 2) & 0x33333333U); + return (((v + (v >> 4)) & 0xF0F0F0FU) * 0x1010101U) >> 24; +} + +/// Return the number of off bits in the given 32-bit value. +inline Index32 CountOff(Index32 v) { return CountOn(~v); } + +/// Return the number of on bits in the given 64-bit value. +inline Index32 +CountOn(Index64 v) +{ + v = v - ((v >> 1) & UINT64_C(0x5555555555555555)); + v = (v & UINT64_C(0x3333333333333333)) + ((v >> 2) & UINT64_C(0x3333333333333333)); + return (((v + (v >> 4)) & UINT64_C(0xF0F0F0F0F0F0F0F)) * UINT64_C(0x101010101010101)) >> 56; +} + +/// Return the number of off bits in the given 64-bit value. +inline Index32 CountOff(Index64 v) { return CountOn(~v); } + +/// Return the least significant on bit of the given 8-bit value. +inline Index32 +FindLowestOn(Byte v) +{ + assert(v); + static const Byte DeBruijn[8] = {0, 1, 6, 2, 7, 5, 4, 3}; + return DeBruijn[Byte((v & -v) * 0x1DU) >> 5]; +} + +/// Return the least significant on bit of the given 32-bit value. +inline Index32 +FindLowestOn(Index32 v) +{ + assert(v); + //return ffs(v); + static const Byte DeBruijn[32] = { + 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 + }; + return DeBruijn[Index32((v & -v) * 0x077CB531U) >> 27]; +} + +/// Return the least significant on bit of the given 64-bit value. +inline Index32 +FindLowestOn(Index64 v) +{ + assert(v); + //return ffsll(v); + static const Byte DeBruijn[64] = { + 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, + 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, + 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, + 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12, + }; + return DeBruijn[Index64((v & -v) * UINT64_C(0x022FDD63CC95386D)) >> 58]; +} + +/// Return the most significant on bit of the given 32-bit value. +inline Index32 +FindHighestOn(Index32 v) +{ + static const Byte DeBruijn[32] = { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 + }; + v |= v >> 1; // first round down to one less than a power of 2 + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return DeBruijn[Index32(v * 0x07C4ACDDU) >> 27]; +} + + +//////////////////////////////////////// + + +/// Base class for the bit mask iterators +template +class BaseMaskIterator +{ +protected: + Index32 mPos;//bit position + const NodeMask* mParent;//this iterator can't change the parent_mask! +public: + BaseMaskIterator() : mPos(NodeMask::SIZE), mParent(NULL) {} + BaseMaskIterator(Index32 pos,const NodeMask *parent) : mPos(pos), mParent(parent) + { + assert( (parent==NULL && pos==0 ) || (parent!=NULL && pos<=NodeMask::SIZE) ); + } + bool operator==(const BaseMaskIterator &iter) const {return mPos == iter.mPos;} + bool operator!=(const BaseMaskIterator &iter) const {return mPos != iter.mPos;} + bool operator< (const BaseMaskIterator &iter) const {return mPos < iter.mPos;} + 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: + typedef BaseMaskIterator BaseType; + using BaseType::mPos;//bit position; + using BaseType::mParent;//this iterator can't change the parent_mask! +public: + OnMaskIterator() : BaseType() {} + OnMaskIterator(Index32 pos,const NodeMask *parent) : BaseType(pos,parent) {} + void increment() + { + assert(mParent != NULL); + mPos = mParent->findNextOn(mPos+1); + assert(mPos <= NodeMask::SIZE); + } + void increment(Index n) { while(n-- && this->next()) ; } + bool next() + { + this->increment(); + return this->test(); + } + bool operator*() const {return true;} + OnMaskIterator& operator++() + { + this->increment(); + return *this; + } +}; // class OnMaskIterator + + +template +class OffMaskIterator: public BaseMaskIterator +{ +private: + typedef BaseMaskIterator BaseType; + using BaseType::mPos;//bit position; + using BaseType::mParent;//this iterator can't change the parent_mask! +public: + OffMaskIterator() : BaseType() {} + OffMaskIterator(Index32 pos,const NodeMask *parent) : BaseType(pos,parent) {} + void increment() + { + assert(mParent != NULL); + mPos=mParent->findNextOff(mPos+1); + assert(mPos <= NodeMask::SIZE); + } + void increment(Index n) { while(n-- && this->next()) ; } + bool next() + { + this->increment(); + return this->test(); + } + bool operator*() const {return false;} + OffMaskIterator& operator++() + { + this->increment(); + return *this; + } +}; // class OffMaskIterator + + +template +class DenseMaskIterator: public BaseMaskIterator +{ +private: + typedef BaseMaskIterator BaseType; + using BaseType::mPos;//bit position; + using BaseType::mParent;//this iterator can't change the parent_mask! + +public: + DenseMaskIterator() : BaseType() {} + DenseMaskIterator(Index32 pos,const NodeMask *parent) : BaseType(pos,parent) {} + void increment() + { + assert(mParent != NULL); + mPos += 1;//careful - the increment might go beyond the end + assert(mPos<= NodeMask::SIZE); + } + void increment(Index n) { while(n-- && this->next()) ; } + bool next() + { + this->increment(); + return this->test(); + } + bool operator*() const {return mParent->isOn(mPos);} + DenseMaskIterator& operator++() + { + this->increment(); + return *this; + } +}; // class DenseMaskIterator + + +/// @brief Bit mask for the internal and leaf nodes of VDB. This +/// is a 64-bit implementation. +/// +/// @note A template specialization for Log2Dim=1 and Log2Dim=2 are +/// given below. +template +class NodeMask +{ +public: + BOOST_STATIC_ASSERT( Log2Dim>2 ); + + static const Index32 LOG2DIM = Log2Dim; + static const Index32 DIM = 1<> 6;// 2^6=64 + typedef Index64 Word; + +private: + + // The bits are represented as a linear array of Words, and the + // size of a Word is 32 or 64 bits depending on the platform. + // The BIT_MASK is defined as the number of bits in a Word - 1 + //static const Index32 BIT_MASK = sizeof(void*) == 8 ? 63 : 31; + //static const Index32 LOG2WORD = BIT_MASK == 63 ? 6 : 5; + //static const Index32 WORD_COUNT = SIZE >> LOG2WORD; + //typedef boost::mpl::if_c::type Word; + + Word mWords[WORD_COUNT];//only member data! + +public: + /// Default constructor sets all bits off + NodeMask() { this->setOff(); } + /// All bits are set to the specified state + NodeMask(bool on) { this->set(on); } + /// Copy constructor + NodeMask(const NodeMask &other) { *this = other; } + /// Destructor + ~NodeMask() {} + /// Assignment operator + 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; + } + + typedef OnMaskIterator OnIterator; + typedef OffMaskIterator OffIterator; + typedef DenseMaskIterator DenseIterator; + + OnIterator beginOn() const { return OnIterator(this->findFirstOn(),this); } + OnIterator endOn() const { return OnIterator(SIZE,this); } + OffIterator beginOff() const { return OffIterator(this->findFirstOff(),this); } + OffIterator endOff() const { return OffIterator(SIZE,this); } + DenseIterator beginDense() const { return DenseIterator(0,this); } + DenseIterator endDense() const { return DenseIterator(SIZE,this); } + + bool operator == (const NodeMask &other) const + { + int n = WORD_COUNT; + for (const Word *w1=mWords, *w2=other.mWords; n-- && *w1++ == *w2++;) ; + return n == -1; + } + + bool operator != (const NodeMask &other) const { return !(*this == other); } + + // + // Bitwise logical operations + // + NodeMask operator!() const { NodeMask m(*this); m.toggle(); return m; } + const NodeMask& operator&=(const NodeMask& other) + { + Index32 n = WORD_COUNT; + const Word* w2 = other.mWords; + for ( Word* w1 = mWords; n--; ++w1, ++w2) *w1 &= *w2; + return *this; + } + const NodeMask& operator|=(const NodeMask& other) + { + Index32 n = WORD_COUNT; + const Word* w2 = other.mWords; + for ( Word* w1 = mWords; n--; ++w1, ++w2) *w1 |= *w2; + return *this; + } + const NodeMask& operator^=(const NodeMask& other) + { + Index32 n = WORD_COUNT; + const Word* w2 = other.mWords; + for ( Word* w1 = mWords; n--; ++w1, ++w2) *w1 ^= *w2; + return *this; + } + NodeMask operator&(const NodeMask& other) const { NodeMask m(*this); m &= other; return m; } + NodeMask operator|(const NodeMask& other) const { NodeMask m(*this); m |= other; return m; } + NodeMask operator^(const NodeMask& other) const { NodeMask m(*this); m ^= other; return m; } + /// Return the byte size of this NodeMask + static Index32 memUsage() { return WORD_COUNT*sizeof(Word); } + /// Return the total number of on bits + Index32 countOn() const + { + Index32 sum = 0, n = WORD_COUNT; + for (const Word* w = mWords; n--; ++w) sum += CountOn(*w); + return sum; + } + /// Return the total number of on bits + Index32 countOff() const { return SIZE-this->countOn(); } + /// Set the nth bit on + void setOn(Index32 n) { + assert( (n >> 6) < WORD_COUNT ); + mWords[n >> 6] |= Word(1) << (n & 63); + } + /// Set the nth bit off + void setOff(Index32 n) { + assert( (n >> 6) < WORD_COUNT ); + mWords[n >> 6] &= ~(Word(1) << (n & 63)); + } + /// Set the nth bit to the specified state + void set(Index32 n, bool On) { On ? this->setOn(n) : this->setOff(n); } + /// Set all bits to the specified state + void set(bool on) + { + const Word state = on ? ~Word(0) : Word(0); + Index32 n = WORD_COUNT; + for (Word* w = mWords; n--; ++w) *w = state; + } + /// Set all bits on + void setOn() + { + Index32 n = WORD_COUNT; + for (Word* w = mWords; n--; ++w) *w = ~Word(0); + } + /// Set all bits off + void setOff() + { + Index32 n = WORD_COUNT; + for (Word* w = mWords; n--; ++w) *w = Word(0); + } + /// Toggle the state of the nth bit + void toggle(Index32 n) { + assert( (n >> 6) < WORD_COUNT ); + mWords[n >> 6] ^= 1 << (n & 63); + } + /// Toggle the state of all bits in the mask + void toggle() + { + Index32 n = WORD_COUNT; + for (Word* w = mWords; n--; ++w) *w = ~*w; + } + /// Set the first bit on + void setFirstOn() { this->setOn(0); } + /// Set the last bit on + void setLastOn() { this->setOn(SIZE-1); } + /// Set the first bit off + void setFirstOff() { this->setOff(0); } + /// Set the last bit off + void setLastOff() { this->setOff(SIZE-1); } + /// Return @c true if the nth bit is on + bool isOn(Index32 n) const + { + assert( (n >> 6) < WORD_COUNT ); + return 0 != (mWords[n >> 6] & (Word(1) << (n & 63))); + } + /// Return @c true if the nth bit is off + bool isOff(Index32 n) const {return !this->isOn(n); } + /// Return @c true if all the bits are on + bool isOn() const + { + int n = WORD_COUNT; + for (const Word *w = mWords; n-- && *w++ == ~Word(0);) ; + return n == -1; + } + /// Return @c true if all the bits are off + bool isOff() const + { + int n = WORD_COUNT; + for (const Word *w = mWords; n-- && *w++ == Word(0);) ; + return n == -1; + } + Index32 findFirstOn() const + { + Index32 n = 0; + const Word* w = mWords; + for (; nnth word of the bit mask, for a word of arbitrary size. + template + WordT getWord(Index n) const + { + assert(n*8*sizeof(WordT) < SIZE); + return reinterpret_cast(mWords)[n]; + } + template + WordT& getWord(Index n) + { + assert(n*8*sizeof(WordT) < SIZE); + return reinterpret_cast(mWords)[n]; + } + //@} + + void save(std::ostream& os) const + { + os.write(reinterpret_cast(mWords), this->memUsage()); + } + void load(std::istream& is) { + is.read(reinterpret_cast(mWords), this->memUsage()); + } + /// @brief simple print method for debugging + void printInfo(std::ostream& os=std::cout) const + { + os << "NodeMask: Dim=" << DIM << " Log2Dim=" << Log2Dim + << " Bit count=" << SIZE << " word count=" << WORD_COUNT << std::endl; + } + void printBits(std::ostream& os=std::cout, Index32 max_out=80u) const + { + const Index32 n=(SIZE>max_out ? max_out : SIZE); + for (Index32 i=0; i < n; ++i) { + if ( !(i & 63) ) + os << "||"; + else if ( !(i%8) ) + os << "|"; + os << this->isOn(i); + } + os << "|" << std::endl; + } + void printAll(std::ostream& os=std::cout, Index32 max_out=80u) const + { + this->printInfo(os); + this->printBits(os, max_out); + } + + Index32 findNextOn(Index32 start) const + { + Index32 n = start >> 6;//initiate + if (n >= WORD_COUNT) return SIZE; // check for out of bounds + Index32 m = start & 63; + Word b = mWords[n]; + if (b & (Word(1) << m)) return start;//simpel case: start is on + b &= ~Word(0) << m;// mask out lower bits + while(!b && ++n> 6;//initiate + if (n >= WORD_COUNT) return SIZE; // check for out of bounds + Index32 m = start & 63; + Word b = ~mWords[n]; + if (b & (Word(1) << m)) return start;//simpel case: start is on + b &= ~Word(0) << m;// mask out lower bits + while(!b && ++n +class NodeMask<1> +{ +public: + + static const Index32 LOG2DIM = 1; + static const Index32 DIM = 2; + static const Index32 SIZE = 8; + static const Index32 WORD_COUNT = 1; + typedef Byte Word; + +private: + + Byte mByte;//only member data! + +public: + /// Default constructor sets all bits off + NodeMask() : mByte(0x00U) {} + /// All bits are set to the specified state + NodeMask(bool on) : mByte(on ? 0xFFU : 0x00U) {} + /// Copy constructor + NodeMask(const NodeMask &other) : mByte(other.mByte) {} + /// Destructor + ~NodeMask() {} + /// Assignment operator + void operator = (const NodeMask &other) { mByte = other.mByte; } + + typedef OnMaskIterator OnIterator; + typedef OffMaskIterator OffIterator; + typedef DenseMaskIterator DenseIterator; + + OnIterator beginOn() const { return OnIterator(this->findFirstOn(),this); } + OnIterator endOn() const { return OnIterator(SIZE,this); } + OffIterator beginOff() const { return OffIterator(this->findFirstOff(),this); } + OffIterator endOff() const { return OffIterator(SIZE,this); } + DenseIterator beginDense() const { return DenseIterator(0,this); } + DenseIterator endDense() const { return DenseIterator(SIZE,this); } + + bool operator == (const NodeMask &other) const { return mByte == other.mByte; } + + bool operator != (const NodeMask &other) const {return mByte != other.mByte; } + + // + // Bitwise logical operations + // + NodeMask operator!() const { NodeMask m(*this); m.toggle(); return m; } + const NodeMask& operator&=(const NodeMask& other) + { + mByte &= other.mByte; + return *this; + } + const NodeMask& operator|=(const NodeMask& other) + { + mByte |= other.mByte; + return *this; + } + const NodeMask& operator^=(const NodeMask& other) + { + mByte ^= other.mByte; + return *this; + } + NodeMask operator&(const NodeMask& other) const { NodeMask m(*this); m &= other; return m; } + NodeMask operator|(const NodeMask& other) const { NodeMask m(*this); m |= other; return m; } + NodeMask operator^(const NodeMask& other) const { NodeMask m(*this); m ^= other; return m; } + /// Return the byte size of this NodeMask + static Index32 memUsage() { return 1; } + /// Return the total number of on bits + Index32 countOn() const { return CountOn(mByte); } + /// Return the total number of on bits + Index32 countOff() const { return CountOff(mByte); } + /// Set the nth bit on + void setOn(Index32 n) { + assert( n < 8 ); + mByte |= 0x01U << (n & 7); + } + /// Set the nth bit off + void setOff(Index32 n) { + assert( n < 8 ); + mByte &= ~(0x01U << (n & 7)); + } + /// Set the nth bit to the specified state + void set(Index32 n, bool On) { On ? this->setOn(n) : this->setOff(n); } + /// Set all bits to the specified state + void set(bool on) { mByte = on ? 0xFFU : 0x00U; } + /// Set all bits on + void setOn() { mByte = 0xFFU; } + /// Set all bits off + void setOff() { mByte = 0x00U; } + /// Toggle the state of the nth bit + void toggle(Index32 n) { + assert( n < 8 ); + mByte ^= 0x01U << (n & 7); + } + /// Toggle the state of all bits in the mask + void toggle() { mByte = ~mByte; } + /// Set the first bit on + void setFirstOn() { this->setOn(0); } + /// Set the last bit on + void setLastOn() { this->setOn(7); } + /// Set the first bit off + void setFirstOff() { this->setOff(0); } + /// Set the last bit off + void setLastOff() { this->setOff(7); } + /// Return true if the nth bit is on + bool isOn(Index32 n) const + { + assert( n < 8 ); + return mByte & (0x01U << (n & 7)); + } + /// Return true if the nth bit is off + bool isOff(Index32 n) const {return !this->isOn(n); } + /// Return true if all the bits are on + bool isOn() const { return mByte == 0xFFU; } + /// Return true if all the bits are off + bool isOff() const { return mByte == 0; } + Index32 findFirstOn() const { return mByte ? FindLowestOn(mByte) : 8; } + Index32 findFirstOff() const + { + const Byte b = ~mByte; + return b ? FindLowestOn(b) : 8; + } + /* + //@{ + /// Return the nth word of the bit mask, for a word of arbitrary size. + /// @note This version assumes WordT=Byte and n=0! + template + WordT getWord(Index n) const + { + BOOST_STATIC_ASSERT(sizeof(WordT) == sizeof(Byte)); + assert(n == 0); + return reinterpret_cast(mByte); + } + template + WordT& getWord(Index n) + { + BOOST_STATIC_ASSERT(sizeof(WordT) == sizeof(Byte)); + assert(n == 0); + return reinterpret_cast(mByte); + } + //@} + */ + void save(std::ostream& os) const + { + os.write(reinterpret_cast(&mByte), 1); + } + void load(std::istream& is) { is.read(reinterpret_cast(&mByte), 1); } + /// @brief simple print method for debugging + void printInfo(std::ostream& os=std::cout) const + { + os << "NodeMask: Dim=2, Log2Dim=1, Bit count=8, Word count=1"<isOn(i); + os << "||" << std::endl; + } + void printAll(std::ostream& os=std::cout) const + { + this->printInfo(os); + this->printBits(os); + } + + Index32 findNextOn(Index32 start) const + { + if (start>=8) return 8; + const Byte b = mByte & (0xFFU << start); + return b ? FindLowestOn(b) : 8; + } + + Index32 findNextOff(Index32 start) const + { + if (start>=8) return 8; + const Byte b = ~mByte & (0xFFU << start); + return b ? FindLowestOn(b) : 8; + } + +};// NodeMask<1> + + +/// @brief Template specialization of NodeMask for Log2Dim=2, i.e. 4^3 nodes +template<> +class NodeMask<2> +{ +public: + + static const Index32 LOG2DIM = 2; + static const Index32 DIM = 4; + static const Index32 SIZE = 64; + static const Index32 WORD_COUNT = 1; + typedef Index64 Word; + +private: + + Word mWord;//only member data! + +public: + /// Default constructor sets all bits off + NodeMask() : mWord(UINT64_C(0x00)) {} + /// All bits are set to the specified state + NodeMask(bool on) : mWord(on ? UINT64_C(0xFFFFFFFFFFFFFFFF) : UINT64_C(0x00)) {} + /// Copy constructor + NodeMask(const NodeMask &other) : mWord(other.mWord) {} + /// Destructor + ~NodeMask() {} + /// Assignment operator + void operator = (const NodeMask &other) { mWord = other.mWord; } + + typedef OnMaskIterator OnIterator; + typedef OffMaskIterator OffIterator; + typedef DenseMaskIterator DenseIterator; + + OnIterator beginOn() const { return OnIterator(this->findFirstOn(),this); } + OnIterator endOn() const { return OnIterator(SIZE,this); } + OffIterator beginOff() const { return OffIterator(this->findFirstOff(),this); } + OffIterator endOff() const { return OffIterator(SIZE,this); } + DenseIterator beginDense() const { return DenseIterator(0,this); } + DenseIterator endDense() const { return DenseIterator(SIZE,this); } + + bool operator == (const NodeMask &other) const { return mWord == other.mWord; } + + bool operator != (const NodeMask &other) const {return mWord != other.mWord; } + + // + // Bitwise logical operations + // + NodeMask operator!() const { NodeMask m(*this); m.toggle(); return m; } + const NodeMask& operator&=(const NodeMask& other) + { + mWord &= other.mWord; + return *this; + } + const NodeMask& operator|=(const NodeMask& other) + { + mWord |= other.mWord; + return *this; + } + const NodeMask& operator^=(const NodeMask& other) + { + mWord ^= other.mWord; + return *this; + } + NodeMask operator&(const NodeMask& other) const { NodeMask m(*this); m &= other; return m; } + NodeMask operator|(const NodeMask& other) const { NodeMask m(*this); m |= other; return m; } + NodeMask operator^(const NodeMask& other) const { NodeMask m(*this); m ^= other; return m; } + /// Return the byte size of this NodeMask + static Index32 memUsage() { return 8; } + /// Return the total number of on bits + Index32 countOn() const { return CountOn(mWord); } + /// Return the total number of on bits + Index32 countOff() const { return CountOff(mWord); } + /// Set the nth bit on + void setOn(Index32 n) { + assert( n < 64 ); + mWord |= UINT64_C(0x01) << (n & 63); + } + /// Set the nth bit off + void setOff(Index32 n) { + assert( n < 64 ); + mWord &= ~(UINT64_C(0x01) << (n & 63)); + } + /// Set the nth bit to the specified state + void set(Index32 n, bool On) { On ? this->setOn(n) : this->setOff(n); } + /// Set all bits to the specified state + void set(bool on) { mWord = on ? UINT64_C(0xFFFFFFFFFFFFFFFF) : UINT64_C(0x00); } + /// Set all bits on + void setOn() { mWord = UINT64_C(0xFFFFFFFFFFFFFFFF); } + /// Set all bits off + void setOff() { mWord = UINT64_C(0x00); } + /// Toggle the state of the nth bit + void toggle(Index32 n) { + assert( n < 64 ); + mWord ^= UINT64_C(0x01) << (n & 63); + } + /// Toggle the state of all bits in the mask + void toggle() { mWord = ~mWord; } + /// Set the first bit on + void setFirstOn() { this->setOn(0); } + /// Set the last bit on + void setLastOn() { this->setOn(63); } + /// Set the first bit off + void setFirstOff() { this->setOff(0); } + /// Set the last bit off + void setLastOff() { this->setOff(63); } + /// Return true if the nth bit is on + bool isOn(Index32 n) const + { + assert( n < 64 ); + return 0 != (mWord & (UINT64_C(0x01) << (n & 63))); + } + /// Return true if the nth bit is off + bool isOff(Index32 n) const {return !this->isOn(n); } + /// Return true if all the bits are on + bool isOn() const { return mWord == UINT64_C(0xFFFFFFFFFFFFFFFF); } + /// Return true if all the bits are off + bool isOff() const { return mWord == 0; } + Index32 findFirstOn() const { return mWord ? FindLowestOn(mWord) : 64; } + Index32 findFirstOff() const + { + const Word w = ~mWord; + return w ? FindLowestOn(w) : 64; + } + //@{ + /// Return the nth word of the bit mask, for a word of arbitrary size. + template + WordT getWord(Index n) const + { + assert(n*8*sizeof(WordT) < SIZE); + return reinterpret_cast(&mWord)[n]; + } + template + WordT& getWord(Index n) + { + assert(n*8*sizeof(WordT) < SIZE); + return reinterpret_cast(mWord)[n]; + } + //@} + void save(std::ostream& os) const + { + os.write(reinterpret_cast(&mWord), 8); + } + void load(std::istream& is) { is.read(reinterpret_cast(&mWord), 8); } + /// @brief simple print method for debugging + void printInfo(std::ostream& os=std::cout) const + { + os << "NodeMask: Dim=4, Log2Dim=2, Bit count=64, Word count=1"<isOn(i); + } + os << "||" << std::endl; + } + void printAll(std::ostream& os=std::cout) const + { + this->printInfo(os); + this->printBits(os); + } + + Index32 findNextOn(Index32 start) const + { + if (start>=64) return 64; + const Word w = mWord & (UINT64_C(0xFFFFFFFFFFFFFFFF) << start); + return w ? FindLowestOn(w) : 64; + } + + Index32 findNextOff(Index32 start) const + { + if (start>=64) return 64; + const Word w = ~mWord & (UINT64_C(0xFFFFFFFFFFFFFFFF) << start); + return w ? FindLowestOn(w) : 64; + } + +};// NodeMask<2> + + +// Unlike NodeMask above this RootNodeMask has a run-time defined size. +// It is only included for backward compatibility and will likely be +// deprecated in the future! +// This class is 32-bit specefic, hence the use if Index32 vs Index! +class RootNodeMask +{ +protected: + Index32 mBitSize, mIntSize; + Index32 *mBits; + +public: + RootNodeMask(): mBitSize(0), mIntSize(0), mBits(NULL) {} + RootNodeMask(Index32 bit_size): + mBitSize(bit_size), mIntSize(((bit_size-1)>>5)+1), mBits(new Index32[mIntSize]) + { + for (Index32 i=0; i>5)+1; + delete [] mBits; + mBits = new Index32[mIntSize]; + for (Index32 i=0; igetBitSize()), mParent(parent) { + assert( pos<=mBitSize ); + } + bool operator==(const BaseIterator &iter) const {return mPos == iter.mPos;} + bool operator!=(const BaseIterator &iter) const {return mPos != iter.mPos;} + bool operator< (const BaseIterator &iter) const {return mPos < iter.mPos;} + 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!=NULL); + mPos=mParent->findNextOn(mPos+1); + assert(mPos <= mBitSize); + } + void increment(Index n) { + for (Index i=0; inext(); ++i) {} + } + bool next() { + this->increment(); + return this->test(); + } + bool operator*() const {return true;} + OnIterator& operator++() { + this->increment(); + return *this; + } + }; // class OnIterator + + class OffIterator: public BaseIterator + { + protected: + using BaseIterator::mPos;//bit position; + using BaseIterator::mBitSize;//bit size; + using BaseIterator::mParent;//this iterator can't change the parent_mask! + public: + OffIterator() : BaseIterator() {} + OffIterator(Index32 pos,const RootNodeMask *parent) : BaseIterator(pos,parent) {} + void increment() { + assert(mParent!=NULL); + mPos=mParent->findNextOff(mPos+1); + assert(mPos <= mBitSize); + } + void increment(Index n) { + for (Index i=0; inext(); ++i) {} + } + bool next() { + this->increment(); + return this->test(); + } + bool operator*() const {return true;} + OffIterator& operator++() { + this->increment(); + return *this; + } + }; // class OffIterator + + class DenseIterator: public BaseIterator + { + protected: + using BaseIterator::mPos;//bit position; + using BaseIterator::mBitSize;//bit size; + using BaseIterator::mParent;//this iterator can't change the parent_mask! + public: + DenseIterator() : BaseIterator() {} + DenseIterator(Index32 pos,const RootNodeMask *parent) : BaseIterator(pos,parent) {} + void increment() { + assert(mParent!=NULL); + mPos += 1;//carefull - the increament might go beyond the end + assert(mPos<= mBitSize); + } + void increment(Index n) { + for (Index i=0; inext(); ++i) {} + } + bool next() { + this->increment(); + return this->test(); + } + bool operator*() const {return mParent->isOn(mPos);} + DenseIterator& operator++() { + this->increment(); + return *this; + } + }; // class DenseIterator + + OnIterator beginOn() const { return OnIterator(this->findFirstOn(),this); } + OnIterator endOn() const { return OnIterator(mBitSize,this); } + OffIterator beginOff() const { return OffIterator(this->findFirstOff(),this); } + OffIterator endOff() const { return OffIterator(mBitSize,this); } + DenseIterator beginDense() const { return DenseIterator(0,this); } + DenseIterator endDense() const { return DenseIterator(mBitSize,this); } + + bool operator == (const RootNodeMask &B) const { + if (mBitSize != B.mBitSize) return false; + for (Index32 i=0; icountOn(); } + + void setOn(Index32 i) { + assert(mBits); + assert( (i>>5) < mIntSize); + mBits[i>>5] |= 1<<(i&31); + } + + void setOff(Index32 i) { + assert(mBits); + assert( (i>>5) < mIntSize); + mBits[i>>5] &= ~(1<<(i&31)); + } + + void set(Index32 i, bool On) { On ? this->setOn(i) : this->setOff(i); } + + void setOn() { + assert(mBits); + for (Index32 i=0; i>5) < mIntSize); + mBits[i>>5] ^= 1<<(i&31); + } + void toggle() { + assert(mBits); + for (Index32 i=0; isetOn(0); } + void setLastOn() { this->setOn(mBitSize-1); } + void setFirstOff() { this->setOff(0); } + void setLastOff() { this->setOff(mBitSize-1); } + bool isOn(Index32 i) const { + assert(mBits); + assert( (i>>5) < mIntSize); + return ( mBits[i >> 5] & (1<<(i&31)) ); + } + bool isOff(Index32 i) const { + assert(mBits); + assert( (i>>5) < mIntSize); + return ( ~mBits[i >> 5] & (1<<(i&31)) ); + } + + bool isOn() const { + if (!mBits) return false;//undefined is off + for (Index32 i=0; iisOn(i); + } + os << "|" << std::endl; + } + + void printAll(std::ostream& os=std::cout, Index32 max_out=80u) const { + this->printInfo(os); + this->printBits(os,max_out); + } + + Index32 findNextOn(Index32 start) const { + assert(mBits); + Index32 n = start >> 5, m = start & 31;//initiate + if (n>=mIntSize) return mBitSize; // check for out of bounds + Index32 b = mBits[n]; + if (b & (1<> 5, m = start & 31;//initiate + if (n>=mIntSize) return mBitSize; // check for out of bounds + Index32 b = ~mBits[n]; + if (b & (1< + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace util { + +/// @brief Dummy NOOP interrupter class defining interface +/// +/// This shows the required interface for the @c InterrupterType template argument +/// using by several threaded applications (e.g. tools/PointAdvect.h). The host +/// application calls start() at the beginning of an interruptible operation, end() +/// at the end of the operation, and wasInterrupted() periodically during the operation. +/// If any call to wasInterrupted() returns @c true, the operation will be aborted. +/// @note This Dummy interrupter will NEVER interrupt since wasInterrupted() always +/// returns false! +struct NullInterrupter +{ + /// Default constructor + NullInterrupter () {} + /// Signal the start of an interruptible operation. + /// @param name an optional descriptive name for the operation + void start(const char* name = NULL) { (void)name; } + /// Signal the end of an interruptible operation. + void end() {} + /// Check if an interruptible operation should be aborted. + /// @param percent an optional (when >= 0) percentage indicating + /// the fraction of the operation that has been completed + /// @note this method is assumed to be thread-safe. The current + /// implementation is clearly a NOOP and should compile out during + /// optimization! + inline bool wasInterrupted(int percent = -1) { (void)percent; return false; } +}; + +/// This method allows NullInterrupter::wasInterrupted to be compiled +/// out when client code only has a pointer (vs reference) to the interrupter. +/// +/// @note This is a free-standing function since C++ doesn't allow for +/// partial template specialization (in client code of the interrupter). +template +inline bool wasInterrupted(T* i, int percent = -1) { return i && i->wasInterrupted(percent); } + +/// Specialization for NullInterrupter +template<> +inline bool wasInterrupted(util::NullInterrupter*, int) { return false; } + +} // namespace util +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_UTIL_NULL_INTERRUPTER_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/util/Util.cc b/openvdb_2_3_0_library/openvdb/util/Util.cc new file mode 100755 index 0000000..5826727 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/util/Util.cc @@ -0,0 +1,77 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Util.h" +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace util { + +const Index32 INVALID_IDX = std::numeric_limits::max(); + +const Coord COORD_OFFSETS[26] = +{ + Coord( 1, 0, 0), /// Voxel-face adjacent neghbours + Coord(-1, 0, 0), /// 0 to 5 + Coord( 0, 1, 0), + Coord( 0, -1, 0), + Coord( 0, 0, 1), + Coord( 0, 0, -1), + Coord( 1, 0, -1), /// Voxel-edge adjacent neghbours + Coord(-1, 0, -1), /// 6 to 17 + Coord( 1, 0, 1), + Coord(-1, 0, 1), + Coord( 1, 1, 0), + Coord(-1, 1, 0), + Coord( 1, -1, 0), + Coord(-1, -1, 0), + Coord( 0, -1, 1), + Coord( 0, -1, -1), + Coord( 0, 1, 1), + Coord( 0, 1, -1), + Coord(-1, -1, -1), /// Voxel-corner adjacent neghbours + Coord(-1, -1, 1), /// 18 to 25 + Coord( 1, -1, 1), + Coord( 1, -1, -1), + Coord(-1, 1, -1), + Coord(-1, 1, 1), + Coord( 1, 1, 1), + Coord( 1, 1, -1) +}; + +} // namespace util +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/util/Util.h b/openvdb_2_3_0_library/openvdb/util/Util.h new file mode 100755 index 0000000..2acb3f3 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/util/Util.h @@ -0,0 +1,165 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_UTIL_UTIL_HAS_BEEN_INCLUDED +#define OPENVDB_UTIL_UTIL_HAS_BEEN_INCLUDED + +#include +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace util { + +OPENVDB_API extern const Index32 INVALID_IDX; + +/// @brief coordinate offset table for neighboring voxels +OPENVDB_API extern const Coord COORD_OFFSETS[26]; + + +//////////////////////////////////////// + + +/// Return @a voxelCoord rounded to the closest integer coordinates. +inline Coord +nearestCoord(const Vec3d& voxelCoord) +{ + Coord ijk; + ijk[0] = int(std::floor(voxelCoord[0])); + ijk[1] = int(std::floor(voxelCoord[1])); + ijk[2] = int(std::floor(voxelCoord[2])); + return ijk; +} + + +//////////////////////////////////////// + + +/// @brief Functor for use with tools::foreach() to compute the boolean intersection +/// between the value masks of corresponding leaf nodes in two trees +template +class LeafTopologyIntOp +{ +public: + LeafTopologyIntOp(const TreeType2& tree): mOtherTree(&tree) {} + + inline void operator()(const typename TreeType1::LeafIter& lIter) const + { + const Coord xyz = lIter->origin(); + const typename TreeType2::LeafNodeType* leaf = mOtherTree->probeConstLeaf(xyz); + if (leaf) {//leaf node + lIter->topologyIntersection(*leaf, zeroVal()); + } else if (!mOtherTree->isValueOn(xyz)) {//inactive tile + lIter->setValuesOff(); + } + } + +private: + const TreeType2* mOtherTree; +}; + + +/// @brief Functor for use with tools::foreach() to compute the boolean difference +/// between the value masks of corresponding leaf nodes in two trees +template +class LeafTopologyDiffOp +{ +public: + LeafTopologyDiffOp(const TreeType2& tree): mOtherTree(&tree) {} + + inline void operator()(const typename TreeType1::LeafIter& lIter) const + { + const Coord xyz = lIter->origin(); + const typename TreeType2::LeafNodeType* leaf = mOtherTree->probeConstLeaf(xyz); + if (leaf) {//leaf node + lIter->topologyDifference(*leaf, zeroVal()); + } else if (mOtherTree->isValueOn(xyz)) {//active tile + lIter->setValuesOff(); + } + } + +private: + const TreeType2* mOtherTree; +}; + + +//////////////////////////////////////// + + +/// @brief Perform a boolean intersection between two leaf nodes' topology masks. +/// @return a pointer to a new, boolean-valued tree containing the overlapping voxels. +template +inline typename TreeType1::template ValueConverter::Type::Ptr +leafTopologyIntersection(const TreeType1& lhs, const TreeType2& rhs, bool threaded = true) +{ + typedef typename TreeType1::template ValueConverter::Type BoolTreeType; + + typename BoolTreeType::Ptr topologyTree(new BoolTreeType( + lhs, /*inactiveValue=*/false, /*activeValue=*/true, TopologyCopy())); + + tools::foreach(topologyTree->beginLeaf(), + LeafTopologyIntOp(rhs), threaded); + + topologyTree->pruneInactive(); + return topologyTree; +} + + +/// @brief Perform a boolean difference between two leaf nodes' topology masks. +/// @return a pointer to a new, boolean-valued tree containing the non-overlapping +/// voxels from the lhs. +template +inline typename TreeType1::template ValueConverter::Type::Ptr +leafTopologyDifference(const TreeType1& lhs, const TreeType2& rhs, bool threaded = true) +{ + typedef typename TreeType1::template ValueConverter::Type BoolTreeType; + + typename BoolTreeType::Ptr topologyTree(new BoolTreeType( + lhs, /*inactiveValue=*/false, /*activeValue=*/true, TopologyCopy())); + + tools::foreach(topologyTree->beginLeaf(), + LeafTopologyDiffOp(rhs), threaded); + + topologyTree->pruneInactive(); + return topologyTree; +} + +} // namespace util +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_UTIL_UTIL_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/util/logging.h b/openvdb_2_3_0_library/openvdb/util/logging.h new file mode 100755 index 0000000..4646e07 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/util/logging.h @@ -0,0 +1,83 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 + +#ifndef OPENVDB_USE_LOG4CPLUS + +/// Log an info message of the form 'someVar << "some text" << ...'. +#define OPENVDB_LOG_INFO(message) +/// Log a warning message of the form 'someVar << "some text" << ...'. +#define OPENVDB_LOG_WARN(message) do { std::cerr << message << std::endl; } while (0); +/// Log an error message of the form 'someVar << "some text" << ...'. +#define OPENVDB_LOG_ERROR(message) do { std::cerr << message << std::endl; } while (0); +/// Log a fatal error message of the form 'someVar << "some text" << ...'. +#define OPENVDB_LOG_FATAL(message) do { std::cerr << message << std::endl; } while (0); +/// In debug builds only, log a debugging message of the form 'someVar << "text" << ...'. +#define OPENVDB_LOG_DEBUG(message) +/// @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) + +#else // ifdef OPENVDB_USE_LOG4CPLUS + +#include +#include +#include + +#define OPENVDB_LOG(level, message) \ + do { \ + log4cplus::Logger _log = log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("main")); \ + if (_log.isEnabledFor(log4cplus::level##_LOG_LEVEL)) { \ + std::ostringstream _buf; \ + _buf << message; \ + _log.forcedLog(log4cplus::level##_LOG_LEVEL, _buf.str(), __FILE__, __LINE__); \ + } \ + } while (0); + +#define OPENVDB_LOG_INFO(message) OPENVDB_LOG(INFO, message) +#define OPENVDB_LOG_WARN(message) OPENVDB_LOG(WARN, message) +#define OPENVDB_LOG_ERROR(message) OPENVDB_LOG(ERROR, message) +#define OPENVDB_LOG_FATAL(message) OPENVDB_LOG(FATAL, message) +#ifdef DEBUG +#define OPENVDB_LOG_DEBUG(message) OPENVDB_LOG(DEBUG, message) +#else +#define OPENVDB_LOG_DEBUG(message) +#endif +#define OPENVDB_LOG_DEBUG_RUNTIME(message) OPENVDB_LOG(DEBUG, message) + +#endif // OPENVDB_USE_LOG4CPLUS + +#endif // OPENVDB_UTIL_LOGGING_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/version.h b/openvdb_2_3_0_library/openvdb/version.h new file mode 100755 index 0000000..706c7c0 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/version.h @@ -0,0 +1,153 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_VERSION_HAS_BEEN_INCLUDED +#define OPENVDB_VERSION_HAS_BEEN_INCLUDED + +#include "Platform.h" +#include // for std::istream +#include + + +/// The version namespace name for this library version +/// +/// Fully-namespace-qualified symbols are named as follows: +/// vdb::vX_Y_Z::Vec3i, vdb::vX_Y_Z::io::File, vdb::vX_Y_Z::tree::Tree, etc., +/// where X, Y and Z are OPENVDB_LIBRARY_MAJOR_VERSION, OPENVDB_LIBRARY_MINOR_VERSION +/// and OPENVDB_LIBRARY_PATCH_VERSION, respectively (defined below). +#define OPENVDB_VERSION_NAME v2_3_0 + +// Library major, minor and patch version numbers +#define OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER 2 +#define OPENVDB_LIBRARY_MINOR_VERSION_NUMBER 3 +#define OPENVDB_LIBRARY_PATCH_VERSION_NUMBER 0 + +/// Library version number as a packed integer ("%02x%02x%04x", major, minor, patch) +#define OPENVDB_LIBRARY_VERSION_NUMBER \ + ((OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER << 24) | \ + ((OPENVDB_LIBRARY_MINOR_VERSION_NUMBER & 0xFF) << 16) | \ + (OPENVDB_LIBRARY_PATCH_VERSION_NUMBER & 0xFFFF)) + +/// If OPENVDB_REQUIRE_VERSION_NAME is undefined, symbols from the version +/// namespace are promoted to the top-level namespace (e.g., vdb::v1_0_0::io::File +/// can be referred to simply as vdb::io::File). Otherwise, symbols must be fully +/// namespace-qualified. +#ifdef OPENVDB_REQUIRE_VERSION_NAME +#define OPENVDB_USE_VERSION_NAMESPACE +#else +/// @note The empty namespace clause below ensures that +/// OPENVDB_VERSION_NAME is recognized as a namespace name. +#define OPENVDB_USE_VERSION_NAMESPACE \ + namespace OPENVDB_VERSION_NAME {} \ + using namespace OPENVDB_VERSION_NAME; +#endif + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +/// @brief The magic number is stored in the first four bytes of every VDB file. +/// @details This can be used to quickly test whether we have a valid file or not. +const int32_t OPENVDB_MAGIC = 0x56444220; + +// Library major, minor and patch version numbers +const uint32_t + OPENVDB_LIBRARY_MAJOR_VERSION = OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER, + OPENVDB_LIBRARY_MINOR_VERSION = OPENVDB_LIBRARY_MINOR_VERSION_NUMBER, + OPENVDB_LIBRARY_PATCH_VERSION = OPENVDB_LIBRARY_PATCH_VERSION_NUMBER; +/// Library version number as a packed integer ("%02x%02x%04x", major, minor, patch) +const uint32_t OPENVDB_LIBRARY_VERSION = OPENVDB_LIBRARY_VERSION_NUMBER; + +/// @brief The current version number of the VDB file format +/// @details This can be used to enable various backwards compatability switches +/// or to reject files that cannot be read. +const uint32_t OPENVDB_FILE_VERSION = 222; + +/// Notable file format version numbers +enum { + OPENVDB_FILE_VERSION_ROOTNODE_MAP = 213, + OPENVDB_FILE_VERSION_INTERNALNODE_COMPRESSION = 214, + OPENVDB_FILE_VERSION_SIMPLIFIED_GRID_TYPENAME = 215, + OPENVDB_FILE_VERSION_GRID_INSTANCING = 216, + OPENVDB_FILE_VERSION_BOOL_LEAF_OPTIMIZATION = 217, + OPENVDB_FILE_VERSION_BOOST_UUID = 218, + OPENVDB_FILE_VERSION_NO_GRIDMAP = 219, + OPENVDB_FILE_VERSION_NEW_TRANSFORM = 219, + OPENVDB_FILE_VERSION_SELECTIVE_COMPRESSION = 220, + OPENVDB_FILE_VERSION_FLOAT_FRUSTUM_BBOX = 221, + OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION = 222 +}; + + +struct VersionId { uint32_t first, second; VersionId(): first(0), second(0) {} }; + +namespace io { +/// @brief Return the file format version number associated with the given input stream. +OPENVDB_API uint32_t getFormatVersion(std::istream&); +/// @brief Return the (major, minor) library version number associated with the given input stream. +OPENVDB_API VersionId getLibraryVersion(std::istream&); +/// @brief Return a string of the form "./", giving the library +/// and file format version numbers associated with the given input stream. +OPENVDB_API std::string getVersion(std::istream&); +// Associate the current file format and library version numbers with the given input stream. +OPENVDB_API void setCurrentVersion(std::istream&); +// Associate specific file format and library version numbers with the given stream. +OPENVDB_API void setVersion(std::ios_base&, const VersionId& libraryVersion, uint32_t fileVersion); +// Return a bitwise OR of compression option flags (COMPRESS_ZIP, COMPRESS_ACTIVE_MASK, etc.) +// specifying whether and how input data is compressed or output data should be compressed. +OPENVDB_API uint32_t getDataCompression(std::ios_base&); +// Associate with the given stream a bitwise OR of compression option flags (COMPRESS_ZIP, +// COMPRESS_ACTIVE_MASK, etc.) specifying whether and how input data is compressed +// or output data should be compressed. +OPENVDB_API void setDataCompression(std::ios_base&, uint32_t compressionFlags); +// Return the class (GRID_LEVEL_SET, GRID_UNKNOWN, etc.) of the grid +// currently being read from or written to the given stream. +OPENVDB_API uint32_t getGridClass(std::ios_base&); +// brief Associate with the given stream the class (GRID_LEVEL_SET, GRID_UNKNOWN, etc.) +// of the grid currently being read or written. +OPENVDB_API void setGridClass(std::ios_base&, uint32_t); +// Return a pointer to the background value of the grid currently being +// read from or written to the given stream. +OPENVDB_API const void* getGridBackgroundValuePtr(std::ios_base&); +// Specify (a pointer to) the background value of the grid currently being +// read from or written to the given stream. +// The pointer must remain valid until the entire grid has been read or written. +OPENVDB_API void setGridBackgroundValuePtr(std::ios_base&, const void* background); +} // namespace io + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_VERSION_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/viewer/Camera.cc b/openvdb_2_3_0_library/openvdb/viewer/Camera.cc new file mode 100755 index 0000000..f1527b4 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/viewer/Camera.cc @@ -0,0 +1,259 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Camera.h" + +#include + +#if defined(__APPLE__) || defined(MACOSX) +#include +#include +#else +#include +#include +#endif + +#include + + +namespace openvdb_viewer { + +const double Camera::sDeg2rad = M_PI / 180.0; + + +Camera::Camera() + : mFov(65.0) + , mNearPlane(0.1) + , mFarPlane(10000.0) + , mTarget(openvdb::Vec3d(0.0)) + , mLookAt(mTarget) + , mUp(openvdb::Vec3d(0.0, 1.0, 0.0)) + , mForward(openvdb::Vec3d(0.0, 0.0, 1.0)) + , mRight(openvdb::Vec3d(1.0, 0.0, 0.0)) + , mEye(openvdb::Vec3d(0.0, 0.0, -1.0)) + , mTumblingSpeed(0.5) + , mZoomSpeed(0.2) + , mStrafeSpeed(0.05) + , mHead(30.0) + , mPitch(45.0) + , mTargetDistance(25.0) + , mDistance(mTargetDistance) + , mMouseDown(false) + , mStartTumbling(false) + , mZoomMode(false) + , mChanged(true) + , mNeedsDisplay(true) + , mMouseXPos(0.0) + , mMouseYPos(0.0) + , mWheelPos(0) +{ +} + + +void +Camera::lookAt(const openvdb::Vec3d& p, double dist) +{ + mLookAt = p; + mDistance = dist; + mNeedsDisplay = true; +} + + +void +Camera::lookAtTarget() +{ + mLookAt = mTarget; + mDistance = mTargetDistance; + mNeedsDisplay = true; +} + + +void +Camera::setSpeed(double zoomSpeed, double strafeSpeed, double tumblingSpeed) +{ + mZoomSpeed = std::max(0.0001, zoomSpeed); + mStrafeSpeed = std::max(0.0001, strafeSpeed); + mTumblingSpeed = std::max(0.2, tumblingSpeed); + mTumblingSpeed = std::min(1.0, tumblingSpeed); +} + + +void +Camera::setTarget(const openvdb::Vec3d& p, double dist) +{ + mTarget = p; + mTargetDistance = dist; +} + + +void +Camera::aim() +{ + // Get the window size + int width, height; + glfwGetWindowSize(&width, &height); + + // Make sure that height is non-zero to avoid division by zero + height = std::max(1, height); + + glViewport(0, 0, width, height); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Set up the projection matrix + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + // Window aspect (assumes square pixels) + double aspectRatio = (double)width / (double)height; + + // Set perspective view (fov is in degrees in the y direction.) + gluPerspective(mFov, aspectRatio, mNearPlane, mFarPlane); + + if (mChanged) { + + mChanged = false; + + mEye[0] = mLookAt[0] + mDistance * std::cos(mHead * sDeg2rad) * std::cos(mPitch * sDeg2rad); + mEye[1] = mLookAt[1] + mDistance * std::sin(mHead * sDeg2rad); + mEye[2] = mLookAt[2] + mDistance * std::cos(mHead * sDeg2rad) * std::sin(mPitch * sDeg2rad); + + mForward = mLookAt - mEye; + mForward.normalize(); + + mUp[1] = std::cos(mHead * sDeg2rad) > 0 ? 1.0 : -1.0; + mRight = mForward.cross(mUp); + } + + // Set up modelview matrix + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + gluLookAt(mEye[0], mEye[1], mEye[2], + mLookAt[0], mLookAt[1], mLookAt[2], + mUp[0], mUp[1], mUp[2]); + mNeedsDisplay = false; +} + + +void +Camera::keyCallback(int key, int ) +{ + if (glfwGetKey(key) == GLFW_PRESS) { + switch(key) { + case GLFW_KEY_SPACE: + mZoomMode = true; + break; + } + } else if (glfwGetKey(key) == GLFW_RELEASE) { + switch(key) { + case GLFW_KEY_SPACE: + mZoomMode = false; + break; + } + } + + mChanged = true; +} + + +void +Camera::mouseButtonCallback(int button, int action) +{ + if (button == GLFW_MOUSE_BUTTON_LEFT) { + if (action == GLFW_PRESS) mMouseDown = true; + else if (action == GLFW_RELEASE) mMouseDown = false; + } else if (button == GLFW_MOUSE_BUTTON_RIGHT) { + if (action == GLFW_PRESS) { + mMouseDown = true; + mZoomMode = true; + } else if (action == GLFW_RELEASE) { + mMouseDown = false; + mZoomMode = false; + } + } + if (action == GLFW_RELEASE) mMouseDown = false; + + mStartTumbling = true; + mChanged = true; +} + + +void +Camera::mousePosCallback(int x, int y) +{ + if (mStartTumbling) { + mMouseXPos = x; + mMouseYPos = y; + mStartTumbling = false; + } + + double dx, dy; + dx = x - mMouseXPos; + dy = y - mMouseYPos; + + if (mMouseDown && !mZoomMode) { + mNeedsDisplay = true; + mHead += dy * mTumblingSpeed; + mPitch += dx * mTumblingSpeed; + } else if (mMouseDown && mZoomMode) { + mNeedsDisplay = true; + mLookAt += (dy * mUp - dx * mRight) * mStrafeSpeed; + } + + mMouseXPos = x; + mMouseYPos = y; + mChanged = true; +} + + +void +Camera::mouseWheelCallback(int pos, int prevPos) +{ + double speed = std::abs(prevPos - pos); + + if (prevPos < pos) { + mDistance += speed * mZoomSpeed; + setSpeed(mDistance * 0.1, mDistance * 0.002, mDistance * 0.02); + } else { + double temp = mDistance - speed * mZoomSpeed; + mDistance = std::max(0.0, temp); + setSpeed(mDistance * 0.1, mDistance * 0.002, mDistance * 0.02); + } + + mChanged = true; + mNeedsDisplay = true; +} + +} // namespace openvdb_viewer + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/viewer/Camera.h b/openvdb_2_3_0_library/openvdb/viewer/Camera.h new file mode 100755 index 0000000..60e65de --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/viewer/Camera.h @@ -0,0 +1,86 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// +// +/// @file Camera.h +/// @brief Basic GL camera class + +#ifndef OPENVDB_VIEWER_CAMERA_HAS_BEEN_INCLUDED +#define OPENVDB_VIEWER_CAMERA_HAS_BEEN_INCLUDED + +#include + + +namespace openvdb_viewer { + +class Camera +{ +public: + Camera(); + + void aim(); + + void lookAt(const openvdb::Vec3d& p, double dist = 1.0); + void lookAtTarget(); + + void setTarget(const openvdb::Vec3d& p, double dist = 1.0); + + void setNearFarPlanes(double n, double f) { mNearPlane = n; mFarPlane = f; } + void setFieldOfView(double degrees) { mFov = degrees; } + void setSpeed(double zoomSpeed, double strafeSpeed, double tumblingSpeed); + + void keyCallback(int key, int action); + void mouseButtonCallback(int button, int action); + void mousePosCallback(int x, int y); + void mouseWheelCallback(int pos, int prevPos); + + bool needsDisplay() const { return mNeedsDisplay; } + +private: + // Camera parameters + double mFov, mNearPlane, mFarPlane; + openvdb::Vec3d mTarget, mLookAt, mUp, mForward, mRight, mEye; + double mTumblingSpeed, mZoomSpeed, mStrafeSpeed; + double mHead, mPitch, mTargetDistance, mDistance; + + // Input states + bool mMouseDown, mStartTumbling, mZoomMode, mChanged, mNeedsDisplay; + double mMouseXPos, mMouseYPos; + int mWheelPos; + + static const double sDeg2rad; +}; // class Camera + +} // namespace openvdb_viewer + +#endif // OPENVDB_VIEWER_CAMERA_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/viewer/ClipBox.cc b/openvdb_2_3_0_library/openvdb/viewer/ClipBox.cc new file mode 100755 index 0000000..fa0b57e --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/viewer/ClipBox.cc @@ -0,0 +1,300 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "ClipBox.h" + +#include +#include + + +namespace openvdb_viewer { + +ClipBox::ClipBox() + : mStepSize(1.0) + , mBBox() + , mXIsActive(false) + , mYIsActive(false) + , mZIsActive(false) + , mShiftIsDown(false) + , mCtrlIsDown(false) +{ + GLdouble front [] = { 0.0, 0.0, 1.0, 0.0}; + std::copy(front, front + 4, mFrontPlane); + + GLdouble back [] = { 0.0, 0.0,-1.0, 0.0}; + std::copy(back, back + 4, mBackPlane); + + GLdouble left [] = { 1.0, 0.0, 0.0, 0.0}; + std::copy(left, left + 4, mLeftPlane); + + GLdouble right [] = {-1.0, 0.0, 0.0, 0.0}; + std::copy(right, right + 4, mRightPlane); + + GLdouble top [] = { 0.0, 1.0, 0.0, 0.0}; + std::copy(top, top + 4, mTopPlane); + + GLdouble bottom [] = { 0.0,-1.0, 0.0, 0.0}; + std::copy(bottom, bottom + 4, mBottomPlane); +} + + +void +ClipBox::setBBox(const openvdb::BBoxd& bbox) +{ + mBBox = bbox; + reset(); +} + + +void +ClipBox::update(double steps) +{ + if (mXIsActive) { + GLdouble s = steps * mStepSize.x() * 4.0; + + if (mShiftIsDown || mCtrlIsDown) { + mLeftPlane[3] -= s; + mLeftPlane[3] = -std::min(-mLeftPlane[3], (mRightPlane[3] - mStepSize.x())); + mLeftPlane[3] = -std::max(-mLeftPlane[3], mBBox.min().x()); + } + + if (!mShiftIsDown || mCtrlIsDown) { + mRightPlane[3] += s; + mRightPlane[3] = std::min(mRightPlane[3], mBBox.max().x()); + mRightPlane[3] = std::max(mRightPlane[3], (-mLeftPlane[3] + mStepSize.x())); + } + } + + if (mYIsActive) { + GLdouble s = steps * mStepSize.y() * 4.0; + + if (mShiftIsDown || mCtrlIsDown) { + mTopPlane[3] -= s; + mTopPlane[3] = -std::min(-mTopPlane[3], (mBottomPlane[3] - mStepSize.y())); + mTopPlane[3] = -std::max(-mTopPlane[3], mBBox.min().y()); + } + + if (!mShiftIsDown || mCtrlIsDown) { + mBottomPlane[3] += s; + mBottomPlane[3] = std::min(mBottomPlane[3], mBBox.max().y()); + mBottomPlane[3] = std::max(mBottomPlane[3], (-mTopPlane[3] + mStepSize.y())); + } + } + + if (mZIsActive) { + GLdouble s = steps * mStepSize.z() * 4.0; + + if (mShiftIsDown || mCtrlIsDown) { + mFrontPlane[3] -= s; + mFrontPlane[3] = -std::min(-mFrontPlane[3], (mBackPlane[3] - mStepSize.z())); + mFrontPlane[3] = -std::max(-mFrontPlane[3], mBBox.min().z()); + } + + if (!mShiftIsDown || mCtrlIsDown) { + mBackPlane[3] += s; + mBackPlane[3] = std::min(mBackPlane[3], mBBox.max().z()); + mBackPlane[3] = std::max(mBackPlane[3], (-mFrontPlane[3] + mStepSize.z())); + } + } +} + + +void +ClipBox::reset() +{ + mFrontPlane[3] = std::abs(mBBox.min().z()); + mBackPlane[3] = mBBox.max().z(); + + mLeftPlane[3] = std::abs(mBBox.min().x()); + mRightPlane[3] = mBBox.max().x(); + + mTopPlane[3] = std::abs(mBBox.min().y()); + mBottomPlane[3] = mBBox.max().y(); +} + + +void +ClipBox::update() const +{ + glClipPlane(GL_CLIP_PLANE0, mFrontPlane); + glClipPlane(GL_CLIP_PLANE1, mBackPlane); + glClipPlane(GL_CLIP_PLANE2, mLeftPlane); + glClipPlane(GL_CLIP_PLANE3, mRightPlane); + glClipPlane(GL_CLIP_PLANE4, mTopPlane); + glClipPlane(GL_CLIP_PLANE5, mBottomPlane); +} + + +void +ClipBox::enableClipping() const +{ + update(); + if (-mFrontPlane[3] > mBBox.min().z()) glEnable(GL_CLIP_PLANE0); + if (mBackPlane[3] < mBBox.max().z()) glEnable(GL_CLIP_PLANE1); + if (-mLeftPlane[3] > mBBox.min().x()) glEnable(GL_CLIP_PLANE2); + if (mRightPlane[3] < mBBox.max().x()) glEnable(GL_CLIP_PLANE3); + if (-mTopPlane[3] > mBBox.min().y()) glEnable(GL_CLIP_PLANE4); + if (mBottomPlane[3] < mBBox.max().y()) glEnable(GL_CLIP_PLANE5); +} + + +void +ClipBox::disableClipping() const +{ + glDisable(GL_CLIP_PLANE0); + glDisable(GL_CLIP_PLANE1); + glDisable(GL_CLIP_PLANE2); + glDisable(GL_CLIP_PLANE3); + glDisable(GL_CLIP_PLANE4); + glDisable(GL_CLIP_PLANE5); +} + + +void +ClipBox::render() +{ + bool drawBbox = false; + + const GLenum geoMode = GL_LINE_LOOP; + + glColor3f(0.1, 0.1, 0.9); + if (-mFrontPlane[3] > mBBox.min().z()) { + glBegin(geoMode); + glVertex3f(mBBox.min().x(), mBBox.min().y(), -mFrontPlane[3]); + glVertex3f(mBBox.min().x(), mBBox.max().y(), -mFrontPlane[3]); + glVertex3f(mBBox.max().x(), mBBox.max().y(), -mFrontPlane[3]); + glVertex3f(mBBox.max().x(), mBBox.min().y(), -mFrontPlane[3]); + glEnd(); + drawBbox = true; + } + + if (mBackPlane[3] < mBBox.max().z()) { + glBegin(geoMode); + glVertex3f(mBBox.min().x(), mBBox.min().y(), mBackPlane[3]); + glVertex3f(mBBox.min().x(), mBBox.max().y(), mBackPlane[3]); + glVertex3f(mBBox.max().x(), mBBox.max().y(), mBackPlane[3]); + glVertex3f(mBBox.max().x(), mBBox.min().y(), mBackPlane[3]); + glEnd(); + drawBbox = true; + } + + glColor3f(0.9, 0.1, 0.1); + if (-mLeftPlane[3] > mBBox.min().x()) { + glBegin(geoMode); + glVertex3f(-mLeftPlane[3], mBBox.min().y(), mBBox.min().z()); + glVertex3f(-mLeftPlane[3], mBBox.max().y(), mBBox.min().z()); + glVertex3f(-mLeftPlane[3], mBBox.max().y(), mBBox.max().z()); + glVertex3f(-mLeftPlane[3], mBBox.min().y(), mBBox.max().z()); + glEnd(); + drawBbox = true; + } + + if (mRightPlane[3] < mBBox.max().x()) { + glBegin(geoMode); + glVertex3f(mRightPlane[3], mBBox.min().y(), mBBox.min().z()); + glVertex3f(mRightPlane[3], mBBox.max().y(), mBBox.min().z()); + glVertex3f(mRightPlane[3], mBBox.max().y(), mBBox.max().z()); + glVertex3f(mRightPlane[3], mBBox.min().y(), mBBox.max().z()); + glEnd(); + drawBbox = true; + } + + glColor3f(0.1, 0.9, 0.1); + if (-mTopPlane[3] > mBBox.min().y()) { + glBegin(geoMode); + glVertex3f(mBBox.min().x(), -mTopPlane[3], mBBox.min().z()); + glVertex3f(mBBox.min().x(), -mTopPlane[3], mBBox.max().z()); + glVertex3f(mBBox.max().x(), -mTopPlane[3], mBBox.max().z()); + glVertex3f(mBBox.max().x(), -mTopPlane[3], mBBox.min().z()); + glEnd(); + drawBbox = true; + } + + if (mBottomPlane[3] < mBBox.max().y()) { + glBegin(geoMode); + glVertex3f(mBBox.min().x(), mBottomPlane[3], mBBox.min().z()); + glVertex3f(mBBox.min().x(), mBottomPlane[3], mBBox.max().z()); + glVertex3f(mBBox.max().x(), mBottomPlane[3], mBBox.max().z()); + glVertex3f(mBBox.max().x(), mBottomPlane[3], mBBox.min().z()); + glEnd(); + drawBbox = true; + } + + if (drawBbox) { + glColor3f(0.5, 0.5, 0.5); + glBegin(GL_LINE_LOOP); + glVertex3f(mBBox.min().x(), mBBox.min().y(), mBBox.min().z()); + glVertex3f(mBBox.min().x(), mBBox.min().y(), mBBox.max().z()); + glVertex3f(mBBox.max().x(), mBBox.min().y(), mBBox.max().z()); + glVertex3f(mBBox.max().x(), mBBox.min().y(), mBBox.min().z()); + glEnd(); + + glBegin(GL_LINE_LOOP); + glVertex3f(mBBox.min().x(), mBBox.max().y(), mBBox.min().z()); + glVertex3f(mBBox.min().x(), mBBox.max().y(), mBBox.max().z()); + glVertex3f(mBBox.max().x(), mBBox.max().y(), mBBox.max().z()); + glVertex3f(mBBox.max().x(), mBBox.max().y(), mBBox.min().z()); + glEnd(); + + glBegin(GL_LINES); + glVertex3f(mBBox.min().x(), mBBox.min().y(), mBBox.min().z()); + glVertex3f(mBBox.min().x(), mBBox.max().y(), mBBox.min().z()); + glVertex3f(mBBox.min().x(), mBBox.min().y(), mBBox.max().z()); + glVertex3f(mBBox.min().x(), mBBox.max().y(), mBBox.max().z()); + glVertex3f(mBBox.max().x(), mBBox.min().y(), mBBox.max().z()); + glVertex3f(mBBox.max().x(), mBBox.max().y(), mBBox.max().z()); + glVertex3f(mBBox.max().x(), mBBox.min().y(), mBBox.min().z()); + glVertex3f(mBBox.max().x(), mBBox.max().y(), mBBox.min().z()); + glEnd(); + } +} + + +//////////////////////////////////////// + + +bool +ClipBox::mouseButtonCallback(int /*button*/, int /*action*/) +{ + return false; // unhandled +} + + +bool +ClipBox::mousePosCallback(int /*x*/, int /*y*/) +{ + return false; // unhandled +} + +} // namespace openvdb_viewer + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/viewer/ClipBox.h b/openvdb_2_3_0_library/openvdb/viewer/ClipBox.h new file mode 100755 index 0000000..210482a --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/viewer/ClipBox.h @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_VIEWER_CLIPBOX_HAS_BEEN_INCLUDED +#define OPENVDB_VIEWER_CLIPBOX_HAS_BEEN_INCLUDED + +#include + +#if defined(__APPLE__) || defined(MACOSX) +#include +#include +#else +#include +#include +#endif + + +namespace openvdb_viewer { + +class ClipBox +{ +public: + ClipBox(); + + void enableClipping() const; + void disableClipping() const; + + void setBBox(const openvdb::BBoxd&); + void setStepSize(const openvdb::Vec3d& s) { mStepSize = s; } + + void render(); + + void update(double steps); + void reset(); + + bool isActive() const { return (mXIsActive || mYIsActive ||mZIsActive); } + + bool& activateXPlanes() { return mXIsActive; } + bool& activateYPlanes() { return mYIsActive; } + bool& activateZPlanes() { return mZIsActive; } + + bool& shiftIsDown() { return mShiftIsDown; } + bool& ctrlIsDown() { return mCtrlIsDown; } + + bool mouseButtonCallback(int button, int action); + bool mousePosCallback(int x, int y); + +private: + void update() const; + + openvdb::Vec3d mStepSize; + openvdb::BBoxd mBBox; + bool mXIsActive, mYIsActive, mZIsActive, mShiftIsDown, mCtrlIsDown; + GLdouble mFrontPlane[4], mBackPlane[4], mLeftPlane[4], mRightPlane[4], + mTopPlane[4], mBottomPlane[4]; + double mMouseXPos, mMouseYPos; +}; // class ClipBox + +} // namespace openvdb_viewer + +#endif // OPENVDB_VIEWER_CLIPBOX_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/viewer/Font.cc b/openvdb_2_3_0_library/openvdb/viewer/Font.cc new file mode 100755 index 0000000..fc480c2 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/viewer/Font.cc @@ -0,0 +1,200 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Font.h" + +#include // for OPENVDB_START_THREADSAFE_STATIC_WRITE +#include + + +namespace openvdb_viewer { + +GLuint BitmapFont13::sOffset = 0; + +GLubyte BitmapFont13::sCharacters[95][13] = { + { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X18, 0X18, 0X00, 0X00, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18 }, + { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X36, 0X36, 0X36, 0X36 }, + { 0X00, 0X00, 0X00, 0X66, 0X66, 0XFF, 0X66, 0X66, 0XFF, 0X66, 0X66, 0X00, 0X00 }, + { 0X00, 0X00, 0X18, 0X7E, 0XFF, 0X1B, 0X1F, 0X7E, 0XF8, 0XD8, 0XFF, 0X7E, 0X18 }, + { 0X00, 0X00, 0X0E, 0X1B, 0XDB, 0X6E, 0X30, 0X18, 0X0C, 0X76, 0XDB, 0XD8, 0X70 }, + { 0X00, 0X00, 0X7F, 0XC6, 0XCF, 0XD8, 0X70, 0X70, 0XD8, 0XCC, 0XCC, 0X6C, 0X38 }, + { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X18, 0X1C, 0X0C, 0X0E }, + { 0X00, 0X00, 0X0C, 0X18, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X18, 0X0C }, + { 0X00, 0X00, 0X30, 0X18, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X18, 0X30 }, + { 0X00, 0X00, 0X00, 0X00, 0X99, 0X5A, 0X3C, 0XFF, 0X3C, 0X5A, 0X99, 0X00, 0X00 }, + { 0X00, 0X00, 0X00, 0X18, 0X18, 0X18, 0XFF, 0XFF, 0X18, 0X18, 0X18, 0X00, 0X00 }, + { 0X00, 0X00, 0X30, 0X18, 0X1C, 0X1C, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0X00, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X00, 0X38, 0X38, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X60, 0X60, 0X30, 0X30, 0X18, 0X18, 0X0C, 0X0C, 0X06, 0X06, 0X03, 0X03 }, + { 0X00, 0X00, 0X3C, 0X66, 0XC3, 0XE3, 0XF3, 0XDB, 0XCF, 0XC7, 0XC3, 0X66, 0X3C }, + { 0X00, 0X00, 0X7E, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X78, 0X38, 0X18 }, + { 0X00, 0X00, 0XFF, 0XC0, 0XC0, 0X60, 0X30, 0X18, 0X0C, 0X06, 0X03, 0XE7, 0X7E }, + { 0X00, 0X00, 0X7E, 0XE7, 0X03, 0X03, 0X07, 0X7E, 0X07, 0X03, 0X03, 0XE7, 0X7E }, + { 0X00, 0X00, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0XFF, 0XCC, 0X6C, 0X3C, 0X1C, 0X0C }, + { 0X00, 0X00, 0X7E, 0XE7, 0X03, 0X03, 0X07, 0XFE, 0XC0, 0XC0, 0XC0, 0XC0, 0XFF }, + { 0X00, 0X00, 0X7E, 0XE7, 0XC3, 0XC3, 0XC7, 0XFE, 0XC0, 0XC0, 0XC0, 0XE7, 0X7E }, + { 0X00, 0X00, 0X30, 0X30, 0X30, 0X30, 0X18, 0X0C, 0X06, 0X03, 0X03, 0X03, 0XFF }, + { 0X00, 0X00, 0X7E, 0XE7, 0XC3, 0XC3, 0XE7, 0X7E, 0XE7, 0XC3, 0XC3, 0XE7, 0X7E }, + { 0X00, 0X00, 0X7E, 0XE7, 0X03, 0X03, 0X03, 0X7F, 0XE7, 0XC3, 0XC3, 0XE7, 0X7E }, + { 0X00, 0X00, 0X00, 0X38, 0X38, 0X00, 0X00, 0X38, 0X38, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X30, 0X18, 0X1C, 0X1C, 0X00, 0X00, 0X1C, 0X1C, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X06, 0X0C, 0X18, 0X30, 0X60, 0XC0, 0X60, 0X30, 0X18, 0X0C, 0X06 }, + { 0X00, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0X00, 0XFF, 0XFF, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X60, 0X30, 0X18, 0X0C, 0X06, 0X03, 0X06, 0X0C, 0X18, 0X30, 0X60 }, + { 0X00, 0X00, 0X18, 0X00, 0X00, 0X18, 0X18, 0X0C, 0X06, 0X03, 0XC3, 0XC3, 0X7E }, + { 0X00, 0X00, 0X3F, 0X60, 0XCF, 0XDB, 0XD3, 0XDD, 0XC3, 0X7E, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0XC3, 0XC3, 0XC3, 0XC3, 0XFF, 0XC3, 0XC3, 0XC3, 0X66, 0X3C, 0X18 }, + { 0X00, 0X00, 0XFE, 0XC7, 0XC3, 0XC3, 0XC7, 0XFE, 0XC7, 0XC3, 0XC3, 0XC7, 0XFE }, + { 0X00, 0X00, 0X7E, 0XE7, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XE7, 0X7E }, + { 0X00, 0X00, 0XFC, 0XCE, 0XC7, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC7, 0XCE, 0XFC }, + { 0X00, 0X00, 0XFF, 0XC0, 0XC0, 0XC0, 0XC0, 0XFC, 0XC0, 0XC0, 0XC0, 0XC0, 0XFF }, + { 0X00, 0X00, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XFC, 0XC0, 0XC0, 0XC0, 0XFF }, + { 0X00, 0X00, 0X7E, 0XE7, 0XC3, 0XC3, 0XCF, 0XC0, 0XC0, 0XC0, 0XC0, 0XE7, 0X7E }, + { 0X00, 0X00, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XFF, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3 }, + { 0X00, 0X00, 0X7E, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X7E }, + { 0X00, 0X00, 0X7C, 0XEE, 0XC6, 0X06, 0X06, 0X06, 0X06, 0X06, 0X06, 0X06, 0X06 }, + { 0X00, 0X00, 0XC3, 0XC6, 0XCC, 0XD8, 0XF0, 0XE0, 0XF0, 0XD8, 0XCC, 0XC6, 0XC3 }, + { 0X00, 0X00, 0XFF, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0 }, + { 0X00, 0X00, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XDB, 0XFF, 0XFF, 0XE7, 0XC3 }, + { 0X00, 0X00, 0XC7, 0XC7, 0XCF, 0XCF, 0XDF, 0XDB, 0XFB, 0XF3, 0XF3, 0XE3, 0XE3 }, + { 0X00, 0X00, 0X7E, 0XE7, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XE7, 0X7E }, + { 0X00, 0X00, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XFE, 0XC7, 0XC3, 0XC3, 0XC7, 0XFE }, + { 0X00, 0X00, 0X3F, 0X6E, 0XDF, 0XDB, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0X66, 0X3C }, + { 0X00, 0X00, 0XC3, 0XC6, 0XCC, 0XD8, 0XF0, 0XFE, 0XC7, 0XC3, 0XC3, 0XC7, 0XFE }, + { 0X00, 0X00, 0X7E, 0XE7, 0X03, 0X03, 0X07, 0X7E, 0XE0, 0XC0, 0XC0, 0XE7, 0X7E }, + { 0X00, 0X00, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0XFF }, + { 0X00, 0X00, 0X7E, 0XE7, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3 }, + { 0X00, 0X00, 0X18, 0X3C, 0X3C, 0X66, 0X66, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3 }, + { 0X00, 0X00, 0XC3, 0XE7, 0XFF, 0XFF, 0XDB, 0XDB, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3 }, + { 0X00, 0X00, 0XC3, 0X66, 0X66, 0X3C, 0X3C, 0X18, 0X3C, 0X3C, 0X66, 0X66, 0XC3 }, + { 0X00, 0X00, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X3C, 0X3C, 0X66, 0X66, 0XC3 }, + { 0X00, 0X00, 0XFF, 0XC0, 0XC0, 0X60, 0X30, 0X7E, 0X0C, 0X06, 0X03, 0X03, 0XFF }, + { 0X00, 0X00, 0X3C, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X3C }, + { 0X00, 0X03, 0X03, 0X06, 0X06, 0X0C, 0X0C, 0X18, 0X18, 0X30, 0X30, 0X60, 0X60 }, + { 0X00, 0X00, 0X3C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X3C }, + { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0XC3, 0X66, 0X3C, 0X18 }, + { 0XFF, 0XFF, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X18, 0X38, 0X30, 0X70 }, + { 0X00, 0X00, 0X7F, 0XC3, 0XC3, 0X7F, 0X03, 0XC3, 0X7E, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0XFE, 0XC3, 0XC3, 0XC3, 0XC3, 0XFE, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0 }, + { 0X00, 0X00, 0X7E, 0XC3, 0XC0, 0XC0, 0XC0, 0XC3, 0X7E, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X7F, 0XC3, 0XC3, 0XC3, 0XC3, 0X7F, 0X03, 0X03, 0X03, 0X03, 0X03 }, + { 0X00, 0X00, 0X7F, 0XC0, 0XC0, 0XFE, 0XC3, 0XC3, 0X7E, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X30, 0X30, 0X30, 0X30, 0X30, 0XFC, 0X30, 0X30, 0X30, 0X33, 0X1E }, + { 0X7E, 0XC3, 0X03, 0X03, 0X7F, 0XC3, 0XC3, 0XC3, 0X7E, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XFE, 0XC0, 0XC0, 0XC0, 0XC0 }, + { 0X00, 0X00, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X00, 0X00, 0X18, 0X00 }, + { 0X38, 0X6C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X00, 0X00, 0X0C, 0X00 }, + { 0X00, 0X00, 0XC6, 0XCC, 0XF8, 0XF0, 0XD8, 0XCC, 0XC6, 0XC0, 0XC0, 0XC0, 0XC0 }, + { 0X00, 0X00, 0X7E, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X78 }, + { 0X00, 0X00, 0XDB, 0XDB, 0XDB, 0XDB, 0XDB, 0XDB, 0XFE, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0XC6, 0XC6, 0XC6, 0XC6, 0XC6, 0XC6, 0XFC, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X7C, 0XC6, 0XC6, 0XC6, 0XC6, 0XC6, 0X7C, 0X00, 0X00, 0X00, 0X00 }, + { 0XC0, 0XC0, 0XC0, 0XFE, 0XC3, 0XC3, 0XC3, 0XC3, 0XFE, 0X00, 0X00, 0X00, 0X00 }, + { 0X03, 0X03, 0X03, 0X7F, 0XC3, 0XC3, 0XC3, 0XC3, 0X7F, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XE0, 0XFE, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0XFE, 0X03, 0X03, 0X7E, 0XC0, 0XC0, 0X7F, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X1C, 0X36, 0X30, 0X30, 0X30, 0X30, 0XFC, 0X30, 0X30, 0X30, 0X00 }, + { 0X00, 0X00, 0X7E, 0XC6, 0XC6, 0XC6, 0XC6, 0XC6, 0XC6, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X18, 0X3C, 0X3C, 0X66, 0X66, 0XC3, 0XC3, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0XC3, 0XE7, 0XFF, 0XDB, 0XC3, 0XC3, 0XC3, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0XC3, 0X66, 0X3C, 0X18, 0X3C, 0X66, 0XC3, 0X00, 0X00, 0X00, 0X00 }, + { 0XC0, 0X60, 0X60, 0X30, 0X18, 0X3C, 0X66, 0X66, 0XC3, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0XFF, 0X60, 0X30, 0X18, 0X0C, 0X06, 0XFF, 0X00, 0X00, 0X00, 0X00 }, + { 0X00, 0X00, 0X0F, 0X18, 0X18, 0X18, 0X38, 0XF0, 0X38, 0X18, 0X18, 0X18, 0X0F }, + { 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18 }, + { 0X00, 0X00, 0XF0, 0X18, 0X18, 0X18, 0X1C, 0X0F, 0X1C, 0X18, 0X18, 0X18, 0XF0 }, + { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X06, 0X8F, 0XF1, 0X60, 0X00, 0X00, 0X00 } +}; // sCharacters + + +void +BitmapFont13::initialize() +{ + OPENVDB_START_THREADSAFE_STATIC_WRITE + + glShadeModel(GL_FLAT); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + BitmapFont13::sOffset = glGenLists(128); + + for (GLuint c = 32; c < 127; ++c) { + glNewList(c + BitmapFont13::sOffset, GL_COMPILE); + glBitmap(8, 13, 0.0, 2.0, 10.0, 0.0, BitmapFont13::sCharacters[c-32]); + glEndList(); + } + OPENVDB_FINISH_THREADSAFE_STATIC_WRITE +} + + +void +BitmapFont13::enableFontRendering() +{ + glPushMatrix(); + int width, height; + glfwGetWindowSize(&width, &height); + height = height < 1 ? 1 : height; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glOrtho (0, width, 0, height, -1.0, 1.0); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + //glShadeModel(GL_FLAT); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); +} + + +void +BitmapFont13::disableFontRendering() +{ + glFlush(); + glPopMatrix(); +} + + +void +BitmapFont13::print(GLint px, GLint py, const std::string& str) +{ + glRasterPos2i(px, py); + glPushAttrib(GL_LIST_BIT); + glListBase(BitmapFont13::sOffset); + glCallLists(str.length(), GL_UNSIGNED_BYTE, reinterpret_cast(str.c_str())); + glPopAttrib(); +} + +} // namespace openvdb_viewer + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/viewer/Font.h b/openvdb_2_3_0_library/openvdb/viewer/Font.h new file mode 100755 index 0000000..ebb5156 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/viewer/Font.h @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_VIEWER_FONT_HAS_BEEN_INCLUDED +#define OPENVDB_VIEWER_FONT_HAS_BEEN_INCLUDED + +#include + +#if defined(__APPLE__) || defined(MACOSX) +#include +#include +#else +#include +#include +#endif + + +namespace openvdb_viewer { + +class BitmapFont13 +{ +public: + BitmapFont13() {} + + static void initialize(); + + static void enableFontRendering(); + static void disableFontRendering(); + + static void print(GLint px, GLint py, const std::string&); + +private: + static GLuint sOffset; + static GLubyte sCharacters[95][13]; +}; + +} // namespace openvdb_viewer + +#endif // OPENVDB_VIEWER_FONT_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/viewer/RenderModules.cc b/openvdb_2_3_0_library/openvdb/viewer/RenderModules.cc new file mode 100755 index 0000000..30011d8 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/viewer/RenderModules.cc @@ -0,0 +1,604 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "RenderModules.h" +#include +#include + + +namespace openvdb_viewer { + +// BufferObject + +BufferObject::BufferObject(): + mVertexBuffer(0), + mNormalBuffer(0), + mIndexBuffer(0), + mColorBuffer(0), + mPrimType(GL_POINTS), + mPrimNum(0) +{ +} + +BufferObject::~BufferObject() { clear(); } + +void +BufferObject::render() const +{ + if (mPrimNum == 0 || !glIsBuffer(mIndexBuffer) || !glIsBuffer(mVertexBuffer)) { + OPENVDB_LOG_DEBUG_RUNTIME("request to render empty or uninitialized buffer"); + return; + } + + const bool usesColorBuffer = glIsBuffer(mColorBuffer); + const bool usesNormalBuffer = glIsBuffer(mNormalBuffer); + + glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, 0); + + if (usesColorBuffer) { + glBindBuffer(GL_ARRAY_BUFFER, mColorBuffer); + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(3, GL_FLOAT, 0, 0); + } + + if (usesNormalBuffer) { + glEnableClientState(GL_NORMAL_ARRAY); + glBindBuffer(GL_ARRAY_BUFFER, mNormalBuffer); + glNormalPointer(GL_FLOAT, 0, 0); + } + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); + glDrawElements(mPrimType, mPrimNum, GL_UNSIGNED_INT, 0); + + // disable client-side capabilities + if (usesColorBuffer) glDisableClientState(GL_COLOR_ARRAY); + if (usesNormalBuffer) glDisableClientState(GL_NORMAL_ARRAY); + + // release vbo's + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +void +BufferObject::genIndexBuffer(const std::vector& v, GLenum primType) +{ + // clear old buffer + if (glIsBuffer(mIndexBuffer) == GL_TRUE) glDeleteBuffers(1, &mIndexBuffer); + + // gen new buffer + glGenBuffers(1, &mIndexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); + if (glIsBuffer(mIndexBuffer) == GL_FALSE) throw "Error: Unable to create index buffer"; + + // upload data + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + sizeof(GLuint) * v.size(), &v[0], GL_STATIC_DRAW); // upload data + if (GL_NO_ERROR != glGetError()) throw "Error: Unable to upload index buffer data"; + + // release buffer + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + mPrimNum = v.size(); + mPrimType = primType; +} + +void +BufferObject::genVertexBuffer(const std::vector& v) +{ + if (glIsBuffer(mVertexBuffer) == GL_TRUE) glDeleteBuffers(1, &mVertexBuffer); + + glGenBuffers(1, &mVertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); + if (glIsBuffer(mVertexBuffer) == GL_FALSE) throw "Error: Unable to create vertex buffer"; + + glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * v.size(), &v[0], GL_STATIC_DRAW); + if (GL_NO_ERROR != glGetError()) throw "Error: Unable to upload vertex buffer data"; + + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +void +BufferObject::genNormalBuffer(const std::vector& v) +{ + if (glIsBuffer(mNormalBuffer) == GL_TRUE) glDeleteBuffers(1, &mNormalBuffer); + + glGenBuffers(1, &mNormalBuffer); + glBindBuffer(GL_ARRAY_BUFFER, mNormalBuffer); + if (glIsBuffer(mNormalBuffer) == GL_FALSE) throw "Error: Unable to create normal buffer"; + + glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * v.size(), &v[0], GL_STATIC_DRAW); + if (GL_NO_ERROR != glGetError()) throw "Error: Unable to upload normal buffer data"; + + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +void +BufferObject::genColorBuffer(const std::vector& v) +{ + if (glIsBuffer(mColorBuffer) == GL_TRUE) glDeleteBuffers(1, &mColorBuffer); + + glGenBuffers(1, &mColorBuffer); + glBindBuffer(GL_ARRAY_BUFFER, mColorBuffer); + if (glIsBuffer(mColorBuffer) == GL_FALSE) throw "Error: Unable to create color buffer"; + + glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * v.size(), &v[0], GL_STATIC_DRAW); + if (GL_NO_ERROR != glGetError()) throw "Error: Unable to upload color buffer data"; + + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +void +BufferObject::clear() +{ + if (glIsBuffer(mIndexBuffer) == GL_TRUE) glDeleteBuffers(1, &mIndexBuffer); + if (glIsBuffer(mVertexBuffer) == GL_TRUE) glDeleteBuffers(1, &mVertexBuffer); + if (glIsBuffer(mColorBuffer) == GL_TRUE) glDeleteBuffers(1, &mColorBuffer); + if (glIsBuffer(mNormalBuffer) == GL_TRUE) glDeleteBuffers(1, &mNormalBuffer); + + mPrimType = GL_POINTS; + mPrimNum = 0; +} + + +//////////////////////////////////////// + + +ShaderProgram::ShaderProgram(): + mProgram(0), + mVertShader(0), + mFragShader(0) +{ +} + +ShaderProgram::~ShaderProgram() { clear(); } + +void +ShaderProgram::setVertShader(const std::string& s) +{ + mVertShader = glCreateShader(GL_VERTEX_SHADER); + if (glIsShader(mVertShader) == GL_FALSE) throw "Error: Unable to create shader program."; + + GLint length = s.length(); + const char *str = s.c_str(); + glShaderSource(mVertShader, 1, &str, &length); + + glCompileShader(mVertShader); + if (GL_NO_ERROR != glGetError()) throw "Error: Unable to compile vertex shader."; +} + +void +ShaderProgram::setFragShader(const std::string& s) +{ + mFragShader = glCreateShader(GL_FRAGMENT_SHADER); + if (glIsShader(mFragShader) == GL_FALSE) throw "Error: Unable to create shader program."; + + GLint length = s.length(); + const char *str = s.c_str(); + glShaderSource(mFragShader, 1, &str, &length); + + glCompileShader(mFragShader); + if (GL_NO_ERROR != glGetError()) throw "Error: Unable to compile fragment shader."; +} + +void +ShaderProgram::build() +{ + mProgram = glCreateProgram(); + if (glIsProgram(mProgram) == GL_FALSE) throw "Error: Unable to create shader program."; + + if (glIsShader(mVertShader) == GL_TRUE) glAttachShader(mProgram, mVertShader); + if (GL_NO_ERROR != glGetError()) throw "Error: Unable to attach vertex shader."; + + if (glIsShader(mFragShader) == GL_TRUE) glAttachShader(mProgram, mFragShader); + if (GL_NO_ERROR != glGetError()) throw "Error: Unable to attach fragment shader."; + + + glLinkProgram(mProgram); + + GLint linked; + glGetProgramiv(mProgram, GL_LINK_STATUS, &linked); + + if (!linked) throw "Error: Unable to link shader program."; +} + +void +ShaderProgram::build(const std::vector& attributes) +{ + mProgram = glCreateProgram(); + if (glIsProgram(mProgram) == GL_FALSE) throw "Error: Unable to create shader program."; + + + for (GLuint n = 0, N = attributes.size(); n < N; ++n) { + glBindAttribLocation(mProgram, n, attributes[n]); + } + + if (glIsShader(mVertShader) == GL_TRUE) glAttachShader(mProgram, mVertShader); + if (GL_NO_ERROR != glGetError()) throw "Error: Unable to attach vertex shader."; + + if (glIsShader(mFragShader) == GL_TRUE) glAttachShader(mProgram, mFragShader); + if (GL_NO_ERROR != glGetError()) throw "Error: Unable to attach fragment shader."; + + + glLinkProgram(mProgram); + + GLint linked; + glGetProgramiv(mProgram, GL_LINK_STATUS, &linked); + + if (!linked) throw "Error: Unable to link shader program."; +} + +void +ShaderProgram::startShading() const +{ + if (glIsProgram(mProgram) == GL_FALSE) + throw "Error: called startShading() on uncompiled shader program."; + + glUseProgram(mProgram); +} + +void +ShaderProgram::stopShading() const +{ + glUseProgram(0); +} + +void +ShaderProgram::clear() +{ + GLsizei numShaders; + GLuint shaders[2]; + + glGetAttachedShaders(mProgram, 2, &numShaders, shaders); + + // detach and remove shaders + for (GLsizei n = 0; n < numShaders; ++n) { + + glDetachShader(mProgram, shaders[n]); + + if (glIsShader(shaders[n]) == GL_TRUE) glDeleteShader(shaders[n]); + } + + // remove program + if (glIsProgram(mProgram)) glDeleteProgram(mProgram); +} + + +//////////////////////////////////////// + + +openvdb::Vec3s TreeTopologyOp::sNodeColors[] = { + openvdb::Vec3s(0.045, 0.045, 0.045), // root + openvdb::Vec3s(0.0432, 0.33, 0.0411023), // first internal node level + openvdb::Vec3s(0.871, 0.394, 0.01916), // intermediate internal node levels + openvdb::Vec3s(0.00608299, 0.279541, 0.625) // leaf nodes +}; + + +//////////////////////////////////////// + +// ViewportModule + +ViewportModule::ViewportModule(): + mAxisGnomonScale(1.5), + mGroundPlaneScale(8.0) +{ +} + + +void +ViewportModule::render() +{ + if (!mIsVisible) return; + + /// @todo use VBO's + + // Ground plane + glPushMatrix(); + glScalef(mGroundPlaneScale, mGroundPlaneScale, mGroundPlaneScale); + glColor3f( 0.6, 0.6, 0.6); + + OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN + + float step = 0.125; + for (float x = -1; x < 1.125; x+=step) { + + if (fabs(x) == 0.5 || fabs(x) == 0.0) { + glLineWidth(1.5); + } else { + glLineWidth(1.0); + } + + glBegin( GL_LINES ); + glVertex3f(x, 0, 1); + glVertex3f(x, 0, -1); + glVertex3f(1, 0, x); + glVertex3f(-1, 0, x); + glEnd(); + } + + OPENVDB_NO_FP_EQUALITY_WARNING_END + + + glPopMatrix(); + + // Axis gnomon + GLfloat modelview[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, &modelview[0]); + + // Stash current viewport settigs. + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, &viewport[0]); + + GLint width = viewport[2] / 20; + GLint height = viewport[3] / 20; + glViewport(0, 0, width, height); + + + glPushMatrix(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + + GLfloat campos[3] = { modelview[2], modelview[6], modelview[10] }; + GLfloat up[3] = { modelview[1], modelview[5], modelview[9] }; + + gluLookAt(campos[0], campos[1], campos[2], 0.0, 0.0, 0.0, up[0], up[1], up[2]); + + glScalef(mAxisGnomonScale, mAxisGnomonScale, mAxisGnomonScale); + + glLineWidth(1.0); + + glBegin(GL_LINES); + glColor3f(1.0f, 0.0f, 0.0f); + glVertex3f(0, 0, 0); + glVertex3f(1, 0, 0); + + glColor3f(0.0f, 1.0f, 0.0f ); + glVertex3f(0, 0, 0); + glVertex3f(0, 1, 0); + + glColor3f(0.0f, 0.0f, 1.0f); + glVertex3f(0, 0, 0); + glVertex3f(0, 0, 1); + glEnd(); + + glLineWidth(1.0); + + // reset viewport + glPopMatrix(); + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); + +} + + +//////////////////////////////////////// + +// Tree topology render module + +TreeTopologyModule::TreeTopologyModule(const openvdb::GridBase::ConstPtr& grid): + mGrid(grid), + mIsInitialized(false) +{ + mShader.setVertShader( + "#version 120\n" + "void main() {\n" + "gl_FrontColor = gl_Color;\n" + "gl_Position = ftransform();\n" + "gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" + "}\n"); + + mShader.setFragShader( + "#version 120\n" + "void main() {\n" + "gl_FragColor = gl_Color;}\n"); + + mShader.build(); +} + + +void +TreeTopologyModule::init() +{ + mIsInitialized = true; + + // extract grid topology + TreeTopologyOp drawTopology(mBufferObject); + + if (!util::processTypedGrid(mGrid, drawTopology)) { + OPENVDB_LOG_INFO("Ignoring unrecognized grid type" + " during tree topology module initialization."); + } +} + + +void +TreeTopologyModule::render() +{ + if (!mIsVisible) return; + if (!mIsInitialized) init(); + + mShader.startShading(); + + mBufferObject.render(); + + mShader.stopShading(); +} + +//////////////////////////////////////// + +// Active value render module + +ActiveValueModule::ActiveValueModule(const openvdb::GridBase::ConstPtr& grid): + mGrid(grid), + mIsInitialized(false) +{ + mFlatShader.setVertShader( + "#version 120\n" + "void main() {\n" + "gl_FrontColor = gl_Color;\n" + "gl_Position = ftransform();\n" + "gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" + "}\n"); + + mFlatShader.setFragShader( + "#version 120\n" + "void main() {\n" + "gl_FragColor = gl_Color;}\n"); + + mFlatShader.build(); + + mSurfaceShader.setVertShader( + "#version 120\n" + "varying vec3 normal;\n" + "void main() {\n" + "gl_FrontColor = gl_Color;\n" + "normal = normalize(gl_NormalMatrix * gl_Normal);\n" + "gl_Position = ftransform();\n" + "gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" + "}\n"); + + + mSurfaceShader.setFragShader( + "#version 120\n" + "varying vec3 normal;\n" + "void main() {\n" + "vec3 normalized_normal = normalize(normal);\n" + "float w = 0.5 * (1.0 + dot(normalized_normal, vec3(0.0, 1.0, 0.0)));\n" + "vec4 diffuseColor = w * gl_Color + (1.0 - w) * (gl_Color * 0.3);\n" + "gl_FragColor = diffuseColor;\n" + "}\n"); + + mSurfaceShader.build(); +} + + +void +ActiveValueModule::init() +{ + mIsInitialized = true; + + ActiveScalarValuesOp drawScalars(mInteriorBuffer, mSurfaceBuffer); + + if (!util::processTypedScalarGrid(mGrid, drawScalars)) { + + ActiveVectorValuesOp drawVectors(mVectorBuffer); + + if(!util::processTypedVectorGrid(mGrid, drawVectors)) { + OPENVDB_LOG_INFO("Ignoring unrecognized grid type" + " during active value module initialization."); + } + } +} + + +void +ActiveValueModule::render() +{ + if (!mIsVisible) return; + if (!mIsInitialized) init(); + + mFlatShader.startShading(); + mInteriorBuffer.render(); + mVectorBuffer.render(); + mFlatShader.stopShading(); + + mSurfaceShader.startShading(); + mSurfaceBuffer.render(); + mSurfaceShader.stopShading(); +} + + +//////////////////////////////////////// + +// Meshing module + +MeshModule::MeshModule(const openvdb::GridBase::ConstPtr& grid): + mGrid(grid), + mIsInitialized(false) +{ + mShader.setVertShader( + "#version 120\n" + "varying vec3 normal;\n" + "void main() {\n" + "normal = normalize(gl_NormalMatrix * gl_Normal);\n" + "gl_Position = ftransform();\n" + "gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" + "}\n"); + + mShader.setFragShader( + "#version 120\n" + "varying vec3 normal;\n" + "const vec4 skyColor = vec4(0.9, 0.9, 1.0, 1.0);\n" + "const vec4 groundColor = vec4(0.3, 0.3, 0.2, 1.0);\n" + "void main() {\n" + "vec3 normalized_normal = normalize(normal);\n" + "float w = 0.5 * (1.0 + dot(normalized_normal, vec3(0.0, 1.0, 0.0)));\n" + "vec4 diffuseColor = w * skyColor + (1.0 - w) * groundColor;\n" + "gl_FragColor = diffuseColor;\n" + "}\n"); + + mShader.build(); +} + + +void +MeshModule::init() +{ + mIsInitialized = true; + + MeshOp drawMesh(mBufferObject); + + if (!util::processTypedScalarGrid(mGrid, drawMesh)) { + OPENVDB_LOG_INFO( + "Ignoring non-scalar grid type during mesh module initialization."); + } +} + + +void +MeshModule::render() +{ + if (!mIsVisible) return; + if (!mIsInitialized) init(); + + mShader.startShading(); + + mBufferObject.render(); + + mShader.stopShading(); +} + +} // namespace openvdb_viewer + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/viewer/RenderModules.h b/openvdb_2_3_0_library/openvdb/viewer/RenderModules.h new file mode 100755 index 0000000..834dff7 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/viewer/RenderModules.h @@ -0,0 +1,1086 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_VIEWER_RENDERMODULES_HAS_BEEN_INCLUDED +#define OPENVDB_VIEWER_RENDERMODULES_HAS_BEEN_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include + +#if defined(__APPLE__) || defined(MACOSX) +#include +#include +#else +#include +#include +#endif + + +namespace openvdb_viewer { + +// OpenGL helper objects. + +class BufferObject +{ +public: + BufferObject(); + ~BufferObject(); + + void render() const; + + /// @note accepted @c primType: GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, + /// GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, + /// GL_QUAD_STRIP, GL_QUADS and GL_POLYGON + void genIndexBuffer(const std::vector&, GLenum primType); + + void genVertexBuffer(const std::vector&); + void genNormalBuffer(const std::vector&); + void genColorBuffer(const std::vector&); + + void clear(); + +private: + GLuint mVertexBuffer, mNormalBuffer, mIndexBuffer, mColorBuffer; + GLenum mPrimType; + GLsizei mPrimNum; +}; + + +class ShaderProgram +{ +public: + ShaderProgram(); + ~ShaderProgram(); + + void setVertShader(const std::string&); + void setFragShader(const std::string&); + + void build(); + void build(const std::vector& attributes); + + void startShading() const; + void stopShading() const; + + void clear(); + +private: + GLuint mProgram, mVertShader, mFragShader; +}; + + +//////////////////////////////////////// + + +/// @brief interface class. +class RenderModule +{ +public: + virtual void render() = 0; + virtual ~RenderModule() {} + virtual bool& visible() { return mIsVisible; } +protected: + RenderModule() : mIsVisible(true) {} + bool mIsVisible; +}; + + +//////////////////////////////////////// + + +/// @brief Basic render module, axis gnomon and ground plane. +class ViewportModule: public RenderModule +{ +public: + ViewportModule(); + void render(); + +private: + float mAxisGnomonScale, mGroundPlaneScale; +}; + + +//////////////////////////////////////// + + +/// @brief Tree topology render module +class TreeTopologyModule: public RenderModule +{ +public: + TreeTopologyModule(const openvdb::GridBase::ConstPtr&); + ~TreeTopologyModule() {} + + void render(); + +private: + void init(); + const openvdb::GridBase::ConstPtr& mGrid; + BufferObject mBufferObject; + bool mIsInitialized; + ShaderProgram mShader; +}; + + +//////////////////////////////////////// + + +/// @brief Tree topology render module +class ActiveValueModule: public RenderModule +{ +public: + ActiveValueModule(const openvdb::GridBase::ConstPtr&); + ~ActiveValueModule() {} + + void render(); + +private: + void init(); + const openvdb::GridBase::ConstPtr& mGrid; + + BufferObject mInteriorBuffer, mSurfaceBuffer, mVectorBuffer; + bool mIsInitialized; + ShaderProgram mFlatShader, mSurfaceShader; +}; + + +//////////////////////////////////////// + + +/// @brief Surfacing render module +class MeshModule: public RenderModule +{ +public: + MeshModule(const openvdb::GridBase::ConstPtr&); + ~MeshModule() {} + + void render(); +private: + void init(); + const openvdb::GridBase::ConstPtr& mGrid; + + BufferObject mBufferObject; + bool mIsInitialized; + ShaderProgram mShader; +}; + + +//////////////////////////////////////// + + +class TreeTopologyOp +{ +public: + TreeTopologyOp(BufferObject& buffer) : mBuffer(&buffer) {} + + template + void operator()(typename GridType::ConstPtr grid) + { + using openvdb::Index64; + + Index64 nodeCount = grid->tree().leafCount() + grid->tree().nonLeafCount(); + const Index64 N = nodeCount * 8 * 3; + + std::vector points(N); + std::vector colors(N); + std::vector indices(N); + + + openvdb::Vec3d ptn; + openvdb::Vec3s color; + openvdb::CoordBBox bbox; + Index64 pOffset = 0, iOffset = 0, cOffset = 0, idx = 0; + + for (typename GridType::TreeType::NodeCIter iter = grid->tree().cbeginNode(); iter; ++iter) + { + iter.getBoundingBox(bbox); + + // Nodes are rendered as cell-centered + const openvdb::Vec3d min(bbox.min().x()-0.5, bbox.min().y()-0.5, bbox.min().z()-0.5); + const openvdb::Vec3d max(bbox.max().x()+0.5, bbox.max().y()+0.5, bbox.max().z()+0.5); + + // corner 1 + ptn = grid->indexToWorld(min); + points[pOffset++] = ptn[0]; + points[pOffset++] = ptn[1]; + points[pOffset++] = ptn[2]; + + // corner 2 + ptn = openvdb::Vec3d(min.x(), min.y(), max.z()); + ptn = grid->indexToWorld(ptn); + points[pOffset++] = ptn[0]; + points[pOffset++] = ptn[1]; + points[pOffset++] = ptn[2]; + + // corner 3 + ptn = openvdb::Vec3d(max.x(), min.y(), max.z()); + ptn = grid->indexToWorld(ptn); + points[pOffset++] = ptn[0]; + points[pOffset++] = ptn[1]; + points[pOffset++] = ptn[2]; + + // corner 4 + ptn = openvdb::Vec3d(max.x(), min.y(), min.z()); + ptn = grid->indexToWorld(ptn); + points[pOffset++] = ptn[0]; + points[pOffset++] = ptn[1]; + points[pOffset++] = ptn[2]; + + // corner 5 + ptn = openvdb::Vec3d(min.x(), max.y(), min.z()); + ptn = grid->indexToWorld(ptn); + points[pOffset++] = ptn[0]; + points[pOffset++] = ptn[1]; + points[pOffset++] = ptn[2]; + + // corner 6 + ptn = openvdb::Vec3d(min.x(), max.y(), max.z()); + ptn = grid->indexToWorld(ptn); + points[pOffset++] = ptn[0]; + points[pOffset++] = ptn[1]; + points[pOffset++] = ptn[2]; + + // corner 7 + ptn = grid->indexToWorld(max); + points[pOffset++] = ptn[0]; + points[pOffset++] = ptn[1]; + points[pOffset++] = ptn[2]; + + // corner 8 + ptn = openvdb::Vec3d(max.x(), max.y(), min.z()); + ptn = grid->indexToWorld(ptn); + points[pOffset++] = ptn[0]; + points[pOffset++] = ptn[1]; + points[pOffset++] = ptn[2]; + + + // edge 1 + indices[iOffset++] = idx; + indices[iOffset++] = idx + 1; + // edge 2 + indices[iOffset++] = idx + 1; + indices[iOffset++] = idx + 2; + // edge 3 + indices[iOffset++] = idx + 2; + indices[iOffset++] = idx + 3; + // edge 4 + indices[iOffset++] = idx + 3; + indices[iOffset++] = idx; + // edge 5 + indices[iOffset++] = idx + 4; + indices[iOffset++] = idx + 5; + // edge 6 + indices[iOffset++] = idx + 5; + indices[iOffset++] = idx + 6; + // edge 7 + indices[iOffset++] = idx + 6; + indices[iOffset++] = idx + 7; + // edge 8 + indices[iOffset++] = idx + 7; + indices[iOffset++] = idx + 4; + // edge 9 + indices[iOffset++] = idx; + indices[iOffset++] = idx + 4; + // edge 10 + indices[iOffset++] = idx + 1; + indices[iOffset++] = idx + 5; + // edge 11 + indices[iOffset++] = idx + 2; + indices[iOffset++] = idx + 6; + // edge 12 + indices[iOffset++] = idx + 3; + indices[iOffset++] = idx + 7; + + + // node vertex color + const int level = iter.getLevel(); + color = sNodeColors[(level == 0) ? 3 : (level == 1) ? 2 : 1]; + + for (Index64 n = 0; n < 8; ++n) { + colors[cOffset++] = color[0]; + colors[cOffset++] = color[1]; + colors[cOffset++] = color[2]; + } + + idx += 8; + } // end node iteration + + // gen buffers and upload data to GPU + mBuffer->genVertexBuffer(points); + mBuffer->genColorBuffer(colors); + mBuffer->genIndexBuffer(indices, GL_LINES); + } + +private: + BufferObject *mBuffer; + + static openvdb::Vec3s sNodeColors[]; + +}; // TreeTopologyOp + + +//////////////////////////////////////// + +template +class PointGenerator +{ +public: + typedef openvdb::tree::LeafManager LeafManagerType; + + PointGenerator( + std::vector& points, + std::vector& indices, + LeafManagerType& leafs, + std::vector& indexMap, + const openvdb::math::Transform& transform, + openvdb::Index64 voxelsPerLeaf = TreeType::LeafNodeType::NUM_VOXELS) + : mPoints(points) + , mIndices(indices) + , mLeafs(leafs) + , mIndexMap(indexMap) + , mTransform(transform) + , mVoxelsPerLeaf(voxelsPerLeaf) + { + } + + void runParallel() + { + tbb::parallel_for(mLeafs.getRange(), *this); + } + + + inline void operator()(const typename LeafManagerType::RangeType& range) const + { + using openvdb::Index64; + + typedef typename TreeType::LeafNodeType::ValueOnCIter ValueOnCIter; + + openvdb::Vec3d pos; + size_t index = 0; + Index64 activeVoxels = 0; + + for (size_t n = range.begin(); n < range.end(); ++n) { + + index = mIndexMap[n]; + ValueOnCIter it = mLeafs.leaf(n).cbeginValueOn(); + + activeVoxels = mLeafs.leaf(n).onVoxelCount(); + + if (activeVoxels <= mVoxelsPerLeaf) { + + for ( ; it; ++it) { + pos = mTransform.indexToWorld(it.getCoord()); + insertPoint(pos, index); + ++index; + } + + } else if (1 == mVoxelsPerLeaf) { + + pos = mTransform.indexToWorld(it.getCoord()); + insertPoint(pos, index); + + } else { + + std::vector coords; + coords.reserve(static_cast(activeVoxels)); + for ( ; it; ++it) { coords.push_back(it.getCoord()); } + + pos = mTransform.indexToWorld(coords[0]); + insertPoint(pos, index); + ++index; + + pos = mTransform.indexToWorld(coords[static_cast(activeVoxels-1)]); + insertPoint(pos, index); + ++index; + + Index64 r = Index64(std::floor(double(mVoxelsPerLeaf) / activeVoxels)); + for (Index64 i = 1, I = mVoxelsPerLeaf - 2; i < I; ++i) { + pos = mTransform.indexToWorld(coords[static_cast(i * r)]); + insertPoint(pos, index); + ++index; + } + } + } + } + +private: + void insertPoint(const openvdb::Vec3d& pos, size_t index) const + { + mIndices[index] = index; + const size_t element = index * 3; + mPoints[element ] = pos[0]; + mPoints[element + 1] = pos[1]; + mPoints[element + 2] = pos[2]; + } + + std::vector& mPoints; + std::vector& mIndices; + LeafManagerType& mLeafs; + std::vector& mIndexMap; + const openvdb::math::Transform& mTransform; + const openvdb::Index64 mVoxelsPerLeaf; +}; // PointGenerator + + +template +class PointAttributeGenerator +{ +public: + typedef typename GridType::ValueType ValueType; + + PointAttributeGenerator( + std::vector& points, + std::vector& colors, + const GridType& grid, + ValueType minValue, + ValueType maxValue, + openvdb::Vec3s (&colorMap)[4], + bool isLevelSet = false) + : mPoints(points) + , mColors(colors) + , mNormals(NULL) + , mGrid(grid) + , mAccessor(grid.tree()) + , mMinValue(minValue) + , mMaxValue(maxValue) + , mColorMap(colorMap) + , mIsLevelSet(isLevelSet) + , mZeroValue(openvdb::zeroVal()) + { + init(); + } + + PointAttributeGenerator( + std::vector& points, + std::vector& colors, + std::vector& normals, + const GridType& grid, + ValueType minValue, + ValueType maxValue, + openvdb::Vec3s (&colorMap)[4], + bool isLevelSet = false) + : mPoints(points) + , mColors(colors) + , mNormals(&normals) + , mGrid(grid) + , mAccessor(grid.tree()) + , mMinValue(minValue) + , mMaxValue(maxValue) + , mColorMap(colorMap) + , mIsLevelSet(isLevelSet) + , mZeroValue(openvdb::zeroVal()) + { + init(); + } + + void runParallel() + { + tbb::parallel_for(tbb::blocked_range(0, (mPoints.size() / 3)), *this); + } + + inline void operator()(const tbb::blocked_range& range) const + { + openvdb::Coord ijk; + openvdb::Vec3d pos, tmpNormal, normal(0.0, -1.0, 0.0); + openvdb::Vec3s color(0.9, 0.3, 0.3); + float w = 0.0; + + size_t e1, e2, e3, voxelNum = 0; + for (size_t n = range.begin(); n < range.end(); ++n) { + e1 = 3 * n; + e2 = e1 + 1; + e3 = e2 + 1; + + pos[0] = mPoints[e1]; + pos[1] = mPoints[e2]; + pos[2] = mPoints[e3]; + + pos = mGrid.worldToIndex(pos); + ijk[0] = int(pos[0]); + ijk[1] = int(pos[1]); + ijk[2] = int(pos[2]); + + const ValueType& value = mAccessor.getValue(ijk); + + if (value < mZeroValue) { // is negative + if (mIsLevelSet) { + color = mColorMap[1]; + } else { + w = (float(value) - mOffset[1]) * mScale[1]; + color = w * mColorMap[0] + (1.0 - w) * mColorMap[1]; + } + } else { + if (mIsLevelSet) { + color = mColorMap[2]; + } else { + w = (float(value) - mOffset[0]) * mScale[0]; + color = w * mColorMap[2] + (1.0 - w) * mColorMap[3]; + } + } + + mColors[e1] = color[0]; + mColors[e2] = color[1]; + mColors[e3] = color[2]; + + if (mNormals) { + + if ((voxelNum % 2) == 0) { + tmpNormal = openvdb::math::ISGradient< + openvdb::math::CD_2ND>::result(mAccessor, ijk); + + double length = tmpNormal.length(); + if (length > 1.0e-7) { + tmpNormal *= 1.0 / length; + normal = tmpNormal; + } + } + ++voxelNum; + + (*mNormals)[e1] = normal[0]; + (*mNormals)[e2] = normal[1]; + (*mNormals)[e3] = normal[2]; + } + } + } + +private: + + void init() + { + mOffset[0] = float(std::min(mZeroValue, mMinValue)); + mScale[0] = 1.0 / float(std::abs(std::max(mZeroValue, mMaxValue) - mOffset[0])); + mOffset[1] = float(std::min(mZeroValue, mMinValue)); + mScale[1] = 1.0 / float(std::abs(std::max(mZeroValue, mMaxValue) - mOffset[1])); + } + + std::vector& mPoints; + std::vector& mColors; + std::vector* mNormals; + + const GridType& mGrid; + openvdb::tree::ValueAccessor mAccessor; + + ValueType mMinValue, mMaxValue; + openvdb::Vec3s (&mColorMap)[4]; + const bool mIsLevelSet; + + ValueType mZeroValue; + float mOffset[2], mScale[2]; +}; // PointAttributeGenerator + + +//////////////////////////////////////// + + +class ActiveScalarValuesOp +{ +public: + + ActiveScalarValuesOp( + BufferObject& interiorBuffer, BufferObject& surfaceBuffer) + : mInteriorBuffer(&interiorBuffer) + , mSurfaceBuffer(&surfaceBuffer) + { + } + + template + void operator()(typename GridType::ConstPtr grid) + { + using openvdb::Index64; + + const Index64 maxVoxelPoints = 26000000; + + openvdb::Vec3s colorMap[4]; + colorMap[0] = openvdb::Vec3s(0.3, 0.9, 0.3); // green + colorMap[1] = openvdb::Vec3s(0.9, 0.3, 0.3); // red + colorMap[2] = openvdb::Vec3s(0.9, 0.9, 0.3); // yellow + colorMap[3] = openvdb::Vec3s(0.3, 0.3, 0.9); // blue + + ////////// + + typedef typename GridType::ValueType ValueType; + typedef typename GridType::TreeType TreeType; + typedef typename TreeType::template ValueConverter::Type BoolTreeT; + + const TreeType& tree = grid->tree(); + const bool isLevelSetGrid = grid->getGridClass() == openvdb::GRID_LEVEL_SET; + + ValueType minValue, maxValue; + openvdb::tree::LeafManager leafs(tree); + + { + openvdb::tools::MinMaxVoxel minmax(leafs); + minmax.runParallel(); + minValue = minmax.minVoxel(); + maxValue = minmax.maxVoxel(); + } + + openvdb::Index64 voxelsPerLeaf = TreeType::LeafNodeType::NUM_VOXELS; + + if (!isLevelSetGrid) { + + typename BoolTreeT::Ptr interiorMask(new BoolTreeT(false)); + + { // Generate Interior Points + interiorMask->topologyUnion(tree); + interiorMask->voxelizeActiveTiles(); + + if (interiorMask->activeLeafVoxelCount() > maxVoxelPoints) { + voxelsPerLeaf = std::max(1, + (maxVoxelPoints / interiorMask->leafCount())); + } + + openvdb::tools::erodeVoxels(*interiorMask, 2); + + openvdb::tree::LeafManager maskleafs(*interiorMask); + std::vector indexMap(maskleafs.leafCount()); + size_t voxelCount = 0; + for (Index64 l = 0, L = maskleafs.leafCount(); l < L; ++l) { + indexMap[l] = voxelCount; + voxelCount += std::min(maskleafs.leaf(l).onVoxelCount(), voxelsPerLeaf); + } + + std::vector points(voxelCount * 3), colors(voxelCount * 3); + std::vector indices(voxelCount); + + PointGenerator pointGen( + points, indices, maskleafs, indexMap, grid->transform(), voxelsPerLeaf); + pointGen.runParallel(); + + + PointAttributeGenerator attributeGen( + points, colors, *grid, minValue, maxValue, colorMap); + attributeGen.runParallel(); + + + // gen buffers and upload data to GPU + mInteriorBuffer->genVertexBuffer(points); + mInteriorBuffer->genColorBuffer(colors); + mInteriorBuffer->genIndexBuffer(indices, GL_POINTS); + } + + { // Generate Surface Points + typename BoolTreeT::Ptr surfaceMask(new BoolTreeT(false)); + surfaceMask->topologyUnion(tree); + surfaceMask->voxelizeActiveTiles(); + + openvdb::tree::ValueAccessor interiorAcc(*interiorMask); + for (typename BoolTreeT::LeafIter leafIt = surfaceMask->beginLeaf(); + leafIt; ++leafIt) + { + const typename BoolTreeT::LeafNodeType* leaf = + interiorAcc.probeConstLeaf(leafIt->origin()); + if (leaf) leafIt->topologyDifference(*leaf, false); + } + surfaceMask->pruneInactive(); + + openvdb::tree::LeafManager maskleafs(*surfaceMask); + std::vector indexMap(maskleafs.leafCount()); + size_t voxelCount = 0; + for (Index64 l = 0, L = maskleafs.leafCount(); l < L; ++l) { + indexMap[l] = voxelCount; + voxelCount += std::min(maskleafs.leaf(l).onVoxelCount(), voxelsPerLeaf); + } + + std::vector + points(voxelCount * 3), + colors(voxelCount * 3), + normals(voxelCount * 3); + std::vector indices(voxelCount); + + PointGenerator pointGen( + points, indices, maskleafs, indexMap, grid->transform(), voxelsPerLeaf); + pointGen.runParallel(); + + PointAttributeGenerator attributeGen( + points, colors, normals, *grid, minValue, maxValue, colorMap); + attributeGen.runParallel(); + + mSurfaceBuffer->genVertexBuffer(points); + mSurfaceBuffer->genColorBuffer(colors); + mSurfaceBuffer->genNormalBuffer(normals); + mSurfaceBuffer->genIndexBuffer(indices, GL_POINTS); + } + + return; + } + + // Level set rendering + if (tree.activeLeafVoxelCount() > maxVoxelPoints) { + voxelsPerLeaf = std::max(1, (maxVoxelPoints / tree.leafCount())); + } + + std::vector indexMap(leafs.leafCount()); + size_t voxelCount = 0; + for (Index64 l = 0, L = leafs.leafCount(); l < L; ++l) { + indexMap[l] = voxelCount; + voxelCount += std::min(leafs.leaf(l).onVoxelCount(), voxelsPerLeaf); + } + + std::vector + points(voxelCount * 3), + colors(voxelCount * 3), + normals(voxelCount * 3); + std::vector indices(voxelCount); + + PointGenerator pointGen( + points, indices, leafs, indexMap, grid->transform(), voxelsPerLeaf); + pointGen.runParallel(); + + PointAttributeGenerator attributeGen( + points, colors, normals, *grid, minValue, maxValue, colorMap, isLevelSetGrid); + attributeGen.runParallel(); + + mSurfaceBuffer->genVertexBuffer(points); + mSurfaceBuffer->genColorBuffer(colors); + mSurfaceBuffer->genNormalBuffer(normals); + mSurfaceBuffer->genIndexBuffer(indices, GL_POINTS); + } + +private: + BufferObject *mInteriorBuffer; + BufferObject *mSurfaceBuffer; +}; // ActiveScalarValuesOp + + +class ActiveVectorValuesOp +{ +public: + + ActiveVectorValuesOp(BufferObject& vectorBuffer) + : mVectorBuffer(&vectorBuffer) + { + } + + template + void operator()(typename GridType::ConstPtr grid) + { + using openvdb::Index64; + + typedef typename GridType::ValueType ValueType; + typedef typename GridType::TreeType TreeType; + typedef typename TreeType::template ValueConverter::Type BoolTreeT; + + + const TreeType& tree = grid->tree(); + + double length = 0.0; + { + ValueType minVal, maxVal; + tree.evalMinMax(minVal, maxVal); + length = maxVal.length(); + } + + typename BoolTreeT::Ptr mask(new BoolTreeT(false)); + mask->topologyUnion(tree); + mask->voxelizeActiveTiles(); + + ///@todo thread and restructure. + + const Index64 voxelCount = mask->activeLeafVoxelCount(); + + const Index64 pointCount = voxelCount * 2; + std::vector points(pointCount*3), colors(pointCount*3); + std::vector indices(pointCount); + + openvdb::Coord ijk; + openvdb::Vec3d pos, color, normal; + openvdb::tree::LeafManager leafs(*mask); + + openvdb::tree::ValueAccessor acc(tree); + + Index64 idx = 0, pt = 0, cc = 0; + for (Index64 l = 0, L = leafs.leafCount(); l < L; ++l) { + typename BoolTreeT::LeafNodeType::ValueOnIter iter = leafs.leaf(l).beginValueOn(); + for (; iter; ++iter) { + ijk = iter.getCoord(); + ValueType vec = acc.getValue(ijk); + + pos = grid->indexToWorld(ijk); + + points[idx++] = pos[0]; + points[idx++] = pos[1]; + points[idx++] = pos[2]; + + indices[pt] = pt; + ++pt; + indices[pt] = pt; + + ++pt; + double w = vec.length() / length; + vec.normalize(); + pos += grid->voxelSize()[0] * 0.9 * vec; + + points[idx++] = pos[0]; + points[idx++] = pos[1]; + points[idx++] = pos[2]; + + + color = w * openvdb::Vec3d(0.9, 0.3, 0.3) + + (1.0 - w) * openvdb::Vec3d(0.3, 0.3, 0.9); + + colors[cc++] = color[0] * 0.3; + colors[cc++] = color[1] * 0.3; + colors[cc++] = color[2] * 0.3; + + colors[cc++] = color[0]; + colors[cc++] = color[1]; + colors[cc++] = color[2]; + } + } + + mVectorBuffer->genVertexBuffer(points); + mVectorBuffer->genColorBuffer(colors); + mVectorBuffer->genIndexBuffer(indices, GL_LINES); + } + +private: + BufferObject *mVectorBuffer; + +}; // ActiveVectorValuesOp + + +//////////////////////////////////////// + + +class MeshOp +{ +public: + MeshOp(BufferObject& buffer) : mBuffer(&buffer) {} + + template + void operator()(typename GridType::ConstPtr grid) + { + using openvdb::Index64; + + openvdb::tools::VolumeToMesh mesher( + grid->getGridClass() == openvdb::GRID_LEVEL_SET ? 0.0 : 0.01); + mesher(*grid); + + // Copy points and generate point normals. + std::vector points(mesher.pointListSize() * 3); + std::vector normals(mesher.pointListSize() * 3); + + openvdb::tree::ValueAccessor acc(grid->tree()); + typedef openvdb::math::Gradient Gradient; + openvdb::math::GenericMap map(grid->transform()); + openvdb::Coord ijk; + + for (Index64 n = 0, i = 0, N = mesher.pointListSize(); n < N; ++n) { + const openvdb::Vec3s& p = mesher.pointList()[n]; + points[i++] = p[0]; + points[i++] = p[1]; + points[i++] = p[2]; + } + + // Copy primitives + openvdb::tools::PolygonPoolList& polygonPoolList = mesher.polygonPoolList(); + Index64 numQuads = 0; + for (Index64 n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { + numQuads += polygonPoolList[n].numQuads(); + } + + std::vector indices; + indices.reserve(numQuads * 4); + openvdb::Vec3d normal, e1, e2; + + for (Index64 n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { + const openvdb::tools::PolygonPool& polygons = polygonPoolList[n]; + for (Index64 i = 0, I = polygons.numQuads(); i < I; ++i) { + const openvdb::Vec4I& quad = polygons.quad(i); + indices.push_back(quad[0]); + indices.push_back(quad[1]); + indices.push_back(quad[2]); + indices.push_back(quad[3]); + + e1 = mesher.pointList()[quad[1]]; + e1 -= mesher.pointList()[quad[0]]; + e2 = mesher.pointList()[quad[2]]; + e2 -= mesher.pointList()[quad[1]]; + normal = e1.cross(e2); + + const double length = normal.length(); + if (length > 1.0e-7) normal *= (1.0 / length); + + for (Index64 v = 0; v < 4; ++v) { + normals[quad[v]*3] = -normal[0]; + normals[quad[v]*3+1] = -normal[1]; + normals[quad[v]*3+2] = -normal[2]; + } + } + } + + // Construct and transfer GPU buffers. + mBuffer->genVertexBuffer(points); + mBuffer->genNormalBuffer(normals); + mBuffer->genIndexBuffer(indices, GL_QUADS); + } + +private: + BufferObject *mBuffer; + + static openvdb::Vec3s sNodeColors[]; + +}; // MeshOp + + +//////////////////////////////////////// + + +namespace util { + +/// Helper class used internally by processTypedGrid() +template +struct GridProcessor { + static inline void call(OpType& op, openvdb::GridBase::Ptr grid) { +#ifdef _MSC_VER + op.operator()(openvdb::gridPtrCast(grid)); +#else + op.template operator()(openvdb::gridPtrCast(grid)); +#endif + } +}; + +/// Helper class used internally by processTypedGrid() +template +struct GridProcessor { + static inline void call(OpType& op, openvdb::GridBase::ConstPtr grid) { +#ifdef _MSC_VER + op.operator()(openvdb::gridConstPtrCast(grid)); +#else + op.template operator()(openvdb::gridConstPtrCast(grid)); +#endif + } +}; + + +/// Helper function used internally by processTypedGrid() +template +inline void +doProcessTypedGrid(GridPtrType grid, OpType& op) +{ + GridProcessor::value>::call(op, grid); +} + + +//////////////////////////////////////// + + +/// @brief Utility function that, given a generic grid pointer, +/// calls a functor on the fully-resolved grid +/// +/// Usage: +/// @code +/// struct PruneOp { +/// template +/// void operator()(typename GridT::Ptr grid) const { grid->tree()->prune(); } +/// }; +/// +/// processTypedGrid(myGridPtr, PruneOp()); +/// @endcode +/// +/// @return @c false if the grid type is unknown or unhandled. +template +bool +processTypedGrid(GridPtrType grid, OpType& op) +{ + using namespace openvdb; + if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else return false; + return true; +} + + +/// @brief Utility function that, given a generic grid pointer, calls +/// a functor on the fully-resolved grid, provided that the grid's +/// voxel values are scalars +/// +/// Usage: +/// @code +/// struct PruneOp { +/// template +/// void operator()(typename GridT::Ptr grid) const { grid->tree()->prune(); } +/// }; +/// +/// processTypedScalarGrid(myGridPtr, PruneOp()); +/// @endcode +/// +/// @return @c false if the grid type is unknown or non-scalar. +template +bool +processTypedScalarGrid(GridPtrType grid, OpType& op) +{ + using namespace openvdb; + if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else return false; + return true; +} + + +/// @brief Utility function that, given a generic grid pointer, calls +/// a functor on the fully-resolved grid, provided that the grid's +/// voxel values are vectors +template +bool +processTypedVectorGrid(GridPtrType grid, OpType& op) +{ + using namespace openvdb; + if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else if (grid->template isType()) doProcessTypedGrid(grid, op); + else return false; + return true; +} + +} // namespace util + +} // namespace openvdb_viewer + +#endif // OPENVDB_VIEWER_RENDERMODULES_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/viewer/Viewer.cc b/openvdb_2_3_0_library/openvdb/viewer/Viewer.cc new file mode 100755 index 0000000..1893ffe --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/viewer/Viewer.cc @@ -0,0 +1,758 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#include "Viewer.h" + +#include "Camera.h" +#include "ClipBox.h" +#include "Font.h" +#include "RenderModules.h" +#include // for formattedInt() +#include +#include // for std::setprecision() +#include +#include +#include +#include + +#if defined(__APPLE__) || defined(MACOSX) +#include +#include +#else +#include +#include +#endif + +#include + + +namespace openvdb_viewer { + +class ViewerImpl +{ +public: + typedef boost::shared_ptr CameraPtr; + typedef boost::shared_ptr ClipBoxPtr; + typedef boost::shared_ptr RenderModulePtr; + + ViewerImpl(); + + void init(const std::string& progName, bool verbose = false); + + void view(const openvdb::GridCPtrVec&, int width = 900, int height = 800); + + void showPrevGrid(); + void showNextGrid(); + + bool needsDisplay(); + void setNeedsDisplay(); + + void toggleRenderModule(size_t n); + void toggleInfoText(); + + void setWindowTitle(double fps = 0.0); + void viewGrids(const openvdb::GridCPtrVec&, int width, int height); + void render(); + void showNthGrid(size_t n); + void updateCutPlanes(int wheelPos); + + void keyCallback(int key, int action); + void mouseButtonCallback(int button, int action); + void mousePosCallback(int x, int y); + void mouseWheelCallback(int pos); + void windowSizeCallback(int width, int height); + void windowRefreshCallback(); + +private: + CameraPtr mCamera; + ClipBoxPtr mClipBox; + + std::vector mRenderModules; + openvdb::GridCPtrVec mGrids; + + size_t mGridIdx, mUpdates; + std::string mGridName, mProgName, mGridInfo, mTransformInfo, mTreeInfo; + int mWheelPos; + bool mShiftIsDown, mCtrlIsDown, mShowInfo; +}; // class ViewerImpl + + +//////////////////////////////////////// + + +namespace { + +ViewerImpl* sViewer = NULL; +tbb::mutex sLock; + + +void +keyCB(int key, int action) +{ + if (sViewer) sViewer->keyCallback(key, action); +} + + +void +mouseButtonCB(int button, int action) +{ + if (sViewer) sViewer->mouseButtonCallback(button, action); +} + + +void +mousePosCB(int x, int y) +{ + if (sViewer) sViewer->mousePosCallback(x, y); +} + + +void +mouseWheelCB(int pos) +{ + if (sViewer) sViewer->mouseWheelCallback(pos); +} + + +void +windowSizeCB(int width, int height) +{ + if (sViewer) sViewer->windowSizeCallback(width, height); +} + + +void +windowRefreshCB() +{ + if (sViewer) sViewer->windowRefreshCallback(); +} + +} // unnamed namespace + + +//////////////////////////////////////// + + +Viewer +init(const std::string& progName, bool verbose) +{ + if (sViewer == NULL) { + tbb::mutex::scoped_lock(sLock); + if (sViewer == NULL) { + OPENVDB_START_THREADSAFE_STATIC_WRITE + sViewer = new ViewerImpl; + OPENVDB_FINISH_THREADSAFE_STATIC_WRITE + } + } + sViewer->init(progName, verbose); + + return Viewer(); +} + + +//////////////////////////////////////// + + +Viewer::Viewer() +{ +} + + +void +Viewer::view(const openvdb::GridCPtrVec& grids, int width, int height) +{ + if (sViewer) sViewer->view(grids, width, height); +} + + +void +Viewer::showPrevGrid() +{ + if (sViewer) sViewer->showPrevGrid(); +} + + +void +Viewer::showNextGrid() +{ + if (sViewer) sViewer->showNextGrid(); +} + + +//////////////////////////////////////// + + +ViewerImpl::ViewerImpl() + : mCamera(new Camera) + , mClipBox(new ClipBox) + , mRenderModules(0) + , mGridIdx(0) + , mUpdates(0) + , mWheelPos(0) + , mShiftIsDown(false) + , mCtrlIsDown(false) + , mShowInfo(true) +{ +} + + +void +ViewerImpl::init(const std::string& progName, bool verbose) +{ + mProgName = progName; + + if (glfwInit() != GL_TRUE) { + OPENVDB_LOG_ERROR("GLFW Initialization Failed."); + } + + if (verbose) { + if (glfwOpenWindow(100, 100, 8, 8, 8, 8, 24, 0, GLFW_WINDOW)) { + int major, minor, rev; + glfwGetVersion(&major, &minor, &rev); + std::cout << "GLFW: " << major << "." << minor << "." << rev << "\n" + << "OpenGL: " << glGetString(GL_VERSION) << std::endl; + glfwCloseWindow(); + } + } +} + + +//////////////////////////////////////// + + +void +ViewerImpl::setWindowTitle(double fps) +{ + std::ostringstream ss; + ss << mProgName << ": " + << (mGridName.empty() ? std::string("OpenVDB") : mGridName) + << " (" << (mGridIdx + 1) << " of " << mGrids.size() << ") @ " + << std::setprecision(1) << std::fixed << fps << " fps"; + glfwSetWindowTitle(ss.str().c_str()); +} + + +//////////////////////////////////////// + + +void +ViewerImpl::render() +{ + mCamera->aim(); + + // draw scene + mRenderModules[0]->render(); // ground plane. + + mClipBox->render(); + mClipBox->enableClipping(); + + for (size_t n = 1, N = mRenderModules.size(); n < N; ++n) { + mRenderModules[n]->render(); + } + + mClipBox->disableClipping(); + + // Render text + + if (mShowInfo) { + BitmapFont13::enableFontRendering(); + + glColor3f (0.2, 0.2, 0.2); + + int width, height; + glfwGetWindowSize(&width, &height); + + BitmapFont13::print(10, height - 13 - 10, mGridInfo); + BitmapFont13::print(10, height - 13 - 30, mTransformInfo); + BitmapFont13::print(10, height - 13 - 50, mTreeInfo); + + BitmapFont13::disableFontRendering(); + } +} + + +//////////////////////////////////////// + + +void +ViewerImpl::view(const openvdb::GridCPtrVec& gridList, int width, int height) +{ + viewGrids(gridList, width, height); +} + + +openvdb::BBoxd +worldSpaceBBox(const openvdb::math::Transform& xform, const openvdb::CoordBBox& bbox) +{ + openvdb::Vec3d pMin = openvdb::Vec3d(std::numeric_limits::max()); + openvdb::Vec3d pMax = -pMin; + + const openvdb::Coord& min = bbox.min(); + const openvdb::Coord& max = bbox.max(); + openvdb::Coord ijk; + + // corner 1 + openvdb::Vec3d ptn = xform.indexToWorld(min); + for (int i = 0; i < 3; ++i) { + if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; + if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; + } + + // corner 2 + ijk[0] = min.x(); + ijk[1] = min.y(); + ijk[2] = max.z(); + ptn = xform.indexToWorld(ijk); + for (int i = 0; i < 3; ++i) { + if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; + if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; + } + + // corner 3 + ijk[0] = max.x(); + ijk[1] = min.y(); + ijk[2] = max.z(); + ptn = xform.indexToWorld(ijk); + for (int i = 0; i < 3; ++i) { + if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; + if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; + } + + // corner 4 + ijk[0] = max.x(); + ijk[1] = min.y(); + ijk[2] = min.z(); + ptn = xform.indexToWorld(ijk); + for (int i = 0; i < 3; ++i) { + if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; + if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; + } + + // corner 5 + ijk[0] = min.x(); + ijk[1] = max.y(); + ijk[2] = min.z(); + ptn = xform.indexToWorld(ijk); + for (int i = 0; i < 3; ++i) { + if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; + if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; + } + + // corner 6 + ijk[0] = min.x(); + ijk[1] = max.y(); + ijk[2] = max.z(); + ptn = xform.indexToWorld(ijk); + for (int i = 0; i < 3; ++i) { + if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; + if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; + } + + + // corner 7 + ptn = xform.indexToWorld(max); + for (int i = 0; i < 3; ++i) { + if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; + if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; + } + + // corner 8 + ijk[0] = max.x(); + ijk[1] = max.y(); + ijk[2] = min.z(); + ptn = xform.indexToWorld(ijk); + for (int i = 0; i < 3; ++i) { + if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; + if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; + } + + return openvdb::BBoxd(pMin, pMax); +} + + +void +ViewerImpl::viewGrids(const openvdb::GridCPtrVec& gridList, int width, int height) +{ + mGrids = gridList; + mGridIdx = size_t(-1); + mGridName.clear(); + + // Create window + if (!glfwOpenWindow(width, height, // Window size + 8, 8, 8, 8, // # of R,G,B, & A bits + 32, 0, // # of depth & stencil buffer bits + GLFW_WINDOW)) // Window mode + { + glfwTerminate(); + return; + } + + glfwSetWindowTitle(mProgName.c_str()); + glfwSwapBuffers(); + + BitmapFont13::initialize(); + + ////////// + + // Eval grid bbox + + openvdb::BBoxd bbox(openvdb::Vec3d(0.0), openvdb::Vec3d(0.0)); + + if (!gridList.empty()) { + bbox = worldSpaceBBox(gridList[0]->transform(), gridList[0]->evalActiveVoxelBoundingBox()); + openvdb::Vec3d voxelSize = gridList[0]->voxelSize(); + + for (size_t n = 1; n < gridList.size(); ++n) { + bbox.expand(worldSpaceBBox(gridList[n]->transform(), + gridList[n]->evalActiveVoxelBoundingBox())); + + voxelSize = minComponent(voxelSize, gridList[n]->voxelSize()); + } + + mClipBox->setStepSize(voxelSize); + } + + mClipBox->setBBox(bbox); + + + // setup camera + + openvdb::Vec3d extents = bbox.extents(); + double max_extent = std::max(extents[0], std::max(extents[1], extents[2])); + + mCamera->setTarget(bbox.getCenter(), max_extent); + mCamera->lookAtTarget(); + mCamera->setSpeed(/*zoom=*/0.1, /*strafe=*/0.002, /*tumbling=*/0.02); + + ////////// + + // register callback functions + + glfwSetKeyCallback(keyCB); + glfwSetMouseButtonCallback(mouseButtonCB); + glfwSetMousePosCallback(mousePosCB); + glfwSetMouseWheelCallback(mouseWheelCB); + glfwSetWindowSizeCallback(windowSizeCB); + glfwSetWindowRefreshCallback(windowRefreshCB); + + + ////////// + + // Screen color + glClearColor(0.85, 0.85, 0.85, 0.0f); + + glDepthFunc(GL_LESS); + glEnable(GL_DEPTH_TEST); + glShadeModel(GL_SMOOTH); + + glPointSize(4); + glLineWidth(2); + ////////// + + // construct render modules + showNthGrid(/*n=*/0); + + + // main loop + + size_t frame = 0; + double time = glfwGetTime(); + + glfwSwapInterval(1); + + do { + if (needsDisplay()) render(); + + // eval fps + ++frame; + double elapsed = glfwGetTime() - time; + if (elapsed > 1.0) { + time = glfwGetTime(); + setWindowTitle(/*fps=*/double(frame) / elapsed); + frame = 0; + } + + // Swap front and back buffers + glfwSwapBuffers(); + + // exit if the esc key is pressed or the window is closed. + } while (!glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED)); + + glfwTerminate(); +} + + +//////////////////////////////////////// + + +void +ViewerImpl::updateCutPlanes(int wheelPos) +{ + double speed = std::abs(mWheelPos - wheelPos); + if (mWheelPos < wheelPos) mClipBox->update(speed); + else mClipBox->update(-speed); + setNeedsDisplay(); +} + + +//////////////////////////////////////// + + +void +ViewerImpl::showPrevGrid() +{ + const size_t numGrids = mGrids.size(); + size_t idx = ((numGrids + mGridIdx) - 1) % numGrids; + showNthGrid(idx); +} + + +void +ViewerImpl::showNextGrid() +{ + const size_t numGrids = mGrids.size(); + size_t idx = (mGridIdx + 1) % numGrids; + showNthGrid(idx); +} + + +void +ViewerImpl::showNthGrid(size_t n) +{ + n = n % mGrids.size(); + if (n == mGridIdx) return; + + mGridName = mGrids[n]->getName(); + mGridIdx = n; + + // save render settings + std::vector active(mRenderModules.size()); + for (size_t i = 0, I = active.size(); i < I; ++i) { + active[i] = mRenderModules[i]->visible(); + } + + mRenderModules.clear(); + mRenderModules.push_back(RenderModulePtr(new ViewportModule)); + mRenderModules.push_back(RenderModulePtr(new TreeTopologyModule(mGrids[n]))); + mRenderModules.push_back(RenderModulePtr(new MeshModule(mGrids[n]))); + mRenderModules.push_back(RenderModulePtr(new ActiveValueModule(mGrids[n]))); + + if (active.empty()) { + for (size_t i = 2, I = mRenderModules.size(); i < I; ++i) { + mRenderModules[i]->visible() = false; + } + } else { + for (size_t i = 0, I = active.size(); i < I; ++i) { + mRenderModules[i]->visible() = active[i]; + } + } + + // Collect info + { + std::ostringstream ostrm; + std::string s = mGrids[n]->getName(); + const openvdb::GridClass cls = mGrids[n]->getGridClass(); + if (!s.empty()) ostrm << s << " / "; + ostrm << mGrids[n]->valueType() << " / "; + if (cls == openvdb::GRID_UNKNOWN) ostrm << " class unknown"; + else ostrm << " " << openvdb::GridBase::gridClassToString(cls); + mGridInfo = ostrm.str(); + } + { + openvdb::Coord dim = mGrids[n]->evalActiveVoxelDim(); + std::ostringstream ostrm; + ostrm << dim[0] << " x " << dim[1] << " x " << dim[2] + << " / voxel size " << std::setprecision(4) << mGrids[n]->voxelSize()[0] + << " (" << mGrids[n]->transform().mapType() << ")"; + mTransformInfo = ostrm.str(); + } + { + std::ostringstream ostrm; + const openvdb::Index64 count = mGrids[n]->activeVoxelCount(); + ostrm << openvdb::util::formattedInt(count) + << " active voxel" << (count == 1 ? "" : "s"); + mTreeInfo = ostrm.str(); + } + + setWindowTitle(); +} + + +//////////////////////////////////////// + + +void +ViewerImpl::keyCallback(int key, int action) +{ + OPENVDB_START_THREADSAFE_STATIC_WRITE + + mCamera->keyCallback(key, action); + const bool keyPress = glfwGetKey(key) == GLFW_PRESS; + mShiftIsDown = glfwGetKey(GLFW_KEY_LSHIFT); + mCtrlIsDown = glfwGetKey(GLFW_KEY_LCTRL); + + if (keyPress) { + switch (key) { + case '1': + toggleRenderModule(1); + break; + case '2': + toggleRenderModule(2); + break; + case '3': + toggleRenderModule(3); + break; + case 'c': case 'C': + mClipBox->reset(); + break; + case 'h': case 'H': // center home + mCamera->lookAt(openvdb::Vec3d(0.0), 10.0); + break; + case 'g': case 'G': // center geometry + mCamera->lookAtTarget(); + break; + case 'i': case 'I': + toggleInfoText(); + break; + case GLFW_KEY_LEFT: + showPrevGrid(); + break; + case GLFW_KEY_RIGHT: + showNextGrid(); + break; + } + } + + switch (key) { + case 'x': case 'X': + mClipBox->activateXPlanes() = keyPress; + break; + case 'y': case 'Y': + mClipBox->activateYPlanes() = keyPress; + break; + case 'z': case 'Z': + mClipBox->activateZPlanes() = keyPress; + break; + } + + mClipBox->shiftIsDown() = mShiftIsDown; + mClipBox->ctrlIsDown() = mCtrlIsDown; + + setNeedsDisplay(); + + OPENVDB_FINISH_THREADSAFE_STATIC_WRITE +} + + +void +ViewerImpl::mouseButtonCallback(int button, int action) +{ + mCamera->mouseButtonCallback(button, action); + mClipBox->mouseButtonCallback(button, action); + if (mCamera->needsDisplay()) setNeedsDisplay(); +} + + +void +ViewerImpl::mousePosCallback(int x, int y) +{ + bool handled = mClipBox->mousePosCallback(x, y); + if (!handled) mCamera->mousePosCallback(x, y); + if (mCamera->needsDisplay()) setNeedsDisplay(); +} + + +void +ViewerImpl::mouseWheelCallback(int pos) +{ + if (mClipBox->isActive()) { + updateCutPlanes(pos); + } else { + mCamera->mouseWheelCallback(pos, mWheelPos); + if (mCamera->needsDisplay()) setNeedsDisplay(); + } + + mWheelPos = pos; +} + + +void +ViewerImpl::windowSizeCallback(int, int) +{ + setNeedsDisplay(); +} + + +void +ViewerImpl::windowRefreshCallback() +{ + setNeedsDisplay(); +} + + +//////////////////////////////////////// + + +bool +ViewerImpl::needsDisplay() +{ + if (mUpdates < 2) { + mUpdates += 1; + return true; + } + return false; +} + + +void +ViewerImpl::setNeedsDisplay() +{ + mUpdates = 0; +} + + +void +ViewerImpl::toggleRenderModule(size_t n) +{ + mRenderModules[n]->visible() = !mRenderModules[n]->visible(); +} + + +void +ViewerImpl::toggleInfoText() +{ + mShowInfo = !mShowInfo; +} + +} // namespace openvdb_viewer + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) diff --git a/openvdb_2_3_0_library/openvdb/viewer/Viewer.h b/openvdb_2_3_0_library/openvdb/viewer/Viewer.h new file mode 100755 index 0000000..511a386 --- /dev/null +++ b/openvdb_2_3_0_library/openvdb/viewer/Viewer.h @@ -0,0 +1,76 @@ +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +// +// Redistributions of source code must retain the above copyright +// and license notice and the following restrictions and disclaimer. +// +// * Neither the name of DreamWorks Animation nor the names of +// its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +// LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +// +/////////////////////////////////////////////////////////////////////////// + +#ifndef OPENVDB_VIEWER_VIEWER_HAS_BEEN_INCLUDED +#define OPENVDB_VIEWER_VIEWER_HAS_BEEN_INCLUDED + +#include +#include + + +namespace openvdb_viewer { + +class Viewer; + + +/// @brief Initialize and return a viewer. +/// @param progName the name of the calling program (for use in info displays) +/// @param verbose if true, print diagnostic info to stdout +/// @note Currently, the viewer window is a singleton (but that might change +/// in the future), so although this function returns a new Viewer instance +/// on each call, all instances are associated with the same window. +/// Typically, then, this function should be called only once. +Viewer init(const std::string& progName, bool verbose = false); + + +/// Manager for a window that displays OpenVDB grids +class Viewer +{ +public: + /// Resize the window associated with this viewer and display the given grids. + void view(const openvdb::GridCPtrVec&, int width = 900, int height = 800); + + /// When multiple grids are being viewed, advance to the next grid. + void showNextGrid(); + /// When multiple grids are being viewed, return to the previous grid. + void showPrevGrid(); + +private: + friend Viewer init(const std::string&, bool); + Viewer(); +}; + +} // namespace openvdb_viewer + +#endif // OPENVDB_VIEWER_VIEWER_HAS_BEEN_INCLUDED + +// Copyright (c) 2012-2013 DreamWorks Animation LLC +// All rights reserved. This software is distributed under the +// Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ )