openvdb/0000755000000000000000000000000012603226506011213 5ustar rootrootopenvdb/Grid.cc0000644000000000000000000003113112603226506012406 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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() {} 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 { const char* const fields[] = { 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; fields[i] != NULL; ++i) { if (Metadata::ConstPtr m = (*this)[fields[i]]) { ret->insertMeta(fields[i], *m); } } return ret; } //////////////////////////////////////// #ifndef OPENVDB_2_ABI_COMPATIBLE void GridBase::clipGrid(const BBoxd& worldBBox) { const CoordBBox indexBBox = this->constTransform().worldToIndexNodeCentered(worldBBox); this->clip(indexBBox); } #endif } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Makefile0000644000000000000000000007252612603226506012667 0ustar rootroot# Copyright (c) 2012-2015 DreamWorks Animation LLC # # All rights reserved. This software is distributed under the # Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) # # Redistributions of source code must retain the above copyright # and license notice and the following restrictions and disclaimer. # # * Neither the name of DreamWorks Animation nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 DESTDIR # depend recompute source file header dependencies # clean delete generated files from the local directory # test run tests # # Options: # abi=2 build for compatibility with the OpenVDB 2.x Grid ABI # (some OpenVDB 3.x features will be disabled) # 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 DESTDIR=/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 DESTDIR := /tmp/OpenVDB # The parent directory of the boost/ header directory BOOST_INCL_DIR := $(HT)/include # The directory containing libboost_iostreams, libboost_system, etc. BOOST_LIB_DIR := $(HDSO) BOOST_LIB := -lboost_iostreams -lboost_system BOOST_THREAD_LIB := -lboost_thread # The parent directory of the OpenEXR/ header directory EXR_INCL_DIR := $(HT)/include # The directory containing IlmImf EXR_LIB_DIR := $(HDSO) EXR_LIB := -lIlmImf # The parent directory of the OpenEXR/ header directory (which contains half.h) ILMBASE_INCL_DIR := $(EXR_INCL_DIR) # The directory containing libIlmThread, libIlmThread, libHalf etc. ILMBASE_LIB_DIR := $(EXR_LIB_DIR) ILMBASE_LIB := -lIlmThread -lIex -lImath 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 # The parent directory of the blosc.h header # (leave blank if Blosc is unavailable) BLOSC_INCL_DIR := $(HT)/include # The directory containing libblosc BLOSC_LIB_DIR := $(HDSO) BLOSC_LIB := -lblosc # 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/glfw-3.0.1/include # The directory containing libglfw GLFW_LIB_DIR := /rel/third_party/glfw/glfw-3.0.1/lib GLFW_LIB := -lglfw # The major version number of the GLFW library # (header filenames changed between GLFW 2 and 3, so this must be specified explicitly) GLFW_MAJOR_VERSION := 3 # 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: ifneq (,$(INSTALL_DIR)) $(warning Warning: $$(INSTALL_DIR) is no longer used; set $$(DESTDIR) instead.) endif # 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_blosc := no ifneq (,$(and $(BLOSC_LIB_DIR),$(BLOSC_INCL_DIR),$(BLOSC_LIB))) has_blosc := yes endif has_glfw := no ifneq (,$(and $(GLFW_LIB_DIR),$(GLFW_INCL_DIR),$(GLFW_LIB))) has_glfw := yes endif has_log4cplus := no ifneq (,$(and $(LOG4CPLUS_LIB_DIR),$(LOG4CPLUS_INCL_DIR),$(LOG4CPLUS_LIB))) has_log4cplus := yes endif has_python := no ifneq (,$(and $(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 .. -isystem $(BOOST_INCL_DIR) -isystem $(ILMBASE_INCL_DIR) -isystem $(TBB_INCL_DIR) ifeq (yes,$(has_blosc)) INCLDIRS += -isystem $(BLOSC_INCL_DIR) endif ifeq (yes,$(has_log4cplus)) INCLDIRS += -isystem $(LOG4CPLUS_INCL_DIR) endif CXXFLAGS += -pthread $(OPTIMIZE) $(INCLDIRS) ifeq (yes,$(has_blosc)) CXXFLAGS += -DOPENVDB_USE_BLOSC endif ifeq (yes,$(has_log4cplus)) CXXFLAGS += -DOPENVDB_USE_LOG4CPLUS endif ifeq (2,$(strip $(abi))) CXXFLAGS += -DOPENVDB_2_ABI_COMPATIBLE endif ifneq (2,$(strip $(GLFW_MAJOR_VERSION))) CXXFLAGS += -DOPENVDB_USE_GLFW_3 endif LIBS := \ -ldl -lm -lz \ -L$(ILMBASE_LIB_DIR) $(HALF_LIB) \ -L$(TBB_LIB_DIR) $(TBB_LIB) \ -L$(BOOST_LIB_DIR) $(BOOST_LIB) \ # LIBS_RPATH := \ -ldl -lm -lz \ -Wl,-rpath,$(ILMBASE_LIB_DIR) -L$(ILMBASE_LIB_DIR) $(HALF_LIB) \ -Wl,-rpath,$(TBB_LIB_DIR) -L$(TBB_LIB_DIR) $(TBB_LIB) \ -Wl,-rpath,$(BOOST_LIB_DIR) -L$(BOOST_LIB_DIR) $(BOOST_LIB) \ # ifeq (yes,$(has_blosc)) LIBS += -L$(BLOSC_LIB_DIR) $(BLOSC_LIB) LIBS_RPATH += -Wl,-rpath,$(BLOSC_LIB_DIR) -L$(BLOSC_LIB_DIR) $(BLOSC_LIB) endif 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/io.h \ io/Queue.h \ io/Stream.h \ io/TempFile.h \ math/BBox.h \ math/ConjGradient.h \ math/Coord.h \ math/DDA.h \ math/FiniteDifference.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/ChangeBackground.h \ tools/Clip.h \ tools/Composite.h \ tools/Dense.h \ tools/DenseSparseTools.h \ tools/Diagnostics.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/PointIndexGrid.h \ tools/PointPartitioner.h \ tools/PointScatter.h \ tools/PoissonSolver.h \ tools/Prune.h \ tools/RayIntersector.h \ tools/RayTracer.h \ tools/SignedFloodFill.h \ tools/Statistics.h \ tools/ValueTransformer.h \ tools/VectorTransformer.h \ tools/VelocityFields.h \ tools/VolumeAdvect.h \ tools/VolumeToMesh.h \ tools/VolumeToSpheres.h \ tree/InternalNode.h \ tree/Iterator.h \ tree/LeafManager.h \ tree/LeafNode.h \ tree/LeafNodeBool.h \ tree/NodeManager.h \ tree/NodeUnion.h \ tree/RootNode.h \ tree/Tree.h \ tree/TreeIterator.h \ tree/ValueAccessor.h \ Types.h \ util/CpuTimer.h \ util/Formats.h \ util/logging.h \ util/MapsUtil.h \ util/Name.h \ util/NodeMasks.h \ util/NullInterrupter.h \ util/PagedArray.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 \ io/TempFile.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/TestConjGradient.cc \ unittest/TestCoord.cc \ unittest/TestCpt.cc \ unittest/TestCurl.cc \ unittest/TestDense.cc \ unittest/TestDenseSparseTools.cc \ unittest/TestDiagnostics.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/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/TestPointIndexGrid.cc \ unittest/TestPointPartitioner.cc \ unittest/TestPoissonSolver.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/TestUtil.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 -isystem python -isystem $(PYTHON_INCL_DIR) -isystem $(PYCONFIG_INCL_DIR) ifneq (,$(strip $(NUMPY_INCL_DIR))) PYCXXFLAGS += -isystem $(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 ifndef MBSD LIBOPENVDB_SHARED_NAME := $(LIBOPENVDB_NAME).so LIBOPENVDB_SHARED := $(LIBOPENVDB_NAME).so.$(LIB_VERSION) LIBOPENVDB_SONAME := $(LIBOPENVDB_NAME).so.$(SO_VERSION) LIBOPENVDB_SONAME_FLAGS := -Wl,-soname,$(LIBOPENVDB_SONAME) else LIBOPENVDB_SHARED_NAME := $(LIBOPENVDB_NAME).dylib LIBOPENVDB_SHARED := $(LIBOPENVDB_NAME).$(LIB_VERSION).dylib LIBOPENVDB_SONAME := $(LIBOPENVDB_NAME).$(SO_VERSION).dylib LIBOPENVDB_SONAME_FLAGS := -Wl,-install_name,$(DESTDIR)/lib/$(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 ifndef MBSD LIBVIEWER_SHARED_NAME := $(LIBVIEWER_NAME).so LIBVIEWER_SHARED := $(LIBVIEWER_NAME).so.$(LIB_VERSION) LIBVIEWER_SONAME := $(LIBVIEWER_NAME).so.$(SO_VERSION) LIBVIEWER_SONAME_FLAGS := -Wl,-soname,$(LIBVIEWER_SONAME) else LIBVIEWER_SHARED_NAME := $(LIBVIEWER_NAME).dylib LIBVIEWER_SHARED := $(LIBVIEWER_NAME).$(LIB_VERSION).dylib LIBVIEWER_SONAME := $(LIBVIEWER_NAME).$(SO_VERSION).dylib LIBVIEWER_SONAME_FLAGS := -Wl,-install_name,$(DESTDIR)/lib/$(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,$(DESTDIR)/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_SHARED_NAME) \ $(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_SHARED_NAME) $(LIBOPENVDB_SONAME) $(LIBOPENVDB_SHARED_NAME): $(LIBOPENVDB_SHARED) ln -f -s $< $@ $(LIBOPENVDB_SONAME): $(LIBOPENVDB_SHARED) ln -f -s $< $@ $(LIBOPENVDB_SHARED): $(OBJ_NAMES) @echo "Building $@ because of $(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 $(list_deps)" $(AR) cr $@ $^ endif # shared $(DOC_INDEX): doxygen-config $(INCLUDE_NAMES) $(SRC_NAMES) $(DOC_FILES) @echo "Generating documentation because of $(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 $(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 $(list_deps)" $(CXX) $(CXXFLAGS) -o $@ cmd/openvdb_print/main.cc -I . \ $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) \ $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) vdb_render: $(LIBOPENVDB) cmd/openvdb_render/main.cc @echo "Building $@ because of $(list_deps)" $(CXX) $(CXXFLAGS) -o $@ cmd/openvdb_render/main.cc -I . \ -isystem $(EXR_INCL_DIR) -isystem $(ILMBASE_INCL_DIR) \ -Wl,-rpath,$(EXR_LIB_DIR) -L$(EXR_LIB_DIR) $(EXR_LIB) \ -Wl,-rpath,$(ILMBASE_LIB_DIR) -L$(ILMBASE_LIB_DIR) $(ILMBASE_LIB) \ $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) \ $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) # Create an openvdb_viewer/ symlink to the viewer/ subdirectory, # to mirror the DWA directory structure. openvdb_viewer: ln -f -s viewer openvdb_viewer ifneq (yes,$(has_glfw)) vdb_view: @echo "$@"': GLFW is unavailable' else $(LIBVIEWER_INCLUDE_NAMES): openvdb_viewer $(LIBVIEWER_OBJ_NAMES): $(LIBVIEWER_INCLUDE_NAMES) $(LIBVIEWER_OBJ_NAMES): %.o: %.cc @echo "Building $@ because of $(list_deps)" $(CXX) -c $(CXXFLAGS) -I . -isystem $(GLFW_INCL_DIR) -DGL_GLEXT_PROTOTYPES=1 -fPIC -o $@ $< vdb_view: $(LIBOPENVDB) $(LIBVIEWER_OBJ_NAMES) cmd/openvdb_view/main.cc @echo "Building $@ because of $(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) \ $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) \ $(LIBVIEWER_FLAGS) $(LIBS_RPATH) $(BOOST_THREAD_LIB) $(CONCURRENT_MALLOC_LIB) endif # Build the Python module $(PYTHON_OBJ_NAMES): $(PYTHON_INCLUDE_NAMES) $(PYTHON_OBJ_NAMES): %.o: %.cc @echo "Building $@ because of $(list_deps)" $(CXX) -c $(CXXFLAGS) -I . $(PYCXXFLAGS) -o $@ $< $(PYTHON_MODULE): $(LIBOPENVDB) $(PYTHON_OBJ_NAMES) @echo "Building $@ because of $(list_deps)" $(CXX) $(CXXFLAGS) $(PYCXXFLAGS) -shared $(PYTHON_SONAME_FLAGS) -o $@ $(PYTHON_OBJ_NAMES) \ -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) \ $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) ifeq (yes,$(has_python)) ifneq (,$(strip $(EPYDOC))) pydoc: $(PYTHON_MODULE) $(LIBOPENVDB_SONAME) @echo "Generating Python module documentation because of $(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 $(list_deps)" $(CXX) -c $(CXXFLAGS) -isystem $(CPPUNIT_INCL_DIR) -fPIC -o $@ $< ifneq (,$(strip $(CPPUNIT_INCL_DIR))) vdb_test: $(LIBOPENVDB) $(UNITTEST_OBJ_NAMES) @echo "Building $@ because of $(list_deps)" $(CXX) $(CXXFLAGS) -o $@ $(UNITTEST_OBJ_NAMES) \ -Wl,-rpath,$(CPPUNIT_LIB_DIR) -L$(CPPUNIT_LIB_DIR) $(CPPUNIT_LIB) \ $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) \ $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) test: lib 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 $(DESTDIR)/include/openvdb @echo "Created $(DESTDIR)/include/openvdb" pushd $(DESTDIR)/include/openvdb > /dev/null; \ mkdir -p $(HEADER_SUBDIRS); popd > /dev/null for f in $(INCLUDE_NAMES); \ do cp -f $$f $(DESTDIR)/include/openvdb/$$f; done @# if [ -f $(LIBVIEWER) ]; \ then \ mkdir -p $(DESTDIR)/include/openvdb_viewer; \ echo "Created $(DESTDIR)/include/openvdb_viewer"; \ cp -f $(LIBVIEWER_PUBLIC_INCLUDE_NAMES) $(DESTDIR)/include/openvdb_viewer/; \ fi @echo "Copied header files to $(DESTDIR)/include" @# mkdir -p $(DESTDIR)/lib @echo "Created $(DESTDIR)/lib/" cp -f $(LIBOPENVDB) $(DESTDIR)/lib pushd $(DESTDIR)/lib > /dev/null; \ if [ -f $(LIBOPENVDB_SHARED) ]; then \ ln -f -s $(LIBOPENVDB_SHARED) $(LIBOPENVDB_SHARED_NAME); \ ln -f -s $(LIBOPENVDB_SHARED) $(LIBOPENVDB_SONAME); \ fi; \ popd > /dev/null @echo "Copied libopenvdb to $(DESTDIR)/lib/" @# if [ -f $(LIBVIEWER) ]; \ then \ cp -f $(LIBVIEWER) $(DESTDIR)/lib; \ pushd $(DESTDIR)/lib > /dev/null; \ if [ -f $(LIBVIEWER_SHARED) ]; then \ ln -f -s $(LIBVIEWER_SHARED) $(LIBVIEWER_SHARED_NAME); fi; \ popd > /dev/null; \ echo "Copied libopenvdb_viewer to $(DESTDIR)/lib/"; \ fi @# if [ -f $(PYTHON_MODULE) ]; \ then \ installdir=$(DESTDIR)/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=$(DESTDIR)/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 $(DESTDIR)/bin @echo "Created $(DESTDIR)/bin/" cp -f vdb_print $(DESTDIR)/bin @echo "Copied vdb_print to $(DESTDIR)/bin/" cp -f vdb_render $(DESTDIR)/bin @echo "Copied vdb_render to $(DESTDIR)/bin/" if [ -f vdb_view ]; \ then \ cp -f vdb_view $(DESTDIR)/bin; \ echo "Copied vdb_view to $(DESTDIR)/bin/"; \ fi @# if [ -d doc/html ]; \ then \ mkdir -p $(DESTDIR)/share/doc/openvdb; \ echo "Created $(DESTDIR)/share/doc/openvdb/"; \ cp -r -f doc/html $(DESTDIR)/share/doc/openvdb; \ echo "Copied documentation to $(DESTDIR)/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) openvdb_viewer @echo "Generating dependencies because of $(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%'` \ -isystem $(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-2015 DreamWorks Animation LLC # All rights reserved. This software is distributed under the # Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Types.h0000644000000000000000000004537312603226506012504 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #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; // Quaternions typedef math::Quat QuatR; //////////////////////////////////////// /// @brief Integer wrapper, required to distinguish PointIndexGrid and /// PointDataGrid from Int32Grid and Int64Grid /// @note @c Kind is a dummy parameter used to create distinct types. template struct PointIndex { BOOST_STATIC_ASSERT(boost::is_integral::value); typedef IntType_ IntType; PointIndex(IntType i = IntType(0)): mIndex(i) {} operator IntType() const { return mIndex; } /// Needed to support the (zeroVal() + val) idiom. template PointIndex operator+(T x) { return PointIndex(mIndex + IntType(x)); } private: IntType mIndex; }; typedef PointIndex PointIndex32; typedef PointIndex PointIndex64; typedef PointIndex PointDataIndex32; typedef PointIndex PointDataIndex64; //////////////////////////////////////// template struct VecTraits { static const bool IsVec = false; static const int Size = 1; typedef T ElementType; }; template struct VecTraits > { static const bool IsVec = true; static const int Size = 2; typedef T ElementType; }; template struct VecTraits > { static const bool IsVec = true; static const int Size = 3; typedef T ElementType; }; template struct VecTraits > { static const bool IsVec = true; static const int Size = 4; typedef T ElementType; }; //////////////////////////////////////// /// @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 }; }; template<> struct CanConvertType { enum {value = true}; }; template<> struct CanConvertType { enum {value = true}; }; //////////////////////////////////////// // 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 "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"; } template<> inline const char* typeNameAsString() { return "ptidx32"; } template<> inline const char* typeNameAsString() { return "ptidx64"; } template<> inline const char* typeNameAsString() { return "ptdataidx32"; } template<> inline const char* typeNameAsString() { return "ptdataidx64"; } //////////////////////////////////////// /// @brief This struct collects both input and output arguments to "grid combiner" functors /// used with the tree::TypedGrid::combineExtended() and combine2Extended() methods. /// AValueType and BValueType are the value types of the two grids being combined. /// /// @see openvdb/tree/Tree.h for usage information. /// /// Setter methods return references to this object, to facilitate the following usage: /// @code /// CombineArgs args; /// myCombineOp(args.setARef(aVal).setBRef(bVal).setAIsActive(true).setBIsActive(false)); /// @endcode template class CombineArgs { public: 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 {}; // Dummy class that distinguishes constructors during file input class PartialCreate {}; } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #if defined(__ICC) // Use these defines to bracket a region of code that has safe static accesses. // Keep the region as small as possible. #define OPENVDB_START_THREADSAFE_STATIC_REFERENCE __pragma(warning(disable:1710)) #define OPENVDB_FINISH_THREADSAFE_STATIC_REFERENCE __pragma(warning(default:1710)) #define OPENVDB_START_THREADSAFE_STATIC_WRITE __pragma(warning(disable:1711)) #define OPENVDB_FINISH_THREADSAFE_STATIC_WRITE __pragma(warning(default:1711)) #define OPENVDB_START_THREADSAFE_STATIC_ADDRESS __pragma(warning(disable:1712)) #define OPENVDB_FINISH_THREADSAFE_STATIC_ADDRESS __pragma(warning(default:1712)) // Use these defines to bracket a region of code that has unsafe static accesses. // Keep the region as small as possible. #define OPENVDB_START_NON_THREADSAFE_STATIC_REFERENCE __pragma(warning(disable:1710)) #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_REFERENCE __pragma(warning(default:1710)) #define OPENVDB_START_NON_THREADSAFE_STATIC_WRITE __pragma(warning(disable:1711)) #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_WRITE __pragma(warning(default:1711)) #define OPENVDB_START_NON_THREADSAFE_STATIC_ADDRESS __pragma(warning(disable:1712)) #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_ADDRESS __pragma(warning(default:1712)) // Simpler version for one-line cases #define OPENVDB_THREADSAFE_STATIC_REFERENCE(CODE) \ __pragma(warning(disable:1710)); CODE; __pragma(warning(default:1710)) #define OPENVDB_THREADSAFE_STATIC_WRITE(CODE) \ __pragma(warning(disable:1711)); CODE; __pragma(warning(default:1711)) #define OPENVDB_THREADSAFE_STATIC_ADDRESS(CODE) \ __pragma(warning(disable:1712)); CODE; __pragma(warning(default:1712)) #else // GCC does not support these compiler warnings #define OPENVDB_START_THREADSAFE_STATIC_REFERENCE #define OPENVDB_FINISH_THREADSAFE_STATIC_REFERENCE #define OPENVDB_START_THREADSAFE_STATIC_WRITE #define OPENVDB_FINISH_THREADSAFE_STATIC_WRITE #define OPENVDB_START_THREADSAFE_STATIC_ADDRESS #define OPENVDB_FINISH_THREADSAFE_STATIC_ADDRESS #define OPENVDB_START_NON_THREADSAFE_STATIC_REFERENCE #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_REFERENCE #define OPENVDB_START_NON_THREADSAFE_STATIC_WRITE #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_WRITE #define OPENVDB_START_NON_THREADSAFE_STATIC_ADDRESS #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_ADDRESS #define OPENVDB_THREADSAFE_STATIC_REFERENCE(CODE) CODE #define OPENVDB_THREADSAFE_STATIC_WRITE(CODE) CODE #define OPENVDB_THREADSAFE_STATIC_ADDRESS(CODE) CODE #endif // defined(__ICC) #endif // OPENVDB_TYPES_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/0000755000000000000000000000000012603226506012170 5ustar rootrootopenvdb/util/Formats.cc0000644000000000000000000001046412603226506014117 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Formats.h" #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { int printBytes(std::ostream& os, uint64_t bytes, const std::string& head, const std::string& tail, bool exact, int width, int precision) { const uint64_t one = 1; int group = 0; // Write to a string stream so that I/O manipulators like // std::setprecision() don't alter the output stream. std::ostringstream ostr; ostr << head; ostr << std::setprecision(precision) << std::setiosflags(std::ios::fixed); if (bytes >> 40) { ostr << std::setw(width) << (double(bytes) / double(one << 40)) << " TB"; group = 4; } else if (bytes >> 30) { ostr << std::setw(width) << (double(bytes) / double(one << 30)) << " GB"; group = 3; } else if (bytes >> 20) { ostr << std::setw(width) << (double(bytes) / double(one << 20)) << " MB"; group = 2; } else if (bytes >> 10) { ostr << std::setw(width) << (double(bytes) / double(one << 10)) << " KB"; group = 1; } else { ostr << std::setw(width) << bytes << " Bytes"; } if (exact && group) ostr << " (" << bytes << " Bytes)"; ostr << tail; os << ostr.str(); return group; } int printNumber(std::ostream& os, uint64_t number, const std::string& head, const std::string& tail, bool exact, int width, int precision) { int group = 0; // Write to a string stream so that I/O manipulators like // std::setprecision() don't alter the output stream. std::ostringstream ostr; ostr << head; ostr << std::setprecision(precision) << std::setiosflags(std::ios::fixed); if (number / UINT64_C(1000000000000)) { ostr << std::setw(width) << (double(number) / 1000000000000.0) << " trillion"; group = 4; } else if (number / UINT64_C(1000000000)) { ostr << std::setw(width) << (double(number) / 1000000000.0) << " billion"; group = 3; } else if (number / UINT64_C(1000000)) { ostr << std::setw(width) << (double(number) / 1000000.0) << " million"; group = 2; } else if (number / UINT64_C(1000)) { ostr << std::setw(width) << (double(number) / 1000.0) << " thousand"; group = 1; } else { ostr << std::setw(width) << number; } if (exact && group) ostr << " (" << number << ")"; ostr << tail; os << ostr.str(); return group; } } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/Util.h0000644000000000000000000001363312603226506013264 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_UTIL_UTIL_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_UTIL_HAS_BEEN_INCLUDED #include #include #include #include // for tree::pruneInactive namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { OPENVDB_API extern const Index32 INVALID_IDX; /// @brief coordinate offset table for neighboring voxels OPENVDB_API extern const Coord COORD_OFFSETS[26]; //////////////////////////////////////// /// Return @a voxelCoord rounded to the closest integer coordinates. inline Coord nearestCoord(const Vec3d& voxelCoord) { Coord ijk; ijk[0] = int(std::floor(voxelCoord[0])); ijk[1] = int(std::floor(voxelCoord[1])); ijk[2] = int(std::floor(voxelCoord[2])); return ijk; } //////////////////////////////////////// /// @brief Functor for use with tools::foreach() to compute the boolean intersection /// between the value masks of corresponding leaf nodes in two trees template class LeafTopologyIntOp { public: LeafTopologyIntOp(const TreeType2& tree): mOtherTree(&tree) {} inline void operator()(const typename TreeType1::LeafIter& lIter) const { const Coord xyz = lIter->origin(); const typename TreeType2::LeafNodeType* leaf = mOtherTree->probeConstLeaf(xyz); if (leaf) {//leaf node lIter->topologyIntersection(*leaf, zeroVal()); } else if (!mOtherTree->isValueOn(xyz)) {//inactive tile lIter->setValuesOff(); } } private: const TreeType2* mOtherTree; }; /// @brief Functor for use with tools::foreach() to compute the boolean difference /// between the value masks of corresponding leaf nodes in two trees template class LeafTopologyDiffOp { public: LeafTopologyDiffOp(const TreeType2& tree): mOtherTree(&tree) {} inline void operator()(const typename TreeType1::LeafIter& lIter) const { const Coord xyz = lIter->origin(); const typename TreeType2::LeafNodeType* leaf = mOtherTree->probeConstLeaf(xyz); if (leaf) {//leaf node lIter->topologyDifference(*leaf, zeroVal()); } else if (mOtherTree->isValueOn(xyz)) {//active tile lIter->setValuesOff(); } } private: const TreeType2* mOtherTree; }; //////////////////////////////////////// /// @brief Perform a boolean intersection between two leaf nodes' topology masks. /// @return a pointer to a new, boolean-valued tree containing the overlapping voxels. template inline typename TreeType1::template ValueConverter::Type::Ptr leafTopologyIntersection(const TreeType1& lhs, const TreeType2& rhs, bool threaded = true) { typedef typename TreeType1::template ValueConverter::Type BoolTreeType; typename BoolTreeType::Ptr topologyTree(new BoolTreeType( lhs, /*inactiveValue=*/false, /*activeValue=*/true, TopologyCopy())); tools::foreach(topologyTree->beginLeaf(), LeafTopologyIntOp(rhs), threaded); tools::pruneInactive(*topologyTree, threaded); return topologyTree; } /// @brief Perform a boolean difference between two leaf nodes' topology masks. /// @return a pointer to a new, boolean-valued tree containing the non-overlapping /// voxels from the lhs. template inline typename TreeType1::template ValueConverter::Type::Ptr leafTopologyDifference(const TreeType1& lhs, const TreeType2& rhs, bool threaded = true) { typedef typename TreeType1::template ValueConverter::Type BoolTreeType; typename BoolTreeType::Ptr topologyTree(new BoolTreeType( lhs, /*inactiveValue=*/false, /*activeValue=*/true, TopologyCopy())); tools::foreach(topologyTree->beginLeaf(), LeafTopologyDiffOp(rhs), threaded); tools::pruneInactive(*topologyTree, threaded); return topologyTree; } } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_UTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/Util.cc0000644000000000000000000000544612603226506013425 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/MapsUtil.h0000644000000000000000000002741412603226506014107 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/Name.h0000644000000000000000000000512612603226506013225 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_UTIL_NAME_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_NAME_HAS_BEEN_INCLUDED #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { typedef std::string Name; inline Name readString(std::istream& is) { uint32_t size; is.read(reinterpret_cast(&size), sizeof(uint32_t)); std::string buffer(size, ' '); if (size>0) is.read(&buffer[0], size); return buffer; } inline void writeString(std::ostream& os, const Name& name) { uint32_t size = uint32_t(name.size()); os.write(reinterpret_cast(&size), sizeof(uint32_t)); os.write(&name[0], size); } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_NAME_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/CpuTimer.h0000644000000000000000000001022512603226506014071 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_UTIL_CPUTIMER_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_CPUTIMER_HAS_BEEN_INCLUDED #include #include #include // for ostringstream #include //for setprecision namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { /// @brief Simple timer for basic profiling. /// /// @code /// CpuTimer timer; /// // code here will not be timed! /// timer.start("algorithm"); /// // code to be timed goes here /// timer.stop(); /// @endcode /// /// or to time multiple blocks of code /// /// @code /// CpuTimer timer("algorithm 1"); /// // code to be timed goes here /// timer.restart("algorithm 2"); /// // code to be timed goes here /// timer.stop(); /// @endcode class CpuTimer { public: /// @brief Initiate timer CpuTimer() : mT0(tbb::tick_count::now()) {} /// @brief Prints message and re-start timer. /// /// @note Should normally be followed by a call to stop() CpuTimer(const std::string& msg) { this->start(msg); } /// @brief Start timer. /// /// @note Should normally be followed by a call to time() inline void start() { mT0 = tbb::tick_count::now(); } /// @brief Print message and re-start timer. /// /// @note Should normally be followed by a call to stop() inline void start(const std::string& msg) { std::cerr << msg << " ... "; this->start(); } /// @brief Stop previous timer, print message and re-start timer. /// /// @note Should normally be followed by a call to stop() inline void restart(const std::string& msg) { this->stop(); this->start(msg); } /// Return Time diference in milliseconds since construction or start was called. inline double delta() const { tbb::tick_count::interval_t dt = tbb::tick_count::now() - mT0; return 1000.0*dt.seconds(); } /// @brief Print time in milliseconds since construction or start was called. inline void stop() const { const double t = this->delta(); std::ostringstream ostr; ostr << "completed in " << std::setprecision(3) << t << " ms\n"; std::cerr << ostr.str(); } private: tbb::tick_count mT0; };// CpuTimer } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_CPUTIMER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/NodeMasks.h0000644000000000000000000013572012603226506014235 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 //#include // for ffs namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { /// Return the number of on bits in the given 8-bit value. inline Index32 CountOn(Byte v) { // Simple LUT: #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif 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(static_cast(~v)); } /// Return the number of on bits in the given 32-bit value. inline Index32 CountOn(Index32 v) { v = v - ((v >> 1) & 0x55555555U); v = (v & 0x33333333U) + ((v >> 2) & 0x33333333U); return (((v + (v >> 4)) & 0xF0F0F0FU) * 0x1010101U) >> 24; } /// Return the number of off bits in the given 32-bit value. inline Index32 CountOff(Index32 v) { return CountOn(~v); } /// Return the number of on bits in the given 64-bit value. inline Index32 CountOn(Index64 v) { v = v - ((v >> 1) & UINT64_C(0x5555555555555555)); v = (v & UINT64_C(0x3333333333333333)) + ((v >> 2) & UINT64_C(0x3333333333333333)); return static_cast( (((v + (v >> 4)) & UINT64_C(0xF0F0F0F0F0F0F0F)) * UINT64_C(0x101010101010101)) >> 56); } /// Return the number of off bits in the given 64-bit value. inline Index32 CountOff(Index64 v) { return CountOn(~v); } /// Return the least significant on bit of the given 8-bit value. inline Index32 FindLowestOn(Byte v) { assert(v); #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif const Byte DeBruijn[8] = {0, 1, 6, 2, 7, 5, 4, 3}; return DeBruijn[Byte((v & -v) * 0x1DU) >> 5]; } /// Return the least significant on bit of the given 32-bit value. inline Index32 FindLowestOn(Index32 v) { assert(v); //return ffs(v); #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif const Byte DeBruijn[32] = { 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 }; return DeBruijn[Index32((v & -v) * 0x077CB531U) >> 27]; } /// Return the least significant on bit of the given 64-bit value. inline Index32 FindLowestOn(Index64 v) { assert(v); //return ffsll(v); #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif const Byte DeBruijn[64] = { 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12, }; return DeBruijn[Index64((v & -v) * UINT64_C(0x022FDD63CC95386D)) >> 58]; } /// Return the most significant on bit of the given 32-bit value. inline Index32 FindHighestOn(Index32 v) { #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif const Byte DeBruijn[32] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 }; v |= v >> 1; // first round down to one less than a power of 2 v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; return DeBruijn[Index32(v * 0x07C4ACDDU) >> 27]; } //////////////////////////////////////// /// Base class for the bit mask iterators template class BaseMaskIterator { protected: Index32 mPos;//bit position const NodeMask* mParent;//this iterator can't change the parent_mask! public: BaseMaskIterator() : mPos(NodeMask::SIZE), mParent(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 // /// @brief Apply a functor to the words of the this and the other mask. /// /// @details An example that implements the "operator&=" method: /// @code /// struct Op { inline void operator()(W &w1, const W& w2) const { w1 &= w2; } }; /// @endcode template const NodeMask& foreach(const NodeMask& other, const WordOp& op) { Word *w1 = mWords; const Word *w2 = other.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2) op( *w1, *w2); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const WordOp& op) { Word *w1 = mWords; const Word *w2 = other1.mWords, *w3 = other2.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2, ++w3) op( *w1, *w2, *w3); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const NodeMask& other3, const WordOp& op) { Word *w1 = mWords; const Word *w2 = other1.mWords, *w3 = other2.mWords, *w4 = other3.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2, ++w3, ++w4) op( *w1, *w2, *w3, *w4); return *this; } /// @brief Bitwise intersection const NodeMask& operator&=(const NodeMask& other) { Word *w1 = mWords; const Word *w2 = other.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2) *w1 &= *w2; return *this; } /// @brief Bitwise union const NodeMask& operator|=(const NodeMask& other) { Word *w1 = mWords; const Word *w2 = other.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2) *w1 |= *w2; return *this; } /// @brief Bitwise difference const NodeMask& operator-=(const NodeMask& other) { Word *w1 = mWords; const Word *w2 = other.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2) *w1 &= ~*w2; return *this; } /// @brief Bitwise XOR const NodeMask& operator^=(const NodeMask& other) { Word *w1 = mWords; const Word *w2 = other.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2) *w1 ^= *w2; return *this; } NodeMask operator!() const { NodeMask m(*this); m.toggle(); return m; } NodeMask operator&(const NodeMask& other) const { NodeMask m(*this); m &= other; return m; } NodeMask operator|(const NodeMask& other) const { NodeMask m(*this); m |= other; return m; } NodeMask operator^(const NodeMask& other) const { NodeMask m(*this); m ^= other; return m; } /// Return the byte size of this NodeMask static Index32 memUsage() { return static_cast(WORD_COUNT*sizeof(Word)); } /// Return the total number of on bits Index32 countOn() const { Index32 sum = 0, n = WORD_COUNT; for (const Word* w = mWords; n--; ++w) sum += CountOn(*w); return sum; } /// Return the total number of on bits Index32 countOff() const { return SIZE-this->countOn(); } /// Set the nth bit on void setOn(Index32 n) { assert( (n >> 6) < WORD_COUNT ); mWords[n >> 6] |= Word(1) << (n & 63); } /// Set the nth bit off void setOff(Index32 n) { assert( (n >> 6) < WORD_COUNT ); mWords[n >> 6] &= ~(Word(1) << (n & 63)); } /// Set the nth bit to the specified state void set(Index32 n, bool On) { On ? this->setOn(n) : this->setOff(n); } /// Set all bits to the specified state void set(bool on) { const Word state = on ? ~Word(0) : Word(0); Index32 n = WORD_COUNT; for (Word* w = mWords; n--; ++w) *w = state; } /// Set all bits on void setOn() { Index32 n = WORD_COUNT; for (Word* w = mWords; n--; ++w) *w = ~Word(0); } /// Set all bits off void setOff() { Index32 n = WORD_COUNT; for (Word* w = mWords; n--; ++w) *w = Word(0); } /// Toggle the state of the nth bit void toggle(Index32 n) { assert( (n >> 6) < WORD_COUNT ); mWords[n >> 6] ^= Word(1) << (n & 63); } /// Toggle the state of all bits in the mask void toggle() { Index32 n = WORD_COUNT; for (Word* w = mWords; n--; ++w) *w = ~*w; } /// Set the first bit on void setFirstOn() { this->setOn(0); } /// Set the last bit on void setLastOn() { this->setOn(SIZE-1); } /// Set the first bit off void setFirstOff() { this->setOff(0); } /// Set the last bit off void setLastOff() { this->setOff(SIZE-1); } /// Return @c true if the nth bit is on bool isOn(Index32 n) const { assert( (n >> 6) < WORD_COUNT ); return 0 != (mWords[n >> 6] & (Word(1) << (n & 63))); } /// Return @c true if the nth bit is off bool isOff(Index32 n) const {return !this->isOn(n); } /// Return @c true if all the bits are on bool isOn() const { int n = WORD_COUNT; for (const Word *w = mWords; n-- && *w++ == ~Word(0);) ; return n == -1; } /// Return @c true if all the bits are off bool isOff() const { int n = WORD_COUNT; for (const Word *w = mWords; n-- && *w++ == Word(0);) ; return n == -1; } 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 // /// @brief Apply a functor to the words of the this and the other mask. /// /// @details An example that implements the "operator&=" method: /// @code /// struct Op { inline void operator()(Word &w1, const Word& w2) const { w1 &= w2; } }; /// @endcode template const NodeMask& foreach(const NodeMask& other, const WordOp& op) { op(mByte, other.mByte); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const WordOp& op) { op(mByte, other1.mByte, other2.mByte); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const NodeMask& other3, const WordOp& op) { op(mByte, other1.mByte, other2.mByte, other3.mByte); return *this; } /// @brief Bitwise intersection const NodeMask& operator&=(const NodeMask& other) { mByte &= other.mByte; return *this; } /// @brief Bitwise union const NodeMask& operator|=(const NodeMask& other) { mByte |= other.mByte; return *this; } /// @brief Bitwise difference const NodeMask& operator-=(const NodeMask& other) { mByte &= static_cast(~other.mByte); return *this; } /// @brief Bitwise XOR const NodeMask& operator^=(const NodeMask& other) { mByte ^= other.mByte; return *this; } NodeMask operator!() const { NodeMask m(*this); m.toggle(); return m; } NodeMask operator&(const NodeMask& other) const { NodeMask m(*this); m &= other; return m; } NodeMask operator|(const NodeMask& other) const { NodeMask m(*this); m |= other; return m; } NodeMask operator^(const NodeMask& other) const { NodeMask m(*this); m ^= other; return m; } /// Return the byte size of this NodeMask static Index32 memUsage() { return 1; } /// Return the total number of on bits Index32 countOn() const { return CountOn(mByte); } /// Return the total number of on bits Index32 countOff() const { return CountOff(mByte); } /// Set the nth bit on void setOn(Index32 n) { assert( n < 8 ); mByte = mByte | static_cast(0x01U << (n & 7)); } /// Set the nth bit off void setOff(Index32 n) { assert( n < 8 ); mByte = mByte & static_cast(~(0x01U << (n & 7))); } /// Set the nth bit to the specified state void set(Index32 n, bool On) { On ? this->setOn(n) : this->setOff(n); } /// Set all bits to the specified state void set(bool on) { mByte = on ? 0xFFU : 0x00U; } /// Set all bits on void setOn() { mByte = 0xFFU; } /// Set all bits off void setOff() { mByte = 0x00U; } /// Toggle the state of the nth bit void toggle(Index32 n) { assert( n < 8 ); mByte = mByte ^ static_cast(0x01U << (n & 7)); } /// Toggle the state of all bits in the mask void toggle() { mByte = static_cast(~mByte); } /// Set the first bit on void setFirstOn() { this->setOn(0); } /// Set the last bit on void setLastOn() { this->setOn(7); } /// Set the first bit off void setFirstOff() { this->setOff(0); } /// Set the last bit off void setLastOff() { this->setOff(7); } /// Return true if the nth bit is on bool isOn(Index32 n) const { assert( n < 8 ); return mByte & (0x01U << (n & 7)); } /// Return true if the nth bit is off bool isOff(Index32 n) const {return !this->isOn(n); } /// Return true if all the bits are on bool isOn() const { return mByte == 0xFFU; } /// Return true if all the bits are off bool isOff() const { return mByte == 0; } Index32 findFirstOn() const { return mByte ? FindLowestOn(mByte) : 8; } Index32 findFirstOff() const { const Byte b = static_cast(~mByte); return b ? FindLowestOn(b) : 8; } /* //@{ /// Return the nth word of the bit mask, for a word of arbitrary size. /// @note This version assumes WordT=Byte and n=0! template WordT getWord(Index n) const { 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 = static_cast(mByte & (0xFFU << start)); return b ? FindLowestOn(b) : 8; } Index32 findNextOff(Index32 start) const { if (start>=8) return 8; const Byte b = static_cast(~mByte & (0xFFU << start)); return b ? FindLowestOn(b) : 8; } };// NodeMask<1> /// @brief Template specialization of NodeMask for Log2Dim=2, i.e. 4^3 nodes template<> class NodeMask<2> { public: static const Index32 LOG2DIM = 2; static const Index32 DIM = 4; static const Index32 SIZE = 64; static const Index32 WORD_COUNT = 1; 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 // /// @brief Apply a functor to the words of the this and the other mask. /// /// @details An example that implements the "operator&=" method: /// @code /// struct Op { inline void operator()(Word &w1, const Word& w2) const { w1 &= w2; } }; /// @endcode template const NodeMask& foreach(const NodeMask& other, const WordOp& op) { op(mWord, other.mWord); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const WordOp& op) { op(mWord, other1.mWord, other2.mWord); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const NodeMask& other3, const WordOp& op) { op(mWord, other1.mWord, other2.mWord, other3.mWord); return *this; } /// @brief Bitwise intersection const NodeMask& operator&=(const NodeMask& other) { mWord &= other.mWord; return *this; } /// @brief Bitwise union const NodeMask& operator|=(const NodeMask& other) { mWord |= other.mWord; return *this; } /// @brief Bitwise difference const NodeMask& operator-=(const NodeMask& other) { mWord &= ~other.mWord; return *this; } /// @brief Bitwise XOR const NodeMask& operator^=(const NodeMask& other) { mWord ^= other.mWord; return *this; } NodeMask operator!() const { NodeMask m(*this); m.toggle(); return m; } NodeMask operator&(const NodeMask& other) const { NodeMask m(*this); m &= other; return m; } NodeMask operator|(const NodeMask& other) const { NodeMask m(*this); m |= other; return m; } NodeMask operator^(const NodeMask& other) const { NodeMask m(*this); m ^= other; return m; } /// Return the byte size of this NodeMask static Index32 memUsage() { return 8; } /// Return the total number of on bits Index32 countOn() const { return CountOn(mWord); } /// Return the total number of on bits Index32 countOff() const { return CountOff(mWord); } /// Set the nth bit on void setOn(Index32 n) { assert( n < 64 ); mWord |= UINT64_C(0x01) << (n & 63); } /// Set the nth bit off void setOff(Index32 n) { assert( n < 64 ); mWord &= ~(UINT64_C(0x01) << (n & 63)); } /// Set the nth bit to the specified state void set(Index32 n, bool On) { On ? this->setOn(n) : this->setOff(n); } /// Set all bits to the specified state void set(bool on) { mWord = on ? UINT64_C(0xFFFFFFFFFFFFFFFF) : UINT64_C(0x00); } /// Set all bits on void setOn() { mWord = UINT64_C(0xFFFFFFFFFFFFFFFF); } /// Set all bits off void setOff() { mWord = UINT64_C(0x00); } /// Toggle the state of the nth bit void toggle(Index32 n) { assert( n < 64 ); mWord ^= UINT64_C(0x01) << (n & 63); } /// Toggle the state of all bits in the mask void toggle() { mWord = ~mWord; } /// Set the first bit on void setFirstOn() { this->setOn(0); } /// Set the last bit on void setLastOn() { this->setOn(63); } /// Set the first bit off void setFirstOff() { this->setOff(0); } /// Set the last bit off void setLastOff() { this->setOff(63); } /// Return true if the nth bit is on bool isOn(Index32 n) const { assert( n < 64 ); return 0 != (mWord & (UINT64_C(0x01) << (n & 63))); } /// Return true if the nth bit is off bool isOff(Index32 n) const {return !this->isOn(n); } /// Return true if all the bits are on bool isOn() const { return mWord == UINT64_C(0xFFFFFFFFFFFFFFFF); } /// Return true if all the bits are off bool isOff() const { return mWord == 0; } 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; i(mIntSize*sizeof(Index32) + sizeof(*this)); } Index32 countOn() const { assert(mBits); Index32 n=0; for (Index32 i=0; i< mIntSize; ++i) n += CountOn(mBits[i]); return n; } Index32 countOff() const { return mBitSize-this->countOn(); } void setOn(Index32 i) { assert(mBits); assert( (i>>5) < mIntSize); mBits[i>>5] |= 1<<(i&31); } void setOff(Index32 i) { assert(mBits); assert( (i>>5) < mIntSize); mBits[i>>5] &= ~(1<<(i&31)); } void set(Index32 i, bool On) { On ? this->setOn(i) : this->setOff(i); } void setOn() { assert(mBits); for (Index32 i=0; i>5) < mIntSize); mBits[i>>5] ^= 1<<(i&31); } void toggle() { assert(mBits); for (Index32 i=0; isetOn(0); } void setLastOn() { this->setOn(mBitSize-1); } void setFirstOff() { this->setOff(0); } void setLastOff() { this->setOff(mBitSize-1); } bool isOn(Index32 i) const { assert(mBits); assert( (i>>5) < mIntSize); return ( mBits[i >> 5] & (1<<(i&31)) ); } bool isOff(Index32 i) const { assert(mBits); assert( (i>>5) < mIntSize); return ( ~mBits[i >> 5] & (1<<(i&31)) ); } bool isOn() const { if (!mBits) return false;//undefined is off for (Index32 i=0; 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<(sizeof(Index32*)+(2+mIntSize)*sizeof(Index32));//in bytes } }; // class RootNodeMask } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_NODEMASKS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/logging.h0000644000000000000000000000757612603226506014006 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/PagedArray.h0000644000000000000000000007243512603226506014373 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// /// /// @file PagedArray.h /// /// @author Ken Museth /// /// @brief Concurrent page-based linear data structure with O(1) /// random access and std-compliant iterators. It is /// primarily intended for applications that involve /// multi-threading of dynamically growing linear arrays with /// fast random access. #ifndef OPENVDB_UTIL_PAGED_ARRAY_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_PAGED_ARRAY_HAS_BEEN_INCLUDED #include #include #include #include // std::swap #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { //////////////////////////////////////// /// @brief Concurrent page-based linear data structure with O(1) /// random access and std-compliant iterators. It is /// primarily intended for applications that involve /// multi-threading of dynamically growing linear arrays with /// fast random access. /// /// @note Multiple threads can grow the page-table and push_back /// new elements concurrently. A ValueBuffer provides accelerated /// and threadsafe push_back at the cost of potentially re-ordering /// elements (when multiple instances are used). /// /// @details This data structure employes contiguous pages of elements /// (like a std::deque) which avoids moving data when the /// capacity is out-grown and new pages are allocated. The /// size of the pages can be controlled with the Log2PageSize /// template parameter (defaults to 1024 elements of type ValueT). /// /// There are three fundamentally different ways to insert elements to /// this container - each with different advanteges and disadvanteges. /// /// The simplest way to insert elements is to use PagedArray::push_back e.g. /// @code /// PagedArray array; /// for (int i=0; i<100000; ++i) array.push_back(i); /// @endcode /// or with tbb task-based multi-threading /// @code /// struct Functor1 { /// Functor1(int n, PagedArray& _array) : array(&_array) { /// tbb::parallel_for(tbb::blocked_range(0, n, PagedArray::pageSize()), *this); /// } /// void operator()(const tbb::blocked_range& r) const { /// for (int i=r.begin(), n=r.end(); i!=n; ++i) array->push_back(i); /// } /// PagedArray* array; /// }; /// PagedArray array; /// Functor1 tmp(10000, array); /// @endcode /// PagedArray::push_back has the advantage that it's thread-safe and /// preserves the ordering of the inserted elements. In fact it returns /// the linear offset to the added element which can then be used for /// fast O(1) random access. The disadvantage is it's the slowest of /// the three different ways of inserting elements. /// /// The fastest way (by far) to insert elements is to use one (or /// more) instances of a PagedArray::ValueBuffer, e.g. /// @code /// PagedArray array; /// PagedArray::ValueBuffer buffer(array); /// for (int i=0; i<100000; ++i) buffer.push_back(i); /// buffer.flush(); /// @endcode /// or /// @code /// PagedArray array; /// {//local scope of a single thread /// PagedArray::ValueBuffer buffer(array); /// for (int i=0; i<100000; ++i) buffer.push_back(i); /// } /// @endcode /// or with tbb task-based multi-threading /// @code /// struct Functor2 { /// Functor2(int n, PagedArray& array) : buffer(array) { /// tbb::parallel_for(tbb::blocked_range(0, n, PagedArray::pageSize()), *this); /// } /// void operator()(const tbb::blocked_range& r) const { /// for (int i=r.begin(), n=r.end(); i!=n; ++i) buffer.push_back(i); /// } /// mutable typename PagedArray::ValueBuffer buffer; /// }; /// PagedArray array; /// Functor2 tmp(10000, array); /// @endcode /// or with tbb Thread Local Storage for even better performance (due /// to fewer concurrent instantiations of partially full ValueBuffers) /// @code /// struct Functor3 { /// typedef tbb::enumerable_thread_specific::ValueBuffer> PoolType; /// Functor3(size_t n, PoolType& _pool) : pool(&_pool) { /// tbb::parallel_for(tbb::blocked_range(0, n, PagedArray::pageSize()), *this); /// } /// void operator()(const tbb::blocked_range& r) const { /// PagedArray::ValueBuffer& buffer = pool->local(); /// for (int i=r.begin(), n=r.end(); i!=n; ++i) buffer.push_back(i); /// } /// PoolType* pool; /// }; /// PagedArray array; /// PagedArray::ValueBuffer exemplar(array);//dummy used for initialization /// Functor3::PoolType pool(exemplar);//thread local storage pool of ValueBuffers /// Functor3 tmp(10000, pool); /// for (Functor3::PoolType::iterator i=pool.begin(); i!=pool.end(); ++i) i->flush(); /// @endcode /// This technique generally outperforms PagedArray::push_back, /// std::vector::push_back, std::deque::push_back and even /// tbb::concurrent_vector::push_back. Additionally it /// is thread-safe as long as each thread has it's own instance of a /// PagedArray::ValueBuffer. The only disadvantage is the ordering of /// the elements is undefined if multiple instance of a /// PagedArray::ValueBuffer are employed. This is typically the case /// in the context of multi-threading, where the /// ordering of inserts are undefined anyway. Note that a local scope /// can be used to guarentee that the ValueBuffer has inerted all its /// elements by the time the scope ends. Alternatively the ValueBuffer /// can be explicitly flushed by calling ValueBuffer::flush. /// /// The third way to insert elements is to resize the container and use /// random access, e.g. /// @code /// PagedArray array; /// array.resize(100000); /// for (int i=0; i<100000; ++i) array[i] = i; /// @endcode /// or in terms of the random access iterator /// @code /// PagedArray array; /// array.resize(100000); /// for (PagedArray::Iterator i=array.begin(); i!=array.end(); ++i) *i = i.pos(); /// @endcode /// While this approach is both fast and thread-safe it suffers from the /// major disadvantage that the problem size, i.e. number of elements, needs to /// be known in advance. If that's the case you might as well consider /// using std::vector or a raw c-style array! In other words the /// PagedArray is most useful in the context of applications that /// involve multi-threading of dynamically growing linear arrays that /// require fast random access. template class PagedArray { private: class Page; typedef std::deque PageTableT; public: typedef ValueT ValueType; /// @brief Default constructor PagedArray() : mPageTable(), mSize(), mCapacity(0), mGrowthMutex() { mSize = 0; } /// @brief Destructor removed all allocated pages ~PagedArray() { this->clear(); } /// @brief Caches values into a local memory Page to improve /// performance of push_back into a PagedArray. /// /// @note The ordering of inserted elements is undefined when /// multiple ValueBuffers are used! /// /// @warning By design this ValueBuffer is not threadsafe so /// make sure to create an instance per thread! class ValueBuffer; /// Const std-compliant iterator class ConstIterator; /// Non-const std-compliant iterator class Iterator; /// @brief Thread safe insertion, adds a new element at /// the end and increases the container size by one. /// /// @note Constant time complexity. May allocate a new page. size_t push_back(const ValueType& value) { const size_t index = mSize.fetch_and_increment(); if (index >= mCapacity) this->grow(index); (*mPageTable[index >> Log2PageSize])[index] = value; return index; } /// @brief Slightly faster then the thread-safe push_back above. /// /// @note For best performance consider using the ValueBuffer! /// /// @warning Not thread-safe! size_t push_back_unsafe(const ValueType& value) { const size_t index = mSize.fetch_and_increment(); if (index >= mCapacity) { mPageTable.push_back( new Page() ); mCapacity += Page::Size; } (*mPageTable[index >> Log2PageSize])[index] = value; return index; } /// @brief Returns the last element, decrements the size by one. /// /// @details Consider subsequnetly calling shrink_to_fit to /// reduce the page table to match the new size. /// /// @note Calling this method on an empty containter is /// undefined (as is also the case for std containers). /// /// @warning If values were added to the container by means of /// multiple ValueBuffers the last value might not be what you /// expect since the ordering is generally not perserved. Only /// PagedArray::push_back preserves the ordering (or a single /// instance of a ValueBuffer). ValueType pop_back() { assert(mSize>0); --mSize; return (*mPageTable[mSize >> Log2PageSize])[mSize]; } /// @brief Reduce the page table to fix the current size. /// /// @warning Not thread-safe! void shrink_to_fit(); /// @brief Return a reference to the value at the specified offset /// /// @note This random access has constant time complexity. /// /// @warning It is assumed that the i'th element is already allocated! ValueType& operator[](size_t i) { assert(i>Log2PageSize])[i]; } /// @brief Return a const-reference to the value at the specified offset /// /// @note This random access has constant time complexity. /// /// @warning It is assumed that the i'th element is already allocated! const ValueType& operator[](size_t i) const { assert(i>Log2PageSize])[i]; } /// @brief Set all elements to the specified value void fill(const ValueType& v) { tbb::spin_mutex::scoped_lock lock(mGrowthMutex); Fill tmp(this, v); } /// @brief Resize this array to the specified size. /// /// @note This will grow or shrink the page table. /// /// @warning Not thread-safe! void resize(size_t size) { mSize = size; if (size > mCapacity) { this->grow(size-1); } else { this->shrink_to_fit(); } } /// @brief Resize this array to the specified size and /// set all elements to the specified value. /// /// @warning Not thread-safe! void resize(size_t size, const ValueType& v) { this->resize(size); this->fill(v); } /// @brief Return the number of elements in this array. size_t size() const { return mSize; } /// @brief Return the maximum number of elements that this array /// can contain without allocating more memory pages. size_t capacity() const { return mCapacity; } /// @brief Return the number of additional elements that can be /// added to this array without allocating more memory pages. size_t freeCount() const { return mCapacity - mSize; } /// @brief Return the number of allocated memory pages. size_t pageCount() const { return mPageTable.size(); } /// @brief Return the number of elements per memory page. static size_t pageSize() { return Page::Size; } /// @brief Return log2 of the number of elements per memory page. static size_t log2PageSize() { return Log2PageSize; } /// @brief Return the memory footprint of this array in bytes. size_t memUsage() const { return sizeof(*this) + mPageTable.size() * Page::memUsage(); } /// @brief Return true if the container contains no elements. bool isEmpty() const { return mSize == 0; } /// @brief Return true if the page table is partially full, i.e. the /// last non-empty page contains less than pageSize() elements. /// /// @details When the page table is partially full calling merge() /// or using a ValueBuffer will rearrange the ordering of /// existing elements. bool isPartiallyFull() const { return (mSize & Page::Mask) > 0; } /// @brief Removes all elements from the array and delete all pages. /// /// @warning Not thread-safe! void clear() { tbb::spin_mutex::scoped_lock lock(mGrowthMutex); for (size_t i=0, n=mPageTable.size(); ibegin(), this->end(), std::less() ); } /// @brief Parallel sort of all the elements in descending order. void invSort() { tbb::parallel_sort(this->begin(), this->end(), std::greater()); } /// @brief Parallel sort of all the elements based on a custom /// functor with the api: /// @code bool operator()(const ValueT& a, const ValueT& b) @endcode /// which returns true if a comes before b. template void sort() { tbb::parallel_sort(this->begin(), this->end(), Functor() ); } /// @brief Transfer all the elements (and pages) from the other array to this array. /// /// @note The other PagedArray is empty on return. /// /// @warning The ordering of elements is undefined if this page table is partially full! void merge(PagedArray& other); /// @brief Print information for debugging void print(std::ostream& os = std::cout) const { os << "PagedArray:\n" << "\tSize: " << this->size() << " elements\n" << "\tPage table: " << this->pageCount() << " pages\n" << "\tPage size: " << this->pageSize() << " elements\n" << "\tCapacity: " << this->capacity() << " elements\n" << "\tFootrpint: " << this->memUsage() << " bytes\n"; } private: // Disallow copy construction and assignment PagedArray(const PagedArray&);//not implemented void operator=(const PagedArray&);//not implemented friend class ValueBuffer; // Private class for concurrent fill struct Fill; void grow(size_t index) { tbb::spin_mutex::scoped_lock lock(mGrowthMutex); while(index >= mCapacity) { mPageTable.push_back( new Page() ); mCapacity += Page::Size; } } void add_full(Page*& page, size_t size); void add_partially_full(Page*& page, size_t size); void add(Page*& page, size_t size) { tbb::spin_mutex::scoped_lock lock(mGrowthMutex); if (size == Page::Size) {//page is full this->add_full(page, size); } else if (size>0) {//page is only partially full this->add_partially_full(page, size); } } PageTableT mPageTable;//holds points to allocated pages tbb::atomic mSize;// current number of elements in array size_t mCapacity;//capacity of array given the current page count tbb::spin_mutex mGrowthMutex;//Mutex-lock required to grow pages }; // Public class PagedArray //////////////////////////////////////////////////////////////////////////////// template void PagedArray::shrink_to_fit() { if (mPageTable.size() > (mSize >> Log2PageSize) + 1) { tbb::spin_mutex::scoped_lock lock(mGrowthMutex); const size_t pageCount = (mSize >> Log2PageSize) + 1; if (mPageTable.size() > pageCount) { delete mPageTable.back(); mPageTable.pop_back(); mCapacity -= Page::Size; } } } template void PagedArray::merge(PagedArray& other) { if (!other.isEmpty()) { tbb::spin_mutex::scoped_lock lock(mGrowthMutex); // extract last partially full page if it exists Page* page = NULL; const size_t size = mSize & Page::Mask; //number of elements in the last page if ( size > 0 ) { page = mPageTable.back(); mPageTable.pop_back(); mSize -= size; } // transfer all pages from the other page table mPageTable.insert(mPageTable.end(), other.mPageTable.begin(), other.mPageTable.end()); mSize += other.mSize; mCapacity = Page::Size*mPageTable.size(); other.mSize = 0; other.mCapacity = 0; PageTableT().swap(other.mPageTable); // add back last partially full page if (page) this->add_partially_full(page, size); } } template void PagedArray::add_full(Page*& page, size_t size) { assert(size == Page::Size);//page must be full if (mSize & Page::Mask) {//page-table is partially full Page*& tmp = mPageTable.back(); std::swap(tmp, page);//swap last table entry with page } mPageTable.push_back( page ); mCapacity += Page::Size; mSize += size; page = NULL; } template void PagedArray::add_partially_full(Page*& page, size_t size) { assert(size > 0 && size < Page::Size);//page must be partially full if (size_t m = mSize & Page::Mask) {//page table is also partially full ValueT *s = page->data(), *t = mPageTable.back()->data() + m; for (size_t i=std::min(mSize+size, mCapacity)-mSize; i; --i) *t++ = *s++; if (mSize+size > mCapacity) {//grow page table mPageTable.push_back( new Page() ); t = mPageTable.back()->data(); for (size_t i=mSize+size-mCapacity; i; --i) *t++ = *s++; mCapacity += Page::Size; } } else {//page table is full so simply append page mPageTable.push_back( page ); mCapacity += Page::Size; page = NULL; } mSize += size; } //////////////////////////////////////////////////////////////////////////////// // Public member-class of PagedArray template class PagedArray:: ValueBuffer { public: typedef PagedArray PagedArrayType; /// @brief Constructor from a PageArray ValueBuffer(PagedArray& parent) : mParent(&parent), mPage(new Page()), mSize(0) {} /// @warning This copy-constructor is shallow in the sense that no /// elements are copied, i.e. size = 0. ValueBuffer(const ValueBuffer& other) : mParent(other.mParent), mPage(new Page()), mSize(0) {} /// @brief Destructor that transfers an buffered values to the parent PagedArray. ~ValueBuffer() { this->flush(); delete mPage; } /// @brief Add a value to the buffer and increment the size. /// /// @details If the internal memory page is full it will /// automaically flush the page to the parent PagedArray. void push_back(const ValueT& v) { (*mPage)[mSize++] = v; if (mSize == Page::Size) this->flush(); } /// @brief Manually transfer the values in this buffer to the parent PagedArray. /// /// @note This method is also called by the destructor and /// puach_back so it should only be called when manually want to /// sync up the buffer with the array, e.g. during debugging. void flush() { mParent->add(mPage, mSize); if (mPage == NULL) mPage = new Page(); mSize = 0; } /// @brief Return a reference to the parent PagedArray PagedArrayType& parent() const { return *mParent; } /// @brief Return the current number of elements cached in this buffer. size_t size() const { return mSize; } private: ValueBuffer& operator=(const ValueBuffer& other);//not implemented PagedArray* mParent; Page* mPage; size_t mSize; };// Public class PagedArray::ValueBuffer //////////////////////////////////////////////////////////////////////////////// // Const std-compliant iterator // Public member-class of PagedArray template class PagedArray:: ConstIterator : public std::iterator { public: typedef std::iterator BaseT; typedef typename BaseT::difference_type difference_type; // constructors and assignment ConstIterator() : mPos(0), mParent(NULL) {} ConstIterator(const PagedArray& parent, size_t pos=0) : mPos(pos), mParent(&parent) {} ConstIterator(const ConstIterator& other) : mPos(other.mPos), mParent(other.mParent) {} ConstIterator& operator=(const ConstIterator& other) { mPos=other.mPos; mParent=other.mParent; return *this; } // prefix ConstIterator& operator++() { ++mPos; return *this; } ConstIterator& operator--() { --mPos; return *this; } // postfix ConstIterator operator++(int) { ConstIterator tmp(*this); ++mPos; return tmp; } ConstIterator operator--(int) { ConstIterator tmp(*this); --mPos; return tmp; } // value access const ValueT& operator*() const { return (*mParent)[mPos]; } const ValueT* operator->() const { return &(this->operator*()); } const ValueT& operator[](const difference_type& pos) const { return (*mParent)[mPos+pos]; } // offset ConstIterator& operator+=(const difference_type& pos) { mPos += pos; return *this; } ConstIterator& operator-=(const difference_type& pos) { mPos -= pos; return *this; } ConstIterator operator+(const difference_type &pos) const { return Iterator(*mParent,mPos+pos); } ConstIterator operator-(const difference_type &pos) const { return Iterator(*mParent,mPos-pos); } difference_type operator-(const ConstIterator& other) const { return mPos - other.pos(); } // comparisons bool operator==(const ConstIterator& other) const { return mPos == other.mPos; } bool operator!=(const ConstIterator& other) const { return mPos != other.mPos; } bool operator>=(const ConstIterator& other) const { return mPos >= other.mPos; } bool operator<=(const ConstIterator& other) const { return mPos <= other.mPos; } bool operator< (const ConstIterator& other) const { return mPos < other.mPos; } bool operator> (const ConstIterator& other) const { return mPos > other.mPos; } // non-std methods bool isValid() const { return mParent != NULL && mPos < mParent->size(); } size_t pos() const { return mPos; } private: size_t mPos; const PagedArray* mParent; };// Public class PagedArray::ConstIterator //////////////////////////////////////////////////////////////////////////////// // Public member-class of PagedArray template class PagedArray:: Iterator : public std::iterator { public: typedef std::iterator BaseT; typedef typename BaseT::difference_type difference_type; // constructors and assignment Iterator() : mPos(0), mParent(NULL) {} Iterator(PagedArray& parent, size_t pos=0) : mPos(pos), mParent(&parent) {} Iterator(const Iterator& other) : mPos(other.mPos), mParent(other.mParent) {} Iterator& operator=(const Iterator& other) { mPos=other.mPos; mParent=other.mParent; return *this; } // prefix Iterator& operator++() { ++mPos; return *this; } Iterator& operator--() { --mPos; return *this; } // postfix Iterator operator++(int) { Iterator tmp(*this); ++mPos; return tmp; } Iterator operator--(int) { Iterator tmp(*this); --mPos; return tmp; } // value access ValueT& operator*() const { return (*mParent)[mPos]; } ValueT* operator->() const { return &(this->operator*()); } ValueT& operator[](const difference_type& pos) const { return (*mParent)[mPos+pos]; } // offset Iterator& operator+=(const difference_type& pos) { mPos += pos; return *this; } Iterator& operator-=(const difference_type& pos) { mPos -= pos; return *this; } Iterator operator+(const difference_type &pos) const { return Iterator(*mParent, mPos+pos); } Iterator operator-(const difference_type &pos) const { return Iterator(*mParent, mPos-pos); } difference_type operator-(const Iterator& other) const { return mPos - other.pos(); } // comparisons bool operator==(const Iterator& other) const { return mPos == other.mPos; } bool operator!=(const Iterator& other) const { return mPos != other.mPos; } bool operator>=(const Iterator& other) const { return mPos >= other.mPos; } bool operator<=(const Iterator& other) const { return mPos <= other.mPos; } bool operator< (const Iterator& other) const { return mPos < other.mPos; } bool operator> (const Iterator& other) const { return mPos > other.mPos; } // non-std methods bool isValid() const { return mParent != NULL && mPos < mParent->size(); } size_t pos() const { return mPos; } private: size_t mPos; PagedArray* mParent; };// Public class PagedArray::Iterator //////////////////////////////////////////////////////////////////////////////// // Private member-class of PagedArray implementing a memory page template class PagedArray:: Page { public: static const size_t Size = 1UL << Log2PageSize; static const size_t Mask = Size - 1UL; static size_t memUsage() { return sizeof(ValueT)*Size; } Page() : mData(new ValueT[Size]) {} ~Page() { delete [] mData; } ValueT& operator[](const size_t i) { return mData[i & Mask]; } const ValueT& operator[](const size_t i) const { return mData[i & Mask]; } void fill(const ValueT& v) { ValueT* p = mData; for (size_t i=Size; i; --i) *p++ = v; } ValueT* data() { return mData; } protected: Page(const Page& other);//copy construction is not implemented Page& operator=(const Page& rhs);//copy assignment is not implemented ValueT* mData; };// Private class PagedArray::Page //////////////////////////////////////////////////////////////////////////////// // Private member-class of PagedArray implementing concurrent fill of a Page template struct PagedArray:: Fill { Fill(PagedArray* _d, const ValueT& _v) : d(_d), v(_v) { tbb::parallel_for(tbb::blocked_range(0, d->pageCount()), *this); } void operator()(const tbb::blocked_range& r) const { for (size_t i=r.begin(); i!=r.end(); ++i) d->mPageTable[i]->fill(v); } PagedArray* d; const ValueT& v; };// Private class PagedArray::Fill } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_PAGED_ARRAY_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/NullInterrupter.h0000644000000000000000000001004712603226506015521 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file NullInterrupter.h #ifndef OPENVDB_UTIL_NULL_INTERRUPTER_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_NULL_INTERRUPTER_HAS_BEEN_INCLUDED #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { /// @brief Dummy NOOP interrupter class defining interface /// /// This shows the required interface for the @c InterrupterType template argument /// using by several threaded applications (e.g. tools/PointAdvect.h). The host /// application calls start() at the beginning of an interruptible operation, end() /// at the end of the operation, and wasInterrupted() periodically during the operation. /// If any call to wasInterrupted() returns @c true, the operation will be aborted. /// @note This Dummy interrupter will NEVER interrupt since wasInterrupted() always /// returns false! struct NullInterrupter { /// Default constructor NullInterrupter () {} /// Signal the start of an interruptible operation. /// @param name an optional descriptive name for the operation void start(const char* name = NULL) { (void)name; } /// Signal the end of an interruptible operation. void end() {} /// Check if an interruptible operation should be aborted. /// @param percent an optional (when >= 0) percentage indicating /// the fraction of the operation that has been completed /// @note this method is assumed to be thread-safe. The current /// implementation is clearly a NOOP and should compile out during /// optimization! inline bool wasInterrupted(int percent = -1) { (void)percent; return false; } }; /// This method allows NullInterrupter::wasInterrupted to be compiled /// out when client code only has a pointer (vs reference) to the interrupter. /// /// @note This is a free-standing function since C++ doesn't allow for /// partial template specialization (in client code of the interrupter). template inline bool wasInterrupted(T* i, int percent = -1) { return i && i->wasInterrupted(percent); } /// Specialization for NullInterrupter template<> inline bool wasInterrupted(util::NullInterrupter*, int) { return false; } } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_NULL_INTERRUPTER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/Formats.h0000644000000000000000000001245612603226506013764 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/CHANGES0000644000000000000000000021223112603226302012201 0ustar rootrootOpenVDB Version History ======================= Version 3.1.0 - October 1, 2015 Highlights: - New features: advection of arbitrary volumes, general-purpose preconditioned linear solver and Poisson solver, segmentation of topologically-enclosed regions of a volume, new and faster bitmask operators, concurrent paged array, volume diagnostics - Optimizations: threaded grid constructors and topology operations; faster mesh to volume conversion, SDF to fog volume conversion and grid pruning; faster, unbounded particle partitioning - New Houdini nodes: Advect, Diagnostics, Rasterize Points, Remap, Remove Divergence, Sort Points New features: - Added tools::VolumeAdvection, for sparse advection of non-level-set volumes. - Added a preconditioned conjugate gradient solver. - Added a Poisson solver for functions sampled on grids. - Added tools::extractEnclosedRegion, which detects topologically-enclosed (watertight) exterior regions (cavities) that can result from CSG union operations between level sets with concavities that are capped. (See the unit test TestPoissonSolver::testSolveWithSegmentDomain for an example in which this tool is used to identify regions of trapped fluid when solving for pressure in a volume of incompressible fluid.) - Added util::PagedArray, a concurrent, dynamic linear array data structure with fast O(1) value access (both random and sequential). - Added tools::Sampler, which provides a unified API for both staggered and non-staggered interpolation of various orders. - Added equality and inequality operators to Metadata and MetaMap. - Added tools::CheckLevelSet and tools::CheckFogVolume, which perform various tests on symmetric, narrow-band level sets and fog volumes, respectively, to diagnose potential issues. - Added support for value accessors that aren't registered with their trees. (Bypassing accessor registration can improve performance in rare cases but should be used with caution, since the accessor will be left in an invalid state if the tree topology is modified.) - Added a tree::stealNodes() method that transfers ownership of all nodes of a certain type and inserts them into a linear array. - Added a tools::createLevelSetBox() factory function for level-set grids. - Added tools::Dense::offsetToCoord(). - Added LeafNode::Buffer::data(), which provides direct access to a leaf node's voxel value array, avoiding out-of-core overhead. Use with caution. - Added a util::NodeMask::foreach() method for efficient evaluation of complex bitwise operations. - Added a bitwise difference method to util::NodeMask. - Added a -version flag to vdb_print, vdb_render and vdb_view. Improvements: - Deep, conversion and topology copy Grid constructors are now threaded and up to five times faster. - Grid::topologyUnion(), Grid::topologyIntersection() and Grid::topologyDifference() are now much faster due to threading. - Significantly improved the performance, parallel scaling and memory usage of the tools::meshToVolume(), and implemented a more robust inside/outside sign classification scheme. - Reimplemented tools::PointPartitioner for improved performance, concurrency and memory usage. The tool is now unbounded in the sense that points may be distributed anywhere in index space. - Significantly improved the performance of tools::sdfToFogVolume(). - Significantly improved the performance of the tools::sdfInteriorMask() and added support for both grid and tree inputs. - Made various optimizations and improvements to tools::LevelSetMorphing. - Aggregated tools::DiscreteField and tools::EnrightField (formerly in tools/LevelSetAdvect.h) and tools::VelocitySampler and tools::VelocityIntegrator (formerly in tools/PointAdvect.h) into a single header, tools/VelocityFields.h. - Modified tools::signedFloodFill() to accept grids of any signed scalar value type, not just floating-point grids. - tools::prune() is now faster, and it employs an improved compression technique on trees with floating-point values. Bug fixes: - Fixed a build issue that could result in spurious "Blosc encoding is not supported" errors unless OPENVDB_USE_BLOSC was #defined when compiling client code. - Added NaN and inf checks to tools::PointPartitioner. - Fixed a vdb_view issue whereby the frame buffer size did not necessarily match the window size. [Contributed by Rafael Campos] - Fixed a roundoff issue in tools::LevelSetTracker that could result in NaNs. - Changed tools::CheckNormGrad to check the magnitude of the gradient rather than the square of the magnitude. - Fixed parameter type inconsistencies in math/Ray.h and tools/RayIntersector.h. [Contributed by Kévin Dietrich] - Fixed incorrect handling of signed values in tools::clip() (and the Clip SOP). API changes: - Removed the math::Hermite class since it was no longer used and caused build issues for some. - Refactored the LevelSetAdvection, LevelSetFilter, LevelSetMeasure and LevelSetTracker tools. - Extended the API of tools::Diagnose and disabled copy construction. - Extended and unified the API of various Samplers. - Added an optional template argument to the ValueAccessor class to allow for unregistered accessors. Houdini: - Added a Rasterize Points SOP that produces density volumes and transfers arbitrary point attributes using a weighted-average scheme. The node incorporates a VOP subnetwork for procedural modeling, and its accompanying creation script defines a default network with VEX procedures for cloud and velocity field modeling. (See the creation script file header for installation details.) - Merged the Advect Level Set SOP into a new Advect SOP that supports advection of arbitrary volumes, not just level sets. - Added a Remove Divergence SOP that eliminates divergence from a velocity field. - Added a Diagnostics SOP that can identify various problems with level sets, fog volumes and other grids. - Added a Sort Points SOP that spatially reorders a list of points so that points that are close together in space are also close together in the list. This can improve CPU cache coherency and performance for random-access operations. - Added a Remap SOP that maps voxel values in an input range to values in an output range through a user-defined transfer function. - Added an option to the Convert SOP to activate interior voxels. [Contributed by SESI] - The To Spheres SOP can now optionally output a pscale attribute. - Added openvdb_houdini::SOP_NodeVDB::duplicateSourceStealable(), which in conjunction with the Unload flag can help to minimize deep copying of grids between nodes. The Advect, Convert, Fill, Filter, Fracture, Noise, Offset Level Set, Prune, Remap, Remove Divergence, Renormalize Level Set, Resize Narrow Band, Smooth Level Set and Transform SOPs all have this optimization enabled, meaning that they can potentially steal, rather than copy, data from upstream nodes that have the Unload flag enabled. [Contributed by Double Negative] - Redesigned the UI of the Visualize SOP and added toggles to draw with or without color, to use the grid name as the attribute name for points with values, and to attach grid index coordinates to points. - Added toggles to the Filter, Rebuild Level Set, Resize Narrow Band, Smooth Level Set and To Spheres SOPs to specify units in either world space or index space. - Fixed an issue whereby grids generated by the Rebuild Level Set SOP did not always display as surfaces in the viewport. - The Metadata SOP now sets appropriate viewport visualization options when the grid class is changed. Version 3.0.0 - January 14, 2015 - The io::File class now supports delayed loading of .vdb files, meaning that memory is not allocated for voxel values until the values are actually accessed. (This feature is enabled by default.) Until a grid has been fully loaded, its source .vdb file must not be modified or deleted, so for safety, io::File::open() automatically makes private copies of source files that are smaller than a user-specified limit (see io::File::setCopyMaxBytes()). The limit can be set to zero to disable copying, but if it cannot be guaranteed that a file will not be modified, then it is best not to enable delayed loading for that file. - .vdb files can now optionally be compressed with the Blosc LZ4 codec. Blosc compresses almost as well as ZLIB, but it is much faster. - Added tools::PointPartitioner, a tool for fast spatial sorting of points stored in an external array, and tools::PointIndexGrid, an acceleration structure for fast range and nearest-neighbor searches. - Added tree::NodeManager, which linearizes a tree to facilitate efficient multithreading across all tree levels. - Added tools::prune() (and other variants), which replaces and outperforms Tree::prune(). - Added tools::signedFloodFill(), which replaces and outperforms Tree::signedFloodFill(). - Added tools::changeBackground (and other variants), which replaces and outperforms Tree::setBackground(). - Added a fast but approximate narrow-band level set dilation method, a fast narrow-band level set erosion method, and a masked normalization method to tools::LevelSetTracker. - Added tools::Diagnose, which performs multithreaded diagnostics on grids to identify issues like values that are NaNs or out-of-range. It optionally generates a boolean grid of all values that fail user-defined tests. - Added optional alpha masks to tools::LevelSetMorphing. - Fixed an intermittent crash in tools::LevelSetMorphing. - Added tools::topologyToLevelSet(), which generates a level set from the implicit boundary between active and inactive voxels in an arbitrary input grid. [DWA internal] - Improved the performance of point scattering (by orders of magnitude) and added a DenseUniformPointScatter class as well as support for fractional numbers of particles per voxel. - Added edge-adjacent (6+12=18 neighbors) and vertex-adjacent (6+12+8=26 neighbors) dilation algorithms to tools::Morphology::dilateVoxels(). The default dilation pattern is still face-adjacent (6 neighbors). - Improved the performance and memory footprint of the ParticlesToLevelSet tool for large numbers (tens to hundreds of millions) of particles. - Added Tree::getNodes(), which allows for fast construction of linear arrays of tree nodes for use in multithreaded code such as the LeafManager or NodeManager. - Added math::Extrema and tools::extrema() to efficiently compute minimum and maximum values in a grid. - Added support for material color grids to all level set shaders, and added an option to vdb_render that allows one to specify a reference grid to be used for material color lookups. - Added openvdb::getLibraryVersionString() and OPENVDB_LIBRARY_VERSION_STRING. - Modified the mesh to volume converter to always set the grid background value to the exterior narrow-band width, and added finite value checks to narrow band parameters. - tools::volumeToMesh() now compiles for all grid types but throws an exception if the input grid does not have a scalar value type. - Added an io::File::readGrid() overload and readBuffers() overloads to the grid, tree and node classes that allow one to specify a bounding box against which to clip a grid while reading it. For large grids, clipping while reading can result in significantly lower memory usage than clipping after reading. - Added Grid::clipGrid(), which clips a grid against a world-space bounding box, and Grid::clip() and Tree::clip(), which clip against an index-space bounding box. - Added tools::clip(), which clips a grid either against a bounding box or against the active voxels of a mask grid. - io::File::readGridPartial() allocates the nodes of a grid's tree as before, but it now allocates leaf nodes without data buffers. (This feature is mainly for internal use; partially-read grids should be used with care if at all, and they should be treated as read-only.) - Grid names retrieved using an io::File::NameIterator now always uniquely identify grids; they no longer generate 'more than one grid named "x"' warnings when there are multiple grids of the same name in a file (for files written starting with this version of the OpenVDB library). - Fixed a bug in Tree::ValueOffIter that could cause depth-bounded iterators to return incorrect values. - Eliminated a recursive call in TreeValueIteratorBase::advance() that could cause crashes on systems with a limited stack size. - Fixed memory leaks in RootNode::topologyDifference() and RootNode::topologyIntersection(). - Fixed a memory leak in io::Queue when the queue was full and a write task could not be added within the timeout interval. - Fixed a potential division by zero crash in tools::compDiv() with integer-valued grids. - Fixed kernel normalization in tools::Filter so that it is correct for integer-valued grids. - Fixed a bug in LeafNode::Buffer::getValue() whereby Visual C++ would return a reference to a temporary. [Contributed by SESI] - Fixed a bug in tools::ParticlesToLevelSet related to attribute transfer when leaf nodes are produced without active values. - Added util/CpuTimer.h and removed the more simplistic CpuTimer from unittest/util.h. - Eliminated the use of getopt() for command-line argument parsing in vdb_test. - openvdb::initialize() now properly initializes log4cplus if it is enabled, eliminating "No appenders could be found" errors. - Fixed a bug in the QuantizedUnitVec::pack() method that caused quantization artifacts. - Added convenience class tools::AlphaMask to tools/Interpolation.h - Added constructors and methods to both math::RandInt and math::Rand01 to set and reset the random seed value. - Added convenience methods for transforming bounding boxes to math::Transform. - vdb_view is now compatible with both GLFW 2 and GLFW 3. - Made many small changes to address type conversion and other warnings reported by newer compilers like GCC 4.8 and ICC 14. - Replaced the HALF_INCL_DIR and HALF_LIB_DIR Makefile variables with ILMBASE_INCL_DIR and ILMBASE_LIB_DIR and added ILMBASE_LIB, to match OpenEXR's library organization. [Contributed by Double Negative] - Eliminated most local (function-scope) static variables, because Visual C++ doesn't guarantee thread-safe initialization of local statics. [Contributed by SESI] - Fixed a bug in readString() related to empty strings. [Contributed by Fabio Piparo] - Fixed a bug in the tools::VolumeToMesh simplification scheme that was creating visual artifacts. API changes: - The addition of a GridBase::readBuffers() virtual function overload and the GridBase::clip(), GridBase::readNonresidentBuffers() and Tree::clipUnallocatedNodes() virtual functions changes the grid ABI so that it is incompatible with earlier versions of the OpenVDB library (such as the ones in Houdini 12.5 and 13). Define the macro OPENVDB_2_ABI_COMPATIBLE when compiling OpenVDB to disable these changes and preserve ABI compatibility. - All shaders now have a template argument to specify the type of an optional material color grid, but the default type mimics the old, uniform color behavior. - Removed a deprecated io::Stream::write() overload. - The point counts in the UniformPointScatter and NonUniformPointScatter tools are now specified and returned as Index64. - math::RandInt has an extra template argument to specify the integer type. The RandomInt typedef is unchanged. - io::readData(), io::HalfReader::read() and io::HalfWriter::write() now take a uint32_t argument indicating the type of compression instead of a bool indicating whether compression is enabled. - Removed io::Archive::isCompressionEnabled() and io::Archive::setCompressionEnabled() and renamed io::Archive::compressionFlags() and io::Archive::setCompressionFlags() to io::Archive::compression() and io::Archive::setCompression(). - Internal and leaf node classes are now required to provide "PartialCreate" constructors that optionally bypass the allocation of voxel buffers. Leaf node classes must now also provide allocate() and isAllocated() methods to manage the allocation of their buffers. - Removed pruneInactive() and pruneLevelSet() methods from the Tree and various node classes. These methods have been replaced by the much faster pruning functions found in tools/Prune.h. - Removed signedFloodFill() methods from the Grid, Tree and various node classes. These methods have been replaced by the much faster functions found in tools/SignedFloodFill.h. - Removed Grid::setBackground() and Tree::setBackground() (use the faster changeBackground() tool instead), and removed the default argument from RootNode::setBackground(). Python: - Added grid methods convertToPolygons() and convertToQuads(), which convert volumes to meshes, and createLevelSetFromPolygons(), which converts meshes to volumes. NumPy is required. Maya: - Added an adaptive polygonal surface extraction node. Houdini: - Added a new Resize Narrow Band SOP that can efficiently adjust the width of a level set's narrow band. This allows, for example, for a level set to be created quickly from points or polygons with a very narrow band that is then quickly resized to a desired width. - Fixed bugs in the Smooth Level Set and Reshape Level Set SOPs that caused them to ignore the selected discretization scheme. - Added a Morph Level Set SOP. - Added a From Points SOP to very quickly generate a level set from a point cloud, ignoring any radius attribute. [DWA internal] - Added a Voxel Scale mode to the Resample SOP. - Improved the performance and memory footprint of the From Particles SOP for large numbers (tens to hundreds of millions) of particles. - The Scatter SOP now accepts fractional numbers of particles per voxel. - Improved the performance of the Scatter SOP by more than an order of magnitude. - The Clip SOP now has a toggle to choose explicitly between a mask grid or a bounding box as the clipping region. As a consequence, the mask grid can now be unnamed. - Added the OpenVDB library version number to the Extended Operator Information for all SOPs. - SOPs are now linked with an rpath to the directory containing the OpenVDB library. - Like the native Houdini file SOP, the Read SOP now allows missing frames to be reported either as errors or as warnings. - The Read SOP now has an optional input for geometry, the bounding box of which can be used to clip grids as they are read. For large grids, clipping while reading can result in significantly lower memory usage than clipping after reading. - The From Polygons and Convert SOPs now default to using the polygon soup mesh representation, which uses less memory. Version 2.3.0 - April 23, 2014 - Added tools::extractSparseTree(), which selectively extracts and transforms data from a dense grid to produce a sparse tree, and tools::extractSparseTreeWithMask(), which copies data from the index-space intersection of a sparse tree and a dense input grid. - Added copy constructors to the Grid, Tree, RootNode, InternalNode and LeafNode classes, and an assignment operator overload to RootNode, that allow the source and destination to have different value types. - Modified Tree::combine2() to permit combination of trees with different value types. - Added CanConvertType and RootNode::SameConfiguration metafunctions, which perform compile-time tests for value type and tree type compatibility, and a RootNode::hasCompatibleValueType() method, which does runtime checking. - Added optional support for logging using log4cplus. See logging.h and the INSTALL file for details. - Added VolumeRayIntersector::hits(), which returns all the hit segments along a ray. This is generally more efficient than repeated calls to VolumeRayIntersector::march(). - Added member class Ray::TimeSpan and method Ray::valid(), and deprecated method Ray::test(). - Fixed a bug in VolumeHDDA that could cause rendering artifacts when a ray's start time was zero. [Contributed by Mike Farnsworth] - Added tools::compositeToDense(), which composites data from a sparse tree into a dense array, using a sparse alpha mask. Over, Add, Sub, Min, Max, Mult, and Set are supported operations. - Added tools::transformDense(), which applies a functor to the value of each voxel of a dense grid within a given bounding box. - Improved the performance of node iterators. API changes: - Collected the digital differential analyzer code from math/Ray.h and tools/RayIntersector.h into a new header file, math/DDA.h. - Rewrote VolumeHDDA and made several changes to its API. (VolumeHDDA is used internally by VolumeRayIntersector, whose API is unchanged.) - Tree::combine2(), RootNode::combine2(), InternalNode::combine2(), LeafNode::combine2() and CombineArgs all now require an additional template argument, which determines the type of the other tree. - Assignment operators for LeafManager::LeafRange::Iterator, BaseMaskIterator, NodeMask and RootNodeMask now return references to the respective objects. - Removed a number of methods that were deprecated in version 2.0.0 or earlier. Houdini: - Added a Clip SOP, which does volumetric clipping. - Added an Occlusion Mask SOP, which generates a mask of the voxels inside a camera frustum that are occluded by objects in an input grid. - The Combine SOP now applies the optional signed flood fill only to level set grids, since that operation isn't meaningful for other grids. - The Filter SOP now processes all grid types, not just scalar grids. Version 2.2.0 - February 20, 2014 - Added a simple, multithreaded volume renderer, and added volume rendering support to the vdb_render command-line renderer. - Added an option to the LevelSetRayIntersector and to vdb_render to specify the isovalue of the level set. - Added methods to the LevelSetRayIntersector to return the time of intersection along a world or index ray and to return the level set isovalue. - Improved the performance of the VolumeRayIntersector and added support for voxel dilation to account for interpolation kernels. - Added a section to the Cookbook on interpolation using BoxSampler, GridSampler, DualGridSampler, et al. - Added a section to the Overview on grids and grid metadata. - Modified tools::DualGridSampler so it is more consistent with tools::GridSampler. - tools::cpt(), tools::curl(), tools::laplacian(), tools::meanCurvature() and tools::normalize() now output grids with appropriate vector types (covariant, contravariant, etc.). - Added tools::transformVectors(), which applies an affine transformation to the voxel values of a vector-valued grid in accordance with the grid's Vector Type and World Space/Local Space metadata setting. - Added tools::compDiv(), which combines grids by dividing the values of corresponding voxels. - Fixed a bug in the mean curvature computation that could produce NaNs in regions with constant values. - Added a Grid::topologyDifference() method. - Added exp() and sum() methods to math::Vec2, math::Vec3 and math::Vec4. - Improved tools::fillWithSpheres() for small volumes that are just a few voxels across. - Improved the accuracy of the mesh to volume converter. - Fixed a bug in the mesh to volume converter that caused incorrect sign classifications for narrow-band level sets. - Fixed a bug in NonlinearFrustumMap::applyIJT() that resulted in incorrect values when computing the gradient of a grid with a frustum transform. - Fixed a file I/O bug whereby some .vdb files could not be read correctly if they contained grids with more than two distinct inactive values. - Fixed an off-by-one bug in the numbering of unnamed grids in .vdb files. The first unnamed grid in a file is now retrieved using the name "[0]", instead of "[1]". - Fixed a build issue reported by Clang 3.2 in tools/GridOperators.h. - Fixed a memory leak in tools::Film. - Added library and file format version number constants to the Python module. - Improved convergence in the volume renderer. [Contributed by Jerry Tessendorf and Mark Matthews] - Made various changes for compatibility with Houdini 13 and with C++11 compilers. [Contributed by SESI] API changes: - tools::VolumeRayIntersector::march() no longer returns an int to distinguish tile vs. voxel hits. Instead, it now returns false if no intersection is detected and true otherwise. Also, t0 and t1 might now correspond to the first and last hits of multiple adjacent leaf nodes and/or active tiles. - tools::DualGridSampler is no longer templated on the target grid type, and the value accessor is now passed as an argument. - The .vdb file format has changed slightly. Tools built with older versions of OpenVDB should be recompiled to ensure that they can read files in the new format. Houdini: - Added topology union, intersection and difference operations to the Combine SOP. These operations combine the active voxel topologies of grids that may have different value types. - Added a Divide operation to the Combine SOP. - Added support for boolean grids to the Combine, Resample, Scatter, Prune and Visualize SOPs. - The Fill SOP now accepts a vector as the fill value, and it allows the fill region bounds to be specified either in index space (as before), in world space, or using the bounds of geometry connected to an optional new reference input. - Added a toggle to the Offset Level Set SOP to specify the offset in either world or voxel units. - Added a toggle to the Transform and Resample SOPs to apply the transform to the voxel values of vector-valued grids, in accordance with those grids' Vector Type and World Space/Local Space metadata settings. - Added a Vector Type menu to the Vector Merge SOP. - Removed masking options from the Renormalize SOP (since masking is not supported yet). - Reimplemented the Vector Merge SOP for better performance and interruptibility and to fix a bug in the handling of tile values. Version 2.1.0 - December 12, 2013 - Added a small number of Maya nodes, primarily for conversion of geometry to and from OpenVDB volumes and for visualization of volumes. - Added an initial implementation of level set morphing (with improvements to follow soon). - Added tools::LevelSetMeasure, which efficiently computes the surface area, volume and average mean-curvature of narrow-band level sets, in both world and voxel units. Those quantities are now exposed as intrinsic attributes on the Houdini VDB primitive and can be queried using the native Measure SOP. - tools::Dense now supports the XYZ memory layout used by Houdini and Maya in addition to the ZYX layout used in OpenVDB trees. - Improved the performance of masking in the level set filter tool and added inversion and scaling of the mask input, so that any scalar-valued volume can be used as a mask, not just volumes with a [0, 1] range. - Added optional masking to the non-level-set filters, to the grid operators (CPT, curl, divergence, gradient, Laplacian, mean curvature, magnitude, and normalize) and to the Analysis and Filter SOPs. - Added more narrow band controls to the Rebuild Level Set SOP. - Improved the accuracy of the level set rebuild tool. - Added tools::activate() and tools::deactivate(), which set the active states of tiles and voxels whose values are equal to or approximately equal to a given value, and added a Deactivate Background Voxels toggle to the Combine SOP. - Added math::BBox::applyMap() and math::BBox::applyInverseMap(), which allow for transformation of axis-aligned bounding boxes. - Added a position shader to the level set ray-tracer (primarily for debugging purposes). - Added an io::Queue class that manages a concurrent queue for asynchronous serialization of grids to files or streams. - Fixed a bug in io::Archive whereby writing unnamed, instanced grids (i.e., grids sharing a tree) to a file rendered the file unreadable. - Fixed a bug in the volume to mesh converter that caused it to generate invalid polygons when the zero crossing lay between active and inactive regions. - Fixed a bug in the point scatter tool (and the Scatter SOP) whereby the last voxel always remained empty. - Fixed a bug in the Read SOP that caused grids with the same name to be renamed with a numeric suffix (e.g., "grid[1]", "grid[2]", etc.). - Fixed some unit test failures on 64-bit Itanium machines. API changes: - The Filter tool is now templated on a mask grid, and threading is controlled using a grain size, for consistency with most of the other level set tools. - The level set filter tool is now templated on a mask grid. - All shaders now take a ray direction instead of a ray. Version 2.0.0 - October 31, 2013 - Added a Python module with functions for basic manipulation of grids (but no tools, yet). - Added ray intersector tools for efficient, hierarchical intersection of rays with level-set and generic volumes. - Added a Ray class and a hierarchical Digital Differential Analyzer for fast ray traversal. - Added a fully multithreaded level set ray tracer and camera classes that mimic Houdini's cameras. - Added a simple, command-line renderer (currently for level sets only). - Implemented a new meshing scheme that produces topologically robust two-manifold meshes and is twice as fast as the previous scheme. - Implemented a new, topologically robust (producing two-manifold meshes) level-set-based seamless fracture scheme. The new scheme eliminates visible scarring seen in the previous implementation by subdividing internal, nonplanar quads near fracture seams. In addition, fracture seam points are now tagged, allowing them to be used to drive pre-fracture dynamics such as local surface buckling. - Improved the performance of Tree::evalActiveVoxelBoundingBox() and Tree::activeVoxelCount(), and significantly improved the performance of Tree::evalLeafBoundingBox() (by about 30x). - Added a tool (and a Houdini SOP) that fills a volume with adaptively-sized overlapping or non-overlapping spheres. - Added a Ray SOP that can be used to perform geometry projections using level-set ray intersections or closest-point queries. - Added a tool that performs accelerated closest surface point queries from arbitrary points in world space to narrow-band level sets. - Increased the speed of masked level set filtering by 20% for the most common cases. - Added math::BoxStencil, with support for trilinear interpolation and gradient computation. - Added Tree::topologyIntersection(), which intersects a tree's active values with those of another tree, and Tree::topologyDifference(), which performs topological subtraction of one tree's active values from another's. In both cases, the ValueTypes of the two trees need not be the same. - Added Tree::activeTileCount(), which returns the number of active tiles in a tree. - Added math::MinIndex() and math::MaxIndex(), which find the minimum and maximum components of a vector without any branching. - Added math::BBox::minExtent(), which returns a bounding box's shortest axis. - The default math::BBox constructor now generates an invalid bounding box rather than an empty bounding box positioned at the origin. The new behavior is consistent with math::CoordBBox. [Thanks to Rick Hankins for suggesting this fix.] - Added CoordBBox::reset(), which resets a bounding box to its initial, invalid state. - Fixed a bug in the default ScaleMap constructor that left some data used in the inverse uninitialized. - Added MapBase::applyJT(), which applies the Jacobian transpose to a vector (the Jacobian transpose takes a range-space vector to a domain-space vector, e.g., world to index), and added MapBase::inverseMap(), which returns a new map representing the inverse of the original map (except for NonlinearFrustumMap, which does not currently have a defined inverse map). Note: Houdini 12.5 uses an earlier version of OpenVDB, and maps created with that version lack virtual table entries for these new methods, so do not call these methods from Houdini 12.5. - Reimplemented math::RandomInt using Boost.Random instead of rand() (which is not thread-safe), and deprecated math::randUniform() and added math::Random01 to replace it. - Modified tools::copyFromDense() and tools::copyToDense() to allow for implicit type conversion (e.g., between a Dense and a FloatTree) and fixed several bugs in tools::CopyFromDense. - Fixed bugs in math::Stats and math::Histogram that could produce NaNs or other incorrect behavior if certain methods were called on populations of size zero. - Renamed tolerance to math::Tolerance and negative() to math::negative() and removed math::toleranceValue(). - Implemented a closest point on line segment algorithm, math::closestPointOnSegmentToPoint(). - Fixed meshing issues relating to masking and automatic partitioning. - Grid::merge() and Tree::merge() now accept an optional MergePolicy argument that specifies one of three new merging schemes. (The old merging scheme, which is no longer available, used logic for each tree level that was inconsistent with the other levels and that could result in active tiles being replaced with nodes having only inactive values.) - Renamed LeafNode::coord2offset(), LeafNode::offset2coord() and LeafNode::offset2globalCoord() to coordToOffset(), offsetToLocalCoord() and offsetToGlobalCoord(), respectively, and likewise for InternalNode. [Thanks to Rick Hankins for suggesting this change.] - Replaced Tree methods setValueOnMin(), setValueOnMax() and setValueOnSum() with tools::setValueOnMin(), tools::setValueOnMax() and tools::setValueOnSum() (and a new tools::setValueOnMult()) and added Tree::modifyValue() and Tree::modifyValueAndActiveState(), which modify voxel values in place via user-supplied functors. Similarly, replaced ValueAccessor::setValueOnSum() with ValueAccessor::modifyValue() and ValueAccessor::modifyValueAndActiveState(), and added a modifyValue() method to all value iterators. - Removed LeafNode::addValue() and LeafNode::scaleValue(). - Added convenience classes Tree3 and Tree5 for custom tree configurations. - Added an option to the From Particles SOP to generate an alpha mask, which can be used to constrain level set filtering so as to preserve surface details. - The mesh to volume converter now handles point-degenerate polygons. - Fixed a bug in the Level Set Smooth, Level Set Renormalize and Level Set Offset SOPs that caused the group name to be ignored. - Fixed various OS X and Windows build issues. [Contributions from SESI and DD] Version 1.2.0 - June 28, 2013 - Level set filters now accept an optional alpha mask grid. - Implemented sharp feature extraction for level set surfacing. This enhances the quality of the output mesh and reduces aliasing artifacts. - Added masking options to the meshing tools, as well as a spatial multiplier for the adaptivity threshold, automatic partitioning, and the ability to preserve edges and corners when mesh adaptivity is applied. - The mesh to volume attribute transfer scheme now takes surface orientation into account, which improves accuracy in proximity to edges and corners. - Added a foreach() method to tools::LeafManager that, like tools::foreach(), applies a user-supplied functor to each leaf node in parallel. - Rewrote the particle to level set converter, simplifying the API, improving performance (especially when particles have a fixed radius), adding the capability to transfer arbitrary point attributes, and fixing a velocity trail bug. - Added utility methods Sign(), SignChange(), isApproxZero(), Cbrt() and ZeroCrossing() to math/Math.h. - Added a probeNode() method to the value accessor and to tree nodes that returns a pointer to the node that contains a given voxel. - Deprecated LeafNode::addValue() and LeafNode::scaleValue(). - Doubled the speed of the mesh to volume converter (which also improves the performance of the fracture and level set rebuild tools) and improved its inside/outside voxel classification near edges and corners. - tools::GridSampler now accepts either a grid, a tree or a value accessor, and it offers faster index-based access methods and much better performance in cases where many instances are allocated. - Extended tools::Dense to make it more compatible with existing tools. - Fixed a crash in io::Archive whenever the library was unloaded from memory and then reloaded. [Contributed by Ollie Harding] - Fixed a bug in GU_PrimVDB::buildFromPrimVolume(), seen during the conversion from Houdini volumes to OpenVDB grids, that could cause signed flood fill to be applied to non-level set grids, resulting in active tiles with incorrect values. - Added a Prune SOP with several pruning schemes. Version 1.1.1 - May 10 2013 - Added a simple dense grid class and tools to copy data from dense voxel arrays into OpenVDB grids and vice-versa. - Starting with Houdini 12.5.396, plugins built with this version of OpenVDB can coexist with native Houdini OpenVDB nodes. - The level set fracture tool now smooths seam line edges during mesh extraction, eliminating staircase artifacts. - Significantly improved the performance of the util::leafTopologyIntersection() and util::leafTopologyDifference() utilities and added a LeafNode::topologyDifference() method. - Added convenience functions that provide simplified interfaces to the mesh to volume and volume to mesh converters. - Added a tools::accumulate() function that is similar to tools::foreach() but can be used to accumulate the results of computations over the values of a grid. - Added tools::statistics(), tools::opStatistics() and tools::histogram(), which efficiently compute statistics (mean, variance, etc.) and histograms of grid values (using math::Stats and math::Histogram). - Modified CoordBBox to adhere to TBB's splittable type requirements, so that, for example, a CoordBBox can be used as a blocked iteration range. - Added Tree::addTile(), Tree::addLeaf() and Tree::stealNode(), for fine control over tree construction. - Addressed a numerical stability issue when performing Gaussian filtering of level set grids. - Changed the return type of CoordBBox::volume() to reduce the risk of overflow. - When the input mesh is self-intersecting, the mesh to volume converter now produces a level set with a monotonic gradient field. - Fixed a threading bug in the mesh to volume converter that caused it to produce different results for the same input. - Fixed a bug in the particle to level set converter that prevented particles with zero velocity from being rasterized in Trail mode. - Added an optional input to the Create SOP into which to merge newly-created grids. - Fixed a bug in the Resample SOP that caused it to produce incorrect narrow-band widths when resampling level set grids. - Fixed a bug in the To Polygons SOP that caused intermittent crashes when the optional reference input was connected. - Fixed a bug in the Advect Level Set SOP that caused a crash when the velocity input was connected but empty. - The Scatter and Sample Point SOPs now warn instead of erroring when given empty grids. - Fixed a crash in vdb_view when stepping through multiple grids after changing render modes. - vdb_view can now render fog volumes and vector fields, and it now features interactively adjustable clipping planes that enable one to view the interior of a volume. Version 1.1.0 - April 4 2013 - The resampleToMatch() tool, the Resample SOP and the Combine SOP now use level set rebuild to correctly and safely resample level sets. Previously, scaling a level set would invalidate the signed distance field, leading to holes and other artifacts. - Added a mask-based topological erosion tool, and rewrote and simplified the dilation tool. - The LevelSetAdvection tool can now advect forward or backward in time. - Tree::pruneLevelSet() now replaces each pruned node with a tile having the inside or outside background value, instead of arbitrarily selecting one of the node's tile or voxel values. - When a grid is saved to a file with saveFloatAsHalf() set to true, the grid's background value is now also quantized to 16 bits. (Not quantizing the background value caused a mismatch with the values of background tiles.) - As with tools::foreach(), it is now possible to specify whether functors passed to tools::transformValues() should be shared across threads. - tree::LeafManager can now be instantiated with a const tree, although buffer swapping with const trees is disabled. - Added a Grid::signedFloodFill() overload that allows one to specify inside and outside values. - Fixed a bug in Grid::setBackground() so that now only the values of inactive voxels change. - Fixed Grid::topologyUnion() so that it actually unions tree topology, instead of just the active states of tiles and voxels. The previous behavior broke multithreaded code that relied on input and output grids having compatible tree topology. - math::Transform now includes an isIdentity() predicate and methods to pre- and postmultiply by a matrix. - Modified the node mask classes to permit octree-like tree configurations (i.e., with a branching factor of two) and to use 64-bit operations instead of 32-bit operations. - Implemented a new, more efficient closest point on triangle algorithm. - Implemented a new vertex normal scheme in the volume to mesh converter, and resolved some overlapping polygon issues. - The volume to mesh converter now meshes not just active voxels but also active tiles. - Fixed a bug in the mesh to volume converter that caused unsigned distance field conversion to produce empty grids. - Fixed a bug in the level set fracture tool whereby the cutter overlap toggle was ignored. - Fixed an infinite loop bug in vdb_view. - Updated vdb_view to use the faster and less memory-intensive OpenVDB volume to mesh converter instead of marching cubes, and rewrote the shader to be OpenGL 3.2 and GLSL 1.2 compatible. - Given multiple input files or a file containing multiple grids, vdb_view now displays one grid at a time. The left and right arrow keys cycle between grids. - The To Polygons SOP now has an option to associate the input grid's name with each output polygon. Version 1.0.0 - March 14 2013 - tools::levelSetRebuild() now throws an exception when given a non-scalar or non-floating-point grid. - The tools in tools/GridOperators.h are now interruptible, as is the Analysis SOP. - Added a leaf node iterator and a TBB-compatible range class to the LeafManager. - Modified the VolumeToMesh tool to handle surface topology issues around fracture seam lines. - Modified the Makefile to allow vdb_view to compile on OS X systems (provided that GLFW is available). - Fixed a bug in the Create SOP that resulted in "invalid parameter name" warnings. - The Combine SOP now optionally resamples the A grid into the B grid's index space (or vice-versa) if the A and B transforms differ. - The Vector Split and Vector Merge SOPs now skip inactive voxels by default, but they can optionally be made to include inactive voxels, as they did before. - The LevelSetFracture tool now supports custom rotations for each cutter instance, and the Fracture SOP now uses quaternions to generate uniformly-distributed random rotations. Version 0.104.0 - February 15 2013 - Added a tool and a SOP to rebuild a level set from any scalar volume. - .vdb files are now saved using a mask-based compression scheme that is an order of magnitude faster than ZLIB and produces comparable file sizes for level set and fog volume grids. (ZLIB compression is still enabled by default for other classes of grids). - The Filter and LevelSetFilter tools now include a Gaussian filter, and mean (box) filtering is now 10-50x faster. - The isosurface meshing tool is now more robust (to level sets with one voxel wide narrow bands, for example). - Mesh to volume conversion is on average 1.5x faster and up to 5.5x faster for high-resolution meshes where the polygon/voxel size ratio is small. - Added createLevelSet() and createLevelSetSphere() factory functions for level set grids. - tree::ValueAccessor is now faster for trees of height 2, 3 and 4 (the latter is the default), and it now allows one to specify, via a template argument, the number of node levels to be cached, which can also improve performance in special cases. - Added a toggle to tools::foreach() to specify whether or not the functor should be shared across threads. - Added Mat4s and Mat4d metadata types. - Added explicit pre- and postmultiplication methods to the Transform, Map and Mat4 classes and deprecated the old accumulation methods. - Modified NonlinearFrustumMap to be more compatible with Houdini's frustum transform. - Fixed a GridTransformer bug that caused it to translate the output grid incorrectly in some cases. - Fixed a bug in the tree-level LeafIterator that resulted in intermittent crashes in tools::dilateVoxels(). - The Hermite data type and Hermite grids are no longer supported. - Added tools/GridOperators.h, which includes new, cleaner implementations of the Cpt, Curl, Divergence, Gradient, Laplacian, Magnitude, MeanCurvature and Normalize tools. - Interrupt support has been improved in several tools, including tools::ParticlesToLevelSet. - Simplified the API of the Stencil class and added an intersects() method to test for intersection with a specified isovalue. - Renamed voxelDimensions to voxelSize in transform classes and elsewhere. - Deprecated houdini_utils::ParmFactory::setChoiceList() in favor of houdini_utils::ParmFactory::setChoiceListItems(), which requires a list of token, label string pairs. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Fixed a bug in houdini_utils::getNodeChain() that caused the Offset Level Set, Smooth Level Set and Renormalize Level Set SOPs to ignore frame changes. [Contributed by SESI] - The From Particles SOP now provides the option to write into an existing grid. - Added a SOP to edit grid metadata. - The Fracture SOP now supports multiple cutter objects. - Added a To Polygons SOP that complements the Fracture SOP and allows for elimination of seam lines, generation of correct vertex normals and grouping of polygons when surfacing fracture fragments, using the original level set or mesh as a reference. Version 0.103.1 - January 15 2013 - tree::ValueAccessor read operations are now faster for four-level trees. (Preliminary benchmark tests suggest a 30-40% improvement.) - For vector-valued grids, tools::compMin() and tools::compMax() now compare vector magnitudes instead of individual components. - Migrated grid sampling code to a new file, Interpolation.h, and deprecated old files and classes. - Added a level-set fracture tool and a Fracture SOP. - Added tools::sdfInteriorMask(), which creates a mask of the interior region of a level set grid. - Fixed a bug in the mesh to volume converter that produced unexpected nonzero values for voxels at the intersection of two polygons, and another bug that produced narrow-band widths that didn't respect the background value when the half-band width was less than three voxels. - houdini_utils::ParmFactory can now correctly generate ramp multi-parms. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - The Convert SOP can now convert between signed distance fields and fog volumes and from volumes to meshes. [Contributed by SESI] - For level sets, the From Mesh and From Particles SOPs now match the reference grid's narrow-band width. - The Scatter SOP can now optionally scatter points in the interior of a level set. Version 0.103.0 - December 21 2012 - The mesh to volume converter is now 60% faster at generating level sets with wide bands, and the From Mesh SOP is now interruptible. - Fixed a threading bug in the recently-added compReplace() tool that caused it to produce incorrect output. - Added a probeConstLeaf() method to the Tree, ValueAccessor and node classes. - The Houdini VDB primitive doesn't create a "name" attribute unnecessarily (i.e., if its grid's name is empty), but it now correctly allows the name to be changed to the empty string. - Fixed a crash in the Vector Merge SOP when fewer than three grids were merged. - The From Particles SOP now features a "maximum half-width" parameter to help avoid runaway computations. Version 0.102.0 - December 13 2012 - Added tools::compReplace(), which copies the active values of one grid into another, and added a "Replace A With Active B" mode to the Combine SOP. - Grid::signedFloodFill() no longer enters an infinite loop when filling an empty grid. - Fixed a bug in the particle to level set converter that sometimes produced level sets with holes, and fixed a bug in the SOP that could result in random output. - Fixed an issue in the frustum preview feature of the Create SOP whereby rendering very large frustums could cause high CPU usage. - Added streamline support to the constrained advection scheme in the Advect Points SOP. - Added an Advect Level Set SOP. Version 0.101.1 - December 11 2012 (DWA internal release) - Partially reverted the Houdini VDB primitive's grid accessor methods to their pre-0.98.0 behavior. A primitive's grid can once again be accessed by shared pointer, but now also by reference. Accessor methods for grid metadata have also been added, and the primitive now ensures that metadata and transforms are never shared. - Fixed an intermittent crash in the From Particles SOP. Version 0.101.0 - December 6 2012 (DWA internal release) - Partially reverted the Grid's tree and transform accessor methods to their pre-0.98.0 behavior, eliminating copy-on-write but preserving their return-by-reference semantics. These methods are now supplemented with a suite of shared pointer accessors. - Restructured the mesh to volume converter for a 40% speedup and to be more robust to non-manifold geometry, to better preserve sharp features, to support arbitrary tree configurations and to respect narrow-band limits. - Added a getNodeBoundingBox() method to RootNode, InternalNode and LeafNode that returns the index space spanned by a node. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Renamed the Reshape Level Set SOP to Offset Level Set. - Fixed a crash in the Convert SOP and added support for conversion of empty grids. Version 0.100.0 - November 30 2012 (DWA internal release) - Greatly improved the performance of the level set to fog volume converter. - Improved the performance of the median filter and of level set CSG operations. - Reintroduced Tree::pruneLevelSet(), a specialized pruneInactive() for level-set grids. - Added utilities to the houdini_utils library to facilitate the collection of a chain of adjacent nodes of a particular type so that they can be cooked in a single step. (For example, adjacent xform SOPs could be collapsed by composing their transformation matrices into a single matrix.) - Added pruning and flood-filling options to the Convert SOP. - Reimplemented the Filter SOP, omitting level-set-specific filters and adding node chaining (to reduce memory usage when applying several filters in sequence). - Added a toggle to the Read SOP to read grid metadata and transforms only. - Changed the attribute transfer scheme on the From Mesh and From Particles SOPs to allow for custom grid names and vector type metadata. Version 0.99.0 - November 21 2012 - Added Grid methods that return non-const Tree and Transform references without triggering deep copies, as well as const methods that return const shared pointers. - Added Grid methods to populate a grid's metadata with statistics like the active voxel count, and to retrieve that metadata. By default, statistics are now computed and added to grids whenever they are written to .vdb files. - Added io::File::readGridMetadata() and io::File::readAllGridMetadata() methods to read just the grid metadata and transforms from a .vdb file. - Fixed numerical precision issues in the csgUnion, csgIntersection and csgDifference tools, and added toggles to optionally disable postprocess pruning. - Fixed an issue in vdb_view with the ordering of GL vertex buffer calls. [Contributed by Bill Katz] - Fixed an intermittent crash in the ParticlesToLevelSet tool, as well as a race condition that could cause data corruption. - The ParticlesToLevelSet tool and From Particles SOP can now transfer arbitrary point attribute values from the input particles to output voxels. - Fixed a bug in the Convert SOP whereby the names of primitives were lost during conversion, and another bug that resulted in an arithmetic error when converting an empty grid. - Fixed a bug in the Combine SOP that caused the Operation selection to be lost. Version 0.98.0 - November 16 2012 - Tree and Transform objects (and Grid objects in the context of Houdini SOPs) are now passed and accessed primarily by reference rather than by shared pointer. See the online documentation for details about this important API change. [Contributed by SESI] - Reimplemented CoordBBox to address several off-by-one bugs related to bounding box dimensions. - Fixed an off-by-one bug in Grid::evalActiveVoxelBoundingBox(). - Introduced the LeafManager class, which will eventually replace the LeafArray class. LeafManager supports dynamic buffers stored as a structure of arrays (SOA), unlike LeafArray, which supports only static buffers stored as an array of structures (AOS). - Improved the performance of the LevelSetFilter and LevelSetTracker tools by rewriting them to use the new LeafManager class. - Added Tree and ValueAccessor setValueOnly() methods, which change the value of a voxel without changing its active state, and Tree and ValueAccessor probeLeaf() methods that return the leaf node that contains a given voxel (unless the voxel is represented by a tile). - Added a LevelSetAdvection tool that propagates and tracks narrow-band level sets. - Introduced a new GridSampler class that supports world-space (or index-space) sampling of grid values. - Changed the interpretation of the NonlinearFrustumMap's taper parameter to be the ratio of the near and far plane depths. - Added a ParmFactory::setChoiceList() overload that accepts (token, label) string pairs, and a setDefault() overload that accepts an STL string. - Fixed a crash in the Combine SOP in Copy B mode. - Split the Level Set Filter SOP into three separate SOPs, Level Set Smooth, Level Set Reshape and Level Set Renormalize. When two or more of these nodes are connected in sequence, they interact to reduce memory usage: the last node in the sequence performs all of the operations in one step. - The Advect Points SOP can now output polyline streamlines that trace the paths of the points. - Added an option to the Analysis SOP to specify names for output grids. - Added camera-derived frustum transform support to the Create SOP. Version 0.97.0 - October 18 2012 - Added a narrow-band level set interface tracking tool (up to fifth-order in space but currently only first-order in time, with higher temporal orders to be added soon). - Added a level set filter tool to perform unrestricted surface smoothing (e.g., Laplacian flow), filtering (e.g., mean value) and morphological operations (e.g., morphological opening). - Added adaptivity to the level set meshing tool for faster mesh extraction with fewer polygons, without postprocessing. - Added a ValueAccessor::touchLeaf() method that creates (if necessary) and returns the leaf node containing a given voxel. It can be used to preallocate leaf nodes over which to run parallel algorithms. - Fixed a bug in Grid::merge() whereby active tiles were sometimes lost. - Added LeafManager, which is similar to LeafArray but supports a dynamic buffer count and allocates buffers more efficiently. Useful for temporal integration (e.g., for level set propagation and interface tracking), LeafManager is meant to replace LeafArray, which will be deprecated in the next release. - Added a LeafNode::fill() method to efficiently populate leaf nodes with constant values. - Added a Tree::visitActiveBBox() method that applies a functor to the bounding boxes of all active tiles and leaf nodes and that can be used to improve the performance of ray intersection tests, rendering of bounding boxes, etc. - Added a Tree::voxelizeActiveTiles() method to densify active tiles. While convenient and fast, this can produce large dense grids, so use it with caution. - Repackaged Tree::pruneLevelSet() as a Tree::pruneOp()-compatible functor. LevelSetPrune is a specialized pruneInactive() for level-set grids and is used in interface tracking. - Added a GridBase::pruneGrid() method. - Added a Grid:hasUniformVoxels() method. - Renamed tools::dilate() to tools::dilateVoxels() and improved its performance. The new name reflects the fact that the current implementation ignores active tiles. - Added a tools::resampleToMatch() function that resamples an input grid into an output grid with a different transform such that, after resampling, the input and output grids coincide, but the output grid's transform is preserved. - Significantly improved the performance of depth-bounded value iterators (ValueOnIter, ValueAllIter, etc.) when the depth bound excludes leaf nodes. - Exposed the value buffers inside leaf nodes with LeafNode::buffer(). This allows for very fast access (const and non-const) to voxel values using linear array offsets instead of (i,j,k) coordinates. - In openvdb_houdini/UT_VDBTools.h, added operators for use with processTypedGrid() that resample grids in several different ways. - Added a policy mechanism to houdini_utils::OpFactory that allows for customization of operator names, icons, and Help URLs. - Renamed many of the Houdini SOPs to make the names more consistent. - Added an Advect Points SOP. - Added a Level Set Filter SOP that allows for unrestricted surface deformations, unlike the older Filter SOP, which restricts surface motion to the initial narrow band. - Added staggered vector sampling to the Sample Points SOP. - Added a minimum radius threshold to the particle voxelization tool and SOP. - Merged the Composite and CSG SOPs into a single Combine SOP. - Added a tool and a SOP to efficiently generate narrow-band level set representations of spheres. - In the Visualize SOP, improved the performance of tree topology generation, which is now enabled by default. Version 0.96.0 - September 24 2012 - Fixed a memory corruption bug in the mesh voxelizer tool. - Temporarily removed the optional clipping feature from the level set mesher. - Added "Staggered Vector Field" to the list of grid classes in the Create SOP. Version 0.95.0 - September 20 2012 - Added a quad meshing tool for higher-quality level set meshing and updated the Visualizer SOP to use it. - Fixed a precision error in the mesh voxelizer and improved the quality of inside/outside voxel classification. Output grids are now also tagged as either level sets or fog volumes. - Modified the GridResampler to use the signed flood fill optimization only on grids that are tagged as level sets. - Added a quaternion class to the math library and a method to return the trace of a Mat3. - Fixed a bug in the ValueAccessor copy constructor that caused the copy to reference the original. - Fixed a bug in RootNode::setActiveState() that caused a crash when marking a (virtual) background voxel as inactive. - Added a Tree::pruneLevelSet() method that is similar to but faster than pruneInactive() for level set grids. - Added fast leaf node voxel access methods that index by linear offset (as returned by ValueIter::pos()) instead of by (i, j, k) coordinates. - Added a Tree::touchLeaf() method that can be used to preallocate a static tree topology over which to safely perform multithreaded processing. - Added a grain size argument to the LeafArray class for finer control of parallelism. - Modified the Makefile to make it easier to omit the doc, vdb_test and vdb_view targets. - Added utility functions (in houdini/UT_VDBUtils.h) to convert between Houdini and OpenVDB matrix and vector types. [Contributed by SESI] - Added accessors to GEO_PrimVDB that make it easier to directly access voxel data and that are used by the HScript volume expression functions in Houdini 12.5. [Contributed by SESI] - As of Houdini 12.1.77, the native transform SOP operates on OpenVDB primitives. [Contributed by SESI] - Added a Convert SOP that converts OpenVDB grids to Houdini volumes and vice-versa. Version 0.94.1 - September 7 2012 - Fixed bugs in RootNode and InternalNode setValue*() and fill() methods that could cause neighboring voxels to become inactive. - Fixed a bug in Tree::hasSameTopology() that caused false positives when only active states and not values differed. - Added a Tree::hasActiveTiles() method. - For better cross-platform consistency, substituted bitwise AND operations for right shifts in the ValueAccessor hash key computation. - vdb_view no longer aborts when asked to surface a vector-valued grid--but it still doesn't render the surface. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Added an option to the MeshVoxelizer SOP to convert both open and closed surfaces to unsigned distance fields. - The Filter SOP now allows multiple filters to be applied in user-specified order. Version 0.94.0 - August 30 2012 - Added a method to union just the active states of voxels from one grid with those of another grid of a possibly different type. - Fixed an incorrect scale factor in the Laplacian diffusion filter. - Fixed a bug in Tree::merge that could leave a tree with invalid value accessors. - Added TreeValueIteratorBase::setActiveState() and deprecated setValueOn(). - Removed tools/FastSweeping.h. It will be replaced with a much more efficient implementation in the near future. - ZLIB compression of .vdb files is now optional, but enabled by default. [Contributed by SESI] - Made various changes for Clang and Visual C++ compatibility. [Contributed by SESI] - The MeshVoxelizer SOP can now transfer arbitrary point and primitive attribute values from the input mesh to output voxels. Version 0.93.0 - August 24 2012 - Renamed symbols in math/Operators.h to avoid ambiguities that GCC 4.4 reports as errors. - Simplified the API for the stencil version of the closest-point transform operator. - Added logic to io::Archive::readGrid() to set the grid name metadata from the descriptor if the metadata doesn't already exist. - Added guards to prevent nesting of openvdb_houdini::Interrupter::start() and end() calls. Version 0.92.0 - August 23 2012 - Added a Laplacian diffusion filter. - Fixed a bug in the initialization of the sparse contour tracer that caused mesh-to-volume conversion to fail in certain cases. - Fixed a bug in the curvature stencil that caused mean curvature filtering to produce wrong results. - Increased the speed of the GridTransformer by as much as 20% for fog volumes. - Added optional pruning to the Resample SOP. - Modified the PointSample SOP to allow it to work with ungrouped, anonymous grids. - Fixed a crash in the LevelSetNoise SOP. Version 0.91.0 - August 16 2012 - tools::GridTransformer and tools::GridResampler now correctly (but not yet efficiently) process tiles in sparse grids. - Added an optional CopyPolicy argument to GridBase::copyGrid() and to Grid::copy() that specifies whether and how the grid's tree should be copied. - Added a GridBase::newTree() method that replaces a grid's tree with a new, empty tree of the correct type. - Fixed a crash in Tree::setValueOff() when the new value was equal to the background value. - Fixed bugs in Tree::prune() that could result in output tiles with incorrect active states. - Added librt to the link dependencies to address build failures on Ubuntu systems. - Made various small changes to the Makefile and the source code that should help with Mac OS X compatibility. - The Composite and Resample SOPs now correctly copy the input grid's metadata to the output grid. Version 0.90.1 - August 7 2012 - Fixed a bug in the BBox::getCenter() method. - Added missing header files to various files. - io::File::NameIterator::gridName() now returns a unique name of the form "name[1]", "name[2]", etc. if a file contains multiple grids with the same name. - Fixed a bug in the Writer SOP that caused grid names to be discarded. - The Resample SOP now correctly sets the background value of the output grid. Version 0.90.0 - August 3 2012 (initial public release) - Added a basic GL viewer for OpenVDB files. - Greatly improved the performance of two commonly-used Tree methods, evalActiveVoxelBoundingBox() and memUsage(). - Eliminated the GridMap class. File I/O now uses STL containers of grid pointers instead. - Refactored stencil-based tools (Gradient, Laplacian, etc.) and rewrote some of them for generality and better performance. Most now behave correctly for grids with nonlinear index-to-world transforms. - Added a library of index-space finite difference operators. - Added a "Hermite" grid type that compactly stores each voxel's upwind normals and can be used to convert volumes to and from polygonal meshes. - Added a tool (and a Houdini SOP) to scatter points randomly throughout a volume. openvdb/python/0000755000000000000000000000000012603226506012534 5ustar rootrootopenvdb/python/pyutil.h0000644000000000000000000002414512603226506014241 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/test/0000755000000000000000000000000012603226506013513 5ustar rootrootopenvdb/python/test/TestOpenVDB.py0000644000000000000000000007674612603226506016206 0ustar rootroot#!/usr/local/bin/python # Copyright (c) 2012-2015 DreamWorks Animation LLC # # All rights reserved. This software is distributed under the # Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) # # Redistributions of source code must retain the above copyright # and license notice and the following restrictions and disclaimer. # # * Neither the name of DreamWorks Animation nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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.0625) # = (2 + 2.125)/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) def testMeshConversion(self): import time # Skip this test if NumPy is not available. try: import numpy as np except ImportError: return # Test mesh to volume conversion. # Generate the vertices of a cube. cubeVertices = [(x, y, z) for x in (0, 100) for y in (0, 100) for z in (0, 100)] cubePoints = np.array(cubeVertices, float) # Generate the faces of a cube. cubeQuads = np.array([ (0, 1, 3, 2), # left (0, 2, 6, 4), # front (4, 6, 7, 5), # right (5, 7, 3, 1), # back (2, 3, 7, 6), # top (0, 4, 5, 1), # bottom ], float) voxelSize = 2.0 halfWidth = 3.0 xform = openvdb.createLinearTransform(voxelSize) # Only scalar, floating-point grids support createLevelSetFromPolygons() # (and the OpenVDB module might have been compiled without DoubleGrid support). grids = [] for gridType in [n for n in openvdb.GridTypes if n.__name__ in ('FloatGrid', 'DoubleGrid')]: # Skip this test if the OpenVDB module was built without NumPy support. try: grid = gridType.createLevelSetFromPolygons( cubePoints, quads=cubeQuads, transform=xform, halfWidth=halfWidth) except NotImplementedError: return #openvdb.write('/tmp/testMeshConversion.vdb', grid) self.assertEqual(grid.transform, xform) self.assertEqual(grid.background, halfWidth * voxelSize) dim = grid.evalActiveVoxelDim() self.assert_(50 < dim[0] < 58) self.assert_(50 < dim[1] < 58) self.assert_(50 < dim[2] < 58) grids.append(grid) # Boolean-valued grids can't be used to store level sets. self.assertRaises(TypeError, lambda: openvdb.BoolGrid.createLevelSetFromPolygons( cubePoints, quads=cubeQuads, transform=xform, halfWidth=halfWidth)) # Vector-valued grids can't be used to store level sets. self.assertRaises(TypeError, lambda: openvdb.Vec3SGrid.createLevelSetFromPolygons( cubePoints, quads=cubeQuads, transform=xform, halfWidth=halfWidth)) # The "points" argument to createLevelSetFromPolygons() must be a NumPy array. self.assertRaises(TypeError, lambda: openvdb.FloatGrid.createLevelSetFromPolygons( cubeVertices, quads=cubeQuads, transform=xform, halfWidth=halfWidth)) # The "points" argument to createLevelSetFromPolygons() must be a NumPy float or int array. self.assertRaises(TypeError, lambda: openvdb.FloatGrid.createLevelSetFromPolygons( np.array(cubeVertices, bool), quads=cubeQuads, transform=xform, halfWidth=halfWidth)) # The "triangles" argument to createLevelSetFromPolygons() must be an N x 3 NumPy array. self.assertRaises(TypeError, lambda: openvdb.FloatGrid.createLevelSetFromPolygons( cubePoints, triangles=cubeQuads, transform=xform, halfWidth=halfWidth)) # Test volume to mesh conversion. # Vector-valued grids can't be meshed. self.assertRaises(TypeError, lambda: openvdb.Vec3SGrid().convertToQuads()) for grid in grids: points, quads = grid.convertToQuads() # These checks are intended mainly to test the Python/C++ bindings, # not the OpenVDB volume to mesh converter. self.assert_(len(points) > 8) self.assert_(len(quads) > 6) pmin, pmax = points.min(0), points.max(0) self.assert_(-2 < pmin[0] < 2) self.assert_(-2 < pmin[1] < 2) self.assert_(-2 < pmin[2] < 2) self.assert_(98 < pmax[0] < 102) self.assert_(98 < pmax[1] < 102) self.assert_(98 < pmax[2] < 102) points, triangles, quads = grid.convertToPolygons(adaptivity=1) self.assert_(len(points) > 8) pmin, pmax = points.min(0), points.max(0) self.assert_(-2 < pmin[0] < 2) self.assert_(-2 < pmin[1] < 2) self.assert_(-2 < pmin[2] < 2) self.assert_(98 < pmax[0] < 102) self.assert_(98 < pmax[1] < 102) self.assert_(98 < pmax[2] < 102) 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-2015 DreamWorks Animation LLC # All rights reserved. This software is distributed under the # Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyOpenVDBModule.cc0000644000000000000000000006502612603226506016030 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #ifdef PY_OPENVDB_USE_NUMPY #define PY_ARRAY_UNIQUE_SYMBOL PY_OPENVDB_ARRAY_API #include // for import_array() #endif #include "openvdb/openvdb.h" #include "pyopenvdb.h" #include "pyGrid.h" #include "pyutil.h" namespace py = boost::python; // Forward declarations void exportTransform(); void exportMetadata(); void exportFloatGrid(); void exportIntGrid(); void exportVec3Grid(); namespace _openvdbmodule { using namespace openvdb; /// Helper class to convert between a Python numeric sequence /// (tuple, list, etc.) and an openvdb::Coord struct CoordConverter { /// @return a Python tuple object equivalent to the given Coord. static PyObject* convert(const openvdb::Coord& xyz) { py::object obj = py::make_tuple(xyz[0], xyz[1], xyz[2]); Py_INCREF(obj.ptr()); ///< @todo is this the right way to ensure that the object ///< doesn't get freed on exit? return obj.ptr(); } /// @return 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 = int(std::strlen(name)); \ const char* msg = e.what(); \ if (0 == std::strncmp(msg, name, namelen)) msg += namelen; \ if (0 == std::strncmp(msg, ": ", 2)) msg += 2; \ PyErr_SetString(_pyname, msg); \ } /// Define an overloaded function that translate all OpenVDB exceptions into /// their Python equivalents. /// @todo IllegalValueException and LookupError are redundant and should someday be removed. PYOPENVDB_CATCH(openvdb::ArithmeticError, PyExc_ArithmeticError) PYOPENVDB_CATCH(openvdb::IllegalValueException, PyExc_ValueError) PYOPENVDB_CATCH(openvdb::IndexError, PyExc_IndexError) PYOPENVDB_CATCH(openvdb::IoError, PyExc_IOError) PYOPENVDB_CATCH(openvdb::KeyError, PyExc_KeyError) PYOPENVDB_CATCH(openvdb::LookupError, PyExc_LookupError) PYOPENVDB_CATCH(openvdb::NotImplementedError, PyExc_NotImplementedError) PYOPENVDB_CATCH(openvdb::ReferenceError, PyExc_ReferenceError) PYOPENVDB_CATCH(openvdb::RuntimeError, PyExc_RuntimeError) PYOPENVDB_CATCH(openvdb::TypeError, PyExc_TypeError) PYOPENVDB_CATCH(openvdb::ValueError, PyExc_ValueError) #undef PYOPENVDB_CATCH //////////////////////////////////////// py::object readFromFile(const std::string& 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(); #ifdef PY_OPENVDB_USE_NUMPY // Initialize NumPy. import_array(); #endif using namespace openvdb::OPENVDB_VERSION_NAME; // Initialize OpenVDB. initialize(); _openvdbmodule::CoordConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::MetaMapConverter::registerConverter(); #define PYOPENVDB_TRANSLATE_EXCEPTION(_classname) \ py::register_exception_translator<_classname>(&_openvdbmodule::translateException<_classname>) PYOPENVDB_TRANSLATE_EXCEPTION(ArithmeticError); PYOPENVDB_TRANSLATE_EXCEPTION(IllegalValueException); PYOPENVDB_TRANSLATE_EXCEPTION(IndexError); PYOPENVDB_TRANSLATE_EXCEPTION(IoError); PYOPENVDB_TRANSLATE_EXCEPTION(KeyError); PYOPENVDB_TRANSLATE_EXCEPTION(LookupError); PYOPENVDB_TRANSLATE_EXCEPTION(NotImplementedError); PYOPENVDB_TRANSLATE_EXCEPTION(ReferenceError); PYOPENVDB_TRANSLATE_EXCEPTION(RuntimeError); PYOPENVDB_TRANSLATE_EXCEPTION(TypeError); PYOPENVDB_TRANSLATE_EXCEPTION(ValueError); #undef PYOPENVDB_TRANSLATE_EXCEPTION // Export the python bindings. exportTransform(); exportMetadata(); exportFloatGrid(); exportIntGrid(); exportVec3Grid(); py::def("read", &_openvdbmodule::readFromFile, (py::arg("filename"), py::arg("gridname")), "read(filename, gridname) -> Grid\n\n" "Read a single grid from a .vdb file."); py::def("readAll", &_openvdbmodule::readAllFromFile, py::arg("filename"), "readAll(filename) -> list, dict\n\n" "Read a .vdb file and return a list of grids and\n" "a dict of file-level metadata."); py::def("readMetadata", &_openvdbmodule::readFileMetadata, py::arg("filename"), "readMetadata(filename) -> dict\n\n" "Read file-level metadata from a .vdb file."); py::def("readGridMetadata", &_openvdbmodule::readGridMetadataFromFile, (py::arg("filename"), py::arg("gridname")), "readGridMetadata(filename, gridname) -> Grid\n\n" "Read a single grid's metadata and transform (but not its tree)\n" "from a .vdb file."); py::def("readAllGridMetadata", &_openvdbmodule::readAllGridMetadataFromFile, py::arg("filename"), "readAllGridMetadata(filename) -> list\n\n" "Read a .vdb file and return a list of grids populated with\n" "their metadata and transforms, but not their trees."); py::def("write", &_openvdbmodule::writeToFile, (py::arg("filename"), py::arg("grids"), py::arg("metadata") = py::object()), "write(filename, grids, metadata=None)\n\n" "Write a grid or a sequence of grids and, optionally, a dict\n" "of (name, value) metadata pairs to a .vdb file."); // 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyAccessor.h0000644000000000000000000003214112603226506015021 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyFloatGrid.cc0000644000000000000000000000554212603226506015275 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyGrid.h0000644000000000000000000024726612603226506014164 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file pyGrid.h /// @author Peter Cucka /// @brief Boost.Python wrapper for openvdb::Grid #ifndef OPENVDB_PYGRID_HAS_BEEN_INCLUDED #define OPENVDB_PYGRID_HAS_BEEN_INCLUDED #include #include #include #ifdef PY_OPENVDB_USE_NUMPY #define PY_ARRAY_UNIQUE_SYMBOL PY_OPENVDB_ARRAY_API #define NO_IMPORT_ARRAY // NumPy gets initialized during module initialization #include // for PyArrayObject #include "openvdb/tools/MeshToVolume.h" #include "openvdb/tools/VolumeToMesh.h" // for tools::volumeToMesh() #endif #include "openvdb/openvdb.h" #include "openvdb/io/Stream.h" #include "openvdb/math/Math.h" // for math::isExactlyEqual() #include "openvdb/tools/LevelSetSphere.h" #include "openvdb/tools/Dense.h" #include "openvdb/tools/ChangeBackground.h" #include "openvdb/tools/Prune.h" #include "openvdb/tools/SignedFloodFill.h" #include "pyutil.h" #include "pyAccessor.h" // for pyAccessor::AccessorWrap #include "pyopenvdb.h" #include // for memcpy() #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) { tools::changeBackground(grid.tree(), extractValueArg(obj, "setBackground")); } inline void setGridName(GridBase::Ptr grid, py::object strObj) { if (grid) { if (!strObj) { // if name is None grid->removeMeta(GridBase::META_GRID_NAME); } else { const std::string name = pyutil::extractArg( strObj, "setName", /*className=*/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) { tools::prune(grid.tree(), extractValueArg(tolerance, "prune")); } template inline void pruneInactive(GridType& grid, py::object valObj) { if (valObj.is_none()) { tools::pruneInactive(grid.tree()); } else { tools::pruneInactiveWithValue( grid.tree(), extractValueArg(valObj, "pruneInactive")); } } template inline void fill(GridType& grid, py::object minObj, py::object maxObj, py::object valObj, bool active) { const Coord bmin = extractValueArg(minObj, "fill", 1, "tuple(int, int, int)"), bmax = extractValueArg(maxObj, "fill", 2, "tuple(int, int, int)"); grid.fill(CoordBBox(bmin, bmax), extractValueArg(valObj, "fill", 3), active); } template inline void signedFloodFill(GridType& grid) { tools::signedFloodFill(grid.tree()); } //////////////////////////////////////// #ifndef PY_OPENVDB_USE_NUMPY template inline void copyFromArray(GridType&, const py::object&, py::object, py::object) { PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); boost::python::throw_error_already_set(); } template inline void copyToArray(GridType&, const py::object&, py::object) { PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); boost::python::throw_error_already_set(); } #else // if defined(PY_OPENVDB_USE_NUMPY) 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; }; #if 0 template struct CppToNumPy {}; //template<> struct NumPyToCpp { enum { typenum = NPY_HALF }; }; template<> struct CppToNumPy { enum { typenum = NPY_FLOAT }; }; template<> struct CppToNumPy { enum { typenum = NPY_DOUBLE }; }; template<> struct CppToNumPy { enum { typenum = NPY_BOOL }; }; template<> struct CppToNumPy { enum { typenum = NPY_INT16 }; }; template<> struct CppToNumPy { enum { typenum = NPY_INT32 }; }; template<> struct CppToNumPy { enum { typenum = NPY_INT64 }; }; template<> struct CppToNumPy { enum { typenum = NPY_UINT32 }; }; template<> struct CppToNumPy { enum { typenum = NPY_UINT64 }; }; #endif // 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, // or throw an exception if arrObj is not a NumPy array object. const py::numeric::array arrayObj = pyutil::extractArg( arrObj, opName[toGrid], pyutil::GridTraits::name(), /*argIdx=*/1, "numpy.ndarray"); PyArrayObject* arrayObjPtr = reinterpret_cast(arrayObj.ptr()); const PyArray_Descr* dtype = PyArray_DESCR(arrayObjPtr); 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(arrayObjPtr); 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) //////////////////////////////////////// #ifndef PY_OPENVDB_USE_NUMPY template inline typename GridType::Ptr meshToLevelSet(py::object, py::object, py::object, py::object, py::object) { PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); boost::python::throw_error_already_set(); } template inline py::object volumeToQuadMesh(const GridType&, py::object) { PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); boost::python::throw_error_already_set(); } template inline py::object volumeToMesh(const GridType&, py::object, py::object) { PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); boost::python::throw_error_already_set(); } #else // if defined(PY_OPENVDB_USE_NUMPY) // Helper class for meshToLevelSet() template struct CopyVecOp { void operator()(const void* srcPtr, DstT* dst, size_t count) { const SrcT* src = static_cast(srcPtr); for (size_t i = count; i > 0; --i, ++src, ++dst) { *dst = static_cast(*src); } } }; // Partial specialization for source and destination arrays of the same type template struct CopyVecOp { void operator()(const void* srcPtr, T* dst, size_t count) { const T* src = static_cast(srcPtr); ::memcpy(dst, src, count * sizeof(T)); } }; // Helper function for use with meshToLevelSet() to copy vectors of various types // and sizes from NumPy arrays to STL vectors template inline void copyVecArray(py::numeric::array& arrayObj, std::vector& vec) { typedef typename VecT::ValueType ValueT; // Get the input array dimensions. PyArrayObject* arrayObjPtr = reinterpret_cast(arrayObj.ptr()); const PyArray_Descr* dtype = PyArray_DESCR(arrayObjPtr); const size_t M = py::extract(arrayObj.attr("shape")[0]); const size_t N = VecT().numElements(); if (M == 0 || N == 0) return; // Preallocate the output vector. vec.resize(M); // Copy values from the input array to the output vector (with type conversion, if necessary). const void* src = PyArray_DATA(arrayObjPtr); ValueT* dst = &vec[0][0]; switch (dtype->type_num) { case NPY_FLOAT: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case NPY_DOUBLE: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case NPY_INT16: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case NPY_INT32: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case NPY_INT64: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case NPY_UINT32: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case NPY_UINT64: CopyVecOp::type, ValueT>()(src, dst, M*N); break; default: break; } } /// @brief Given NumPy arrays of points, triangle indices and quad indices, /// call tools::meshToLevelSet() to generate a level set grid. template inline typename GridType::Ptr meshToLevelSet(py::object pointsObj, py::object trianglesObj, py::object quadsObj, py::object xformObj, py::object halfWidthObj) { struct Local { // Return the name of the Python grid method (for use in error messages). static const char* methodName() { return "createLevelSetFromPolygons"; } // Raise a Python exception if the given NumPy array does not have dimensions M x N // or does not have an integer or floating-point data type. static void validate2DNumPyArray(py::numeric::array arrayObj, const int N, const char* desiredType) { PyArrayObject* arrayObjPtr = reinterpret_cast(arrayObj.ptr()); const PyArray_Descr* dtype = PyArray_DESCR(arrayObjPtr); const py::object shape = arrayObj.attr("shape"); const int numDims = int(py::len(shape)); bool wrongArrayType = false; // Check array dimensions. if (numDims != 2 || py::extract(shape[1]) != N) { wrongArrayType = true; } else { // Check array data type. switch (dtype->type_num) { case NPY_FLOAT: case NPY_DOUBLE: case NPY_INT16: //case NPY_HALF: case NPY_INT32: case NPY_INT64: case NPY_UINT32: case NPY_UINT64: break; default: wrongArrayType = true; break; } } if (wrongArrayType) { // Generate an error message and raise a Python TypeError. std::string arrayTypeName; if (PyObject_HasAttrString(arrayObj.ptr(), "dtype")) { arrayTypeName = pyutil::str(arrayObj.attr("dtype")); } else { arrayTypeName = "'_'"; arrayTypeName[1] = dtype->kind; } std::ostringstream os; os << "expected N x 3 numpy.ndarray of " << desiredType << ", found "; switch (numDims) { case 0: os << "zero-dimensional"; break; case 1: os << "one-dimensional"; break; default: os << py::extract(shape[0]); for (int i = 1; i < numDims; ++i) { os << " x " << py::extract(shape[i]); } break; } os << " " << arrayTypeName << " array as argument 1 to " << pyutil::GridTraits::name() << "." << methodName() << "()"; PyErr_SetString(PyExc_TypeError, os.str().c_str()); py::throw_error_already_set(); } } }; // Extract the narrow band half width from the arguments to this method. const float halfWidth = extractValueArg( halfWidthObj, Local::methodName(), /*argIdx=*/5, "float"); // Extract the transform from the arguments to this method. math::Transform::Ptr xform = math::Transform::createLinearTransform(); if (!xformObj.is_none()) { xform = extractValueArg( xformObj, Local::methodName(), /*argIdx=*/4, "Transform"); } // Extract the list of mesh vertices from the arguments to this method. std::vector points; if (!pointsObj.is_none()) { // Extract a reference to (not a copy of) a NumPy array argument, // or throw an exception if the argument is not a NumPy array object. py::numeric::array arrayObj = extractValueArg( pointsObj, Local::methodName(), /*argIdx=*/1, "numpy.ndarray"); // Throw an exception if the array has the wrong type or dimensions. Local::validate2DNumPyArray(arrayObj, /*N=*/3, /*desiredType=*/"float"); // Copy values from the array to the vector. copyVecArray(arrayObj, points); } // Extract the list of triangle indices from the arguments to this method. std::vector triangles; if (!trianglesObj.is_none()) { py::numeric::array arrayObj = extractValueArg( trianglesObj, Local::methodName(), /*argIdx=*/2, "numpy.ndarray"); Local::validate2DNumPyArray(arrayObj, /*N=*/3, /*desiredType=*/"int"); copyVecArray(arrayObj, triangles); } // Extract the list of quad indices from the arguments to this method. std::vector quads; if (!quadsObj.is_none()) { py::numeric::array arrayObj = extractValueArg( quadsObj, Local::methodName(), /*argIdx=*/3, "numpy.ndarray"); Local::validate2DNumPyArray(arrayObj, /*N=*/4, /*desiredType=*/"int"); copyVecArray(arrayObj, quads); } // Generate and return a level set grid. return tools::meshToLevelSet(*xform, points, triangles, quads, halfWidth); } template inline py::object volumeToQuadMesh(const GridType& grid, py::object isovalueObj) { const double isovalue = pyutil::extractArg( isovalueObj, "convertToQuads", /*className=*/NULL, /*argIdx=*/2, "float"); // Mesh the input grid and populate lists of mesh vertices and face vertex indices. std::vector points; std::vector quads; tools::volumeToMesh(grid, points, quads, isovalue); // Copy vertices into an N x 3 NumPy array. py::object pointArrayObj = py::numeric::array(py::list(), "float32"); if (!points.empty()) { npy_intp dims[2] = { npy_intp(points.size()), 3 }; // Construct a NumPy array that wraps the point vector. if (PyArrayObject* arrayObj = reinterpret_cast( PyArray_SimpleNewFromData(/*nd=*/2, dims, NPY_FLOAT, &points[0]))) { // Create a deep copy of the array (because the point vector will be // destroyed when this function returns). pointArrayObj = pyutil::pyBorrow(PyArray_NewCopy(arrayObj, NPY_CORDER)); } } // Copy face indices into an N x 4 NumPy array. py::object quadArrayObj = py::numeric::array(py::list(), "uint32"); if (!quads.empty()) { npy_intp dims[2] = { npy_intp(quads.size()), 4 }; if (PyArrayObject* arrayObj = reinterpret_cast( PyArray_SimpleNewFromData(/*dims=*/2, dims, NPY_UINT32, &quads[0]))) { quadArrayObj = pyutil::pyBorrow(PyArray_NewCopy(arrayObj, NPY_CORDER)); } } return py::make_tuple(pointArrayObj, quadArrayObj); } template inline py::object volumeToMesh(const GridType& grid, py::object isovalueObj, py::object adaptivityObj) { const double isovalue = pyutil::extractArg( isovalueObj, "convertToPolygons", /*className=*/NULL, /*argIdx=*/2, "float"); const double adaptivity = pyutil::extractArg( adaptivityObj, "convertToPolygons", /*className=*/NULL, /*argIdx=*/3, "float"); // Mesh the input grid and populate lists of mesh vertices and face vertex indices. std::vector points; std::vector triangles; std::vector quads; tools::volumeToMesh(grid, points, triangles, quads, isovalue, adaptivity); // Copy vertices into an N x 3 NumPy array. py::object pointArrayObj = py::numeric::array(py::list(), "float32"); if (!points.empty()) { npy_intp dims[2] = { npy_intp(points.size()), 3 }; // Construct a NumPy array that wraps the point vector. if (PyArrayObject* arrayObj = reinterpret_cast( PyArray_SimpleNewFromData(/*dims=*/2, dims, NPY_FLOAT, &points[0]))) { // Create a deep copy of the array (because the point vector will be // destroyed when this function returns). pointArrayObj = pyutil::pyBorrow(PyArray_NewCopy(arrayObj, NPY_CORDER)); } } // Copy triangular face indices into an N x 3 NumPy array. py::object triangleArrayObj = py::numeric::array(py::list(), "uint32"); if (!triangles.empty()) { npy_intp dims[2] = { npy_intp(triangles.size()), 3 }; if (PyArrayObject* arrayObj = reinterpret_cast( PyArray_SimpleNewFromData(/*dims=*/2, dims, NPY_UINT32, &triangles[0]))) { triangleArrayObj = pyutil::pyBorrow(PyArray_NewCopy(arrayObj, NPY_CORDER)); } } // Copy quadrilateral face indices into an N x 4 NumPy array. py::object quadArrayObj = py::numeric::array(py::list(), "uint32"); if (!quads.empty()) { npy_intp dims[2] = { npy_intp(quads.size()), 4 }; if (PyArrayObject* arrayObj = reinterpret_cast( PyArray_SimpleNewFromData(/*dims=*/2, dims, NPY_UINT32, &quads[0]))) { quadArrayObj = pyutil::pyBorrow(PyArray_NewCopy(arrayObj, NPY_CORDER)); } } return py::make_tuple(pointArrayObj, triangleArrayObj, quadArrayObj); } #endif // defined(PY_OPENVDB_USE_NUMPY) //////////////////////////////////////// template inline void applyMap(const char* methodName, GridType& grid, py::object funcObj) { 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&, const ValueT&) { PyErr_SetString(PyExc_AttributeError, "can't set attribute 'value'"); py::throw_error_already_set(); } static void setActive(const IterT&, bool /*on*/) { PyErr_SetString(PyExc_AttributeError, "can't set attribute 'active'"); py::throw_error_already_set(); } }; /// @brief Value returned by the next() method of a grid's value iterator /// @details This class allows both dictionary-style (e.g., items["depth"]) and /// attribute access (e.g., items.depth) to the items returned by an iterator. /// @todo Create a reusable base class for "named dicts" like this? template class IterValueProxy { public: 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() && math::isExactlyEqual(other.getValue(), this->getValue()) && other.getBBoxMin() == this->getBBoxMin() && other.getBBoxMax() == this->getBBoxMax() && other.getVoxelCount() == this->getVoxelCount()); } bool operator!=(const IterValueProxy& other) const { return !(*this == other); } /// Print this dictionary to a stream. std::ostream& put(std::ostream& os) const { // valuesAsStrings = ["%s: %s" % key, repr(this[key]) for key in this.keys()] py::list valuesAsStrings; for (int i = 0; this->keys()[i] != 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: public 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("convertToQuads", &pyGrid::volumeToQuadMesh, (py::arg("isovalue")=0), "convertToQuads(isovalue=0) -> points, quads\n\n" "Uniformly mesh a scalar grid that has a continuous isosurface\n" "at the given isovalue. Return a NumPy array of world-space\n" "points and a NumPy array of 4-tuples of point indices, which\n" "specify the vertices of the quadrilaterals that form the mesh.") .def("convertToPolygons", &pyGrid::volumeToMesh, (py::arg("isovalue")=0, py::arg("adaptivity")=0), "convertToPolygons(isovalue=0, adaptivity=0) -> points, triangles, quads\n\n" "Adaptively mesh a scalar grid that has a continuous isosurface\n" "at the given isovalue. Return a NumPy array of world-space\n" "points and NumPy arrays of 3- and 4-tuples of point indices,\n" "which specify the vertices of the triangles and quadrilaterals\n" "that form the mesh. Adaptivity can vary from 0 to 1, where 0\n" "produces a high-polygon-count mesh that closely approximates\n" "the isosurface, and 1 produces a lower-polygon-count mesh\n" "with some loss of surface detail.") .def("createLevelSetFromPolygons", &pyGrid::meshToLevelSet, (py::arg("points"), py::arg("triangles")=py::object(), py::arg("quads")=py::object(), py::arg("transform")=py::object(), py::arg("halfWidth")=openvdb::LEVEL_SET_HALF_WIDTH), ("createLevelSetFromPolygons(points, triangles=None, quads=None,\n" " transform=None, halfWidth=" + boost::lexical_cast(openvdb::LEVEL_SET_HALF_WIDTH) + ") -> " + pyGridTypeName + "\n\n" "Convert a triangle and/or quad mesh to a narrow-band level set volume.\n" "The mesh must form a closed surface, but the surface need not be\n" "manifold and may have self intersections and degenerate faces.\n" "The mesh is described by a NumPy array of world-space points\n" "and NumPy arrays of 3- and 4-tuples of point indices that specify\n" "the vertices of the triangles and quadrilaterals that form the mesh.\n" "Either the triangle or the quad array may be empty or None.\n" "The resulting volume will have the given transform (or the identity\n" "transform if no transform is given) and a narrow band width of\n" "2 x halfWidth voxels.").c_str()) .staticmethod("createLevelSetFromPolygons") .def("prune", &pyGrid::prune, (py::arg("tolerance")=0), "prune(tolerance=0)\n\n" "Remove nodes whose values all have the same active state\n" "and are equal to within a given tolerance.") .def("pruneInactive", &pyGrid::pruneInactive, (py::arg("value")=py::object()), "pruneInactive(value=None)\n\n" "Remove nodes whose values are all inactive and replace them\n" "with either background tiles or tiles of the given value\n" "(if the value is not None).") .def("empty", &GridType::empty, "empty() -> bool\n\n" "Return True if this grid contains only background voxels.") .def("__nonzero__", &pyGrid::notEmpty) .def("clear", &GridType::clear, "clear()\n\n" "Remove all tiles from this grid and all nodes other than the root node.") .def("merge", &GridType::merge, ("merge(" + pyGridTypeName + ")\n\n" "Move child nodes from the other grid into this grid wherever\n" "those nodes correspond to constant-value tiles in this grid,\n" "and replace leaf-level inactive voxels in this grid with\n" "corresponding voxels in the other grid that are active.\n\n" "Note: this operation always empties the other grid.").c_str()) .def("mapOn", &pyGrid::mapOn, py::arg("function"), "mapOn(function)\n\n" "Iterate over all the active (\"on\") values (tile and voxel)\n" "of this grid and replace each value with function(value).\n\n" "Example: grid.mapOn(lambda x: x * 2 if x < 0.5 else x)") .def("mapOff", &pyGrid::mapOff, py::arg("function"), "mapOff(function)\n\n" "Iterate over all the inactive (\"off\") values (tile and voxel)\n" "of this grid and replace each value with function(value).\n\n" "Example: grid.mapOff(lambda x: x * 2 if x < 0.5 else x)") .def("mapAll", &pyGrid::mapAll, py::arg("function"), "mapAll(function)\n\n" "Iterate over all values (tile and voxel) of this grid\n" "and replace each value with function(value).\n\n" "Example: grid.mapAll(lambda x: x * 2 if x < 0.5 else x)") .def("combine", &pyGrid::combine, (py::arg("grid"), py::arg("function")), "combine(grid, function)\n\n" "Compute function(self, other) over all corresponding pairs\n" "of values (tile or voxel) of this grid and the other grid\n" "and store the result in this grid.\n\n" "Note: this operation always empties the other grid.\n\n" "Example: grid.combine(otherGrid, lambda a, b: min(a, b))") // // Iterators // .def("citerOnValues", &pyGrid::IterTraits::begin, "citerOnValues() -> iterator\n\n" "Return a read-only iterator over this grid's active\ntile and voxel values.") .def("citerOffValues", &pyGrid::IterTraits::begin, "iterOffValues() -> iterator\n\n" "Return a read-only iterator over this grid's inactive\ntile and voxel values.") .def("citerAllValues", &pyGrid::IterTraits::begin, "iterAllValues() -> iterator\n\n" "Return a read-only iterator over all of this grid's\ntile and voxel values.") .def("iterOnValues", &pyGrid::IterTraits::begin, "iterOnValues() -> iterator\n\n" "Return a read/write iterator over this grid's active\ntile and voxel values.") .def("iterOffValues", &pyGrid::IterTraits::begin, "iterOffValues() -> iterator\n\n" "Return a read/write iterator over this grid's inactive\ntile and voxel values.") .def("iterAllValues", &pyGrid::IterTraits::begin, "iterAllValues() -> iterator\n\n" "Return a read/write iterator over all of this grid's\ntile and voxel values.") ; // py::class_ 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyMetadata.cc0000644000000000000000000001001412603226506015130 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include "openvdb/openvdb.h" namespace py = boost::python; using namespace openvdb::OPENVDB_VERSION_NAME; namespace { class MetadataWrap: public Metadata, public py::wrapper { public: Name typeName() const { return static_cast(this->get_override("typeName")()); } Metadata::Ptr copy() const { return static_cast(this->get_override("copy")()); } void copy(const Metadata& other) { this->get_override("copy")(other); } std::string str() const {return static_cast(this->get_override("str")());} bool asBool() const { return static_cast(this->get_override("asBool")()); } Index32 size() const { return static_cast(this->get_override("size")()); } protected: void readValue(std::istream& is, Index32 numBytes) { this->get_override("readValue")(is, numBytes); } void writeValue(std::ostream& os) const { this->get_override("writeValue")(os); } }; // aliases disambiguate the different versions of copy Metadata::Ptr (MetadataWrap::*copy0)() const = &MetadataWrap::copy; void (MetadataWrap::*copy1)(const Metadata&) = &MetadataWrap::copy; } // end anonymous namespace void exportMetadata() { 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyTransform.cc0000644000000000000000000003146312603226506015376 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include "openvdb/openvdb.h" #include "pyutil.h" namespace py = boost::python; using namespace openvdb::OPENVDB_VERSION_NAME; namespace pyTransform { inline void scale1(math::Transform& t, double s) { t.preScale(s); } inline void scale3(math::Transform& t, const Vec3d& xyz) { t.preScale(xyz); } inline Vec3d voxelDim0(math::Transform& t) { return t.voxelSize(); } inline Vec3d voxelDim1(math::Transform& t, const Vec3d& p) { return t.voxelSize(p); } inline double voxelVolume0(math::Transform& t) { return t.voxelVolume(); } inline double voxelVolume1(math::Transform& t, const Vec3d& p) { return t.voxelVolume(p); } inline Vec3d indexToWorld(math::Transform& t, const Vec3d& p) { return t.indexToWorld(p); } inline Vec3d worldToIndex(math::Transform& t, const Vec3d& p) { return t.worldToIndex(p); } inline Coord worldToIndexCellCentered(math::Transform& t, const Vec3d& p) { return t.worldToIndexCellCentered(p); } inline Coord worldToIndexNodeCentered(math::Transform& t, const Vec3d& p) { return t.worldToIndexNodeCentered(p); } inline std::string info(math::Transform& t) { std::ostringstream ostr; t.print(ostr); return ostr.str(); } inline math::Transform::Ptr createLinearFromDim(double dim) { return math::Transform::createLinearTransform(dim); } inline math::Transform::Ptr createLinearFromMat(py::object obj) { Mat4R m; // Verify that obj is a four-element sequence. bool is4x4Seq = (PySequence_Check(obj.ptr()) && PySequence_Length(obj.ptr()) == 4); if (is4x4Seq) { for (int row = 0; is4x4Seq && row < 4; ++row) { // Verify that each element of obj is itself a four-element sequence. py::object rowObj = obj[row]; if (PySequence_Check(rowObj.ptr()) && PySequence_Length(rowObj.ptr()) == 4) { // Extract four numeric values from this row of the sequence. for (int col = 0; is4x4Seq && col < 4; ++col) { if (py::extract(rowObj[col]).check()) { m[row][col] = py::extract(rowObj[col]); } else { is4x4Seq = false; } } } else { is4x4Seq = false; } } } if (!is4x4Seq) { PyErr_Format(PyExc_ValueError, "expected a 4 x 4 sequence of numeric values"); py::throw_error_already_set(); } return math::Transform::createLinearTransform(m); } inline math::Transform::Ptr createFrustum(const Coord& xyzMin, const Coord& xyzMax, double taper, double depth, double voxelDim = 1.0) { return math::Transform::createFrustumTransform( BBoxd(xyzMin.asVec3d(), xyzMax.asVec3d()), taper, depth, voxelDim); } //////////////////////////////////////// struct PickleSuite: public py::pickle_suite { enum { STATE_DICT = 0, STATE_MAJOR, STATE_MINOR, STATE_FORMAT, STATE_XFORM }; /// Return @c true, indicating that this pickler preserves a Transform's __dict__. static bool getstate_manages_dict() { return true; } /// Return a tuple representing the state of the given Transform. static py::tuple getstate(py::object xformObj) { py::tuple state; py::extract x(xformObj); if (x.check()) { // Extract a Transform from the Python object. math::Transform xform = x(); std::ostringstream ostr(std::ios_base::binary); // Serialize the Transform to a string. xform.write(ostr); // Construct a state tuple comprising the Python object's __dict__, // the version numbers of the serialization format, // and the serialized Transform. 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", &pyTransform::worldToIndexCellCentered, py::arg("xyz"), "worldToIndexCellCentered((x, y, z)) -> (i, j, k)\n\n" "Apply the inverse of this transformation to the given coordinates\n" "and round the result to the nearest integer coordinates.") .def("worldToIndexNodeCentered", &pyTransform::worldToIndexNodeCentered, py::arg("xyz"), "worldToIndexNodeCentered((x, y, z)) -> (i, j, k)\n\n" "Apply the inverse of this transformation to the given coordinates\n" "and round the result down to the nearest integer coordinates.") // Allow Transforms to be compared for equality and inequality. .def(py::self == py::other()) .def(py::self != py::other()) ; py::def("createLinearTransform", &pyTransform::createLinearFromMat, py::arg("matrix"), "createLinearTransform(matrix) -> Transform\n\n" "Create a new linear transform from a 4 x 4 matrix given as a sequence\n" "of the form [[a, b, c, d], [e, f, g, h], [i, j, k, l], [m, n, o, p]],\n" "where [m, n, o, p] is the translation component."); py::def("createLinearTransform", &pyTransform::createLinearFromDim, (py::arg("voxelSize") = 1.0), "createLinearTransform(voxelSize) -> Transform\n\n" "Create a new linear transform with the given uniform voxel size."); py::def("createFrustumTransform", &pyTransform::createFrustum, (py::arg("xyzMin"), py::arg("xyzMax"), py::arg("taper"), py::arg("depth"), py::arg("voxelSize") = 1.0), "createFrustumTransform(xyzMin, xyzMax, taper, depth, voxelSize) -> Transform\n\n" "Create a new frustum transform with unit bounding box (xyzMin, xyzMax)\n" "and the given taper, depth and uniform voxel size."); // allows Transform::Ptr Grid::getTransform() to work py::register_ptr_to_python(); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyopenvdb.h0000644000000000000000000001054712603226506014722 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyIntGrid.cc0000644000000000000000000000407012603226506014755 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyVec3Grid.cc0000644000000000000000000000406212603226506015024 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/doc/0000755000000000000000000000000012603226634011762 5ustar rootrootopenvdb/doc/python.txt0000644000000000000000000005204712603226304014046 0ustar rootroot/** @page python Using OpenVDB in Python This section describes the OpenVDB Python module and includes Python code snippets and some complete programs that illustrate how to perform common tasks. (An API reference is also available, if Epydoc is installed.) As of OpenVDB 2.0, the Python module exposes most of the functionality of the C++ @vdblink::Grid Grid@endlink class, including I/O, metadata management, voxel access and iteration, but almost none of the many @ref secToolUtils "tools". We expect to add support for tools in forthcoming releases. The Python module supports a fixed set of grid types. If the symbol @c PY_OPENVDB_WRAP_ALL_GRID_TYPES is defined at compile time, most of the grid types declared in openvdb.h are accessible in Python, otherwise only @b FloatGrid, @b BoolGrid and @b Vec3SGrid are accessible. To add support for grids with other value types or configurations, search for @c PY_OPENVDB_WRAP_ALL_GRID_TYPES in the module source code, update the code as appropriate and recompile the module. (It is possible that this process will be streamlined in the future with a plugin mechanism.) Note however that adding grid types can significantly increase the time and memory needed to compile the module and can significantly increase the size of the resulting executable. In addition, grids of custom types that are saved to .vdb files or pickled will not be readable by clients using the standard version of the module. Also note that the @vdblink::tree::Tree Tree@endlink class is not exposed in Python. Much of its functionality is either available through the @vdblink::Grid Grid@endlink or is too low-level to be generally useful in Python. Although trees are not accessible in Python, they can of course be operated on indirectly. Of note are the grid methods @b copy, which returns a new grid that shares its tree with the original grid, @b deepCopy, which returns a new grid that owns its own tree, and @b sharesWith, which reports whether two grids share a tree. @section sPyContents Contents - @ref sPyBasics - @ref sPyHandlingMetadata - @ref sPyAccessors - @ref sPyIteration - @ref sPyNumPy - @ref sPyMeshConversion - @ref sPyCppAPI @section sPyBasics Getting started The following example is a complete program that illustrates some of the basic steps to create grids and write them to disk: @code{.py} import pyopenvdb as vdb # A grid comprises a sparse tree representation of voxel data, # user-supplied metadata and a voxel space to world space transform, # which defaults to the identity transform. # A FloatGrid stores one single-precision floating point value per voxel. # Other grid types include BoolGrid and Vec3SGrid. The module-level # attribute pyopenvdb.GridTypes gives the complete list. cube = vdb.FloatGrid() cube.fill(min=(100, 100, 100), max=(199, 199, 199), value=1.0) # Name the grid "cube". cube.name = 'cube' # Populate another FloatGrid with a sparse, narrow-band level set # representation of a sphere with radius 50 voxels, located at # (1.5, 2, 3) in index space. sphere = vdb.createLevelSetSphere(radius=50, center=(1.5, 2, 3)) # Associate some metadata with the grid. sphere['radius'] = 50.0 # Associate a scaling transform with the grid that sets the voxel size # to 0.5 units in world space. sphere.transform = vdb.createLinearTransform(voxelSize=0.5) # Name the grid "sphere". sphere.name = 'sphere' # Write both grids to a VDB file. vdb.write('mygrids.vdb', grids=[cube, sphere]) @endcode This example shows how to read grids from files, and some ways to modify grids: @code{.py} import pyopenvdb as vdb # Read a .vdb file and return a list of grids populated with # their metadata and transforms, but not their trees. filename = 'mygrids.vdb' grids = vdb.readAllGridMetadata(filename) # Look for and read in a level-set grid that has certain metadata. sphere = None for grid in grids: if (grid.gridClass == vdb.GridClass.LEVEL_SET and 'radius' in grid and grid['radius'] > 10.0): sphere = vdb.read(filename, grid.name) else: print 'skipping grid', grid.name if sphere: # Convert the level set sphere to a narrow-band fog volume, in which # interior voxels have value 1, exterior voxels have value 0, and # narrow-band voxels have values varying linearly from 0 to 1. outside = sphere.background width = 2.0 * outside # Visit and update all of the grid's active values, which correspond to # voxels in the narrow band. for iter in sphere.iterOnValues(): dist = iter.value iter.value = (outside - dist) / width # Visit all of the grid's inactive tile and voxel values and update # the values that correspond to the interior region. for iter in sphere.iterOffValues(): if iter.value < 0.0: iter.value = 1.0 iter.active = False # Set exterior voxels to 0. sphere.background = 0.0 sphere.gridClass = vdb.GridClass.FOG_VOLUME @endcode @section sPyHandlingMetadata Handling metadata Metadata of various types (string, bool, int, float, and 2- and 3-element sequences of ints or floats) can be attached both to individual grids and to files on disk, either by supplying a Python dictionary of (name, value) pairs or, in the case of grids, through a dictionary-like interface. Add (name, value) metadata pairs to a grid as you would to a dictionary. A new value overwrites an existing value if the name matches an existing name. @code{.py} >>> import pyopenvdb as vdb >>> grid = vdb.Vec3SGrid() >>> grid['vector'] = 'gradient' >>> grid['radius'] = 50.0 >>> grid['center'] = (10, 15, 10) >>> grid.metadata {'vector': 'gradient', 'radius': 50.0, 'center': (10, 15, 10)} >>> grid['radius'] 50.0 >>> 'radius' in grid, 'misc' in grid (True, False) # OK to overwrite an existing value with a value of another type: >>> grid['center'] = 0.0 # A 4-element sequence is not a supported metadata value type: >>> grid['center'] = (0, 0, 0, 0) File "", line 1, in TypeError: metadata value "(0, 0, 0, 0)" of type tuple is not allowed # Metadata names must be strings: >>> grid[0] = (10.5, 15, 30) File "", line 1, in TypeError: expected str, found int as argument 1 to __setitem__() @endcode Alternatively, replace all or some of a grid’s metadata by supplying a (name, value) dictionary: @code{.py} >>> metadata = { ... 'vector': 'gradient', ... 'radius': 50.0, ... 'center': (10, 15, 10) ... } # Replace all of the grid's metadata. >>> grid.metadata = metadata >>> metadata = { ... 'center': [10.5, 15, 30], ... 'scale': 3.14159 ... } # Overwrite "center" and add "scale": >>> grid.updateMetadata(metadata) @endcode Iterate over a grid’s metadata as you would over a dictionary: @code{.py} >>> for key in grid: ... print '%s = %s' % (key, grid[key]) ... vector = gradient radius = 50.0 scale = 3.14159 center = (10.5, 15.0, 30.0) @endcode Removing metadata is also straightforward: @code{.py} >>> del grid['vector'] >>> del grid['center'] >>> del grid['vector'] # error: already removed File "", line 1, in KeyError: 'vector' >>> grid.metadata = {} # remove all metadata @endcode Some grid metadata is exposed in the form of properties, either because it might be frequently accessed (a grid’s name, for example) or because its allowed values are somehow restricted: @code{.py} >>> grid = vdb.createLevelSetSphere(radius=10.0) >>> grid.metadata {'class': 'level set'} >>> grid.gridClass = vdb.GridClass.FOG_VOLUME >>> grid.metadata {'class': 'fog volume'} # The gridClass property requires a string value: >>> grid.gridClass = 123 File "", line 1, in TypeError: expected str, found int as argument 1 to setGridClass() # Only certain strings are recognized; see pyopenvdb.GridClass # for the complete list. >>> grid.gridClass = 'Hello, world.' >>> grid.metadata {'class': 'unknown'} >>> grid.metadata = {} >>> grid.vectorType = vdb.VectorType.COVARIANT >>> grid.metadata {'vector_type': 'covariant'} >>> grid.name = 'sphere' >>> grid.creator = 'Python' >>> grid.metadata {'vector_type': 'covariant', 'name': 'sphere', 'creator': 'Python'} @endcode Setting these properties to @c None removes the corresponding metadata, but the properties retain default values: @code{.py} >>> grid.creator = grid.vectorType = None >>> grid.metadata {'name': 'sphere'} >>> grid.creator, grid.vectorType ('', 'invariant') @endcode Metadata can be associated with a .vdb file at the time the file is written, by supplying a (name, value) dictionary as the optional @c metadata argument to the @b write function: @code{.py} >>> metadata = { ... 'creator': 'Python', ... 'time': '11:05:00' ... } >>> vdb.write('mygrids.vdb', grids=grid, metadata=metadata) @endcode File-level metadata can be retrieved with either the @b readMetadata function or the @b readAll function: @code{.py} >>> metadata = vdb.readMetadata('mygrids.vdb') >>> metadata {'creator': 'Python', 'time': '11:05:00'} >>> grids, metadata = vdb.readAll('mygrids.vdb') >>> metadata {'creator': 'Python', 'time': '11:05:00'} >>> [grid.name for grid in grids] ['sphere'] @endcode @section sPyAccessors Voxel access Grids provide read-only and read/write accessors for voxel lookup via @ijk index coordinates. Accessors store references to their parent grids, so a grid will not be deleted while it has accessors in use. @code{.py} >>> import pyopenvdb as vdb # Read two grids from a file. >>> grids, metadata = vdb.readAll('smoke2.vdb') >>> [grid.name for grid in grids] ['density', 'v'] # Get read/write accessors to the two grids. >>> dAccessor = grids[0].getAccessor() >>> vAccessor = grids[1].getAccessor() >>> ijk = (100, 103, 101) >>> dAccessor.probeValue(ijk) (0.17614534497261047, True) # Change the value of a voxel. >>> dAccessor.setValueOn(ijk, 0.125) >>> dAccessor.probeValue(ijk) (0.125, True) >>> vAccessor.probeValue(ijk) ((-2.90625, 9.84375, 0.84228515625), True) # Change the active state of a voxel. >>> vAccessor.setActiveState(ijk, False) >>> vAccessor.probeValue(ijk) ((-2.90625, 9.84375, 0.84228515625), False) # Get a read-only accessor to one of the grids. >>> dAccessor = grids[0].getConstAccessor() >>> dAccessor.setActiveState(ijk, False) File "", line 1, in TypeError: accessor is read-only # Delete the accessors once they are no longer needed, # so that the grids can be garbage-collected. >>> del dAccessor, vAccessor @endcode @section sPyIteration Iteration Grids provide read-only and read/write iterators over their values. Iteration is over sequences of value objects (BoolGrid.Values, FloatGrid.Values, etc.) that expose properties such as the number of voxels spanned by a value (one, for a voxel value, more than one for a tile value), its coordinates and its active state. Value objects returned by read-only iterators are immutable; those returned by read/write iterators permit assignment to their active state and value properties, which modifies the underlying grid. Value objects store references to their parent grids, so a grid will not be deleted while one of its value objects is in use. @code{.py} >>> import pyopenvdb as vdb >>> grid = vdb.read('smoke2.vdb', gridname='v') >>> grid.__class__.__name__ 'Vec3SGrid' # Iterate over inactive values and print the coordinates of the first # five voxel values and the bounding boxes of the first five tile values. >>> voxels = tiles = 0 ... N = 5 ... for item in grid.citerOffValues(): # read-only iterator ... if voxels == N and tiles == N: ... break ... if item.count == 1: ... if voxels < N: ... voxels += 1 ... print 'voxel', item.min ... else: ... if tiles < N: ... tiles += 1 ... print 'tile', item.min, item.max ... tile (0, 0, 0) (7, 7, 7) tile (0, 0, 8) (7, 7, 15) tile (0, 0, 16) (7, 7, 23) tile (0, 0, 24) (7, 7, 31) tile (0, 0, 32) (7, 7, 39) voxel (40, 96, 88) voxel (40, 96, 89) voxel (40, 96, 90) voxel (40, 96, 91) voxel (40, 96, 92) # Iterate over and normalize all active values. >>> from math import sqrt >>> for item in grid.iterOnValues(): # read/write iterator ... vector = item.value ... magnitude = sqrt(sum(x * x for x in vector)) ... item.value = [x / magnitude for x in vector] ... @endcode For some operations, it might be more convenient to use one of the grid methods @b mapOn, @b mapOff or @b mapAll. These methods iterate over a grid’s tiles and voxels (active, inactive or both, respectively) and replace each value x with f(x), where f is a callable object. These methods are not multithreaded. @code{.py} >>> import pyopenvdb as vdb >>> from math import sqrt >>> grid = vdb.read('smoke2.vdb', gridname='v') >>> def normalize(vector): ... magnitude = sqrt(sum(x * x for x in vector)) ... return [x / magnitude for x in vector] ... >>> grid.mapOn(normalize) @endcode Similarly, the @b combine method iterates over corresponding pairs of values (tile and voxel) of two grids @e A and @e B of the same type (FloatGrid, Vec3SGrid, etc.), replacing values in @e A with f(a, b), where f is a callable object. This operation assumes that index coordinates @ijk in both grids correspond to the same physical, world space location. Also, the operation always leaves grid @e B empty. @code{.py} >>> import pyopenvdb as vdb >>> density = vdb.read('smoke2.vdb', gridname='density') >>> density.__class__.__name__ 'FloatGrid' >>> sphere = vdb.createLevelSetSphere(radius=50.0, center=(100, 300, 100)) >>> density.combine(sphere, lambda a, b: min(a, b)) @endcode For now, @b combine operates only on tile and voxel values, not on their active states or other attributes. @section sPyNumPy Working with NumPy arrays Large data sets are often handled in Python using NumPy. The OpenVDB Python module can optionally be compiled with NumPy support. With NumPy enabled, the @b copyFromArray and @b copyToArray grid methods can be used to exchange data efficiently between scalar-valued grids and three-dimensional NumPy arrays and between vector-valued grids and four-dimensional NumPy arrays. @code{.py} >>> import pyopenvdb as vdb >>> import numpy >>> array = numpy.random.rand(200, 200, 200) >>> array.dtype dtype('float64') # Copy values from a three-dimensional array of doubles # into a grid of floats. >>> grid = vdb.FloatGrid() >>> grid.copyFromArray(array) >>> grid.activeVoxelCount() == array.size True >>> grid.evalActiveVoxelBoundingBox() ((0, 0, 0), (199, 199, 199)) # Copy values from a four-dimensional array of ints # into a grid of float vectors. >>> vecarray = numpy.ndarray((60, 70, 80, 3), int) >>> vecarray.fill(42) >>> vecgrid = vdb.Vec3SGrid() >>> vecgrid.copyFromArray(vecarray) >>> vecgrid.activeVoxelCount() == 60 * 70 * 80 True >>> vecgrid.evalActiveVoxelBoundingBox() ((0, 0, 0), (59, 69, 79)) @endcode When copying from a NumPy array, values in the array that are equal to the destination grid’s background value (or close to it, if the @c tolerance argument to @b copyFromArray is nonzero) are set to the background value and are marked inactive. All other values are marked active. @code{.py} >>> grid.clear() >>> grid.copyFromArray(array, tolerance=0.2) >>> print '%d%% of copied voxels are active' % ( ... round(100.0 * grid.activeVoxelCount() / array.size)) 80% of copied voxels are active @endcode The optional @c ijk argument specifies the index coordinates of the voxel in the destination grid into which to start copying values. That is, array index (0, 0, 0) maps to voxel @ijk. @code{.py} >>> grid.clear() >>> grid.copyFromArray(array, ijk=(-1, 2, 3)) >>> grid.evalActiveVoxelBoundingBox() ((-1, 2, 3), (198, 201, 202)) @endcode The @b copyToArray method also accepts an @c ijk argument. It specifies the index coordinates of the voxel to be copied to array index (0, 0, 0). @code{.py} >>> grid = vdb.createLevelSetSphere(radius=10.0) >>> array = numpy.ndarray((40, 40, 40), int) >>> array.fill(0) # Copy values from a grid of floats into # a three-dimensional array of ints. >>> grid.copyToArray(array, ijk=(-15, -20, -35)) >>> array[15, 20] array([ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3]) @endcode @b copyToArray has no @c tolerance argument, because there is no distinction between active and inactive values in the destination array. @section sPyMeshConversion Mesh conversion Also available if the OpenVDB Python module is compiled with NumPy support (see @ref sPyNumPy "above") are grid methods to convert polygonal meshes to level sets (see @vdblink::tools::meshToLevelSet() tools::meshToLevelSet@endlink for some restrictions) and to convert isosurfaces of scalar-valued grids to meshes. @code{.py} >>> import pyopenvdb as vdb >>> import numpy >>> grid = vdb.read('bunny.vdb', 'ls_bunny') # Convert a volume to a quadrilateral mesh. >>> points, quads = grid.convertToQuads() # World-space vertices of the mesh: >>> points array([[-14.05082607, -0.10118673, -0.40250054], [-14.05230808, -0.05570767, -0.45693323], [-14.05613995, -0.0734246 , -0.42150033], ..., [ 7.25201273, 13.25417805, 6.45283508], [ 7.25596714, 13.31225586, 6.40827513], [ 7.30518484, 13.21096039, 6.40724468]], dtype=float32) # Quadrilateral faces of the mesh, given by # 4-tuples of indices into the vertex list: >>> quads array([[ 5, 2, 1, 4], [ 11, 7, 6, 10], [ 14, 9, 8, 13], ..., [1327942, 1327766, 1339685, 1339733], [1339728, 1327921, 1327942, 1339733], [1339733, 1339685, 1339661, 1339728]], dtype=uint32) # Convert the mesh back to a (single-precision) volume. # Give the resulting grid the original grid's transform. >>> gridFromQuads = vdb.FloatGrid.createLevelSetFromPolygons( ... points, quads=quads, transform=grid.transform) # Alternatively, mesh a volume adaptively for a lower polygon count. # Adaptive meshing generates both triangular and quadrilateral faces. >>> points, triangles, quads = grid.convertToPolygons(adaptivity=0.5) # World-space vertices of the mesh: >>> points array([[-14.02906322, -0.07213751, -0.49265194], [-14.11877823, -0.11127799, -0.17118289], [-13.85006142, -0.08145611, -0.86669081], ..., [ 7.31098318, 12.97358608, 6.55133963], [ 7.20240211, 12.80632019, 6.80356836], [ 7.23679161, 13.28100395, 6.45595646]], dtype=float32) # Triangular faces of the mesh, given by # triples of indices into the vertex list: >>> triangles array([[ 8, 7, 0], [ 14, 9, 8], [ 14, 11, 9], ..., [22794, 22796, 22797], [22784, 22783, 22810], [22796, 22795, 22816]], dtype=uint32) # Quadrilateral faces of the mesh, given by # 4-tuples of indices into the vertex list: >>> quads array([[ 4, 3, 6, 5], [ 8, 9, 10, 7], [ 11, 12, 10, 9], ..., [23351, 23349, 23341, 23344], [23344, 23117, 23169, 23351], [23169, 23167, 23349, 23351]], dtype=uint32) # Convert the mesh to a double-precision volume. >>> doubleGridFromPolys = vdb.DoubleGrid.createLevelSetFromPolygons( ... points, triangles, quads, transform=grid.transform) @endcode The mesh representation is similar to that of the commonly-used Wavefront .obj file format, except that the vertex array is indexed starting from 0 rather than 1. To output mesh data to a file in .obj format, one might do the following: @code{.py} >>> def writeObjFile(filename, points, triangles=[], quads=[]): ... f = open(filename, 'w') ... # Output vertices. ... for xyz in points: ... f.write('v %g %g %g\n' % tuple(xyz)) ... f.write('\n') ... # Output faces. ... for ijk in triangles: ... f.write('f %d %d %d\n' % ... (ijk[0]+1, ijk[1]+1, ijk[2]+1)) # offset vertex indices by one ... for ijkl in quads: ... f.write('f %d %d %d %d\n' % ... (ijkl[0]+1, ijkl[1]+1, ijkl[2]+1, ijkl[3]+1)) ... f.close() ... >>> mesh = grid.convertToPolygons(adaptivity=0.8) >>> writeObjFile('bunny.obj', *mesh) @endcode @section sPyCppAPI C++ glue routines Python objects of type @b FloatGrid, @b Vec3SGrid, etc. are backed by C structs that “inherit” from @c PyObject. The OpenVDB Python extension module includes public functions that you can call in your own extension modules to convert between @vdblink::Grid openvdb::Grids@endlink and PyObjects. See the pyopenvdb.h reference for a description of these functions and a usage example. Your extension module might need to link against the OpenVDB extension module in order to access these functions. On UNIX systems, it might also be necessary to specify the @c RTLD_GLOBAL flag when importing the OpenVDB module, to allow its symbols to be shared across modules. See @b setdlopenflags in the Python @b sys module for one way to do this. */ openvdb/doc/api_0_98_0.txt0000644000000000000000000002016312603226304014246 0ustar rootroot/** @page api_0_98_0 Porting to OpenVDB 0.98.0 Starting in OpenVDB 0.98.0, @vdblink::tree::Tree Tree@endlink and @vdblink::math::Transform Transform@endlink objects (and @vdblink::Grid Grid@endlink objects in the context of Houdini SOPs) are passed and accessed primarily by reference rather than by shared pointer. (This is partly for performance reasons; the overhead of copying shared pointers, especially in a threaded environment, can be significant.) Furthermore, those objects now exhibit copy-on-write semantics, so that in most cases it is no longer necessary to make explicit deep copies. These changes were, for the most part, requested and implemented by Side Effects. Accessor methods like @vdblink::Grid::tree() Grid::tree@endlink, @vdblink::Grid::transform() Grid::transform@endlink and @c GEO_PrimVDB::getGrid that used to return shared pointers now return const references. Variants like @c Grid::constTree, which returned const shared pointers, have been removed, and new read/write accessors have been added. The latter, including @c Grid::treeRW(), @c Grid::transformRW() and @c GEO_PrimVDB::getGridRW, return non-const references, but they also ensure that ownership of the returned object is exclusive to the container (that is, they ensure that the grid has exclusive ownership of the tree or transform and that the primitive has exclusive ownership of the grid). The logic is as follows: if a grid, for example, has sole ownership of a tree, then @c Grid::treeRW() returns a non-const reference to that tree; but if the tree is shared (with other grids, perhaps), then @c Grid::treeRW() assigns the grid a new, deep copy of the tree and returns a non-const reference to the new tree. Shared pointers to @c Tree, @c Transform and @c Grid objects can still be requested from their respective containers via @c Grid::sharedTree(), @c Grid::sharedTransform() and @c GEO_PrimVDB::getSharedGrid, but their use is now discouraged. For Houdini SOPs, there are additional changes. First, VDB primitives are now normally processed in-place. That is, rather than extract a primitive's grid, make a deep copy of it and construct a new primitive to hold the copy, one now requests read/write access to a primitive's grid and then modifies the resulting grid. (SOPs that generate primitives or that replace grids of one type with another type can still do so using the old approach, however.) Second, grid metadata such as a grid's class (level set, fog volume, etc.), value type (@c float, @c vec3s, etc.), background value and so on (the full list is hardcoded into @c GEO_PrimVDB) is now exposed via "intrinsic" attributes of grid primitives, rather than via primitive attributes as before. As a result, this information no longer appears in a SOP's geometry spreadsheet, nor does any extra metadata that a SOP might add to a grid during processing. The metadata is still preserved in the @c Grid objects, though. Third, @c openvdb_houdini::processTypedGrid, which passes a shared grid pointer to a functor that also takes a shared pointer, is now deprecated in favor of @c openvdb_houdini::UTvdbProcessTypedGrid, which accepts shared pointers, raw pointers or references to grids (provided that the functor accepts an argument of the same kind). Below is a comparison of the old and new usage in the typical case of a SOP that processes input grids: Old API (0.97.0 and earlier) @code struct MyGridProcessor { template void operator()(typename GridT::Ptr grid) const { // Process the grid's tree. (1) grid->tree()->pruneInactive(); } }; SOP_OpenVDB_Example::cookMySop(OP_Context& context) { ... duplicateSource(0, context); // Process each VDB primitive that belongs to the selected group. for (openvdb_houdini::VdbPrimIterator it(gdp, group); it; ++it) { openvdb_houdini::GU_PrimVDB* vdbPrim = *it; // Deep copy the primitive's grid if it is going to be modified. openvdb_houdini::GridPtr grid = vdbPrim->getGrid()->deepCopyGrid(); // Otherwise, retrieve a read-only grid pointer. //openvdb_houdini::GridCPtr grid = vdbPrim->getGrid(); // Process the grid. MyGridProcessor proc; (2) openvdb_houdini::processTypedGrid(grid, proc); // Create a new VDB primitive that contains the modified grid, // and in the output detail replace the original primitive with // the new one. (3) openvdb_houdini::replaceVdbPrimitive(*gdp, grid, *vdbPrim); } } @endcode New API (0.98.0 and later) @code struct MyGridProcessor { template void operator()(GridT& grid) const { // Request write access to the grid's tree, then process the tree. (1) grid.treeRW().pruneInactive(); } }; SOP_OpenVDB_Example::cookMySop(OP_Context& context) { ... duplicateSource(0, context); // Process each VDB primitive that belongs to the selected group. for (openvdb_houdini::VdbPrimIterator it(gdp, group); it; ++it) { openvdb_houdini::GU_PrimVDB* vdbPrim = *it; // Get write access to the grid associated with the primitive. // If the grid is shared with other primitives, this will // make a deep copy of it. openvdb_houdini::Grid& grid = vdbPrim->getGridRW(); // If the grid is not going to be modified, get read-only access. //const openvdb_houdini::Grid& grid = vdbPrim->getGrid(); // Process the grid. MyGridProcessor proc; (2) openvdb_houdini::UTvdbProcessTypedGrid( (4) vdbPrim->getStorageType(), grid, proc); } } @endcode @b Notes
  1. In the old API, @vdblink::Grid::tree() Grid::tree@endlink returned either a shared, non-const pointer to a tree or a shared const pointer, depending on whether the grid itself was non-const or const. In the new API, @vdblink::Grid::tree() Grid::tree@endlink always returns a const reference, and @c Grid::treeRW() always returns a non-const reference.
  2. @c openvdb_houdini::processTypedGrid (old API) accepts only shared pointers to grids (or shared pointers to const grids), together with functors that accept shared pointers to grids. @c openvdb_houdini::UTvdbProcessTypedGrid (new API) accepts const and non-const references, shared pointers and raw pointers, together with matching functors. That is, all of the following are valid pairs of grid and functor arguments to @c UTvdbProcessTypedGrid(): @code openvdb_houdini::Grid& grid = vdbPrim->getGridRW(); struct MyProc { template operator()(GridT&) {...} }; const openvdb_houdini::Grid& grid = vdbPrim->getGrid(); struct MyProc { template operator()(const GridT&) {...} }; openvdb_houdini::GridPtr grid = vdbPrim->getSharedGrid(); struct MyProc { template operator()(typename GridT::Ptr) {...} }; openvdb_houdini::GridCPtr grid = vdbPrim->getSharedConstGrid(); struct MyProc { template operator()(typename GridT::ConstPtr) {...} }; openvdb_houdini::Grid* grid = &vdbPrim->getGridRW(); struct MyProc { template operator()(GridT*) {...} }; const openvdb_houdini::Grid* grid = &vdbPrim->getGrid(); struct MyProc { template operator()(const GridT*) {...} }; @endcode
  3. In the old API, input grid primitives were (usually) deleted after processing, and a new primitive was created for each output grid. @c openvdb_houdini::replaceVdbPrimitive() did both operations in one step and had the side effect of transferring the output grid's metadata to primitive attributes. In the new API, @c replaceVdbPrimitive() is rarely needed, because input grids can usually be processed in-place, and most commonly-used metadata is exposed via intrinsic attributes, which don't need to be manually updated.
  4. The first argument to @c openvdb_houdini::UTvdbProcessTypedGrid() is the grid's storage type, which can be retrieved either by passing the grid to @c openvdb_houdini::UTvdbGetGridType() or by calling @c GEO_PrimVDB::getStorageType() on the primitive.
*/ openvdb/doc/doc.txt0000644000000000000000000006423112603226304013270 0ustar rootroot/** @mainpage OpenVDB The @b OpenVDB library comprises a hierarchical data structure and a suite of tools for the efficient manipulation of sparse, possibly time-varying, volumetric data discretized on a three-dimensional grid. It is based on VDB, which was developed by Ken Museth at DreamWorks Animation, and it offers an effectively infinite 3D index space, compact storage (both in memory and on disk), fast data access (both random and sequential), and a collection of algorithms specifically optimized for the data structure for common tasks such as filtering, CSG, compositing, sampling and voxelization from other geometric representations. The technical details of VDB are described in the paper “VDB: High-Resolution Sparse Volumes with Dynamic Topology”. @b OpenVDB is maintained by DreamWorks Animation and was developed primarily by - Ken Museth - Peter Cucka - Mihai Aldén - David Hill See the @subpage overview "Overview" for an introduction to the library. See @subpage transformsAndMaps "Transforms and Maps" for more discussion of the transforms used in @b OpenVDB. See the @subpage faq "FAQ" for frequently asked questions about @b OpenVDB. See the @subpage codeExamples "Cookbook" to get started using @b OpenVDB. See @subpage python "Using OpenVDB in Python" to get started with the @b OpenVDB Python module. See the @subpage changes "Release Notes" for what's new in this version of @b OpenVDB. Contributors, please familiarize yourselves with our @subpage codingStyle "coding standards". @page overview OpenVDB Overview @section Contents - @ref secOverview - @ref secTree - @ref subsecTreeConfig - @ref secSparsity - @ref subsecValues - @ref subsecInactive - @ref secSpaceAndTrans - @ref subsecVoxSpace - @ref subsecWorSpace - @ref subsecTrans - @ref 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 */ openvdb/doc/codingstyle.txt0000644000000000000000000002724212603226304015050 0ustar rootroot/** @page codingStyle Coding Style @section Introduction This document details the coding practices that are used in the OpenVDB codebase. Contributed code should conform to these guidelines to maintain consistency and maintainability. If there is a rule that you would like clarified, changed, or added, please send a note to openvdb@gmail.com. @section sStyleContents Contents - @ref sNamingConventions - @ref sNamespaceConventions - @ref sClassConventions - @ref sClassMethods - @ref sClassInstanceVariables - @ref sClassStaticVariables - @ref sLocalVariablesAndArguments - @ref sConstants - @ref sEnumerationNames - @ref sEnumerationValues - @ref sTypedefs - @ref sGlobalVariables - @ref sGlobalFunctions - @ref sBooleans - @ref sPractices - @ref sGeneral - @ref sFormatting - @ref sIncludeStatements - @ref sComments - @ref sPrimitiveTypes - @ref sMacros - @ref sClasses - @ref sConditionalStatements - @ref sNamespaces - @ref sExceptions - @ref sTemplates - @ref sMiscellaneous @section sNamingConventions Naming Conventions @subsection sNamespaceConventions Namespaces -# Lowercase words, keep simple and short (e.g., @c tools, @c tree). @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 global static constants instead. (Local static constants are not guaranteed to be initialized in a thread-safe manner.) -# Avoid macro functions. Use @c inline and templates instead. @subsection sClasses Classes -# Constructors that can be called with only one argument should be prefixed with the @c explicit keyword to avoid unintended type conversions. -# Always list the destructor. -# Never call virtual methods from destructors. -# If you have a copy constructor, make sure you have an assignment operator. -# If you have an assignment operator, you probably need a copy constructor. -# If you have data members that are pointers to dynamically allocated memory, make sure you have a copy constructor and an assignment operator, both of which do the right thing with that memory. -# Classes which are to be subclassed always have a virtual destructor, even if it is empty. -# Check against self assignment and return *this in assignment operators. -# Declare methods as const as much as possible. -# Declare object-valued input arguments as const references wherever possible. Primitives may be passed by value, however. -# Arithmetic, logical, bitwise, dereference, and address of operators should only be used when their semantics are clear, obvious, and unambiguous. -# The application operator [ () ] is allowed for functors. -# Conversion operators should be avoided. -# Never return const references to stack allocated or implicitly computed objects. -# If a class does not have a copy constructor and/or assignment operator, consider creating a private unimplemented copy constructor and/or assignment operator to prevent automatically generated versions from being used. @subsection sConditionalStatements Conditional Statements -# For test expressions, use a form that indicates as clearly as possible the types of operands by avoiding implicit casts. -# Assignments that occur within conditional statements must have no effect in the enclosing scope. -# Allow for arithmetic imprecision when comparing floating point values. -# In switch statements, always comment fallthroughs and empty cases. @section sNamespaces Namespaces -# Namespaces should be used whenever possible. -# Avoid pulling in entire namespaces with @c using directives (e.g., using namespace std;). -# @c using declarations are allowed for individual symbols (e.g., using std::vector;), but they should never appear in a header file. -# Define global operators in the namespace of their arguments. -# Namespaces are not indented. @subsection sExceptions Exceptions -# Appropriate use of exceptions is encouraged. -# Methods should declare all exceptions they might throw using comments, but not exception specifications. -# Throw scope local exception instances, not pointers or references or globals. -# Catch exceptions by reference. -# Never allow an exception to escape from a destructor. @subsection sTemplates Templates -# Use @c typename rather than @c class when declaring template type parameters. @subsection sMiscellaneous Miscellaneous -# Don’t use pointers to run through arrays of non-primitive types. Use explicit array indexing, iterators or generic algorithms instead. -# Use C++ casts (static_cast<int>(x) or int(x)), not C casts ((int)x). -# Multiple variables of the same data type may be declared on the same line -# Library code must never deliberately terminate the application in response to an error condition. -# Avoid using malloc/free when new/delete can be used instead. -# Avoid @c goto. -# Avoid \"magic numbers\". Use named constants when necessary. -# If you use typeid/typeinfo, be aware that although all runtimes support typeinfo::name(), the format of the string it returns varies between compilers and even for a given compiler the value is not guaranteed to be unique. */ openvdb/doc/examplecode.txt0000644000000000000000000013310612603226304015007 0ustar rootroot/** @page codeExamples OpenVDB Cookbook This section provides code snippets and some complete programs that illustrate how to use OpenVDB and how to perform common tasks. @section sCookbookContents Contents - @ref sHelloWorld - @ref sCompilingHelloWorld - @ref sAllocatingGrids - @ref sPopulatingGrids - @ref sModifyingGrids - @ref sStreamIO - @ref sHandlingMetadata - @ref sAddingMetadata - @ref sGettingMetadata - @ref sRemovingMetadata - @ref sIteration - @ref sNodeIterator - @ref sLeafIterator - @ref sValueIterator - @ref sIteratorRange - @ref sInterpolation - @ref sSamplers - @ref sGridSampler - @ref sDualGridSampler - @ref sXformTools - @ref sResamplingTools - @ref sValueXformTools - @ref sCombiningGrids - @ref sCsgTools - @ref sCompTools - @ref sCombineTools - @ref sGenericProg - @ref sTypedGridMethods @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. @subsection sIteratorRange Iterator Range A @vdblink::tree::IteratorRange tree::IteratorRange@endlink wraps any grid or tree iterator and gives the iterator TBB splittable range semantics, so that it can be used as the Range argument to functions like @c tbb::parallel_for() and @c tbb::parallel_reduce(). (This is in fact how @vdblink::tools::foreach() tools::foreach()@endlink and @vdblink::tools::transformValues() tools::transformValues()@endlink are implemented; see @ref sValueXformTools, below, for more on those functions.) There is some overhead to splitting, since grid and tree iterators are not random-access, but the overhead should typically be negligible compared with the amount of work done per subrange. The following is a complete program that uses @vdblink::tree::IteratorRange tree::IteratorRange@endlink. The program iterates in parallel over the leaf nodes of a tree (by splitting the iteration range of a @vdblink::tree::Tree::LeafCIter Tree::LeafCIter@endlink) and computes the total number of active leaf-level voxels by incrementing a global, thread-safe counter. @code #include #include #include #include #include #include // Global active voxel counter, atomically updated from multiple threads tbb::atomic activeLeafVoxelCount; // Functor for use with tbb::parallel_for() that operates on a grid's leaf nodes template struct LeafProcessor { typedef typename GridType::TreeType TreeType; typedef typename TreeType::LeafNodeType LeafNode; // Define an IteratorRange that splits the iteration space of a leaf iterator. typedef openvdb::tree::IteratorRange IterRange; void operator()(IterRange& range) const { // Note: this code must be thread-safe. // Iterate over a subrange of the leaf iterator's iteration space. for ( ; range; ++range) { // Retrieve the leaf node to which the iterator is pointing. const LeafNode& leaf = *range.iterator(); // Update the global counter. activeLeafVoxelCount.fetch_and_add(leaf.onVoxelCount()); } } }; int main() { openvdb::initialize(); // Generate a level set grid. openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere(/*radius=*/20.0, /*center=*/openvdb::Vec3f(1.5, 2, 3), /*voxel size=*/0.5); // Construct a functor for use with tbb::parallel_for() // that processes the leaf nodes of a FloatGrid. typedef LeafProcessor FloatLeafProc; FloatLeafProc proc; // Wrap a leaf iterator in an IteratorRange. FloatLeafProc::IterRange range(grid->tree().cbeginLeaf()); // Iterate over leaf nodes in parallel. tbb::parallel_for(range, proc); std::cout << activeLeafVoxelCount << " active leaf voxels" << std::endl; // The computed voxel count should equal the grid's active voxel count, // since all of the active voxels in a level set grid are stored at the // leaf level (that is, there are no active tiles in a level set grid). assert(activeLeafVoxelCount == grid->activeVoxelCount()); } @endcode @section sInterpolation Interpolation of grid values Applications such as rendering require evaluation of grids at arbitrary, fractional coordinates in either index or world space. This is achieved, of course, by interpolating between known grid values at neighboring whole-voxel locations, that is, at integer coordinates in index space. The following sections introduce OpenVDB’s various interpolation schemes as well as the @ref sGridSampler and @ref sDualGridSampler classes for efficient, continuous sampling of grids. In most cases, GridSampler is the preferred interface for interpolation, but note that when a fixed transform is to be applied to all values in a grid (that is, the grid is to be resampled), it is both easier and more efficient to use the multithreaded @vdblink::tools::GridTransformer GridTransformer@endlink class, introduced in @ref sXformTools. @subsection sSamplers Index-space samplers OpenVDB offers low-level zero-, first- and second-order interpolators @vdblink::tools::PointSampler PointSampler@endlink, @vdblink::tools::BoxSampler BoxSampler@endlink and @vdblink::tools::QuadraticSampler QuadraticSampler@endlink, in addition to the variants @vdblink::tools::StaggeredPointSampler StaggeredPointSampler@endlink, @vdblink::tools::StaggeredBoxSampler StaggeredBoxSampler@endlink and @vdblink::tools::StaggeredQuadraticSampler StaggeredQuadraticSampler@endlink for @ref sStaggered "staggered" velocity grids. @code #include #include const GridType grid = ...; // Choose fractional coordinates in index space. const openvdb::Vec3R ijk(10.5, -100.2, 50.3); // Compute the value of the grid at ijk via nearest-neighbor (zero-order) // interpolation. GridType::ValueType v0 = openvdb::tools::PointSampler::sample(grid.tree(), ijk); // Compute the value via trilinear (first-order) interpolation. GridType::ValueType v1 = openvdb::tools::BoxSampler::sample(grid.tree(), ijk); // Compute the value via triquadratic (second-order) interpolation. GridType::ValueType v2 = openvdb::tools::QuadraticSampler::sample(grid.tree(), ijk); @endcode These examples invoke the @vdblink::tree::Tree::getValue() getValue@endlink method on the grid’s tree to fetch sample values in the neighborhood of @ijk. Accessing values via the tree is thread-safe due to the lack of caching, but for that reason it is also suboptimal. For better performance, use @ref subsecValueAccessor "value accessors" (but be careful to use one accessor per computational thread): @code GridType::ConstAccessor accessor = grid.getConstAccessor(); GridType::ValueType v0 = openvdb::tools::PointSampler::sample(accessor, ijk); GridType::ValueType v1 = openvdb::tools::BoxSampler::sample(accessor, ijk); GridType::ValueType v2 = openvdb::tools::QuadraticSampler::sample(accessor, ijk); @endcode Another issue with these low-level interpolators is that they operate only in index space. To interpolate in world space, use the higher-level classes discussed below. @subsection sGridSampler Grid Sampler The @vdblink::tools::GridSampler GridSampler@endlink class allows for continuous sampling in both world space and index space and can be used with grids, trees or value accessors. @code #include #include const GridType grid = ...; // Instantiate the GridSampler template on the grid type and on a box sampler // for thread-safe but uncached trilinear interpolation. openvdb::tools::GridSampler sampler(grid); // Compute the value of the grid at fractional coordinates in index space. GridType::ValueType indexValue = sampler.isSample(openvdb::Vec3R(10.5, -100.2, 50.3)); // Compute the value of the grid at a location in world space. GridType::ValueType worldValue = sampler.wsSample(openvdb::Vec3R(0.25, 1.4, -1.1)); // Request a value accessor for accelerated access. // (Because value accessors employ a cache, it is important to declare // one accessor per thread.) GridType::ConstAccessor accessor = grid.getConstAccessor(); // Instantiate the GridSampler template on the accessor type and on // a box sampler for accelerated trilinear interpolation. openvdb::tools::GridSampler fastSampler(accessor, grid.transform()); // Compute the value of the grid at fractional coordinates in index space. indexValue = fastSampler.isSample(openvdb::Vec3R(10.5, -100.2, 50.3)); // Compute the value of the grid at a location in world space. worldValue = fastSampler.wsSample(openvdb::Vec3R(0.25, 1.4, -1.1)); @endcode Note that when constructing a GridSampler with either a tree or a value accessor, you must also supply an index-to-world transform. When constructing a GridSampler with a grid, the grid's transform is used automatically. @subsection sDualGridSampler Dual Grid Sampler It might sometimes be necessary to interpolate values from a source grid into the index space of a target grid. If this transformation is to be applied to all of the values in the source grid, then it is best to use the tools in GridTransformer.h. For other cases, consider using the @vdblink::tools::DualGridSampler DualGridSampler@endlink class. Like the GridSampler class, this class can be used with grids, trees or value accessors. In addition, DualGridSampler checks if the source and target grids are aligned (that is, they have the same transform), in which case it avoids unnecessary interpolation. @code #include #include const GridType sourceGrid = ...; // Instantiate the DualGridSampler template on the grid type and on // a box sampler for thread-safe but uncached trilinear interpolation. openvdb::tools::DualGridSampler sampler(sourceGrid, targetGrid.constTransform()); // Compute the value of the source grid at a location in the // target grid's index space. GridType::ValueType value = sampler(openvdb::Coord(-23, -50, 202)); // Request a value accessor for accelerated access to the source grid. // (Because value accessors employ a cache, it is important to declare // one accessor per thread.) GridType::ConstAccessor accessor = sourceGrid.getConstAccessor(); // Instantiate the DualGridSampler template on the accessor type and on // a box sampler for accelerated trilinear interpolation. openvdb::tools::DualGridSampler fastSampler(accessor, sourceGrid.constTransform(), targetGrid.constTransform()); // Compute the value of the source grid at a location in the // target grid's index space. value = fastSampler(openvdb::Coord(-23, -50, 202)); @endcode Note that interpolation is done by invoking a DualGridSampler as a functor, in contrast to the more general-purpose GridSampler. @section sXformTools Transforming grids @subsection sResamplingTools Geometric transformation A @vdblink::tools::GridTransformer GridTransformer@endlink applies a geometric transformation to an input grid using one of several sampling schemes, and stores the result in an output grid. The operation is multithreaded by default, though threading can be disabled by calling @vdblink::tools::GridTransformer::setThreaded() setThreaded(false)@endlink. A @c GridTransformer object can be reused to apply the same transformation to multiple input grids, optionally using different sampling schemes. @code #include #include openvdb::FloatGrid::Ptr sourceGrid = ... targetGrid = ...; // Get the source and target grids' index space to world space transforms. const openvdb::math::Transform &sourceXform = sourceGrid->transform(), &targetXform = targetGrid->transform(); // Compute a source grid to target grid transform. // (For this example, we assume that both grids' transforms are linear, // so that they can be represented as 4 x 4 matrices.) openvdb::Mat4R xform = sourceXform.baseMap()->getAffineMap()->getMat4() * targetXform.baseMap()->getAffineMap()->getMat4().inverse(); // Create the transformer. openvdb::tools::GridTransformer transformer(xform); // Resample using nearest-neighbor interpolation. transformer.transformGrid( *sourceGrid, *targetGrid); // Resample using trilinear interpolation. transformer.transformGrid( *sourceGrid, *targetGrid); // Resample using triquadratic interpolation. transformer.transformGrid( *sourceGrid, *targetGrid); // Prune the target tree for optimal sparsity. targetGrid->tree().prune(); @endcode @subsection sValueXformTools Value transformation This example uses @vdblink::tools::foreach() tools::foreach()@endlink to multiply all values (both tile and voxel and both active and inactive) of a scalar, floating-point grid by two: @code #include #include // Define a local function that doubles the value to which the given // value iterator points. struct Local { static inline void op(const openvdb::FloatGrid::ValueAllIter& iter) { iter.setValue(*iter * 2); } }; openvdb::FloatGrid::Ptr grid = ...; // Apply the function to all values. openvdb::tools::foreach(grid->beginValueAll(), Local::op); @endcode This example uses @vdblink::tools::foreach() tools::foreach()@endlink to rotate all active vectors of a vector-valued grid by 45 degrees about the @em y axis: @code #include #include // Define a functor that multiplies the vector to which the given // value iterator points by a fixed matrix. struct MatMul { openvdb::math::Mat3s M; MatMul(const openvdb::math::Mat3s& mat): M(mat) {} inline void operator()(const openvdb::Vec3SGrid::ValueOnIter& iter) const { iter.setValue(M.transform(*iter)); } }; openvdb::Vec3SGrid::Ptr grid = ...; // Construct the rotation matrix. openvdb::math::Mat3s rot45 = openvdb::math::rotation(openvdb::math::Y_AXIS, M_PI_4); // Apply the functor to all active values. openvdb::tools::foreach(grid->beginValueOn(), MatMul(rot45)); @endcode @vdblink::tools::transformValues() tools::transformValues()@endlink is similar to @vdblink::tools::foreach() tools::foreach()@endlink, but it populates an output grid with transformed values from an input grid that may have a different value type. The following example populates a scalar, floating-point grid with the lengths of all active vectors from a vector-valued grid (like @vdblink::tools::magnitude() tools::magnitude()@endlink): @code #include #include // Define a local function that, given an iterator pointing to a vector value // in an input grid, sets the corresponding tile or voxel in a scalar, // floating-point output grid to the length of the vector. struct Local { static inline void op( const openvdb::Vec3SGrid::ValueOnCIter& iter, openvdb::FloatGrid::ValueAccessor& accessor) { if (iter.isVoxelValue()) { // set a single voxel accessor.setValue(iter.getCoord(), iter->length()); } else { // fill an entire tile openvdb::CoordBBox bbox; iter.getBoundingBox(bbox); accessor.getTree().fill(bbox, iter->length()); } } }; openvdb::Vec3SGrid::ConstPtr inGrid = ...; // Create a scalar grid to hold the transformed values. openvdb::FloatGrid::Ptr outGrid = openvdb::FloatGrid::create(); // Populate the output grid with transformed values. openvdb::tools::transformValues(inGrid->cbeginValueOn(), *outGrid, Local::op); @endcode @section sCombiningGrids Combining grids The following examples show various ways in which a pair of grids can be combined in @ref subsecVoxSpace "index space". The assumption is that index coordinates @ijk in both grids correspond to the same physical, @ref subsecWorSpace "world space" location. When the grids have different transforms, it is usually necessary to first @ref sResamplingTools "resample" one grid into the other grid's @ref subsecVoxSpace "index space". @subsection sCsgTools Level set CSG operations The level set CSG functions in tools/Composite.h operate on pairs of grids of the same type, using sparse traversal for efficiency. These operations always leave the second grid empty. @code #include #include // Two grids of the same type containing level set volumes openvdb::FloatGrid::Ptr gridA(...), gridB(...); // Save copies of the two grids; CSG operations always modify // the A grid and leave the B grid empty. openvdb::FloatGrid::ConstPtr copyOfGridA = gridA->deepCopy(), copyOfGridB = gridB->deepCopy(); // Compute the union (A u B) of the two level sets. openvdb::tools::csgUnion(*gridA, *gridB); // Restore the original level sets. gridA = copyOfGridA->deepCopy(); gridB = copyOfGridB->deepCopy(); // Compute the intersection (A n B) of the two level sets. openvdb::tools::csgIntersection(gridA, gridB); // Restore the original level sets. gridA = copyOfGridA->deepCopy(); gridB = copyOfGridB->deepCopy(); // Compute the difference (A / B) of the two level sets. openvdb::tools::csgDifference(gridA, gridB); @endcode @subsection sCompTools Compositing operations Like the @ref sCsgTools "CSG operations", the compositing functions in tools/Composite.h operate on pairs of grids of the same type, and they always leave the second grid empty. @code #include #include // Two grids of the same type openvdb::FloatGrid::Ptr gridA = ..., gridB = ...; // Save copies of the two grids; compositing operations always // modify the A grid and leave the B grid empty. openvdb::FloatGrid::ConstPtr copyOfGridA = gridA->deepCopy(), copyOfGridB = gridB->deepCopy(); // At each voxel, compute a = max(a, b). openvdb::tools::compMax(*gridA, *gridB); // Restore the original grids. gridA = copyOfGridA->deepCopy(); gridB = copyOfGridB->deepCopy(); // At each voxel, compute a = min(a, b). openvdb::tools::compMin(*gridA, *gridB); // Restore the original grids. gridA = copyOfGridA->deepCopy(); gridB = copyOfGridB->deepCopy(); // At each voxel, compute a = a + b. openvdb::tools::compSum(*gridA, *gridB); // Restore the original grids. gridA = copyOfGridA->deepCopy(); gridB = copyOfGridB->deepCopy(); // At each voxel, compute a = a * b. openvdb::tools::compMul(*gridA, *gridB); @endcode @subsection sCombineTools Generic combination The @vdblink::tree::Tree::combine() Tree::combine()@endlink family of methods apply a user-supplied operator to pairs of corresponding values of two trees. These methods are efficient because they take into account the sparsity of the trees; they are not multithreaded, however. This example uses the @vdblink::tree::Tree::combine() Tree::combine()@endlink method to compute the difference between corresponding voxels of two floating-point grids: @code #include // Define a local function that subtracts two floating-point values. struct Local { static inline void diff(const float& a, const float& b, float& result) { result = a - b; } }; openvdb::FloatGrid::Ptr aGrid = ..., bGrid = ...; // Compute the difference between corresponding voxels of aGrid and bGrid // and store the result in aGrid, leaving bGrid empty. aGrid->tree().combine(bGrid->tree(), Local::diff); @endcode Another @vdblink::tree::Tree::combine() Tree::combine()@endlink example, this time using a functor to preserve state: @code #include // Define a functor that computes f * a + (1 - f) * b for pairs of // floating-point values a and b. struct Blend { Blend(float f): frac(f) {} inline void operator()(const float& a, const float& b, float& result) const { result = frac * a + (1.0 - frac) * b; } float frac; }; openvdb::FloatGrid::Ptr aGrid = ..., bGrid = ...; // Compute a = 0.25 * a + 0.75 * b for all corresponding voxels of // aGrid and bGrid. Store the result in aGrid and empty bGrid. aGrid->tree().combine(bGrid->tree(), Blend(0.25)); @endcode The @vdblink::tree::Tree::combineExtended() Tree::combineExtended()@endlink method invokes a function of the form void f(CombineArgs\& args), where the @vdblink::CombineArgs CombineArgs@endlink object encapsulates an @em a and a @em b value and their active states as well as a result value and its active state. In the following example, voxel values in floating-point @c aGrid are replaced with corresponding values from floating-point @c bGrid (leaving @c bGrid empty) wherever the @em b values are larger. The active states of any transferred values are preserved. @code #include // Define a local function that retrieves a and b values from a CombineArgs // struct and then sets the result member to the maximum of a and b. struct Local { static inline void max(CombineArgs& args) { if (args.b() > args.a()) { // Transfer the B value and its active state. args.setResult(args.b()); args.setResultIsActive(args.bIsActive()); } else { // Preserve the A value and its active state. args.setResult(args.a()); args.setResultIsActive(args.aIsActive()); } } }; openvdb::FloatGrid::Ptr aGrid = ..., bGrid = ...; aGrid->tree().combineExtended(bGrid->tree(), Local::max); @endcode Like @c combine(), @vdblink::tree::Tree::combine2() Tree::combine2()@endlink applies an operation to pairs of corresponding values of two trees. However, @c combine2() writes the result to a third, output tree and does not modify either of the two input trees. (As a result, it is less space-efficient than the @c combine() method.) Here, the voxel differencing example above is repeated using @c combine2(): @code #include struct Local { static inline void diff(const float& a, const float& b, float& result) { result = a - b; } }; openvdb::FloatGrid::ConstPtr aGrid = ..., bGrid = ...; openvdb::FloatGrid::Ptr resultGrid = openvdb::FloatGrid::create(); // Combine aGrid and bGrid and write the result into resultGrid. // The input grids are not modified. resultGrid->tree().combine2(aGrid->tree(), bGrid->tree(), Local::diff); @endcode An @vdblink::tree::Tree::combine2Extended() extended combine2()@endlink is also available. @section sGenericProg Generic programming @subsection sTypedGridMethods Calling Grid methods A common task is to perform some operation on all of the grids in a file, where the operation involves @vdblink::Grid Grid@endlink method calls and the grids are of different types. Only a handful of @c Grid methods, such as @vdblink::Grid::activeVoxelCount() activeVoxelCount()@endlink, are virtual and can be called through a @vdblink::GridBase GridBase@endlink pointer; most are not, because they require knowledge of the Grid's value type. For example, one might want to @vdblink::tree::Tree::prune() prune()@endlink the trees of all of the grids in a file regardless of their type, but @c Tree::prune() is non-virtual because it accepts an optional pruning tolerance argument whose type is the grid's value type. The @c processTypedGrid() function below makes this kind of task easier. It is called with a @c GridBase pointer and a functor whose call operator accepts a pointer to a @c Grid of arbitrary type. The call operator should be templated on the grid type and, if necessary, overloaded for specific grid types. @code template void processTypedGrid(openvdb::GridBase::Ptr grid, OpType& op) { #define CALL_OP(GridType) \ op.template operator()(openvdb::gridPtrCast(grid)) if (grid->isType()) CALL_OP(openvdb::BoolGrid); else if (grid->isType()) CALL_OP(openvdb::FloatGrid); else if (grid->isType()) CALL_OP(openvdb::DoubleGrid); else if (grid->isType()) CALL_OP(openvdb::Int32Grid); else if (grid->isType()) CALL_OP(openvdb::Int64Grid); else if (grid->isType()) CALL_OP(openvdb::Vec3IGrid); else if (grid->isType()) CALL_OP(openvdb::Vec3SGrid); else if (grid->isType()) CALL_OP(openvdb::Vec3DGrid); else if (grid->isType()) CALL_OP(openvdb::StringGrid); #undef CALL_OP } @endcode The following example shows how to use @c processTypedGrid() to implement a generic pruning operation for grids of all built-in types: @code #include // Define a functor that prunes the trees of grids of arbitrary type // with a fixed pruning tolerance. struct PruneOp { double tolerance; PruneOp(double t): tolerance(t) {} template void operator()(typename GridType::Ptr grid) const { grid->tree().prune(typename GridType::ValueType(tolerance)); } // Overload to handle string-valued grids void operator()(openvdb::StringGrid::Ptr grid) const { grid->tree().prune(); } }; // Read all grids from a file. openvdb::io::File file("mygrids.vdb"); file.open(); openvdb::GridPtrVecPtr myGrids = file.getGrids(); file.close(); // Prune each grid with a tolerance of 1%. const PruneOp pruner(/*tolerance=*/0.01); for (openvdb::GridPtrVecIter iter = myGrids->begin(); iter != myGrids->end(); ++iter) { openvdb::GridBase::Ptr grid = *iter; processTypedGrid(grid, pruner); } @endcode */ openvdb/doc/changes.txt0000644000000000000000000025412212603226304014133 0ustar rootroot/** @page changes Release Notes @htmlonly @endhtmlonly @par Version 3.1.0 - October 1, 2015 @par Highlights: - New features: advection of arbitrary volumes, general-purpose preconditioned linear solver and Poisson solver, segmentation of topologically-enclosed regions of a volume, new and faster bitmask operators, concurrent paged array, volume diagnostics - Optimizations: threaded grid constructors and topology operations; faster mesh to volume conversion, SDF to fog volume conversion and grid pruning; faster, unbounded particle partitioning - New Houdini nodes: Advect, Diagnostics, Rasterize Points, Remap, Remove Divergence, Sort Points @par New features: - Added a @vdblink::tools::VolumeAdvection volume advection@endlink tool for sparse advection of non-level-set volumes. - Added a preconditioned @vdblink::math::pcg::solve() conjugate gradient solver@endlink. - Added a @vdblink::tools::poisson::solve() Poisson solver@endlink for functions sampled on grids. - Added @vdblink::tools::extractEnclosedRegion extractEnclosedRegion@endlink, which detects topologically-enclosed (watertight) exterior regions (cavities) that can result from CSG union operations between level sets with concavities that are capped. (See the unit test @c TestPoissonSolver::testSolveWithSegmentDomain for an example in which this tool is used to identify regions of trapped fluid when solving for pressure in a volume of incompressible fluid.) - Added @vdblink::util::PagedArray PagedArray@endlink, a concurrent, dynamic linear array data structure with fast O(1) value access (both random and sequential). - Added @vdblink::tools::Sampler Sampler@endlink, which provides a unified API for both staggered and non-staggered interpolation of various orders. - Added equality and inequality operators to @vdblink::Metadata Metadata@endlink and @vdblink::MetaMap MetaMap@endlink. - Added @vdblink::tools::CheckLevelSet CheckLevelSet@endlink and @vdblink::tools::CheckFogVolume CheckFogVolume@endlink tools that perform various tests on symmetric, narrow-band level sets and fog volumes, respectively, to diagnose potential issues. - Added support for value accessors that are not registered with their trees. (Bypassing accessor registration can improve performance in rare cases but should be used with caution, since the accessor will be left in an invalid state if the tree topology is modified.) - Added a @vdblink::tree::Tree::stealNodes() stealNodes@endlink method that transfers ownership of all nodes in a tree of a certain type and inserts them into a linear array. - Added a @vdblink::tools::createLevelSetBox() tools::createLevelSetBox@endlink factory function for level-set grids. - Added @vdblink::tools::Dense::offsetToCoord() Dense::offsetToCoord@endlink. - Added @vdblink::tree::LeafNode::Buffer::data() LeafNode::Buffer::data@endlink, which provides direct access to a leaf node’s voxel value array, avoiding out-of-core overhead. Use with caution. - Added a @vdblink::util::NodeMask::foreach() NodeMask::foreach@endlink method for efficient evaluation of complex bitwise operations. - Added a bitwise difference method to @vdblink::util::NodeMask::operator-=() NodeMask@endlink. - Added a @c -version option to @c vdb_print, @c vdb_render and @c vdb_view. @par Improvements: - Deep, conversion and topology copy @vdblink::Grid Grid@endlink constructors are now threaded and up to five times faster. - @vdblink::Grid::topologyUnion() Grid::topologyUnion@endlink, @vdblink::Grid::topologyIntersection() Grid::topologyIntersection@endlink, and @vdblink::Grid::topologyDifference() Grid::topologyDifference@endlink are now much faster due to threading. - Significantly improved the performance, parallel scaling and memory usage of the @vdblink::tools::meshToVolume() mesh to volume@endlink converter, and implemented a more robust inside/outside sign classification scheme. - Reimplemented the @vdblink::tools::PointPartitioner point partitioning@endlink tool for improved performance, concurrency and memory usage. The tool is now unbounded in the sense that points may be distributed anywhere in index space. - Significantly improved the performance of the @vdblink::tools::sdfToFogVolume() SDF to fog volume@endlink converter. - Significantly improved the performance of the @vdblink::tools::sdfInteriorMask() sdfInteriorMask@endlink tool and added support for both grid and tree inputs. - Made various optimizations and improvements to the @vdblink::tools::LevelSetMorphing level set morphing@endlink tool. - Aggregated @vdblink::tools::DiscreteField DiscreteField@endlink and @vdblink::tools::EnrightField EnrightField@endlink (formerly in tools/LevelSetAdvect.h) and @vdblink::tools::VelocitySampler VelocitySampler@endlink and @vdblink::tools::VelocityIntegrator VelocityIntegrator@endlink (formerly in tools/PointAdvect.h) into a single header, tools/VelocityFields.h. - Modified the @vdblink::tools::signedFloodFill() signed flood fill@endlink tool to accept grids of any signed scalar value type, not just floating-point grids. - The @vdblink::tools::prune() prune@endlink tool is now faster, and it employs an improved compression technique on trees with floating-point values. @par Bug fixes: - Fixed a build issue that could result in spurious “Blosc encoding is not supported” errors unless @c OPENVDB_USE_BLOSC was @#defined when compiling client code. - Added NaN and inf checks to the @vdblink::tools::PointPartitioner point partitioning@endlink tool. - Fixed a vdb_view issue whereby the frame buffer size did not necessarily match the window size. [Contributed by Rafael Campos] - Fixed a roundoff issue in @vdblink::tools::LevelSetTracker LevelSetTracker@endlink that could result in NaNs. - Changed @vdblink::tools::CheckNormGrad CheckNormGrad@endlink to check the magnitude of the gradient rather than the square of the magnitude. - Fixed parameter type inconsistencies in math/Ray.h and tools/RayIntersector.h. [Contributed by Kévin Dietrich] - Fixed incorrect handling of signed values in the @vdblink::tools::clip() clip@endlink tool (and the Clip SOP). @par API changes: - Removed the math::Hermite class since it was no longer used and caused build issues for some. - Refactored the @vdblink::tools::LevelSetAdvection level set advection@endlink, @vdblink::tools::LevelSetFilter level set filtering@endlink, @vdblink::tools::LevelSetMeasure level set measuring@endlink and @vdblink::tools::LevelSetTracker level set tracking@endlink tools. - Extended the API of the @vdblink::tools::Diagnose Diagnose@endlink tool and disabled copy construction. - Extended and unified the API of various Samplers. - Added an optional template argument to the @vdblink::tree::ValueAccessor ValueAccessor@endlink class to allow for unregistered accessors. @par Houdini: - Added a Rasterize Points SOP that produces density volumes and transfers arbitrary point attributes using a weighted-average scheme. The node incorporates a VOP subnetwork for procedural modeling, and its accompanying creation script defines a default network with VEX procedures for cloud and velocity field modeling. (See the creation script file header for installation details.) - Merged the Advect Level Set SOP into a new Advect SOP that supports advection of arbitrary volumes, not just level sets. - Added a Remove Divergence SOP that eliminates divergence from a velocity field. - Added a Diagnostics SOP that can identify various problems with level sets, fog volumes and other grids. - Added a Sort Points SOP that spatially reorders a list of points so that points that are close together in space are also close together in the list. This can improve CPU cache coherency and performance for random-access operations. - Added a Remap SOP that maps voxel values in an input range to values in an output range through a user-defined transfer function. - Added an option to the Convert SOP to activate interior voxels. [Contributed by SESI] - The To Spheres SOP can now optionally output a pscale attribute. - Added openvdb_houdini::SOP_NodeVDB::duplicateSourceStealable(), which in conjunction with the Unload flag can help to minimize deep copying of grids between nodes. The Advect, Convert, Fill, Filter, Fracture, Noise, Offset Level Set, Prune, Remap, Remove Divergence, Renormalize Level Set, Resize Narrow Band, Smooth Level Set and Transform SOPs all have this optimization enabled, meaning that they can potentially steal, rather than copy, data from upstream nodes that have the Unload flag enabled. [Contributed by Double Negative] - Redesigned the UI of the Visualize SOP and added toggles to draw with or without color, to use the grid name as the attribute name for points with values, and to attach grid index coordinates to points. - Added toggles to the Filter, Rebuild Level Set, Resize Narrow Band, Smooth Level Set and To Spheres SOPs to specify units in either world space or index space. - Fixed an issue whereby grids generated by the Rebuild Level Set SOP did not always display as surfaces in the viewport. - The Metadata SOP now sets appropriate viewport visualization options when the grid class is changed. @htmlonly @endhtmlonly @par Version 3.0.0 - January 14, 2015 - The @vdblink::io::File File@endlink class now supports delayed loading of .vdb files, meaning that memory is not allocated for voxel values until the values are actually accessed. (This feature is enabled by default.) Until a grid has been fully loaded, its source .vdb file must not be modified or deleted, so for safety, @vdblink::io::File::open() File::open@endlink automatically makes private copies of source files that are smaller than a user-specified limit (see @vdblink::io::File::setCopyMaxBytes() File::setCopyMaxBytes@endlink). The limit can be set to zero to disable copying, but if it cannot be guaranteed that a file will not be modified, then it is best not to enable delayed loading for that file. - .vdb files can now optionally be compressed with the Blosc LZ4 codec. Blosc compresses almost as well as ZLIB, but it is much faster. - Added @vdblink::tools::PointPartitioner PointPartitioner@endlink, a tool for fast spatial sorting of points stored in an external array, and @link PointIndexGrid.h PointIndexGrid@endlink, an acceleration structure for fast range and nearest-neighbor searches. - Added @link NodeManager.h tree::NodeManager@endlink, which linearizes a tree to facilitate efficient multithreading across all tree levels. - Added @vdblink::tools::prune() tools::prune@endlink (and other variants), which replaces and outperforms @c Tree::prune. - Added @vdblink::tools::signedFloodFill() tools::signedFloodFill@endlink, which replaces and outperforms @c Tree::signedFloodFill. - Added @vdblink::tools::changeBackground() tools::changeBackground@endlink (and other variants), which replaces and outperforms @c Tree::setBackground(). - Added a fast but approximate narrow-band level set @vdblink::tools::LevelSetTracker::dilate() dilation@endlink method, a fast narrow-band level set @vdblink::tools::LevelSetTracker::erode() erosion@endlink method, and a @vdblink::tools::LevelSetTracker::normalize(const MaskType*) masked normalization@endlink method to @vdblink::tools::LevelSetTracker LevelSetTracker@endlink. - Added @vdblink::tools::Diagnose Diagnose@endlink, which performs multithreaded diagnostics on grids to identify issues like values that are NaNs or out-of-range. It optionally generates a boolean grid of all values that fail user-defined tests. - Added optional alpha masks to @vdblink::tools::LevelSetMorphing LevelSetMorphing@endlink. - Fixed an intermittent crash in @vdblink::tools::LevelSetMorphing LevelSetMorphing@endlink. - Added @c tools::topologyToLevelSet(), which generates a level set from the implicit boundary between active and inactive voxels in an arbitrary input grid. [DWA internal] - Improved the performance of point scattering (by orders of magnitude) and added a @vdblink::tools::DenseUniformPointScatter DenseUniformPointScatter@endlink class as well as support for fractional numbers of particles per voxel. - Improved the performance and memory footprint of the @vdblink::tools::ParticlesToLevelSet ParticlesToLevelSet@endlink tool for large numbers (tens to hundreds of millions) of particles. - Added edge-adjacent (6+12=18 neighbors) and vertex-adjacent (6+12+8=26 neighbors) dilation algorithms to @vdblink::tools::Morphology::dilateVoxels Morphology::dilateVoxels@endlink. The default dilation pattern is still face-adjacent (6 neighbors). - Added @vdblink::tree::Tree::getNodes() Tree::getNodes@endlink, which allows for fast construction of linear arrays of tree nodes for use in multithreaded code such as the @vdblink::tree::LeafManager LeafManager@endlink or @link NodeManager.h tree::NodeManager@endlink. - Added @vdblink::math::Extrema math::Extrema@endlink and @vdblink::tools::extrema() tools::extrema@endlink to efficiently compute minimum and maximum values in a grid. - Added support for material color grids to all level set @vdblink::tools::BaseShader shaders@endlink, and added an option to @c vdb_render that allows one to specify a reference grid to be used for material color lookups. - Added @vdblink::getLibraryVersionString() getLibraryVersionString@endlink and @link OPENVDB_LIBRARY_VERSION_STRING@endlink. - Modified the mesh to volume converter to always set the grid background value to the exterior narrow-band width, and added finite value checks to narrow band parameters. - @vdblink::tools::volumeToMesh() tools::volumeToMesh@endlink now compiles for all grid types but throws an exception if the input grid does not have a scalar value type. - Added a @vdblink::io::File::readGrid(const Name&, const BBoxd&) File::readGrid@endlink overload and @vdblink::GridBase::readBuffers(std::istream&, const CoordBBox&) readBuffers@endlink overloads to the grid, tree and node classes that allow one to specify a bounding box against which to clip a grid while reading it. For large grids, clipping while reading can result in significantly lower memory usage than clipping after reading. - Added @vdblink::GridBase::clipGrid() GridBase::clipGrid@endlink, which clips a grid against a world-space bounding box, and @vdblink::GridBase::clip() GridBase::clip@endlink and @vdblink::tree::Tree::clip() Tree::clip@endlink, which clip against an index-space bounding box. - Added @vdblink::tools::clip() tools::clip@endlink, which clips a grid either against a bounding box or against the active voxels of a mask grid. - @vdblink::io::File::readGridPartial() File::readGridPartial@endlink allocates the nodes of a grid’s tree as before, but it now allocates leaf nodes without data buffers. (This feature is mainly for internal use. Partially-read grids should be used with care if at all, and they should be treated as read-only.) - Grid names retrieved using a @vdblink::io::File::NameIterator File::NameIterator@endlink now always uniquely identify grids; they no longer generate ‘more than one grid named “x”’ warnings when there are multiple grids of the same name in a file (for files written starting with this version of the OpenVDB library). - Fixed a bug in @vdblink::tree::Tree::ValueOffIter Tree::ValueOffIter@endlink that could cause @vdblink::tree::TreeValueIteratorBase::setMaxDepth() depth-bounded@endlink iterators to return incorrect values. - Eliminated a recursive call in @vdblink::tree::TreeValueIteratorBase::next() TreeValueIteratorBase::next@endlink that could cause crashes on systems with a limited stack size. - Fixed memory leaks in @vdblink::tree::RootNode::topologyDifference() RootNode::topologyDifference@endlink and @vdblink::tree::RootNode::topologyIntersection() RootNode::topologyIntersection@endlink. - Fixed a memory leak in @vdblink::io::Queue io::Queue@endlink when the queue was full and a write task could not be added within the timeout interval. - Fixed a potential division by zero crash in @vdblink::tools::compDiv() tools::compDiv@endlink with integer-valued grids. - Fixed kernel normalization in the @vdblink::tools::Filter filter tool@endlink so that it is correct for integer-valued grids. - Fixed a bug in @vdblink::tree::LeafNode::Buffer::getValue() LeafNode::Buffer::getValue@endlink whereby Visual C++ would return a reference to a temporary. [Contributed by SESI] - Fixed a bug in @vdblink::tools::ParticlesToLevelSet tools::ParticlesToLevelSet@endlink related to attribute transfer when leaf nodes are produced without active values. - Added @vdblink::util::CpuTimer util::CpuTimer@endlink and removed the more simplistic @c unittest_util::CpuTimer from @c unittest/util.h. - Eliminated the use of @c getopt for command-line argument parsing in @c vdb_test. - @vdblink::initialize() openvdb::initialize@endlink now properly initializes log4cplus if it is enabled, eliminating “No appenders could be found” errors. - Fixed a bug in the @vdblink::math::QuantizedUnitVec::pack() QuantizedUnitVec::pack@endlink method that caused quantization artifacts. - Added convenience class @vdblink::tools::AlphaMask AlphaMask@endlink - Added constructors and methods to both @vdblink::math::RandInt RandInt@endlink and @vdblink::math::Rand01 Rand01@endlink to set and reset the random seed value. - Added convenience methods for @vdblink::math::Transform::indexToWorld(const BBoxd&) const transforming@endlink @vdblink::math::Transform::worldToIndex(const BBoxd&) const bounding@endlink @vdblink::math::Transform::worldToIndexCellCentered(const BBoxd&) const boxes@endlink to @vdblink::math::Transform math::Transform@endlink. - @c vdb_view is now compatible with both GLFW 2 and GLFW 3. - Made many small changes to address type conversion and other warnings reported by newer compilers like GCC 4.8 and ICC 14. - Replaced the @c HALF_INCL_DIR and @c HALF_LIB_DIR Makefile variables with @c ILMBASE_INCL_DIR and @c ILMBASE_LIB_DIR and added @c ILMBASE_LIB, to match OpenEXR’s library organization. [Contributed by Double Negative] - Eliminated most local (function-scope) static variables, because Visual C++ doesn’t guarantee thread-safe initialization of local statics. [Contributed by SESI] - Fixed a bug in @vdblink::readString() readString@endlink related to empty strings. [Contributed by Fabio Piparo] - Fixed a bug in the @vdblink::tools::VolumeToMesh VolumeToMesh@endlink simplification scheme that was creating visual artifacts. @par API changes: - The addition of a @vdblink::GridBase::readBuffers(std::istream&, const CoordBBox&) GridBase::readBuffers@endlink virtual function overload and the @vdblink::GridBase::clip() GridBase::clip@endlink @vdblink::GridBase::readNonresidentBuffers() GridBase::readNonresidentBuffers@endlink and @vdblink::tree::Tree::clipUnallocatedNodes() Tree::clipUnallocatedNodes@endlink virtual functions changes the grid ABI so that it is incompatible with earlier versions of the OpenVDB library (such as the ones in Houdini 12.5 and 13). Define the macro @c OPENVDB_2_ABI_COMPATIBLE when compiling OpenVDB to disable these changes and preserve ABI compatibility. - All @vdblink::tools::BaseShader shaders@endlink now have a template argument to specify the type of an optional material color grid, but the default type mimics the old, uniform color behavior. - Removed a deprecated @vdblink::io::Stream::write() io::Stream::write@endlink overload. - The point counts in @vdblink::tools::UniformPointScatter UniformPointScatter@endlink and @vdblink::tools::NonUniformPointScatter NonUniformPointScatter@endlink are now specified and returned as @vdblink::Index64 Index64@endlink. - @vdblink::math::RandInt RandInt@endlink has an extra template argument to specify the integer type. The @vdblink::math::RandomInt RandomInt@endlink typedef is unchanged. - @vdblink::io::readData() io::readData@endlink, @vdblink::io::HalfReader::read() io::HalfReader::read@endlink and @vdblink::io::HalfWriter::write() io::HalfWriter::write@endlink now take a @c uint32_t argument indicating the type of compression instead of a @c bool indicating whether compression is enabled. - Removed @c io::Archive::isCompressionEnabled() and @c io::Archive::setCompressionEnabled() and renamed @c io::Archive::compressionFlags() and @c io::Archive::setCompressionFlags() to @vdblink::io::Archive::compression() io::Archive::compression@endlink and @vdblink::io::Archive::setCompression() io::Archive::setCompression@endlink. - Internal and leaf node classes are now required to provide "PartialCreate" constructors that optionally bypass the allocation of voxel buffers. Leaf node classes must now also provide @vdblink::tree::LeafNode::allocate() allocate@endlink and @vdblink::tree::LeafNode::isAllocated() isAllocated@endlink methods to manage the allocation of their buffers. - Removed @c pruneInactive and @c pruneLevelSet methods from the @vdblink::tree::Tree Tree@endlink and various node classes. These methods have been replaced by the much faster pruning functions found in tools/Prune.h. - Removed @c signedFloodFill methods from the @vdblink::Grid Grid@endlink, @vdblink::tree::Tree Tree@endlink and various node classes. These methods have been replaced by the much faster functions found in tools/SignedFloodFill.h. - Removed @c Grid::setBackground() and @c Tree::setBackground() (use the faster @vdblink::tools::changeBackground() changeBackground@endlink tool instead), and removed the default argument from @vdblink::tree::RootNode::setBackground() RootNode::setBackground@endlink. @par Python: - Added grid methods @c convertToPolygons() and @c convertToQuads(), which convert volumes to meshes, and @c createLevelSetFromPolygons(), which converts meshes to volumes. NumPy is required. @par Maya: - Added an adaptive polygonal surface extraction node. @par Houdini: - Added a new Resize Narrow Band SOP that can efficiently adjust the width of a level set’s narrow band. This allows, for example, for a level set to be created quickly from points or polygons with a very narrow band that is then quickly resized to a desired width. - Fixed bugs in the Smooth Level Set and Reshape Level Set SOPs that caused them to ignore the selected discretization scheme. - Added a Morph Level Set SOP. - Added a From Points SOP to very quickly generate a level set from a point cloud, ignoring any radius attribute. [DWA internal] - Added a Voxel Scale mode to the Resample SOP. - Improved the performance and memory footprint of the From Particles SOP for large numbers (tens to hundreds of millions) of particles. - The Scatter SOP now accepts fractional numbers of particles per voxel. - Improved the performance of the Scatter SOP by more than an order of magnitude. - The Clip SOP now has a toggle to choose explicitly between a mask grid or a bounding box as the clipping region. As a consequence, the mask grid can now be unnamed. - Added the OpenVDB library version number to the Extended Operator Information for all SOPs. - SOPs are now linked with an rpath to the directory containing the OpenVDB library. - Like the native Houdini file SOP, the Read SOP now allows missing frames to be reported either as errors or as warnings. - The Read SOP now has an optional input for geometry, the bounding box of which can be used to clip grids as they are read. For large grids, clipping while reading can result in significantly lower memory usage than clipping after reading. - The From Polygons and Convert SOPs now default to using the polygon soup mesh representation, which uses less memory. @htmlonly @endhtmlonly @par Version 2.3.0 - April 23, 2014 - Added @vdblink::tools::extractSparseTree() extractSparseTree@endlink, which selectively extracts and transforms data from a dense grid to produce a sparse tree, and @vdblink::tools::extractSparseTreeWithMask() extractSparseTreeWithMask@endlink, which copies data from the index-space intersection of a sparse tree and a dense input grid. - Added copy constructors to the @vdblink::Grid::Grid(const Grid&) Grid@endlink, @vdblink::tree::Tree::Tree(const Tree&) Tree@endlink, @vdblink::tree::RootNode::RootNode(const RootNode&) RootNode@endlink, @vdblink::tree::InternalNode::InternalNode(const InternalNode&) InternalNode@endlink and @vdblink::tree::LeafNode::LeafNode(const LeafNode&) LeafNode@endlink classes, and an assignment operator overload to @vdblink::tree::RootNode::operator=(const RootNode&) RootNode@endlink, that allow the source and destination to have different value types. - Modified @vdblink::tree::Tree::combine2() Tree::combine2@endlink to permit combination of trees with different value types. - Added @vdblink::CanConvertType CanConvertType@endlink and @vdblink::tree::RootNode::SameConfiguration RootNode::SameConfiguration@endlink metafunctions, which perform compile-time tests for value type and tree type compatibility, and a @vdblink::tree::RootNode::hasCompatibleValueType() RootNode::hasCompatibleValueType@endlink method, which does runtime checking. - Added optional support for logging using log4cplus. See logging.h and the @c INSTALL file for details. - Added @vdblink::tools::VolumeRayIntersector::hits() VolumeRayIntersector::hits@endlink, which returns all the hit segments along a ray. This is generally more efficient than repeated calls to @vdblink::tools::VolumeRayIntersector::march() VolumeRayIntersector::march@endlink. - Added member class @vdblink::math::Ray::TimeSpan Ray::TimeSpan@endlink and method @vdblink::math::Ray::valid() Ray::valid@endlink, and deprecated method @vdblink::math::Ray::test() Ray::test@endlink. - Fixed a bug in @vdblink::math::VolumeHDDA VolumeHDDA@endlink that could cause rendering artifacts when a ray’s start time was zero. [Contributed by Mike Farnsworth] - Added a @vdblink::tools::compositeToDense() compositeToDense@endlink tool, which composites data from a sparse tree into a dense array, using a sparse alpha mask. Over, Add, Sub, Min, Max, Mult, and Set are supported operations. - Added a @vdblink::tools::transformDense() transformDense@endlink tool, which applies a functor to the value of each voxel of a dense grid within a given bounding box. - Improved the performance of node iterators. @par API changes: - Collected the digital differential analyzer code from math/Ray.h and tools/RayIntersector.h into a new header file, math/DDA.h. - Rewrote @vdblink::math::VolumeHDDA VolumeHDDA@endlink and made several changes to its API. (@vdblink::math::VolumeHDDA VolumeHDDA@endlink is used internally by @vdblink::tools::VolumeRayIntersector VolumeRayIntersector@endlink, whose API is unchanged.) - @vdblink::tree::Tree::combine2() Tree::combine2@endlink, @vdblink::tree::RootNode::combine2() RootNode::combine2@endlink, @vdblink::tree::InternalNode::combine2() InternalNode::combine2@endlink, @vdblink::tree::LeafNode::combine2() LeafNode::combine2@endlink and @vdblink::CombineArgs CombineArgs@endlink all now require an additional template argument, which determines the type of the other tree. - Assignment operators for @vdblink::tree::LeafManager::LeafRange::Iterator::operator=() LeafManager::LeafRange::Iterator@endlink, @vdblink::util::BaseMaskIterator::operator=() BaseMaskIterator@endlink, @vdblink::util::NodeMask::operator=() NodeMask@endlink and @vdblink::util::RootNodeMask::operator=() RootNodeMask@endlink now return references to the respective objects. - Removed a number of methods that were deprecated in version 2.0.0 or earlier. @par Houdini: - Added a Clip SOP, which does volumetric clipping. - Added an Occlusion Mask SOP, which generates a mask of the voxels inside a camera frustum that are occluded by objects in an input grid. - The Combine SOP now applies the optional signed flood fill only to level set grids, since that operation isn’t meaningful for other grids. - The Filter SOP now processes all grid types, not just scalar grids. @htmlonly @endhtmlonly @par Version 2.2.0 - February 20, 2014 - Added a simple, multithreaded @vdblink::tools::VolumeRender volume renderer@endlink, and added volume rendering support to the @c vdb_render command-line renderer. - Added an option to the @vdblink::tools::LevelSetRayIntersector LevelSetRayIntersector@endlink and to @c vdb_render to specify the isovalue of the level set. - Added methods to the @vdblink::tools::LevelSetRayIntersector LevelSetRayIntersector@endlink to return the time of intersection along a world or index ray and to return the level set isovalue. - Improved the performance of the @vdblink::tools::VolumeRayIntersector VolumeRayIntersector@endlink and added support for voxel dilation to account for interpolation kernels. - Added a @ref sInterpolation "section" to the Cookbook on interpolation using @vdblink::tools::BoxSampler BoxSampler@endlink, @vdblink::tools::GridSampler GridSampler@endlink, @vdblink::tools::DualGridSampler DualGridSampler@endlink, et al. - Added a @ref secGrid "section" to the Overview on grids and grid metadata. - Modified @vdblink::tools::DualGridSampler DualGridSampler@endlink so it is more consistent with @vdblink::tools::GridSampler GridSampler@endlink. - The @vdblink::tools::cpt() cpt@endlink, @vdblink::tools::curl() curl@endlink, @vdblink::tools::laplacian() laplacian@endlink, @vdblink::tools::meanCurvature() meanCurvature@endlink and @vdblink::tools::normalize() normalize@endlink tools now output grids with appropriate @vdblink::VecType vector types@endlink (covariant, contravariant, etc.). - Added a @vdblink::tools::transformVectors() transformVectors@endlink tool, which applies an affine transformation to the voxel values of a vector-valued grid in accordance with the grid’s @vdblink::VecType vector type@endlink and @vdblink::Grid::isInWorldSpace() world space/local space@endlink setting. - Added a @vdblink::tools::compDiv() compDiv@endlink tool, which combines grids by dividing the values of corresponding voxels. - Fixed a bug in the mean curvature computation that could produce NaNs in regions with constant values. - Added a @vdblink::Grid::topologyDifference() Grid::topologyDifference@endlink method. - Added @vdblink::math::Vec3::exp() exp@endlink and @vdblink::math::Vec3::sum() sum@endlink methods to @vdblink::math::Vec2 Vec2@endlink, @vdblink::math::Vec3 Vec3@endlink and @vdblink::math::Vec4 Vec4@endlink. - Improved the @vdblink::tools::fillWithSpheres() fillWithSpheres@endlink tool for small volumes that are just a few voxels across. - Improved the accuracy of the mesh to volume converter. - Fixed a bug in the mesh to volume converter that caused incorrect sign classifications for narrow-band level sets. - Fixed a bug in @vdblink::math::NonlinearFrustumMap::applyIJT() NonlinearFrustumMap::applyIJT@endlink that resulted in incorrect values when computing the gradient of a grid with a frustum transform. - Fixed a file I/O bug whereby some .vdb files could not be read correctly if they contained grids with more than two distinct inactive values. - Fixed an off-by-one bug in the numbering of unnamed grids in .vdb files. The first unnamed grid in a file is now retrieved using the name “[0]”, instead of “[1]”. - Fixed a build issue reported by Clang 3.2 in tools/GridOperators.h. - Fixed a memory leak in @vdblink::tools::Film Film@endlink. - Added library and file format version number constants to the Python module. - Improved convergence in the @vdblink::tools::VolumeRender volume renderer@endlink. [Contributed by Jerry Tessendorf and Mark Matthews] - Made various changes for compatibility with Houdini 13 and with C++11 compilers. [Contributed by SESI] @par API changes: - @vdblink::tools::VolumeRayIntersector::march() VolumeRayIntersector::march@endlink no longer returns an @c int to distinguish tile vs. voxel hits. Instead, it now returns @c false if no intersection is detected and @c true otherwise. Also, @e t0 and @e t1 might now correspond to the first and last hits of multiple adjacent leaf nodes and/or active tiles. - @vdblink::tools::DualGridSampler DualGridSampler@endlink is no longer templated on the target grid type, and the value accessor is now passed as an argument. - The .vdb file format has changed slightly. Tools built with older versions of OpenVDB should be recompiled to ensure that they can read files in the new format. @par Houdini: - Added topology union, intersection and difference operations to the Combine SOP. These operations combine the active voxel topologies of grids that may have different value types. - Added a Divide operation to the Combine SOP. - Added support for boolean grids to the Combine, Resample, Scatter, Prune and Visualize SOPs. - The Fill SOP now accepts a vector as the fill value, and it allows the fill region bounds to be specified either in index space (as before), in world space, or using the bounds of geometry connected to an optional new reference input. - Added a toggle to the Offset Level Set SOP to specify the offset in either world or voxel units. - Added a toggle to the Transform and Resample SOPs to apply the transform to the voxel values of vector-valued grids, in accordance with those grids’ @vdblink::VecType vector types@endlink and @vdblink::Grid::isInWorldSpace() world space/local space@endlink settings. - Added a Vector Type menu to the Vector Merge SOP. - Removed masking options from the Renormalize SOP (since masking is not supported yet). - Reimplemented the Vector Merge SOP for better performance and interruptibility and to fix a bug in the handling of tile values. @htmlonly @endhtmlonly @par Version 2.1.0 - December 12, 2013 - Added a small number of Maya nodes, primarily for conversion of geometry to and from OpenVDB volumes and for visualization of volumes. - Added an initial implementation of @vdblink::tools::LevelSetMorphing level set morphing@endlink (with improvements to follow soon). - Added @vdblink::tools::LevelSetMeasure tools::LevelSetMeasure@endlink, which efficiently computes the surface area, volume and average mean-curvature of narrow-band level sets, in both world and voxel units. Those quantities are now exposed as intrinsic attributes on the Houdini VDB primitive and can be queried using the native Measure SOP. - @vdblink::tools::Dense tools::Dense@endlink now supports the XYZ memory layout used by Houdini and Maya in addition to the ZYX layout used in OpenVDB trees. - Improved the performance of masking in the @vdblink::tools::LevelSetFilter level set filter@endlink tool and added inversion and scaling of the mask input, so that any scalar-valued volume can be used as a mask, not just volumes with a [0, 1] range. - Added optional masking to the non-level-set filters, to the grid operators (CPT, curl, divergence, gradient, Laplacian, mean curvature, magnitude, and normalize) and to the Analysis and Filter SOPs. - Added more narrow band controls to the Rebuild Level Set SOP. - Improved the accuracy of the @vdblink::tools::levelSetRebuild() level set rebuild@endlink tool. - Added @vdblink::tools::activate() tools::activate@endlink and @vdblink::tools::deactivate() tools::deactivate@endlink, which set the active states of tiles and voxels whose values are equal to or approximately equal to a given value, and added a Deactivate Background Voxels toggle to the Combine SOP. - Added @vdblink::math::BBox::applyMap() BBox::applyMap@endlink and @vdblink::math::BBox::applyInverseMap() BBox::applyInverseMap@endlink, which allow for transformation of axis-aligned bounding boxes. - Added a @vdblink::tools::PositionShader position shader@endlink to the level set ray-tracer (primarily for debugging purposes). - Added an @vdblink::io::Queue io::Queue@endlink class that manages a concurrent queue for asynchronous serialization of grids to files or streams. - Fixed a bug in @vdblink::io::Archive io::Archive@endlink whereby writing unnamed, instanced grids (i.e., grids sharing a tree) to a file rendered the file unreadable. - Fixed a bug in the @vdblink::tools::VolumeToMesh volume to mesh@endlink converter that caused it to generate invalid polygons when the zero crossing lay between active and inactive regions. - Fixed a bug in the @vdblink::tools::UniformPointScatter point scatter@endlink tool (and the Scatter SOP) whereby the last voxel always remained empty. - Fixed a bug in the Read SOP that caused grids with the same name to be renamed with a numeric suffix (e.g., “grid[1]” “grid[2]”, etc.). - Fixed some unit test failures on 64-bit Itanium machines. @par API changes: - The @vdblink::tools::Filter Filter@endlink tool is now templated on a mask grid, and threading is controlled using a grain size, for consistency with most of the other level set tools. - The @vdblink::tools::LevelSetFilter LevelSetFilter@endlink tool is now templated on a mask grid. - All shaders now take a ray direction instead of a ray. @htmlonly @endhtmlonly @par Version 2.0.0 - October 31, 2013 - Added a @ref python "Python module" with functions for basic manipulation of grids (but no tools, yet). - Added ray intersector tools for efficient, hierarchical intersection of rays with @vdblink::tools::LevelSetRayIntersector level-set@endlink and @vdblink::tools::VolumeRayIntersector generic@endlink volumes. - Added a @vdblink::math::Ray Ray@endlink class and a hierarchical @vdblink::math::DDA Digital Differential Analyzer@endlink for fast ray traversal. - Added a fully multi-threaded @vdblink::tools::LevelSetRayTracer level set ray tracer@endlink and @vdblink::tools::PerspectiveCamera camera@endlink @vdblink::tools::OrthographicCamera classes@endlink that mimic Houdini’s cameras. - Added a simple, command-line renderer (currently for level sets only). - Implemented a new meshing scheme that produces topologically robust two-manifold meshes and is twice as fast as the previous scheme. - Implemented a new, topologically robust (producing two-manifold meshes) level-set-based seamless fracture scheme. The new scheme eliminates visible scarring seen in the previous implementation by subdividing internal, nonplanar quads near fracture seams. In addition, fracture seam points are now tagged, allowing them to be used to drive pre-fracture dynamics such as local surface buckling. - Improved the performance of @vdblink::tree::Tree::evalActiveVoxelBoundingBox() Tree::evalActiveVoxelBoundingBox@endlink and @vdblink::tree::Tree::activeVoxelCount() Tree::activeVoxelCount@endlink, and significantly improved the performance of @vdblink::tree::Tree::evalLeafBoundingBox() Tree::evalLeafBoundingBox@endlink (by about 30x). - Added a tool (and a Houdini SOP) that fills a volume with adaptively-sized overlapping or non-overlapping spheres. - Added a Ray SOP that can be used to perform geometry projections using level-set ray intersections or closest-point queries. - Added a @vdblink::tools::ClosestSurfacePoint tool@endlink that performs accelerated closest surface point queries from arbitrary points in world space to narrow-band level sets. - Increased the speed of masked level set filtering by 20% for the most common cases. - Added @vdblink::math::BoxStencil math::BoxStencil@endlink, with support for trilinear interpolation and gradient computation. - Added @vdblink::tree::Tree::topologyIntersection() Tree::topologyIntersection@endlink, which intersects a tree’s active values with those of another tree, and @vdblink::tree::Tree::topologyDifference() Tree::topologyDifference@endlink, which performs topological subtraction of one tree’s active values from another’s. In both cases, the ValueTypes of the two trees need not be the same. - Added @vdblink::tree::Tree::activeTileCount() Tree::activeTileCount@endlink, which returns the number of active tiles in a tree. - Added @vdblink::math::MinIndex() math::MinIndex@endlink and @vdblink::math::MaxIndex() math::MaxIndex@endlink, which find the minimum and maximum components of a vector without any branching. - Added @vdblink::math::BBox::minExtent() BBox::minExtent@endlink, which returns a bounding box’s shortest axis. - The default @vdblink::math::BBox BBox@endlink constructor now generates an invalid bounding box rather than an empty bounding box positioned at the origin. The new behavior is consistent with @vdblink::math::CoordBBox CoordBBox@endlink. [Thanks to Rick Hankins for suggesting this fix.] - Added @vdblink::math::CoordBBox::reset() CoordBBox::reset@endlink, which resets a bounding box to its initial, invalid state. - Fixed a bug in the default @vdblink::math::ScaleMap ScaleMap@endlink constructor that left some data used in the inverse uninitialized. - Added @vdblink::math::MapBase::applyJT MapBase::applyJT@endlink, which applies the Jacobian transpose to a vector (the Jacobian transpose takes a range-space vector to a domain-space vector, e.g., world to index), and added @vdblink::math::MapBase::inverseMap() MapBase::inverseMap@endlink, which returns a new map representing the inverse of the original map (except for @vdblink::math::NonlinearFrustumMap NonlinearFrustumMap@endlink, which does not currently have a defined inverse map).
@b Note: Houdini 12.5 uses an earlier version of OpenVDB, and maps created with that version lack virtual table entries for these new methods, so do not call these methods from Houdini 12.5. - Reimplemented @vdblink::math::RandomInt math::RandomInt@endlink using Boost.Random instead of @c rand() (which is not thread-safe), and deprecated @c math::randUniform() and added @vdblink::math::Random01 math::Random01@endlink to replace it. - Modified @vdblink::tools::copyFromDense() tools::copyFromDense@endlink and @vdblink::tools::copyToDense() tools::copyToDense@endlink to allow for implicit type conversion (e.g., between a @vdblink::tools::Dense Dense<Int32>@endlink and a @vdblink::FloatTree FloatTree@endlink) and fixed several bugs in @vdblink::tools::CopyFromDense tools::CopyFromDense@endlink. - Fixed bugs in @vdblink::math::Stats math::Stats@endlink and @vdblink::math::Histogram math::Histogram@endlink that could produce NaNs or other incorrect behavior if certain methods were called on populations of size zero. - Renamed struct tolerance to @vdblink::math::Tolerance math::Tolerance@endlink and @c negative to @vdblink::math::negative() math::negative@endlink and removed @c math::toleranceValue(). - Implemented a closest point on line segment algorithm, @vdblink::math::closestPointOnSegmentToPoint() math::closestPointOnSegmentToPoint@endlink. - Fixed meshing issues relating to masking and automatic partitioning. - @vdblink::Grid::merge() Grid::merge@endlink and @vdblink::tree::Tree::merge() Tree::merge@endlink now accept an optional @vdblink::MergePolicy MergePolicy@endlink argument that specifies one of three new merging schemes. (The old merging scheme, which is no longer available, used logic for each tree level that was inconsistent with the other levels and that could result in active tiles being replaced with nodes having only inactive values.) - Renamed @c LeafNode::coord2offset(), @c LeafNode::offset2coord() and @c LeafNode::offset2globalCoord() to @vdblink::tree::LeafNode::coordToOffset() coordToOffset@endlink, @vdblink::tree::LeafNode::offsetToLocalCoord() offsetToLocalCoord@endlink, and @vdblink::tree::LeafNode::offsetToGlobalCoord() offsetToGlobalCoord@endlink, respectively, and likewise for @vdblink::tree::InternalNode::offsetToGlobalCoord() InternalNode@endlink. [Thanks to Rick Hankins for suggesting this change.] - Replaced @vdblink::tree::Tree Tree@endlink methods @c setValueOnMin, @c setValueOnMax and @c setValueOnSum with @vdblink::tools::setValueOnMin() tools::setValueOnMin@endlink, @vdblink::tools::setValueOnMax() tools::setValueOnMax@endlink and @vdblink::tools::setValueOnSum() tools::setValueOnSum@endlink (and a new @vdblink::tools::setValueOnMult() tools::setValueOnMult@endlink) and added @vdblink::tree::Tree::modifyValue() Tree::modifyValue@endlink and @vdblink::tree::Tree::modifyValueAndActiveState() Tree::modifyValueAndActiveState@endlink, which modify voxel values in-place via user-supplied functors. Similarly, replaced @c ValueAccessor::setValueOnSum() with @vdblink::tree::ValueAccessor::modifyValue() ValueAccessor::modifyValue@endlink and @vdblink::tree::ValueAccessor::modifyValueAndActiveState() ValueAccessor::modifyValueAndActiveState@endlink, and added a @vdblink::tree::TreeValueIteratorBase::modifyValue() modifyValue@endlink method to all value iterators. - Removed @c LeafNode::addValue and @c LeafNode::scaleValue. - Added convenience classes @vdblink::tree::Tree3 tree::Tree3@endlink and @vdblink::tree::Tree5 tree::Tree5@endlink for custom tree configurations. - Added an option to the From Particles SOP to generate an alpha mask, which can be used to constrain level set filtering so as to preserve surface details. - The mesh to volume converter now handles point-degenerate polygons. - Fixed a bug in the Level Set Smooth, Level Set Renormalize and Level Set Offset SOPs that caused the group name to be ignored. - Fixed various OS X and Windows build issues. [Contributions from SESI and DD] @htmlonly @endhtmlonly @par Version 1.2.0 - June 28 2013 - @vdblink::tools::LevelSetFilter Level set filters@endlink now accept an optional alpha mask grid. - Implemented sharp feature extraction for level set surfacing. This enhances the quality of the output mesh and reduces aliasing artifacts. - Added masking options to the meshing tools, as well as a spatial multiplier for the adaptivity threshold, automatic partitioning, and the ability to preserve edges and corners when mesh adaptivity is applied. - The mesh to volume attribute transfer scheme now takes surface orientation into account, which improves accuracy in proximity to edges and corners. - Added a @vdblink::tree::LeafManager::foreach() foreach@endlink method to @vdblink::tree::LeafManager tree::LeafManager@endlink that, like @vdblink::tools::foreach() tools::foreach@endlink, applies a user-supplied functor to each leaf node in parallel. - Rewrote the particle to level set converter, simplifying the API, improving performance (especially when particles have a fixed radius), adding the capability to transfer arbitrary point attributes, and fixing a velocity trail bug. - Added utility methods @vdblink::math::Sign() Sign@endlink, @vdblink::math::SignChange() SignChange@endlink, @vdblink::math::isApproxZero() isApproxZero@endlink, @vdblink::math::Cbrt() Cbrt@endlink and @vdblink::math::ZeroCrossing() ZeroCrossing@endlink to math/Math.h. - Added a @vdblink::tree::ValueAccessor3::probeNode() probeNode@endlink method to the value accessor and to tree nodes that returns a pointer to the node that contains a given voxel. - Deprecated @c LeafNode::addValue and @c LeafNode::scaleValue. - Doubled the speed of the mesh to volume converter (which also improves the performance of the fracture and level set rebuild tools) and improved its inside/outside voxel classification near edges and corners. - @vdblink::tools::GridSampler GridSampler@endlink now accepts either a grid, a tree or a value accessor, and it offers faster index-based access methods and much better performance in cases where many instances are allocated. - Extended @vdblink::tools::Dense tools::Dense@endlink to make it more compatible with existing tools. - Fixed a crash in @vdblink::io::Archive io::Archive@endlink whenever the library was unloaded from memory and then reloaded. [Contributed by Ollie Harding] - Fixed a bug in @c GU_PrimVDB::buildFromPrimVolume(), seen during the conversion from Houdini volumes to OpenVDB grids, that could cause signed flood fill to be applied to non-level set grids, resulting in active tiles with incorrect values. - Added a Prune SOP with several pruning schemes. @htmlonly @endhtmlonly @par Version 1.1.1 - May 10 2013 - Added a simple @vdblink::tools::Dense dense grid class@endlink and tools to copy data from dense voxel arrays into OpenVDB grids and vice-versa. - Starting with Houdini 12.5.396, plugins built with this version of OpenVDB can coexist with native Houdini OpenVDB nodes. - The level set fracture tool now smooths seam line edges during mesh extraction, eliminating staircase artifacts. - Significantly improved the performance of the @vdblink::util::leafTopologyIntersection() leafTopologyIntersection@endlink and @vdblink::util::leafTopologyDifference() leafTopologyDifference@endlink utilities and added a @vdblink::tree::LeafNode::topologyDifference() LeafNode::topologyDifference@endlink method. - Added convenience functions that provide simplified interfaces to the @vdblink::tools::meshToLevelSet() mesh to volume@endlink and @vdblink::tools::volumeToMesh() volume to mesh@endlink converters. - Added a @vdblink::tools::accumulate() tools::accumulate@endlink function that is similar to @vdblink::tools::foreach() tools::foreach@endlink but can be used to accumulate the results of computations over the values of a grid. - Added @vdblink::tools::statistics() tools::statistics@endlink, @vdblink::tools::opStatistics() tools::opStatistics@endlink and @vdblink::tools::histogram() tools::histogram@endlink, which efficiently compute statistics (mean, variance, etc.) and histograms of grid values (using @vdblink::math::Stats math::Stats@endlink and @vdblink::math::Histogram math::Histogram@endlink). - Modified @vdblink::math::CoordBBox CoordBBox@endlink to adhere to TBB’s splittable type requirements, so that, for example, a @c CoordBBox can be used as a blocked iteration range. - Added @vdblink::tree::Tree::addTile() Tree::addTile@endlink, @vdblink::tree::Tree::addLeaf() Tree::addLeaf@endlink and @vdblink::tree::Tree::stealNode() Tree::stealNode@endlink, for fine control over tree construction. - Addressed a numerical stability issue when performing Gaussian filtering of level set grids. - Changed the return type of @vdblink::math::CoordBBox::volume() CoordBBox::volume@endlink to reduce the risk of overflow. - When the input mesh is self-intersecting, the mesh to volume converter now produces a level set with a monotonic gradient field. - Fixed a threading bug in the mesh to volume converter that caused it to produce different results for the same input. - Fixed a bug in the particle to level set converter that prevented particles with zero velocity from being rasterized in Trail mode. - Added an optional input to the Create SOP into which to merge newly-created grids. - Fixed a bug in the Resample SOP that caused it to produce incorrect narrow-band widths when resampling level set grids. - Fixed a bug in the To Polygons SOP that caused intermittent crashes when the optional reference input was connected. - Fixed a bug in the Advect Level Set SOP that caused a crash when the velocity input was connected but empty. - The Scatter and Sample Point SOPs now warn instead of erroring when given empty grids. - Fixed a crash in @c vdb_view when stepping through multiple grids after changing render modes. - @c vdb_view can now render fog volumes and vector fields, and it now features interactively adjustable clipping planes that enable one to view the interior of a volume. @htmlonly @endhtmlonly @par Version 1.1.0 - April 4 2013 - The @vdblink::tools::resampleToMatch() resampleToMatch@endlink tool, the Resample SOP and the Combine SOP now use level set rebuild to correctly and safely resample level sets. Previously, scaling a level set would invalidate the signed distance field, leading to holes and other artifacts. - Added a mask-based topological @vdblink::tools::erodeVoxels erosion tool@endlink, and rewrote and simplified the @vdblink::tools::dilateVoxels dilation tool@endlink. - The @vdblink::tools::LevelSetAdvection LevelSetAdvection@endlink tool can now advect forward or backward in time. - Tree::pruneLevelSet() now replaces each pruned node with a tile having the inside or outside background value, instead of arbitrarily selecting one of the node’s tile or voxel values. - When a grid is saved to a file with @vdblink::Grid::saveFloatAsHalf() saveFloatAsHalf@endlink set to @c true, the grid’s background value is now also quantized to 16 bits. (Not quantizing the background value caused a mismatch with the values of background tiles.) - As with @vdblink::tools::foreach() tools::foreach@endlink, it is now possible to specify whether functors passed to @vdblink::tools::transformValues() tools::transformValues@endlink should be shared across threads. - @vdblink::tree::LeafManager tree::LeafManager@endlink can now be instantiated with a @const tree, although buffer swapping with @const trees is disabled. - Added a @vdblink::Grid::signedFloodFill() Grid::signedFloodFill@endlink overload that allows one to specify inside and outside values. - Fixed a bug in Grid::setBackground() so that now only the values of inactive voxels change. - Fixed @vdblink::Grid::topologyUnion() Grid::topologyUnion@endlink so that it actually unions tree topology, instead of just the active states of tiles and voxels. The previous behavior broke multithreaded code that relied on input and output grids having compatible tree topology. - @vdblink::math::Transform math::Transform@endlink now includes an @vdblink::math::Transform::isIdentity() isIdentity@endlink predicate and methods to @vdblink::math::Transform::preMult(const Mat4d&) pre-@endlink and @vdblink::math::Transform::postMult(const Mat4d&) postmultiply@endlink by a matrix. - Modified the @link NodeMasks.h node mask@endlink classes to permit octree-like tree configurations (i.e., with a branching factor of two) and to use 64-bit operations instead of 32-bit operations. - Implemented a new, more efficient @vdblink::math::closestPointOnTriangleToPoint() closest point on triangle@endlink algorithm. - Implemented a new vertex normal scheme in the volume to mesh converter, and resolved some overlapping polygon issues. - The volume to mesh converter now meshes not just active voxels but also active tiles. - Fixed a bug in the mesh to volume converter that caused unsigned distance field conversion to produce empty grids. - Fixed a bug in the level set fracture tool whereby the cutter overlap toggle was ignored. - Fixed an infinite loop bug in @c vdb_view. - Updated @c vdb_view to use the faster and less memory-intensive OpenVDB volume to mesh converter instead of marching cubes, and rewrote the shader to be OpenGL 3.2 and GLSL 1.2 compatible. - Given multiple input files or a file containing multiple grids, @c vdb_view now displays one grid at a time. The left and right arrow keys cycle between grids. - The To Polygons SOP now has an option to associate the input grid’s name with each output polygon. @htmlonly @endhtmlonly @par Version 1.0.0 - March 14 2013 - @vdblink::tools::levelSetRebuild() tools::levelSetRebuild@endlink now throws an exception when given a non-scalar or non-floating-point grid. - The tools in tools/GridOperators.h are now interruptible, as is the Analysis SOP. - Added a @vdblink::tree::LeafManager::LeafRange::Iterator leaf node iterator@endlink and a TBB-compatible @vdblink::tree::LeafManager::LeafRange range class@endlink to the LeafManager. - Modified the @vdblink::tools::VolumeToMesh VolumeToMesh@endlink tool to handle surface topology issues around fracture seam lines. - Modified the Makefile to allow @c vdb_view to compile on OS X systems (provided that GLFW is available). - Fixed a bug in the Create SOP that resulted in "invalid parameter name" warnings. - The Combine SOP now optionally resamples the A grid into the B grid’s index space (or vice-versa) if the A and B transforms differ. - The Vector Split and Vector Merge SOPs now skip inactive voxels by default, but they can optionally be made to include inactive voxels, as they did before. - The @vdblink::tools::LevelSetFracture LevelSetFracture@endlink tool now supports custom rotations for each cutter instance, and the Fracture SOP now uses quaternions to generate uniformly-distributed random rotations. @htmlonly @endhtmlonly @par Version 0.104.0 - February 15 2013 - Added a @vdblink::tools::levelSetRebuild() tool@endlink and a SOP to rebuild a level set from any scalar volume. - @c .vdb files are now saved using a mask-based compression scheme that is an order of magnitude faster than ZLIB and produces comparable file sizes for level set and fog volume grids. (ZLIB compression is still enabled by default for other classes of grids). - The @vdblink::tools::Filter Filter@endlink and @vdblink::tools::LevelSetFilter LevelSetFilter@endlink tools now include a Gaussian filter, and mean (box) filtering is now 10-50x faster. - The isosurface @vdblink::tools::VolumeToMesh meshing tool@endlink is now more robust (to level sets with one voxel wide narrow bands, for example). - Mesh to volume conversion is on average 1.5x faster and up to 5.5x faster for high-resolution meshes where the polygon/voxel size ratio is small. - Added @vdblink::createLevelSet() createLevelSet@endlink and @vdblink::createLevelSetSphere() createLevelSetSphere@endlink factory functions for level set grids. - @vdblink::tree::ValueAccessor tree::ValueAccessor@endlink is now faster for trees of height 2, 3 and 4 (the latter is the default), and it now allows one to specify, via a template argument, the number of node levels to be cached, which can also improve performance in special cases. - Added a toggle to @vdblink::tools::foreach() tools::foreach@endlink to specify whether or not the functor should be shared across threads. - Added @vdblink::Mat4SMetadata Mat4s@endlink and @vdblink::Mat4DMetadata Mat4d@endlink metadata types. - Added explicit pre- and postmultiplication methods to the @c Transform, @c Map and @c Mat4 classes and deprecated the old accumulation methods. - Modified @vdblink::math::NonlinearFrustumMap NonlinearFrustumMap@endlink to be more compatible with Houdini’s frustum transform. - Fixed a @vdblink::tools::GridTransformer GridTransformer@endlink bug that caused it to translate the output grid incorrectly in some cases. - Fixed a bug in the tree-level @vdblink::tree::LeafIteratorBase LeafIterator@endlink that resulted in intermittent crashes in @vdblink::tools::dilateVoxels() tools::dilateVoxels@endlink. - The @c Hermite data type and Hermite grids are no longer supported. - Added tools/GridOperators.h, which includes new, cleaner implementations of the @vdblink::tools::cpt() closest point transform@endlink, @vdblink::tools::curl() curl@endlink, @vdblink::tools::divergence() divergence@endlink, @vdblink::tools::gradient() gradient@endlink, @vdblink::tools::laplacian() Laplacian@endlink, @vdblink::tools::magnitude() magnitude@endlink, @vdblink::tools::meanCurvature() mean curvature@endlink and @vdblink::tools::normalize() normalize@endlink tools. - Interrupt support has been improved in several tools, including @vdblink::tools::ParticlesToLevelSet tools::ParticlesToLevelSet@endlink. - Simplified the API of the @vdblink::math::BaseStencil Stencil@endlink class and added an @vdblink::math::BaseStencil::intersects() intersects@endlink method to test for intersection with a specified isovalue. - Renamed @c voxelDimensions to @c voxelSize in transform classes and elsewhere. - Deprecated @c houdini_utils::ParmFactory::setChoiceList in favor of @c houdini_utils::ParmFactory::setChoiceListItems, which requires a list of token, label string pairs. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Fixed a bug in @c houdini_utils::getNodeChain() that caused the Offset Level Set, Smooth Level Set and Renormalize Level Set SOPs to ignore frame changes. [Contributed by SESI] - The From Particles SOP now provides the option to write into an existing grid. - Added a SOP to edit grid metadata. - The Fracture SOP now supports multiple cutter objects. - Added a To Polygons SOP that complements the Fracture SOP and allows for elimination of seam lines, generation of correct vertex normals and grouping of polygons when surfacing fracture fragments, using the original level set or mesh as a reference. @htmlonly @endhtmlonly @par Version 0.103.1 - January 15 2013 - @vdblink::tree::ValueAccessor tree::ValueAccessor@endlink read operations are now faster for four-level trees. (Preliminary benchmark tests suggest a 30-40% improvement.) - For vector-valued grids, @vdblink::tools::compMin() tools::compMin@endlink and @vdblink::tools::compMax() tools::compMax@endlink now compare vector magnitudes instead of individual components. - Migrated grid sampling code to a new file, Interpolation.h, and deprecated old files and classes. - Added a level-set @vdblink::tools::LevelSetFracture fracture tool@endlink and a Fracture SOP. - Added @vdblink::tools::sdfInteriorMask() tools::sdfInteriorMask@endlink, which creates a mask of the interior region of a level set grid. - Fixed a bug in the mesh to volume converter that produced unexpected nonzero values for voxels at the intersection of two polygons, and another bug that produced narrow-band widths that didn’t respect the background value when the half-band width was less than three voxels. - @c houdini_utils::ParmFactory can now correctly generate ramp multi-parms. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - The Convert SOP can now convert between signed distance fields and fog volumes and from volumes to meshes. [Contributed by SESI] - For level sets, the From Mesh and From Particles SOPs now match the reference grid’s narrow-band width. - The Scatter SOP can now optionally scatter points in the interior of a level set. @htmlonly @endhtmlonly @par Version 0.103.0 - December 21 2012 - The mesh to volume converter is now 60% faster at generating level sets with wide bands, and the From Mesh SOP is now interruptible. - Fixed a threading bug in the recently-added @vdblink::tools::compReplace() compReplace@endlink tool that caused it to produce incorrect output. - Added a @vdblink::tree::Tree::probeConstLeaf() probeConstLeaf@endlink method to the @vdblink::tree::Tree::probeConstLeaf() Tree@endlink, @vdblink::tree::ValueAccessor::probeConstLeaf() ValueAccessor@endlink and @vdblink::tree::RootNode::probeConstLeaf() node@endlink classes. - The Houdini VDB primitive doesn’t create a @c name attribute unnecessarily (i.e., if its grid’s name is empty), but it now correctly allows the name to be changed to the empty string. - Fixed a crash in the Vector Merge SOP when fewer than three grids were merged. - The From Particles SOP now features a "maximum half-width" parameter to help avoid runaway computations. @htmlonly @endhtmlonly @par Version 0.102.0 - December 13 2012 - Added @vdblink::tools::compReplace() tools::compReplace@endlink, which copies the active values of one grid into another, and added a "Replace A With Active B" mode to the Combine SOP. - @vdblink::Grid::signedFloodFill() Grid::signedFloodFill@endlink no longer enters an infinite loop when filling an empty grid. - Fixed a bug in the particle to level set converter that sometimes produced level sets with holes, and fixed a bug in the SOP that could result in random output. - Fixed an issue in the frustum preview feature of the Create SOP whereby rendering very large frustums could cause high CPU usage. - Added streamline support to the constrained advection scheme in the Advect Points SOP. - Added an Advect Level Set SOP. @htmlonly @endhtmlonly @par Version 0.101.1 - December 11 2012 (DWA internal release) - Partially reverted the Houdini VDB primitive’s grid accessor methods to their pre-0.98.0 behavior. A primitive’s grid can once again be accessed by shared pointer, but now also by reference. Accessor methods for grid metadata have also been added, and the primitive now ensures that metadata and transforms are never shared. - Fixed an intermittent crash in the From Particles SOP. @htmlonly @endhtmlonly @par Version 0.101.0 - December 6 2012 (DWA internal release) - Partially reverted the @vdblink::Grid Grid@endlink’s @vdblink::Grid::tree() tree@endlink and @vdblink::Grid::transform() transform@endlink accessor methods to their pre-0.98.0 behavior, eliminating copy-on-write but preserving their return-by-reference semantics. These methods are now supplemented with a suite of @vdblink::Grid::treePtr() shared@endlink @vdblink::Grid::baseTreePtr() pointer@endlink @vdblink::Grid::transformPtr() accessors@endlink. - Restructured the @vdblink::tools::meshToVolume mesh to volume converter@endlink for a 40% speedup and to be more robust to non-manifold geometry, to better preserve sharp features, to support arbitrary tree configurations and to respect narrow-band limits. - Added a @c getNodeBoundingBox method to @vdblink::tree::RootNode::getNodeBoundingBox() RootNode@endlink, @vdblink::tree::InternalNode::getNodeBoundingBox() InternalNode@endlink and @vdblink::tree::LeafNode::getNodeBoundingBox() LeafNode@endlink that returns the index space spanned by a node. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Renamed the Reshape Level Set SOP to Offset Level Set. - Fixed a crash in the Convert SOP and added support for conversion of empty grids. @htmlonly @endhtmlonly @par Version 0.100.0 - November 30 2012 (DWA internal release) - Greatly improved the performance of the level set to fog volume @vdblink::tools::sdfToFogVolume() converter@endlink. - Improved the performance of the @vdblink::tools::Filter::median() median filter@endlink and of level set @vdblink::tools::csgUnion() CSG@endlink operations. - Reintroduced Tree::pruneLevelSet(), a specialized Tree::pruneInactive() for level-set grids. - Added utilities to the @c houdini_utils library to facilitate the collection of a chain of adjacent nodes of a particular type so that they can be cooked in a single step. (For example, adjacent @c xform SOPs could be collapsed by composing their transformation matrices into a single matrix.) - Added pruning and flood-filling options to the Convert SOP. - Reimplemented the Filter SOP, omitting level-set-specific filters and adding node chaining (to reduce memory usage when applying several filters in sequence). - Added a toggle to the Read SOP to read grid metadata and transforms only. - Changed the attribute transfer scheme on the From Mesh and From Particles SOPs to allow for custom grid names and vector type metadata. @htmlonly @endhtmlonly @par Version 0.99.0 - November 21 2012 - Added @vdblink::Grid Grid@endlink methods that return non-const tree and transform references without triggering deep copies, as well as @c const methods that return @c const shared pointers. - Added @c Grid methods to @vdblink::Grid::addStatsMetadata populate@endlink a grid’s metadata with statistics like the active voxel count, and to @vdblink::Grid::getStatsMetadata retrieve@endlink that metadata. By default, statistics are now computed and added to grids whenever they are written to .vdb files. - Added @vdblink::io::File::readGridMetadata io::File::readGridMetadata@endlink and @vdblink::io::File::readAllGridMetadata io::File::readAllGridMetadata@endlink methods to read just the grid metadata and transforms from a .vdb file. - Fixed numerical precision issues in the @vdblink::tools::csgUnion csgUnion@endlink, @vdblink::tools::csgIntersection csgIntersection@endlink and @vdblink::tools::csgDifference csgDifference@endlink tools, and added toggles to optionally disable postprocess pruning. - Fixed an issue in @c vdb_view with the ordering of GL vertex buffer calls. [Contributed by Bill Katz] - Fixed an intermittent crash in the @vdblink::tools::ParticlesToLevelSet ParticlesToLevelSet@endlink tool, as well as a race condition that could cause data corruption. - The @c ParticlesToLevelSet tool and From Particles SOP can now transfer arbitrary point attribute values from the input particles to output voxels. - Fixed a bug in the Convert SOP whereby the names of primitives were lost during conversion, and another bug that resulted in arithmetic errors when converting empty grids. - Fixed a bug in the Combine SOP that caused the Operation selection to be lost. @htmlonly @endhtmlonly @par Version 0.98.0 - November 16 2012 - @vdblink::tree::Tree Tree@endlink and @vdblink::math::Transform Transform@endlink objects (and @vdblink::Grid Grid@endlink objects in the context of Houdini SOPs) are now passed and accessed primarily by reference rather than by shared pointer. See @subpage api_0_98_0 "Porting to OpenVDB 0.98.0" for details about this important API change. [Contributed by SESI] - Reimplemented @vdblink::math::CoordBBox CoordBBox@endlink to address several off-by-one bugs related to bounding box dimensions. - Fixed an off-by-one bug in @vdblink::Grid::evalActiveVoxelBoundingBox() evalActiveVoxelBoundingBox@endlink. - Introduced the @vdblink::tree::LeafManager LeafManager@endlink class, which will eventually replace the @c LeafArray class. @c LeafManager supports dynamic buffers stored as a structure of arrays (SOA), unlike @c LeafArray, which supports only static buffers stored as an array of structures (AOS). - Improved the performance of the @vdblink::tools::LevelSetFilter LevelSetFilter@endlink and @vdblink::tools::LevelSetTracker LevelSetTracker@endlink tools by rewriting them to use the new @vdblink::tree::LeafManager LeafManager@endlink class. - Added @vdblink::tree::Tree::setValueOnly() Tree::setValueOnly@endlink and @vdblink::tree::ValueAccessor::setValueOnly() ValueAccessor::setValueOnly@endlink methods, which change the value of a voxel without changing its active state, and @vdblink::tree::Tree::probeLeaf() Tree::probeLeaf@endlink and @vdblink::tree::ValueAccessor::probeLeaf() ValueAccessor::probeLeaf@endlink methods that return the leaf node that contains a given voxel (unless the voxel is represented by a tile). - Added a @vdblink::tools::LevelSetAdvection LevelSetAdvection@endlink tool that propagates and tracks narrow-band level sets. - Introduced a new @vdblink::tools::GridSampler GridSampler@endlink class that supports world-space (or index-space) sampling of grid values. - Changed the interpretation of the @vdblink::math::NonlinearFrustumMap NonlinearFrustumMap@endlink’s @em taper parameter to be the ratio of the near and far plane depths. - Added a @c ParmFactory::setChoiceList() overload that accepts (@em token, @em label) string pairs, and a @c setDefault() overload that accepts an STL string. - Fixed a crash in the Combine SOP in Copy B mode. - Split the Level Set Filter SOP into three separate SOPs, Level Set Smooth, Level Set Reshape and Level Set Renormalize. When two or more of these nodes are connected in sequence, they interact to reduce memory usage: the last node in the sequence performs all of the operations in one step. - The Advect Points SOP can now output polyline streamlines that trace the paths of the points. - Added an option to the Analysis SOP to specify names for output grids. - Added camera-derived frustum transform support to the Create SOP. @htmlonly @endhtmlonly @par Version 0.97.0 - October 18 2012 - Added a narrow-band @vdblink::tools::LevelSetTracker level set interface tracking tool@endlink (up to fifth-order in space but currently only first-order in time, with higher temporal orders to be added soon). - Added a @vdblink::tools::LevelSetFilter level set filter tool@endlink to perform unrestricted surface smoothing (e.g., Laplacian flow), filtering (e.g., mean value) and morphological operations (e.g., morphological opening). - Added adaptivity to the @vdblink::tools::VolumeToMesh level set meshing tool@endlink for faster mesh extraction with fewer polygons, without postprocessing. - Added a @vdblink::tree::ValueAccessor::touchLeaf() ValueAccessor::touchLeaf@endlink method that creates (if necessary) and returns the leaf node containing a given voxel. It can be used to preallocate leaf nodes over which to run parallel algorithms. - Fixed a bug in @vdblink::Grid::merge() Grid::merge@endlink whereby active tiles were sometimes lost. - Added @vdblink::tree::LeafManager LeafManager@endlink, which is similar to @c LeafArray but supports a dynamic buffer count and allocates buffers more efficiently. Useful for temporal integration (e.g., for level set propagation and interface tracking), @c LeafManager is meant to replace @c LeafArray, which will be deprecated in the next release. - Added a @vdblink::tree::LeafNode::fill() LeafNode::fill@endlink method to efficiently populate leaf nodes with constant values. - Added a @vdblink::tree::Tree::visitActiveBBox() Tree::visitActiveBBox@endlink method that applies a functor to the bounding boxes of all active tiles and leaf nodes and that can be used to improve the performance of ray intersection tests, rendering of bounding boxes, etc. - Added a @vdblink::tree::Tree::voxelizeActiveTiles() Tree::voxelizeActiveTiles@endlink method to densify active tiles. While convenient and fast, this can produce large dense grids, so use it with caution. - Repackaged @c Tree::pruneLevelSet() as a Tree::pruneOp()-compatible functor. Tree::LevelSetPrune is a specialized Tree::pruneInactive for level-set grids and is used in interface tracking. - Added a GridBase::pruneGrid() method. - Added a @vdblink::Grid::hasUniformVoxels() Grid:hasUniformVoxels@endlink method. - Renamed @c tools::dilate to @vdblink::tools::dilateVoxels() dilateVoxels@endlink and improved its performance. The new name reflects the fact that the current implementation ignores active tiles. - Added a @vdblink::tools::resampleToMatch() tools::resampleToMatch@endlink function that resamples an input grid into an output grid with a different transform such that, after resampling, the input and output grids coincide, but the output grid’s transform is preserved. - Significantly improved the performance of depth-bounded value iterators (@vdblink::tree::Tree::ValueOnIter ValueOnIter@endlink, @vdblink::tree::Tree::ValueAllIter ValueAllIter@endlink, etc.) when the depth bound excludes leaf nodes. - Exposed the value buffers inside leaf nodes with @vdblink::tree::LeafNode::buffer() LeafNode::buffer@endlink. This allows for very fast access (const and non-const) to voxel values using linear array offsets instead of @ijk coordinates. - In openvdb_houdini/UT_VDBTools.h, added operators for use with @c processTypedGrid that resample grids in several different ways. - Added a policy mechanism to @c houdini_utils::OpFactory that allows for customization of operator names, icons, and Help URLs. - Renamed many of the Houdini SOPs to make the names more consistent. - Added an Advect Points SOP. - Added a Level Set Filter SOP that allows for unrestricted surface deformations, unlike the older Filter SOP, which restricts surface motion to the initial narrow band. - Added staggered vector sampling to the Sample Points SOP. - Added a minimum radius threshold to the particle voxelization tool and SOP. - Merged the Composite and CSG SOPs into a single Combine SOP. - Added a tool and a SOP to efficiently generate narrow-band level set representations of spheres. - In the Visualize SOP, improved the performance of tree topology generation, which is now enabled by default. @htmlonly @endhtmlonly @par Version 0.96.0 - September 24 2012 - Fixed a memory corruption bug in the mesh voxelizer tool. - Temporarily removed the optional clipping feature from the level set mesher. - Added "Staggered Vector Field" to the list of grid classes in the Create SOP. @htmlonly @endhtmlonly @par Version 0.95.0 - September 20 2012 - Added a quad @vdblink::tools::VolumeToMesh meshing@endlink tool for higher-quality level set meshing and updated the Visualizer SOP to use it. - Fixed a precision error in the @vdblink::tools::meshToVolume mesh voxelizer@endlink and improved the quality of inside/outside voxel classification. Output grids are now also @vdblink::Grid::setGridClass() classified@endlink as either level sets or fog volumes. - Modified the @vdblink::tools::GridResampler GridResampler@endlink to use the signed flood fill optimization only on grids that are tagged as level sets. - Added a @vdblink::math::Quat quaternion@endlink class to the math library and a method to return the @vdblink::math::Mat3::trace trace@endlink of a @c Mat3. - Fixed a bug in the @vdblink::tree::ValueAccessor::ValueAccessor(const ValueAccessor&) ValueAccessor@endlink copy constructor that caused the copy to reference the original. - Fixed a bug in @vdblink::tree::RootNode::setActiveState() RootNode::setActiveState@endlink that caused a crash when marking a (virtual) background voxel as inactive. - Added a @c Tree::pruneLevelSet method that is similar to but faster than Tree::pruneInactive() for level set grids. - Added fast leaf node voxel access @vdblink::tree::LeafNode::getValue(Index) const methods@endlink that index by linear offset (as returned by @vdblink::tree::LeafNode::ValueOnIter::pos() ValueIter::pos@endlink) instead of by @ijk coordinates. - Added a @vdblink::tree::Tree::touchLeaf() Tree::touchLeaf@endlink method that can be used to preallocate a static tree topology over which to safely perform multithreaded processing. - Added a grain size argument to @c LeafArray for finer control of parallelism. - Modified the Makefile to make it easier to omit the doc, @c vdb_test and @c vdb_view targets. - Added utility functions (in houdini/UT_VDBUtils.h) to convert between Houdini and OpenVDB matrix and vector types. [Contributed by SESI] - Added accessors to @c GEO_PrimVDB that make it easier to directly access voxel data and that are used by the HScript volume expression functions in Houdini 12.5. [Contributed by SESI] - As of Houdini 12.1.77, the native transform SOP operates on OpenVDB primitives. [Contributed by SESI] - Added a Convert SOP that converts OpenVDB grids to Houdini volumes and vice-versa. @htmlonly @endhtmlonly @par Version 0.94.1 - September 7 2012 - Fixed bugs in @vdblink::tree::RootNode RootNode@endlink and @vdblink::tree::InternalNode InternalNode@endlink @c setValue*() and @c fill() methods that could cause neighboring voxels to become inactive. - Fixed a bug in @vdblink::tree::Tree::hasSameTopology() Tree::hasSameTopology@endlink that caused false positives when only active states and not values differed. - Added a @vdblink::tree::Tree::hasActiveTiles() Tree::hasActiveTiles@endlink method. - For better cross-platform consistency, substituted bitwise AND operations for right shifts in the @vdblink::tree::ValueAccessor ValueAccessor@endlink hash key computation. - @c vdb_view no longer aborts when asked to surface a vector-valued grid—but it still doesn’t render the surface. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Added an option to the MeshVoxelizer SOP to convert both open and closed surfaces to unsigned distance fields. - The Filter SOP now allows multiple filters to be applied in user-specified order. @htmlonly @endhtmlonly @par Version 0.94.0 - August 30 2012 - Added a @vdblink::Grid::topologyUnion() method@endlink to union just the active states of voxels from one grid with those of another grid of a possibly different type. - Fixed an incorrect scale factor in the Laplacian diffusion @vdblink::tools::Filter::laplacian() filter@endlink. - Fixed a bug in @vdblink::tree::Tree::merge() Tree::merge@endlink that could leave a tree with invalid value accessors. - Added @vdblink::tree::TreeValueIteratorBase::setActiveState() TreeValueIteratorBase::setActiveState@endlink and deprecated @c setValueOn. - Removed @c tools/FastSweeping.h. It will be replaced with a much more efficient implementation in the near future. - ZLIB compression of .vdb files is now optional, but enabled by default. [Contributed by SESI] - Made various changes for Clang and Visual C++ compatibility. [Contributed by SESI] - The MeshVoxelizer SOP can now transfer arbitrary point and primitive attribute values from the input mesh to output voxels. @htmlonly @endhtmlonly @par Version 0.93.0 - August 24 2012 - Renamed symbols in math/Operators.h to avoid ambiguities that GCC 4.4 reports as errors. - Simplified the API for the stencil version of the closest-point transform @vdblink::math::CPT operator@endlink. - Added logic to @vdblink::io::Archive::readGrid() io::Archive::readGrid@endlink to set the grid name metadata from the descriptor if the metadata doesn’t already exist. - Added guards to prevent nesting of @c openvdb_houdini::Interrupter::start() and @c end() calls. @htmlonly @endhtmlonly @par Version 0.92.0 - August 23 2012 - Added a Laplacian diffusion @vdblink::tools::Filter::laplacian() filter@endlink. - Fixed a bug in the initialization of the sparse contour tracer that caused mesh-to-volume conversion to fail in certain cases. - Fixed a bug in the curvature stencil that caused mean curvature filtering to produce wrong results. - Increased the speed of the @vdblink::tools::GridTransformer GridTransformer@endlink by as much as 20% for fog volumes. - Added optional pruning to the Resample SOP. - Modified the PointSample SOP to allow it to work with ungrouped, anonymous grids. - Fixed a crash in the LevelSetNoise SOP. @htmlonly @endhtmlonly @par Version 0.91.0 - August 16 2012 - @vdblink::tools::GridTransformer tools::GridTransformer@endlink and @vdblink::tools::GridResampler tools::GridResampler@endlink now correctly (but not yet efficiently) process tiles in sparse grids. - Added an optional @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 Tree::prune() that could result in output tiles with incorrect active states. - Added @c librt to the link dependencies to address build failures on Ubuntu systems. - Made various small changes to the Makefile and the source code that should help with Mac OS X compatibility. - The Composite and Resample SOPs now correctly copy the input grid’s metadata to the output grid. @htmlonly @endhtmlonly @par Version 0.90.1 - August 7 2012 - Fixed a bug in the @vdblink::math::BBox::getCenter() BBox::getCenter()@endlink method. - Added missing header files to various files. - @vdblink::io::File::NameIterator::gridName() io::File::NameIterator::gridName()@endlink now returns a unique name of the form "name[1]", "name[2]", etc. if a file contains multiple grids with the same name. - Fixed a bug in the Writer SOP that caused grid names to be discarded. - The Resample SOP now correctly sets the background value of the output grid. @htmlonly @endhtmlonly @par Version 0.90.0 - August 3 2012 (initial public release) - Added a basic GL viewer for OpenVDB files. - Greatly improved the performance of two commonly-used @c Tree methods, @vdblink::tree::Tree::evalActiveVoxelBoundingBox() evalActiveVoxelBoundingBox()@endlink and @vdblink::tree::Tree::memUsage() memUsage()@endlink. - Eliminated the @c GridMap class. File I/O now uses STL containers of grid pointers instead. - Refactored stencil-based tools (Gradient, Laplacian, etc.) and rewrote some of them for generality and better performance. Most now behave correctly for grids with nonlinear index-to-world transforms. - Added a @link FiniteDifference.h library@endlink of index-space finite difference operators. - Added a Hermite grid type that compactly stores each voxel’s upwind normals and can be used to convert volumes to and from polygonal meshes. - Added a @link PointScatter.h tool@endlink (and a Houdini SOP) to scatter points randomly throughout a volume. */ openvdb/doc/faq.txt0000644000000000000000000003174312603226304013274 0ustar rootroot/** @page faq Frequently Asked Questions @section sFAQContents Contents - @ref sWhatIsVDB - @ref sWhatLicense - @ref sWhatCLA - @ref sWhyUseVDB - @ref sVersionNumbering - @ref sGeneralizedOctree - @ref sLevelSet - @ref sCustomizeVDB - @ref sAdaptiveGrid - @ref sMeaningOfVDB - @ref sAccessor - @ref sValue - @ref sState - @ref sVoxel - @ref sTile - @ref sBackground - @ref sThreadSafe - @ref sMaxRes - @ref sCompareVDB - @ref sReplaceDense - @ref sFuture - @ref sContribute @section sWhatIsVDB What is OpenVDB? OpenVDB is a library comprising a compact hierarchical data structure and a suite of tools for the efficient manipulation of sparse, possibly time-varying, volumetric data discretized on a three-dimensional grid. It is based on VDB, which was developed by Ken Museth at DreamWorks Animation, and it offers an effectively infinite 3D index space, compact storage (both in memory and on disk), fast data access (both random and sequential), and a collection of algorithms specifically optimized for the data structure for common tasks such as filtering, constructive solid geometry (CSG), discretization of partial differential equations, voxelization of polygons, skinning of particles, volumetric compositing and sampling. The technical details of VDB are described in the paper "VDB: High-Resolution Sparse Volumes with Dynamic Topology". @section sWhatLicense What license is OpenVDB distributed under? OpenVDB is released under the Mozilla Public License Version 2.0, which is a free, open source, and detailed software license developed and maintained by the Mozilla Foundation. It is characterized as a hybridization of the modified BSD license and GNU General Public License (GPL) that seeks to balance the concerns of proprietary and open source developers. For more information about this license, see the Mozilla FAQ. @section sWhatCLA Is there a Contributor License Agreement for OpenVDB? Yes, developers who wish to contribute code to be considered for inclusion in the OpenVDB distribution must first complete this Contributor License Agreement and submit it to DreamWorks (directions are in the CLA). @section sWhyUseVDB Why should I use OpenVDB? The typical reasons to adopt OpenVDB are if you are storing sparse data and/or if you are performing sparse computations. OpenVDB is also effectively unbounded, which makes it very convenient for applications where the topology of the data is dynamic or unknown. Unlike many existing sparse data structures, OpenVDB is also optimized for numerical simulations, multithreaded volume compositing, near real-time boolean CSG operations, fast random and sequential data access and voxelization of points and polygons. @section sVersionNumbering What is the version numbering system for OpenVDB? We currently use a major.minor.patch version numbering system, and with every release of OpenVDB we change one or more of the three numbers. The patch number is incremented for new features and bug fixes that change neither the API nor the file format of the library nor the ABIs of the @c Grid and @c Transform classes and their constituent classes. The minor version is incremented for releases that change the API without changing the @c Grid or @c Transform ABIs, and for releases that change the file format in such a way that older files can still be read. The major version is incremented when the @c Grid or @c Transform ABIs change, or when the file format changes in a non-backward-compatible way (which should be rare). No release of OpenVDB guarantees ABI compatibility across the entire library, but rather only for the @c Grid and @c Transform classes, and primarily to allow third-party app plugins compiled against different versions of the library to coexist and exchange data. @section sCustomizeVDB Can I customize the configuration of OpenVDB? Yes! OpenVDB is specifically developed to be highly customizable. That is, the user can define the sizes of all the nodes at each level of a tree as well as the number of tree levels and the data type of values stored in the tree. However, note that since OpenVDB makes extensive use of C++ templating, configurations are fixed at compile time rather than at run time. This is a fundamental design characteristic of OpenVDB, and it leads to very high performance, thanks to techniques like inlining and template metaprogramming. @section sGeneralizedOctree Is OpenVDB merely a generalized octree or N-tree? No! While OpenVDB can conceptually be configured as a (height-balanced) octree, it is much more than an octree or N-tree. Whereas octrees and N-trees have fixed branching factors of respectively two and N in each coordinate direction, OpenVDB's branching factors typically vary between tree levels and are only limited to be powers of two. To understand why and also learn about other unique features of OpenVDB, refer to the paper in ACM Transactions on Graphics. @section sLevelSet Is OpenVDB primarily for level set applications? No! Don't let the fact that OpenVDB can represent and operate so well on level sets mislead you into thinking that it is limited to or even focusing on this special type of sparse volumetric application. OpenVDB was developed for general-purpose volumetric processing and numerical simulation, and we have even had success using it for volumetric applications that traditionally call for blocked or dense grids. However, the fact remains that narrow-band level sets play an essential role in many volumetric applications, and this explains why OpenVDB includes so many tools and algorithms specifically for level sets. @section sAdaptiveGrid Is OpenVDB an adaptive grid? Let's first stress that the term "adaptive grid" is somewhat ambiguous. Some use it to mean a grid that can store data sampled at adaptive voxel sizes typically derived from a so-called "refinement oracle", whereas others mean multiple grids with different fixed voxel sizes all sampling the same data. An example of the former is an octree and of the latter is a mipmap. Since OpenVDB stores both data values and child nodes at each level of the tree, it is adaptive only in the first sense, not the second. The level of adaptivity or refinement between the tree levels is defined by the branching factors of the nodes, which are fixed at compile time. @section sMeaningOfVDB What does "VDB" stand for? Over the years VDB has been interpreted to mean different things, none of which are very descriptive: "Voxel Data Base", "Volumetric Data Blocks", "Volumetric Dynamic B+tree", etc. In early presentations of VDB we even used a different name, "DB+Grid", which was abandoned to emphasize its distinction from similarly named, but different, existing sparse data structures like DT-Grid or DB-Grid. The simple truth is that "VDB" is just a name. :-) @section sAccessor Why are there no coordinate-based access methods on the grid? It might surprise you that the @c Grid class doesn't directly provide access to voxels via their @ijk coordinates. Instead, the recommended procedure is to ask the grid for a "value accessor", which is an accelerator object that performs bottom-up tree traversal using cached information from previous traversals. Caching greatly improves performance, but it is inherently not thread-safe. However, a single grid may have multiple value accessors, so each thread can safely be assigned its own value accessor. Uncached—and therefore slower, but thread-safe—random access is possible through a grid's tree, for example with a call like grid.tree().getValue(ijk). @section sValue How and where does OpenVDB store values? OpenVDB stores voxel data in a tree with a fixed maximum height (chosen at compile time), with a root node that has a dynamic branching factor, with internal nodes that have fixed branching factors, and with leaf nodes of fixed dimensions. Values can be stored in nodes at all levels of the tree. Values stored in leaf nodes correspond to individual voxels; all other values correspond to "tiles" (see below). @section sState What are active and inactive values? Every value in a grid has a binary state that we refer to as its "active state". The interpretation of this binary state is application-specific, but typically an active (on) value is "interesting" in some sense, and an inactive (off) value is less interesting or uninteresting. For example, the values in a narrow-band level set are all active, and values outside the narrow band are all inactive. Active states of values are stored in very compact bit masks that support sparse iteration, so visiting all active (or inactive) values in a grid can be done very efficiently. @section sVoxel How are voxels represented in OpenVDB? Values stored in nodes of type @c LeafNode (which, when they exist, have a fixed depth), correspond to individual voxels. These are the smallest addressable units of index space. @section sTile What are tiles? Values stored in nodes of type @c RootNode or @c InternalNode correspond to regions of index space with a constant value and active state and with power of two dimensions. We refer to such regions as "tiles". By construction, tiles have no child nodes. @section sBackground What is the background value? A tree's background value is the value that is returned whenever one accesses a region of index space that is not explicitly represented by voxels or tiles in the tree. Thus, you can think of the background value as the default value associated with an empty tree. Note that the background value is always inactive! @section sThreadSafe Is OpenVDB thread-safe? Yes and no. If you're asking if OpenVDB can safely be used in a multithreaded application then the answer is a resounding yes. In fact, many of the tools included in OpenVDB are multithreaded (using Intel's Threading Building Blocks library). However, like virtually all data structures that employ caching and allocate-on-insert, certain operations in OpenVDB are not thread-safe in the general sense. In particular, it is not safe to retrieve voxels from a grid while another thread is inserting voxels. For multithreaded insertion operations we typically assign a separate grid to each thread and then merge the grids as threads terminate. This technique works remarkably well: because OpenVDB is sparse and hierarchical, merging is very efficient. For more details, please consult the @subpage codeExamples and Appendix B in the Transactions on Graphics paper. @section sMaxRes Is OpenVDB unbounded? Yes, to within available memory and the 32-bit precision of the coordinates used to index voxels. And OpenVDB supports signed coordinates, unlike most existing sparse data structures, so there are almost no restrictions on the available grid resolution or the index range of OpenVDB grids. @section sCompareVDB How does OpenVDB compare to existing sparse data structures? OpenVDB is very different from existing sparse data structures that you are likely to have heard of. Foremost it is hierarchical (unlike DT-Grid and Field3D), supports simulations and dynamic topology (unlike GigaVoxels), is effectively unbounded (unlike Field3D), and offers fast random and sequential voxel access. @section sReplaceDense Does OpenVDB replace dense grids? This depends a lot on your application of dense grids and your configuration of OpenVDB. Clearly, if you are storing or processing sparse data, OpenVDB will offer a smaller memory footprint and faster (sparse) data processing. However, even in some cases where both data and computation are dense, OpenVDB can offer benefits like improved CPU cache performance due to its underlying blocking and hierarchical tree structure. Exceptions are of course algorithms that expect dense data to be laid out linearly in memory, or applications that use very small grids. The simple truth is only a benchmark comparison can tell you the preferred data structure, but for what it's worth it is our experience that for the volumetric applications we encounter in production, OpenVDB is almost always superior. @section sFuture What future improvements to OpenVDB are planned? We are continuing to expand the OpenVDB toolset and improve its performance. In the near future we plan to add OpenVDB Maya plugins, Python bindings to more library tools, support for accelerated particle storage and lookup, level set morphing and measures (e.g. area and volume), more rendering options, a fluid pressure solver, improved methods for particle skinning, asynchronous I/O, optimization by means of SIMD instructions, out-of-core support, advection of densities (vs. level sets) and much more. We would very much like to hear your suggestions and preferences. @section sContribute How can I contribute to OpenVDB? If you have bug reports or ideas for improvements or new features, please contact us! If you want to contribute code, please see our License page. */ openvdb/doc/math.txt0000644000000000000000000004374412603226304013462 0ustar rootroot/** @page transformsAndMaps Transforms and Maps @section sMathContents Contents - @ref sTransforms - @ref sLinearTransforms - @ref sFrustumTransforms - @ref sCellVsVertex - @ref sVoxels - @ref sStaggered - @ref sMaps - @ref sGettingMat4 - @ref sCostOfMaps - @ref sGradientAndMaps @section sTransforms Transforms in OpenVDB The OpenVDB @vdblink::tree::Tree Tree@endlink is a sparse representation of a three-dimensional array of voxels, each element of which is addressed via discrete, three-dimensional index space coordinates, typically in the form of a @vdblink::math::Coord Coord@endlink. For example, the following code retrieves the floating-point value of a voxel with index coordinates (1, 2, 3): @code openvdb::FloatGrid grid = ...; openvdb::FloatGrid::Accessor accessor = grid.getAccessor(); openvdb::Coord ijk(1,2,3); float value = accessor.getValue(ijk); @endcode A @vdblink::math::Transform Transform@endlink relates index space coordinates to world space coordinates that give a spatial context for the discretized data.Translation from index coordinates @ijk to world space coordinates @xyz is done with a call to the @vdblink::math::Transform::indexToWorld() indexToWorld@endlink method, and from world space coordinates to index space coordinates with a call to @vdblink::math::Transform::worldToIndex() worldToIndex@endlink: @code // Create a linear transform that scales i, j and k by 0.1 openvdb::math::Transform::Ptr linearTransform = openvdb::math::Transform::createLinearTransform(0.1); // Compute the location in world space that is the image of (1,2,3). // The result will be (0.1, 0.2, 0.3). openvdb::Coord ijk(1,2,3); openvdb::Vec3d worldSpacePoint = linearTransform->indexToWorld(ijk); // Compute the location in index space that is the pre-image of (0.1, 0.2, 0.3). // The result will be (1.0, 2.0, 3.0). openvdb::Vec3d indexSpacePoint = linearTransform->worldToIndex(worldSpacePoint); @endcode In the above example, there are two things to notice. First, world space locations are specified as three-dimensional, double-precision, floating-point vectors, and second, @c worldToIndex does not return discrete coordinates, but rather a floating-point vector. This is a reflection of the fact that not every location in a continuous world space, i.e., not every @xyz, is the image of discrete integral coordinates @ijk. @subsection sLinearTransforms Linear Transforms Currently two different types of transforms are supported: linear and frustum transforms. A linear transform can be composed of scale, translation, rotation, and shear; essentially those things that can be mathematically represented by an invertible, constant-coefficient, @f$3 \times 3@f$ matrix and a translation (mathematically, an affine map). An essential feature of a linear transformation is that it treats all regions of index space equally: a small box in index space about origin @ijk=(0,0,0) is mapped (sheared, scaled, rotated, etc.) in just the same way that a small box about any other index point is mapped. @code // Create a linear transform from a 4x4 matrix (identity in this example). openvdb::math::Mat4d mat = openvdb::math::Mat4d::identity(); openvdb::math::Transform::Ptr linearTransform = openvdb::math::Transform::createLinearTransform(mat); // Rotate the transform by 90 degrees about the X axis. // As a result the j-index will now map into the -z physical direction, // and the k-index will map to the +y physical direction. linearTransform->preRotate(M_PI/2, openvdb::math::X_AXIS); @endcode @subsection sFrustumTransforms Frustum Transforms The frustum transform is a nonlinear transform that treats different index points differently. And while the linear transform can be applied to any point in index or world space, the frustum transform is designed to operate on a subset of space. Specifically, it transforms a given box in index space to a tapered box in world space that is a frustum of a rectangular pyramid. @code // Create the bounding box that will be mapped by the transform into // the shape of a frustum. // The points (0,0,0), (100,0,0), (0,50,0) and (100,50,0) will map to // the corners of the near plane of the frustum, while the corners // of the far plane will be the images of (0,0,120), (100,0,120), // (0,50,120) and (100,50,120). const openvdb::math::BBoxd bbox(/*min=*/openvdb::math::Vec3d(0,0,0), /*max=*/openvdb::math::Vec3d(100,50,120)); // The far plane of the frustum will be twice as big as the near plane. const double taper = 2; // The depth of the frustum will be 10 times the x-width of the near plane. cosnt double depth = 10; // The x-width of the frustum in world space units const double xWidth = 100; // Construct a frustum transform that results in a frustum whose // near plane is centered on the origin in world space. openvdb::math::Transform::Ptr frustumTransform = openvdb::math:::Transform::createFrustumTransform( bbox, taper, depth, xWidth); // The frustum shape can be rotated, scaled, translated and even // sheared if desired. For example, the following call translates // the frustum by 10,15,0 in world space: frustumTransform->postTranslate(openvdb::math::Vec3d(10,15,0)); // Compute the world space image of a given point within // the index space bounding box that defines the frustum. openvdb::Coord ijk(20,10,18); openvdb::Vec3d worldLocation = frustumTransform->indexToWorld(ijk); @endcode @subsection sCellVsVertex Cell-Centered vs. Vertex-Centered Transforms When partitioning world space with a regular grid, two popular configurations are cell-centered and vertex-centered grids. This is really a question of interpretation and transforms. The cell-centered interpretation imagines world space as divided into discrete cells (e.g., cubes) centered on the image of the index-space lattice points. So the physical location @xyz that is the image (result of @c indexToWorld) of a lattice point @ijk is the center of a cube. In the vertex-centered approach, the images of the lattice points form the vertices of cells, so the location @xyz would be a corner, not the center, of a cube. The link between transforms and cell-centered or vertex-centered partitioning of world space is tenuous. It boils down to this: the “first” cell vertex often is aligned with the origin. In the cell-centered case, when the cells are cubes of length @f$\Delta@f$ on a side, the transform @f$(x,y,z) = (\Delta i + \Delta/2, \Delta j + \Delta/2, \Delta k + \Delta /2 )@f$ will place the center of the first cube (i.e., the image of @f$(0,0,0)@f$) at the world space location of @f$(\Delta/2, \Delta/2, \Delta/2)@f$, and the cube will have walls coincident with the @f$x=0@f$, @f$y=0@f$ and @f$z=0@f$ planes. Using the OpenVDB transforms to create a so-called cell-centered transform could be done like this: @code // -- Constructing a uniform, cell-centered transform -- // The grid spacing const double delta = 0.1; // The offset to cell-center points const openvdb::math::Vec3d offset(delta/2., delta/2., delta/2.); // A linear transform with the correct spacing openvdb::math::Transform::Ptr transform = openvdb::math:::Transform::createLinearTransform(delta); // Add the offset. transform->postTranslate(offset); @endcode In contrast, for the vertex-centered partitions of space the first vertex is just the image of the first lattice point @f$ijk = (0,0,0)@f$, and the transform would lack the offset used in the cell-centered case; so it would simply be @f$(x,y,z) = (\Delta i , \Delta j ,\Delta k)@f$ @code // -- Constructing a uniform, vertex-centered transform -- // The grid spacing const double delta = 0.1; // A linear transform with the correct spacing openvdb::math::Transform::Ptr transform = openvdb::math:::Transform::createLinearTransform(delta); @endcode @subsection sVoxels Voxel Interpretations A similar and often related concept to cell- and vertex-centered partitioning of world space is the idea of a voxel. A voxel historically refers to the volumetric equivalent of a pixel and as such implies a small region of world space. A voxel could, for instance, be the image under transform of a vertex-centered (or cell-centered) box in index space. In this way the voxel can be seen as a generalization of regular grid cells. The interpretation of data stored in a grid can be related to the concept of a voxel but need not be. An application might interpret the grid value indexed by @ijk as the spatial average of a quantity in a defined world-space voxel centered on the image of that lattice point. But in other situations the value stored at @ijk might be a discrete sample taken from a single point in world space. The @vdblink::math::Transform Transform@endlink class does include methods such as @vdblink::math::Transform::voxelSize() voxelSize@endlink and @vdblink::math::Transform::voxelVolume() voxelVolume@endlink that suppose a particular interpretation of a voxel. They assume a voxel that is the image of a vertex-centered cube in index space, so the @c voxelSize methods return the lengths of line segments in world space that connect vertices: @code openvdb::Coord ijk(0,0,0); openvdb::Coord tmp0(1,0,0), tmp1(0,1,0), tmp2(0,0,1); openvdb::math::Vec3d size; size.x() = (xform.indexToWorld(ijk + tmp0) - xform.indexToWorld(ijk)).length(); size.y() = (xform.indexToWorld(ijk + tmp1) - xform.indexToWorld(ijk)).length(); size.z() = (xform.indexToWorld(ijk + tmp2) - xform.indexToWorld(ijk)).length(); // The voxelSize() for the voxel at (0,0,0) is consistent with // the computation above. assert(xform.voxelSize(ijk) == size); @endcode In the case where the transform is linear, the result of @c voxelSize will be independent of the actual location @ijk, but the voxel size for a nonlinear transform such as a frustum will be spatially varying. The related @c voxelVolume can not in general be computed from the @c voxelSize, because there is no guarantee that a general transform will convert a cube-shaped voxel into another cube. As a result, the @c voxelVolume actually returns the determinant of the transform, which will be a correct measure of volume even if the voxel is sheared into a general parallelepiped. @subsection sStaggered Staggered Velocity Grids Staggered velocity data is often used in fluid simulations, and the relationship between data interpretation and transforms is somewhat peculiar when using a vector grid to hold staggered velocity data. In this case the lattice point @ijk identifies a cell in world space by mapping to the cell’s center, but each element of the velocity vector is identified with a different face of the cell. The first element of the vector is identified with the image of the @f$(i-1/2,j,k)@f$ face, the second element with @f$(i,j-1/2,k)@f$, and the third element with @f$(i,j,k-1/2)@f$. @section sMaps Maps in OpenVDB Transforms The actual work of a @vdblink::math::Transform Transform@endlink is performed by an implementation object called a @vdblink::math::MapBase Map@endlink. The map in turn is a polymorphic object whose derived types are designed to optimally represent the most common transformations. Specifically, the @vdblink::math::Transform Transform@endlink holds a @vdblink::math::MapBase MapBase@endlink pointer to a derived type that has been simplified to minimize calculations. When the transform is updated by prepending or appending a linear operation (e.g., with @vdblink::math::Transform::preRotate() preRotate@endlink), the implementation map is recomputed and simplified if possible. For example, in many level-set-oriented applications the transform between index space and world space is simply a uniform scaling of the index points, i.e., @f$(x,y,z) = (\Delta i, \Delta j, \Delta k)@f$, where @f$\Delta@f$ has some world space units. For transforms such as this, the implementation is a @vdblink::math::UniformScaleMap UniformScaleMap@endlink. @code // Create a linear transform that scales i, j and k by 0.1 openvdb::math::Transform::Ptr linearTransform = openvdb::math::Transform::createLinearTransform(0.1); // Create an equivalent map. openvdb::math::UniformScaleMap uniformScale(0.1); // At this time the map holds a openvdb::math::UniformScaleMap. assert(linearTransform->mapType() == openvdb::math::UniformScaleMap::type()); openvdb::Coord ijk(1,2,3); // Applying the transform... openvdb::math::Vec3d transformResult = linearTransform->indexToWorld(ijk); // ...is equivalent to applying the map. openvdb::math::Vec3d mapResult = uniformScale.applyMap(ijk); assert(mapResult == transformResult); @endcode There are specialized maps for a variety of common linear transforms: pure translation (@vdblink::math::TranslationMap TranslationMap@endlink), uniform scale (@vdblink::math::UniformScaleMap UniformScaleMap@endlink), uniform scale and translation (@vdblink::math::UniformScaleTranslateMap UniformScaleTranslateMap@endlink), non-uniform scale (@vdblink::math::ScaleMap ScaleMap@endlink), and non-uniform scale and translation (@vdblink::math::ScaleTranslateMap ScaleTranslateMap@endlink). A general affine map (@vdblink::math::AffineMap AffineMap@endlink) is used for all other cases (those that include non-degenerate rotation or shear). @subsection sGettingMat4 An Equivalent Matrix Representation The matrix representation used within OpenVDB adheres to the minority convention of right-multiplication of the matrix against a vector: @code // Example matrix transform that scales, next translates, // and finally rotates an incoming vector openvdb::math::Mat4d transform = openvdb::math::Mat4d::identity(); transform.preScale(openvdb::math::Vec3d(2,3,2)); transform.postTranslate(openvdb::math::Vec3d(1,0,0)); transform.postRotate(openvdb::math::X_AXIS, M_PI/3.0); // Location of a point in index space openvdb::math::Vec3d indexSpace(1,2,3); // Apply the transform by right-multiplying the matrix. openvdb::math::Vec3d worldSpace = indexSpace * transform; @endcode Any linear map can produce an equivalent @vdblink::math::AffineMap AffineMap@endlink, which in turn can produce an equivalent @f$4 \times 4@f$ matrix. Starting with a linear transform one can produce a consistent matrix as follows: @code openvdb::math::Mat4d matrix; if (transform->isLinear()) { // Get the equivalent 4x4 matrix. matrix = transform->getBaseMap()->getAffineMap()->getMat4(); } @endcode This could be used as an intermediate form when constructing new linear transforms by combining old ones. @code // Get the matrix equivalent to linearTransformA. openvdb::math::Mat4d matrixA = linearTransformA->getBaseMap()->getAffineMap()->getMat4(); // Invert the matrix equivalent to linearTransformB. openvdb::math::Mat4d matrixBinv = (linearTransformB->getBaseMap()->getAffineMap()->getMat4()).inverse(); // Create a new transform that maps the index space of linearTransformA // to the index space of linearTransformB. openvdb::math::Transform::Ptr linearTransformAtoB = openvdb::math::Trasform::createLinearTransform(matrixA * matrixBinv); @endcode Notice that in the above example, the internal representation used by the transform will be simplified if possible to use one of the various map types. @subsection sCostOfMaps Working Directly with Maps Accessing a transform’s map through virtual function calls introduces some overhead and precludes compile-time optimizations such as inlining. For this reason, the more computationally intensive OpenVDB tools are templated to work directly with any underlying map. This allows the tools direct access to the map’s simplified representation and gives the compiler a free hand to inline. Maps themselves know nothing of index space and world space, but are simply functions @f$x_{range} = f(x_{domain})@f$ that map 3-vectors from one space (the domain) to another (the range), or from the range back to the domain (@f$x_{domain} = f^{-1}(x_{range})@f$), by means of the methods @vdblink::math::MapBase::applyMap() applyMap@endlink and @vdblink::math::MapBase::applyInverseMap() applyInverseMap@endlink. @code // Create a simple uniform scale map that scales by 10. openvdb::math::UniformScaleMap usm(10.0); // A point in the domain openvdb::math::Vec3d domainPoint(0,1,3); // The resulting range point openvdb::math::Vec3d rangePoint = usm.applyMap(domainPoint); // The map is inverted thus: assert(domainPoint == usm.applyInverseMap(rangePoint)); @endcode In many tools, the actual map type is not known a priori and must be deduced at runtime prior to calling the appropriate map-specific or map-templated code. The type of map currently being used by a transform can be determined using the @vdblink::math::Transform::mapType() mapType@endlink method: @code // Test for a uniform scale map. if (transform->mapType() == openvdb::math::UniformScaleMap::type()) { // This call would return a null pointer in the case of a map type mismatch. openvdb::math::UniformScaleMap::ConstPtr usm = transform->map(); // Call a function that accepts UniformScaleMaps. dofoo(*usm) } @endcode To simplify this process, the function @vdblink::math::processTypedMap processTypedMap@endlink has been provided. It accepts a transform and a functor templated on the map type. @subsection sGradientAndMaps Maps and Mathematical Operations In addition to supporting the mapping of a point from one space to another, maps also support mapping of local gradients. This results from use of the chain rule of calculus in computing the Jacobian of the map. Essentially, the gradient calculated in the domain of a map can be converted to the gradient in the range of the map, allowing one to compute a gradient in index space and then transform it to a gradient in world space. @code // Compute the gradient at a point in index space in a // floating-point grid using the second-order central difference. openvdb::FloatGrid grid = ...; openvdb::Coord ijk(2,3,5) openvdb::math::Vec3d isGrad = openvdb::math::ISGradient::result(grid, ijk); // Apply the inverse Jacobian transform to convert the result to the // gradient in the world space defined by a map that scales index space // to create voxels of size 0.1 x 0.2 x 0.1 openvdb::math::ScaleMap scalemap(0.1, 0.2, 0.1); openvdb::math::Vec3d wsGrad = scalemap.applyIJT(isGrad); @endcode */ openvdb/version.h0000644000000000000000000001331512603226506013054 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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" /// The version namespace name for this library version /// /// Fully-namespace-qualified symbols are named as follows: /// openvdb::vX_Y_Z::Vec3i, openvdb::vX_Y_Z::io::File, openvdb::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 v3_1_0 // Library major, minor and patch version numbers #define OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER 3 #define OPENVDB_LIBRARY_MINOR_VERSION_NUMBER 1 #define OPENVDB_LIBRARY_PATCH_VERSION_NUMBER 0 /// @brief Library version number string of the form ".." /// @details This is a macro rather than a static constant because we typically /// want the compile-time version number, not the runtime version number /// (although the two are usually the same). #define OPENVDB_LIBRARY_VERSION_STRING "3.1.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., openvdb::v1_0_0::io::File /// can be referred to simply as openvdb::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 = 223; /// 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, OPENVDB_FILE_VERSION_BLOSC_COMPRESSION = 223, OPENVDB_FILE_VERSION_POINT_INDEX_GRID = 223 }; /// Return a library version number string of the form "..". inline const char* getLibraryVersionString() { return OPENVDB_LIBRARY_VERSION_STRING; } struct VersionId { uint32_t first, second; VersionId(): first(0), second(0) {} VersionId(uint32_t major, uint32_t minor): first(major), second(minor) {} }; } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_VERSION_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Metadata.h0000644000000000000000000000373512603226506013114 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/README0000644000000000000000000000116012603226302012063 0ustar rootroot======================================================================== OpenVDB ======================================================================== The OpenVDB library comprises a hierarchical data structure and a suite of tools for the efficient manipulation of sparse, possibly time-varying, volumetric data discretized on a three-dimensional grid. For instructions on library installation and dependencies see INSTALL For documentation of the library and code examples see: www.openvdb.org/documentation/doxygen For more details visit the project's home page: www.openvdb.org openvdb/tree/0000755000000000000000000000000012603226506012152 5ustar rootrootopenvdb/tree/LeafNode.h0000644000000000000000000024151412603226506014007 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #include // for io::readData(), etc. #include "Iterator.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; }; #ifndef OPENVDB_2_ABI_COMPATIBLE struct FileInfo { FileInfo(): bufpos(0) , maskpos(0) {} std::streamoff bufpos; std::streamoff maskpos; io::MappedFile::Ptr mapping; boost::shared_ptr meta; }; #endif /// @brief Array of fixed size @f$2^{3 \times {\rm Log2Dim}}@f$ that stores /// the voxel values of a LeafNode class Buffer { public: #ifdef OPENVDB_2_ABI_COMPATIBLE /// Default constructor Buffer(): mData(new ValueType[SIZE]) {} /// Construct a buffer populated with the specified value. explicit Buffer(const ValueType& val): mData(new ValueType[SIZE]) { this->fill(val); } /// Copy constructor Buffer(const Buffer& other): mData(new ValueType[SIZE]) { *this = other; } /// Destructor ~Buffer() { delete[] mData; } /// Return @c true if this buffer's values have not yet been read from disk. bool isOutOfCore() const { return false; } /// Return @c true if memory for this buffer has not yet been allocated. bool empty() const { return (mData == NULL); } #else typedef ValueType WordType; static const Index WORD_COUNT = SIZE; /// Default constructor Buffer(): mData(new ValueType[SIZE]), mOutOfCore(0) {} /// Construct a buffer populated with the specified value. explicit Buffer(const ValueType& val): mData(new ValueType[SIZE]), mOutOfCore(0) { this->fill(val); } /// Copy constructor Buffer(const Buffer& other): mData(NULL), mOutOfCore(other.mOutOfCore) { if (other.isOutOfCore()) { mFileInfo = new FileInfo(*other.mFileInfo); } else { this->allocate(); ValueType* target = mData; const ValueType* source = other.mData; Index n = SIZE; while (n--) *target++ = *source++; } } /// Construct a buffer but don't allocate memory for the full array of values. Buffer(PartialCreate, const ValueType&): mData(NULL), mOutOfCore(0) {} /// Destructor ~Buffer() { if (this->isOutOfCore()) { this->detachFromFile(); } else { this->deallocate(); } } /// Return @c true if this buffer's values have not yet been read from disk. bool isOutOfCore() const { return bool(mOutOfCore); } /// Return @c true if memory for this buffer has not yet been allocated. bool empty() const { return !mData || this->isOutOfCore(); } #endif /// Allocate memory for this buffer if it has not already been allocated. bool allocate() { if (mData == NULL) mData = new ValueType[SIZE]; return !this->empty(); } /// Populate this buffer with a constant value. void fill(const ValueType& val) { this->detachFromFile(); if (mData != NULL) { ValueType* target = mData; Index n = SIZE; while (n--) *target++ = val; } } /// Return a const reference to the i'th element of this buffer. const ValueType& getValue(Index i) const { return this->at(i); } /// Return a const reference to the i'th element of this buffer. const ValueType& operator[](Index i) const { return this->at(i); } /// Set the i'th value of this buffer to the specified value. void setValue(Index i, const ValueType& val) { assert(i < SIZE); #ifdef OPENVDB_2_ABI_COMPATIBLE mData[i] = val; #else this->loadValues(); if (mData) mData[i] = val; #endif } /// Copy the other buffer's values into this buffer. Buffer& operator=(const Buffer& other) { if (&other != this) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (this->isOutOfCore()) { this->detachFromFile(); } else { if (other.isOutOfCore()) this->deallocate(); } if (other.isOutOfCore()) { mOutOfCore = other.mOutOfCore; mFileInfo = new FileInfo(*other.mFileInfo); } else { #endif this->allocate(); ValueType* target = mData; const ValueType* source = other.mData; Index n = SIZE; while (n--) *target++ = *source++; #ifndef OPENVDB_2_ABI_COMPATIBLE } #endif } return *this; } /// @brief Return @c true if the contents of the other buffer /// exactly equal the contents of this buffer. bool operator==(const Buffer& other) const { this->loadValues(); other.loadValues(); const ValueType *target = mData, *source = other.mData; if (!target && !source) return true; if (!target || !source) return false; Index n = SIZE; while (n && math::isExactlyEqual(*target++, *source++)) --n; return n == 0; } /// @brief Return @c true if the contents of the other buffer /// are not exactly equal to the contents of this buffer. bool operator!=(const Buffer& other) const { return !(other == *this); } /// Exchange this buffer's values with the other buffer's values. void swap(Buffer& other) { std::swap(mData, other.mData); #ifndef OPENVDB_2_ABI_COMPATIBLE std::swap(mOutOfCore, other.mOutOfCore); #endif } /// Return the memory footprint of this buffer in bytes. Index memUsage() const { size_t n = sizeof(*this); #ifdef OPENVDB_2_ABI_COMPATIBLE if (mData) n += SIZE * sizeof(ValueType); #else if (this->isOutOfCore()) n += sizeof(FileInfo); else if (mData) n += SIZE * sizeof(ValueType); #endif return static_cast(n); } /// Return the number of values contained in this buffer. static Index size() { return SIZE; } /// @brief Return a const pointer to the array of voxel values. /// @details This method guarantees that the buffer is allocated and loaded. /// @warning This method should only be used by experts seeking low-level optimizations. const ValueType* data() const { #ifndef OPENVDB_2_ABI_COMPATIBLE this->loadValues(); if (mData == NULL) { Buffer* self = const_cast(this); // Since this method is const we need a lock (which // will be contended at most once) to make it thread-safe. tbb::spin_mutex::scoped_lock lock(self->mMutex); if (mData == NULL) self->mData = new ValueType[SIZE]; } #endif return mData; } /// @brief Return a pointer to the array of voxel values. /// @details This method guarantees that the buffer is allocated and loaded. /// @warning This method should only be used by experts seeking low-level optimizations. ValueType* data() { #ifndef OPENVDB_2_ABI_COMPATIBLE this->loadValues(); if (mData == NULL) mData = new ValueType[SIZE]; #endif return mData; } private: /// If this buffer is empty, return zero, otherwise return the value at index @ i. const ValueType& at(Index i) const { assert(i < SIZE); #ifdef OPENVDB_2_ABI_COMPATIBLE return mData[i]; #else this->loadValues(); // We can't use the ternary operator here, otherwise Visual C++ returns // a reference to a temporary. if (mData) return mData[i]; else return sZero; #endif } /// @brief Return a non-const reference to the value at index @a i. /// @details This method is private since it makes assumptions about the /// buffer's memory layout. Buffers associated with custom leaf node types /// (e.g., a bool buffer implemented as a bitmask) might not be able to /// return non-const references to their values. ValueType& operator[](Index i) { return const_cast(this->at(i)); } bool deallocate() { if (mData != NULL && !this->isOutOfCore()) { delete[] mData; mData = NULL; return true; } return false; } #ifdef OPENVDB_2_ABI_COMPATIBLE void setOutOfCore(bool) {} void loadValues() const {} void doLoad() const {} bool detachFromFile() { return false; } #else inline void setOutOfCore(bool b) { mOutOfCore = b; } // To facilitate inlining in the common case in which the buffer is in-core, // the loading logic is split into a separate function, doLoad(). inline void loadValues() const { if (this->isOutOfCore()) this->doLoad(); } inline void doLoad() const; inline bool detachFromFile() { if (this->isOutOfCore()) { delete mFileInfo; mFileInfo = NULL; this->setOutOfCore(false); return true; } return false; } #endif friend class ::TestLeaf; // Allow the parent LeafNode to access this buffer's data pointer. friend class LeafNode; #ifdef OPENVDB_2_ABI_COMPATIBLE ValueType* mData; #else union { ValueType* mData; FileInfo* mFileInfo; }; Index32 mOutOfCore; // currently interpreted as bool; extra bits reserved for future use tbb::spin_mutex mMutex; // 1 byte //int8_t mReserved[3]; // padding for alignment static const ValueType sZero; #endif }; // 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); #ifndef OPENVDB_2_ABI_COMPATIBLE /// @brief "Partial creation" constructor used during file input /// @param coords the grid index coordinates of a voxel /// @param value a value with which to fill the buffer /// @param active the active state to which to initialize all voxels /// @details This constructor does not allocate memory for voxel values. LeafNode(PartialCreate, const Coord& coords, const ValueType& value = zeroVal(), bool active = false); #endif /// Deep copy constructor LeafNode(const LeafNode&); /// 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(); } #ifndef OPENVDB_2_ABI_COMPATIBLE /// Return @c true if memory for this node's buffer has been allocated. bool isAllocated() const { return !mBuffer.isOutOfCore() && !mBuffer.empty(); } /// Allocate memory for this node's buffer if it has not already been allocated. bool allocate() { return mBuffer.allocate(); } #endif /// Return the memory in bytes occupied by this node. Index64 memUsage() const; /// Expand the given bounding box so that it includes this leaf node's active voxels. /// If visitVoxels is false this LeafNode will be approximated as dense, i.e. with all /// voxels active. Else the individual active voxels are visited to produce a tight bbox. void evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels = true) const; /// @brief Return the bounding box of this node, i.e., the full index space /// spanned by this leaf node. CoordBBox getNodeBoundingBox() const { return CoordBBox::createCube(mOrigin, DIM); } /// Set the grid index coordinates of this node's local origin. void setOrigin(const Coord& origin) { mOrigin = origin; } //@{ /// Return the grid index coordinates of this node's local origin. const Coord& origin() const { return mOrigin; } void getOrigin(Coord& origin) const { origin = mOrigin; } void getOrigin(Int32& x, Int32& y, Int32& z) const { mOrigin.asXYZ(x, y, z); } //@} /// Return the linear table offset of the given global or local coordinates. static Index coordToOffset(const Coord& xyz); /// @brief Return the local coordinates for a linear table offset, /// where offset 0 has coordinates (0, 0, 0). static Coord offsetToLocalCoord(Index n); /// Return the global coordinates for a linear table offset. Coord offsetToGlobalCoord(Index n) const; /// Return a string representation of this node. std::string str() const; /// @brief Return @c true if the given node (which may have a different @c ValueType /// than this node) has the same active value topology as this node. template bool hasSameTopology(const LeafNode* other) const; /// Check for buffer, state and origin equivalence. bool operator==(const LeafNode& other) const; bool operator!=(const LeafNode& other) const { return !(other == *this); } protected: 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 Read buffers that intersect the given bounding box. /// @param is the stream from which to read /// @param bbox an index-space bounding box /// @param fromHalf if true, floating-point input values are assumed to be 16-bit void readBuffers(std::istream& is, const CoordBBox& bbox, bool fromHalf = false); /// @brief Write buffers to a stream. /// @param os the stream to which to write /// @param toHalf if true, output floating-point values as 16-bit half floats void writeBuffers(std::ostream& os, bool toHalf = false) const; size_t streamingSize(bool toHalf = false) const; // // Accessor methods // /// Return the value of the voxel at the given coordinates. const ValueType& getValue(const Coord& xyz) const; /// Return the value of the voxel at the given linear offset. const ValueType& getValue(Index offset) const; /// @brief Return @c true if the voxel at the given coordinates is active. /// @param xyz the coordinates of the voxel to be probed /// @param[out] val the value of the voxel at the given coordinates bool probeValue(const Coord& xyz, ValueType& val) const; /// @brief Return @c true if the voxel at the given offset is active. /// @param offset the linear offset of the voxel to be probed /// @param[out] val the value of the voxel at the given coordinates bool probeValue(Index offset, ValueType& val) const; /// Return the level (i.e., 0) at which leaf node values reside. static Index getValueLevel(const Coord&) { return LEVEL; } /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on); /// Set the active state of the voxel at the given offset but don't change its value. void setActiveState(Index offset, bool on) { assert(offsetsetValueOn(LeafNode::coordToOffset(xyz), val); } /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, const ValueType& val) { this->setValueOn(xyz, val); } /// Set the value of the voxel at the given offset and mark the voxel as active. void setValueOn(Index offset, const ValueType& val) { mBuffer.setValue(offset, val); mValueMask.setOn(offset); } /// @brief Apply a functor to the value of the voxel at the given offset /// and mark the voxel as active. template void modifyValue(Index offset, const ModifyOp& op) { ValueType val = mBuffer[offset]; op(val); mBuffer.setValue(offset, val); mValueMask.setOn(offset); } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. template void modifyValue(const Coord& xyz, const ModifyOp& op) { this->modifyValue(this->coordToOffset(xyz), op); } /// Apply a functor to the voxel at the given coordinates. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { const Index offset = this->coordToOffset(xyz); bool state = mValueMask.isOn(offset); ValueType val = mBuffer[offset]; op(val, state); mBuffer.setValue(offset, val); mValueMask.set(offset, state); } /// Mark all voxels as active but don't change their values. void setValuesOn() { mValueMask.setOn(); } /// Mark all voxels as inactive but don't change their values. void setValuesOff() { mValueMask.setOff(); } /// Return @c true if the voxel at the given coordinates is active. bool isValueOn(const Coord& xyz) const {return this->isValueOn(LeafNode::coordToOffset(xyz));} /// Return @c true if the voxel at the given offset is active. bool isValueOn(Index offset) const { return mValueMask.isOn(offset); } /// Return @c false since leaf nodes never contain tiles. static bool hasActiveTiles() { return false; } /// Set all voxels that lie outside the given axis-aligned box to the background. void clip(const CoordBBox&, const ValueType& background); /// Set all voxels within an axis-aligned box to the specified value and active state. void fill(const CoordBBox& bbox, const ValueType&, bool active = true); /// Set all voxels to the specified value but don't change their active states. void fill(const ValueType& value); /// Set all voxels to the specified value and active state. void fill(const ValueType& value, bool active); /// @brief Copy into a dense grid the values of the voxels that lie within /// a given bounding box. /// /// @param bbox inclusive bounding box of the voxels to be copied into the dense grid /// @param dense dense grid with a stride in @e z of one (see tools::Dense /// in tools/Dense.h for the required API) /// /// @note @a bbox is assumed to be identical to or contained in the coordinate domains /// of both the dense grid and this node, i.e., no bounds checking is performed. /// @note Consider using tools::CopyToDense in tools/Dense.h /// instead of calling this method directly. template void copyToDense(const CoordBBox& bbox, DenseT& dense) const; /// @brief Copy from a dense grid into this node the values of the voxels /// that lie within a given bounding box. /// @details Only values that are different (by more than the given tolerance) /// from the background value will be active. Other values are inactive /// and truncated to the background value. /// /// @param bbox inclusive bounding box of the voxels to be copied into this node /// @param dense dense grid with a stride in @e z of one (see tools::Dense /// in tools/Dense.h for the required API) /// @param background background value of the tree that this node belongs to /// @param tolerance tolerance within which a value equals the background value /// /// @note @a bbox is assumed to be identical to or contained in the coordinate domains /// of both the dense grid and this node, i.e., no bounds checking is performed. /// @note Consider using tools::CopyFromDense in tools/Dense.h /// instead of calling this method directly. template void copyFromDense(const CoordBBox& bbox, const DenseT& dense, const ValueType& background, const ValueType& tolerance); /// @brief Return the value of the voxel at the given coordinates. /// @note Used internally by ValueAccessor. template const ValueType& getValueAndCache(const Coord& xyz, AccessorT&) const { return this->getValue(xyz); } /// @brief Return @c true if the voxel at the given coordinates is active. /// @note Used internally by ValueAccessor. template bool isValueOnAndCache(const Coord& xyz, AccessorT&) const { return this->isValueOn(xyz); } /// @brief Change the value of the voxel at the given coordinates and mark it as active. /// @note Used internally by ValueAccessor. template void setValueAndCache(const Coord& xyz, const ValueType& val, AccessorT&) { this->setValueOn(xyz, val); } /// @brief Change the value of the voxel at the given coordinates /// but preserve its state. /// @note Used internally by ValueAccessor. template void setValueOnlyAndCache(const Coord& xyz, const ValueType& val, AccessorT&) { this->setValueOnly(xyz, val); } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @note Used internally by ValueAccessor. template void modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&) { this->modifyValue(xyz, op); } /// Apply a functor to the voxel at the given coordinates. /// @note Used internally by ValueAccessor. template void modifyValueAndActiveStateAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&) { this->modifyValueAndActiveState(xyz, op); } /// @brief Change the value of the voxel at the given coordinates and mark it as inactive. /// @note Used internally by ValueAccessor. template void setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT&) { this->setValueOff(xyz, value); } /// @brief Set the active state of the voxel at the given coordinates /// without changing its value. /// @note Used internally by ValueAccessor. template void setActiveStateAndCache(const Coord& xyz, bool on, AccessorT&) { this->setActiveState(xyz, on); } /// @brief Return @c true if the voxel at the given coordinates is active /// and return the voxel value in @a val. /// @note Used internally by ValueAccessor. template bool probeValueAndCache(const Coord& xyz, ValueType& val, AccessorT&) const { return this->probeValue(xyz, val); } /// @brief Return the value of the voxel at the given coordinates and return /// its active state and level (i.e., 0) in @a state and @a level. /// @note Used internally by ValueAccessor. template const ValueType& getValue(const Coord& xyz, bool& state, int& level, AccessorT&) const { const Index offset = this->coordToOffset(xyz); state = mValueMask.isOn(offset); level = LEVEL; return mBuffer[offset]; } /// @brief Return the LEVEL (=0) at which leaf node values reside. /// @note Used internally by ValueAccessor (note last argument is a dummy). template static Index getValueLevelAndCache(const Coord&, AccessorT&) { return LEVEL; } /// @brief Return a const reference to the first value in the buffer. /// @note Though it is potentially risky you can convert this /// to a non-const pointer by means of const_case&. const ValueType& getFirstValue() const { return mBuffer[0]; } /// Return a const reference to the last value in the buffer. const ValueType& getLastValue() const { return mBuffer[SIZE - 1]; } /// @brief Replace inactive occurrences of @a oldBackground with @a newBackground, /// and inactive occurrences of @a -oldBackground with @a -newBackground. void resetBackground(const ValueType& oldBackground, const ValueType& newBackground); void negate(); 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. void prune(const ValueType& /*tolerance*/ = zeroVal()) {} 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; } template void getNodes(ArrayT&) const {} template void stealNodes(ArrayT&, const ValueType&, bool) {} //@} void addTile(Index level, const Coord&, const ValueType&, bool); void addTile(Index offset, const ValueType&, bool); template void addTileAndCache(Index, const Coord&, const ValueType&, bool, AccessorT&); //@{ /// @brief Return a pointer to this node. LeafNode* touchLeaf(const Coord&) { return this; } template LeafNode* touchLeafAndCache(const Coord&, AccessorT&) { return this; } template NodeT* probeNodeAndCache(const Coord&, AccessorT&) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (!(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 in the range this->getFirstValue() +/- @a tolerance. /// /// /// @param constValue Is updated with the first value of this leaf node. /// @param state Is updated with the state of all values IF method /// returns @c true. Else the value is undefined! /// @param tolerance The tolerance used to determine if values are /// approximatly equal to the for value. bool isConstant(ValueType& constValue, bool& state, const ValueType& tolerance = zeroVal()) const; /// Return @c true if all of this node's values have the same active state /// and are in the range (@a maxValue + @a minValue)/2 +/- @a tolerance. /// /// @param minValue Is updated with the minimum of all values IF method /// returns @c true. Else the value is undefined! /// @param maxValue Is updated with the maximum of all values IF method /// returns @c true. Else the value is undefined! /// @param state Is updated with the state of all values IF method /// returns @c true. Else the value is undefined! /// @param tolerance The tolerance used to determine if values are /// approximatly constant. bool isConstant(ValueType& minValue, ValueType& maxValue, bool& state, const ValueType& tolerance = zeroVal()) const; /// Return @c true if all of this node's values are inactive. bool isInactive() const { return mValueMask.isOff(); } protected: friend class ::TestLeaf; template friend class ::TestLeafIO; // During topology-only construction, access is needed // to protected/private members of other template instances. template friend class LeafNode; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; // Allow iterators to call mask accessor methods (see below). /// @todo Make mask accessors public? friend class IteratorBase; friend class IteratorBase; friend class IteratorBase; // Mask accessors public: bool isValueMaskOn(Index n) const { return mValueMask.isOn(n); } bool isValueMaskOn() const { return mValueMask.isOn(); } bool isValueMaskOff(Index n) const { return mValueMask.isOff(n); } bool isValueMaskOff() const { return mValueMask.isOff(); } const NodeMaskType& getValueMask() const { return mValueMask; } NodeMaskType& getValueMask() { return mValueMask; } 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); private: /// Buffer containing the actual data values Buffer mBuffer; /// Bitmask that determines which voxels are active NodeMaskType mValueMask; /// Global grid index coordinates (x,y,z) of the local origin of this node Coord mOrigin; }; // end of LeafNode class #ifndef OPENVDB_2_ABI_COMPATIBLE template const T LeafNode::Buffer::sZero = zeroVal(); #endif //////////////////////////////////////// //@{ /// 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))) { } #ifndef OPENVDB_2_ABI_COMPATIBLE template inline LeafNode::LeafNode(PartialCreate, const Coord& xyz, const ValueType& val, bool active): mBuffer(PartialCreate(), val), mValueMask(active), mOrigin(xyz & (~(DIM - 1))) { } #endif template inline LeafNode::LeafNode(const LeafNode &other): mBuffer(other.mBuffer), mValueMask(other.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.setValue(offset, val); mValueMask.setOff(offset); } template inline void LeafNode::setActiveState(const Coord& xyz, bool on) { mValueMask.set(this->coordToOffset(xyz), on); } template inline void LeafNode::setValueOnly(const Coord& xyz, const ValueType& val) { this->setValueOnly(LeafNode::coordToOffset(xyz), val); } template inline void LeafNode::setValueOnly(Index offset, const ValueType& val) { assert(offset inline void LeafNode::clip(const CoordBBox& clipBBox, const T& background) { CoordBBox nodeBBox = this->getNodeBoundingBox(); if (!clipBBox.hasOverlap(nodeBBox)) { // This node lies completely outside the clipping region. Fill it with the background. this->fill(background, /*active=*/false); } else if (clipBBox.isInside(nodeBBox)) { // This node lies completely inside the clipping region. Leave it intact. return; } // This node isn't completely contained inside the clipping region. // Set any voxels that lie outside the region to the background value. // Construct a boolean mask that is on inside the clipping region and off outside it. NodeMaskType mask; nodeBBox.intersect(clipBBox); Coord xyz; int &x = xyz.x(), &y = xyz.y(), &z = xyz.z(); for (x = nodeBBox.min().x(); x <= nodeBBox.max().x(); ++x) { for (y = nodeBBox.min().y(); y <= nodeBBox.max().y(); ++y) { for (z = nodeBBox.min().z(); z <= nodeBBox.max().z(); ++z) { mask.setOn(static_cast(this->coordToOffset(xyz))); } } } // Set voxels that lie in the inactive region of the mask (i.e., outside // the clipping region) to the background value. for (MaskOffIterator maskIter = mask.beginOff(); maskIter; ++maskIter) { this->setValueOff(maskIter.pos(), background); } } //////////////////////////////////////// template inline void LeafNode::fill(const CoordBBox& bbox, const ValueType& value, bool active) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->allocate()) return; #endif 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 { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->isAllocated()) return; #endif 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) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->allocate()) return; #endif 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); } //////////////////////////////////////// #ifndef OPENVDB_2_ABI_COMPATIBLE template inline void LeafNode::Buffer::doLoad() const { if (!this->isOutOfCore()) return; Buffer* self = const_cast(this); // This lock will be contended at most once, after which this buffer // will no longer be out-of-core. tbb::spin_mutex::scoped_lock lock(self->mMutex); if (!this->isOutOfCore()) return; boost::scoped_ptr info(self->mFileInfo); assert(info.get() != NULL); assert(info->mapping.get() != NULL); assert(info->meta.get() != NULL); /// @todo For now, we have to clear the mData pointer in order for allocate() to take effect. self->mData = NULL; self->allocate(); boost::shared_ptr buf = info->mapping->createBuffer(); std::istream is(buf.get()); io::setStreamMetadataPtr(is, info->meta, /*transfer=*/true); NodeMaskType mask; is.seekg(info->maskpos); mask.load(is); is.seekg(info->bufpos); io::readCompressedValues(is, self->mData, SIZE, mask, io::getHalfFloat(is)); self->setOutOfCore(false); } #endif //////////////////////////////////////// template inline void LeafNode::readBuffers(std::istream& is, bool fromHalf) { this->readBuffers(is, CoordBBox::inf(), fromHalf); } template inline void LeafNode::readBuffers(std::istream& is, const CoordBBox& clipBBox, bool fromHalf) { #ifndef OPENVDB_2_ABI_COMPATIBLE std::streamoff maskpos = is.tellg(); #endif // Read in the value mask. mValueMask.load(is); int8_t numBuffers = 1; if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { // Read in the origin. is.read(reinterpret_cast(&mOrigin), sizeof(Coord::ValueType) * 3); // Read in the number of buffers, which should now always be one. is.read(reinterpret_cast(&numBuffers), sizeof(int8_t)); } CoordBBox nodeBBox = this->getNodeBoundingBox(); if (!clipBBox.hasOverlap(nodeBBox)) { // This node lies completely outside the clipping region. // Read and discard its voxel values. Buffer temp; io::readCompressedValues(is, temp.mData, SIZE, mValueMask, fromHalf); mValueMask.setOff(); mBuffer.setOutOfCore(false); } else { #ifndef OPENVDB_2_ABI_COMPATIBLE // If this node lies completely inside the clipping region and it is being read // from a memory-mapped file, delay loading of its buffer until the buffer // is actually accessed. (If this node requires clipping, its buffer // must be accessed and therefore must be loaded.) io::MappedFile::Ptr mappedFile = io::getMappedFilePtr(is); const bool delayLoad = ((mappedFile.get() != NULL) && clipBBox.isInside(nodeBBox)); if (delayLoad) { mBuffer.setOutOfCore(true); mBuffer.mFileInfo = new FileInfo; mBuffer.mFileInfo->bufpos = is.tellg(); mBuffer.mFileInfo->mapping = mappedFile; // Save the offset to the value mask, because the in-memory copy // might change before the value buffer gets read. mBuffer.mFileInfo->maskpos = maskpos; mBuffer.mFileInfo->meta = io::getStreamMetadataPtr(is); // Read and discard voxel values. Buffer temp; io::readCompressedValues(is, temp.mData, SIZE, mValueMask, fromHalf); } else { #endif mBuffer.allocate(); io::readCompressedValues(is, mBuffer.mData, SIZE, mValueMask, fromHalf); mBuffer.setOutOfCore(false); // Get this tree's background value. T background = zeroVal(); if (const void* bgPtr = io::getGridBackgroundValuePtr(is)) { background = *static_cast(bgPtr); } this->clip(clipBBox, background); #ifndef OPENVDB_2_ABI_COMPATIBLE } #endif } if (numBuffers > 1) { // Read in and discard auxiliary buffers that were created with earlier // versions of the library. (Auxiliary buffers are not mask compressed.) const bool zipped = io::getDataCompression(is) & io::COMPRESS_ZIP; Buffer temp; for (int i = 1; i < numBuffers; ++i) { if (fromHalf) { io::HalfReader::isReal, T>::read(is, temp.mData, SIZE, zipped); } else { io::readData(is, temp.mData, SIZE, zipped); } } } } template inline void LeafNode::writeBuffers(std::ostream& os, bool toHalf) const { // Write out the value mask. mValueMask.save(os); mBuffer.loadValues(); io::writeCompressedValues(os, mBuffer.mData, SIZE, mValueMask, /*childMask=*/NodeMaskType(), toHalf); } //////////////////////////////////////// template inline bool LeafNode::operator==(const LeafNode& other) const { return mOrigin == other.mOrigin && mValueMask == other.mValueMask && mBuffer == other.mBuffer; } template inline Index64 LeafNode::memUsage() const { // Use sizeof(*this) to capture alignment-related padding // (but note that sizeof(*this) includes sizeof(mBuffer)). return sizeof(*this) + mBuffer.memUsage() - sizeof(mBuffer); } template inline void LeafNode::evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels) const { CoordBBox this_bbox = this->getNodeBoundingBox(); if (bbox.isInside(this_bbox)) return;//this LeafNode is already enclosed in the bbox if (ValueOnCIter iter = this->cbeginValueOn()) {//any active values? if (visitVoxels) {//use voxel granularity? this_bbox.reset(); for(; iter; ++iter) this_bbox.expand(this->offsetToLocalCoord(iter.pos())); this_bbox.translate(this->origin()); } bbox.expand(this_bbox); } } template template inline bool LeafNode::hasSameTopology(const LeafNode* other) const { assert(other); return (Log2Dim == OtherLog2Dim && mValueMask == other->getValueMask()); } template inline bool LeafNode::isConstant(ValueType& value, bool& state, const ValueType& tolerance) const { state = mValueMask.isOn(); if (!(state || mValueMask.isOff())) return false;// Are values neither active nor inactive? value = mBuffer[0]; for (Index i = 1; i < SIZE; ++i) { if ( !math::isApproxEqual(mBuffer[i], value, tolerance) ) return false; } return true; } template inline bool LeafNode::isConstant(ValueType& minValue, ValueType& maxValue, bool& state, const ValueType& tolerance) const { state = mValueMask.isOn(); if (!(state || mValueMask.isOff())) return false;// Are values neither active nor inactive? const T range = 2 * tolerance; minValue = maxValue = mBuffer[0]; for (Index i = 1; i < SIZE; ++i) {// early termination const T& v = mBuffer[i]; if (v < minValue) { if ((maxValue - v) > range) return false; minValue = v; } else if (v > maxValue) { if ((v - minValue) > range) return false; maxValue = v; } } return true; } //////////////////////////////////////// template inline void LeafNode::addTile(Index /*level*/, const Coord& xyz, const ValueType& val, bool active) { this->addTile(this->coordToOffset(xyz), val, active); } template inline void LeafNode::addTile(Index offset, const ValueType& val, bool active) { assert(offset < SIZE); setValueOnly(offset, val); setActiveState(offset, active); } template template inline void LeafNode::addTileAndCache(Index level, const Coord& xyz, const ValueType& val, bool active, AccessorT&) { this->addTile(level, xyz, val, active); } //////////////////////////////////////// template inline void LeafNode::resetBackground(const ValueType& oldBackground, const ValueType& newBackground) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->allocate()) return; #endif typename NodeMaskType::OffIterator iter; // For all inactive values... for (iter = this->mValueMask.beginOff(); iter; ++iter) { ValueType &inactiveValue = mBuffer[iter.pos()]; if (math::isApproxEqual(inactiveValue, oldBackground)) { inactiveValue = newBackground; } else if (math::isApproxEqual(inactiveValue, math::negative(oldBackground))) { inactiveValue = math::negative(newBackground); } } } template template inline void LeafNode::merge(const LeafNode& other) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->allocate()) return; #endif 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) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->allocate()) return; #endif OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (Policy != MERGE_ACTIVE_STATES_AND_NODES) return; if (!tileActive) return; // Replace all inactive values with the active tile value. for (typename NodeMaskType::OffIterator iter = mValueMask.beginOff(); iter; ++iter) { const Index n = iter.pos(); mBuffer[n] = tileValue; mValueMask.setOn(n); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template template inline void LeafNode::topologyUnion(const LeafNode& other) { mValueMask |= other.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() { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->allocate()) return; #endif for (Index i = 0; i < SIZE; ++i) { mBuffer[i] = -mBuffer[i]; } } //////////////////////////////////////// template template inline void LeafNode::combine(const LeafNode& other, CombineOp& op) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->allocate()) return; #endif CombineArgs args; for (Index i = 0; i < SIZE; ++i) { op(args.setARef(mBuffer[i]) .setAIsActive(mValueMask.isOn(i)) .setBRef(other.mBuffer[i]) .setBIsActive(other.mValueMask.isOn(i)) .setResultRef(mBuffer[i])); mValueMask.set(i, args.resultIsActive()); } } template template inline void LeafNode::combine(const ValueType& value, bool valueIsActive, CombineOp& op) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->allocate()) return; #endif CombineArgs args; args.setBRef(value).setBIsActive(valueIsActive); for (Index i = 0; i < SIZE; ++i) { op(args.setARef(mBuffer[i]) .setAIsActive(mValueMask.isOn(i)) .setResultRef(mBuffer[i])); mValueMask.set(i, args.resultIsActive()); } } //////////////////////////////////////// template template inline void LeafNode::combine2(const LeafNode& other, const OtherType& value, bool valueIsActive, CombineOp& op) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->allocate()) return; #endif CombineArgs args; args.setBRef(value).setBIsActive(valueIsActive); for (Index i = 0; i < SIZE; ++i) { op(args.setARef(other.mBuffer[i]) .setAIsActive(other.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) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->allocate()) return; #endif CombineArgs args; args.setARef(value).setAIsActive(valueIsActive); for (Index i = 0; i < SIZE; ++i) { op(args.setBRef(other.mBuffer[i]) .setBIsActive(other.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) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!this->allocate()) return; #endif 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/InternalNode.h0000644000000000000000000036102612603226506014715 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 /// /// @todo Multi-thred topologyDifference #ifndef OPENVDB_TREE_INTERNALNODE_HAS_BEEN_INCLUDED #define OPENVDB_TREE_INTERNALNODE_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include // for io::readData(), etc. #include // for Abs(), isExactlyEqual() #include #include #include "Iterator.h" #include "NodeUnion.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& fillValue, bool active = false); #ifndef OPENVDB_2_ABI_COMPATIBLE InternalNode(PartialCreate, const Coord&, const ValueType& fillValue, bool active = false); #endif /// @brief Deep copy constructor /// /// @note This method is multi-threaded! InternalNode(const InternalNode&); /// @brief Value conversion copy constructor /// /// @note This method is multi-threaded! template explicit InternalNode(const InternalNode& other); /// @brief Topology copy constructor /// /// @note This method is multi-threaded! template InternalNode(const InternalNode& other, const ValueType& background, TopologyCopy); /// @brief Topology copy constructor /// /// @note This method is multi-threaded! template InternalNode(const InternalNode& other, const ValueType& offValue, const ValueType& onValue, TopologyCopy); 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); } /// @warning This iterator will also visit child nodes so use isChildMaskOn to skip them! ValueOffCIter cbeginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } ValueAllCIter cbeginValueAll() const { return ValueAllCIter(mChildMask.beginOff(), this); } ValueOnCIter beginValueOn() const { return cbeginValueOn(); } /// @warning This iterator will also visit child nodes so use isChildMaskOn to skip them! ValueOffCIter beginValueOff() const { return cbeginValueOff(); } ValueAllCIter beginValueAll() const { return cbeginValueAll(); } ValueOnIter beginValueOn() { return ValueOnIter(mValueMask.beginOn(), this); } /// @warning This iterator will also visit child nodes so use isChildMaskOn to skip them! ValueOffIter beginValueOff() { return ValueOffIter(mValueMask.beginOff(), this); } ValueAllIter beginValueAll() { return ValueAllIter(mChildMask.beginOff(), this); } 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. /// /// @note This method also returns @c false if this node contains any child nodes. bool isConstant(ValueType& constValue, bool& state, const ValueType& tolerance = zeroVal()) const; /// Return @c true if all of this node's tables entries have /// the same active @a state and the values are in the range /// (@a maxValue + @a minValue)/2 +/- @a tolerance. /// /// @param minValue Is updated with the minimum of all values IF method /// returns @c true. Else the value is undefined! /// @param maxValue Is updated with the maximum of all values IF method /// returns @c true. Else the value is undefined! /// @param state Is updated with the state of all values IF method /// returns @c true. Else the value is undefined! /// @param tolerance The tolerance used to determine if values are /// approximatly constant. /// /// @note This method also returns @c false if this node contains any child nodes. bool isConstant(ValueType& minValue, ValueType& maxValue, bool& state, const ValueType& tolerance = zeroVal()) const; /// Return @c true if this node has no children and only contains inactive values. bool isInactive() const { return this->isChildMaskOff() && this->isValueMaskOff(); } /// Return @c true if the voxel at the given coordinates is active. bool isValueOn(const Coord& xyz) const; /// Return @c true if the voxel at the given offset is active. bool isValueOn(Index offset) const { return mValueMask.isOn(offset); } /// Return @c true if this node or any of its child nodes have any active tiles. bool hasActiveTiles() const; const ValueType& getValue(const Coord& xyz) const; bool probeValue(const Coord& xyz, ValueType& value) const; /// @brief Return the level of the tree (0 = leaf) at which the value /// at the given coordinates resides. Index getValueLevel(const Coord& xyz) const; /// @brief If the first entry in this node's table is a tile, return the tile's value. /// Otherwise, return the result of calling getFirstValue() on the child. const ValueType& getFirstValue() const; /// @brief If the last entry in this node's table is a tile, return the tile's value. /// Otherwise, return the result of calling getLastValue() on the child. const ValueType& getLastValue() const; /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on); /// Set the value of the voxel at the given coordinates but don't change its active state. void setValueOnly(const Coord& xyz, const ValueType& value); /// Mark the voxel at the given coordinates as active but don't change its value. void setValueOn(const Coord& xyz); /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValueOn(const Coord& xyz, const ValueType& value); /// Mark the voxel at the given coordinates as inactive but don't change its value. void setValueOff(const Coord& xyz); /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value); /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. template void modifyValue(const Coord& xyz, const ModifyOp& op); /// Apply a functor to the voxel at the given coordinates. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op); /// Return the value of the voxel at the given coordinates and, if necessary, update /// the accessor with pointers to the nodes along the path from the root node to /// the node containing the voxel. /// @note Used internally by ValueAccessor. template const ValueType& getValueAndCache(const Coord& xyz, AccessorT&) const; /// Return @c true if the voxel at the given coordinates is active and, if necessary, /// update the accessor with pointers to the nodes along the path from the root node /// to the node containing the voxel. /// @note Used internally by ValueAccessor. template bool isValueOnAndCache(const Coord& xyz, AccessorT&) const; /// Change the value of the voxel at the given coordinates and mark it as active. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// Set the value of the voxel at the given coordinate but preserves its active state. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueOnlyAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); /// Apply a functor to the voxel at the given coordinates. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void modifyValueAndActiveStateAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); /// Change the value of the voxel at the given coordinates and mark it as inactive. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// Set the active state of the voxel at the given coordinates without changing its value. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setActiveStateAndCache(const Coord& xyz, bool on, AccessorT&); /// Return, in @a value, the value of the voxel at the given coordinates and, /// if necessary, update the accessor with pointers to the nodes along /// the path from the root node to the node containing the voxel. /// @return @c true if the voxel at the given coordinates is active /// @note Used internally by ValueAccessor. template bool probeValueAndCache(const Coord& xyz, ValueType& value, AccessorT&) const; /// @brief Return the level of the tree (0 = leaf) at which the value /// at the given coordinates resides. /// /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template Index getValueLevelAndCache(const Coord& xyz, AccessorT&) const; /// Mark all values (both tiles and voxels) as active. void setValuesOn(); // // I/O // void writeTopology(std::ostream&, bool toHalf = false) const; void readTopology(std::istream&, bool fromHalf = false); void writeBuffers(std::ostream&, bool toHalf = false) const; void readBuffers(std::istream&, bool fromHalf = false); void readBuffers(std::istream&, const CoordBBox&, bool fromHalf = false); // // Aux methods // /// @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); /// 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; /// Set all voxels that lie outside the given axis-aligned box to the background. void clip(const CoordBBox&, const ValueType& background); /// @brief Reduce the memory footprint of this tree by replacing with tiles /// any nodes whose values are all the same (optionally to within a tolerance) /// and have the same active state. void prune(const ValueType& tolerance = zeroVal()); /// @brief Add the specified leaf to this node, possibly creating a child branch /// in the process. If the leaf node already exists, replace it. void addLeaf(LeafNodeType* leaf); /// @brief Same as addLeaf() except, if necessary, update the accessor with pointers /// to the nodes along the path from the root node to the node containing the coordinate. template void addLeafAndCache(LeafNodeType* leaf, AccessorT&); /// @brief Return a pointer to the node of type @c NodeT that contains voxel (x, y, z) /// and replace it with a tile of the specified value and state. /// If no such node exists, leave the tree unchanged and return @c 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 Adds all nodes of a certain type to a container with the following API: /// @code /// struct ArrayT { /// typedef value_type;// defines the type of nodes to be added to the array /// void push_back(value_type nodePtr);// method that add nodes to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// typedef LeafType* value_type; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.getNodes(array); /// @endcode template void getNodes(ArrayT& array); template void getNodes(ArrayT& array) const; //@} /// @brief Steals all nodes of a certain type from the tree and /// adds them to a container with the following API: /// @code /// struct ArrayT { /// typedef value_type;// defines the type of nodes to be added to the array /// void push_back(value_type nodePtr);// method that add nodes to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// typedef LeafType* value_type; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.stealNodes(array); /// @endcode template void stealNodes(ArrayT& array, const ValueType& value, bool state); /// @brief Change inactive tiles or voxels with value oldBackground to newBackground /// or -oldBackground to -newBackground. Active values are unchanged. void resetBackground(const ValueType& oldBackground, const ValueType& newBackground); /// @brief Return @c true if the given tree branch has the same node and active value /// topology as this tree branch (but possibly a different @c ValueType). template bool hasSameTopology(const InternalNode* other) const; protected: //@{ /// Allow iterators to call mask accessor methods (setValueMask(), setChildMask(), etc.). /// @todo Make mask accessors public? friend class IteratorBase; friend class IteratorBase; friend class IteratorBase; //@} /// @brief During topology-only construction, access is needed /// to protected/private members of other template instances. template friend class InternalNode; // Mask accessors public: bool isValueMaskOn(Index n) const { return mValueMask.isOn(n); } bool isValueMaskOn() const { return mValueMask.isOn(); } bool isValueMaskOff(Index n) const { return mValueMask.isOff(n); } bool isValueMaskOff() const { return mValueMask.isOff(); } bool isChildMaskOn(Index n) const { return mChildMask.isOn(n); } bool isChildMaskOff(Index n) const { return mChildMask.isOff(n); } bool isChildMaskOff() const { return mChildMask.isOff(); } const NodeMaskType& getValueMask() const { return mValueMask; } const NodeMaskType& getChildMask() const { return mChildMask; } NodeMaskType getValueOffMask() const { NodeMaskType mask = mValueMask; mask |= mChildMask; mask.toggle(); return mask; } const UnionType* getTable() const { return mNodes; } protected: //@{ /// Use a mask accessor to ensure consistency between the child and value masks; /// i.e., the value mask should always be off wherever the child mask is on. void setValueMask(Index n, bool on) { mValueMask.set(n, mChildMask.isOn(n) ? false : on); } //@} void makeChildNodeEmpty(Index n, const ValueType& value); void setChildNode( Index i, ChildNodeType* child);//assumes a tile void resetChildNode(Index i, ChildNodeType* child);//checks for an existing child ChildNodeType* unsetChildNode(Index i, const ValueType& value); template static inline void doVisit(NodeT&, VisitorOp&); template static inline void doVisit2Node(NodeT&, OtherNodeT&, VisitorOp&); template static inline void doVisit2(NodeT&, OtherChildAllIterT&, VisitorOp&, bool otherIsLHS); ///@{ /// @brief Returns a pointer to the child node at the linear offset n. /// @warning This protected method assumes that a child node exists at /// the specified linear offset! ChildNodeType* getChildNode(Index n); const ChildNodeType* getChildNode(Index n) const; ///@} // Protected member classes for multi-threading template struct DeepCopy; template struct TopologyCopy1; template struct TopologyCopy2; template struct TopologyUnion; template struct TopologyDifference; template struct TopologyIntersection; UnionType mNodes[NUM_VALUES]; NodeMaskType mChildMask, mValueMask; /// Global grid index coordinates (x,y,z) of the local origin of this node Coord mOrigin; }; // class InternalNode //////////////////////////////////////// //@{ /// Helper metafunction used to implement InternalNode::SameConfiguration /// (which, as an inner class, can't be independently specialized) template struct SameInternalConfig { static const bool value = false; }; template struct SameInternalConfig > { static const bool value = ChildT1::template SameConfiguration::value; }; //@} //////////////////////////////////////// template inline InternalNode::InternalNode(const ValueType& background) { for (Index i = 0; i < NUM_VALUES; ++i) mNodes[i].setValue(background); } template inline InternalNode::InternalNode(const Coord& origin, const ValueType& val, bool active): mOrigin(origin[0] & ~(DIM - 1), // zero out the low-order bits origin[1] & ~(DIM - 1), origin[2] & ~(DIM - 1)) { if (active) mValueMask.setOn(); for (Index i = 0; i < NUM_VALUES; ++i) mNodes[i].setValue(val); } #ifndef OPENVDB_2_ABI_COMPATIBLE // For InternalNodes, the PartialCreate constructor is identical to its // non-PartialCreate counterpart. template inline InternalNode::InternalNode(PartialCreate, const Coord& origin, const ValueType& val, bool active) : mOrigin(origin[0] & ~(DIM-1), origin[1] & ~(DIM-1), origin[2] & ~(DIM-1)) { if (active) mValueMask.setOn(); for (Index i = 0; i < NUM_VALUES; ++i) mNodes[i].setValue(val); } #endif template template struct InternalNode::DeepCopy { DeepCopy(const OtherInternalNode* source, InternalNode* target) : s(source), t(target) { tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); //(*this)(tbb::blocked_range(0, NUM_VALUES));//serial } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (s->mChildMask.isOff(i)) { t->mNodes[i].setValue(ValueType(s->mNodes[i].getValue())); } else { t->mNodes[i].setChild(new ChildNodeType(*(s->mNodes[i].getChild()))); } } } const OtherInternalNode* s; InternalNode* t; };// DeepCopy template inline InternalNode::InternalNode(const InternalNode& other): mChildMask(other.mChildMask), mValueMask(other.mValueMask), mOrigin(other.mOrigin) { DeepCopy > tmp(&other, this); } // Copy-construct from a node with the same configuration but a different ValueType. template template inline InternalNode::InternalNode(const InternalNode& other) : mChildMask(other.mChildMask) , mValueMask(other.mValueMask) , mOrigin(other.mOrigin) { DeepCopy > tmp(&other, this); } template template struct InternalNode::TopologyCopy1 { TopologyCopy1(const OtherInternalNode* source, InternalNode* target, const ValueType& background) : s(source), t(target), b(background) { tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); //(*this)(tbb::blocked_range(0, NUM_VALUES));//serial } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (s->isChildMaskOn(i)) { t->mNodes[i].setChild(new ChildNodeType(*(s->mNodes[i].getChild()), b, TopologyCopy())); } else { t->mNodes[i].setValue(b); } } } const OtherInternalNode* s; InternalNode* t; const ValueType &b; };// TopologyCopy1 template template inline InternalNode::InternalNode(const InternalNode& other, const ValueType& background, TopologyCopy): mChildMask(other.mChildMask), mValueMask(other.mValueMask), mOrigin(other.mOrigin) { TopologyCopy1 > tmp(&other, this, background); } template template struct InternalNode::TopologyCopy2 { TopologyCopy2(const OtherInternalNode* source, InternalNode* target, const ValueType& offValue, const ValueType& onValue) : s(source), t(target), offV(offValue), onV(onValue) { tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (s->isChildMaskOn(i)) { t->mNodes[i].setChild(new ChildNodeType(*(s->mNodes[i].getChild()), offV, onV, TopologyCopy())); } else { t->mNodes[i].setValue(s->isValueMaskOn(i) ? onV : offV); } } } const OtherInternalNode* s; InternalNode* t; const ValueType &offV, &onV; };// TopologyCopy2 template template inline InternalNode::InternalNode(const InternalNode& other, const ValueType& offValue, const ValueType& onValue, TopologyCopy): mChildMask(other.mChildMask), mValueMask(other.mValueMask), mOrigin(other.mOrigin) { TopologyCopy2 > tmp(&other, this, offValue, onValue); } template inline InternalNode::~InternalNode() { for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { delete mNodes[iter.pos()].getChild(); } } //////////////////////////////////////// template inline Index32 InternalNode::leafCount() const { if (ChildNodeType::getLevel() == 0) return mChildMask.countOn(); Index32 sum = 0; for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { sum += iter->leafCount(); } return sum; } template inline Index32 InternalNode::nonLeafCount() const { Index32 sum = 1; if (ChildNodeType::getLevel() == 0) return sum; for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { sum += iter->nonLeafCount(); } return sum; } template inline Index64 InternalNode::onVoxelCount() const { Index64 sum = ChildT::NUM_VOXELS * mValueMask.countOn(); for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { sum += iter->onVoxelCount(); } return sum; } template inline Index64 InternalNode::offVoxelCount() const { Index64 sum = ChildT::NUM_VOXELS * (NUM_VALUES-mValueMask.countOn()-mChildMask.countOn()); for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { sum += iter->offVoxelCount(); } return sum; } template inline Index64 InternalNode::onLeafVoxelCount() const { Index64 sum = 0; for (ChildOnCIter iter = this->beginChildOn(); iter; ++iter) { sum += mNodes[iter.pos()].getChild()->onLeafVoxelCount(); } return sum; } template inline Index64 InternalNode::offLeafVoxelCount() const { Index64 sum = 0; for (ChildOnCIter iter = this->beginChildOn(); iter; ++iter) { sum += mNodes[iter.pos()].getChild()->offLeafVoxelCount(); } return sum; } template inline Index64 InternalNode::onTileCount() const { Index64 sum = mValueMask.countOn(); for (ChildOnCIter iter = this->cbeginChildOn(); LEVEL>1 && iter; ++iter) { sum += iter->onTileCount(); } return sum; } template inline Index64 InternalNode::memUsage() const { Index64 sum = NUM_VALUES * sizeof(UnionType) + mChildMask.memUsage() + mValueMask.memUsage() + sizeof(mOrigin); for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { sum += iter->memUsage(); } return sum; } template inline void InternalNode::evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels) const { if (bbox.isInside(this->getNodeBoundingBox())) return; for (ValueOnCIter i = this->cbeginValueOn(); i; ++i) { bbox.expand(i.getCoord(), ChildT::DIM); } for (ChildOnCIter i = this->cbeginChildOn(); i; ++i) { i->evalActiveBoundingBox(bbox, visitVoxels); } } //////////////////////////////////////// template inline void InternalNode::prune(const ValueType& tolerance) { bool state = false; ValueType value = zeroVal(); for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { const Index i = iter.pos(); ChildT* child = mNodes[i].getChild(); child->prune(tolerance); if (child->isConstant(value, state, tolerance)) { delete child; mChildMask.setOff(i); mValueMask.set(i, state); mNodes[i].setValue(value); } } } //////////////////////////////////////// template template inline NodeT* InternalNode::stealNode(const Coord& xyz, const ValueType& value, bool state) { if ((NodeT::LEVEL == ChildT::LEVEL && !(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& value, bool& state, const ValueType& tolerance) const { if ( !(mChildMask.isOff()) ) return false; state = mValueMask.isOn(); if (!(state || mValueMask.isOff())) return false;// Are values neither active nor inactive? value = mNodes[0].getValue(); for (Index i = 1; i < NUM_VALUES; ++i) { if ( !math::isApproxEqual(mNodes[i].getValue(), value, tolerance) ) return false; } return true; } //////////////////////////////////////// template inline bool InternalNode::isConstant(ValueType& minValue, ValueType& maxValue, bool& state, const ValueType& tolerance) const { if ( !(mChildMask.isOff()) ) return false; state = mValueMask.isOn(); if (!(state || mValueMask.isOff())) return false;// Are values neither active nor inactive? const ValueType range = 2 * tolerance; minValue = maxValue = mNodes[0].getValue(); for (Index i = 1; i < NUM_VALUES; ++i) { const ValueType& v = mNodes[i].getValue(); if (v < minValue) { if ((maxValue - v) > range) return false; minValue = v; } else if (v > maxValue) { if ((v - minValue) > range) return false; maxValue = v; } } return true; } //////////////////////////////////////// template inline bool InternalNode::hasActiveTiles() const { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN const bool anyActiveTiles = !mValueMask.isOff(); if (LEVEL==1 || anyActiveTiles) return anyActiveTiles; for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { if (iter->hasActiveTiles()) return true; } return false; OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template inline bool InternalNode::isValueOn(const Coord& xyz) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOff(n)) return this->isValueMaskOn(n); return mNodes[n].getChild()->isValueOn(xyz); } template template inline bool InternalNode::isValueOnAndCache(const Coord& xyz, AccessorT& acc) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOff(n)) return this->isValueMaskOn(n); acc.insert(xyz, mNodes[n].getChild()); return mNodes[n].getChild()->isValueOnAndCache(xyz, acc); } template inline const typename ChildT::ValueType& InternalNode::getValue(const Coord& xyz) const { const Index n = this->coordToOffset(xyz); return this->isChildMaskOff(n) ? mNodes[n].getValue() : mNodes[n].getChild()->getValue(xyz); } template template inline const typename ChildT::ValueType& InternalNode::getValueAndCache(const Coord& xyz, AccessorT& acc) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOn(n)) { acc.insert(xyz, mNodes[n].getChild()); return mNodes[n].getChild()->getValueAndCache(xyz, acc); } return mNodes[n].getValue(); } template inline Index InternalNode::getValueLevel(const Coord& xyz) const { const Index n = this->coordToOffset(xyz); return this->isChildMaskOff(n) ? LEVEL : mNodes[n].getChild()->getValueLevel(xyz); } template template inline Index InternalNode::getValueLevelAndCache(const Coord& xyz, AccessorT& acc) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOn(n)) { acc.insert(xyz, mNodes[n].getChild()); return mNodes[n].getChild()->getValueLevelAndCache(xyz, acc); } return LEVEL; } template inline bool InternalNode::probeValue(const Coord& xyz, ValueType& value) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOff(n)) { value = mNodes[n].getValue(); return this->isValueMaskOn(n); } return mNodes[n].getChild()->probeValue(xyz, value); } template template inline bool InternalNode::probeValueAndCache(const Coord& xyz, ValueType& value, AccessorT& acc) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOn(n)) { acc.insert(xyz, mNodes[n].getChild()); return mNodes[n].getChild()->probeValueAndCache(xyz, value, acc); } value = mNodes[n].getValue(); return this->isValueMaskOn(n); } template inline void InternalNode::setValueOff(const Coord& xyz) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild && this->isValueMaskOn(n)) { // If the voxel belongs to a constant tile that is active, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), /*active=*/true)); } if (hasChild) mNodes[n].getChild()->setValueOff(xyz); } template inline void InternalNode::setValueOn(const Coord& xyz) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild && !this->isValueMaskOn(n)) { // If the voxel belongs to a constant tile that is inactive, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), /*active=*/false)); } if (hasChild) mNodes[n].getChild()->setValueOn(xyz); } template inline void InternalNode::setValueOff(const Coord& xyz, const ValueType& value) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool active = this->isValueMaskOn(n); if (active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel belongs to a tile that is either active or that // has a constant value that is different from the one provided, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) mNodes[n].getChild()->setValueOff(xyz, value); } template template inline void InternalNode::setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool active = this->isValueMaskOn(n); if (active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel belongs to a tile that is either active or that // has a constant value that is different from the one provided, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) { ChildT* child = mNodes[n].getChild(); acc.insert(xyz, child); child->setValueOffAndCache(xyz, value, acc); } } template inline void InternalNode::setValueOn(const Coord& xyz, const ValueType& value) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool active = this->isValueMaskOn(n); // tile's active state if (!active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel belongs to a tile that is either inactive or that // has a constant value that is different from the one provided, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) mNodes[n].getChild()->setValueOn(xyz, value); } template template inline void InternalNode::setValueAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool active = this->isValueMaskOn(n); if (!active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel belongs to a tile that is either inactive or that // has a constant value that is different from the one provided, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) { acc.insert(xyz, mNodes[n].getChild()); mNodes[n].getChild()->setValueAndCache(xyz, value, acc); } } template inline void InternalNode::setValueOnly(const Coord& xyz, const ValueType& value) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild && !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel has a tile value that is different from the one provided, // a child subtree must be constructed. const bool active = this->isValueMaskOn(n); hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } if (hasChild) mNodes[n].getChild()->setValueOnly(xyz, value); } template template inline void InternalNode::setValueOnlyAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild && !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel has a tile value that is different from the one provided, // a child subtree must be constructed. const bool active = this->isValueMaskOn(n); hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } if (hasChild) { acc.insert(xyz, mNodes[n].getChild()); mNodes[n].getChild()->setValueOnlyAndCache(xyz, value, acc); } } template inline void InternalNode::setActiveState(const Coord& xyz, bool on) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { if (on != this->isValueMaskOn(n)) { // If the voxel belongs to a tile with the wrong active state, // then a child subtree must be constructed. // 'on' is the voxel's new state, therefore '!on' is the tile's current state hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), !on)); } } if (hasChild) mNodes[n].getChild()->setActiveState(xyz, on); } template template inline void InternalNode::setActiveStateAndCache(const Coord& xyz, bool on, AccessorT& acc) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { if (on != this->isValueMaskOn(n)) { // If the voxel belongs to a tile with the wrong active state, // then a child subtree must be constructed. // 'on' is the voxel's new state, therefore '!on' is the tile's current state hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), !on)); } } if (hasChild) { ChildT* child = mNodes[n].getChild(); acc.insert(xyz, child); child->setActiveStateAndCache(xyz, on, acc); } } template inline void InternalNode::setValuesOn() { mValueMask = !mChildMask; for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { mNodes[iter.pos()].getChild()->setValuesOn(); } } template template inline void InternalNode::modifyValue(const Coord& xyz, const ModifyOp& op) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { // Need to create a child if the tile is inactive, // in order to activate voxel (x, y, z). const bool active = this->isValueMaskOn(n); bool createChild = !active; if (!createChild) { // Need to create a child if applying the functor // to the tile value produces a different value. const ValueType& tileVal = mNodes[n].getValue(); ValueType modifiedVal = tileVal; op(modifiedVal); createChild = !math::isExactlyEqual(tileVal, modifiedVal); } if (createChild) { hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) mNodes[n].getChild()->modifyValue(xyz, op); } template template inline void InternalNode::modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT& acc) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { // Need to create a child if the tile is inactive, // in order to activate voxel (x, y, z). const bool active = this->isValueMaskOn(n); bool createChild = !active; if (!createChild) { // Need to create a child if applying the functor // to the tile value produces a different value. const ValueType& tileVal = mNodes[n].getValue(); ValueType modifiedVal = tileVal; op(modifiedVal); createChild = !math::isExactlyEqual(tileVal, modifiedVal); } if (createChild) { hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) { ChildNodeType* child = mNodes[n].getChild(); acc.insert(xyz, child); child->modifyValueAndCache(xyz, op, acc); } } template template inline void InternalNode::modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool tileState = this->isValueMaskOn(n); const ValueType& tileVal = mNodes[n].getValue(); bool modifiedState = !tileState; ValueType modifiedVal = tileVal; op(modifiedVal, modifiedState); // Need to create a child if applying the functor to the tile // produces a different value or active state. if (modifiedState != tileState || !math::isExactlyEqual(modifiedVal, tileVal)) { hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, tileVal, tileState)); } } if (hasChild) mNodes[n].getChild()->modifyValueAndActiveState(xyz, op); } template template inline void InternalNode::modifyValueAndActiveStateAndCache( const Coord& xyz, const ModifyOp& op, AccessorT& acc) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool tileState = this->isValueMaskOn(n); const ValueType& tileVal = mNodes[n].getValue(); bool modifiedState = !tileState; ValueType modifiedVal = tileVal; op(modifiedVal, modifiedState); // Need to create a child if applying the functor to the tile // produces a different value or active state. if (modifiedState != tileState || !math::isExactlyEqual(modifiedVal, tileVal)) { hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, tileVal, tileState)); } } if (hasChild) { ChildNodeType* child = mNodes[n].getChild(); acc.insert(xyz, child); child->modifyValueAndActiveStateAndCache(xyz, op, acc); } } //////////////////////////////////////// template inline void InternalNode::clip(const CoordBBox& clipBBox, const ValueType& background) { CoordBBox nodeBBox = this->getNodeBoundingBox(); if (!clipBBox.hasOverlap(nodeBBox)) { // This node lies completely outside the clipping region. Fill it with background tiles. this->fill(nodeBBox, background, /*active=*/false); } else if (clipBBox.isInside(nodeBBox)) { // This node lies completely inside the clipping region. Leave it intact. return; } // This node isn't completely contained inside the clipping region. // Clip tiles and children, and replace any that lie outside the region // with background tiles. for (Index pos = 0; pos < NUM_VALUES; ++pos) { const Coord xyz = this->offsetToGlobalCoord(pos); // tile or child origin CoordBBox tileBBox(xyz, xyz.offsetBy(ChildT::DIM - 1)); // tile or child bounds if (!clipBBox.hasOverlap(tileBBox)) { // This table entry lies completely outside the clipping region. // Replace it with a background tile. this->makeChildNodeEmpty(pos, background); mValueMask.setOff(pos); } else if (!clipBBox.isInside(tileBBox)) { // This table entry does not lie completely inside the clipping region // and must be clipped. if (this->isChildMaskOn(pos)) { mNodes[pos].getChild()->clip(clipBBox, background); } else { // Replace this tile with a background tile, then fill the clip region // with the tile's original value. (This might create a child branch.) tileBBox.intersect(clipBBox); const ValueType val = mNodes[pos].getValue(); const bool on = this->isValueMaskOn(pos); mNodes[pos].setValue(background); mValueMask.setOff(pos); this->fill(tileBBox, val, on); } } else { // This table entry lies completely inside the clipping region. Leave it intact. } } } //////////////////////////////////////// template inline void InternalNode::fill(const CoordBBox& bbox, const ValueType& value, bool active) { 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) { #ifndef OPENVDB_2_ABI_COMPATIBLE const ValueType background = (!io::getGridBackgroundValuePtr(is) ? zeroVal() : *static_cast(io::getGridBackgroundValuePtr(is))); #endif mChildMask.load(is); mValueMask.load(is); if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_INTERNALNODE_COMPRESSION) { for (Index i = 0; i < NUM_VALUES; ++i) { if (this->isChildMaskOn(i)) { ChildNodeType* child = #ifdef OPENVDB_2_ABI_COMPATIBLE new ChildNodeType(offsetToGlobalCoord(i), zeroVal()); #else new ChildNodeType(PartialCreate(), offsetToGlobalCoord(i), background); #endif mNodes[i].setChild(child); child->readTopology(is); } else { ValueType value; is.read(reinterpret_cast(&value), sizeof(ValueType)); mNodes[i].setValue(value); } } } else { const bool oldVersion = (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION); const Index numValues = (oldVersion ? mChildMask.countOff() : NUM_VALUES); { // Read in (and uncompress, if necessary) all of this node's values // into a contiguous array. 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) { #ifdef OPENVDB_2_ABI_COMPATIBLE ChildNodeType* child = new ChildNodeType(iter.getCoord(), zeroVal()); #else ChildNodeType* child = new ChildNodeType(PartialCreate(), iter.getCoord(), background); #endif mNodes[iter.pos()].setChild(child); child->readTopology(is, fromHalf); } } } //////////////////////////////////////// template inline const typename ChildT::ValueType& InternalNode::getFirstValue() const { return (this->isChildMaskOn(0) ? mNodes[0].getChild()->getFirstValue() : mNodes[0].getValue()); } template inline const typename ChildT::ValueType& InternalNode::getLastValue() const { const Index n = NUM_VALUES - 1; return (this->isChildMaskOn(n) ? mNodes[n].getChild()->getLastValue() : mNodes[n].getValue()); } //////////////////////////////////////// template inline void InternalNode::negate() { for (Index i = 0; i < NUM_VALUES; ++i) { if (this->isChildMaskOn(i)) { mNodes[i].getChild()->negate(); } else { mNodes[i].setValue(math::negative(mNodes[i].getValue())); } } } template 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 struct InternalNode::TopologyUnion { typedef typename NodeMaskType::Word W; struct A { inline void operator()(W &tV, const W& sV, const W& tC) const { tV = (tV | sV) & ~tC; } }; TopologyUnion(const OtherInternalNode* source, InternalNode* target) : s(source), t(target) { //(*this)(tbb::blocked_range(0, NUM_VALUES));//single thread for debugging tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); // Bit processing is done in a single thread! t->mChildMask |= s->mChildMask;//serial but very fast bitwise post-process A op; t->mValueMask.foreach(s->mValueMask, t->mChildMask, op); assert((t->mValueMask & t->mChildMask).isOff());//no overlapping active tiles and child nodes } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (s->mChildMask.isOn(i)) {// Loop over other node's child nodes const typename OtherInternalNode::ChildNodeType& other = *(s->mNodes[i].getChild()); if (t->mChildMask.isOn(i)) {//this has a child node t->mNodes[i].getChild()->topologyUnion(other); } else {// this is a tile so replace it with a child branch with identical topology ChildT* child = new ChildT(other, t->mNodes[i].getValue(), TopologyCopy()); if (t->mValueMask.isOn(i)) child->setValuesOn();//activate all values t->mNodes[i].setChild(child); } } else if (s->mValueMask.isOn(i) && t->mChildMask.isOn(i)) { t->mNodes[i].getChild()->setValuesOn(); } } } const OtherInternalNode* s; InternalNode* t; };// TopologyUnion template template inline void InternalNode::topologyUnion(const InternalNode& other) { TopologyUnion > tmp(&other, this); } template template struct InternalNode::TopologyIntersection { typedef typename NodeMaskType::Word W; struct A { inline void operator()(W &tC, const W& sC, const W& sV, const W& tV) const { tC = (tC & (sC | sV)) | (tV & sC); } }; TopologyIntersection(const OtherInternalNode* source, InternalNode* target, const ValueType& background) : s(source), t(target), b(background) { //(*this)(tbb::blocked_range(0, NUM_VALUES));//single thread for debugging tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); // Bit processing is done in a single thread! A op; t->mChildMask.foreach(s->mChildMask, s->mValueMask, t->mValueMask, op); t->mValueMask &= s->mValueMask; assert((t->mValueMask & t->mChildMask).isOff());//no overlapping active tiles and child nodes } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (t->mChildMask.isOn(i)) {// Loop over this node's child nodes ChildT* child = t->mNodes[i].getChild(); if (s->mChildMask.isOn(i)) {//other also has a child node child->topologyIntersection(*(s->mNodes[i].getChild()), b); } else if (s->mValueMask.isOff(i)) {//other is an inactive tile delete child;//convert child to an inactive tile t->mNodes[i].setValue(b); } } else if (t->mValueMask.isOn(i) && s->mChildMask.isOn(i)) {//active tile -> a branch t->mNodes[i].setChild(new ChildT(*(s->mNodes[i].getChild()), t->mNodes[i].getValue(), TopologyCopy())); } } } const OtherInternalNode* s; InternalNode* t; const ValueType& b; };// TopologyIntersection template template inline void InternalNode::topologyIntersection(const InternalNode& other, const ValueType& background) { TopologyIntersection > tmp(&other, this, background); } template template struct InternalNode::TopologyDifference { typedef typename NodeMaskType::Word W; struct A {inline void operator()(W &tC, const W& sC, const W& sV, const W& tV) const { tC = (tC & (sC | ~sV)) | (tV & sC); } }; struct B {inline void operator()(W &tV, const W& sC, const W& sV, const W& tC) const { tV &= ~((tC & sV) | (sC | sV)); } }; TopologyDifference(const OtherInternalNode* source, InternalNode* target, const ValueType& background) : s(source), t(target), b(background) { //(*this)(tbb::blocked_range(0, NUM_VALUES));//single thread for debugging tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); // Bit processing is done in a single thread! const NodeMaskType oldChildMask(t->mChildMask);//important to avoid cross pollution A op1; t->mChildMask.foreach(s->mChildMask, s->mValueMask, t->mValueMask, op1); B op2; t->mValueMask.foreach(t->mChildMask, s->mValueMask, oldChildMask, op2); assert((t->mValueMask & t->mChildMask).isOff());//no overlapping active tiles and child nodes } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (t->mChildMask.isOn(i)) {// Loop over this node's child nodes ChildT* child = t->mNodes[i].getChild(); if (s->mChildMask.isOn(i)) { child->topologyDifference(*(s->mNodes[i].getChild()), b); } else if (s->mValueMask.isOn(i)) { delete child;//convert child to an inactive tile t->mNodes[i].setValue(b); } } else if (t->mValueMask.isOn(i)) {//this is an active tile if (s->mChildMask.isOn(i)) { const typename OtherInternalNode::ChildNodeType& other = *(s->mNodes[i].getChild()); ChildT* child = new ChildT(other.origin(), t->mNodes[i].getValue(), true); child->topologyDifference(other, b); t->mNodes[i].setChild(child);//replace the active tile with a child branch } } } } const OtherInternalNode* s; InternalNode* t; const ValueType& b; };// TopologyDifference template template inline void InternalNode::topologyDifference(const InternalNode& other, const ValueType& background) { TopologyDifference > tmp(&other, this, background); } //////////////////////////////////////// template template inline void InternalNode::combine(InternalNode& other, CombineOp& op) { const ValueType zero = zeroVal(); CombineArgs args; for (Index i = 0; i < NUM_VALUES; ++i) { if (this->isChildMaskOff(i) && other.isChildMaskOff(i)) { // Both this node and the other node have constant values (tiles). // Combine the two values and store the result as this node's new tile value. op(args.setARef(mNodes[i].getValue()) .setAIsActive(isValueMaskOn(i)) .setBRef(other.mNodes[i].getValue()) .setBIsActive(other.isValueMaskOn(i))); mNodes[i].setValue(args.result()); mValueMask.set(i, args.resultIsActive()); } else if (this->isChildMaskOn(i) && other.isChildMaskOff(i)) { // Combine this node's child with the other node's constant value. ChildNodeType* child = mNodes[i].getChild(); assert(child); if (child) { child->combine(other.mNodes[i].getValue(), other.isValueMaskOn(i), op); } } else if (this->isChildMaskOff(i) && other.isChildMaskOn(i)) { // Combine this node's constant value with the other node's child. ChildNodeType* child = other.mNodes[i].getChild(); assert(child); if (child) { // Combine this node's constant value with the other node's child, // but use a new functor in which the A and B values are swapped, // since the constant value is the A value, not the B value. SwappedCombineOp swappedOp(op); child->combine(mNodes[i].getValue(), isValueMaskOn(i), swappedOp); // Steal the other node's child. other.mChildMask.setOff(i); other.mNodes[i].setValue(zero); this->setChildNode(i, child); } } else /*if (isChildMaskOn(i) && other.isChildMaskOn(i))*/ { // Combine this node's child with the other node's child. ChildNodeType *child = mNodes[i].getChild(), *otherChild = other.mNodes[i].getChild(); assert(child); assert(otherChild); if (child && otherChild) { child->combine(*otherChild, op); } } } } template template inline void InternalNode::combine(const ValueType& value, bool valueIsActive, CombineOp& op) { CombineArgs args; for (Index i = 0; i < NUM_VALUES; ++i) { if (this->isChildMaskOff(i)) { // Combine this node's constant value with the given constant value. op(args.setARef(mNodes[i].getValue()) .setAIsActive(isValueMaskOn(i)) .setBRef(value) .setBIsActive(valueIsActive)); mNodes[i].setValue(args.result()); mValueMask.set(i, args.resultIsActive()); } else /*if (isChildMaskOn(i))*/ { // Combine this node's child with the given constant value. ChildNodeType* child = mNodes[i].getChild(); assert(child); if (child) child->combine(value, valueIsActive, op); } } } //////////////////////////////////////// template template inline void InternalNode::combine2(const InternalNode& other0, const OtherNodeType& other1, CombineOp& op) { CombineArgs args; for (Index i = 0; i < NUM_VALUES; ++i) { if (other0.isChildMaskOff(i) && other1.isChildMaskOff(i)) { op(args.setARef(other0.mNodes[i].getValue()) .setAIsActive(other0.isValueMaskOn(i)) .setBRef(other1.mNodes[i].getValue()) .setBIsActive(other1.isValueMaskOn(i))); // Replace child i with a constant value. this->makeChildNodeEmpty(i, args.result()); mValueMask.set(i, args.resultIsActive()); } else { if (this->isChildMaskOff(i)) { // Add a new child with the same coordinates, etc. as the other node's child. const Coord& childOrigin = other0.isChildMaskOn(i) ? other0.mNodes[i].getChild()->origin() : other1.mNodes[i].getChild()->origin(); this->setChildNode(i, new ChildNodeType(childOrigin, mNodes[i].getValue())); } if (other0.isChildMaskOff(i)) { // Combine node1's child with node0's constant value // and write the result into child i. mNodes[i].getChild()->combine2(other0.mNodes[i].getValue(), *other1.mNodes[i].getChild(), other0.isValueMaskOn(i), op); } else if (other1.isChildMaskOff(i)) { // Combine node0's child with node1's constant value // and write the result into child i. mNodes[i].getChild()->combine2(*other0.mNodes[i].getChild(), other1.mNodes[i].getValue(), other1.isValueMaskOn(i), op); } else { // Combine node0's child with node1's child // and write the result into child i. mNodes[i].getChild()->combine2(*other0.mNodes[i].getChild(), *other1.mNodes[i].getChild(), op); } } } } template template inline void InternalNode::combine2(const ValueType& value, const OtherNodeType& other, bool valueIsActive, CombineOp& op) { CombineArgs args; for (Index i = 0; i < NUM_VALUES; ++i) { if (other.isChildMaskOff(i)) { op(args.setARef(value) .setAIsActive(valueIsActive) .setBRef(other.mNodes[i].getValue()) .setBIsActive(other.isValueMaskOn(i))); // Replace child i with a constant value. this->makeChildNodeEmpty(i, args.result()); mValueMask.set(i, args.resultIsActive()); } else { typename OtherNodeType::ChildNodeType* otherChild = other.mNodes[i].getChild(); assert(otherChild); if (this->isChildMaskOff(i)) { // Add a new child with the same coordinates, etc. // as the other node's child. this->setChildNode(i, new ChildNodeType(*otherChild)); } // Combine the other node's child with a constant value // and write the result into child i. mNodes[i].getChild()->combine2(value, *otherChild, valueIsActive, op); } } } template template inline void InternalNode::combine2(const InternalNode& other, const OtherValueType& value, bool valueIsActive, CombineOp& op) { CombineArgs args; for (Index i = 0; i < NUM_VALUES; ++i) { if (other.isChildMaskOff(i)) { op(args.setARef(other.mNodes[i].getValue()) .setAIsActive(other.isValueMaskOn(i)) .setBRef(value) .setBIsActive(valueIsActive)); // Replace child i with a constant value. this->makeChildNodeEmpty(i, args.result()); mValueMask.set(i, args.resultIsActive()); } else { ChildNodeType* otherChild = other.mNodes[i].getChild(); assert(otherChild); if (this->isChildMaskOff(i)) { // Add a new child with the same coordinates, etc. as the other node's child. this->setChildNode(i, new ChildNodeType(otherChild->origin(), mNodes[i].getValue())); } // Combine the other node's child with a constant value // and write the result into child i. mNodes[i].getChild()->combine2(*otherChild, value, valueIsActive, op); } } } //////////////////////////////////////// template template inline void InternalNode::visitActiveBBox(BBoxOp& op) const { for (ValueOnCIter i = this->cbeginValueOn(); i; ++i) { #ifdef _MSC_VER op.operator()(CoordBBox::createCube(i.getCoord(), ChildNodeType::DIM)); #else op.template operator()(CoordBBox::createCube(i.getCoord(), ChildNodeType::DIM)); #endif } if (op.template descent()) { for (ChildOnCIter i = this->cbeginChildOn(); i; ++i) i->visitActiveBBox(op); } else { for (ChildOnCIter i = this->cbeginChildOn(); i; ++i) { #ifdef _MSC_VER op.operator()(i->getNodeBoundingBox()); #else op.template operator()(i->getNodeBoundingBox()); #endif } } } template template inline void InternalNode::visit(VisitorOp& op) { doVisit(*this, op); } template template inline void InternalNode::visit(VisitorOp& op) const { doVisit(*this, op); } template template inline void InternalNode::doVisit(NodeT& self, VisitorOp& op) { typename NodeT::ValueType val; for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { if (op(iter)) continue; if (typename ChildAllIterT::ChildNodeType* child = iter.probeChild(val)) { child->visit(op); } } } //////////////////////////////////////// template template inline void InternalNode::visit2Node(OtherNodeType& other, VisitorOp& op) { doVisit2Node(*this, other, op); } template template inline void InternalNode::visit2Node(OtherNodeType& other, VisitorOp& op) const { doVisit2Node(*this, other, op); } template template< typename NodeT, typename OtherNodeT, typename VisitorOp, typename ChildAllIterT, typename OtherChildAllIterT> inline void InternalNode::doVisit2Node(NodeT& self, OtherNodeT& other, VisitorOp& op) { // Allow the two nodes to have different ValueTypes, but not different dimensions. 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 inline void InternalNode::readBuffers(std::istream& is, const CoordBBox& clipBBox, bool fromHalf) { for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { // Stream in the branch rooted at this child. // (We can't skip over children that lie outside the clipping region, // because buffers are serialized in depth-first order and need to be // unserialized in the same order.) iter->readBuffers(is, clipBBox, fromHalf); } // Get this tree's background value. ValueType background = zeroVal(); if (const void* bgPtr = io::getGridBackgroundValuePtr(is)) { background = *static_cast(bgPtr); } this->clip(clipBBox, background); } //////////////////////////////////////// template void InternalNode::getNodeLog2Dims(std::vector& dims) { dims.push_back(Log2Dim); ChildNodeType::getNodeLog2Dims(dims); } template inline void InternalNode::offsetToLocalCoord(Index n, Coord &xyz) { assert(n<(1<<3*Log2Dim)); xyz.setX(n >> 2*Log2Dim); n &= ((1<<2*Log2Dim)-1); xyz.setY(n >> Log2Dim); xyz.setZ(n & ((1< inline Index InternalNode::coordToOffset(const Coord& xyz) { return (((xyz[0] & (DIM-1u)) >> ChildNodeType::TOTAL) << 2*Log2Dim) + (((xyz[1] & (DIM-1u)) >> ChildNodeType::TOTAL) << Log2Dim) + ((xyz[2] & (DIM-1u)) >> ChildNodeType::TOTAL); } template inline Coord InternalNode::offsetToGlobalCoord(Index n) const { Coord local; this->offsetToLocalCoord(n, local); local <<= ChildT::TOTAL; return local + this->origin(); } //////////////////////////////////////// template template inline void InternalNode::getNodes(ArrayT& array) { typedef typename ArrayT::value_type T; BOOST_STATIC_ASSERT(boost::is_pointer::value); typedef typename boost::mpl::if_::type>, const ChildT, ChildT>::type ArrayChildT; for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (boost::is_same::value) { array.push_back(reinterpret_cast(mNodes[iter.pos()].getChild())); } else { iter->getNodes(array);//descent } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } } template template inline void InternalNode::getNodes(ArrayT& array) const { typedef typename ArrayT::value_type T; BOOST_STATIC_ASSERT(boost::is_pointer::value); BOOST_STATIC_ASSERT(boost::is_const::type>::value); for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (boost::is_same::value) { array.push_back(reinterpret_cast(mNodes[iter.pos()].getChild())); } else { iter->getNodes(array);//descent } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } } //////////////////////////////////////// template template inline void InternalNode::stealNodes(ArrayT& array, const ValueType& value, bool state) { typedef typename ArrayT::value_type T; BOOST_STATIC_ASSERT(boost::is_pointer::value); typedef typename boost::mpl::if_::type>, const ChildT, ChildT>::type ArrayChildT; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { const Index n = iter.pos(); if (boost::is_same::value) { array.push_back(reinterpret_cast(mNodes[n].getChild())); mValueMask.set(n, state); mNodes[n].setValue(value); } else { iter->stealNodes(array, value, state);//descent } } if (boost::is_same::value) mChildMask.setOff(); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////// template inline void InternalNode::resetBackground(const ValueType& oldBackground, const ValueType& newBackground) { if (math::isExactlyEqual(oldBackground, newBackground)) return; for (Index i = 0; i < NUM_VALUES; ++i) { if (this->isChildMaskOn(i)) { mNodes[i].getChild()->resetBackground(oldBackground, newBackground); } else if (this->isValueMaskOff(i)) { if (math::isApproxEqual(mNodes[i].getValue(), oldBackground)) { mNodes[i].setValue(newBackground); } else if (math::isApproxEqual(mNodes[i].getValue(), math::negative(oldBackground))) { mNodes[i].setValue(math::negative(newBackground)); } } } } template template inline bool InternalNode::hasSameTopology( const InternalNode* other) const { if (Log2Dim != OtherLog2Dim || mChildMask != other->mChildMask || mValueMask != other->mValueMask) return false; for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { if (!iter->hasSameTopology(other->mNodes[iter.pos()].getChild())) return false; } return true; } template inline void InternalNode::resetChildNode(Index i, ChildNodeType* child) { assert(child); if (this->isChildMaskOn(i)) { delete mNodes[i].getChild(); } else { mChildMask.setOn(i); mValueMask.setOff(i); } mNodes[i].setChild(child); } template inline void InternalNode::setChildNode(Index i, ChildNodeType* child) { assert(child); assert(mChildMask.isOff(i)); mChildMask.setOn(i); mValueMask.setOff(i); mNodes[i].setChild(child); } template inline ChildT* InternalNode::unsetChildNode(Index i, const ValueType& value) { if (this->isChildMaskOff(i)) { mNodes[i].setValue(value); return 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/RootNode.h0000644000000000000000000037746312603226506014100 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #include #include #include #include #include #include //for boost::mpl::vector #include #include #include #include #include #include #include // for truncateRealToHalf() #include // for isZero(), isExactlyEqual(), etc. #include #include // for backward compatibility only (see readTopology()) #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { // Forward declarations template struct NodeChain; template struct SameRootConfig; template struct RootNodeCopyHelper; template struct RootNodeCombineHelper; template class RootNode { public: 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 the background values of the /// child nodes is also updated. Else only the background value /// stored in the RootNode itself is changed. /// /// @note Instead of setting @a updateChildNodes to true, consider /// using tools::changeBackground or /// tools::changeLevelSetBackground which are multi-threaded! void setBackground(const ValueType& value, bool updateChildNodes); /// Return this node's background value. const ValueType& background() const { return mBackground; } /// Return @c true if the given tile is inactive and has the background value. bool isBackgroundTile(const Tile&) const; //@{ /// Return @c true if the given iterator points to an inactive tile with the background value. bool isBackgroundTile(const MapIter&) const; bool isBackgroundTile(const MapCIter&) const; //@} /// Return the number of background tiles. size_t numBackgroundTiles() const; /// @brief Remove all background tiles. /// @return the number of tiles removed. size_t eraseBackgroundTiles(); 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 static_cast(mTable.size()); } Index getWidth() const { return this->getMaxIndex()[0] - this->getMinIndex()[0]; } Index getHeight() const { return this->getMaxIndex()[1] - this->getMinIndex()[1]; } Index getDepth() const { return this->getMaxIndex()[2] - this->getMinIndex()[2]; } /// Return the smallest index of the current tree. Coord getMinIndex() const; /// Return the largest index of the current tree. Coord getMaxIndex() const; /// Return the current index range. Both min and max are inclusive. void getIndexRange(CoordBBox& bbox) const; /// @brief Return @c true if the given tree has the same node and active value /// topology as this tree (but possibly a different @c ValueType). template bool hasSameTopology(const RootNode& other) const; /// Return @c false if the other node's dimensions don't match this node's. template static bool hasSameConfiguration(const RootNode& other); /// Return @c true if values of the other node's ValueType can be converted /// to values of this node's ValueType. template static bool hasCompatibleValueType(const RootNode& other); Index32 leafCount() const; Index32 nonLeafCount() const; Index64 onVoxelCount() const; Index64 offVoxelCount() const; Index64 onLeafVoxelCount() const; Index64 offLeafVoxelCount() const; Index64 onTileCount() const; bool isValueOn(const Coord& xyz) const; 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); void readBuffers(std::istream&, const CoordBBox&, bool fromHalf = false); // // Voxel access // /// Return the value of the voxel at the given coordinates and, if necessary, update /// the accessor with pointers to the nodes along the path from the root node to /// the node containing the voxel. /// @note Used internally by ValueAccessor. template const ValueType& getValueAndCache(const Coord& xyz, AccessorT&) const; /// Return @c true if the voxel at the given coordinates is active and, if necessary, /// update the accessor with pointers to the nodes along the path from the root node /// to the node containing the voxel. /// @note Used internally by ValueAccessor. template bool isValueOnAndCache(const Coord& xyz, AccessorT&) const; /// Change the value of the voxel at the given coordinates and mark it as active. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// Set the value of the voxel at the given coordinates without changing its active state. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueOnlyAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); /// Apply a functor to the voxel at the given coordinates. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void modifyValueAndActiveStateAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); /// Change the value of the voxel at the given coordinates and mark it as inactive. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// Set the active state of the voxel at the given coordinates without changing its value. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setActiveStateAndCache(const Coord& xyz, bool on, AccessorT&); /// Return, in @a value, the value of the voxel at the given coordinates and, /// if necessary, update the accessor with pointers to the nodes along /// the path from the root node to the node containing the voxel. /// @return @c true if the voxel at the given coordinates is active /// @note Used internally by ValueAccessor. template bool probeValueAndCache(const Coord& xyz, ValueType& value, AccessorT&) const; /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides. /// If (x, y, z) isn't explicitly represented in the tree (i.e., it is implicitly /// a background voxel), return -1. If necessary, update the accessor with pointers /// to the nodes along the path from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template int getValueDepthAndCache(const Coord& xyz, AccessorT&) const; /// Set all voxels that lie outside the given axis-aligned box to the background. void clip(const CoordBBox&); /// @brief Reduce the memory footprint of this tree by replacing with tiles /// any nodes whose values are all the same (optionally to within a tolerance) /// and have the same active state. /// /// @note Consider instead using tools::prune which is multi-threaded! void prune(const ValueType& tolerance = zeroVal()); /// @brief Add the given leaf node to this tree, creating a new branch if necessary. /// If a leaf node with the same origin already exists, replace it. void addLeaf(LeafNodeType* leaf); /// @brief Same as addLeaf() but, if necessary, update the given accessor with pointers /// to the nodes along the path from the root node to the node containing the coordinate. template void addLeafAndCache(LeafNodeType* leaf, AccessorT&); /// @brief Return a pointer to the node of type @c NodeT that contains voxel (x, y, z) /// and replace it with a tile of the specified value and state. /// If no such node exists, leave the tree unchanged and return @c 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 root level, /// deleting the existing branch if necessary. void addTile(const Coord& xyz, const ValueType& value, bool state); /// @brief Add a tile containing voxel (x, y, z) at the specified tree level, /// creating a new branch if necessary. Delete any existing lower-level nodes /// that contain (x, y, z). void addTile(Index level, const Coord& xyz, const ValueType& value, bool state); /// @brief Same as addTile() but, if necessary, update the given accessor with pointers /// to the nodes along the path from the root node to the node containing the coordinate. template void addTileAndCache(Index level, const Coord& xyz, const ValueType&, bool state, AccessorT&); /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). /// If no such node exists, create one that preserves the values and /// active states of all voxels. /// @details Use this method to preallocate a static tree topology /// over which to safely perform multithreaded processing. LeafNodeType* touchLeaf(const Coord& xyz); /// @brief Same as touchLeaf() but, if necessary, update the given accessor with pointers /// to the nodes along the path from the root node to the node containing the coordinate. template LeafNodeType* touchLeafAndCache(const Coord& xyz, AccessorT& acc); //@{ /// @brief Return a pointer to the node that contains voxel (x, y, z). /// If no such node exists, return 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 Adds all nodes of a certain type to a container with the following API: /// @code /// struct ArrayT { /// typedef value_type;// defines the type of nodes to be added to the array /// void push_back(value_type nodePtr);// method that add nodes to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// typedef LeafType* value_type; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.getNodes(array); /// @endcode template void getNodes(ArrayT& array); template void getNodes(ArrayT& array) const; //@} //@{ /// @brief Steals all nodes of a certain type from the tree and /// adds them to a container with the following API: /// @code /// struct ArrayT { /// typedef value_type;// defines the type of nodes to be added to the array /// void push_back(value_type nodePtr);// method that add nodes to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// typedef LeafType* value_type; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.stealNodes(array); /// @endcode template void stealNodes(ArrayT& array, const ValueType& value, bool state); template void stealNodes(ArrayT& array) { this->stealNodes(array, mBackground, false); } //@} /// 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::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. #ifdef OPENVDB_2_ABI_COMPATIBLE ChildT* child = new ChildT(origin, mBackground); #else ChildT* child = new ChildT(PartialCreate(), origin, mBackground); #endif child->readTopology(is); mTable[origin] = NodeStruct(*child); } else { // Read in a tile value and insert a tile, but only if the value // is either active or non-background. ValueType value; is.read(reinterpret_cast(&value), sizeof(ValueType)); if (valueMask.isOn(i) || (!math::isApproxEqual(value, mBackground))) { mTable[origin] = NodeStruct(Tile(value, valueMask.isOn(i))); } } } return true; } // Read a RootNode that was stored in the current format. is.read(reinterpret_cast(&mBackground), sizeof(ValueType)); io::setGridBackgroundValuePtr(is, &mBackground); Index numTiles = 0, numChildren = 0; is.read(reinterpret_cast(&numTiles), sizeof(Index)); is.read(reinterpret_cast(&numChildren), sizeof(Index)); if (numTiles == 0 && numChildren == 0) return false; Int32 vec[3]; ValueType value; bool active; // Read tiles. for (Index n = 0; n < numTiles; ++n) { is.read(reinterpret_cast(vec), 3 * sizeof(Int32)); is.read(reinterpret_cast(&value), sizeof(ValueType)); is.read(reinterpret_cast(&active), sizeof(bool)); mTable[Coord(vec)] = NodeStruct(Tile(value, active)); } // Read child nodes. for (Index n = 0; n < numChildren; ++n) { is.read(reinterpret_cast(vec), 3 * sizeof(Int32)); Coord origin(vec); #ifdef OPENVDB_2_ABI_COMPATIBLE ChildT* child = new ChildT(origin, mBackground); #else ChildT* child = new ChildT(PartialCreate(), origin, mBackground); #endif child->readTopology(is, fromHalf); mTable[Coord(vec)] = NodeStruct(*child); } return true; // not empty } template inline void RootNode::writeBuffers(std::ostream& os, bool toHalf) const { for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) getChild(i).writeBuffers(os, toHalf); } } template inline void RootNode::readBuffers(std::istream& is, bool fromHalf) { for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) getChild(i).readBuffers(is, fromHalf); } } template inline void RootNode::readBuffers(std::istream& is, const CoordBBox& clipBBox, bool fromHalf) { const Tile bgTile(mBackground, /*active=*/false); for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) { // Stream in and clip the branch rooted at this child. // (We can't skip over children that lie outside the clipping region, // because buffers are serialized in depth-first order and need to be // unserialized in the same order.) ChildT& child = getChild(i); child.readBuffers(is, clipBBox, fromHalf); } } // Clip root-level tiles and prune children that were clipped. this->clip(clipBBox); } //////////////////////////////////////// template inline void RootNode::clip(const CoordBBox& clipBBox) { const Tile bgTile(mBackground, /*active=*/false); // Iterate over a copy of this node's table so that we can modify the original. // (Copying the table copies child node pointers, not the nodes themselves.) MapType copyOfTable(mTable); for (MapIter i = copyOfTable.begin(), e = copyOfTable.end(); i != e; ++i) { const Coord& xyz = i->first; // tile or child origin CoordBBox tileBBox(xyz, xyz.offsetBy(ChildT::DIM - 1)); // tile or child bounds if (!clipBBox.hasOverlap(tileBBox)) { // This table entry lies completely outside the clipping region. Delete it. setTile(this->findCoord(xyz), bgTile); // delete any existing child node first mTable.erase(xyz); } else if (!clipBBox.isInside(tileBBox)) { // This table entry does not lie completely inside the clipping region // and must be clipped. if (isChild(i)) { getChild(i).clip(clipBBox, mBackground); } else { // Replace this tile with a background tile, then fill the clip region // with the tile's original value. (This might create a child branch.) tileBBox.intersect(clipBBox); const Tile& origTile = getTile(i); setTile(this->findCoord(xyz), bgTile); this->fill(tileBBox, origTile.value, origTile.active); } } else { // This table entry lies completely inside the clipping region. Leave it intact. } } this->prune(); // also erases root-level background tiles } //////////////////////////////////////// template inline void RootNode::prune(const ValueType& tolerance) { bool state = false; ValueType value = zeroVal(); for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (this->isTile(i)) continue; this->getChild(i).prune(tolerance); if (this->getChild(i).isConstant(value, state, tolerance)) { this->setTile(i, Tile(value, state)); } } this->eraseBackgroundTiles(); } //////////////////////////////////////// template template inline NodeT* RootNode::stealNode(const Coord& xyz, const ValueType& value, bool state) { if ((NodeT::LEVEL == ChildT::LEVEL && !(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(const Coord& xyz, const ValueType& value, bool state) { MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) {//background mTable[this->coordToKey(xyz)] = NodeStruct(Tile(value, state)); } else {//child or tile setTile(iter, Tile(value, state));//this also deletes the existing child node } } template inline void RootNode::addTile(Index level, const Coord& xyz, const ValueType& value, bool state) { if (LEVEL >= level) { MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) {//background if (LEVEL > level) { ChildT* child = new ChildT(xyz, mBackground, false); mTable[this->coordToKey(xyz)] = NodeStruct(*child); child->addTile(level, xyz, value, state); } else { mTable[this->coordToKey(xyz)] = NodeStruct(Tile(value, state)); } } else if (isChild(iter)) {//child if (LEVEL > level) { getChild(iter).addTile(level, xyz, value, state); } else { setTile(iter, Tile(value, state));//this also deletes the existing child node } } else {//tile if (LEVEL > level) { ChildT* child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); child->addTile(level, xyz, value, state); } else { setTile(iter, Tile(value, state)); } } } } template template inline void RootNode::addTileAndCache(Index level, const Coord& xyz, const ValueType& value, bool state, AccessorT& acc) { if (LEVEL >= level) { MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) {//background if (LEVEL > level) { ChildT* child = new ChildT(xyz, mBackground, false); acc.insert(xyz, child); mTable[this->coordToKey(xyz)] = NodeStruct(*child); child->addTileAndCache(level, xyz, value, state, acc); } else { mTable[this->coordToKey(xyz)] = NodeStruct(Tile(value, state)); } } else if (isChild(iter)) {//child if (LEVEL > level) { ChildT* child = &getChild(iter); acc.insert(xyz, child); child->addTileAndCache(level, xyz, value, state, acc); } else { setTile(iter, Tile(value, state));//this also deletes the existing child node } } else {//tile if (LEVEL > level) { ChildT* child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); acc.insert(xyz, child); setChild(iter, *child); child->addTileAndCache(level, xyz, value, state, acc); } else { setTile(iter, Tile(value, state)); } } } } //////////////////////////////////////// template inline typename ChildT::LeafNodeType* RootNode::touchLeaf(const Coord& xyz) { ChildT* child = 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 template inline void RootNode::getNodes(ArrayT& array) { typedef typename ArrayT::value_type NodePtr; BOOST_STATIC_ASSERT(boost::is_pointer::value); typedef typename boost::remove_pointer::type NodeType; typedef typename boost::remove_const::type NonConstNodeType; typedef typename boost::mpl::contains::type result; BOOST_STATIC_ASSERT(result::value); typedef typename boost::mpl::if_, const ChildT, ChildT>::type ArrayChildT; for (MapIter iter=mTable.begin(); iter!=mTable.end(); ++iter) { if (ChildT* child = iter->second.child) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (boost::is_same::value) { array.push_back(reinterpret_cast(iter->second.child)); } else { child->getNodes(array);//descent } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } } } template template inline void RootNode::getNodes(ArrayT& array) const { typedef typename ArrayT::value_type NodePtr; BOOST_STATIC_ASSERT(boost::is_pointer::value); typedef typename boost::remove_pointer::type NodeType; BOOST_STATIC_ASSERT(boost::is_const::value); typedef typename boost::remove_const::type NonConstNodeType; typedef typename boost::mpl::contains::type result; BOOST_STATIC_ASSERT(result::value); for (MapCIter iter=mTable.begin(); iter!=mTable.end(); ++iter) { if (const ChildNodeType *child = iter->second.child) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (boost::is_same::value) { array.push_back(reinterpret_cast(iter->second.child)); } else { child->getNodes(array);//descent } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } } } //////////////////////////////////////// template template inline void RootNode::stealNodes(ArrayT& array, const ValueType& value, bool state) { typedef typename ArrayT::value_type NodePtr; BOOST_STATIC_ASSERT(boost::is_pointer::value); typedef typename boost::remove_pointer::type NodeType; typedef typename boost::remove_const::type NonConstNodeType; typedef typename boost::mpl::contains::type result; BOOST_STATIC_ASSERT(result::value); typedef typename boost::mpl::if_, const ChildT, ChildT>::type ArrayChildT; for (MapIter iter=mTable.begin(); iter!=mTable.end(); ++iter) { if (ChildT* child = iter->second.child) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (boost::is_same::value) { array.push_back(reinterpret_cast(&stealChild(iter, Tile(value, state)))); } else { child->stealNodes(array, value, state);//descent } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } } } //////////////////////////////////////// template 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) { MapIter it = this->findCoord(*i); setTile(it, Tile()); // delete any existing child node first mTable.erase(it); } } template template inline void RootNode::topologyDifference(const RootNode& other) { 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)) { setTile(j, Tile()); // delete any existing child node first mTable.erase(j); } else if (this->isTileOn(j)) { this->setTile(j, Tile(this->getTile(j).value, false)); } } } } //////////////////////////////////////// template template inline void RootNode::combine(RootNode& other, CombineOp& op, bool prune) { CombineArgs args; CoordSet keys; this->insertKeys(keys); other.insertKeys(keys); for (CoordSetCIter i = keys.begin(), e = keys.end(); i != e; ++i) { MapIter iter = findOrAddCoord(*i), otherIter = other.findOrAddCoord(*i); if (isTile(iter) && isTile(otherIter)) { // Both this node and the other node have constant values (tiles). // Combine the two values and store the result as this node's new tile value. op(args.setARef(getTile(iter).value) .setAIsActive(isTileOn(iter)) .setBRef(getTile(otherIter).value) .setBIsActive(isTileOn(otherIter))); setTile(iter, Tile(args.result(), args.resultIsActive())); } else if (isChild(iter) && isTile(otherIter)) { // Combine this node's child with the other node's constant value. ChildT& child = getChild(iter); child.combine(getTile(otherIter).value, isTileOn(otherIter), op); } else if (isTile(iter) && isChild(otherIter)) { // Combine this node's constant value with the other node's child, // but use a new functor in which the A and B values are swapped, // since the constant value is the A value, not the B value. SwappedCombineOp swappedOp(op); ChildT& child = getChild(otherIter); child.combine(getTile(iter).value, isTileOn(iter), swappedOp); // Steal the other node's child. setChild(iter, stealChild(otherIter, Tile())); } else /*if (isChild(iter) && isChild(otherIter))*/ { // Combine this node's child with the other node's child. ChildT &child = getChild(iter), &otherChild = getChild(otherIter); child.combine(otherChild, op); } if (prune && isChild(iter)) getChild(iter).prune(); } // Combine background values. op(args.setARef(mBackground).setBRef(other.mBackground)); mBackground = args.result(); // Empty the other tree so as not to leave it in a partially cannibalized state. other.clear(); } //////////////////////////////////////// // This helper class is a friend of RootNode and is needed so that combine2 // can be specialized for compatible and incompatible pairs of RootNode types. template struct RootNodeCombineHelper { static inline void combine2(RootT& self, const RootT&, const OtherRootT& other1, CombineOp&, bool) { // If the two root nodes have different configurations or incompatible ValueTypes, // throw an exception. self.enforceSameConfiguration(other1); self.enforceCompatibleValueTypes(other1); // One of the above two tests should throw, so we should never get here: std::ostringstream ostr; ostr << "cannot combine a " << typeid(OtherRootT).name() << " into a " << typeid(RootT).name(); OPENVDB_THROW(TypeError, ostr.str()); } }; // Specialization for root nodes of compatible types template struct RootNodeCombineHelper { static inline void combine2(RootT& self, const RootT& other0, const OtherRootT& other1, CombineOp& op, bool prune) { self.doCombine2(other0, other1, op, prune); } }; template template inline void RootNode::combine2(const RootNode& other0, const OtherRootNode& other1, CombineOp& op, bool prune) { 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/Iterator.h0000644000000000000000000003164212603226506014122 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/TreeIterator.h0000644000000000000000000014776412603226506014757 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file TreeIterator.h #ifndef OPENVDB_TREE_TREEITERATOR_HAS_BEEN_INCLUDED #define OPENVDB_TREE_TREEITERATOR_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include #include // Prior to 0.96.1, depth-bounded value iterators always descended to the leaf level // and iterated past leaf nodes. Now, they never descend past the maximum depth. // Comment out the following line to restore the older, less-efficient behavior: #define ENABLE_TREE_VALUE_DEPTH_BOUND_OPTIMIZATION namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { /// CopyConstness::Type is either const T2 or T2 with no const qualifier, /// depending on whether T1 is const. For example, /// - CopyConstness::Type is int /// - CopyConstness::Type is int /// - CopyConstness::Type is const int /// - CopyConstness::Type is const int template struct CopyConstness { typedef typename boost::remove_const::type Type; }; template struct CopyConstness { typedef const ToType Type; }; //////////////////////////////////////// namespace iter { template struct InvertedTree { typedef typename InvertedTree::Type SubtreeT; typedef typename boost::mpl::push_back::type Type; }; template struct InvertedTree { typedef typename boost::mpl::vector::type Type; }; } // namespace iter //////////////////////////////////////// /// IterTraits provides the following for iterators of the standard types, /// i.e., for {Child,Value}{On,Off,All}{Iter,CIter}: /// - a NodeConverter template to convert an iterator for one type of node /// to an iterator of the same type for another type of node; for example, /// IterTraits::NodeConverter::Type /// is synonymous with LeafNode::ValueOnIter. /// - a begin(node) function that returns a begin iterator for a node of arbitrary type; /// for example, IterTraits::begin(leaf) returns /// leaf.beginValueOn() /// - a getChild() function that returns a pointer to the child node to which the iterator /// is currently pointing (always NULL if the iterator is a Value iterator) template struct IterTraits { template static ChildT* getChild(const IterT&) { return NULL; } }; template struct IterTraits { typedef typename NodeT::ChildOnIter IterT; static IterT begin(NodeT& node) { return node.beginChildOn(); } template static ChildT* getChild(const IterT& iter) { return &iter.getValue(); } template struct NodeConverter { typedef typename OtherNodeT::ChildOnIter Type; }; }; template struct IterTraits { typedef typename NodeT::ChildOnCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginChildOn(); } template static const ChildT* getChild(const IterT& iter) { return &iter.getValue(); } template struct NodeConverter { typedef typename OtherNodeT::ChildOnCIter Type; }; }; template struct IterTraits { typedef typename NodeT::ChildOffIter IterT; static IterT begin(NodeT& node) { return node.beginChildOff(); } template struct NodeConverter { typedef typename OtherNodeT::ChildOffIter Type; }; }; template struct IterTraits { typedef typename NodeT::ChildOffCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginChildOff(); } template struct NodeConverter { typedef typename OtherNodeT::ChildOffCIter Type; }; }; template struct IterTraits { typedef typename NodeT::ChildAllIter IterT; static IterT begin(NodeT& node) { return node.beginChildAll(); } template static ChildT* getChild(const IterT& iter) { typename IterT::NonConstValueType val; return iter.probeChild(val); } template struct NodeConverter { typedef typename OtherNodeT::ChildAllIter Type; }; }; template struct IterTraits { typedef typename NodeT::ChildAllCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginChildAll(); } template static ChildT* getChild(const IterT& iter) { typename IterT::NonConstValueType val; return iter.probeChild(val); } template struct NodeConverter { typedef typename OtherNodeT::ChildAllCIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueOnIter IterT; static IterT begin(NodeT& node) { return node.beginValueOn(); } template struct NodeConverter { typedef typename OtherNodeT::ValueOnIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueOnCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginValueOn(); } template struct NodeConverter { typedef typename OtherNodeT::ValueOnCIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueOffIter IterT; static IterT begin(NodeT& node) { return node.beginValueOff(); } template struct NodeConverter { typedef typename OtherNodeT::ValueOffIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueOffCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginValueOff(); } template struct NodeConverter { typedef typename OtherNodeT::ValueOffCIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueAllIter IterT; static IterT begin(NodeT& node) { return node.beginValueAll(); } template struct NodeConverter { typedef typename OtherNodeT::ValueAllIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueAllCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginValueAll(); } template struct NodeConverter { typedef typename OtherNodeT::ValueAllCIter Type; }; }; //////////////////////////////////////// /// @brief An IterListItem is an element of a compile-time linked list of iterators /// to nodes of different types. /// /// The list is constructed by traversing the template hierarchy of a Tree in reverse order, /// so typically the elements will be a LeafNode iterator of some type (e.g., ValueOnCIter), /// followed by one or more InternalNode iterators of the same type, followed by a RootNode /// iterator of the same type. /// /// The length of the list is fixed at compile time, and because it is implemented using /// nested, templated classes, much of the list traversal logic can be optimized away. template class IterListItem { public: /// The type of iterator stored in the previous list item typedef typename PrevItemT::IterT PrevIterT; /// The type of node (non-const) whose iterator is stored in this list item typedef typename boost::mpl::front::type _NodeT; /// The type of iterator stored in this list item (e.g., InternalNode::ValueOnCIter) typedef typename IterTraits::template NodeConverter<_NodeT>::Type IterT; /// The type of node (const or non-const) over which IterT iterates (e.g., const RootNode<...>) typedef typename IterT::NodeType NodeT; /// The type of the node with const qualifiers removed ("Non-Const") typedef typename IterT::NonConstNodeType NCNodeT; /// The type of value (with const qualifiers removed) to which the iterator points typedef typename IterT::NonConstValueType NCValueT; /// NodeT's child node type, with the same constness (e.g., const InternalNode<...>) typedef typename CopyConstness::Type ChildT; /// NodeT's child node type with const qualifiers removed typedef typename CopyConstness::Type NCChildT; typedef IterTraits ITraits; /// NodeT's level in its tree (0 = LeafNode) static const Index Level = _Level; IterListItem(PrevItemT* prev): mNext(this), mPrev(prev) {} IterListItem(const IterListItem& other): mIter(other.mIter), mNext(other.mNext), mPrev(NULL) {} IterListItem& operator=(const IterListItem& other) { if (&other != this) { mIter = other.mIter; mNext = other.mNext; mPrev = NULL; ///< @note external call to updateBackPointers() required } return *this; } void updateBackPointers(PrevItemT* prev) { mPrev = prev; mNext.updateBackPointers(this); } void setIter(const IterT& iter) { mIter = iter; } template void setIter(const OtherIterT& iter) { mNext.setIter(iter); } /// Return the node over which this list element's iterator iterates. void getNode(Index lvl, NodeT*& node) const { node = (lvl <= Level) ? mIter.getParentNode() : NULL; } /// Return the node over which one of the following list elements' iterator iterates. template void getNode(Index lvl, OtherNodeT*& node) const { mNext.getNode(lvl, node); } /// @brief Initialize the iterator for level @a lvl of the tree with the node /// over which the corresponding iterator of @a otherListItem is iterating. /// /// For example, if @a otherListItem contains a LeafNode::ValueOnIter, /// initialize this list's leaf iterator with the same LeafNode. template void initLevel(Index lvl, OtherIterListItemT& otherListItem) { if (lvl == Level) { const NodeT* node = NULL; otherListItem.getNode(lvl, node); mIter = (node == NULL) ? IterT() : ITraits::begin(*const_cast(node)); } else { // Forward to one of the following list elements. mNext.initLevel(lvl, otherListItem); } } /// Return The table offset of the iterator at level @a lvl of the tree. Index pos(Index lvl) const { return (lvl == Level) ? mIter.pos() : mNext.pos(lvl); } /// Return @c true if the iterator at level @a lvl of the tree has not yet reached its end. bool test(Index lvl) const { return (lvl == Level) ? mIter.test() : mNext.test(lvl); } /// Increment the iterator at level @a lvl of the tree. bool next(Index lvl) { return (lvl == Level) ? mIter.next() : mNext.next(lvl); } /// @brief If the iterator at level @a lvl of the tree points to a child node, /// initialize the next iterator in this list with that child node. bool down(Index lvl) { if (lvl == Level && mPrev != NULL && mIter) { if (ChildT* child = ITraits::template getChild(mIter)) { mPrev->setIter(PrevItemT::ITraits::begin(*child)); return true; } } return (lvl > Level) ? mNext.down(lvl) : false; } /// @brief Return the global coordinates of the voxel or tile to which the iterator /// at level @a lvl of the tree is currently pointing. Coord getCoord(Index lvl) const { return (lvl == Level) ? mIter.getCoord() : mNext.getCoord(lvl); } Index getChildDim(Index lvl) const { return (lvl == Level) ? NodeT::getChildDim() : mNext.getChildDim(lvl); } /// Return the number of (virtual) voxels spanned by a tile value or child node Index64 getVoxelCount(Index lvl) const { return (lvl == Level) ? ChildT::NUM_VOXELS : mNext.getVoxelCount(lvl); } /// Return @c true if the iterator at level @a lvl of the tree points to an active value. bool isValueOn(Index lvl) const { return (lvl == Level) ? mIter.isValueOn() : mNext.isValueOn(lvl); } /// Return the value to which the iterator at level @a lvl of the tree points. const NCValueT& getValue(Index lvl) const { if (lvl == Level) return mIter.getValue(); return mNext.getValue(lvl); } /// @brief Set the value (to @a val) to which the iterator at level @a lvl /// of the tree points and mark the value as active. /// @note Not valid when @c IterT is a const iterator type void setValue(Index lvl, const NCValueT& val) const { if (lvl == Level) mIter.setValue(val); else mNext.setValue(lvl, val); } /// @brief Set the value (to @a val) to which the iterator at level @a lvl of the tree /// points and mark the value as active if @a on is @c true, or inactive otherwise. /// @note Not valid when @c IterT is a const iterator type void setValueOn(Index lvl, bool on = true) const { if (lvl == Level) mIter.setValueOn(on); else mNext.setValueOn(lvl, on); } /// @brief Mark the value to which the iterator at level @a lvl of the tree points /// as inactive. /// @note Not valid when @c IterT is a const iterator type void setValueOff(Index lvl) const { if (lvl == Level) mIter.setValueOff(); else mNext.setValueOff(lvl); } /// @brief Apply a functor to the item to which this iterator is pointing. /// @note Not valid when @c IterT is a const iterator type template void modifyValue(Index lvl, const ModifyOp& op) const { if (lvl == Level) mIter.modifyValue(op); else mNext.modifyValue(lvl, op); } private: typedef typename boost::mpl::pop_front::type RestT; // NodeVecT minus its first item typedef IterListItem NextItem; IterT mIter; NextItem mNext; PrevItemT* mPrev; }; /// The initial element of a compile-time linked list of iterators to nodes of different types template class IterListItem { public: /// The type of iterator stored in the previous list item typedef typename PrevItemT::IterT PrevIterT; /// The type of node (non-const) whose iterator is stored in this list item typedef typename boost::mpl::front::type _NodeT; /// The type of iterator stored in this list item (e.g., InternalNode::ValueOnCIter) typedef typename IterTraits::template NodeConverter<_NodeT>::Type IterT; /// The type of node (const or non-const) over which IterT iterates (e.g., const RootNode<...>) typedef typename IterT::NodeType NodeT; /// The type of the node with const qualifiers removed ("Non-Const") typedef typename IterT::NonConstNodeType NCNodeT; /// The type of value (with const qualifiers removed) to which the iterator points typedef typename IterT::NonConstValueType NCValueT; typedef IterTraits ITraits; /// NodeT's level in its tree (0 = LeafNode) static const Index Level = 0; IterListItem(PrevItemT*): mNext(this), mPrev(NULL) {} IterListItem(const IterListItem& other): mIter(other.mIter), mNext(other.mNext), mPrev(NULL) {} IterListItem& operator=(const IterListItem& other) { if (&other != this) { mIter = other.mIter; mNext = other.mNext; mPrev = NULL; } return *this; } void updateBackPointers(PrevItemT* = NULL) { mPrev = NULL; mNext.updateBackPointers(this); } void setIter(const IterT& iter) { mIter = iter; } template void setIter(const OtherIterT& iter) { mNext.setIter(iter); } void getNode(Index lvl, NodeT*& node) const { node = (lvl == 0) ? mIter.getParentNode() : NULL; } template void getNode(Index lvl, OtherNodeT*& node) const { mNext.getNode(lvl, node); } template void initLevel(Index lvl, OtherIterListItemT& otherListItem) { if (lvl == 0) { const NodeT* node = NULL; otherListItem.getNode(lvl, node); mIter = (node == NULL) ? IterT() : ITraits::begin(*const_cast(node)); } else { mNext.initLevel(lvl, otherListItem); } } Index pos(Index lvl) const { return (lvl == 0) ? mIter.pos() : mNext.pos(lvl); } bool test(Index lvl) const { return (lvl == 0) ? mIter.test() : mNext.test(lvl); } bool next(Index lvl) { return (lvl == 0) ? mIter.next() : mNext.next(lvl); } bool down(Index lvl) { return (lvl == 0) ? false : mNext.down(lvl); } Coord getCoord(Index lvl) const { return (lvl == 0) ? mIter.getCoord() : mNext.getCoord(lvl); } Index getChildDim(Index lvl) const { return (lvl == 0) ? NodeT::getChildDim() : mNext.getChildDim(lvl); } Index64 getVoxelCount(Index lvl) const { return (lvl == 0) ? 1 : mNext.getVoxelCount(lvl); } bool isValueOn(Index lvl) const { return (lvl == 0) ? mIter.isValueOn() : mNext.isValueOn(lvl); } const NCValueT& getValue(Index lvl) const { if (lvl == 0) return mIter.getValue(); return mNext.getValue(lvl); } void setValue(Index lvl, const NCValueT& val) const { if (lvl == 0) mIter.setValue(val); else mNext.setValue(lvl, val); } void setValueOn(Index lvl, bool on = true) const { if (lvl == 0) mIter.setValueOn(on); else mNext.setValueOn(lvl, on); } void setValueOff(Index lvl) const { if (lvl == 0) mIter.setValueOff(); else mNext.setValueOff(lvl); } template void modifyValue(Index lvl, const ModifyOp& op) const { if (lvl == 0) mIter.modifyValue(op); else mNext.modifyValue(lvl, op); } private: typedef typename boost::mpl::pop_front::type RestT; // NodeVecT minus its first item typedef IterListItem NextItem; IterT mIter; NextItem mNext; PrevItemT* mPrev; }; /// The final element of a compile-time linked list of iterators to nodes of different types template class IterListItem { public: typedef typename boost::mpl::front::type _NodeT; /// The type of iterator stored in the previous list item typedef typename PrevItemT::IterT PrevIterT; /// The type of iterator stored in this list item (e.g., RootNode::ValueOnCIter) typedef typename IterTraits::template NodeConverter<_NodeT>::Type IterT; /// The type of node over which IterT iterates (e.g., const RootNode<...>) typedef typename IterT::NodeType NodeT; /// The type of the node with const qualifiers removed ("Non-Const") typedef typename IterT::NonConstNodeType NCNodeT; /// The type of value (with const qualifiers removed) to which the iterator points typedef typename IterT::NonConstValueType NCValueT; /// NodeT's child node type, with the same constness (e.g., const InternalNode<...>) typedef typename CopyConstness::Type ChildT; /// NodeT's child node type with const qualifiers removed typedef typename CopyConstness::Type NCChildT; typedef IterTraits ITraits; /// NodeT's level in its tree (0 = LeafNode) static const Index Level = _Level; IterListItem(PrevItemT* prev): mPrev(prev) {} IterListItem(const IterListItem& other): mIter(other.mIter), mPrev(NULL) {} IterListItem& operator=(const IterListItem& other) { if (&other != this) { mIter = other.mIter; mPrev = NULL; ///< @note external call to updateBackPointers() required } return *this; } void updateBackPointers(PrevItemT* prev) { mPrev = prev; } // The following method specializations differ from the default template // implementations mainly in that they don't forward. void setIter(const IterT& iter) { mIter = iter; } void getNode(Index lvl, NodeT*& node) const { node = (lvl <= Level) ? mIter.getParentNode() : NULL; } template void initLevel(Index lvl, OtherIterListItemT& otherListItem) { if (lvl == Level) { const NodeT* node = NULL; otherListItem.getNode(lvl, node); mIter = (node == NULL) ? IterT() : ITraits::begin(*const_cast(node)); } } Index pos(Index lvl) const { return (lvl == Level) ? mIter.pos() : Index(-1); } bool test(Index lvl) const { return (lvl == Level) ? mIter.test() : false; } bool next(Index lvl) { return (lvl == Level) ? mIter.next() : false; } bool down(Index lvl) { if (lvl == Level && mPrev != NULL && mIter) { if (ChildT* child = ITraits::template getChild(mIter)) { mPrev->setIter(PrevItemT::ITraits::begin(*child)); return true; } } return false; } Coord getCoord(Index lvl) const { return (lvl == Level) ? mIter.getCoord() : Coord(); } Index getChildDim(Index lvl) const { return (lvl == Level) ? NodeT::getChildDim() : 0; } Index64 getVoxelCount(Index lvl) const { return (lvl == Level) ? ChildT::NUM_VOXELS : 0; } bool isValueOn(Index lvl) const { return (lvl == Level) ? mIter.isValueOn() : false; } const NCValueT& getValue(Index lvl) const { assert(lvl == Level); (void)lvl; // avoid unused variable warning in optimized builds return mIter.getValue(); } void setValue(Index lvl, const NCValueT& val) const { if (lvl == Level) mIter.setValue(val); } void setValueOn(Index lvl, bool on = true) const { if (lvl == Level) mIter.setValueOn(on); } void setValueOff(Index lvl) const { if (lvl == Level) mIter.setValueOff(); } template void modifyValue(Index lvl, const ModifyOp& op) const { if (lvl == Level) mIter.modifyValue(op); } private: IterT mIter; PrevItemT* mPrev; }; //////////////////////////////////////// //#define DEBUG_TREE_VALUE_ITERATOR /// @brief Base class for tree-traversal iterators over tile and voxel values template class TreeValueIteratorBase { public: typedef _TreeT TreeT; typedef _ValueIterT ValueIterT; typedef typename ValueIterT::NodeType NodeT; typedef typename ValueIterT::NonConstValueType ValueT; typedef typename NodeT::ChildOnCIter ChildOnIterT; static const Index ROOT_LEVEL = NodeT::LEVEL; BOOST_STATIC_ASSERT(ValueIterT::NodeType::LEVEL == ROOT_LEVEL); static const Index LEAF_LEVEL = 0, ROOT_DEPTH = 0, LEAF_DEPTH = ROOT_LEVEL; TreeValueIteratorBase(TreeT&); TreeValueIteratorBase(const TreeValueIteratorBase& other); TreeValueIteratorBase& operator=(const TreeValueIteratorBase& other); /// Specify the depth of the highest level of the tree to which to ascend (depth 0 = root). void setMinDepth(Index minDepth); /// Return the depth of the highest level of the tree to which this iterator ascends. Index getMinDepth() const { return ROOT_LEVEL - Index(mMaxLevel); } /// Specify the depth of the lowest level of the tree to which to descend (depth 0 = root). void setMaxDepth(Index maxDepth); /// Return the depth of the lowest level of the tree to which this iterator ascends. Index getMaxDepth() const { return ROOT_LEVEL - Index(mMinLevel); } //@{ /// Return @c true if this iterator is not yet exhausted. bool test() const { return mValueIterList.test(mLevel); } operator bool() const { return this->test(); } //@} /// @brief Advance to the next tile or voxel value. /// Return @c true if this iterator is not yet exhausted. bool next(); /// Advance to the next tile or voxel value. TreeValueIteratorBase& operator++() { this->next(); return *this; } /// @brief Return the level in the tree (0 = leaf) of the node to which /// this iterator is currently pointing. Index getLevel() const { return mLevel; } /// @brief Return the depth in the tree (0 = root) of the node to which /// this iterator is currently pointing. Index getDepth() const { return ROOT_LEVEL - mLevel; } static Index getLeafDepth() { return LEAF_DEPTH; } /// @brief Return in @a node a pointer to the node over which this iterator is /// currently iterating or one of that node's parents, as determined by @a NodeType. /// @return a null pointer if @a NodeType specifies a node at a lower level /// of the tree than that given by getLevel(). template void getNode(NodeType*& node) const { mValueIterList.getNode(mLevel, node); } /// @brief Return the global coordinates of the voxel or tile to which /// this iterator is currently pointing. Coord getCoord() const { return mValueIterList.getCoord(mLevel); } /// @brief Return in @a bbox the axis-aligned bounding box of /// the voxel or tile to which this iterator is currently pointing. /// @return false if the bounding box is empty. bool getBoundingBox(CoordBBox&) const; /// @brief Return the axis-aligned bounding box of the voxel or tile to which /// this iterator is currently pointing. CoordBBox getBoundingBox() const { CoordBBox b; this->getBoundingBox(b); return b; } /// Return the number of (virtual) voxels corresponding to the value Index64 getVoxelCount() const { return mValueIterList.getVoxelCount(mLevel);} /// Return @c true if this iterator is currently pointing to a (non-leaf) tile value. bool isTileValue() const { return mLevel != 0 && this->test(); } /// Return @c true if this iterator is currently pointing to a (leaf) voxel value. bool isVoxelValue() const { return mLevel == 0 && this->test(); } /// Return @c true if the value to which this iterator is currently pointing is active. bool isValueOn() const { return mValueIterList.isValueOn(mLevel); } //@{ /// Return the tile or voxel value to which this iterator is currently pointing. const ValueT& getValue() const { return mValueIterList.getValue(mLevel); } const ValueT& operator*() const { return this->getValue(); } const ValueT* operator->() const { return &(this->operator*()); } //@} /// @brief Change the tile or voxel value to which this iterator is currently pointing /// and mark it as active. void setValue(const ValueT& val) const { mValueIterList.setValue(mLevel, val); } /// @brief Change the active/inactive state of the tile or voxel value to which /// this iterator is currently pointing. void setActiveState(bool on) const { mValueIterList.setValueOn(mLevel, on); } /// Mark the tile or voxel value to which this iterator is currently pointing as inactive. void setValueOff() const { mValueIterList.setValueOff(mLevel); } /// @brief Apply a functor to the item to which this iterator is pointing. /// (Not valid for const iterators.) /// @param op a functor of the form void op(ValueType&) const that modifies /// its argument in place /// @see Tree::modifyValue() template void modifyValue(const ModifyOp& op) const { mValueIterList.modifyValue(mLevel, op); } /// Return a pointer to the tree over which this iterator is iterating. TreeT* getTree() const { return mTree; } /// Return a string (for debugging, mainly) describing this iterator's current state. std::string summary() const; private: bool advance(bool dontIncrement = false); typedef typename iter::InvertedTree::Type InvTreeT; struct PrevChildItem { typedef ChildOnIterT IterT; }; struct PrevValueItem { typedef ValueIterT IterT; }; IterListItem mChildIterList; IterListItem mValueIterList; Index mLevel; int mMinLevel, mMaxLevel; TreeT* mTree; }; // class TreeValueIteratorBase template inline TreeValueIteratorBase::TreeValueIteratorBase(TreeT& tree): mChildIterList(NULL), mValueIterList(NULL), mLevel(ROOT_LEVEL), mMinLevel(int(LEAF_LEVEL)), mMaxLevel(int(ROOT_LEVEL)), mTree(&tree) { mChildIterList.setIter(IterTraits::begin(tree.root())); mValueIterList.setIter(IterTraits::begin(tree.root())); this->advance(/*dontIncrement=*/true); } template inline TreeValueIteratorBase::TreeValueIteratorBase(const TreeValueIteratorBase& other): mChildIterList(other.mChildIterList), mValueIterList(other.mValueIterList), mLevel(other.mLevel), mMinLevel(other.mMinLevel), mMaxLevel(other.mMaxLevel), mTree(other.mTree) { mChildIterList.updateBackPointers(); mValueIterList.updateBackPointers(); } template inline TreeValueIteratorBase& TreeValueIteratorBase::operator=(const TreeValueIteratorBase& other) { if (&other != this) { mChildIterList = other.mChildIterList; mValueIterList = other.mValueIterList; mLevel = other.mLevel; mMinLevel = other.mMinLevel; mMaxLevel = other.mMaxLevel; mTree = other.mTree; mChildIterList.updateBackPointers(); mValueIterList.updateBackPointers(); } return *this; } template inline void TreeValueIteratorBase::setMinDepth(Index minDepth) { mMaxLevel = int(ROOT_LEVEL - minDepth); // level = ROOT_LEVEL - depth if (int(mLevel) > mMaxLevel) this->next(); } template inline void TreeValueIteratorBase::setMaxDepth(Index maxDepth) { // level = ROOT_LEVEL - depth mMinLevel = int(ROOT_LEVEL - std::min(maxDepth, this->getLeafDepth())); if (int(mLevel) < mMinLevel) this->next(); } template inline bool TreeValueIteratorBase::next() { do { if (!this->advance()) return false; } while (int(mLevel) < mMinLevel || int(mLevel) > mMaxLevel); return true; } template inline bool TreeValueIteratorBase::advance(bool dontIncrement) { bool recurse = false; do { recurse = false; Index vPos = mValueIterList.pos(mLevel), cPos = mChildIterList.pos(mLevel); if (vPos == cPos && mChildIterList.test(mLevel)) { /// @todo Once ValueOff iterators properly skip child pointers, remove this block. mValueIterList.next(mLevel); vPos = mValueIterList.pos(mLevel); } if (vPos < cPos) { if (dontIncrement) return true; if (mValueIterList.next(mLevel)) { if (mValueIterList.pos(mLevel) == cPos && mChildIterList.test(mLevel)) { /// @todo Once ValueOff iterators properly skip child pointers, /// remove this block. mValueIterList.next(mLevel); } // If there is a next value and it precedes the next child, return. if (mValueIterList.pos(mLevel) < cPos) return true; } } else { // Advance to the next child, which may or may not precede the next value. if (!dontIncrement) mChildIterList.next(mLevel); } #ifdef DEBUG_TREE_VALUE_ITERATOR std::cout << "\n" << this->summary() << std::flush; #endif // Descend to the lowest level at which the next value precedes the next child. while (mChildIterList.pos(mLevel) < mValueIterList.pos(mLevel)) { #ifdef ENABLE_TREE_VALUE_DEPTH_BOUND_OPTIMIZATION if (int(mLevel) == mMinLevel) { // If the current node lies at the lowest allowed level, none of its // children can be visited, so just advance its child iterator. mChildIterList.next(mLevel); if (mValueIterList.pos(mLevel) == mChildIterList.pos(mLevel) && mChildIterList.test(mLevel)) { /// @todo Once ValueOff iterators properly skip child pointers, /// remove this block. mValueIterList.next(mLevel); } } else #endif if (mChildIterList.down(mLevel)) { --mLevel; // descend one level mValueIterList.initLevel(mLevel, mChildIterList); if (mValueIterList.pos(mLevel) == mChildIterList.pos(mLevel) && mChildIterList.test(mLevel)) { /// @todo Once ValueOff iterators properly skip child pointers, /// remove this block. mValueIterList.next(mLevel); } } else break; #ifdef DEBUG_TREE_VALUE_ITERATOR std::cout << "\n" << this->summary() << std::flush; #endif } // Ascend to the nearest level at which one of the iterators is not yet exhausted. while (!mChildIterList.test(mLevel) && !mValueIterList.test(mLevel)) { if (mLevel == ROOT_LEVEL) return false; ++mLevel; mChildIterList.next(mLevel); dontIncrement = true; recurse = true; } } while (recurse); return true; } template inline bool TreeValueIteratorBase::getBoundingBox(CoordBBox& bbox) const { if (!this->test()) { bbox = CoordBBox(); return false; } bbox.min() = mValueIterList.getCoord(mLevel); bbox.max() = bbox.min().offsetBy(mValueIterList.getChildDim(mLevel) - 1); return true; } template inline std::string TreeValueIteratorBase::summary() const { std::ostringstream ostr; for (int lvl = int(ROOT_LEVEL); lvl >= 0 && lvl >= int(mLevel); --lvl) { if (lvl == 0) ostr << "leaf"; else if (lvl == int(ROOT_LEVEL)) ostr << "root"; else ostr << "int" << (ROOT_LEVEL - lvl); ostr << " v" << mValueIterList.pos(lvl) << " c" << mChildIterList.pos(lvl); if (lvl > int(mLevel)) ostr << " / "; } if (this->test() && mValueIterList.pos(mLevel) < mChildIterList.pos(mLevel)) { if (mLevel == 0) { ostr << " " << this->getCoord(); } else { ostr << " " << this->getBoundingBox(); } } return ostr.str(); } //////////////////////////////////////// /// @brief Base class for tree-traversal iterators over all nodes template class NodeIteratorBase { public: typedef _TreeT TreeT; typedef RootChildOnIterT RootIterT; typedef typename RootIterT::NodeType RootNodeT; typedef typename RootIterT::NonConstNodeType NCRootNodeT; static const Index ROOT_LEVEL = RootNodeT::LEVEL; typedef typename iter::InvertedTree::Type InvTreeT; static const Index LEAF_LEVEL = 0, ROOT_DEPTH = 0, LEAF_DEPTH = ROOT_LEVEL; typedef IterTraits RootIterTraits; NodeIteratorBase(); NodeIteratorBase(TreeT&); NodeIteratorBase(const NodeIteratorBase& other); NodeIteratorBase& operator=(const NodeIteratorBase& other); /// Specify the depth of the highest level of the tree to which to ascend (depth 0 = root). void setMinDepth(Index minDepth); /// Return the depth of the highest level of the tree to which this iterator ascends. Index getMinDepth() const { return ROOT_LEVEL - Index(mMaxLevel); } /// Specify the depth of the lowest level of the tree to which to descend (depth 0 = root). void setMaxDepth(Index maxDepth); /// Return the depth of the lowest level of the tree to which this iterator ascends. Index getMaxDepth() const { return ROOT_LEVEL - Index(mMinLevel); } //@{ /// Return @c true if this iterator is not yet exhausted. bool test() const { return !mDone; } operator bool() const { return this->test(); } //@} /// @brief Advance to the next tile or voxel value. /// @return @c true if this iterator is not yet exhausted. bool next(); /// Advance the iterator to the next leaf node. void increment() { this->next(); } NodeIteratorBase& operator++() { this->increment(); return *this; } /// Increment the iterator n times. void increment(Index n) { for (Index i = 0; i < n && this->next(); ++i) {} } /// @brief Return the level in the tree (0 = leaf) of the node to which /// this iterator is currently pointing. Index getLevel() const { return mLevel; } /// @brief Return the depth in the tree (0 = root) of the node to which /// this iterator is currently pointing. Index getDepth() const { return ROOT_LEVEL - mLevel; } static Index getLeafDepth() { return LEAF_DEPTH; } /// @brief Return the global coordinates of the voxel or tile to which /// this iterator is currently pointing. Coord getCoord() const; /// @brief Return in @a bbox the axis-aligned bounding box of /// the voxel or tile to which this iterator is currently pointing. /// @return false if the bounding box is empty. bool getBoundingBox(CoordBBox& bbox) const; /// @brief Return the axis-aligned bounding box of the voxel or tile to which /// this iterator is currently pointing. CoordBBox getBoundingBox() const { CoordBBox b; this->getBoundingBox(b); return b; } //@{ /// @brief Return the node to which the iterator is pointing. /// @note This iterator doesn't have the usual dereference operators (* and ->), /// because they would have to be overloaded by the returned node type. template void getNode(NodeT*& node) const { node = NULL; mIterList.getNode(mLevel, node); } template void getNode(const NodeT*& node) const { node = NULL; mIterList.getNode(mLevel, node); } //@} TreeT* getTree() const { return mTree; } std::string summary() const; private: struct PrevItem { typedef RootIterT IterT; }; IterListItem mIterList; Index mLevel; int mMinLevel, mMaxLevel; bool mDone; TreeT* mTree; }; // class NodeIteratorBase template inline NodeIteratorBase::NodeIteratorBase(): mIterList(NULL), mLevel(ROOT_LEVEL), mMinLevel(int(LEAF_LEVEL)), mMaxLevel(int(ROOT_LEVEL)), mDone(true), mTree(NULL) { } template inline NodeIteratorBase::NodeIteratorBase(TreeT& tree): mIterList(NULL), mLevel(ROOT_LEVEL), mMinLevel(int(LEAF_LEVEL)), mMaxLevel(int(ROOT_LEVEL)), mDone(false), mTree(&tree) { mIterList.setIter(RootIterTraits::begin(tree.root())); } template inline NodeIteratorBase::NodeIteratorBase(const NodeIteratorBase& other): mIterList(other.mIterList), mLevel(other.mLevel), mMinLevel(other.mMinLevel), mMaxLevel(other.mMaxLevel), mDone(other.mDone), mTree(other.mTree) { mIterList.updateBackPointers(); } template inline NodeIteratorBase& NodeIteratorBase::operator=(const NodeIteratorBase& other) { if (&other != this) { mLevel = other.mLevel; mMinLevel = other.mMinLevel; mMaxLevel = other.mMaxLevel; mDone = other.mDone; mTree = other.mTree; mIterList = other.mIterList; mIterList.updateBackPointers(); } return *this; } template inline void NodeIteratorBase::setMinDepth(Index minDepth) { mMaxLevel = int(ROOT_LEVEL - minDepth); // level = ROOT_LEVEL - depth if (int(mLevel) > mMaxLevel) this->next(); } template inline void NodeIteratorBase::setMaxDepth(Index maxDepth) { // level = ROOT_LEVEL - depth mMinLevel = int(ROOT_LEVEL - std::min(maxDepth, this->getLeafDepth())); if (int(mLevel) < mMinLevel) this->next(); } template inline bool NodeIteratorBase::next() { do { if (mDone) return false; // If the iterator over the current node points to a child, // descend to the child (depth-first traversal). if (int(mLevel) > mMinLevel && mIterList.test(mLevel)) { if (!mIterList.down(mLevel)) return false; --mLevel; } else { // Ascend to the nearest ancestor that has other children. while (!mIterList.test(mLevel)) { if (mLevel == ROOT_LEVEL) { // Can't ascend higher than the root. mDone = true; return false; } ++mLevel; // ascend one level mIterList.next(mLevel); // advance to the next child, if there is one } // Descend to the child. if (!mIterList.down(mLevel)) return false; --mLevel; } } while (int(mLevel) < mMinLevel || int(mLevel) > mMaxLevel); return true; } template inline Coord NodeIteratorBase::getCoord() const { if (mLevel != ROOT_LEVEL) return mIterList.getCoord(mLevel + 1); RootNodeT* root = NULL; this->getNode(root); return root ? root->getMinIndex() : Coord::min(); } template inline bool NodeIteratorBase::getBoundingBox(CoordBBox& bbox) const { if (mLevel == ROOT_LEVEL) { RootNodeT* root = NULL; this->getNode(root); if (root == NULL) { bbox = CoordBBox(); return false; } root->getIndexRange(bbox); return true; } bbox.min() = mIterList.getCoord(mLevel + 1); bbox.max() = bbox.min().offsetBy(mIterList.getChildDim(mLevel + 1) - 1); return true; } template inline std::string NodeIteratorBase::summary() const { std::ostringstream ostr; for (int lvl = int(ROOT_LEVEL); lvl >= 0 && lvl >= int(mLevel); --lvl) { if (lvl == 0) ostr << "leaf"; else if (lvl == int(ROOT_LEVEL)) ostr << "root"; else ostr << "int" << (ROOT_LEVEL - lvl); ostr << " c" << mIterList.pos(lvl); if (lvl > int(mLevel)) ostr << " / "; } CoordBBox bbox; this->getBoundingBox(bbox); ostr << " " << bbox; return ostr.str(); } //////////////////////////////////////// /// @brief Base class for tree-traversal iterators over all leaf nodes (but not leaf voxels) template class LeafIteratorBase { public: typedef RootChildOnIterT RootIterT; typedef typename RootIterT::NodeType RootNodeT; typedef typename RootIterT::NonConstNodeType NCRootNodeT; static const Index ROOT_LEVEL = RootNodeT::LEVEL; typedef typename iter::InvertedTree::Type InvTreeT; typedef typename boost::mpl::front::type NCLeafNodeT; typedef typename CopyConstness::Type LeafNodeT; static const Index LEAF_LEVEL = 0, LEAF_PARENT_LEVEL = LEAF_LEVEL + 1; typedef IterTraits RootIterTraits; LeafIteratorBase(): mIterList(NULL), mTree(NULL) {} LeafIteratorBase(TreeT& tree): mIterList(NULL), mTree(&tree) { // Initialize the iterator list with a root node iterator. mIterList.setIter(RootIterTraits::begin(tree.root())); // Descend along the first branch, initializing the node iterator at each level. Index lvl = ROOT_LEVEL; for ( ; lvl > 0 && mIterList.down(lvl); --lvl) {} // If the first branch terminated above the leaf level, backtrack to the next branch. if (lvl > 0) this->next(); } LeafIteratorBase(const LeafIteratorBase& other): mIterList(other.mIterList), mTree(other.mTree) { mIterList.updateBackPointers(); } LeafIteratorBase& operator=(const LeafIteratorBase& other) { if (&other != this) { mTree = other.mTree; mIterList = other.mIterList; mIterList.updateBackPointers(); } return *this; } //@{ /// Return the leaf node to which the iterator is pointing. LeafNodeT* getLeaf() const { LeafNodeT* n = NULL; mIterList.getNode(LEAF_LEVEL, n); return n; } LeafNodeT& operator*() const { return *this->getLeaf(); } LeafNodeT* operator->() const { return this->getLeaf(); } //@} bool test() const { return mIterList.test(LEAF_PARENT_LEVEL); } operator bool() const { return this->test(); } //@{ /// Advance the iterator to the next leaf node. bool next(); void increment() { this->next(); } LeafIteratorBase& operator++() { this->increment(); return *this; } //@} /// Increment the iterator n times. void increment(Index n) { for (Index i = 0; i < n && this->next(); ++i) {} } TreeT* getTree() const { return mTree; } private: struct PrevItem { typedef RootIterT IterT; }; /// @note Even though a LeafIterator doesn't iterate over leaf voxels, /// the first item of this linked list of node iterators is a leaf node iterator, /// whose purpose is only to provide access to its parent leaf node. IterListItem mIterList; TreeT* mTree; }; // class LeafIteratorBase template inline bool LeafIteratorBase::next() { // If the iterator is valid for the current node one level above the leaf level, // advance the iterator to the node's next child. if (mIterList.test(LEAF_PARENT_LEVEL) && mIterList.next(LEAF_PARENT_LEVEL)) { mIterList.down(LEAF_PARENT_LEVEL); // initialize the leaf iterator return true; } Index lvl = LEAF_PARENT_LEVEL; while (!mIterList.test(LEAF_PARENT_LEVEL)) { if (mIterList.test(lvl)) { mIterList.next(lvl); } else { do { // Ascend to the nearest level at which // one of the iterators is not yet exhausted. if (lvl == ROOT_LEVEL) return false; ++lvl; if (mIterList.test(lvl)) mIterList.next(lvl); } while (!mIterList.test(lvl)); } // Descend to the lowest child, but not as far as the leaf iterator. while (lvl > LEAF_PARENT_LEVEL && mIterList.down(lvl)) --lvl; } mIterList.down(LEAF_PARENT_LEVEL); // initialize the leaf iterator return true; } //////////////////////////////////////// /// An IteratorRange wraps a tree or node iterator, giving the iterator TBB /// splittable range semantics. template class IteratorRange { public: IteratorRange(const IterT& iter, size_t grainSize = 8): mIter(iter), mGrainSize(grainSize), mSize(0) { mSize = this->size(); } IteratorRange(IteratorRange& other, tbb::split): mIter(other.mIter), mGrainSize(other.mGrainSize), mSize(other.mSize >> 1) { other.increment(mSize); } /// @brief Return a reference to this range's iterator. /// @note The reference is const, because the iterator should not be /// incremented directly. Use this range object's increment() instead. const IterT& iterator() const { return mIter; } bool empty() const { return mSize == 0 || !mIter.test(); } bool test() const { return !this->empty(); } operator bool() const { return !this->empty(); } /// @brief Return @c true if this range is splittable (i.e., if the iterator /// can be advanced more than mGrainSize times). bool is_divisible() const { return mSize > mGrainSize; } /// Advance the iterator @a n times. void increment(Index n = 1) { for ( ; n > 0 && mSize > 0; --n, --mSize, ++mIter) {} } /// Advance the iterator to the next item. IteratorRange& operator++() { this->increment(); return *this; } /// @brief Advance the iterator to the next item. /// @return @c true if the iterator is not yet exhausted. bool next() { this->increment(); return this->test(); } private: Index size() const { Index n = 0; for (IterT it(mIter); it.test(); ++n, ++it) {} return n; } IterT mIter; size_t mGrainSize; /// @note mSize is only an estimate of the number of times mIter can be incremented /// before it is exhausted (because the topology of the underlying tree could change /// during iteration). For the purpose of range splitting, though, that should be /// sufficient, since the two halves need not be of exactly equal size. Index mSize; }; //////////////////////////////////////// /// @brief Base class for tree-traversal iterators over real and virtual voxel values /// @todo class TreeVoxelIteratorBase; } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_TREEITERATOR_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/NodeManager.h0000644000000000000000000006651412603226506014517 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 NodeManager.h /// /// @author Ken Museth /// /// @brief NodeManager produces linear arrays of all tree nodes /// allowing for efficient threading and bottom-up processing. /// /// @note A NodeManager can be constructed from a Tree or LeafManager. /// The latter is slightly more efficient since the cached leaf nodes will be reused. #ifndef OPENVDB_TREE_NODEMANAGER_HAS_BEEN_INCLUDED #define OPENVDB_TREE_NODEMANAGER_HAS_BEEN_INCLUDED #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { // Produce linear arrays of all tree nodes, to facilitate efficient threading // and bottom-up processing. template class NodeManager; //////////////////////////////////////// /// @brief This class caches tree nodes of a specific type in a linear array. /// /// @note It is for internal use and should rarely be used directly. template class NodeList { public: typedef NodeT* value_type; typedef std::deque ListT; NodeList() {} void push_back(NodeT* node) { mList.push_back(node); } NodeT& operator()(size_t n) const { assert(nsize();} class Iterator { public: Iterator(const NodeRange& range, size_t pos): mRange(range), mPos(pos) { assert(this->isValid()); } Iterator& operator=(const Iterator& other) { mRange = other.mRange; mPos = other.mPos; return *this; } /// Advance to the next node. Iterator& operator++() { ++mPos; return *this; } /// Return a reference to the node to which this iterator is pointing. NodeT& operator*() const { return mRange.mNodeList(mPos); } /// Return a pointer to the node to which this iterator is pointing. NodeT* operator->() const { return &(this->operator*()); } /// Return the index into the list of the current node. size_t pos() const { return mPos; } bool isValid() const { return mPos>=mRange.mBegin && mPos<=mRange.mEnd; } /// Return @c true if this iterator is not yet exhausted. bool test() const { return mPos < mRange.mEnd; } /// Return @c true if this iterator is not yet exhausted. operator bool() const { return this->test(); } /// Return @c true if this iterator is exhausted. bool empty() const { return !this->test(); } bool operator!=(const Iterator& other) const { return (mPos != other.mPos) || (&mRange != &other.mRange); } bool operator==(const Iterator& other) const { return !(*this != other); } const NodeRange& nodeRange() const { return mRange; } private: const NodeRange& mRange; size_t mPos; };// NodeList::NodeRange::Iterator Iterator begin() const {return Iterator(*this, mBegin);} Iterator end() const {return Iterator(*this, mEnd);} private: size_t mEnd, mBegin, mGrainSize; const NodeList& mNodeList; static size_t doSplit(NodeRange& r) { assert(r.is_divisible()); size_t middle = r.mBegin + (r.mEnd - r.mBegin) / 2u; r.mEnd = middle; return middle; } };// NodeList::NodeRange /// Return a TBB-compatible NodeRange. NodeRange nodeRange(size_t grainsize = 1) const { return NodeRange(0, this->nodeCount(), *this, grainsize); } template struct NodeTransformer { NodeTransformer(const NodeOp& nodeOp) : mNodeOp(nodeOp) {} void run(const NodeRange& range, bool threaded = true) { threaded ? tbb::parallel_for(range, *this) : (*this)(range); } void operator()(const NodeRange& range) const { for (typename NodeRange::Iterator it = range.begin(); it; ++it) mNodeOp(*it); } const NodeOp mNodeOp; };// NodeRange template void foreach(const NodeOp& op, bool threaded = true, size_t grainSize=1) { NodeTransformer transform(op); transform.run(this->nodeRange(grainSize), threaded); } protected: ListT mList; };// NodeList ///////////////////////////////////////////// /// @brief This class is a link in a chain that each caches tree nodes /// of a specific type in a linear array. /// /// @note It is for internal use and should rarely be used directly. template class NodeManagerLink { public: NodeManagerLink() {} virtual ~NodeManagerLink() {} void clear() { mList.clear(); mNext.clear(); } template void init(ParentT& parent, TreeOrLeafManagerT& tree) { parent.getNodes(mList); for (size_t i=0, n=mList.nodeCount(); i void rebuild(ParentT& parent) { mList.clear(); parent.getNodes(mList); for (size_t i=0, n=mList.nodeCount(); i void processBottomUp(const NodeOp& op, bool threaded, size_t grainSize) { mNext.processBottomUp(op, threaded, grainSize); mList.foreach(op, threaded, grainSize); } template void processTopDown(const NodeOp& op, bool threaded, size_t grainSize) { mList.foreach(op, threaded, grainSize); mNext.processTopDown(op, threaded, grainSize); } protected: NodeList mList; NodeManagerLink mNext; };// NodeManagerLink class //////////////////////////////////////// /// @brief Specialization that terminates the chain of cached tree nodes /// /// @note It is for internal use and should rarely be used directly. template class NodeManagerLink { public: NodeManagerLink() {} virtual ~NodeManagerLink() {} /// @brief Clear all the cached tree nodes void clear() { mList.clear(); } template void rebuild(ParentT& parent) { mList.clear(); parent.getNodes(mList); } Index64 nodeCount() const { return mList.nodeCount(); } Index64 nodeCount(Index) const { return mList.nodeCount(); } template void processBottomUp(const NodeOp& op, bool threaded, size_t grainSize) { mList.foreach(op, threaded, grainSize); } template void processTopDown(const NodeOp& op, bool threaded, size_t grainSize) { mList.foreach(op, threaded, grainSize); } template void init(ParentT& parent, TreeOrLeafManagerT& tree) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (TreeOrLeafManagerT::DEPTH == 2 && NodeT::LEVEL == 0) { tree.getNodes(mList); } else { parent.getNodes(mList); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } protected: NodeList mList; };// NodeManagerLink class //////////////////////////////////////// /// @brief To facilitate threading over the nodes of a tree, cache /// node pointers in linear arrays, one for each level of the tree. /// /// @details This implementation works with trees of any depth, but /// optimized specializations are provided for the most typical tree depths. template class NodeManager { public: static const Index LEVELS = _LEVELS; BOOST_STATIC_ASSERT(LEVELS > 0);//special implementation below typedef typename TreeOrLeafManagerT::RootNodeType RootNodeType; BOOST_STATIC_ASSERT(RootNodeType::LEVEL >= LEVELS); NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) { mChain.init(mRoot, tree); } virtual ~NodeManager() {} /// @brief Clear all the cached tree nodes void clear() { mChain.clear(); } /// @brief Clear and recache all the tree nodes from the /// tree. This is required if tree nodes have been added or removed. void rebuild() { mChain.rebuild(mRoot); } /// @brief Return a reference to the root node. const RootNodeType& root() const { return mRoot; } /// @brief Return the total number of cached nodes (excluding the root node) Index64 nodeCount() const { return mChain.nodeCount(); } /// @brief Return the number of cached nodes at level @a i, where /// 0 corresponds to the lowest level. Index64 nodeCount(Index i) const { return mChain.nodeCount(i); } //@{ /// @brief Threaded method that applies a user-supplied functor /// to all the nodes in the tree. /// /// @param op user-supplied functor, see examples for interface details. /// @param threaded optional toggle to disable threading, on by default. /// @param grainSize optional parameter to specify the grainsize /// for threading, one by default. /// /// @warning The functor object is deep-copied to create TBB tasks. /// /// @par Example: /// @code /// // Functor to offset all the inactive values of a tree. Note /// // this implementation also illustrates how different /// // computation can be applied to the different node types. /// template /// struct OffsetOp /// { /// typedef typename TreeT::ValueType ValueT; /// typedef typename TreeT::RootNodeType RootT; /// typedef typename TreeT::LeafNodeType LeafT; /// OffsetOp(const ValueT& v) : mOffset(v) {} /// /// // Processes the root node. Required by the NodeManager /// void operator()(RootT& root) const /// { /// for (typename RootT::ValueOffIter i = root.beginValueOff(); i; ++i) *i += mOffset; /// } /// // Processes the leaf nodes. Required by the NodeManager /// void operator()(LeafT& leaf) const /// { /// for (typename LeafT::ValueOffIter i = leaf.beginValueOff(); i; ++i) *i += mOffset; /// } /// // Processes the internal nodes. Required by the NodeManager /// template /// void operator()(NodeT& node) const /// { /// for (typename NodeT::ValueOffIter i = node.beginValueOff(); i; ++i) *i += mOffset; /// } /// private: /// const ValueT mOffset; /// }; /// /// // usage: /// OffsetOp op(tree); /// tree::NodeManager nodes(tree); /// nodes.processBottomUp(op); /// /// // or for better performance /// typedef tree::LeafManager T; /// OffsetOp op(leafManager); /// tree::NodeManager nodes(leafManager); /// nodes.processBottomUp(op); /// /// @endcode template void processBottomUp(const NodeOp& op, bool threaded = true, size_t grainSize=1) { mChain.processBottomUp(op, threaded, grainSize); op(mRoot); } template void processTopDown(const NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mChain.processTopDown(op, threaded, grainSize); } //@} protected: RootNodeType& mRoot; NodeManagerLink mChain; private: NodeManager(const NodeManager&) {}//disallow copy-construction };// NodeManager class //////////////////////////////////////////// /// Template specialization of the NodeManager with no caching of nodes template class NodeManager { public: typedef typename TreeOrLeafManagerT::RootNodeType RootNodeType; static const Index LEVELS = 0; NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) {} virtual ~NodeManager() {} /// @brief Clear all the cached tree nodes void clear() {} /// @brief Clear and recache all the tree nodes from the /// tree. This is required if tree nodes have been added or removed. void rebuild() {} /// @brief Return a reference to the root node. const RootNodeType& root() const { return mRoot; } /// @brief Return the total number of cached nodes (excluding the root node) Index64 nodeCount() const { return 0; } Index64 nodeCount(Index) const { return 0; } template void processBottomUp(const NodeOp& op, bool, size_t) { op(mRoot); } template void processTopDown(const NodeOp& op, bool, size_t) { op(mRoot); } protected: RootNodeType& mRoot; private: NodeManager(const NodeManager&) {} // disallow copy-construction }; // NodeManager<0> //////////////////////////////////////////// /// Template specialization of the NodeManager with one level of nodes template class NodeManager { public: typedef typename TreeOrLeafManagerT::RootNodeType RootNodeType; BOOST_STATIC_ASSERT(RootNodeType::LEVEL > 0); static const Index LEVELS = 1; NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (TreeOrLeafManagerT::DEPTH == 2 && NodeT0::LEVEL == 0) { tree.getNodes(mList0); } else { mRoot.getNodes(mList0); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } virtual ~NodeManager() {} /// @brief Clear all the cached tree nodes void clear() { mList0.clear(); } /// @brief Clear and recache all the tree nodes from the /// tree. This is required if tree nodes have been added or removed. void rebuild() { mList0.clear(); mRoot.getNodes(mList0); } /// @brief Return a reference to the root node. const RootNodeType& root() const { return mRoot; } /// @brief Return the total number of cached nodes (excluding the root node) Index64 nodeCount() const { return mList0.nodeCount(); } /// @brief Return the number of cached nodes at level @a i, where /// 0 corresponds to the lowest level. Index64 nodeCount(Index i) const { return i==0 ? mList0.nodeCount() : 0; } template void processBottomUp(const NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.foreach(op, threaded, grainSize); op(mRoot); } template void processTopDown(const NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList0.foreach(op, threaded, grainSize); } protected: typedef RootNodeType NodeT1; typedef typename NodeT1::ChildNodeType NodeT0; typedef NodeList ListT0; NodeT1& mRoot; ListT0 mList0; private: NodeManager(const NodeManager&) {} // disallow copy-construction }; // NodeManager<1> //////////////////////////////////////////// /// Template specialization of the NodeManager with two levels of nodes template class NodeManager { public: typedef typename TreeOrLeafManagerT::RootNodeType RootNodeType; BOOST_STATIC_ASSERT(RootNodeType::LEVEL > 1); static const Index LEVELS = 2; NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) { mRoot.getNodes(mList1); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (TreeOrLeafManagerT::DEPTH == 2 && NodeT0::LEVEL == 0) { tree.getNodes(mList0); } else { for (size_t i=0, n=mList1.nodeCount(); iclear(); mRoot.getNodes(mList1); for (size_t i=0, n=mList1.nodeCount(); i void processBottomUp(const NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.foreach(op, threaded, grainSize); mList1.foreach(op, threaded, grainSize); op(mRoot); } template void processTopDown(const NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList1.foreach(op, threaded, grainSize); mList0.foreach(op, threaded, grainSize); } protected: typedef RootNodeType NodeT2; typedef typename NodeT2::ChildNodeType NodeT1;//upper level typedef typename NodeT1::ChildNodeType NodeT0;//lower level typedef NodeList ListT1;//upper level typedef NodeList ListT0;//lower level NodeT2& mRoot; ListT1 mList1; ListT0 mList0; private: NodeManager(const NodeManager&) {} // disallow copy-construction }; // NodeManager<2> //////////////////////////////////////////// /// Template specialization of the NodeManager with three levels of nodes template class NodeManager { public: typedef typename TreeOrLeafManagerT::RootNodeType RootNodeType; BOOST_STATIC_ASSERT(RootNodeType::LEVEL > 2); static const Index LEVELS = 3; NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) { mRoot.getNodes(mList2); for (size_t i=0, n=mList2.nodeCount(); iclear(); mRoot.getNodes(mList2); for (size_t i=0, n=mList2.nodeCount(); i void processBottomUp(const NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.foreach(op, threaded, grainSize); mList1.foreach(op, threaded, grainSize); mList2.foreach(op, threaded, grainSize); op(mRoot); } template void processTopDown(const NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList2.foreach(op, threaded, grainSize); mList1.foreach(op, threaded, grainSize); mList0.foreach(op, threaded, grainSize); } protected: typedef RootNodeType NodeT3; typedef typename NodeT3::ChildNodeType NodeT2;//upper level typedef typename NodeT2::ChildNodeType NodeT1;//mid level typedef typename NodeT1::ChildNodeType NodeT0;//lower level typedef NodeList ListT2;//upper level of internal nodes typedef NodeList ListT1;//lower level of internal nodes typedef NodeList ListT0;//lower level of internal nodes or leafs NodeT3& mRoot; ListT2 mList2; ListT1 mList1; ListT0 mList0; private: NodeManager(const NodeManager&) {} // disallow copy-construction }; // NodeManager<3> //////////////////////////////////////////// /// Template specialization of the NodeManager with four levels of nodes template class NodeManager { public: typedef typename TreeOrLeafManagerT::RootNodeType RootNodeType; BOOST_STATIC_ASSERT(RootNodeType::LEVEL > 3); static const Index LEVELS = 4; NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) { mRoot.getNodes(mList3); for (size_t i=0, n=mList3.nodeCount(); iclear(); mRoot.getNodes(mList3); for (size_t i=0, n=mList3.nodeCount(); i void processBottomUp(const NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.foreach(op, threaded, grainSize); mList1.foreach(op, threaded, grainSize); mList2.foreach(op, threaded, grainSize); mList3.foreach(op, threaded, grainSize); op(mRoot); } template void processTopDown(const NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList3.foreach(op, threaded, grainSize); mList2.foreach(op, threaded, grainSize); mList1.foreach(op, threaded, grainSize); mList0.foreach(op, threaded, grainSize); } protected: typedef RootNodeType NodeT4; typedef typename NodeT4::ChildNodeType NodeT3;//upper level typedef typename NodeT3::ChildNodeType NodeT2;//upper mid level typedef typename NodeT2::ChildNodeType NodeT1;//lower mid level typedef typename NodeT1::ChildNodeType NodeT0;//lower level typedef NodeList ListT3;//upper level of internal nodes typedef NodeList ListT2;//upper mid level of internal nodes typedef NodeList ListT1;//lower mid level of internal nodes typedef NodeList ListT0;//lower level of internal nodes or leafs NodeT4& mRoot; ListT3 mList3; ListT2 mList2; ListT1 mList1; ListT0 mList0; private: NodeManager(const NodeManager&) {} // disallow copy-construction }; // NodeManager<4> } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_NODEMANAGER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/ValueAccessor.h0000644000000000000000000031221412603226506015065 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file ValueAccessor.h /// /// When traversing a grid in a spatially coherent pattern (e.g., iterating /// over neighboring voxels), request a @c ValueAccessor from the grid /// (with Grid::getAccessor()) and use the accessor's @c getValue() and /// @c setValue() methods. These will typically be significantly faster /// than accessing voxels directly in the grid's tree. /// /// @par Example: /// /// @code /// FloatGrid grid; /// FloatGrid::Accessor acc = grid.getAccessor(); /// // First access is slow: /// acc.setValue(Coord(0, 0, 0), 100); /// // Subsequent nearby accesses are fast, since the accessor now holds pointers /// // to nodes that contain (0, 0, 0) along the path from the root of the grid's /// // tree to the leaf: /// acc.setValue(Coord(0, 0, 1), 100); /// acc.getValue(Coord(0, 2, 0), 100); /// // Slow, because the accessor must be repopulated: /// acc.getValue(Coord(-1, -1, -1)); /// // Fast: /// acc.getValue(Coord(-1, -1, -2)); /// acc.setValue(Coord(-1, -2, 0), -100); /// @endcode #ifndef OPENVDB_TREE_VALUEACCESSOR_HAS_BEEN_INCLUDED #define OPENVDB_TREE_VALUEACCESSOR_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { // Forward declarations of local classes that are not intended for general use // The IsSafe template parameter is explained in the warning below. template class ValueAccessor0; template class ValueAccessor1; template class ValueAccessor2; template class ValueAccessor3; template class CacheItem; /// @brief This base class for ValueAccessors manages registration of an accessor /// with a tree so that the tree can automatically clear the accessor whenever /// one of its nodes is deleted. /// /// @internal A base class is needed because ValueAccessor is templated on both /// a Tree type and a mutex type. The various instantiations of the template /// are distinct, unrelated types, so they can't easily be stored in a container /// such as the Tree's CacheRegistry. This base class, in contrast, is templated /// only on the Tree type, so for any given Tree, only two distinct instantiations /// are possible, ValueAccessorBase and ValueAccessorBase. /// /// @warning If IsSafe = false then the ValueAccessor will not register itself /// with the tree from which it is constructed. While in some rare cases this can /// lead to better performance (since it avoids the small overhead of insertion /// on creation and deletion on destruction) it is also unsafe if the tree is /// modified. So unless you're an expert it is highly recommended to set /// IsSafe = true, which is the default in all derived ValueAccessors defined /// below. However if you know that the tree is no being modifed for the lifespan /// of the ValueAccessor AND the work performed per ValueAccessor is small relative /// to overhead of registering it you should consider setting IsSafe = false. If /// this turns out to improve performance you should really rewrite your code so as /// to better amortize the construction of the ValueAccessor, i.e. reuse it as much /// as possible! template class ValueAccessorBase { public: static const bool IsConstTree = boost::is_const::value; /// @brief Return true if this accessor is safe, i.e. registered /// by the tree from which it is constructed. Un-registered /// accessors can in rare cases be faster because it avoids the /// (small) overhead of registration, but they are unsafe if the /// tree is modified. So unless you're an expert it is highly /// recommended to set IsSafe = true (which is the default). static bool isSafe() { return IsSafe; } ValueAccessorBase(TreeType& tree): mTree(&tree) { if (IsSafe) tree.attachAccessor(*this); } virtual ~ValueAccessorBase() { if (IsSafe && mTree) mTree->releaseAccessor(*this); } /// @brief Return a pointer to the tree associated with this accessor. /// @details The pointer will be null only if the tree from which this accessor /// was constructed was subsequently deleted (which generally leaves the /// accessor in an unsafe state). TreeType* getTree() const { return mTree; } /// Return a reference to the tree associated with this accessor. TreeType& tree() const { assert(mTree); return *mTree; } ValueAccessorBase(const ValueAccessorBase& other): mTree(other.mTree) { if (IsSafe && mTree) mTree->attachAccessor(*this); } ValueAccessorBase& operator=(const ValueAccessorBase& other) { if (&other != this) { if (IsSafe && mTree) mTree->releaseAccessor(*this); mTree = other.mTree; if (IsSafe && mTree) mTree->attachAccessor(*this); } return *this; } virtual void clear() = 0; protected: // Allow trees to deregister themselves. template friend class Tree; virtual void release() { mTree = 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 IsSafe if IsSafe = false then the ValueAccessor will /// not register itself with the tree from which /// it is consturcted (see warning). /// @param CacheLevels the number of nodes to be cached, starting from the leaf level /// and not including the root (i.e., CacheLevels < DEPTH), /// and defaulting to all non-root nodes /// @param MutexType the type of mutex to use (see note) /// /// @warning If IsSafe = false then the ValueAccessor will not register itself /// with the tree from which it is constructed. While in some rare cases this can /// lead to better performance (since it avoids the small overhead of insertion /// on creation and deletion on destruction) it is also unsafe if the tree is /// modified. So unless you're an expert it is highly recommended to set /// IsSafe = true, which is the default. However if you know that the tree is no /// being modifed for the lifespan of the ValueAccessor AND the work performed /// per ValueAccessor is small relative to overhead of registering it you should /// consider setting IsSafe = false. If this improves performance you should /// really rewrite your code so as to better amortize the construction of the /// ValueAccessor, i.e. reuse it as much as possible! /// /// @note If @c MutexType is a TBB-compatible mutex, then multiple threads may /// safely access a single, shared accessor. However, it is highly recommended /// that, instead, each thread be assigned its own, non-mutex-protected accessor. template class ValueAccessor: public ValueAccessorBase<_TreeType, IsSafe> { public: BOOST_STATIC_ASSERT(CacheLevels < _TreeType::DEPTH); typedef _TreeType TreeType; typedef typename TreeType::RootNodeType RootNodeT; typedef typename TreeType::LeafNodeType LeafNodeT; typedef typename RootNodeT::ValueType ValueType; typedef ValueAccessorBase BaseT; typedef typename MutexType::scoped_lock LockT; using BaseT::IsConstTree; ValueAccessor(TreeType& tree): BaseT(tree), mCache(*this) { mCache.insert(Coord(), &tree.root()); } ValueAccessor(const ValueAccessor& other): BaseT(other), mCache(*this, other.mCache) {} ValueAccessor& operator=(const ValueAccessor& other) { if (&other != this) { this->BaseT::operator=(other); mCache.copy(*this, other.mCache); } return *this; } virtual ~ValueAccessor() {} /// Return the number of cache levels employed by this accessor. static Index numCacheLevels() { return CacheLevels; } /// Return @c true if nodes along the path to the given voxel have been cached. bool isCached(const Coord& xyz) const { LockT lock(mMutex); return mCache.isCached(xyz); } /// Return the value of the voxel at the given coordinates. const ValueType& getValue(const Coord& xyz) const { LockT lock(mMutex); return mCache.getValue(xyz); } /// Return the active state of the voxel at the given coordinates. bool isValueOn(const Coord& xyz) const { LockT lock(mMutex); return mCache.isValueOn(xyz); } /// Return the active state of the voxel as well as its value bool probeValue(const Coord& xyz, ValueType& value) const { LockT lock(mMutex); return mCache.probeValue(xyz,value); } /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is /// implicitly a background voxel). int getValueDepth(const Coord& xyz) const { LockT lock(mMutex); return mCache.getValueDepth(xyz); } /// Return @c true if the value of voxel (x, y, z) resides at the leaf level /// of the tree, i.e., if it is not a tile value. bool isVoxel(const Coord& xyz) const { LockT lock(mMutex); return mCache.isVoxel(xyz); } //@{ /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, const ValueType& value) { LockT lock(mMutex); mCache.setValue(xyz, value); } void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } //@} /// Set the value of the voxel at the given coordinate but don't change its active state. void setValueOnly(const Coord& xyz, const ValueType& value) { LockT lock(mMutex); mCache.setValueOnly(xyz, value); } /// Set the value of the voxel at the given coordinates and mark the voxel /// as active. [Experimental] void newSetValue(const Coord& xyz, const ValueType& value) { LockT lock(mMutex); mCache.newSetValue(xyz, value); } /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value) { LockT lock(mMutex); mCache.setValueOff(xyz, value); } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @details See Tree::modifyValue() for details. template void modifyValue(const Coord& xyz, const ModifyOp& op) { LockT lock(mMutex); mCache.modifyValue(xyz, op); } /// @brief Apply a functor to the voxel at the given coordinates. /// @details See Tree::modifyValueAndActiveState() for details. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { LockT lock(mMutex); mCache.modifyValueAndActiveState(xyz, op); } /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on = true) { LockT lock(mMutex); mCache.setActiveState(xyz, on); } /// Mark the voxel at the given coordinates as active but don't change its value. void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } /// Mark the voxel at the given coordinates as inactive but don't change its value. void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } /// Return the cached node of type @a NodeType. [Mainly for internal use] template NodeType* getNode() { LockT lock(mMutex); NodeType* node = 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 class ValueAccessor : public ValueAccessor0 { public: ValueAccessor(TreeType& tree): ValueAccessor0(tree) {} ValueAccessor(const ValueAccessor& other): ValueAccessor0(other) {} virtual ~ValueAccessor() {} }; /// Template specialization of the ValueAccessor with no mutex and one cache level template class ValueAccessor : public ValueAccessor1 { public: ValueAccessor(TreeType& tree): ValueAccessor1(tree) {} ValueAccessor(const ValueAccessor& other): ValueAccessor1(other) {} virtual ~ValueAccessor() {} }; /// Template specialization of the ValueAccessor with no mutex and two cache levels template class ValueAccessor : public ValueAccessor2 { public: ValueAccessor(TreeType& tree): ValueAccessor2(tree) {} ValueAccessor(const ValueAccessor& other): ValueAccessor2(other) {} virtual ~ValueAccessor() {} }; /// Template specialization of the ValueAccessor with no mutex and three cache levels template class ValueAccessor : public ValueAccessor3 { public: ValueAccessor(TreeType& tree): ValueAccessor3(tree) {} ValueAccessor(const ValueAccessor& 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 class ValueAccessorRW: public ValueAccessor { public: ValueAccessorRW(TreeType& tree) : ValueAccessor(tree) { } }; //////////////////////////////////////// // // The classes below are for internal use and should rarely be used directly. // // An element of a compile-time linked list of node pointers, ordered from LeafNode to RootNode template class CacheItem { public: typedef typename boost::mpl::front::type NodeType; typedef typename NodeType::ValueType ValueType; typedef typename NodeType::LeafNodeType LeafNodeType; typedef std::numeric_limits CoordLimits; CacheItem(TreeCacheT& parent): mParent(&parent), mHash(CoordLimits::max()), mNode(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, IsSafe> { public: typedef _TreeType TreeType; typedef typename TreeType::ValueType ValueType; typedef typename TreeType::RootNodeType RootNodeT; typedef typename TreeType::LeafNodeType LeafNodeT; typedef ValueAccessorBase BaseT; ValueAccessor0(TreeType& tree): BaseT(tree) {} ValueAccessor0(const ValueAccessor0& other): BaseT(other) {} /// Return the number of cache levels employed by this accessor. static Index numCacheLevels() { return 0; } ValueAccessor0& operator=(const ValueAccessor0& other) { if (&other != this) this->BaseT::operator=(other); return *this; } virtual ~ValueAccessor0() {} /// Return @c true if nodes along the path to the given voxel have been cached. bool isCached(const Coord&) const { return false; } /// Return the value of the voxel at the given coordinates. const ValueType& getValue(const Coord& xyz) const { assert(BaseT::mTree); return BaseT::mTree->getValue(xyz); } /// Return the active state of the voxel at the given coordinates. bool isValueOn(const Coord& xyz) const { assert(BaseT::mTree); return BaseT::mTree->isValueOn(xyz); } /// Return the active state and, in @a value, the value of the voxel at the given coordinates. bool probeValue(const Coord& xyz, ValueType& value) const { assert(BaseT::mTree); return BaseT::mTree->probeValue(xyz, value); } /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is /// implicitly a background voxel). int getValueDepth(const Coord& xyz) const { assert(BaseT::mTree); return BaseT::mTree->getValueDepth(xyz); } /// Return @c true if the value of voxel (x, y, z) resides at the leaf level /// of the tree, i.e., if it is not a tile value. bool isVoxel(const Coord& xyz) const { assert(BaseT::mTree); return BaseT::mTree->getValueDepth(xyz) == static_cast(RootNodeT::LEVEL); } //@{ /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->setValue(xyz, value); } void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } //@} /// Set the value of the voxel at the given coordinate but don't change its active state. void setValueOnly(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->setValueOnly(xyz, value); } /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->root().setValueOff(xyz, value); } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @details See Tree::modifyValue() for details. template void modifyValue(const Coord& xyz, const ModifyOp& op) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->modifyValue(xyz, op); } /// @brief Apply a functor to the voxel at the given coordinates. /// @details See Tree::modifyValueAndActiveState() for details. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->modifyValueAndActiveState(xyz, op); } /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on = true) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->setActiveState(xyz, on); } /// Mark the voxel at the given coordinates as active but don't change its value. void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } /// Mark the voxel at the given coordinates as inactive but don't change its value. void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } /// Return the cached node of type @a NodeType. [Mainly for internal use] template NodeT* getNode() { return 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, IsSafe> { public: BOOST_STATIC_ASSERT(_TreeType::DEPTH >= 2); BOOST_STATIC_ASSERT( L0 < _TreeType::RootNodeType::LEVEL ); typedef _TreeType TreeType; typedef typename TreeType::ValueType ValueType; typedef typename TreeType::RootNodeType RootNodeT; typedef typename TreeType::LeafNodeType LeafNodeT; typedef ValueAccessorBase BaseT; typedef typename RootNodeT::NodeChainType InvTreeT; typedef typename boost::mpl::at >::type NodeT0; /// Constructor from a tree ValueAccessor1(TreeType& tree) : BaseT(tree), mKey0(Coord::max()), mNode0(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, IsSafe> { public: BOOST_STATIC_ASSERT(_TreeType::DEPTH >= 3); BOOST_STATIC_ASSERT( L0 < L1 && L1 < _TreeType::RootNodeType::LEVEL ); typedef _TreeType TreeType; typedef typename TreeType::ValueType ValueType; typedef typename TreeType::RootNodeType RootNodeT; typedef typename TreeType::LeafNodeType LeafNodeT; typedef ValueAccessorBase BaseT; typedef typename RootNodeT::NodeChainType InvTreeT; typedef typename boost::mpl::at >::type NodeT0; typedef typename boost::mpl::at >::type NodeT1; /// Constructor from a tree ValueAccessor2(TreeType& tree) : BaseT(tree), mKey0(Coord::max()), mNode0(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, IsSafe> { public: BOOST_STATIC_ASSERT(_TreeType::DEPTH >= 4); BOOST_STATIC_ASSERT(L0 < L1 && L1 < L2 && L2 < _TreeType::RootNodeType::LEVEL); typedef _TreeType TreeType; typedef typename TreeType::ValueType ValueType; typedef typename TreeType::RootNodeType RootNodeT; typedef typename TreeType::LeafNodeType LeafNodeT; typedef ValueAccessorBase BaseT; typedef typename RootNodeT::NodeChainType InvTreeT; typedef typename boost::mpl::at >::type NodeT0; typedef typename boost::mpl::at >::type NodeT1; typedef typename boost::mpl::at >::type NodeT2; /// Constructor from a tree ValueAccessor3(TreeType& tree) : BaseT(tree), mKey0(Coord::max()), mNode0(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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/LeafManager.h0000644000000000000000000006445512603226506014503 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file LeafManager.h /// /// @brief A LeafManager manages a linear array of pointers to a given tree's /// leaf nodes, as well as optional auxiliary buffers (one or more per leaf) /// that can be swapped with the leaf nodes' voxel data buffers. /// @details The leaf array is useful for multithreaded computations over /// leaf voxels in a tree with static topology but varying voxel values. /// The auxiliary buffers are convenient for temporal integration. /// Efficient methods are provided for multithreaded swapping and synching /// (i.e., copying the contents) of these buffers. #ifndef OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED #define OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED #include #include #include #include #include #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 TreeT::ValueType ValueType; typedef typename TreeT::RootNodeType RootNodeType; typedef typename TreeType::LeafNodeType NonConstLeafType; typedef typename CopyConstness::Type LeafType; typedef LeafType LeafNodeType; 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 Index DEPTH = 2;//root + leafs 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 a const reference to tree associated with this manager. const TreeType& tree() const { return *mTree; } /// Return a reference to the tree associated with this manager. TreeType& tree() { return *mTree; } /// Return a const reference to root node associated with this manager. const RootNodeType& root() const { return mTree->root(); } /// Return a reference to the root node associated with this manager. RootNodeType& root() { return mTree->root(); } /// Return @c true if the tree associated with this manager is immutable. bool isConstTree() const { return this->IsConstTree; } /// @brief Return a pointer to the leaf node at index @a leafIdx in the array. /// @note For performance reasons no range check is performed (other than an assertion)! LeafType& leaf(size_t leafIdx) const { assert(leafIdx 0), /// but it is not safe to modify the leaf buffer (@a bufferIdx = 0). BufferType& getBuffer(size_t leafIdx, size_t bufferIdx) const { assert(leafIdx < mLeafCount); assert(bufferIdx == 0 || bufferIdx - 1 < mAuxBuffersPerLeaf); return bufferIdx == 0 ? mLeafs[leafIdx]->buffer() : mAuxBuffers[leafIdx * mAuxBuffersPerLeaf + bufferIdx - 1]; } /// @brief Return a @c tbb::blocked_range of leaf array indices. /// /// @note Consider using leafRange() instead, which provides access methods /// to leaf nodes and buffers. RangeType getRange(size_t grainsize = 1) const { return RangeType(0, mLeafCount, grainsize); } /// Return a TBB-compatible LeafRange. LeafRange leafRange(size_t grainsize = 1) const { return LeafRange(0, mLeafCount, *this, grainsize); } /// @brief Swap each leaf node's buffer with the nth corresponding auxiliary buffer, /// where n = @a bufferIdx. /// @return @c true if the swap was successful /// @param bufferIdx index of the buffer that will be swapped with /// the corresponding leaf node buffer /// @param serial if false, swap buffers in parallel using multiple threads. /// @note Recall that the indexing of auxiliary buffers is 1-based, since /// buffer index 0 denotes the leaf node buffer. So buffer index 1 denotes /// the first auxiliary buffer. bool swapLeafBuffer(size_t bufferIdx, bool serial = false) { 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); } template void getNodes(ArrayT& array) { typedef typename ArrayT::value_type T; BOOST_STATIC_ASSERT(boost::is_pointer::value); typedef typename boost::mpl::if_::type>, const LeafType, LeafType>::type LeafT; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (boost::is_same::value) { array.resize(mLeafCount); for (size_t i=0; i(mLeafs[i]); } else { mTree->getNodes(array); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template void getNodes(ArrayT& array) const { typedef typename ArrayT::value_type T; BOOST_STATIC_ASSERT(boost::is_pointer::value); BOOST_STATIC_ASSERT(boost::is_const::type>::value); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (boost::is_same::value) { array.resize(mLeafCount); for (size_t i=0; i(mLeafs[i]); } else { mTree->getNodes(array); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////////////////////////////////////////////////// // All methods below are for internal use only and should never be called directly /// Used internally by tbb::parallel_for() - never call it directly! void operator()(const RangeType& r) const { if (mTask) mTask(const_cast(this), r); else OPENVDB_THROW(ValueError, "task is undefined"); } private: // This a simple wrapper for a c-style array so it mimics the api // of a std container, e.g. std::vector or std::deque, and can be // passed to Tree::getNodes(). struct MyArray { typedef LeafType* value_type;//required by Tree::getNodes value_type* ptr; MyArray(value_type* array) : ptr(array) {} void push_back(value_type leaf) { *ptr++ = leaf; }//required by Tree::getNodes }; void initLeafArray() { const size_t leafCount = mTree->leafCount(); if (leafCount != mLeafCount) { delete [] mLeafs; mLeafs = (leafCount == 0) ? NULL : new LeafType*[leafCount]; mLeafCount = leafCount; } MyArray a(mLeafs); mTree->getNodes(a); } void initAuxBuffers(bool serial) { const size_t auxBufferCount = mLeafCount * mAuxBuffersPerLeaf; if (auxBufferCount != mAuxBufferCount) { delete [] mAuxBuffers; mAuxBuffers = (auxBufferCount == 0) ? 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/Tree.h0000644000000000000000000025507012603226506013233 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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" 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 than calling evalActiveVoxelBoundingBox, /// which visits the individual active voxels, and hence /// evalLeafBoundingBox produces a less tight, i.e. approximate, bbox. /// @return @c false if the bounding box is empty (in which case /// the bbox is set to its default value). virtual bool evalLeafBoundingBox(CoordBBox& bbox) const = 0; /// @brief Return in @a dim the dimensions of the axis-aligned bounding box /// of all leaf nodes. /// @return @c false if the bounding box is empty. virtual bool evalLeafDim(Coord& dim) const = 0; /// @brief Return in @a bbox the axis-aligned bounding box of all /// active voxels and tiles. /// @details This method produces a more accurate, i.e. tighter, /// bounding box than evalLeafBoundingBox which is approximate but /// faster. /// @return @c false if the bounding box is empty (in which case /// the bbox is set to its default value). virtual bool evalActiveVoxelBoundingBox(CoordBBox& bbox) const = 0; /// @brief Return in @a dim the dimensions of the axis-aligned bounding box of all /// active voxels. This is a tighter bounding box than the leaf node bounding box. /// @return @c false if the bounding box is empty. virtual bool evalActiveVoxelDim(Coord& dim) const = 0; virtual void getIndexRange(CoordBBox& bbox) const = 0; #ifndef OPENVDB_2_ABI_COMPATIBLE /// @brief Replace with background tiles any nodes whose voxel buffers /// have not yet been allocated. /// @details Typically, unallocated nodes are leaf nodes whose voxel buffers /// are not yet resident in memory because delayed loading is in effect. /// @sa readNonresidentBuffers, io::File::open virtual void clipUnallocatedNodes() = 0; #endif // // 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; #ifndef OPENVDB_2_ABI_COMPATIBLE /// Return the total number of active tiles. virtual Index64 activeTileCount() const = 0; #endif /// Return the total amount of memory in bytes occupied by this tree. virtual Index64 memUsage() const { return 0; } // // I/O methods // /// @brief Read the tree topology from a stream. /// /// This will read the tree structure and tile values, but not voxel data. virtual void readTopology(std::istream&, bool saveFloatAsHalf = false); /// @brief Write the tree topology to a stream. /// /// This will write the tree structure and tile values, but not voxel data. virtual void writeTopology(std::ostream&, bool saveFloatAsHalf = false) const; /// Read all data buffers for this tree. virtual void readBuffers(std::istream&, bool saveFloatAsHalf = false) = 0; #ifndef OPENVDB_2_ABI_COMPATIBLE /// Read all of this tree's data buffers that intersect the given bounding box. virtual void readBuffers(std::istream&, const CoordBBox&, bool saveFloatAsHalf = false) = 0; /// @brief Read all of this tree's data buffers that are not yet resident in memory /// (because delayed loading is in effect). /// @details If this tree was read from a memory-mapped file, this operation /// disconnects the tree from the file. /// @sa clipUnallocatedNodes, io::File::open, io::MappedFile virtual void readNonresidentBuffers() const = 0; #endif /// Write out all the data buffers for this tree. virtual void writeBuffers(std::ostream&, bool saveFloatAsHalf = false) const = 0; /// @brief Print statistics, memory usage and other information about this tree. /// @param os a stream to which to write textual information /// @param verboseLevel 1: print tree configuration only; /// 2: include node and voxel statistics; /// 3: include memory usage; /// 4: include minimum and maximum voxel values /// @warning @a verboseLevel 4 forces loading of any unallocated nodes. virtual void print(std::ostream& os = std::cout, int verboseLevel = 1) const; 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; } //@} // // 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); #ifndef OPENVDB_2_ABI_COMPATIBLE /// Read all of this tree's data buffers that intersect the given bounding box. virtual void readBuffers(std::istream&, const CoordBBox&, bool saveFloatAsHalf = false); /// @brief Read all of this tree's data buffers that are not yet resident in memory /// (because delayed loading is in effect). /// @details If this tree was read from a memory-mapped file, this operation /// disconnects the tree from the file. /// @sa clipUnallocatedNodes, io::File::open, io::MappedFile virtual void readNonresidentBuffers() const; #endif /// 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. 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(); } /// Set all voxels that lie outside the given axis-aligned box to the background. void clip(const CoordBBox&); #ifndef OPENVDB_2_ABI_COMPATIBLE /// @brief Replace with background tiles any nodes whose voxel buffers /// have not yet been allocated. /// @details Typically, unallocated nodes are leaf nodes whose voxel buffers /// are not yet resident in memory because delayed loading is in effect. /// @sa readNonresidentBuffers, io::File::open virtual void clipUnallocatedNodes(); #endif /// @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 Reduce the memory footprint of this tree by replacing with tiles /// any nodes whose values are all the same (optionally to within a tolerance) /// and have the same active state. /// @warning Will soon be deprecated! void prune(const ValueType& tolerance = zeroVal()) { this->clearAllAccessors(); mRoot.prune(tolerance); } /// @brief Add the given leaf node to this tree, creating a new branch if necessary. /// If a leaf node with the same origin already exists, replace it. 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); } //@} //@{ /// @brief Adds all nodes of a certain type to a container with the following API: /// @code /// struct ArrayT { /// typedef value_type;// defines the type of nodes to be added to the array /// void push_back(value_type nodePtr);// method that add nodes to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// typedef LeafType* value_type; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.getNodes(array); /// @endcode template void getNodes(ArrayT& array) { mRoot.getNodes(array); } template void getNodes(ArrayT& array) const { mRoot.getNodes(array); } //@} /// @brief Steals all nodes of a certain type from the tree and /// adds them to a container with the following API: /// @code /// struct ArrayT { /// typedef value_type;// defines the type of nodes to be added to the array /// void push_back(value_type nodePtr);// method that add nodes to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// typedef LeafType* value_type; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.stealNodes(array); /// @endcode template void stealNodes(ArrayT& array) { mRoot.stealNodes(array); } template void stealNodes(ArrayT& array, const ValueType& value, bool state) { mRoot.stealNodes(array, value, state); } // // Aux methods // /// @brief Return @c true if this tree contains no nodes other than /// the root node and no tiles other than background tiles. bool empty() const { return mRoot.empty(); } /// Remove all tiles from this tree and all nodes other than the root node. void clear() { 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; //@} //@{ /// Dummy implementations void attachAccessor(ValueAccessorBase&) const {} void attachAccessor(ValueAccessorBase&) const {} //@} //@{ /// Deregister an accessor so that it is no longer automatically cleared. void releaseAccessor(ValueAccessorBase&) const; void releaseAccessor(ValueAccessorBase&) const; //@} //@{ /// Dummy implementations void releaseAccessor(ValueAccessorBase&) const {} void releaseAccessor(ValueAccessorBase&) const {} //@} /// @brief Return this tree's background value wrapped as metadata. /// @note Query the metadata object for the value's type. virtual Metadata::Ptr getBackgroundValue() const; /// @brief Return this tree's background value. /// /// @note Use tools::changeBackground to efficiently modify the /// background values. Else use tree.root().setBackground, which /// is serial and hence slower. const ValueType& background() const { return mRoot.background(); } /// Min and max are both inclusive. virtual void getIndexRange(CoordBBox& bbox) const { mRoot.getIndexRange(bbox); } /// 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 tools::pruneInactive. template void topologyIntersection(const Tree& other); /// @brief Difference this tree's set of active values with the active values /// of the other tree, whose @c ValueType may be different. So a /// resulting voxel will be active only if the original voxel is /// active in this tree and inactive in the other tree. /// /// @note This operation can delete branches in this grid if they /// overlap with active tiles in the other grid. Likewise active /// voxels can be turned into inactive voxels resulting in leaf /// nodes with no active values. Thus, it is recommended to /// subsequently call tools::pruneInactive. template void topologyDifference(const Tree& other); /// For a given function @c f, use sparse traversal to compute f(this, other) /// over all corresponding pairs of values (tile or voxel) of this tree and the other tree /// and store the result in this tree. /// This method is typically more space-efficient than the two-tree combine2(), /// since it moves rather than copies nodes from the other tree into this tree. /// @note This operation always empties the other tree. /// @param other a tree of the same type as this tree /// @param op a functor of the form void op(const T& a, const T& b, T& result), /// where @c T is this tree's @c ValueType, that computes /// result = f(a, b) /// @param prune if true, prune the resulting tree one branch at a time (this is usually /// more space-efficient than pruning the entire tree in one pass) /// /// @par Example: /// Compute the per-voxel difference between two floating-point trees, /// @c aTree and @c bTree, and store the result in @c aTree (leaving @c bTree empty). /// @code /// { /// struct Local { /// static inline void diff(const float& a, const float& b, float& result) { /// result = a - b; /// } /// }; /// aTree.combine(bTree, Local::diff); /// } /// @endcode /// /// @par Example: /// Compute f * a + (1 - f) * b over all voxels of two floating-point trees, /// @c aTree and @c bTree, and store the result in @c aTree (leaving @c bTree empty). /// @code /// namespace { /// struct Blend { /// Blend(float f): frac(f) {} /// inline void operator()(const float& a, const float& b, float& result) const { /// result = frac * a + (1.0 - frac) * b; /// } /// float frac; /// }; /// } /// { /// aTree.combine(bTree, Blend(0.25)); // 0.25 * a + 0.75 * b /// } /// @endcode template void combine(Tree& other, CombineOp& op, bool prune = false); #ifndef _MSC_VER template void combine(Tree& other, const CombineOp& op, bool prune = false); #endif /// Like combine(), but with /// @param other a tree of the same type as this tree /// @param op a functor of the form void op(CombineArgs& args) that /// computes args.setResult(f(args.a(), args.b())) and, optionally, /// args.setResultIsActive(g(args.aIsActive(), args.bIsActive())) /// for some functions @c f and @c g /// @param prune if true, prune the resulting tree one branch at a time (this is usually /// more space-efficient than pruning the entire tree in one pass) /// /// This variant passes not only the @em a and @em b values but also the active states /// of the @em a and @em b values to the functor, which may then return, by calling /// @c args.setResultIsActive(), a computed active state for the result value. /// By default, the result is active if either the @em a or the @em b value is active. /// /// @see openvdb/Types.h for the definition of the CombineArgs struct. /// /// @par Example: /// Replace voxel values in floating-point @c aTree with corresponding values /// from floating-point @c bTree (leaving @c bTree empty) wherever the @c bTree /// values are larger. Also, preserve the active states of any transferred values. /// @code /// { /// struct Local { /// static inline void max(CombineArgs& args) { /// if (args.b() > args.a()) { /// // Transfer the B value and its active state. /// args.setResult(args.b()); /// args.setResultIsActive(args.bIsActive()); /// } else { /// // Preserve the A value and its active state. /// args.setResult(args.a()); /// args.setResultIsActive(args.aIsActive()); /// } /// } /// }; /// aTree.combineExtended(bTree, Local::max); /// } /// @endcode template void combineExtended(Tree& other, ExtendedCombineOp& op, bool prune = false); #ifndef _MSC_VER template void combineExtended(Tree& other, const ExtendedCombineOp& op, bool prune = false); #endif /// For a given function @c f, use sparse traversal to compute f(a, b) over all /// corresponding pairs of values (tile or voxel) of trees A and B and store the result /// in this tree. /// @param a,b two trees with the same configuration (levels and node dimensions) /// as this tree but with the B tree possibly having a different value type /// @param op a functor of the form void op(const T1& a, const T2& b, T1& result), /// where @c T1 is this tree's and the A tree's @c ValueType and @c T2 is the /// B tree's @c ValueType, that computes result = f(a, b) /// @param prune if true, prune the resulting tree one branch at a time (this is usually /// more space-efficient than pruning the entire tree in one pass) /// /// @throw TypeError if the B tree's configuration doesn't match this tree's /// or if this tree's ValueType is not constructible from the B tree's ValueType. /// /// @par Example: /// Compute the per-voxel difference between two floating-point trees, /// @c aTree and @c bTree, and store the result in a third tree. /// @code /// { /// struct Local { /// static inline void diff(const float& a, const float& b, float& result) { /// result = a - b; /// } /// }; /// FloatTree resultTree; /// resultTree.combine2(aTree, bTree, Local::diff); /// } /// @endcode template void combine2(const Tree& a, const OtherTreeType& b, CombineOp& op, bool prune = false); #ifndef _MSC_VER template void combine2(const Tree& a, const OtherTreeType& b, const CombineOp& op, bool prune = false); #endif /// Like combine2(), but with /// @param a,b two trees with the same configuration (levels and node dimensions) /// as this tree but with the B tree possibly having a different value type /// @param op a functor of the form void op(CombineArgs& args), where /// @c T1 is this tree's and the A tree's @c ValueType and @c T2 is the B tree's /// @c ValueType, that computes args.setResult(f(args.a(), args.b())) /// and, optionally, /// args.setResultIsActive(g(args.aIsActive(), args.bIsActive())) /// for some functions @c f and @c g /// @param prune if true, prune the resulting tree one branch at a time (this is usually /// more space-efficient than pruning the entire tree in one pass) /// This variant passes not only the @em a and @em b values but also the active states /// of the @em a and @em b values to the functor, which may then return, by calling /// args.setResultIsActive(), a computed active state for the result value. /// By default, the result is active if either the @em a or the @em b value is active. /// /// @throw TypeError if the B tree's configuration doesn't match this tree's /// or if this tree's ValueType is not constructible from the B tree's ValueType. /// /// @see openvdb/Types.h for the definition of the CombineArgs struct. /// /// @par Example: /// Compute the per-voxel maximum values of two single-precision floating-point trees, /// @c aTree and @c bTree, and store the result in a third tree. Set the active state /// of each output value to that of the larger of the two input values. /// @code /// { /// struct Local { /// static inline void max(CombineArgs& args) { /// if (args.b() > args.a()) { /// // Transfer the B value and its active state. /// args.setResult(args.b()); /// args.setResultIsActive(args.bIsActive()); /// } else { /// // Preserve the A value and its active state. /// args.setResult(args.a()); /// args.setResultIsActive(args.aIsActive()); /// } /// } /// }; /// FloatTree aTree = ...; /// FloatTree bTree = ...; /// FloatTree resultTree; /// resultTree.combine2Extended(aTree, bTree, Local::max); /// } /// @endcode /// /// @par Example: /// Compute the per-voxel maximum values of a double-precision and a single-precision /// floating-point tree, @c aTree and @c bTree, and store the result in a third, /// double-precision tree. Set the active state of each output value to that of /// the larger of the two input values. /// @code /// { /// struct Local { /// static inline void max(CombineArgs& args) { /// if (args.b() > args.a()) { /// // Transfer the B value and its active state. /// args.setResult(args.b()); /// args.setResultIsActive(args.bIsActive()); /// } else { /// // Preserve the A value and its active state. /// args.setResult(args.a()); /// args.setResultIsActive(args.aIsActive()); /// } /// } /// }; /// DoubleTree aTree = ...; /// FloatTree bTree = ...; /// DoubleTree resultTree; /// resultTree.combine2Extended(aTree, bTree, Local::max); /// } /// @endcode template void combine2Extended(const Tree& a, const OtherTreeType& b, ExtendedCombineOp& op, bool prune = false); #ifndef _MSC_VER template void combine2Extended(const Tree& a, const OtherTreeType& b, const ExtendedCombineOp&, bool prune = false); #endif /// @brief Use sparse traversal to call the given functor with bounding box /// information for all active tiles and leaf nodes or active voxels in the tree. /// /// @note The bounding boxes are guaranteed to be non-overlapping. /// @param op a functor with a templated call operator of the form /// template void operator()(const CoordBBox& bbox), /// where bbox is the bounding box of either an active tile /// (if @c LEVEL > 0), a leaf node or an active voxel. /// The functor must also provide a templated method of the form /// template bool descent() that returns @c false /// if bounding boxes below the specified tree level are not to be visited. /// In such cases of early tree termination, a bounding box is instead /// derived from each terminating child node. /// /// @par Example: /// Visit and process all active tiles and leaf nodes in a tree, but don't /// descend to the active voxels. The smallest bounding boxes that will be /// visited are those of leaf nodes or level-1 active tiles. /// @code /// { /// struct ProcessTilesAndLeafNodes { /// // Descend to leaf nodes, but no further. /// template inline bool descent() { return LEVEL > 0; } /// // Use this version to descend to voxels: /// //template inline bool descent() { return true; } /// /// template /// inline void operator()(const CoordBBox &bbox) { /// if (LEVEL > 0) { /// // code to process an active tile /// } else { /// // code to process a leaf node /// } /// } /// }; /// ProcessTilesAndLeafNodes op; /// aTree.visitActiveBBox(op); /// } /// @endcode /// @see openvdb/unittest/TestTree.cc for another example. template void visitActiveBBox(BBoxOp& op) const { mRoot.visitActiveBBox(op); } /// Traverse this tree in depth-first order, and at each node call the given functor /// with a @c DenseIterator (see Iterator.h) that points to either a child node or a /// tile value. If the iterator points to a child node and the functor returns true, /// do not descend to the child node; instead, continue the traversal at the next /// iterator position. /// @param op a functor of the form template bool op(IterT&), /// where @c IterT is either a RootNode::ChildAllIter, /// an InternalNode::ChildAllIter or a LeafNode::ChildAllIter /// /// @note There is no iterator that points to a RootNode, so to visit the root node, /// retrieve the @c parent() of a RootNode::ChildAllIter. /// /// @par Example: /// Print information about the nodes and tiles of a tree, but not individual voxels. /// @code /// namespace { /// template /// struct PrintTreeVisitor /// { /// 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; static tbb::atomic sTreeTypeName; }; // end of Tree class template tbb::atomic Tree<_RootNodeType>::sTreeTypeName; /// @brief Tree3::Type is the type of a three-level tree /// (Root, Internal, Leaf) with value type T and /// internal and leaf node log dimensions N1 and N2, respectively. /// @note This is NOT the standard tree configuration (Tree4 is). template struct Tree3 { 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); } #ifndef OPENVDB_2_ABI_COMPATIBLE template inline void Tree::readBuffers(std::istream &is, const CoordBBox& bbox, bool saveFloatAsHalf) { this->clearAllAccessors(); mRoot.readBuffers(is, bbox, saveFloatAsHalf); } template inline void Tree::readNonresidentBuffers() const { for (LeafCIter it = this->cbeginLeaf(); it; ++it) { // Retrieving the value of a leaf voxel forces loading of the leaf node's voxel buffer. it->getValue(Index(0)); } } #endif // !OPENVDB_2_ABI_COMPATIBLE 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 inline void Tree::addTile(Index level, const Coord& xyz, const ValueType& value, bool active) { mRoot.addTile(level, xyz, value, active); } template template inline NodeT* Tree::stealNode(const Coord& xyz, const ValueType& value, bool active) { this->clearAllAccessors(); return mRoot.template stealNode(xyz, value, active); } template inline typename RootNodeType::LeafNodeType* Tree::touchLeaf(const Coord& xyz) { return mRoot.touchLeaf(xyz); } template inline typename RootNodeType::LeafNodeType* Tree::probeLeaf(const Coord& xyz) { return mRoot.probeLeaf(xyz); } template inline const typename RootNodeType::LeafNodeType* Tree::probeConstLeaf(const Coord& xyz) const { return mRoot.probeConstLeaf(xyz); } template template inline NodeType* Tree::probeNode(const Coord& xyz) { return mRoot.template probeNode(xyz); } template template inline const NodeType* Tree::probeNode(const Coord& xyz) const { return this->template probeConstNode(xyz); } template template inline const NodeType* Tree::probeConstNode(const Coord& xyz) const { return mRoot.template probeConstNode(xyz); } //////////////////////////////////////// template inline void Tree::clip(const CoordBBox& bbox) { this->clearAllAccessors(); return mRoot.clip(bbox); } #ifndef OPENVDB_2_ABI_COMPATIBLE template inline void Tree::clipUnallocatedNodes() { this->clearAllAccessors(); for (LeafIter it = this->beginLeaf(); it; ) { const LeafNodeType* leaf = it.getLeaf(); ++it; // advance the iterator before deleting the leaf node if (!leaf->isAllocated()) { this->addTile(/*level=*/0, leaf->origin(), this->background(), /*active=*/false); } } } #endif template inline void Tree::fill(const CoordBBox& bbox, const ValueType& value, bool active) { this->clearAllAccessors(); return mRoot.fill(bbox, value, active); } template Metadata::Ptr Tree::getBackgroundValue() const { Metadata::Ptr result; if (Metadata::isRegisteredType(valueType())) { typedef TypedMetadata MetadataT; result = Metadata::createMetadata(valueType()); if (result->typeName() == MetadataT::staticTypeName()) { MetadataT* m = static_cast(result.get()); m->value() = mRoot.background(); } } return result; } //////////////////////////////////////// template inline void Tree::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() { if (sTreeTypeName == 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 (sTreeTypeName.compare_and_swap(s, NULL) != NULL) delete s; } return *sTreeTypeName; } template template inline bool Tree::hasSameTopology(const Tree& other) const { return mRoot.hasSameTopology(other.root()); } template Index64 Tree::inactiveVoxelCount() const { Coord dim(0, 0, 0); this->evalActiveVoxelDim(dim); const Index64 totalVoxels = dim.x() * dim.y() * dim.z(), activeVoxels = this->activeVoxelCount(); assert(totalVoxels >= activeVoxels); return totalVoxels - activeVoxels; } template inline bool Tree::evalLeafBoundingBox(CoordBBox& bbox) const { bbox.reset(); // default invalid bbox if (this->empty()) return false; // empty mRoot.evalActiveBoundingBox(bbox, false); return true;// not empty } template inline bool Tree::evalActiveVoxelBoundingBox(CoordBBox& bbox) const { bbox.reset(); // default invalid bbox if (this->empty()) return false; // empty mRoot.evalActiveBoundingBox(bbox, true); return true;// not empty } template inline bool Tree::evalActiveVoxelDim(Coord& dim) const { CoordBBox bbox; bool notEmpty = this->evalActiveVoxelBoundingBox(bbox); dim = bbox.extents(); return notEmpty; } template inline bool Tree::evalLeafDim(Coord& dim) const { CoordBBox bbox; bool notEmpty = this->evalLeafBoundingBox(bbox); dim = bbox.extents(); return notEmpty; } template inline void Tree::evalMinMax(ValueType& minVal, ValueType& maxVal) const { minVal = maxVal = zeroVal(); if (ValueOnCIter iter = this->cbeginValueOn()) { minVal = maxVal = *iter; for (++iter; iter; ++iter) { const ValueType& val = *iter; if (val < minVal) minVal = val; if (val > maxVal) maxVal = val; } } } template inline void Tree::getNodeLog2Dims(std::vector& dims) { dims.clear(); RootNodeType::getNodeLog2Dims(dims); } template inline void Tree::print(std::ostream& os, int verboseLevel) const { if (verboseLevel <= 0) return; /// @todo Consider using boost::io::ios_precision_saver instead. struct OnExit { std::ostream& os; std::streamsize savedPrecision; OnExit(std::ostream& _os): os(_os), savedPrecision(os.precision()) {} ~OnExit() { os.precision(savedPrecision); } }; OnExit restorePrecision(os); std::vector dims; Tree::getNodeLog2Dims(dims); os << "Information about Tree:\n" << " Type: " << this->type() << "\n"; os << " Configuration:\n"; if (verboseLevel <= 1) { // Print node types and sizes. os << " Root(" << mRoot.getTableSize() << ")"; if (dims.size() > 1) { for (size_t i = 1, N = dims.size() - 1; i < N; ++i) { os << ", Internal(" << (1 << dims[i]) << "^3)"; } os << ", Leaf(" << (1 << *dims.rbegin()) << "^3)\n"; } os << " Background value: " << mRoot.background() << "\n"; return; } // The following is tree information that is expensive to extract. ValueType minVal = zeroVal(), maxVal = zeroVal(); if (verboseLevel > 3) { // This forces loading of all non-resident nodes. this->evalMinMax(minVal, maxVal); } std::vector nodeCount(dims.size()); #ifndef OPENVDB_2_ABI_COMPATIBLE Index64 unallocatedLeafCount = 0; #endif for (NodeCIter it = cbeginNode(); it; ++it) { ++(nodeCount[it.getDepth()]); #ifndef OPENVDB_2_ABI_COMPATIBLE if (it.getLevel() == 0) { const LeafNodeType* leaf = NULL; it.getNode(leaf); if (leaf && !leaf->isAllocated()) ++unallocatedLeafCount; } #endif } Index64 totalNodeCount = 0; for (size_t i = 0; i < nodeCount.size(); ++i) totalNodeCount += nodeCount[i]; // Print node types, counts and sizes. os << " Root(1 x " << mRoot.getTableSize() << ")"; if (dims.size() > 1) { for (size_t i = 1, N = dims.size() - 1; i < N; ++i) { os << ", Internal(" << util::formattedInt(nodeCount[i]); os << " x " << (1 << dims[i]) << "^3)"; } os << ", Leaf(" << util::formattedInt(*nodeCount.rbegin()); os << " x " << (1 << *dims.rbegin()) << "^3)\n"; } os << " Background value: " << mRoot.background() << "\n"; // Statistics of topology and values if (verboseLevel > 3) { os << " Min value: " << minVal << "\n"; os << " Max value: " << maxVal << "\n"; } const 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 * double(numActiveVoxels)) / double(totalVoxels); os << " Percentage of active voxels: " << std::setprecision(3) << activeRatio << "%\n"; if (leafCount > 0) { const double fillRatio = (100.0 * double(numActiveLeafVoxels)) / (double(leafCount) * double(LeafNodeType::NUM_VOXELS)); os << " Average leaf node fill ratio: " << fillRatio << "%\n"; } #ifndef OPENVDB_2_ABI_COMPATIBLE if (verboseLevel > 2) { os << " Number of unallocated nodes: " << util::formattedInt(unallocatedLeafCount) << " (" << (100.0 * double(unallocatedLeafCount) / double(totalNodeCount)) << "%)\n"; } #endif } 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: "); util::printBytes(os, voxelsMem, " Active leaf voxels: "); if (numActiveVoxels) { util::printBytes(os, denseMem, " Dense equivalent: "); os << " Actual footprint is " << (100.0 * double(actualMem) / double(denseMem)) << "% of an equivalent dense volume\n"; os << " Leaf voxel footprint is " << (100.0 * double(voxelsMem) / double(actualMem)) << "% of actual footprint\n"; } } } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_TREE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/LeafNodeBool.h0000644000000000000000000020040612603226506014616 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 // for math::isZero() #include #include "LeafNode.h" #include "Iterator.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: typedef typename NodeMaskType::Word WordType; static const Index WORD_COUNT = NodeMaskType::WORD_COUNT; 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); // We can't use the ternary operator here, otherwise Visual C++ returns // a reference to a temporary. if (mData.isOn(i)) return LeafNode::sOn; else return 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; } /// Return a point to the c-style array of words encoding the bits. /// @warning This method should only be used by experts that /// seek low-level optimizations. WordType* data() { return &(mData.template getWord(0)); } /// Return a const point to the c-style array of words /// encoding the bits. /// @warning This method should only be used by experts that /// seek low-level optimizations. const WordType* data() const { return const_cast(this)->data(); } 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); #ifndef OPENVDB_2_ABI_COMPATIBLE /// "Partial creation" constructor used during file input LeafNode(PartialCreate, const Coord& xyz, bool value = false, bool active = false); #endif /// Deep copy constructor LeafNode(const LeafNode&); /// 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(); } #ifndef OPENVDB_2_ABI_COMPATIBLE /// @brief Return @c true if memory for this node's buffer has been allocated. /// @details Currently, boolean leaf nodes don't support partial creation, /// so this always returns @c true. bool isAllocated() const { return true; } /// @brief Allocate memory for this node's buffer if it has not already been allocated. /// @details Currently, boolean leaf nodes don't support partial creation, /// so this has no effect. bool allocate() { return true; } #endif /// Return the memory in bytes occupied by this node. Index64 memUsage() const; /// Expand the given bounding box so that it includes this leaf node's active voxels. /// If visitVoxels is false this LeafNode will be approximated as dense, i.e. with all /// voxels active. Else the individual active voxels are visited to produce a tight bbox. void evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels = true) const; /// @brief Return the bounding box of this node, i.e., the full index space /// spanned by this leaf node. CoordBBox getNodeBoundingBox() const { return CoordBBox::createCube(mOrigin, DIM); } /// Set the grid index coordinates of this node's local origin. void setOrigin(const Coord& origin) { mOrigin = origin; } //@{ /// Return the grid index coordinates of this node's local origin. const Coord& origin() const { return mOrigin; } void getOrigin(Coord& origin) const { origin = mOrigin; } void getOrigin(Int32& x, Int32& y, Int32& z) const { mOrigin.asXYZ(x, y, z); } //@} /// Return the linear table offset of the given global or local coordinates. static Index coordToOffset(const Coord& xyz); /// @brief Return the local coordinates for a linear table offset, /// where offset 0 has coordinates (0, 0, 0). static Coord offsetToLocalCoord(Index n); /// Return the global coordinates for a linear table offset. Coord offsetToGlobalCoord(Index n) const; /// Return a string representation of this node. std::string str() const; /// @brief Return @c true if the given node (which may have a different @c ValueType /// than this node) has the same active value topology as this node. template bool hasSameTopology(const LeafNode* other) const; /// Check for buffer equivalence by value. bool operator==(const LeafNode&) const; bool operator!=(const LeafNode&) const; // // Buffer management // /// @brief Exchange this node's data buffer with the given data buffer /// without changing the active states of the values. void swap(Buffer& other) { mBuffer.swap(other); } const Buffer& buffer() const { return mBuffer; } Buffer& buffer() { return mBuffer; } // // I/O methods // /// Read in just the topology. void readTopology(std::istream&, bool fromHalf = false); /// Write out just the topology. void writeTopology(std::ostream&, bool toHalf = false) const; /// Read in the topology and the origin. void readBuffers(std::istream&, bool fromHalf = false); void readBuffers(std::istream& is, const CoordBBox&, bool fromHalf = false); /// Write out the topology and the origin. void writeBuffers(std::ostream&, bool toHalf = false) const; // // Accessor methods // /// Return the value of the voxel at the given coordinates. const bool& getValue(const Coord& xyz) const; /// Return the value of the voxel at the given offset. const bool& getValue(Index offset) const; /// @brief Return @c true if the voxel at the given coordinates is active. /// @param xyz the coordinates of the voxel to be probed /// @param[out] val the value of the voxel at the given coordinates bool probeValue(const Coord& xyz, bool& val) const; /// Return the level (0) at which leaf node values reside. static Index getValueLevel(const Coord&) { return LEVEL; } /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on); /// Set the active state of the voxel at the given offset but don't change its value. void setActiveState(Index offset, bool on) { assert(offsetcoordToOffset(xyz)); } /// Mark the voxel at the given offset as inactive but don't change its value. void setValueOff(Index offset) { assert(offset < SIZE); mValueMask.setOff(offset); } /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, bool val); /// Set the value of the voxel at the given offset and mark the voxel as inactive. void setValueOff(Index offset, bool val); /// Mark the voxel at the given coordinates as active but don't change its value. void setValueOn(const Coord& xyz) { mValueMask.setOn(this->coordToOffset(xyz)); } /// Mark the voxel at the given offset as active but don't change its value. void setValueOn(Index offset) { assert(offset < SIZE); mValueMask.setOn(offset); } /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValueOn(const Coord& xyz, bool val); /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, bool val) { this->setValueOn(xyz, val); } /// Set the value of the voxel at the given offset and mark the voxel as active. void setValueOn(Index offset, bool val); /// @brief Apply a functor to the value of the voxel at the given offset /// and mark the voxel as active. template void modifyValue(Index offset, const ModifyOp& op); /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. template void modifyValue(const Coord& xyz, const ModifyOp& op); /// Apply a functor to the voxel at the given coordinates. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op); /// Mark all voxels as active but don't change their values. void setValuesOn() { mValueMask.setOn(); } /// Mark all voxels as inactive but don't change their values. void setValuesOff() { mValueMask.setOff(); } /// Return @c true if the voxel at the given coordinates is active. bool isValueOn(const Coord& xyz) const { return mValueMask.isOn(this->coordToOffset(xyz)); } /// Return @c true if the voxel at the given offset is active. bool isValueOn(Index offset) const { assert(offset < SIZE); return mValueMask.isOn(offset); } /// Return @c false since leaf nodes never contain tiles. static bool hasActiveTiles() { return false; } /// Set all voxels that lie outside the given axis-aligned box to the background. void clip(const CoordBBox&, bool background); /// Set all voxels within an axis-aligned box to the specified value and active state. void fill(const CoordBBox& bbox, bool value, bool active = true); /// Set all voxels 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 prune(const ValueType& /*tolerance*/ = zeroVal()) {} 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; } template void getNodes(ArrayT&) const {} template void stealNodes(ArrayT&, const ValueType&, bool) {} //@} void addTile(Index level, const Coord&, bool val, bool active); void addTile(Index offset, bool val, bool active); template void addTileAndCache(Index level, const Coord&, bool val, bool active, AccessorT&); //@{ /// @brief Return a pointer to this node. LeafNode* touchLeaf(const Coord&) { return this; } template LeafNode* touchLeafAndCache(const Coord&, AccessorT&) { return this; } LeafNode* probeLeaf(const Coord&) { return this; } template LeafNode* probeLeafAndCache(const Coord&, AccessorT&) { return this; } template NodeT* probeNodeAndCache(const Coord&, AccessorT&) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (!(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))) { } #ifndef OPENVDB_2_ABI_COMPATIBLE template inline LeafNode::LeafNode(PartialCreate, const Coord& xyz, bool value, bool active): mValueMask(active), mBuffer(value), mOrigin(xyz & (~(DIM - 1))) { /// @todo For now, this is identical to the non-PartialCreate constructor. /// Consider modifying the Buffer class to allow it to be constructed /// without allocating a bitmask. } #endif template inline LeafNode::LeafNode(const LeafNode &other): mValueMask(other.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, const CoordBBox& clipBBox, bool fromHalf) { // Boolean LeafNodes don't currently implement lazy loading. // Instead, load the full buffer, then clip it. this->readBuffers(is, fromHalf); // Get this tree's background value. bool background = false; if (const void* bgPtr = io::getGridBackgroundValuePtr(is)) { background = *static_cast(bgPtr); } this->clip(clipBBox, background); } template inline void LeafNode::readBuffers(std::istream& is, bool /*fromHalf*/) { // Read in the value mask. mValueMask.load(is); // Read in the origin. is.read(reinterpret_cast(&mOrigin), sizeof(Coord::ValueType) * 3); if (io::getFormatVersion(is) >= OPENVDB_FILE_VERSION_BOOL_LEAF_OPTIMIZATION) { // Read in the mask for the voxel values. mBuffer.mData.load(is); } else { // Older files stored one or more bool arrays. // Read in the number of buffers, which should now always be one. int8_t numBuffers = 0; is.read(reinterpret_cast(&numBuffers), sizeof(int8_t)); // Read in the buffer. // (Note: prior to the bool leaf optimization, buffers were always compressed.) 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) { 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::clip(const CoordBBox& clipBBox, bool background) { CoordBBox nodeBBox = this->getNodeBoundingBox(); if (!clipBBox.hasOverlap(nodeBBox)) { // This node lies completely outside the clipping region. Fill it with background tiles. this->fill(nodeBBox, background, /*active=*/false); } else if (clipBBox.isInside(nodeBBox)) { // This node lies completely inside the clipping region. Leave it intact. return; } // This node isn't completely contained inside the clipping region. // Set any voxels that lie outside the region to the background value. // Construct a boolean mask that is on inside the clipping region and off outside it. NodeMaskType mask; nodeBBox.intersect(clipBBox); Coord xyz; int &x = xyz.x(), &y = xyz.y(), &z = xyz.z(); for (x = nodeBBox.min().x(); x <= nodeBBox.max().x(); ++x) { for (y = nodeBBox.min().y(); y <= nodeBBox.max().y(); ++y) { for (z = nodeBBox.min().z(); z <= nodeBBox.max().z(); ++z) { mask.setOn(static_cast(this->coordToOffset(xyz))); } } } // Set voxels that lie in the inactive region of the mask (i.e., outside // the clipping region) to the background value. for (MaskOffIter maskIter = mask.beginOff(); maskIter; ++maskIter) { this->setValueOff(maskIter.pos(), background); } } //////////////////////////////////////// template inline void LeafNode::fill(const CoordBBox& bbox, bool value, bool active) { 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; struct Local { inline static bool toBool(const DenseValueType& v) { return !math::isZero(v); } }; const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); const Coord& min = dense.bbox().min(); const DenseValueType* s0 = dense.data() + zStride * (bbox.min()[2] - min[2]); // source const Int32 n0 = bbox.min()[2] & (DIM-1u); for (Int32 x = bbox.min()[0], ex = bbox.max()[0] + 1; x < ex; ++x) { const DenseValueType* s1 = s0 + xStride * (x - min[0]); const Int32 n1 = n0 + ((x & (DIM-1u)) << 2*LOG2DIM); for (Int32 y = bbox.min()[1], ey = bbox.max()[1] + 1; y < ey; ++y) { const DenseValueType* s2 = s1 + yStride * (y - min[1]); Int32 n2 = n1 + ((y & (DIM-1u)) << LOG2DIM); for (Int32 z = bbox.min()[2], ez = bbox.max()[2]+1; z < ez; ++z, ++n2, s2 += zStride) { // Note: if tolerance is true (i.e., 1), then all boolean values compare equal. if (tolerance || (background == Local::toBool(*s2))) { mValueMask.setOff(n2); mBuffer.mData.set(n2, background); } else { mValueMask.setOn(n2); mBuffer.mData.set(n2, Local::toBool(*s2)); } } } } } //////////////////////////////////////// template template inline void LeafNode::combine(const LeafNode& other, CombineOp& op) { CombineArgs args; for (Index i = 0; i < SIZE; ++i) { bool result = false, aVal = mBuffer.mData.isOn(i), bVal = other.mBuffer.mData.isOn(i); op(args.setARef(aVal) .setAIsActive(mValueMask.isOn(i)) .setBRef(bVal) .setBIsActive(other.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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/NodeUnion.h0000644000000000000000000001167112603226506014227 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/0000755000000000000000000000000012603226506012514 5ustar rootrootopenvdb/viewer/ClipBox.cc0000644000000000000000000002327712603226506014376 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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" 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; glColor3d(0.1, 0.1, 0.9); if (-mFrontPlane[3] > mBBox.min().z()) { glBegin(geoMode); glVertex3d(mBBox.min().x(), mBBox.min().y(), -mFrontPlane[3]); glVertex3d(mBBox.min().x(), mBBox.max().y(), -mFrontPlane[3]); glVertex3d(mBBox.max().x(), mBBox.max().y(), -mFrontPlane[3]); glVertex3d(mBBox.max().x(), mBBox.min().y(), -mFrontPlane[3]); glEnd(); drawBbox = true; } if (mBackPlane[3] < mBBox.max().z()) { glBegin(geoMode); glVertex3d(mBBox.min().x(), mBBox.min().y(), mBackPlane[3]); glVertex3d(mBBox.min().x(), mBBox.max().y(), mBackPlane[3]); glVertex3d(mBBox.max().x(), mBBox.max().y(), mBackPlane[3]); glVertex3d(mBBox.max().x(), mBBox.min().y(), mBackPlane[3]); glEnd(); drawBbox = true; } glColor3d(0.9, 0.1, 0.1); if (-mLeftPlane[3] > mBBox.min().x()) { glBegin(geoMode); glVertex3d(-mLeftPlane[3], mBBox.min().y(), mBBox.min().z()); glVertex3d(-mLeftPlane[3], mBBox.max().y(), mBBox.min().z()); glVertex3d(-mLeftPlane[3], mBBox.max().y(), mBBox.max().z()); glVertex3d(-mLeftPlane[3], mBBox.min().y(), mBBox.max().z()); glEnd(); drawBbox = true; } if (mRightPlane[3] < mBBox.max().x()) { glBegin(geoMode); glVertex3d(mRightPlane[3], mBBox.min().y(), mBBox.min().z()); glVertex3d(mRightPlane[3], mBBox.max().y(), mBBox.min().z()); glVertex3d(mRightPlane[3], mBBox.max().y(), mBBox.max().z()); glVertex3d(mRightPlane[3], mBBox.min().y(), mBBox.max().z()); glEnd(); drawBbox = true; } glColor3d(0.1, 0.9, 0.1); if (-mTopPlane[3] > mBBox.min().y()) { glBegin(geoMode); glVertex3d(mBBox.min().x(), -mTopPlane[3], mBBox.min().z()); glVertex3d(mBBox.min().x(), -mTopPlane[3], mBBox.max().z()); glVertex3d(mBBox.max().x(), -mTopPlane[3], mBBox.max().z()); glVertex3d(mBBox.max().x(), -mTopPlane[3], mBBox.min().z()); glEnd(); drawBbox = true; } if (mBottomPlane[3] < mBBox.max().y()) { glBegin(geoMode); glVertex3d(mBBox.min().x(), mBottomPlane[3], mBBox.min().z()); glVertex3d(mBBox.min().x(), mBottomPlane[3], mBBox.max().z()); glVertex3d(mBBox.max().x(), mBottomPlane[3], mBBox.max().z()); glVertex3d(mBBox.max().x(), mBottomPlane[3], mBBox.min().z()); glEnd(); drawBbox = true; } if (drawBbox) { glColor3d(0.5, 0.5, 0.5); glBegin(GL_LINE_LOOP); glVertex3d(mBBox.min().x(), mBBox.min().y(), mBBox.min().z()); glVertex3d(mBBox.min().x(), mBBox.min().y(), mBBox.max().z()); glVertex3d(mBBox.max().x(), mBBox.min().y(), mBBox.max().z()); glVertex3d(mBBox.max().x(), mBBox.min().y(), mBBox.min().z()); glEnd(); glBegin(GL_LINE_LOOP); glVertex3d(mBBox.min().x(), mBBox.max().y(), mBBox.min().z()); glVertex3d(mBBox.min().x(), mBBox.max().y(), mBBox.max().z()); glVertex3d(mBBox.max().x(), mBBox.max().y(), mBBox.max().z()); glVertex3d(mBBox.max().x(), mBBox.max().y(), mBBox.min().z()); glEnd(); glBegin(GL_LINES); glVertex3d(mBBox.min().x(), mBBox.min().y(), mBBox.min().z()); glVertex3d(mBBox.min().x(), mBBox.max().y(), mBBox.min().z()); glVertex3d(mBBox.min().x(), mBBox.min().y(), mBBox.max().z()); glVertex3d(mBBox.min().x(), mBBox.max().y(), mBBox.max().z()); glVertex3d(mBBox.max().x(), mBBox.min().y(), mBBox.max().z()); glVertex3d(mBBox.max().x(), mBBox.max().y(), mBBox.max().z()); glVertex3d(mBBox.max().x(), mBBox.min().y(), mBBox.min().z()); glVertex3d(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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/Font.cc0000644000000000000000000002631612603226506013741 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 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(); GLint vp[4] = { 0, 0, 0, 0 }; glGetIntegerv(GL_VIEWPORT, vp); const int width = vp[2], height = std::max(1, vp[3]); 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(GLsizei(str.length()), GL_UNSIGNED_BYTE, reinterpret_cast(str.c_str())); glPopAttrib(); } } // namespace openvdb_viewer // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/RenderModules.h0000644000000000000000000001250512603226506015440 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #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 ~RenderModule() {} virtual void render() = 0; bool visible() { return mIsVisible; } void setVisible(bool b) { mIsVisible = b; } protected: RenderModule(): mIsVisible(true) {} bool mIsVisible; }; //////////////////////////////////////// /// @brief Basic render module, axis gnomon and ground plane. class ViewportModule: public RenderModule { public: ViewportModule(); virtual ~ViewportModule() {} virtual void render(); private: float mAxisGnomonScale, mGroundPlaneScale; }; //////////////////////////////////////// /// @brief Tree topology render module class TreeTopologyModule: public RenderModule { public: TreeTopologyModule(const openvdb::GridBase::ConstPtr&); virtual ~TreeTopologyModule() {} virtual 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&); virtual ~ActiveValueModule() {} virtual 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&); virtual ~MeshModule() {} virtual void render(); private: void init(); const openvdb::GridBase::ConstPtr& mGrid; BufferObject mBufferObject; bool mIsInitialized; ShaderProgram mShader; }; } // namespace openvdb_viewer #endif // OPENVDB_VIEWER_RENDERMODULES_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/Viewer.cc0000644000000000000000000007153512603226506014277 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 OPENVDB_LIBRARY_MAJOR_VERSION, etc. #include #include #include // for fabs() #include // for std::setprecision() #include #include #include #include #include #include // for nanosleep() #ifdef OPENVDB_USE_GLFW_3 //#define GLFW_INCLUDE_GLU #include #else // if !defined(OPENVDB_USE_GLFW_3) #if defined(__APPLE__) || defined(MACOSX) #include #include #else #include #include #endif #include #endif // !defined(OPENVDB_USE_GLFW_3) 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); std::string getVersionString() const; bool isOpen() const; bool open(int width = 900, int height = 800); void view(const openvdb::GridCPtrVec&); void handleEvents(); void close(); void resize(int width, int height); void showPrevGrid(); void showNextGrid(); bool needsDisplay(); void setNeedsDisplay(); void toggleRenderModule(size_t n); void toggleInfoText(); // Internal void render(); void interrupt(); void setWindowTitle(double fps = 0.0); void showNthGrid(size_t n); void updateCutPlanes(int wheelPos); void swapBuffers(); 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(); static openvdb::BBoxd worldSpaceBBox(const openvdb::math::Transform&, const openvdb::CoordBBox&); static void sleep(double seconds); private: bool mDidInit; CameraPtr mCamera; ClipBoxPtr mClipBox; RenderModulePtr mViewportModule; std::vector mRenderModules; openvdb::GridCPtrVec mGrids; size_t mGridIdx, mUpdates; std::string mGridName, mProgName, mGridInfo, mTransformInfo, mTreeInfo; int mWheelPos; bool mShiftIsDown, mCtrlIsDown, mShowInfo; bool mInterrupt; #if GLFW_VERSION_MAJOR >= 3 GLFWwindow* mWindow; #endif }; // class ViewerImpl class ThreadManager { public: ThreadManager(); void view(const openvdb::GridCPtrVec& gridList); void close(); void resize(int width, int height); private: void doView(); static void* doViewTask(void* arg); tbb::atomic mRedisplay; bool mClose, mHasThread; boost::thread mThread; openvdb::GridCPtrVec mGrids; }; //////////////////////////////////////// namespace { ViewerImpl* sViewer = NULL; ThreadManager* sThreadMgr = NULL; tbb::mutex sLock; #if GLFW_VERSION_MAJOR >= 3 void keyCB(GLFWwindow*, int key, int /*scancode*/, int action, int /*modifiers*/) #else void keyCB(int key, int action) #endif { if (sViewer) sViewer->keyCallback(key, action); } #if GLFW_VERSION_MAJOR >= 3 void mouseButtonCB(GLFWwindow*, int button, int action, int /*modifiers*/) #else void mouseButtonCB(int button, int action) #endif { if (sViewer) sViewer->mouseButtonCallback(button, action); } #if GLFW_VERSION_MAJOR >= 3 void mousePosCB(GLFWwindow*, double x, double y) { if (sViewer) sViewer->mousePosCallback(int(x), int(y)); } #else void mousePosCB(int x, int y) { if (sViewer) sViewer->mousePosCallback(x, y); } #endif #if GLFW_VERSION_MAJOR >= 3 void mouseWheelCB(GLFWwindow*, double /*xoffset*/, double yoffset) { if (sViewer) sViewer->mouseWheelCallback(int(yoffset)); } #else void mouseWheelCB(int pos) { if (sViewer) sViewer->mouseWheelCallback(pos); } #endif #if GLFW_VERSION_MAJOR >= 3 void windowSizeCB(GLFWwindow*, int width, int height) #else void windowSizeCB(int width, int height) #endif { if (sViewer) sViewer->windowSizeCallback(width, height); } #if GLFW_VERSION_MAJOR >= 3 void windowRefreshCB(GLFWwindow*) #else void windowRefreshCB() #endif { if (sViewer) sViewer->windowRefreshCallback(); } } // unnamed namespace //////////////////////////////////////// Viewer init(const std::string& progName, bool background) { if (sViewer == NULL) { tbb::mutex::scoped_lock lock(sLock); if (sViewer == NULL) { OPENVDB_START_THREADSAFE_STATIC_WRITE sViewer = new ViewerImpl; OPENVDB_FINISH_THREADSAFE_STATIC_WRITE } } sViewer->init(progName); if (background) { if (sThreadMgr == NULL) { tbb::mutex::scoped_lock lock(sLock); if (sThreadMgr == NULL) { OPENVDB_START_THREADSAFE_STATIC_WRITE sThreadMgr = new ThreadManager; OPENVDB_FINISH_THREADSAFE_STATIC_WRITE } } } else { if (sThreadMgr != NULL) { tbb::mutex::scoped_lock lock(sLock); delete sThreadMgr; OPENVDB_START_THREADSAFE_STATIC_WRITE sThreadMgr = NULL; OPENVDB_FINISH_THREADSAFE_STATIC_WRITE } } return Viewer(); } void exit() { #if GLFW_VERSION_MAJOR >= 3 // Prior to GLFW 3, glfwTerminate() was called automatically from // an atexit() function installed by glfwInit(), so this was not needed. // But GLFW 3 does not register an atexit() function. glfwTerminate(); #endif } //////////////////////////////////////// Viewer::Viewer() { OPENVDB_LOG_DEBUG_RUNTIME("constructed Viewer from thread " << boost::this_thread::get_id()); } void Viewer::open(int width, int height) { if (sViewer) sViewer->open(width, height); } void Viewer::view(const openvdb::GridCPtrVec& grids) { if (sThreadMgr) { sThreadMgr->view(grids); } else if (sViewer) { sViewer->view(grids); } } void Viewer::handleEvents() { if (sViewer) sViewer->handleEvents(); } void Viewer::close() { if (sThreadMgr) sThreadMgr->close(); else if (sViewer) sViewer->close(); } void Viewer::resize(int width, int height) { if (sViewer) sViewer->resize(width, height); } std::string Viewer::getVersionString() const { std::string version; if (sViewer) version = sViewer->getVersionString(); return version; } //////////////////////////////////////// ThreadManager::ThreadManager() : mClose(false) , mHasThread(false) { mRedisplay = false; } void ThreadManager::view(const openvdb::GridCPtrVec& gridList) { if (!sViewer) return; mGrids = gridList; mClose = false; mRedisplay = true; if (!mHasThread) { mThread = boost::thread(doViewTask, this); mHasThread = true; } } void ThreadManager::close() { if (!sViewer) return; // Tell the viewer thread to exit. mRedisplay = false; mClose = true; // Tell the viewer to terminate its event loop. sViewer->interrupt(); if (mHasThread) { mThread.join(); mHasThread = false; } // Tell the viewer to close its window. sViewer->close(); } void ThreadManager::doView() { // This function runs in its own thread. // The mClose and mRedisplay flags are set from the main thread. while (!mClose) { if (mRedisplay.compare_and_swap(/*set to*/false, /*if*/true)) { if (sViewer) sViewer->view(mGrids); } sViewer->sleep(0.5/*sec*/); } } //static void* ThreadManager::doViewTask(void* arg) { if (ThreadManager* self = static_cast(arg)) { self->doView(); } return NULL; } //////////////////////////////////////// ViewerImpl::ViewerImpl() : mDidInit(false) , mCamera(new Camera) , mClipBox(new ClipBox) , mGridIdx(0) , mUpdates(0) , mWheelPos(0) , mShiftIsDown(false) , mCtrlIsDown(false) , mShowInfo(true) , mInterrupt(false) #if GLFW_VERSION_MAJOR >= 3 , mWindow(NULL) #endif { } void ViewerImpl::init(const std::string& progName) { mProgName = progName; if (!mDidInit) { #if GLFW_VERSION_MAJOR >= 3 struct Local { static void errorCB(int error, const char* descr) { OPENVDB_LOG_ERROR("GLFW Error " << error << ": " << descr); } }; glfwSetErrorCallback(Local::errorCB); #endif if (glfwInit() == GL_TRUE) { OPENVDB_LOG_DEBUG_RUNTIME("initialized GLFW from thread " << boost::this_thread::get_id()); mDidInit = true; } else { OPENVDB_LOG_ERROR("GLFW initialization failed"); } } mViewportModule.reset(new ViewportModule); } std::string ViewerImpl::getVersionString() const { std::ostringstream ostr; ostr << "OpenVDB: " << openvdb::OPENVDB_LIBRARY_MAJOR_VERSION << "." << openvdb::OPENVDB_LIBRARY_MINOR_VERSION << "." << openvdb::OPENVDB_LIBRARY_PATCH_VERSION; int major, minor, rev; glfwGetVersion(&major, &minor, &rev); ostr << ", " << "GLFW: " << major << "." << minor << "." << rev; if (mDidInit) { ostr << ", " << "OpenGL: "; #if GLFW_VERSION_MAJOR >= 3 boost::shared_ptr wPtr; GLFWwindow* w = mWindow; if (!w) { wPtr.reset(glfwCreateWindow(100, 100, "", NULL, NULL), &glfwDestroyWindow); w = wPtr.get(); } if (w) { ostr << glfwGetWindowAttrib(w, GLFW_CONTEXT_VERSION_MAJOR) << "." << glfwGetWindowAttrib(w, GLFW_CONTEXT_VERSION_MINOR) << "." << glfwGetWindowAttrib(w, GLFW_CONTEXT_REVISION); } #else if (!glfwGetWindowParam(GLFW_OPENED)) { if (glfwOpenWindow(100, 100, 8, 8, 8, 8, 24, 0, GLFW_WINDOW)) { ostr << glGetString(GL_VERSION); glfwCloseWindow(); } } else { ostr << glGetString(GL_VERSION); } #endif } return ostr.str(); } #if GLFW_VERSION_MAJOR >= 3 bool ViewerImpl::open(int width, int height) { if (mWindow == NULL) { glfwWindowHint(GLFW_RED_BITS, 8); glfwWindowHint(GLFW_GREEN_BITS, 8); glfwWindowHint(GLFW_BLUE_BITS, 8); glfwWindowHint(GLFW_ALPHA_BITS, 8); glfwWindowHint(GLFW_DEPTH_BITS, 32); glfwWindowHint(GLFW_STENCIL_BITS, 0); mWindow = glfwCreateWindow( width, height, mProgName.c_str(), /*monitor=*/NULL, /*share=*/NULL); OPENVDB_LOG_DEBUG_RUNTIME("created window " << std::hex << mWindow << std::dec << " from thread " << boost::this_thread::get_id()); if (mWindow != NULL) { // Temporarily make the new window the current context, then create a font. boost::shared_ptr curWindow( glfwGetCurrentContext(), glfwMakeContextCurrent); glfwMakeContextCurrent(mWindow); BitmapFont13::initialize(); } } mCamera->setWindow(mWindow); if (mWindow != NULL) { glfwSetKeyCallback(mWindow, keyCB); glfwSetMouseButtonCallback(mWindow, mouseButtonCB); glfwSetCursorPosCallback(mWindow, mousePosCB); glfwSetScrollCallback(mWindow, mouseWheelCB); glfwSetWindowSizeCallback(mWindow, windowSizeCB); glfwSetWindowRefreshCallback(mWindow, windowRefreshCB); } return (mWindow != NULL); } #else // if GLFW_VERSION_MAJOR <= 2 bool ViewerImpl::open(int width, int height) { if (!glfwGetWindowParam(GLFW_OPENED)) { if (!glfwOpenWindow(width, height, 8, 8, 8, 8, // # of R,G,B, & A bits 32, 0, // # of depth & stencil buffer bits GLFW_WINDOW)) // either GLFW_WINDOW or GLFW_FULLSCREEN { return false; } } glfwSetWindowTitle(mProgName.c_str()); BitmapFont13::initialize(); glfwSetKeyCallback(keyCB); glfwSetMouseButtonCallback(mouseButtonCB); glfwSetMousePosCallback(mousePosCB); glfwSetMouseWheelCallback(mouseWheelCB); glfwSetWindowSizeCallback(windowSizeCB); glfwSetWindowRefreshCallback(windowRefreshCB); return true; } #endif bool ViewerImpl::isOpen() const { #if GLFW_VERSION_MAJOR >= 3 return (mWindow != NULL); #else return glfwGetWindowParam(GLFW_OPENED); #endif } // Set a flag so as to break out of the event loop on the next iteration. // (Useful only if the event loop is running in a separate thread.) void ViewerImpl::interrupt() { mInterrupt = true; #if GLFW_VERSION_MAJOR >= 3 if (mWindow) glfwSetWindowShouldClose(mWindow, true); #endif } void ViewerImpl::handleEvents() { #if GLFW_VERSION_MAJOR >= 3 glfwPollEvents(); #endif } void ViewerImpl::close() { #if GLFW_VERSION_MAJOR >= 3 OPENVDB_LOG_DEBUG_RUNTIME("about to close window " << std::hex << mWindow << std::dec << " from thread " << boost::this_thread::get_id()); #else OPENVDB_LOG_DEBUG_RUNTIME("about to close window from thread " << boost::this_thread::get_id()); #endif mViewportModule.reset(); mRenderModules.clear(); #if GLFW_VERSION_MAJOR >= 3 mCamera->setWindow(NULL); GLFWwindow* win = mWindow; mWindow = NULL; glfwDestroyWindow(win); OPENVDB_LOG_DEBUG_RUNTIME("destroyed window " << std::hex << win << std::dec << " from thread " << boost::this_thread::get_id()); #else glfwCloseWindow(); #endif } //////////////////////////////////////// void ViewerImpl::view(const openvdb::GridCPtrVec& gridList) { if (!isOpen()) return; mGrids = gridList; mGridIdx = size_t(-1); mGridName.clear(); // Compute the combined bounding box of all the grids. 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); #if GLFW_VERSION_MAJOR >= 3 // Prepare window for rendering. glfwMakeContextCurrent(mWindow); #endif { // set up camera openvdb::Vec3d extents = bbox.extents(); double maxExtent = std::max(extents[0], std::max(extents[1], extents[2])); mCamera->setTarget(bbox.getCenter(), maxExtent); mCamera->lookAtTarget(); mCamera->setSpeed(); } swapBuffers(); setNeedsDisplay(); ////////// // Screen color glClearColor(0.85f, 0.85f, 0.85f, 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); #if GLFW_VERSION_MAJOR >= 3 OPENVDB_LOG_DEBUG_RUNTIME("starting to render in window " << std::hex << mWindow << std::dec << " from thread " << boost::this_thread::get_id()); #else OPENVDB_LOG_DEBUG_RUNTIME("starting to render from thread " << boost::this_thread::get_id()); #endif mInterrupt = false; for (bool stop = false; !stop; ) { 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 swapBuffers(); sleep(0.01/*sec*/); // Exit if the Esc key is pressed or the window is closed. #if GLFW_VERSION_MAJOR >= 3 handleEvents(); stop = (mInterrupt || glfwWindowShouldClose(mWindow)); #else stop = (mInterrupt || glfwGetKey(GLFW_KEY_ESC) || !glfwGetWindowParam(GLFW_OPENED)); #endif } #if GLFW_VERSION_MAJOR >= 3 if (glfwGetCurrentContext() == mWindow) { ///< @todo not thread-safe // Detach this viewer's GL context. glfwMakeContextCurrent(NULL); OPENVDB_LOG_DEBUG_RUNTIME("detached window " << std::hex << mWindow << std::dec << " from thread " << boost::this_thread::get_id()); } #endif #if GLFW_VERSION_MAJOR >= 3 OPENVDB_LOG_DEBUG_RUNTIME("finished rendering in window " << std::hex << mWindow << std::dec << " from thread " << boost::this_thread::get_id()); #else OPENVDB_LOG_DEBUG_RUNTIME("finished rendering from thread " << boost::this_thread::get_id()); #endif } //////////////////////////////////////// void ViewerImpl::resize(int width, int height) { #if GLFW_VERSION_MAJOR >= 3 if (mWindow) glfwSetWindowSize(mWindow, width, height); #else glfwSetWindowSize(width, height); #endif } //////////////////////////////////////// void ViewerImpl::render() { #if GLFW_VERSION_MAJOR >= 3 if (mWindow == NULL) return; // Prepare window for rendering. glfwMakeContextCurrent(mWindow); #endif mCamera->aim(); // draw scene mViewportModule->render(); // ground plane. mClipBox->render(); mClipBox->enableClipping(); for (size_t n = 0, N = mRenderModules.size(); n < N; ++n) { mRenderModules[n]->render(); } mClipBox->disableClipping(); // Render text if (mShowInfo) { BitmapFont13::enableFontRendering(); glColor3d(0.2, 0.2, 0.2); int width, height; #if GLFW_VERSION_MAJOR >= 3 glfwGetFramebufferSize(mWindow, &width, &height); #else glfwGetWindowSize(&width, &height); #endif BitmapFont13::print(10, height - 13 - 10, mGridInfo); BitmapFont13::print(10, height - 13 - 30, mTransformInfo); BitmapFont13::print(10, height - 13 - 50, mTreeInfo); BitmapFont13::disableFontRendering(); } } //////////////////////////////////////// //static void ViewerImpl::sleep(double secs) { secs = fabs(secs); int isecs = int(secs); struct timespec sleepTime = { isecs /*sec*/, int(1.0e9 * (secs - isecs)) /*nsec*/ }; nanosleep(&sleepTime, /*remainingTime=*/NULL); } //////////////////////////////////////// //static openvdb::BBoxd ViewerImpl::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::updateCutPlanes(int wheelPos) { double speed = std::abs(mWheelPos - wheelPos); if (mWheelPos < wheelPos) mClipBox->update(speed); else mClipBox->update(-speed); setNeedsDisplay(); } //////////////////////////////////////// void ViewerImpl::swapBuffers() { #if GLFW_VERSION_MAJOR >= 3 glfwSwapBuffers(mWindow); #else glfwSwapBuffers(); #endif } //////////////////////////////////////// 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"; #if GLFW_VERSION_MAJOR >= 3 if (mWindow) glfwSetWindowTitle(mWindow, ss.str().c_str()); #else glfwSetWindowTitle(ss.str().c_str()); #endif } //////////////////////////////////////// void ViewerImpl::showPrevGrid() { if (const size_t numGrids = mGrids.size()) { size_t idx = ((numGrids + mGridIdx) - 1) % numGrids; showNthGrid(idx); } } void ViewerImpl::showNextGrid() { if (const size_t numGrids = mGrids.size()) { size_t idx = (mGridIdx + 1) % numGrids; showNthGrid(idx); } } void ViewerImpl::showNthGrid(size_t n) { if (mGrids.empty()) return; 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 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 = 1, I = mRenderModules.size(); i < I; ++i) { mRenderModules[i]->setVisible(false); } } else { for (size_t i = 0, I = active.size(); i < I; ++i) { mRenderModules[i]->setVisible(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) { mCamera->keyCallback(key, action); #if GLFW_VERSION_MAJOR >= 3 if (mWindow == NULL) return; const bool keyPress = (glfwGetKey(mWindow, key) == GLFW_PRESS); /// @todo Should use "modifiers" argument to keyCB(). mShiftIsDown = glfwGetKey(mWindow, GLFW_KEY_LEFT_SHIFT); mCtrlIsDown = glfwGetKey(mWindow, GLFW_KEY_LEFT_CONTROL); #else const bool keyPress = glfwGetKey(key) == GLFW_PRESS; mShiftIsDown = glfwGetKey(GLFW_KEY_LSHIFT); mCtrlIsDown = glfwGetKey(GLFW_KEY_LCTRL); #endif if (keyPress) { switch (key) { case '1': case GLFW_KEY_KP_1: toggleRenderModule(0); break; case '2': case GLFW_KEY_KP_2: toggleRenderModule(1); break; case '3': case GLFW_KEY_KP_3: toggleRenderModule(2); 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; #if GLFW_VERSION_MAJOR >= 3 case GLFW_KEY_ESCAPE: glfwSetWindowShouldClose(mWindow, true); break; #endif } } 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(); } 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 GLFW_VERSION_MAJOR >= 3 pos += mWheelPos; #endif 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]->setVisible(!mRenderModules[n]->visible()); } void ViewerImpl::toggleInfoText() { mShowInfo = !mShowInfo; } } // namespace openvdb_viewer // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/Viewer.h0000644000000000000000000000710112603226506014125 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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; enum { DEFAULT_WIDTH = 900, DEFAULT_HEIGHT = 800 }; /// @brief Initialize and return a viewer. /// @param progName the name of the calling program (for use in info displays) /// @param background if true, run the viewer in a separate thread /// @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. Viewer init(const std::string& progName, bool background); /// @brief Destroy all viewer windows and release resources. /// @details This should be called from the main thread before your program exits. void exit(); /// Manager for a window that displays OpenVDB grids class Viewer { public: /// Set the size of and open the window associated with this viewer. void open(int width = DEFAULT_WIDTH, int height = DEFAULT_HEIGHT); /// Display the given grids. void view(const openvdb::GridCPtrVec&); /// @brief Process any pending user input (keyboard, mouse, etc.) /// in the window associated with this viewer. void handleEvents(); /// @brief Close the window associated with this viewer. /// @warning The window associated with this viewer might be shared with other viewers. void close(); /// Resize the window associated with this viewer. void resize(int width, int height); /// Return a string with version number information. std::string getVersionString() const; private: friend Viewer init(const std::string&, bool); Viewer(); }; } // namespace openvdb_viewer #endif // OPENVDB_VIEWER_VIEWER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/RenderModules.cc0000644000000000000000000013672412603226506015610 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #include #include namespace openvdb_viewer { 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; } template class MinMaxVoxel { public: typedef openvdb::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; }; 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(std::numeric_limits::max()) , mMax(-mMin) { } 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); } } // namespace util //////////////////////////////////////// // 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 = GLsizei(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 = GLint(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 = GLint(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 = 0; 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 = GLuint(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 = 0; GLuint shaders[2] = { 0, 0 }; 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); } //////////////////////////////////////// // 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); glColor3d(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]); } //////////////////////////////////////// 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++] = static_cast(ptn[0]); points[pOffset++] = static_cast(ptn[1]); points[pOffset++] = static_cast(ptn[2]); // corner 2 ptn = openvdb::Vec3d(min.x(), min.y(), max.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = static_cast(ptn[0]); points[pOffset++] = static_cast(ptn[1]); points[pOffset++] = static_cast(ptn[2]); // corner 3 ptn = openvdb::Vec3d(max.x(), min.y(), max.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = static_cast(ptn[0]); points[pOffset++] = static_cast(ptn[1]); points[pOffset++] = static_cast(ptn[2]); // corner 4 ptn = openvdb::Vec3d(max.x(), min.y(), min.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = static_cast(ptn[0]); points[pOffset++] = static_cast(ptn[1]); points[pOffset++] = static_cast(ptn[2]); // corner 5 ptn = openvdb::Vec3d(min.x(), max.y(), min.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = static_cast(ptn[0]); points[pOffset++] = static_cast(ptn[1]); points[pOffset++] = static_cast(ptn[2]); // corner 6 ptn = openvdb::Vec3d(min.x(), max.y(), max.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = static_cast(ptn[0]); points[pOffset++] = static_cast(ptn[1]); points[pOffset++] = static_cast(ptn[2]); // corner 7 ptn = grid->indexToWorld(max); points[pOffset++] = static_cast(ptn[0]); points[pOffset++] = static_cast(ptn[1]); points[pOffset++] = static_cast(ptn[2]); // corner 8 ptn = openvdb::Vec3d(max.x(), max.y(), min.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = static_cast(ptn[0]); points[pOffset++] = static_cast(ptn[1]); points[pOffset++] = static_cast(ptn[2]); // edge 1 indices[iOffset++] = GLuint(idx); indices[iOffset++] = GLuint(idx + 1); // edge 2 indices[iOffset++] = GLuint(idx + 1); indices[iOffset++] = GLuint(idx + 2); // edge 3 indices[iOffset++] = GLuint(idx + 2); indices[iOffset++] = GLuint(idx + 3); // edge 4 indices[iOffset++] = GLuint(idx + 3); indices[iOffset++] = GLuint(idx); // edge 5 indices[iOffset++] = GLuint(idx + 4); indices[iOffset++] = GLuint(idx + 5); // edge 6 indices[iOffset++] = GLuint(idx + 5); indices[iOffset++] = GLuint(idx + 6); // edge 7 indices[iOffset++] = GLuint(idx + 6); indices[iOffset++] = GLuint(idx + 7); // edge 8 indices[iOffset++] = GLuint(idx + 7); indices[iOffset++] = GLuint(idx + 4); // edge 9 indices[iOffset++] = GLuint(idx); indices[iOffset++] = GLuint(idx + 4); // edge 10 indices[iOffset++] = GLuint(idx + 1); indices[iOffset++] = GLuint(idx + 5); // edge 11 indices[iOffset++] = GLuint(idx + 2); indices[iOffset++] = GLuint(idx + 6); // edge 12 indices[iOffset++] = GLuint(idx + 3); indices[iOffset++] = GLuint(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 openvdb::Vec3s TreeTopologyOp::sNodeColors[] = { openvdb::Vec3s(0.045f, 0.045f, 0.045f), // root openvdb::Vec3s(0.0432f, 0.33f, 0.0411023f), // first internal node level openvdb::Vec3s(0.871f, 0.394f, 0.01916f), // intermediate internal node levels openvdb::Vec3s(0.00608299f, 0.279541f, 0.625f) // leaf nodes }; //////////////////////////////////////// // 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(); } //////////////////////////////////////// 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] = GLuint(index); const size_t element = index * 3; mPoints[element ] = static_cast(pos[0]); mPoints[element + 1] = static_cast(pos[1]); mPoints[element + 2] = static_cast(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.9f, 0.3f, 0.3f); 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 = openvdb::Vec3s(w * mColorMap[0] + (1.0 - w) * mColorMap[1]); } } else { if (mIsLevelSet) { color = mColorMap[2]; } else { w = (float(value) - mOffset[0]) * mScale[0]; color = openvdb::Vec3s(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::Vec3d(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] = static_cast(normal[0]); (*mNormals)[e2] = static_cast(normal[1]); (*mNormals)[e3] = static_cast(normal[2]); } } } private: void init() { mOffset[0] = static_cast(std::min(mZeroValue, mMinValue)); mScale[0] = static_cast( 1.0 / (std::abs(std::max(mZeroValue, mMaxValue) - mOffset[0]))); mOffset[1] = static_cast(std::min(mZeroValue, mMinValue)); mScale[1] = static_cast( 1.0 / (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.3f, 0.9f, 0.3f); // green colorMap[1] = openvdb::Vec3s(0.9f, 0.3f, 0.3f); // red colorMap[2] = openvdb::Vec3s(0.9f, 0.9f, 0.3f); // yellow colorMap[3] = openvdb::Vec3s(0.3f, 0.3f, 0.9f); // 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); { util::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); } openvdb::tools::pruneInactive(*surfaceMask); 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++] = static_cast(pos[0]); points[idx++] = static_cast(pos[1]); points[idx++] = static_cast(pos[2]); indices[pt] = GLuint(pt); ++pt; indices[pt] = GLuint(pt); ++pt; double w = vec.length() / length; vec.normalize(); pos += grid->voxelSize()[0] * 0.9 * vec; points[idx++] = static_cast(pos[0]); points[idx++] = static_cast(pos[1]); points[idx++] = static_cast(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++] = static_cast(color[0] * 0.3); colors[cc++] = static_cast(color[1] * 0.3); colors[cc++] = static_cast(color[2] * 0.3); colors[cc++] = static_cast(color[0]); colors[cc++] = static_cast(color[1]); colors[cc++] = static_cast(color[2]); } } mVectorBuffer->genVertexBuffer(points); mVectorBuffer->genColorBuffer(colors); mVectorBuffer->genIndexBuffer(indices, GL_LINES); } private: BufferObject *mVectorBuffer; }; // ActiveVectorValuesOp //////////////////////////////////////// // 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(); } //////////////////////////////////////// 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()); 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 (int v = 0; v < 4; ++v) { normals[quad[v]*3] = static_cast(-normal[0]); normals[quad[v]*3+1] = static_cast(-normal[1]); normals[quad[v]*3+2] = static_cast(-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 //////////////////////////////////////// // 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/Font.h0000644000000000000000000000464112603226506013600 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/ClipBox.h0000644000000000000000000000616412603226506014234 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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]; }; // class ClipBox } // namespace openvdb_viewer #endif // OPENVDB_VIEWER_CLIPBOX_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/Camera.h0000644000000000000000000000655112603226506014064 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #ifdef OPENVDB_USE_GLFW_3 struct GLFWwindow; // forward declaration #endif namespace openvdb_viewer { class Camera { public: Camera(); #ifdef OPENVDB_USE_GLFW_3 void setWindow(GLFWwindow* w) { mWindow = w; } #endif 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 = 0.1, double strafeSpeed = 0.002, double tumblingSpeed = 0.02); 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; #ifdef OPENVDB_USE_GLFW_3 GLFWwindow* mWindow; #endif static const double sDeg2rad; }; // class Camera } // namespace openvdb_viewer #endif // OPENVDB_VIEWER_CAMERA_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/Camera.cc0000644000000000000000000001625312603226506014222 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #ifdef OPENVDB_USE_GLFW_3 #define GLFW_INCLUDE_GLU #include #else // if !defined(OPENVDB_USE_GLFW_3) #if defined(__APPLE__) || defined(MACOSX) #include #include #else #include #include #endif #include #endif // !defined(OPENVDB_USE_GLFW_3) 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) #if GLFW_VERSION_MAJOR >= 3 , mWindow(NULL) #endif { } 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, mDistance * zoomSpeed); mStrafeSpeed = std::max(0.0001, mDistance * strafeSpeed); mTumblingSpeed = std::max(0.2, mDistance * tumblingSpeed); mTumblingSpeed = std::min(1.0, mDistance * tumblingSpeed); } void Camera::setTarget(const openvdb::Vec3d& p, double dist) { mTarget = p; mTargetDistance = dist; } void Camera::aim() { #if GLFW_VERSION_MAJOR >= 3 if (mWindow == NULL) return; #endif // Get the window size int width, height; #if GLFW_VERSION_MAJOR >= 3 glfwGetFramebufferSize(mWindow, &width, &height); #else glfwGetWindowSize(&width, &height); #endif // 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 GLFW_VERSION_MAJOR >= 3 if (mWindow == NULL) return; int state = glfwGetKey(mWindow, key); #else int state = glfwGetKey(key); #endif switch (state) { case GLFW_PRESS: switch(key) { case GLFW_KEY_SPACE: mZoomMode = true; break; } break; case GLFW_RELEASE: switch(key) { case GLFW_KEY_SPACE: mZoomMode = false; break; } 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; } else { double temp = mDistance - speed * mZoomSpeed; mDistance = std::max(0.0, temp); } setSpeed(); mChanged = true; mNeedsDisplay = true; } } // namespace openvdb_viewer // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/cmd/0000755000000000000000000000000012603226506011756 5ustar rootrootopenvdb/cmd/openvdb_print/0000755000000000000000000000000012603226506014627 5ustar rootrootopenvdb/cmd/openvdb_print/main.cc0000644000000000000000000002726112603226506016072 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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" << " -version print version information\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 << (double(n) / 1.0e3) << "K"; } else if (n < 1000000000) { ostr << (double(n) / 1.0e6) << "M"; } else { ostr << (double(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 << (double(n) / double(uint64_t(1) << 30)) << "GB"; } else if (n >> 20) { ostr << (double(n) / double(uint64_t(1) << 20)) << "MB"; } else if (n >> 10) { ostr << (double(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(); } 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 = meta->str(); 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=*/11); 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 = meta->str(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, int(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 = grid->str(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, version = 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 if (arg == "-version" || arg == "--version") { version = true; } else { std::cerr << gProgName << ": \"" << arg << "\" is not a valid option\n"; usage(); } } else if (!arg.empty()) { filenames.push_back(arg); } } if (version) { std::cout << "OpenVDB library version: " << openvdb::getLibraryVersionString() << "\n"; std::cout << "OpenVDB file format version: " << openvdb::OPENVDB_FILE_VERSION << std::endl; if (filenames.empty()) return EXIT_SUCCESS; } 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/cmd/openvdb_view/0000755000000000000000000000000012603226506014445 5ustar rootrootopenvdb/cmd/openvdb_view/main.cc0000644000000000000000000001777412603226506015720 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include // for boost::is_any_of() #include // for boost::starts_with() #include #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 information\n" << " -h, -help print this usage message and exit\n" << " -version print version information\n" << "\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, printGLInfo = false, printVersionInfo = 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") { // deprecated printGLInfo = true; } else if (str == "-h" || str == "-help" || str == "--help") { usage(progName, EXIT_SUCCESS); } else if (str == "-version" || str == "--version") { printVersionInfo = true; printGLInfo = true; } else { usage(progName, EXIT_FAILURE); } } const size_t numFiles = filenames.size(); if (printVersionInfo) { std::cout << "OpenVDB library version: " << openvdb::getLibraryVersionString() << "\n"; std::cout << "OpenVDB file format version: " << openvdb::OPENVDB_FILE_VERSION << std::endl; // If there are no files to view, don't print the OpenGL version, // since that would require opening a viewer window. if (numFiles == 0) return EXIT_SUCCESS; } if (numFiles == 0 && !printGLInfo) usage(progName, EXIT_FAILURE); openvdb_viewer::Viewer viewer = openvdb_viewer::init(progName, /*bg=*/false); if (printGLInfo) { // Now that the viewer window is open, we can get the OpenGL version, if requested. if (!printVersionInfo) { // Preserve the behavior of the deprecated -d option. std::cout << viewer.getVersionString() << std::endl; } else { // Print OpenGL and GLFW versions. std::ostringstream ostr; ostr << viewer.getVersionString(); // returns comma-separated list of versions const std::string s = ostr.str(); std::vector elems; boost::split(elems, s, boost::algorithm::is_any_of(",")); for (size_t i = 0; i < elems.size(); ++i) { boost::trim(elems[i]); // Don't print the OpenVDB library version again. if (!boost::starts_with(elems[i], "OpenVDB:")) { std::cout << elems[i] << std::endl; } } } if (numFiles == 0) return EXIT_SUCCESS; } 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.open(); viewer.view(allGrids); openvdb_viewer::exit(); } 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/cmd/openvdb_render/0000755000000000000000000000000012603226506014752 5ustar rootrootopenvdb/cmd/openvdb_render/main.cc0000644000000000000000000006713412603226506016220 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 = ""; const double LIGHT_DEFAULTS[] = { 0.3, 0.3, 0.0, 0.7, 0.7, 0.7 }; struct RenderOpts { std::string shader; std::string color; openvdb::Vec3SGrid::Ptr colorgrid; 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; std::vector 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.2136f), focal(50.0f), frame(1.0f), znear(1.0e-3f), 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), light(LIGHT_DEFAULTS, LIGHT_DEFAULTS + 6), 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; if (!color.empty()) os << " -color '" << color << "'"; os << " -compression " << compression << " -cpus " << threads << " -cutoff " << cutoff << " -far " << zfar << " -focal " << focal << " -frame " << frame << " -gain " << gain << " -isovalue " << isovalue << " -light " << light[0] << "," << light[1] << "," << light[2] << "," << light[3] << "," << light[4] << "," << light[5]; 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 double 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 volume to be rendered (default: render\n" << " the first floating-point volume 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 volume)\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" << " -version print version information and exit\n" << " -h, -help print this usage message and exit\n" << "\n" << "Level set options:\n" << " -color S name of a vec3s volume to be used to set material colors\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" << " -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[,R,G,B] light source direction and optional color\n" << " (default: [" << opts.light[0] << ", " << opts.light[1] << ", " << opts.light[2] << ", " << opts.light[3] << ", " << opts.light[4] << ", " << opts.light[5] << "])\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" << "Warning:\n" << " This is not (and is not intended to be) a production-quality renderer.\n" << " Use it for fast previewing or simply as a reference implementation\n" << " for integration into existing ray tracers.\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(int(film.width()), int(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(int(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); // Define the shader for level set rendering. The default shader is a diffuse shader. boost::scoped_ptr shader; if (opts.shader == "matte") { if (opts.colorgrid) { shader.reset(new tools::MatteShader(*opts.colorgrid)); } else { shader.reset(new tools::MatteShader<>()); } } else if (opts.shader == "normal") { if (opts.colorgrid) { shader.reset(new tools::NormalShader(*opts.colorgrid)); } else { shader.reset(new tools::NormalShader<>()); } } else if (opts.shader == "position") { const CoordBBox bbox = grid.evalActiveVoxelBoundingBox(); const math::BBox bboxIndex(bbox.min().asVec3d(), bbox.max().asVec3d()); const math::BBox bboxWorld = bboxIndex.applyMap(*(grid.transform().baseMap())); if (opts.colorgrid) { shader.reset(new tools::PositionShader(bboxWorld, *opts.colorgrid)); } else { shader.reset(new tools::PositionShader<>(bboxWorld)); } } else /* if (opts.shader == "diffuse") */ { // default if (opts.colorgrid) { shader.reset(new tools::DiffuseShader(*opts.colorgrid)); } else { shader.reset(new tools::DiffuseShader<>()); } } 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, static_cast(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.light[3], opts.light[4], opts.light[5]); 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 (int i = 1, N = std::min(3, int(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 = float(atof(argv[i])); } else if (parser.check(i, "-camera")) { ++i; opts.camera = argv[i]; } else if (parser.check(i, "-color")) { ++i; opts.color = 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 = float(atof(argv[i])); } else if (parser.check(i, "-focal")) { ++i; opts.focal = float(atof(argv[i])); hasFocal = true; } else if (parser.check(i, "-fov")) { ++i; fov = float(atof(argv[i])); hasFov = true; } else if (parser.check(i, "-frame")) { ++i; opts.frame = float(atof(argv[i])); } else if (parser.check(i, "-gain")) { ++i; opts.gain = atof(argv[i]); } else if (parser.check(i, "-light")) { ++i; opts.light = strToVec(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 = float(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 == "-version" || arg == "--version") { std::cout << "OpenVDB library version: " << openvdb::getLibraryVersionString() << "\n"; std::cout << "OpenVDB file format version: " << openvdb::OPENVDB_FILE_VERSION << std::endl; return EXIT_SUCCESS; } 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 = float( 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); if (!gridName.empty()) { file.open(); grid = openvdb::gridPtrCast(file.readGrid(gridName)); if (!grid) { OPENVDB_THROW(openvdb::ValueError, gridName + " is not a scalar, floating-point volume"); } } else { // If no grid was specified by name, retrieve the first float grid from the file. file.open(/*delayLoad=*/false); openvdb::io::File::NameIterator it = file.beginName(); openvdb::GridPtrVecPtr grids = file.readAllGridMetadata(); for (size_t i = 0; i < grids->size(); ++i, ++it) { grid = openvdb::gridPtrCast(grids->at(i)); if (grid) { gridName = *it; file.close(); file.open(); grid = openvdb::gridPtrCast(file.readGrid(gridName)); break; } } if (!grid) { OPENVDB_THROW(openvdb::ValueError, "no scalar, floating-point volumes in file " + vdbFilename); } } if (!opts.color.empty()) { opts.colorgrid = openvdb::gridPtrCast(file.readGrid(opts.color)); if (!opts.colorgrid) { OPENVDB_THROW(openvdb::ValueError, opts.color + " is not a vec3s color volume"); } } } 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Platform.cc0000644000000000000000000000365512603226506013317 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/openvdb.h0000644000000000000000000000736412603226506013033 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 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 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/openvdb.cc0000644000000000000000000001266112603226506013165 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #include #ifdef OPENVDB_USE_LOG4CPLUS #include #include #endif #ifdef OPENVDB_USE_BLOSC #include #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { typedef tbb::mutex Mutex; typedef Mutex::scoped_lock Lock; namespace { // Declare this at file scope to ensure thread-safe initialization. Mutex sInitMutex; bool sIsInitialized = false; } void initialize() { Lock lock(sInitMutex); if (sIsInitialized) return; #ifdef OPENVDB_USE_LOG4CPLUS { // Configure log4cplus if it was not already configured (at least, // if there are no appenders associated with the root logger). log4cplus::Logger rootLogger = log4cplus::Logger::getRoot(); if (rootLogger.getAllAppenders().empty()) { log4cplus::BasicConfigurator::doConfigure(); rootLogger.setLogLevel(log4cplus::WARN_LOG_LEVEL); // was DEBUG_LOG_LEVEL } } #endif // 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(); StringGrid::registerGrid(); Vec3IGrid::registerGrid(); Vec3SGrid::registerGrid(); Vec3DGrid::registerGrid(); // Register types associated with point index grids. Metadata::registerType(typeNameAsString(), Int32Metadata::createMetadata); Metadata::registerType(typeNameAsString(), Int64Metadata::createMetadata); tools::PointIndexGrid::registerGrid(); #ifdef OPENVDB_USE_BLOSC blosc_init(); if (blosc_set_compressor("lz4") < 0) { OPENVDB_LOG_WARN("Blosc LZ4 compressor is unavailable"); } /// @todo blosc_set_nthreads(int nthreads); #endif #ifdef __ICC // Disable ICC "assignment to statically allocated variable" warning. // This assignment is mutex-protected and therefore thread-safe. __pragma(warning(disable:1711)) #endif sIsInitialized = true; #ifdef __ICC __pragma(warning(default:1711)) #endif } void uninitialize() { Lock lock(sInitMutex); #ifdef __ICC // Disable ICC "assignment to statically allocated variable" warning. // This assignment is mutex-protected and therefore thread-safe. __pragma(warning(disable:1711)) #endif sIsInitialized = false; #ifdef __ICC __pragma(warning(default:1711)) #endif Metadata::clearRegistry(); GridBase::clearRegistry(); math::MapRegistry::clear(); #ifdef OPENVDB_USE_BLOSC // We don't want to destroy Blosc, because it might have been // initialized by some other library. //blosc_destroy(); #endif } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/0000755000000000000000000000000012603226506013072 5ustar rootrootopenvdb/unittest/TestFloatMetadata.cc0000644000000000000000000000562212603226506016754 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTransform.cc0000644000000000000000000004362312603226506016224 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include class TestTransform: public CppUnit::TestCase { public: virtual void setUp(); virtual void tearDown(); CPPUNIT_TEST_SUITE(TestTransform); CPPUNIT_TEST(testLinearTransform); CPPUNIT_TEST(testTransformEquality); CPPUNIT_TEST(testBackwardCompatibility); CPPUNIT_TEST(testIsIdentity); CPPUNIT_TEST(testBoundingBoxes); CPPUNIT_TEST_SUITE_END(); void testLinearTransform(); void testTransformEquality(); void testBackwardCompatibility(); void testIsIdentity(); void testBoundingBoxes(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTransform); //////////////////////////////////////// void TestTransform::setUp() { openvdb::math::MapRegistry::clear(); openvdb::math::AffineMap::registerMap(); openvdb::math::ScaleMap::registerMap(); openvdb::math::UniformScaleMap::registerMap(); openvdb::math::TranslationMap::registerMap(); openvdb::math::ScaleTranslateMap::registerMap(); openvdb::math::UniformScaleTranslateMap::registerMap(); } void TestTransform::tearDown() { openvdb::math::MapRegistry::clear(); } ////openvdb:://////////////////////////////////// void TestTransform::testLinearTransform() { using namespace openvdb; double TOL = 1e-7; // Test: Scaling math::Transform::Ptr t = math::Transform::createLinearTransform(0.5); Vec3R voxelSize = t->voxelSize(); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[2], TOL); CPPUNIT_ASSERT(t->hasUniformScale()); // world to index space Vec3R xyz(-1.0, 2.0, 4.0); xyz = t->worldToIndex(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(-2.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 4.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 8.0, xyz[2], TOL); xyz = Vec3R(-0.7, 2.4, 4.7); // cell centered conversion Coord ijk = t->worldToIndexCellCentered(xyz); CPPUNIT_ASSERT_EQUAL(Coord(-1, 5, 9), ijk); // node centrered conversion ijk = t->worldToIndexNodeCentered(xyz); CPPUNIT_ASSERT_EQUAL(Coord(-2, 4, 9), ijk); // index to world space ijk = Coord(4, 2, -8); xyz = t->indexToWorld(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL( 2.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(-4.0, xyz[2], TOL); // I/O test { std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); t->write(ss); t = math::Transform::createLinearTransform(); // Since we wrote only a fragment of a VDB file (in particular, we didn't // write the header), set the file format version number explicitly. io::setCurrentVersion(ss); t->read(ss); } // check map type CPPUNIT_ASSERT_EQUAL(math::UniformScaleMap::mapType(), t->baseMap()->type()); voxelSize = t->voxelSize(); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[2], TOL); ////////// // Test: Scale, translation & rotation t = math::Transform::createLinearTransform(2.0); // rotate, 180 deg, (produces a diagonal matrix that can be simplified into a scale map) // with diagonal -2, 2, -2 const double PI = std::atan(1.0)*4; t->preRotate(PI, math::Y_AXIS); // this is just a rotation so it will have uniform scale CPPUNIT_ASSERT(t->hasUniformScale()); CPPUNIT_ASSERT_EQUAL(math::ScaleMap::mapType(), t->baseMap()->type()); voxelSize = t->voxelSize(); xyz = t->worldToIndex(Vec3R(-2.0, -2.0, -2.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[2], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[2], TOL); // translate t->postTranslate(Vec3d(1.0, 0.0, 1.0)); CPPUNIT_ASSERT_EQUAL(math::ScaleTranslateMap::mapType(), t->baseMap()->type()); voxelSize = t->voxelSize(); xyz = t->worldToIndex(Vec3R(-2.0, -2.0, -2.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[2], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[2], TOL); // I/O test { std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); t->write(ss); t = math::Transform::createLinearTransform(); // Since we wrote only a fragment of a VDB file (in particular, we didn't // write the header), set the file format version number explicitly. io::setCurrentVersion(ss); t->read(ss); } // check map type CPPUNIT_ASSERT_EQUAL(math::ScaleTranslateMap::mapType(), t->baseMap()->type()); voxelSize = t->voxelSize(); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[2], TOL); xyz = t->worldToIndex(Vec3R(-2.0, -2.0, -2.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[2], TOL); // new transform t = math::Transform::createLinearTransform(1.0); // rotate 90 deg t->preRotate( std::atan(1.0) * 2 , math::Y_AXIS); // check map type CPPUNIT_ASSERT_EQUAL(math::AffineMap::mapType(), t->baseMap()->type()); xyz = t->worldToIndex(Vec3R(1.0, 1.0, 1.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[2], TOL); // I/O test { std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); t->write(ss); t = math::Transform::createLinearTransform(); CPPUNIT_ASSERT_EQUAL(math::UniformScaleMap::mapType(), t->baseMap()->type()); xyz = t->worldToIndex(Vec3R(1.0, 1.0, 1.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, xyz[2], TOL); // Since we wrote only a fragment of a VDB file (in particular, we didn't // write the header), set the file format version number explicitly. io::setCurrentVersion(ss); t->read(ss); } // check map type CPPUNIT_ASSERT_EQUAL(math::AffineMap::mapType(), t->baseMap()->type()); xyz = t->worldToIndex(Vec3R(1.0, 1.0, 1.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[2], TOL); } //////////////////////////////////////// void TestTransform::testTransformEquality() { using namespace openvdb; // maps created in different ways may be equivalent math::Transform::Ptr t1 = math::Transform::createLinearTransform(0.5); math::Mat4d mat = math::Mat4d::identity(); mat.preScale(math::Vec3d(0.5, 0.5, 0.5)); math::Transform::Ptr t2 = math::Transform::createLinearTransform(mat); CPPUNIT_ASSERT( *t1 == *t2); // test that the auto-convert to the simplest form worked CPPUNIT_ASSERT( t1->mapType() == t2->mapType()); mat.preScale(math::Vec3d(1., 1., .4)); math::Transform::Ptr t3 = math::Transform::createLinearTransform(mat); CPPUNIT_ASSERT( *t1 != *t3); // test equality between different but equivalent maps math::UniformScaleTranslateMap::Ptr ustmap( new math::UniformScaleTranslateMap(1.0, math::Vec3d(0,0,0))); math::Transform::Ptr t4( new math::Transform( ustmap) ); CPPUNIT_ASSERT( t4->baseMap()->isType() ); math::Transform::Ptr t5( new math::Transform); // constructs with a scale map CPPUNIT_ASSERT( t5->baseMap()->isType() ); CPPUNIT_ASSERT( *t5 == *t4); CPPUNIT_ASSERT( t5->mapType() != t4->mapType() ); // test inequatlity of two maps of the same type math::UniformScaleTranslateMap::Ptr ustmap2( new math::UniformScaleTranslateMap(1.0, math::Vec3d(1,0,0))); math::Transform::Ptr t6( new math::Transform( ustmap2) ); CPPUNIT_ASSERT( t6->baseMap()->isType() ); CPPUNIT_ASSERT( *t6 != *t4); // test comparison of linear to nonlinear map openvdb::BBoxd bbox(math::Vec3d(0), math::Vec3d(100)); math::Transform::Ptr frustum = math::Transform::createFrustumTransform(bbox, 0.25, 10); CPPUNIT_ASSERT( *frustum != *t1 ); } //////////////////////////////////////// void TestTransform::testBackwardCompatibility() { using namespace openvdb; double TOL = 1e-7; // Register maps math::MapRegistry::clear(); math::AffineMap::registerMap(); math::ScaleMap::registerMap(); math::TranslationMap::registerMap(); math::ScaleTranslateMap::registerMap(); std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); ////////// // Construct and write out an old transform that gets converted // into a ScaleMap on read. // First write the old transform type name writeString(ss, Name("LinearTransform")); // Second write the old transform's base class membes. Coord tmpMin(0), tmpMax(1); ss.write(reinterpret_cast(&tmpMin), sizeof(Coord::ValueType) * 3); ss.write(reinterpret_cast(&tmpMax), sizeof(Coord::ValueType) * 3); // Last write out the old linear transform's members math::Mat4d tmpLocalToWorld = math::Mat4d::identity(), tmpWorldToLocal = math::Mat4d::identity(), tmpVoxelToLocal = math::Mat4d::identity(), tmpLocalToVoxel = math::Mat4d::identity(); tmpVoxelToLocal.preScale(math::Vec3d(0.5, 0.5, 0.5)); tmpLocalToWorld.write(ss); tmpWorldToLocal.write(ss); tmpVoxelToLocal.write(ss); tmpLocalToVoxel.write(ss); // Read in the old transform and converting it to the new map based implementation. math::Transform::Ptr t = math::Transform::createLinearTransform(1.0); t->read(ss); // check map type CPPUNIT_ASSERT_EQUAL(math::UniformScaleMap::mapType(), t->baseMap()->type()); Vec3d voxelSize = t->voxelSize(); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[2], TOL); Vec3d xyz = t->worldToIndex(Vec3d(-1.0, 2.0, 4.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-2.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 4.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 8.0, xyz[2], TOL); ////////// // Construct and write out an old transform that gets converted // into a ScaleTranslateMap on read. ss.clear(); writeString(ss, Name("LinearTransform")); ss.write((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()); } void TestTransform::testBoundingBoxes() { using namespace openvdb; { math::Transform::ConstPtr t = math::Transform::createLinearTransform(0.5); const BBoxd bbox(Vec3d(-8.0), Vec3d(16.0)); BBoxd xBBox = t->indexToWorld(bbox); CPPUNIT_ASSERT_EQUAL(Vec3d(-4.0), xBBox.min()); CPPUNIT_ASSERT_EQUAL(Vec3d(8.0), xBBox.max()); xBBox = t->worldToIndex(xBBox); CPPUNIT_ASSERT_EQUAL(bbox.min(), xBBox.min()); CPPUNIT_ASSERT_EQUAL(bbox.max(), xBBox.max()); } { const double PI = std::atan(1.0) * 4.0, SQRT2 = std::sqrt(2.0); math::Transform::Ptr t = math::Transform::createLinearTransform(1.0); t->preRotate(PI / 4.0, math::Z_AXIS); const BBoxd bbox(Vec3d(-10.0), Vec3d(10.0)); BBoxd xBBox = t->indexToWorld(bbox); // expand in x and y by sqrt(2) CPPUNIT_ASSERT(Vec3d(-10.0 * SQRT2, -10.0 * SQRT2, -10.0).eq(xBBox.min())); CPPUNIT_ASSERT(Vec3d(10.0 * SQRT2, 10.0 * SQRT2, 10.0).eq(xBBox.max())); xBBox = t->worldToIndex(xBBox); // expand again in x and y by sqrt(2) CPPUNIT_ASSERT(Vec3d(-20.0, -20.0, -10.0).eq(xBBox.min())); CPPUNIT_ASSERT(Vec3d(20.0, 20.0, 10.0).eq(xBBox.max())); } /// @todo frustum transform } //////////////////////////////////////// /// @todo Test the new frustum transform. /* void TestTransform::testNonlinearTransform() { using namespace openvdb; double TOL = 1e-7; } */ // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestPrePostAPI.cc0000644000000000000000000005722212603226506016177 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestInit.cc0000644000000000000000000001165612603226506015155 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestFile.cc0000644000000000000000000025247312603226506015135 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 // for remove() and rename() #include #include #include #include #include #include // for stat() #include #ifndef _WIN32 #include #endif #ifdef OPENVDB_USE_BLOSC #include #include // for memset() #endif class TestFile: public CppUnit::TestCase { public: 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); #ifndef OPENVDB_2_ABI_COMPATIBLE CPPUNIT_TEST(testReadClippedGrid); #endif CPPUNIT_TEST(testMultipleBufferIO); CPPUNIT_TEST(testHasGrid); CPPUNIT_TEST(testNameIterator); CPPUNIT_TEST(testReadOldFileFormat); CPPUNIT_TEST(testCompression); CPPUNIT_TEST(testAsync); #ifdef OPENVDB_USE_BLOSC CPPUNIT_TEST(testBlosc); #endif CPPUNIT_TEST_SUITE_END(); void testHeader(); void testWriteGrid(); void testWriteMultipleGrids(); void testWriteFloatAsHalf(); void testWriteInstancedGrids(); void testReadGridDescriptors(); void testGridNaming(); void testEmptyFile(); void testEmptyGridIO(); void testOpen(); void testNonVdbOpen(); void testGetMetadata(); void testReadAll(); void testWriteOpenFile(); void testReadGridMetadata(); void testReadGridPartial(); void testReadGrid(); #ifndef OPENVDB_2_ABI_COMPATIBLE void testReadClippedGrid(); #endif void testMultipleBufferIO(); void testHasGrid(); void testNameIterator(); void testReadOldFileFormat(); void testCompression(); void testAsync(); #ifdef OPENVDB_USE_BLOSC void testBlosc(); #endif }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFile); //////////////////////////////////////// void TestFile::testHeader() { using namespace openvdb::io; File file("something.vdb2"); std::ostringstream ostr(std::ios_base::binary); 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(float(x), float(y), float(z))); tree2.setValue(Coord(x, y, z), Vec3s(float(x), float(y), float(z))); } } } GridPtrVec grids; grids.push_back(grid1); grids.push_back(grid2); const char* filename = "something.vdb2"; { // Write both grids to a file. File vdbFile(filename); vdbFile.write(grids); } { // Verify that both grids can be read back successfully from the file. File vdbFile(filename); vdbFile.open(); GridBase::Ptr bgrid1 = vdbFile.readGrid("grid1"), bgrid2 = vdbFile.readGrid("grid2"); vdbFile.close(); CPPUNIT_ASSERT(bgrid1.get() != 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(); // Remove something.vdb2 when done. We must declare this here before the // other grid smart_ptr's because we re-use them in the test several times. // We will not be able to remove something.vdb2 on Windows if the pointers // are still referencing data opened by the "file" variable. const char* filename = "something.vdb2"; boost::shared_ptr scopedFile(filename, ::remove); // Create grids. Int32Tree::Ptr tree1(new Int32Tree(1)); FloatTree::Ptr tree2(new FloatTree(2.0)); GridBase::Ptr grid1 = createGrid(tree1), grid2 = createGrid(tree1), // instance of grid1 grid3 = createGrid(tree2), grid4 = createGrid(tree2); // instance of grid3 grid1->setName("density"); grid2->setName("density_copy"); // Leave grid3 and grid4 unnamed. // Create transforms. math::Transform::Ptr trans1 = math::Transform::createLinearTransform(0.1); math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); grid1->setTransform(trans1); grid2->setTransform(trans2); grid3->setTransform(trans2); grid4->setTransform(trans1); // Set some values. tree1->setValue(Coord(0, 0, 0), 5); tree1->setValue(Coord(100, 0, 0), 6); tree2->setValue(Coord(0, 0, 0), 10); tree2->setValue(Coord(0, 100, 0), 11); MetaMap::Ptr meta(new MetaMap); meta->insertMeta("author", StringMetadata("Einstein")); meta->insertMeta("year", Int32Metadata(2009)); GridPtrVecPtr grids(new GridPtrVec); grids->push_back(grid1); grids->push_back(grid2); grids->push_back(grid3); grids->push_back(grid4); // Write the grids to a file and then close the file. { io::File vdbFile(filename); vdbFile.write(*grids, *meta); } meta.reset(); // Read the grids back in. io::File file(filename); file.open(); grids = file.getGrids(); meta = file.getMetadata(); // Verify the metadata. CPPUNIT_ASSERT(meta.get() != 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(); { /// @todo (FX-7063) For now, write to a new file, then, when there's /// no longer a need for delayed load from the old file, replace it /// with the new file. const char* tempFilename = "somethingelse.vdb"; boost::shared_ptr scopedTempFile(tempFilename, ::remove); io::File vdbFile(tempFilename); vdbFile.setInstancingEnabled(false); vdbFile.write(*grids, *meta); grids.reset(); // Note: Windows requires that the destination not exist, before we can rename to it. std::remove(filename); std::rename(tempFilename, filename); } file.setInstancingEnabled(true); file.open(); grids = file.getGrids(); CPPUNIT_ASSERT_EQUAL(4, int(grids->size())); // Verify that "density_copy" is not an instance of "density". grid = findGridByName(*grids, "density"); CPPUNIT_ASSERT(grid.get() != 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.gridDescriptors().end()); GridDescriptor file2gd = it->second; CPPUNIT_ASSERT_EQUAL(gd.gridName(), file2gd.gridName()); CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), file2gd.getGridPos()); CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), file2gd.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), file2gd.getEndPos()); it = file2.findDescriptor("density"); CPPUNIT_ASSERT(it != file2.gridDescriptors().end()); file2gd = it->second; CPPUNIT_ASSERT_EQUAL(gd2.gridName(), file2gd.gridName()); CPPUNIT_ASSERT_EQUAL(gd2.getGridPos(), file2gd.getGridPos()); CPPUNIT_ASSERT_EQUAL(gd2.getBlockPos(), file2gd.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd2.getEndPos(), file2gd.getEndPos()); // Clear registries. GridBase::clearRegistry(); math::MapRegistry::clear(); remove("something.vdb2"); } void TestFile::testGridNaming() { using namespace openvdb; using namespace openvdb::io; typedef Int32Tree TreeType; // 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 = "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 = "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_EQUAL(0, int(meta->metaCount())); } void TestFile::testEmptyGridIO() { using namespace openvdb; using namespace openvdb::io; typedef Int32Grid GridType; const char* filename = "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.gridDescriptors().end()); GridDescriptor file2gd = it->second; file2gd.seekToGrid(istr); GridBase::Ptr gd_grid = GridBase::createGrid(file2gd.gridType()); Archive::readGridCompression(istr); gd_grid->readMeta(istr); gd_grid->readTransform(istr); gd_grid->readTopology(istr); CPPUNIT_ASSERT_EQUAL(gd.gridName(), file2gd.gridName()); CPPUNIT_ASSERT(gd_grid.get() != 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.gridDescriptors().end()); file2gd = it->second; file2gd.seekToGrid(istr); gd_grid = GridBase::createGrid(file2gd.gridType()); Archive::readGridCompression(istr); gd_grid->readMeta(istr); gd_grid->readTransform(istr); gd_grid->readTopology(istr); CPPUNIT_ASSERT_EQUAL(gd2.gridName(), file2gd.gridName()); CPPUNIT_ASSERT(gd_grid.get() != 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 closing. 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.inputStream())); CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MAJOR_VERSION, vdbfile.libraryVersion().first); CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MINOR_VERSION, vdbfile.libraryVersion().second); CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MAJOR_VERSION, io::getLibraryVersion(vdbfile.inputStream()).first); CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MINOR_VERSION, io::getLibraryVersion(vdbfile.inputStream()).second); // Ensure that we read in the vdb metadata. CPPUNIT_ASSERT(vdbfile.getMetadata()); CPPUNIT_ASSERT(vdbfile.getMetadata()->metaValue("author") == "Einstein"); CPPUNIT_ASSERT_EQUAL(2009, vdbfile.getMetadata()->metaValue("year")); // Ensure we got the grid descriptors. CPPUNIT_ASSERT_EQUAL(1, int(vdbfile.gridDescriptors().count("density"))); CPPUNIT_ASSERT_EQUAL(1, int(vdbfile.gridDescriptors().count("temperature"))); io::File::NameMapCIter it = vdbfile.findDescriptor("density"); CPPUNIT_ASSERT(it != vdbfile.gridDescriptors().end()); io::GridDescriptor gd = it->second; CPPUNIT_ASSERT_EQUAL(IntTree::treeType(), gd.gridType()); it = vdbfile.findDescriptor("temperature"); CPPUNIT_ASSERT(it != vdbfile.gridDescriptors().end()); gd = it->second; CPPUNIT_ASSERT_EQUAL(FloatTree::treeType(), gd.gridType()); // Ensure we throw an error if there is no file. io::File vdbfile2("somethingelses.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile2.open(), openvdb::IoError); CPPUNIT_ASSERT_THROW(vdbfile2.inputStream(), openvdb::IoError); // Clear registries. GridBase::clearRegistry(); Metadata::clearRegistry(); math::MapRegistry::clear(); // Test closing the file. vdbfile.close(); CPPUNIT_ASSERT(vdbfile.isOpen() == false); CPPUNIT_ASSERT(vdbfile.fileMetadata().get() == NULL); CPPUNIT_ASSERT_EQUAL(0, int(vdbfile.gridDescriptors().size())); CPPUNIT_ASSERT_THROW(vdbfile.inputStream(), openvdb::IoError); remove("something.vdb2"); } void TestFile::testNonVdbOpen() { std::ofstream file("dummy.vdb2", std::ios_base::binary); int64_t something = 1; file.write((char*)&something, sizeof(int64_t)); file.close(); openvdb::io::File vdbfile("dummy.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile.open(), openvdb::IoError); CPPUNIT_ASSERT_THROW(vdbfile.inputStream(), openvdb::IoError); remove("dummy.vdb2"); } void TestFile::testGetMetadata() { using namespace openvdb; GridPtrVec grids; MetaMap meta; meta.insertMeta("author", StringMetadata("Einstein")); meta.insertMeta("year", Int32Metadata(2009)); // Adjust registry before writing. Metadata::clearRegistry(); StringMetadata::registerType(); Int32Metadata::registerType(); // Write the vdb out to a file. io::File vdbfile("something.vdb2"); vdbfile.write(grids, meta); // Check if reading without opening the file CPPUNIT_ASSERT_THROW(vdbfile.getMetadata(), openvdb::IoError); vdbfile.open(); MetaMap::Ptr meta2 = vdbfile.getMetadata(); CPPUNIT_ASSERT_EQUAL(2, int(meta2->metaCount())); CPPUNIT_ASSERT(meta2->metaValue("author") == "Einstein"); CPPUNIT_ASSERT_EQUAL(2009, meta2->metaValue("year")); // Clear registry. Metadata::clearRegistry(); remove("something.vdb2"); } void TestFile::testReadAll() { using namespace openvdb; 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 = "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->metaCount() != 0); statsMetadata->insertMeta(GridBase::META_FILE_COMPRESSION, StringMetadata("")); for (MetaMap::ConstMetaIterator it = grid->beginMeta(), end = grid->endMeta(); it != end; ++it) { // Keep all fields that exist in the source grid. if ((*srcGrid)[it->first]) continue; // Remove any remaining grid statistics fields. if ((*statsMetadata)[it->first]) { otherMetadata->removeMeta(it->first); } } CPPUNIT_ASSERT_EQUAL(srcGrid->str(), otherMetadata->str()); const CoordBBox srcBBox = srcGrid->evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT_EQUAL(srcBBox.min().asVec3i(), grid->metaValue("file_bbox_min")); CPPUNIT_ASSERT_EQUAL(srcBBox.max().asVec3i(), grid->metaValue("file_bbox_max")); CPPUNIT_ASSERT_EQUAL(srcGrid->activeVoxelCount(), Index64(grid->metaValue("file_voxel_count"))); CPPUNIT_ASSERT_EQUAL(srcGrid->memUsage(), Index64(grid->metaValue("file_mem_bytes"))); } } } void TestFile::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"); } //////////////////////////////////////// #ifndef OPENVDB_2_ABI_COMPATIBLE template void validateClippedGrid(const GridT& clipped, const typename GridT::ValueType& fg) { using namespace openvdb; typedef typename GridT::ValueType ValueT; const CoordBBox bbox = clipped.evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT_EQUAL(4, bbox.min().x()); CPPUNIT_ASSERT_EQUAL(4, bbox.min().y()); CPPUNIT_ASSERT_EQUAL(-6, bbox.min().z()); CPPUNIT_ASSERT_EQUAL(4, bbox.max().x()); CPPUNIT_ASSERT_EQUAL(4, bbox.max().y()); CPPUNIT_ASSERT_EQUAL(6, bbox.max().z()); CPPUNIT_ASSERT_EQUAL(6 + 6 + 1, int(clipped.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(2, int(clipped.constTree().leafCount())); typename GridT::ConstAccessor acc = clipped.getConstAccessor(); const ValueT bg = clipped.background(); Coord xyz; int &x = xyz[0], &y = xyz[1], &z = xyz[2]; for (x = -10; x <= 10; ++x) { for (y = -10; y <= 10; ++y) { for (z = -10; z <= 10; ++z) { if (x == 4 && y == 4 && z >= -6 && z <= 6) { CPPUNIT_ASSERT_EQUAL(fg, acc.getValue(Coord(4, 4, z))); } else { CPPUNIT_ASSERT_EQUAL(bg, acc.getValue(Coord(x, y, z))); } } } } } // See also TestGrid::testClipping() void TestFile::testReadClippedGrid() { using namespace openvdb; // Register types. openvdb::initialize(); // World-space clipping region const BBoxd clipBox(Vec3d(4.0, 4.0, -6.0), Vec3d(4.9, 4.9, 6.0)); // Create grids of several types and fill a cubic region of each with a foreground value. const bool bfg = true; BoolGrid::Ptr bgrid = BoolGrid::create(/*bg=*/zeroVal()); bgrid->setName("bgrid"); bgrid->fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/bfg, /*active=*/true); const float ffg = 5.f; FloatGrid::Ptr fgrid = FloatGrid::create(/*bg=*/zeroVal()); fgrid->setName("fgrid"); fgrid->fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/ffg, /*active=*/true); const Vec3s vfg(1.f, -2.f, 3.f); Vec3SGrid::Ptr vgrid = Vec3SGrid::create(/*bg=*/zeroVal()); vgrid->setName("vgrid"); vgrid->fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/vfg, /*active=*/true); GridPtrVec srcGrids; srcGrids.push_back(bgrid); srcGrids.push_back(fgrid); srcGrids.push_back(vgrid); const char* filename = "testReadClippedGrid.vdb"; boost::shared_ptr scopedFile(filename, ::remove); enum { OUTPUT_TO_FILE = 0, OUTPUT_TO_STREAM = 1 }; for (int outputMethod = OUTPUT_TO_FILE; outputMethod <= OUTPUT_TO_STREAM; ++outputMethod) { if (outputMethod == OUTPUT_TO_FILE) { // Write the grids to a file. io::File vdbfile(filename); vdbfile.write(srcGrids); } else { // Stream the grids to a file (i.e., without file offsets). std::ofstream ostrm(filename, std::ios_base::binary); io::Stream(ostrm).write(srcGrids); } // Open the file for reading. io::File vdbfile(filename); vdbfile.open(); GridBase::Ptr grid; // Read and clip each grid. CPPUNIT_ASSERT_NO_THROW(grid = vdbfile.readGrid("bgrid", clipBox)); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_NO_THROW(bgrid = gridPtrCast(grid)); validateClippedGrid(*bgrid, bfg); CPPUNIT_ASSERT_NO_THROW(grid = vdbfile.readGrid("fgrid", clipBox)); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_NO_THROW(fgrid = gridPtrCast(grid)); validateClippedGrid(*fgrid, ffg); CPPUNIT_ASSERT_NO_THROW(grid = vdbfile.readGrid("vgrid", clipBox)); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_NO_THROW(vgrid = gridPtrCast(grid)); validateClippedGrid(*vgrid, vfg); } } #endif // !defined(OPENVDB_2_ABI_COMPATIBLE) //////////////////////////////////////// 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 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 = "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[0]", "level_set[1]", "temperature" }; int count = 0; for (io::File::NameIterator iter = vdbfile.beginName(); iter != vdbfile.endName(); ++iter) { CPPUNIT_ASSERT_EQUAL(names[count], *iter); CPPUNIT_ASSERT_EQUAL(names[count], iter.gridName()); ++count; grid = vdbfile.readGrid(*iter); CPPUNIT_ASSERT(grid); } CPPUNIT_ASSERT_EQUAL(6, count); vdbfile.close(); } void TestFile::testReadOldFileFormat() { /// @todo Save some old-format (prior to OPENVDB_FILE_VERSION) .vdb2 files /// to /work/rd/fx_tools/vdb_unittest/TestFile::testReadOldFileFormat/ /// Verify that the files can still be read correctly. } void TestFile::testCompression() { using namespace openvdb; using namespace openvdb::io; 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 = "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.setCompression(io::COMPRESS_NONE); vdbfile.write(grids); vdbfile.close(); // Get the size of the file in bytes. struct stat buf; buf.st_size = 0; CPPUNIT_ASSERT_EQUAL(0, ::stat(filename, &buf)); uncompressedSize = buf.st_size; } // Write the grids out with various combinations of compression options // and verify that they can be read back successfully. // Currently, only bits 0 and 1 have meaning as compression flags // (see io/Compression.h), so the valid combinations range from 0x0 to 0x3. for (uint32_t flags = 0x0; flags <= 0x3; ++flags) { if (flags != io::COMPRESS_NONE) { io::File vdbfile(filename); vdbfile.setCompression(flags); vdbfile.write(grids); vdbfile.close(); } if (flags != io::COMPRESS_NONE) { // Verify that the compressed file is significantly smaller than // the uncompressed file. size_t compressedSize = 0; struct stat buf; buf.st_size = 0; CPPUNIT_ASSERT_EQUAL(0, ::stat(filename, &buf)); compressedSize = buf.st_size; CPPUNIT_ASSERT(compressedSize < size_t(0.75 * double(uncompressedSize))); } { // Verify that the grids can be read back successfully. io::File vdbfile(filename); vdbfile.open(); GridPtrVecPtr inGrids = vdbfile.getGrids(); CPPUNIT_ASSERT_EQUAL(3, int(inGrids->size())); // Verify that the original and input grids are equal. { const IntGrid::Ptr grid = gridPtrCast((*inGrids)[0]); CPPUNIT_ASSERT(grid.get() != 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; // Register types. openvdb::initialize(); // Create a grid. FloatGrid::Ptr lsGrid = createLevelSet(); unittest_util::makeSphere(/*dim=*/Coord(100), /*ctr=*/Vec3f(50, 50, 50), /*r=*/20.0, *lsGrid, unittest_util::SPHERE_SPARSE_NARROW_BAND); MetaMap fileMetadata; fileMetadata.insertMeta("author", StringMetadata("Einstein")); fileMetadata.insertMeta("year", Int32Metadata(2013)); GridPtrVec grids; grids.push_back(lsGrid); grids.push_back(lsGrid->deepCopy()); grids.push_back(lsGrid->deepCopy()); size_t refFileSize = 0; { // Write a reference file without using asynchronous I/O. const char* filename = "testAsyncref.vdb"; 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 << "testAsync." << i << ".vdb"; const std::string filename = ostr.str(); io::Queue::Id id = queue.write(grids, io::File(filename), fileMetadata); helper.insert(id, filename); } tbb::tick_count start = tbb::tick_count::now(); while (!helper.ids.empty()) { if ((tbb::tick_count::now() - start).seconds() > 60) break; // time out after 1 minute // Wait one second for tasks to complete. tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(1.0/*sec*/)); // Poll each task in the pending map. std::set ids = helper.ids; // iterate over a copy for (std::set::iterator it = ids.begin(); it != ids.end(); ++it) { const io::Queue::Id id = *it; const io::Queue::Status status = queue.status(id); helper.validate(id, status); } } CPPUNIT_ASSERT(helper.ids.empty()); CPPUNIT_ASSERT(queue.empty()); } { // Output multiple files using asynchronous I/O. // Use notifications to get the status of the I/O tasks. TestAsyncHelper helper(refFileSize); io::Queue queue(/*capacity=*/2); queue.addNotifier(helper.notifier()); for (int i = 1; i < 10; ++i) { std::ostringstream ostr; ostr << "testAsync" << i << ".vdb"; const std::string filename = ostr.str(); io::Queue::Id id = queue.write(grids, io::File(filename), fileMetadata); helper.insert(id, filename); } while (!queue.empty()) { tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(1.0/*sec*/)); } } { // Test queue timeout. io::Queue queue(/*capacity=*/1); queue.setTimeout(0/*sec*/); boost::shared_ptr scopedFile1("testAsyncIOa.vdb", ::remove), scopedFile2("testAsyncIOb.vdb", ::remove); std::ofstream file1(scopedFile1.get()), file2(scopedFile2.get()); queue.write(grids, io::Stream(file1)); // With the queue length restricted to 1 and the timeout to 0 seconds, // the next write() call should time out immediately with an exception. // (It is possible, though highly unlikely, for the previous task to complete // in time for this write() to actually succeed.) CPPUNIT_ASSERT_THROW(queue.write(grids, io::Stream(file2)), openvdb::RuntimeError); } } #ifdef OPENVDB_USE_BLOSC // This tests for a data corruption bug that existed in versions of Blosc prior to 1.5.0 // (see https://github.com/Blosc/c-blosc/pull/63). void TestFile::testBlosc() { openvdb::initialize(); const unsigned char rawdata[] = { 0x93, 0xb0, 0x49, 0xaf, 0x62, 0xad, 0xe3, 0xaa, 0xe4, 0xa5, 0x43, 0x20, 0x24, 0x29, 0xc9, 0xaf, 0xee, 0xad, 0x0b, 0xac, 0x3d, 0xa8, 0x1f, 0x99, 0x53, 0x27, 0xb6, 0x2b, 0x16, 0xb0, 0x5f, 0xae, 0x89, 0xac, 0x51, 0xa9, 0xfc, 0xa1, 0xc9, 0x24, 0x59, 0x2a, 0x2f, 0x2d, 0xb4, 0xae, 0xeb, 0xac, 0x2f, 0xaa, 0xec, 0xa4, 0x53, 0x21, 0x31, 0x29, 0x8f, 0x2c, 0x8e, 0x2e, 0x31, 0xad, 0xd6, 0xaa, 0x6d, 0xa6, 0xad, 0x1b, 0x3e, 0x28, 0x0a, 0x2c, 0xfd, 0x2d, 0xf8, 0x2f, 0x45, 0xab, 0x81, 0xa7, 0x1f, 0x95, 0x02, 0x27, 0x3d, 0x2b, 0x85, 0x2d, 0x75, 0x2f, 0xb6, 0x30, 0x13, 0xa8, 0xb2, 0x9c, 0xf3, 0x25, 0x9c, 0x2a, 0x28, 0x2d, 0x0b, 0x2f, 0x7b, 0x30, 0x68, 0x9e, 0x51, 0x25, 0x31, 0x2a, 0xe6, 0x2c, 0xbc, 0x2e, 0x4e, 0x30, 0x5a, 0xb0, 0xe6, 0xae, 0x0e, 0xad, 0x59, 0xaa, 0x08, 0xa5, 0x89, 0x21, 0x59, 0x29, 0xb0, 0x2c, 0x57, 0xaf, 0x8c, 0xad, 0x6f, 0xab, 0x65, 0xa7, 0xd3, 0x12, 0xf5, 0x27, 0xeb, 0x2b, 0xf6, 0x2d, 0xee, 0xad, 0x27, 0xac, 0xab, 0xa8, 0xb1, 0x9f, 0xa2, 0x25, 0xaa, 0x2a, 0x4a, 0x2d, 0x47, 0x2f, 0x7b, 0xac, 0x6d, 0xa9, 0x45, 0xa3, 0x73, 0x23, 0x9d, 0x29, 0xb7, 0x2c, 0xa8, 0x2e, 0x51, 0x30, 0xf7, 0xa9, 0xec, 0xa4, 0x79, 0x20, 0xc5, 0x28, 0x3f, 0x2c, 0x24, 0x2e, 0x09, 0x30, 0xc8, 0xa5, 0xb1, 0x1c, 0x23, 0x28, 0xc3, 0x2b, 0xba, 0x2d, 0x9c, 0x2f, 0xc3, 0x30, 0x44, 0x18, 0x6e, 0x27, 0x3d, 0x2b, 0x6b, 0x2d, 0x40, 0x2f, 0x8f, 0x30, 0x02, 0x27, 0xed, 0x2a, 0x36, 0x2d, 0xfe, 0x2e, 0x68, 0x30, 0x66, 0xae, 0x9e, 0xac, 0x96, 0xa9, 0x7c, 0xa3, 0xa9, 0x23, 0xc5, 0x29, 0xd8, 0x2c, 0xd7, 0x2e, 0x0e, 0xad, 0x90, 0xaa, 0xe4, 0xa5, 0xf8, 0x1d, 0x82, 0x28, 0x2b, 0x2c, 0x1e, 0x2e, 0x0c, 0x30, 0x53, 0xab, 0x9c, 0xa7, 0xd4, 0x96, 0xe7, 0x26, 0x30, 0x2b, 0x7f, 0x2d, 0x6e, 0x2f, 0xb3, 0x30, 0x74, 0xa8, 0xb1, 0x9f, 0x36, 0x25, 0x3e, 0x2a, 0xfa, 0x2c, 0xdd, 0x2e, 0x65, 0x30, 0xfc, 0xa1, 0xe0, 0x23, 0x82, 0x29, 0x8f, 0x2c, 0x66, 0x2e, 0x23, 0x30, 0x2d, 0x22, 0xfb, 0x28, 0x3f, 0x2c, 0x0a, 0x2e, 0xde, 0x2f, 0xaa, 0x28, 0x0a, 0x2c, 0xc8, 0x2d, 0x8f, 0x2f, 0xb0, 0x30, 0xde, 0x2b, 0xa0, 0x2d, 0x5a, 0x2f, 0x8f, 0x30, 0x12, 0xac, 0x9d, 0xa8, 0x0f, 0xa0, 0x51, 0x25, 0x66, 0x2a, 0x1b, 0x2d, 0x0b, 0x2f, 0x82, 0x30, 0x7b, 0xa9, 0xea, 0xa3, 0x63, 0x22, 0x3f, 0x29, 0x7b, 0x2c, 0x60, 0x2e, 0x26, 0x30, 0x76, 0xa5, 0xf8, 0x1d, 0x4c, 0x28, 0xeb, 0x2b, 0xce, 0x2d, 0xb0, 0x2f, 0xd3, 0x12, 0x1d, 0x27, 0x15, 0x2b, 0x57, 0x2d, 0x2c, 0x2f, 0x85, 0x30, 0x0e, 0x26, 0x74, 0x2a, 0xfa, 0x2c, 0xc3, 0x2e, 0x4a, 0x30, 0x08, 0x2a, 0xb7, 0x2c, 0x74, 0x2e, 0x1d, 0x30, 0x8f, 0x2c, 0x3f, 0x2e, 0xf8, 0x2f, 0x24, 0x2e, 0xd0, 0x2f, 0xc3, 0x30, 0xdb, 0xa6, 0xd3, 0x0e, 0x38, 0x27, 0x3d, 0x2b, 0x78, 0x2d, 0x5a, 0x2f, 0xa3, 0x30, 0x68, 0x9e, 0x51, 0x25, 0x31, 0x2a, 0xe6, 0x2c, 0xbc, 0x2e, 0x4e, 0x30, 0xa9, 0x23, 0x59, 0x29, 0x6e, 0x2c, 0x38, 0x2e, 0x06, 0x30, 0xb8, 0x28, 0x10, 0x2c, 0xce, 0x2d, 0x95, 0x2f, 0xb3, 0x30, 0x9b, 0x2b, 0x7f, 0x2d, 0x39, 0x2f, 0x7f, 0x30, 0x4a, 0x2d, 0xf8, 0x2e, 0x58, 0x30, 0xd0, 0x2e, 0x3d, 0x30, 0x30, 0x30, 0x53, 0x21, 0xc5, 0x28, 0x24, 0x2c, 0xef, 0x2d, 0xc3, 0x2f, 0xda, 0x27, 0x58, 0x2b, 0x6b, 0x2d, 0x33, 0x2f, 0x82, 0x30, 0x9c, 0x2a, 0x00, 0x2d, 0xbc, 0x2e, 0x41, 0x30, 0xb0, 0x2c, 0x60, 0x2e, 0x0c, 0x30, 0x1e, 0x2e, 0xca, 0x2f, 0xc0, 0x30, 0x95, 0x2f, 0x9f, 0x30, 0x8c, 0x30, 0x23, 0x2a, 0xc4, 0x2c, 0x81, 0x2e, 0x23, 0x30, 0x5a, 0x2c, 0x0a, 0x2e, 0xc3, 0x2f, 0xc3, 0x30, 0xad, 0x2d, 0x5a, 0x2f, 0x88, 0x30, 0x0b, 0x2f, 0x5b, 0x30, 0x3a, 0x30, 0x7f, 0x2d, 0x2c, 0x2f, 0x72, 0x30, 0xc3, 0x2e, 0x37, 0x30, 0x09, 0x30, 0xb6, 0x30 }; const char* indata = reinterpret_cast(rawdata); size_t inbytes = sizeof(rawdata); const int compbufbytes = int(inbytes + BLOSC_MAX_OVERHEAD), decompbufbytes = int(inbytes + BLOSC_MAX_OVERHEAD); boost::scoped_array compresseddata(new char[compbufbytes]), outdata(new char[decompbufbytes]); for (int compcode = 0; compcode <= BLOSC_ZLIB; ++compcode) { char* compname = NULL; if (0 > blosc_compcode_to_compname(compcode, &compname)) continue; /// @todo This changes the compressor setting globally. if (blosc_set_compressor(compname) < 0) continue; for (int typesize = 1; typesize <= 4; ++typesize) { // Compress the data. ::memset(compresseddata.get(), 0, compbufbytes); int compressedbytes = blosc_compress( /*clevel=*/9, /*doshuffle=*/true, typesize, /*srcsize=*/inbytes, /*src=*/indata, /*dest=*/compresseddata.get(), /*destsize=*/compbufbytes); CPPUNIT_ASSERT(compressedbytes > 0); // Decompress the data. ::memset(outdata.get(), 0, decompbufbytes); int outbytes = blosc_decompress( compresseddata.get(), outdata.get(), decompbufbytes); CPPUNIT_ASSERT(outbytes > 0); CPPUNIT_ASSERT_EQUAL(int(inbytes), outbytes); // Compare original and decompressed data. int diff = 0; for (size_t i = 0; i < inbytes; ++i) { if (outdata[i] != indata[i]) ++diff; } if (diff > 0) { const char* mesg = "Your version of the Blosc library is most likely" " out of date; please install the latest version. " "(Earlier versions have a bug that can cause data corruption.)"; CPPUNIT_ASSERT_MESSAGE(mesg, diff == 0); return; } } } } #endif // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTools.cc0000644000000000000000000026454212603226506015356 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(testPrune); CPPUNIT_TEST(testVolumeAdvect); CPPUNIT_TEST(testTransformValues); CPPUNIT_TEST(testVectorApply); CPPUNIT_TEST(testAccumulate); CPPUNIT_TEST(testUtil); CPPUNIT_TEST(testVectorTransformer); CPPUNIT_TEST(testClipping); 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 testPrune(); void testVolumeAdvect(); void testTransformValues(); void testVectorApply(); void testAccumulate(); void testUtil(); void testVectorTransformer(); void testClipping(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTools); #if 0 namespace { // Simple helper class to write out numbered vdbs template class FrameWriter { public: FrameWriter(int version, typename GridT::Ptr grid): mFrame(0), mVersion(version), mGrid(grid) {} void operator()(const std::string& name, float time, size_t n) { std::ostringstream ostr; ostr << name << "_" << mVersion << "_" << mFrame << ".vdb"; openvdb::io::File file(ostr.str()); openvdb::GridPtrVec grids; grids.push_back(mGrid); file.write(grids); std::cerr << "\nWrote \"" << ostr.str() << "\" with time = " << time << " after CFL-iterations = " << n << std::endl; ++mFrame; } private: int mFrame, mVersion; typename GridT::Ptr mGrid; }; } // unnamed namespace #endif void TestTools::testDilateVoxels() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Index32; using openvdb::Index64; typedef openvdb::tree::Tree4::Type Tree543f; Tree543f::Ptr tree(new Tree543f); openvdb::tools::changeBackground(*tree, /*background=*/5.0); CPPUNIT_ASSERT(tree->empty()); const openvdb::Index leafDim = Tree543f::LeafNodeType::DIM; CPPUNIT_ASSERT_EQUAL(1 << 3, int(leafDim)); { // Set and dilate a single voxel at the center of a leaf node. tree->clear(); tree->setValue(Coord(leafDim >> 1), 1.0); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(Index64(7), tree->activeVoxelCount()); } { // Create an active, leaf node-sized tile. tree->clear(); tree->fill(CoordBBox(Coord(0), Coord(leafDim - 1)), 1.0); CPPUNIT_ASSERT_EQUAL(Index32(0), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(leafDim * leafDim * leafDim), tree->activeVoxelCount()); 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; // } {// test dilateVoxels6 for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { const openvdb::Coord ijk(x,y,z); Tree543f tree1(0.0f); CPPUNIT_ASSERT_EQUAL(Index64(0), tree1.activeVoxelCount()); tree1.setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.isValueOn(ijk)); openvdb::tools::Morphology m(tree1); m.dilateVoxels6(); for (int i=-1; i<=1; ++i) { for (int j=-1; j<=1; ++j) { for (int k=-1; k<=1; ++k) { const openvdb::Coord xyz = ijk.offsetBy(i,j,k), d=ijk-xyz; const int n= openvdb::math::Abs(d[0]) + openvdb::math::Abs(d[1]) + openvdb::math::Abs(d[2]); if (n<=1) { CPPUNIT_ASSERT( tree1.isValueOn(xyz)); } else { CPPUNIT_ASSERT(!tree1.isValueOn(xyz)); } } } } CPPUNIT_ASSERT_EQUAL(Index64(1 + 6), tree1.activeVoxelCount()); } } } } {// test dilateVoxels18 for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { const openvdb::Coord ijk(x,y,z); Tree543f tree1(0.0f); CPPUNIT_ASSERT_EQUAL(Index64(0), tree1.activeVoxelCount()); tree1.setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.isValueOn(ijk)); openvdb::tools::Morphology m(tree1); m.dilateVoxels18(); for (int i=-1; i<=1; ++i) { for (int j=-1; j<=1; ++j) { for (int k=-1; k<=1; ++k) { const openvdb::Coord xyz = ijk.offsetBy(i,j,k), d=ijk-xyz; const int n= openvdb::math::Abs(d[0]) + openvdb::math::Abs(d[1]) + openvdb::math::Abs(d[2]); if (n<=2) { CPPUNIT_ASSERT( tree1.isValueOn(xyz)); } else { CPPUNIT_ASSERT(!tree1.isValueOn(xyz)); } } } } CPPUNIT_ASSERT_EQUAL(Index64(1 + 6 + 12), tree1.activeVoxelCount()); } } } } {// test dilateVoxels26 for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { const openvdb::Coord ijk(x,y,z); Tree543f tree1(0.0f); CPPUNIT_ASSERT_EQUAL(Index64(0), tree1.activeVoxelCount()); tree1.setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.isValueOn(ijk)); openvdb::tools::Morphology m(tree1); m.dilateVoxels26(); for (int i=-1; i<=1; ++i) { for (int j=-1; j<=1; ++j) { for (int k=-1; k<=1; ++k) { const openvdb::Coord xyz = ijk.offsetBy(i,j,k), d=ijk-xyz; const int n = openvdb::math::Abs(d[0]) + openvdb::math::Abs(d[1]) + openvdb::math::Abs(d[2]); if (n<=3) { CPPUNIT_ASSERT( tree1.isValueOn(xyz)); } else { CPPUNIT_ASSERT(!tree1.isValueOn(xyz)); } } } } CPPUNIT_ASSERT_EQUAL(Index64(1 + 6 + 12 + 8), tree1.activeVoxelCount()); } } } } /* // Performance test {// dialte a narrow band of a sphere const float radius = 335.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 1.5f, width = 3.25f; openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, width); //grid->print(std::cerr, 3); const openvdb::Index64 count = grid->tree().activeVoxelCount(); openvdb::util::CpuTimer t; t.start("sphere dilateVoxels6"); openvdb::tools::dilateVoxels(grid->tree()); t.stop(); CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); // grid->print(std::cerr, 3); } {// dialte a narrow band of a sphere const float radius = 335.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 1.5f, width = 3.25f; openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, width); //grid->print(std::cerr, 3); const openvdb::Index64 count = grid->tree().activeVoxelCount(); openvdb::util::CpuTimer t; t.start("sphere dilateVoxels18"); openvdb::tools::dilateVoxels(grid->tree(), 1, openvdb::tools::NN_FACE_EDGE); t.stop(); CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); //grid->print(std::cerr, 3); } {// dialte a narrow band of a sphere const float radius = 335.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 1.5f, width = 3.25f; openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, width); //grid->print(std::cerr, 3); const openvdb::Index64 count = grid->tree().activeVoxelCount(); openvdb::util::CpuTimer t; t.start("sphere dilateVoxels26"); openvdb::tools::dilateVoxels(grid->tree(), 1, openvdb::tools::NN_FACE_EDGE_VERTEX); t.stop(); CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); //grid->print(std::cerr, 3); } */ } 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); openvdb::tools::changeBackground(*tree, /*background=*/5.0); CPPUNIT_ASSERT(tree->empty()); const int leafDim = TreeType::LeafNodeType::DIM; CPPUNIT_ASSERT_EQUAL(1 << 3, leafDim); { // Set, dilate and erode a single voxel at the center of a leaf node. tree->clear(); CPPUNIT_ASSERT_EQUAL(0, int(tree->activeVoxelCount())); tree->setValue(Coord(leafDim >> 1), 1.0); CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(7, int(tree->activeVoxelCount())); openvdb::tools::erodeVoxels(*tree); CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); openvdb::tools::erodeVoxels(*tree); CPPUNIT_ASSERT_EQUAL(0, int(tree->activeVoxelCount())); } { // Create an active, leaf node-sized tile. tree->clear(); tree->fill(CoordBBox(Coord(0), Coord(leafDim - 1)), 1.0); CPPUNIT_ASSERT_EQUAL(0, int(tree->leafCount())); CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim, int(tree->activeVoxelCount())); tree->setValue(Coord(leafDim, leafDim - 1, leafDim - 1), 1.0); CPPUNIT_ASSERT_EQUAL(1, int(tree->leafCount())); CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim + 1,int(tree->activeVoxelCount())); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(3, int(tree->leafCount())); CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim + 1 + 5,int(tree->activeVoxelCount())); openvdb::tools::erodeVoxels(*tree); CPPUNIT_ASSERT_EQUAL(1, int(tree->leafCount())); CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim + 1, int(tree->activeVoxelCount())); } { // Set and dilate a single voxel at each of the eight corners of a leaf node. for (int i = 0; i < 8; ++i) { tree->clear(); openvdb::Coord xyz( i & 1 ? leafDim - 1 : 0, i & 2 ? leafDim - 1 : 0, i & 4 ? leafDim - 1 : 0); tree->setValue(xyz, 1.0); CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(7, int(tree->activeVoxelCount())); openvdb::tools::erodeVoxels(*tree); CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); } } { // Set three active voxels and dilate and erode tree->clear(); tree->setValue(Coord(0), 1.0); tree->setValue(Coord( 1, 0, 0), 1.0); tree->setValue(Coord(-1, 0, 0), 1.0); CPPUNIT_ASSERT_EQUAL(3, int(tree->activeVoxelCount())); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(17, int(tree->activeVoxelCount())); openvdb::tools::erodeVoxels(*tree); CPPUNIT_ASSERT_EQUAL(3, int(tree->activeVoxelCount())); } { struct Info { void test(TreeType::Ptr aTree) { CPPUNIT_ASSERT_EQUAL(activeVoxelCount, int(aTree->activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(leafCount, int(aTree->leafCount())); CPPUNIT_ASSERT_EQUAL(nonLeafCount, int(aTree->nonLeafCount())); } int activeVoxelCount, leafCount, nonLeafCount; }; Info iterInfo[12] = { { 0, 0, 1 },//an empty tree only contains a root node { 1, 1, 3 }, { 7, 1, 3 }, { 25, 1, 3 }, { 63, 1, 3 }, { 129, 4, 3 }, { 231, 7, 9 }, { 377, 7, 9 }, { 575, 7, 9 }, { 833, 10, 9 }, { 1159, 16, 9 }, { 1561, 19, 15 }, }; // Perform repeated dilations, starting with a single voxel. tree->clear(); iterInfo[0].test(tree); tree->setValue(Coord(leafDim >> 1), 1.0); iterInfo[1].test(tree); for (int i = 2; i < 12; ++i) { openvdb::tools::dilateVoxels(*tree); iterInfo[i].test(tree); } for (int i = 10; i >= 0; --i) { openvdb::tools::erodeVoxels(*tree); iterInfo[i].test(tree); } // Now try it using the resursive calls for (int i = 2; i < 12; ++i) { tree->clear(); tree->setValue(Coord(leafDim >> 1), 1.0); openvdb::tools::dilateVoxels(*tree, i-1); iterInfo[i].test(tree); } for (int i = 10; i >= 0; --i) { tree->clear(); tree->setValue(Coord(leafDim >> 1), 1.0); openvdb::tools::dilateVoxels(*tree, 10); openvdb::tools::erodeVoxels(*tree, 11-i); iterInfo[i].test(tree); } } {// erode a narrow band of a sphere 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); } {//erode6 for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { tree->clear(); const openvdb::Coord ijk(x,y,z); CPPUNIT_ASSERT_EQUAL(Index64(0), tree->activeVoxelCount()); tree->setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); CPPUNIT_ASSERT(tree->isValueOn(ijk)); openvdb::tools::dilateVoxels(*tree, 1, openvdb::tools::NN_FACE); CPPUNIT_ASSERT_EQUAL(Index64(1 + 6), tree->activeVoxelCount()); openvdb::tools::erodeVoxels( *tree, 1, openvdb::tools::NN_FACE); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); CPPUNIT_ASSERT(tree->isValueOn(ijk)); } } } } #if 0 {//erode18 /// @todo Not implemented yet for (int iter=1; iter<4; ++iter) { for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { const openvdb::Coord ijk(x,y,z); tree->clear(); CPPUNIT_ASSERT_EQUAL(Index64(0), tree->activeVoxelCount()); tree->setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); CPPUNIT_ASSERT(tree->isValueOn(ijk)); //openvdb::tools::dilateVoxels(*tree, iter, openvdb::tools::NN_FACE_EDGE); openvdb::tools::dilateVoxels(*tree, iter, openvdb::tools::NN_FACE); //std::cerr << "Dilated to: " << tree->activeVoxelCount() << std::endl; //if (iter==1) { // CPPUNIT_ASSERT_EQUAL(Index64(1 + 6 + 12), tree->activeVoxelCount()); //} openvdb::tools::erodeVoxels( *tree, iter, openvdb::tools::NN_FACE_EDGE); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); CPPUNIT_ASSERT(tree->isValueOn(ijk)); } } } } } #endif #if 0 {//erode26 /// @todo Not implemented yet tree->clear(); tree->setValue(openvdb::Coord(3,4,5), 1.0f); openvdb::tools::dilateVoxels(*tree, 1, openvdb::tools::NN_FACE_EDGE_VERTEX); CPPUNIT_ASSERT_EQUAL(Index64(1 + 6 + 12 + 8), tree->activeVoxelCount()); openvdb::tools::erodeVoxels( *tree, 1, openvdb::tools::NN_FACE_EDGE_VERTEX); //openvdb::tools::dilateVoxels(*tree, 12, openvdb::tools::NN_FACE_EDGE); //openvdb::tools::erodeVoxels( *tree, 12, openvdb::tools::NN_FACE_EDGE); CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); CPPUNIT_ASSERT(tree->isValueOn(openvdb::Coord(3,4,5))); } #endif } void TestTools::testActivate() { using namespace openvdb; const Vec3s background(0.0, -1.0, 1.0), foreground(42.0); Vec3STree tree(background); const CoordBBox bbox1(Coord(-200), Coord(-181)), bbox2(Coord(51), Coord(373)); // Set some non-background active voxels. tree.fill(bbox1, Vec3s(0.0), /*active=*/true); // Mark some background voxels as active. tree.fill(bbox2, background, /*active=*/true); CPPUNIT_ASSERT_EQUAL(bbox2.volume() + bbox1.volume(), tree.activeVoxelCount()); // Deactivate all voxels with the background value. tools::deactivate(tree, background, /*tolerance=*/Vec3s(1.0e-6f)); // Verify that there are no longer any active voxels with the background value. CPPUNIT_ASSERT_EQUAL(bbox1.volume(), tree.activeVoxelCount()); // Set some voxels to the foreground value but leave them inactive. tree.fill(bbox2, foreground, /*active=*/false); // Verify that there are no active voxels with the background value. CPPUNIT_ASSERT_EQUAL(bbox1.volume(), tree.activeVoxelCount()); // Activate all voxels with the foreground value. tools::activate(tree, foreground); // Verify that the expected number of voxels are active. CPPUNIT_ASSERT_EQUAL(bbox1.volume() + bbox2.volume(), tree.activeVoxelCount()); } void TestTools::testFilter() { openvdb::FloatGrid::Ptr referenceGrid = openvdb::FloatGrid::create(/*background=*/5.0); const openvdb::Coord dim(40); const openvdb::Vec3f center(25.0f, 20.0f, 20.0f); const float radius = 10.0f; unittest_util::makeSphere( dim, center, radius, *referenceGrid, unittest_util::SPHERE_DENSE); const openvdb::FloatTree& sphere = referenceGrid->tree(); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(sphere.activeVoxelCount())); openvdb::Coord xyz; {// test Filter::offsetFilter openvdb::FloatGrid::Ptr grid = referenceGrid->deepCopy(); openvdb::FloatTree& tree = grid->tree(); openvdb::tools::Filter filter(*grid); const float offset = 2.34f; filter.setGrainSize(0);//i.e. disable threading filter.offset(offset); for (int x=0; x0.0001f) std::cerr << " failed at " << xyz << std::endl; CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0f, delta, /*tolerance=*/0.0001); } } } filter.setGrainSize(1);//i.e. enable threading filter.offset(-offset);//default is multi-threaded for (int x=0; x0.0001f) std::cerr << " failed at " << xyz << std::endl; CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0f, delta, /*tolerance=*/0.0001); } } } //std::cerr << "Successfully completed TestTools::testFilter offset test" << std::endl; } {// test Filter::median openvdb::FloatGrid::Ptr filteredGrid = referenceGrid->deepCopy(); openvdb::FloatTree& filteredTree = filteredGrid->tree(); const int width = 2; openvdb::math::DenseStencil stencil(*referenceGrid, width); openvdb::tools::Filter filter(*filteredGrid); filter.median(width, /*interations=*/1); std::vector tmp; for (int x=0; xdeepCopy(); openvdb::FloatTree& filteredTree = filteredGrid->tree(); const int width = 2; openvdb::math::DenseStencil stencil(*referenceGrid, width); openvdb::tools::Filter filter(*filteredGrid); filter.mean(width, /*interations=*/1); for (int x=0; 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 using namespace openvdb; const int dim = 128; const Vec3f center(0.35f, 0.35f, 0.35f); const float radius = 0.15f, voxelSize = 1.0f/(dim-1); const float halfWidth = 3.0f, gamma = halfWidth*voxelSize; typedef FloatGrid GridT; //typedef Vec3fGrid VectT; {//test tracker::resize GridT::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, halfWidth); typedef tools::LevelSetTracker TrackerT; TrackerT tracker(*grid); tracker.setSpatialScheme(math::FIRST_BIAS); tracker.setTemporalScheme(math::TVD_RK1); ASSERT_DOUBLES_EXACTLY_EQUAL( gamma, grid->background()); ASSERT_DOUBLES_EXACTLY_EQUAL( halfWidth, tracker.getHalfWidth()); CPPUNIT_ASSERT(!tracker.resize()); {// check range of on values in a sphere w/o mask tools::CheckRange c(-gamma, gamma); tools::Diagnose d(*grid); std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check norm of gradient of sphere w/o mask tools::CheckNormGrad c(*grid, 0.9f, 1.1f); tools::Diagnose d(*grid); std::string str = d.check(c, false, true, false, false); //std::cerr << "NormGrad:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } CPPUNIT_ASSERT(tracker.resize(4)); ASSERT_DOUBLES_EXACTLY_EQUAL( 4*voxelSize, grid->background()); ASSERT_DOUBLES_EXACTLY_EQUAL( 4.0f, tracker.getHalfWidth()); {// check range of on values in a sphere w/o mask const float g = gamma + voxelSize; tools::CheckRange c(-g, g); tools::Diagnose d(*grid); std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check norm of gradient of sphere w/o mask tools::CheckNormGrad c(*grid, 0.4f, 1.1f); tools::Diagnose d(*grid); std::string str = d.check(c, false, true, false, false); //std::cerr << "NormGrad:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } } /* {//test tracker GridT::Ptr grid = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); 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); //util::CpuTimer timer; const float tMax = 0.05f/voxelSize; //std::cerr << "\nt-max = " << tMax << std::endl; //timer.start("\nMorphing"); for (float t = 0, dt = 0.1f; !source->empty() && t < tMax; t += dt) { morph.advect(t, t + dt); //fw(name, t + dt, morph.advect(t, t + dt)); } // timer.stop(); const float invDx = 1.0f/voxelSize; openvdb::math::Stats s; for (GridT::ValueOnCIter it = source->tree().cbeginValueOn(); it; ++it) { s.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); } for (GridT::ValueOnCIter it = target->tree().cbeginValueOn(); it; ++it) { s.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); } //s.print("Morph"); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.min(), 0.50); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.max(), 0.50); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.avg(), 0.02); /* openvdb::math::Histogram h(s, 30); for (GridT::ValueOnCIter it = source->tree().cbeginValueOn(); it; ++it) { h.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); } for (GridT::ValueOnCIter it = target->tree().cbeginValueOn(); it; ++it) { h.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); } h.print("Morph"); */ } /* // Uncomment sections below to run this (very time-consuming) test {//test morphing between the bunny and the buddha models loaded from files util::CpuTimer timer; openvdb::initialize();//required whenever I/O of OpenVDB files is performed! openvdb::io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/bunny.vdb"); sourceFile.open(); GridT::Ptr source = openvdb::gridPtrCast(sourceFile.getGrids()->at(0)); openvdb::io::File targetFile("/usr/pic1/Data/OpenVDB/LevelSetModels/buddha.vdb"); targetFile.open(); GridT::Ptr target = openvdb::gridPtrCast(targetFile.getGrids()->at(0)); 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 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( float(r), C, float(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(float(r), C, float(voxelSize)); m.reinit(*sphere); // Test all measures of sphere in world units m.measure(a, v, c); area = 4*Pi*r*r; volume = 4.0/3.0*Pi*r*r*r; curv = 1.0/r; //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; //std::cerr << "Volume of sphere = " << volume << " " << v << std::endl; //std::cerr << "Avg mean curvature of sphere = " << curv << " " << c << std::endl; // Test accuracy of computed measures to within 0.1% of the exact measure. CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); CPPUNIT_ASSERT_DOUBLES_EQUAL(curv, c, percentage*curv); CPPUNIT_ASSERT_DOUBLES_EQUAL(area, openvdb::tools::levelSetArea(*sphere), percentage*area); CPPUNIT_ASSERT_DOUBLES_EQUAL(volume,openvdb::tools::levelSetVolume(*sphere),percentage*volume); // Test all measures of sphere in index units m.measure(a, v, c, false); r /= voxelSize; area = 4*Pi*r*r; volume = 4.0/3.0*Pi*r*r*r; curv = 1.0/r; //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; //std::cerr << "Volume of sphere = " << volume << " " << v << std::endl; //std::cerr << "Avg mean curvature of sphere = " << curv << " " << c << std::endl; // Test accuracy of computed measures to within 0.1% of the exact measure. CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); CPPUNIT_ASSERT_DOUBLES_EQUAL(curv, c, percentage*curv); CPPUNIT_ASSERT_DOUBLES_EQUAL(area, openvdb::tools::levelSetArea(*sphere,false), percentage*area); CPPUNIT_ASSERT_DOUBLES_EQUAL(volume,openvdb::tools::levelSetVolume(*sphere,false), percentage*volume); // Read level set from file /* util::CpuTimer timer; openvdb::initialize();//required whenever I/O of OpenVDB files is performed! openvdb::io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/venusstatue.vdb"); sourceFile.open(); GridT::Ptr model = openvdb::gridPtrCast(sourceFile.getGrids()->at(0)); m.reinit(*model); //m.setGrainSize(1); timer.start("\nParallel measure of area and volume"); m.measure(a, v, false); timer.stop(); std::cerr << "Model: area = " << a << ", volume = " << v << std::endl; timer.start("\nParallel measure of area, volume and curvature"); m.measure(a, v, c, false); timer.stop(); std::cerr << "Model: area = " << a << ", volume = " << v << ", average curvature = " << c << std::endl; m.setGrainSize(0); timer.start("\nSerial measure of area and volume"); m.measure(a, v, false); timer.stop(); std::cerr << "Model: area = " << a << ", volume = " << v << std::endl; timer.start("\nSerial measure of area, volume and curvature"); m.measure(a, v, c, false); timer.stop(); std::cerr << "Model: area = " << a << ", volume = " << v << ", average curvature = " << c << std::endl; */ }//testLevelSetMeasure void TestTools::testMagnitude() { 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(float(i), float(i), float(i)); } for (unsigned int order = 1; order < 5; ++order) { // check all four time integrations schemes // construct an advection tool. By default the number of cpt iterations is zero advectionTool.setIntegrationOrder(order); advectionTool.advect(pointList, /*dt=*/1.0, /*iterations=*/1); // check locations for (size_t i = 0; i < numPoints; i++) { openvdb::Vec3f expected(float(i + 1), float(i + 1), float(i + 1)); CPPUNIT_ASSERT_EQUAL(expected, pointList[i]); } // reset values for (size_t i = 0; i < numPoints; i++) { pointList[i] = openvdb::Vec3f(float(i), float(i), float(i)); } } } { // Setup: Advect a number of points in a uniform velocity field (1,1,1). // over a time dt=1 with each of the 4 different advection schemes. // And then project the point location onto the x-y plane // Points initialized at latice points. // // Uses: DoubleTree (velocity), staggered sampling, constraint projection, advection // // Expected: All advection schemes will have the same result. Modes 1-4: Each point will // be advanced to a new latice point and projected to x-y plane. // The i-th point will be at (i+1,i+1,0). For mode 0 (no advection), i-th point // will be found at (i, i, 0) const size_t numPoints = 4; // create a uniform velocity field in DOUBLE PRECISION const openvdb::Vec3d velocityBackground(1, 1, 1); openvdb::Vec3dGrid::Ptr velocityGrid = openvdb::Vec3dGrid::create(velocityBackground); // create a simple (horizontal) constraint field valid for a // (-10,10)x(-10,10)x(-10,10) const openvdb::Vec3d cptBackground(0, 0, 0); openvdb::Vec3dGrid::Ptr cptGrid = openvdb::Vec3dGrid::create(cptBackground); openvdb::Vec3dTree& cptTree = cptGrid->tree(); // create points std::vector pointList(numPoints); for (unsigned int i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3d(i, i, i); // Initialize the constraint field in a [-10,10]x[-10,10]x[-10,10] box // this test will only work if the points remain in the box openvdb::Coord ijk(0, 0, 0); for (int i = -10; i < 11; i++) { ijk.setX(i); for (int j = -10; j < 11; j++) { ijk.setY(j); for (int k = -10; k < 11; k++) { ijk.setZ(k); // set the value as projection onto the x-y plane cptTree.setValue(ijk, openvdb::Vec3d(i, j, 0)); } } } // construct an advection tool. By default the number of cpt iterations is zero openvdb::tools::ConstrainedPointAdvect, true> constrainedAdvectionTool(*velocityGrid, *cptGrid, 0); constrainedAdvectionTool.setThreaded(false); // change the number of constraint interation from default 0 to 5 constrainedAdvectionTool.setConstraintIterations(5); // test the pure-projection mode (order = 0) constrainedAdvectionTool.setIntegrationOrder(0); // change the number of constraint interation (from 0 to 5) constrainedAdvectionTool.setConstraintIterations(5); constrainedAdvectionTool.advect(pointList, /*dt=*/1.0, /*iterations=*/1); // check locations for (unsigned int i = 0; i < numPoints; i++) { openvdb::Vec3d expected(i, i, 0); // location (i, i, i) projected on to x-y plane for (int n=0; n<3; ++n) { CPPUNIT_ASSERT_DOUBLES_EQUAL(expected[n], pointList[i][n], /*tolerance=*/1e-6); } } // reset values for (unsigned int i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3d(i, i, i); // test all four time integrations schemes for (unsigned int order = 1; order < 5; ++order) { constrainedAdvectionTool.setIntegrationOrder(order); constrainedAdvectionTool.advect(pointList, /*dt=*/1.0, /*iterations=*/1); // check locations for (unsigned int i = 0; i < numPoints; i++) { openvdb::Vec3d expected(i+1, i+1, 0); // location (i,i,i) projected onto x-y plane for (int n=0; n<3; ++n) { CPPUNIT_ASSERT_DOUBLES_EQUAL(expected[n], pointList[i][n], /*tolerance=*/1e-6); } } // reset values for (unsigned int i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3d(i, i, i); } } } //////////////////////////////////////// namespace { struct PointList { struct Point { double x,y,z; }; std::vector list; openvdb::Index64 size() const { return openvdb::Index64(list.size()); } void add(const openvdb::Vec3d &p) { Point q={p[0],p[1],p[2]}; list.push_back(q); } }; } void TestTools::testPointScatter() { 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); {// test fixed point count scattering const openvdb::Index64 pointCount = 1000; PointList points; openvdb::tools::UniformPointScatter scatter(points, pointCount, mtRand); scatter.operator()(*grid); CPPUNIT_ASSERT_EQUAL( pointCount, scatter.getPointCount() ); CPPUNIT_ASSERT_EQUAL( pointCount, points.size() ); } {// test uniform density scattering const float density = 1.0f;//per volume = per voxel since voxel size = 1 PointList points; openvdb::tools::UniformPointScatter scatter(points, density, mtRand); scatter.operator()(*grid); CPPUNIT_ASSERT_EQUAL( scatter.getVoxelCount(), scatter.getPointCount() ); CPPUNIT_ASSERT_EQUAL( scatter.getVoxelCount(), points.size() ); } {// test non-uniform density scattering const float density = 1.0f;//per volume = per voxel since voxel size = 1 PointList points; openvdb::tools::NonUniformPointScatter scatter(points, density, mtRand); scatter.operator()(*grid); CPPUNIT_ASSERT( scatter.getVoxelCount() < scatter.getPointCount() ); CPPUNIT_ASSERT_EQUAL( scatter.getPointCount(), points.size() ); } {// test dense uniform scattering const size_t pointsPerVoxel = 8; PointList points; openvdb::tools::DenseUniformPointScatter scatter(points, pointsPerVoxel, mtRand); scatter.operator()(*grid); CPPUNIT_ASSERT_EQUAL( scatter.getVoxelCount()*pointsPerVoxel, scatter.getPointCount() ); CPPUNIT_ASSERT_EQUAL( scatter.getPointCount(), points.size() ); } } //////////////////////////////////////// void TestTools::testVolumeAdvect() { using namespace openvdb; Vec3fGrid velocity(Vec3f(1.0f, 0.0f, 0.0f)); typedef FloatGrid GridT; typedef tools::VolumeAdvection AdvT; typedef tools::Sampler<1> SamplerT; {//test non-uniform grids (throws) GridT::Ptr density0 = GridT::create(0.0f); density0->transform().preScale(Vec3d(1.0, 2.0, 3.0));//i.e. non-uniform voxels AdvT a(velocity); CPPUNIT_ASSERT_THROW((a.advect(*density0, 0.1f)), RuntimeError); } {// test spatialOrder and temporalOrder AdvT a(velocity); // Default should be SEMI CPPUNIT_ASSERT_EQUAL(1, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(1, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); a.setIntegrator(tools::Scheme::SEMI); CPPUNIT_ASSERT_EQUAL(1, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(1, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); a.setIntegrator(tools::Scheme::MID); CPPUNIT_ASSERT_EQUAL(1, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(2, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); a.setIntegrator(tools::Scheme::RK3); CPPUNIT_ASSERT_EQUAL(1, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(3, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); a.setIntegrator(tools::Scheme::RK4); CPPUNIT_ASSERT_EQUAL(1, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(4, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); a.setIntegrator(tools::Scheme::MAC); CPPUNIT_ASSERT_EQUAL(2, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(2, a.temporalOrder()); CPPUNIT_ASSERT( a.isLimiterOn()); a.setIntegrator(tools::Scheme::BFECC); CPPUNIT_ASSERT_EQUAL(2, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(2, a.temporalOrder()); CPPUNIT_ASSERT( a.isLimiterOn()); a.setLimiter(tools::Scheme::NO_LIMITER); CPPUNIT_ASSERT_EQUAL(2, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(2, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); } {//test RK4 advect without a mask GridT::Ptr density0 = GridT::create(0.0f), density1; density0->fill(CoordBBox(Coord(0),Coord(6)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord( 3,3,3)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(24,3,3)), 0.0f); CPPUNIT_ASSERT( density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT(!density0->tree().isValueOn(Coord(24,3,3))); AdvT a(velocity); a.setIntegrator(tools::Scheme::RK4); for (int i=1; i<=240; ++i) { density1 = a.advect(*density0, 0.1f); //std::ostringstream ostr; //ostr << "densityAdvect" << "_" << i << ".vdb"; //std::cerr << "Writing " << ostr.str() << std::endl; //openvdb::io::File file(ostr.str()); //openvdb::GridPtrVec grids; //grids.push_back(density1); //file.write(grids); density0 = density1; } CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(3,3,3)), 0.0f); CPPUNIT_ASSERT(density0->tree().getValue(Coord(24,3,3)) > 0.0f); CPPUNIT_ASSERT(!density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT( density0->tree().isValueOn(Coord(24,3,3))); } {//test MAC advect without a mask GridT::Ptr density0 = GridT::create(0.0f), density1; density0->fill(CoordBBox(Coord(0),Coord(6)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord( 3,3,3)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(24,3,3)), 0.0f); CPPUNIT_ASSERT( density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT(!density0->tree().isValueOn(Coord(24,3,3))); AdvT a(velocity); a.setIntegrator(tools::Scheme::BFECC); for (int i=1; i<=240; ++i) { density1 = a.advect(*density0, 0.1f); //std::ostringstream ostr; //ostr << "densityAdvect" << "_" << i << ".vdb"; //std::cerr << "Writing " << ostr.str() << std::endl; //openvdb::io::File file(ostr.str()); //openvdb::GridPtrVec grids; //grids.push_back(density1); //file.write(grids); density0 = density1; } CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(3,3,3)), 0.0f); CPPUNIT_ASSERT(density0->tree().getValue(Coord(24,3,3)) > 0.0f); CPPUNIT_ASSERT(!density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT( density0->tree().isValueOn(Coord(24,3,3))); } {//test advect with a mask GridT::Ptr density0 = GridT::create(0.0f), density1; density0->fill(CoordBBox(Coord(0),Coord(6)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord( 3,3,3)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(24,3,3)), 0.0f); CPPUNIT_ASSERT( density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT(!density0->tree().isValueOn(Coord(24,3,3))); BoolGrid::Ptr mask = BoolGrid::create(false); mask->fill(CoordBBox(Coord(4,0,0),Coord(30,8,8)), true); AdvT a(velocity); a.setGrainSize(0); a.setIntegrator(tools::Scheme::MAC); //a.setIntegrator(tools::Scheme::BFECC); //a.setIntegrator(tools::Scheme::RK4); for (int i=1; i<=240; ++i) { density1 = a.advect(*density0, *mask, 0.1f); //std::ostringstream ostr; //ostr << "densityAdvectMask" << "_" << i << ".vdb"; //std::cerr << "Writing " << ostr.str() << std::endl; //openvdb::io::File file(ostr.str()); //openvdb::GridPtrVec grids; //grids.push_back(density1); //file.write(grids); density0 = density1; } CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(3,3,3)), 1.0f); CPPUNIT_ASSERT(density0->tree().getValue(Coord(24,3,3)) > 0.0f); CPPUNIT_ASSERT(density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT(density0->tree().isValueOn(Coord(24,3,3))); } }//testVolumeAdvect //////////////////////////////////////// void TestTools::testFloatApply() { typedef openvdb::FloatTree::ValueOnIter ValueIter; struct Local { static inline float op(float x) { return x * 2.f; } static inline void visit(const ValueIter& it) { it.setValue(op(*it)); } }; const float background = 1.0; openvdb::FloatTree tree(background); const int MIN = -1000, MAX = 1000, STEP = 50; openvdb::Coord xyz; for (int z = MIN; z < MAX; z += STEP) { xyz.setZ(z); for (int y = MIN; y < MAX; y += STEP) { xyz.setY(y); for (int x = MIN; x < MAX; x += STEP) { xyz.setX(x); tree.setValue(xyz, float(x + y + z)); } } } /// @todo set some tile values openvdb::tools::foreach(tree.begin(), Local::visit, /*threaded=*/true); float expected = Local::op(background); //CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, tree.background(), /*tolerance=*/0.0); //expected = Local::op(-background); //CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, -tree.background(), /*tolerance=*/0.0); for (openvdb::FloatTree::ValueOnCIter it = tree.cbeginValueOn(); it; ++it) { xyz = it.getCoord(); expected = Local::op(float(xyz[0] + xyz[1] + xyz[2])); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, it.getValue(), /*tolerance=*/0.0); } } //////////////////////////////////////// namespace { template struct MatMul { openvdb::math::Mat3s mat; MatMul(const openvdb::math::Mat3s& _mat): mat(_mat) {} openvdb::Vec3s xform(const openvdb::Vec3s& v) const { return mat.transform(v); } void operator()(const IterT& it) const { it.setValue(xform(*it)); } }; } void TestTools::testVectorApply() { 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(float(x), float(y), float(z))); } } } /// @todo set some tile values MatMul op(openvdb::math::Mat3s(1, 2, 3, -1, -2, -3, 3, 2, 1)); openvdb::tools::foreach(tree.beginValueOn(), op, /*threaded=*/true); openvdb::Vec3s expected; for (openvdb::VectorTree::ValueOnCIter it = tree.cbeginValueOn(); it; ++it) { xyz = it.getCoord(); expected = op.xform(openvdb::Vec3s(float(xyz[0]), float(xyz[1]), float(xyz[2]))); CPPUNIT_ASSERT_EQUAL(expected, it.getValue()); } } //////////////////////////////////////// namespace { struct AccumSum { int64_t sum; int joins; AccumSum(): sum(0), joins(0) {} void operator()(const openvdb::Int32Tree::ValueOnCIter& it) { if (it.isVoxelValue()) sum += *it; else sum += (*it) * it.getVoxelCount(); } void join(AccumSum& other) { sum += other.sum; joins += 1 + other.joins; } }; struct AccumLeafVoxelCount { 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, float(x + y + z)); } } } // Set some tile values. ftree.fill(CoordBBox(Coord(1024), Coord(1024 + 8 - 1)), 3 * 1024); // level-1 tile ftree.fill(CoordBBox(Coord(2048), Coord(2048 + 32 - 1)), 3 * 2048); // level-2 tile ftree.fill(CoordBBox(Coord(3072), Coord(3072 + 256 - 1)), 3 * 3072); // level-3 tile for (int shareOp = 0; shareOp <= 1; ++shareOp) { FloatToVec op; Tree323v vtree; openvdb::tools::transformValues(ftree.cbeginValueOn(), vtree, op, /*threaded=*/true, shareOp); // The tile count is accurate only if the functor is shared. Otherwise, // it is initialized to zero in the main thread and never changed. CPPUNIT_ASSERT_EQUAL(shareOp ? 3 : 0, int(op.numTiles)); Vec3s expected; for (Tree323v::ValueOnCIter it = vtree.cbeginValueOn(); it; ++it) { xyz = it.getCoord(); expected = op.toVec(float(xyz[0] + xyz[1] + xyz[2])); CPPUNIT_ASSERT_EQUAL(expected, it.getValue()); } // Check values inside the tiles. CPPUNIT_ASSERT_EQUAL(op.toVec(3 * 1024), vtree.getValue(Coord(1024 + 4))); CPPUNIT_ASSERT_EQUAL(op.toVec(3 * 2048), vtree.getValue(Coord(2048 + 16))); CPPUNIT_ASSERT_EQUAL(op.toVec(3 * 3072), vtree.getValue(Coord(3072 + 128))); } } //////////////////////////////////////// void TestTools::testUtil() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Vec3s; 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); } } //////////////////////////////////////// // See also TestGrid::testClipping() void TestTools::testClipping() { using namespace openvdb; FloatGrid cube(0.f); cube.fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/5.f, /*active=*/true); struct Local { static void validate(const FloatGrid& clipped) { const CoordBBox bbox = clipped.evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT_EQUAL(4, bbox.min().x()); CPPUNIT_ASSERT_EQUAL(4, bbox.min().y()); CPPUNIT_ASSERT_EQUAL(-6, bbox.min().z()); CPPUNIT_ASSERT_EQUAL(4, bbox.max().x()); CPPUNIT_ASSERT_EQUAL(4, bbox.max().y()); CPPUNIT_ASSERT_EQUAL(6, bbox.max().z()); CPPUNIT_ASSERT_EQUAL(6 + 6 + 1, int(clipped.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(2, int(clipped.constTree().leafCount())); FloatGrid::ConstAccessor acc = clipped.getConstAccessor(); const float bg = clipped.background(); Coord xyz; int &x = xyz[0], &y = xyz[1], &z = xyz[2]; for (x = -10; x <= 10; ++x) { for (y = -10; y <= 10; ++y) { for (z = -10; z <= 10; ++z) { if (x == 4 && y == 4 && z >= -6 && z <= 6) { CPPUNIT_ASSERT_EQUAL(5.f, acc.getValue(Coord(4, 4, z))); } else { CPPUNIT_ASSERT_EQUAL(bg, acc.getValue(Coord(x, y, z))); } } } } } }; { // Test clipping against a bounding box. BBoxd clipBox(Vec3d(4.0, 4.0, -6.0), Vec3d(4.9, 4.9, 6.0)); FloatGrid::Ptr clipped = tools::clip(cube, clipBox); Local::validate(*clipped); } { // Test clipping against a boolean mask grid. BoolGrid mask(false); mask.fill(CoordBBox(Coord(4, 4, -6), Coord(4, 4, 6)), true, true); FloatGrid::Ptr clipped = tools::clip(cube, mask); Local::validate(*clipped); } { // Test clipping against a non-boolean mask grid. Int32Grid mask(0); mask.fill(CoordBBox(Coord(4, 4, -6), Coord(4, 4, 6)), -5, true); FloatGrid::Ptr clipped = tools::clip(cube, mask); Local::validate(*clipped); } } void TestTools::testPrune() { /// @todo Add more unit-tests! using namespace openvdb; const float value = 5.345f; FloatTree tree(value); CPPUNIT_ASSERT_EQUAL(Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(Index32(1), tree.nonLeafCount()); // root node CPPUNIT_ASSERT(tree.empty()); tree.fill(CoordBBox(Coord(-10), Coord(10)), value, /*active=*/false); CPPUNIT_ASSERT(!tree.empty()); tools::prune(tree); CPPUNIT_ASSERT_EQUAL(Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(Index32(1), tree.nonLeafCount()); // root node CPPUNIT_ASSERT(tree.empty()); /* {// Bechmark serial prune util::CpuTimer timer; initialize();//required whenever I/O of OpenVDB files is performed! io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/crawler.vdb"); sourceFile.open(false);//disable delayed loading FloatGrid::Ptr grid = gridPtrCast(sourceFile.getGrids()->at(0)); const Index32 leafCount = grid->tree().leafCount(); timer.start("\nSerial tolerance prune"); grid->tree().prune(); timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, grid->tree().leafCount()); } {// Bechmark parallel prune util::CpuTimer timer; initialize();//required whenever I/O of OpenVDB files is performed! io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/crawler.vdb"); sourceFile.open(false);//disable delayed loading FloatGrid::Ptr grid = gridPtrCast(sourceFile.getGrids()->at(0)); const Index32 leafCount = grid->tree().leafCount(); timer.start("\nParallel tolerance prune"); tools::prune(grid->tree()); timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, grid->tree().leafCount()); } */ } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLeaf.cc0000644000000000000000000003213612603226506015115 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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(testIsConstant); CPPUNIT_TEST_SUITE_END(); void testBuffer(); void testGetValue(); void testSetValue(); void testIsValueSet(); void testProbeValue(); void testIterators(); void testEquivalence(); void testGetOrigin(); void testIteratorGetCoord(); void testNegativeIndexing(); void testIsConstant(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLeaf); typedef openvdb::tree::LeafNode LeafType; typedef LeafType::Buffer BufferType; using openvdb::Index; void TestLeaf::testBuffer() { {// access BufferType buf; for (Index i = 0; i < BufferType::size(); ++i) { buf.mData[i] = i; CPPUNIT_ASSERT(buf[i] == buf.mData[i]); } for (Index i = 0; i < BufferType::size(); ++i) { buf[i] = i; CPPUNIT_ASSERT_EQUAL(int(i), buf[i]); } } {// swap BufferType buf0, buf1, buf2; int *buf0Data = buf0.mData; int *buf1Data = buf1.mData; for (Index i = 0; i < BufferType::size(); ++i) { buf0[i] = i; buf1[i] = i * 2; } buf0.swap(buf1); CPPUNIT_ASSERT(buf0.mData == buf1Data); CPPUNIT_ASSERT(buf1.mData == buf0Data); buf1.swap(buf0); CPPUNIT_ASSERT(buf0.mData == buf0Data); CPPUNIT_ASSERT(buf1.mData == buf1Data); buf0.swap(buf2); CPPUNIT_ASSERT(buf2.mData == buf0Data); buf2.swap(buf0); CPPUNIT_ASSERT(buf0.mData == buf0Data); } } void TestLeaf::testGetValue() { LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.mBuffer[0] = 2; leaf.mBuffer[1] = 3; leaf.mBuffer[2] = 4; leaf.mBuffer[65] = 10; CPPUNIT_ASSERT_EQUAL(2, leaf.getValue(openvdb::Coord(0, 0, 0))); CPPUNIT_ASSERT_EQUAL(3, leaf.getValue(openvdb::Coord(0, 0, 1))); CPPUNIT_ASSERT_EQUAL(4, leaf.getValue(openvdb::Coord(0, 0, 2))); CPPUNIT_ASSERT_EQUAL(10, leaf.getValue(openvdb::Coord(1, 0, 1))); } void TestLeaf::testSetValue() { LeafType leaf(openvdb::Coord(0, 0, 0), 3); openvdb::Coord xyz(0, 0, 0); leaf.setValueOn(xyz, 10); CPPUNIT_ASSERT_EQUAL(10, leaf.getValue(xyz)); xyz.reset(7, 7, 7); leaf.setValueOn(xyz, 7); CPPUNIT_ASSERT_EQUAL(7, leaf.getValue(xyz)); leaf.setValueOnly(xyz, 10); CPPUNIT_ASSERT_EQUAL(10, leaf.getValue(xyz)); xyz.reset(2, 3, 6); leaf.setValueOn(xyz, 236); CPPUNIT_ASSERT_EQUAL(236, leaf.getValue(xyz)); leaf.setValueOff(xyz, 1); CPPUNIT_ASSERT_EQUAL(1, leaf.getValue(xyz)); CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); } void TestLeaf::testIsValueSet() { LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(1, 5, 7), 10); CPPUNIT_ASSERT(leaf.isValueOn(openvdb::Coord(1, 5, 7))); CPPUNIT_ASSERT(!leaf.isValueOn(openvdb::Coord(0, 5, 7))); CPPUNIT_ASSERT(!leaf.isValueOn(openvdb::Coord(1, 6, 7))); CPPUNIT_ASSERT(!leaf.isValueOn(openvdb::Coord(0, 5, 6))); } void TestLeaf::testProbeValue() { LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(1, 6, 5), 10); LeafType::ValueType val; CPPUNIT_ASSERT(leaf.probeValue(openvdb::Coord(1, 6, 5), val)); CPPUNIT_ASSERT(!leaf.probeValue(openvdb::Coord(1, 6, 4), val)); } void TestLeaf::testIterators() { LeafType leaf(openvdb::Coord(0, 0, 0), 2); leaf.setValueOn(openvdb::Coord(1, 2, 3), -3); leaf.setValueOn(openvdb::Coord(5, 2, 3), 4); LeafType::ValueType sum = 0; for (LeafType::ValueOnIter iter = leaf.beginValueOn(); iter; ++iter) sum += *iter; CPPUNIT_ASSERT_EQUAL((-3 + 4), sum); } void TestLeaf::testEquivalence() { LeafType leaf( openvdb::Coord(0, 0, 0), 2); LeafType leaf2(openvdb::Coord(0, 0, 0), 3); CPPUNIT_ASSERT(leaf != leaf2); for(openvdb::Index32 i = 0; i < LeafType::size(); ++i) { leaf.setValueOnly(i, i); leaf2.setValueOnly(i, i); } CPPUNIT_ASSERT(leaf == leaf2); // set some values. leaf.setValueOn(openvdb::Coord(0, 0, 0), 1); leaf.setValueOn(openvdb::Coord(0, 1, 0), 1); leaf.setValueOn(openvdb::Coord(1, 1, 0), 1); leaf.setValueOn(openvdb::Coord(1, 1, 2), 1); leaf2.setValueOn(openvdb::Coord(0, 0, 0), 1); leaf2.setValueOn(openvdb::Coord(0, 1, 0), 1); leaf2.setValueOn(openvdb::Coord(1, 1, 0), 1); leaf2.setValueOn(openvdb::Coord(1, 1, 2), 1); CPPUNIT_ASSERT(leaf == leaf2); leaf2.setValueOn(openvdb::Coord(0, 0, 1), 1); CPPUNIT_ASSERT(leaf != leaf2); leaf2.setValueOff(openvdb::Coord(0, 0, 1), 1); CPPUNIT_ASSERT(leaf == leaf2); } void TestLeaf::testGetOrigin() { { LeafType leaf(openvdb::Coord(1, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(0, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(8, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(8, 1, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(1024, 1, 3), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(128*8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(1023, 1, 3), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(127*8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(512, 512, 512), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(512, 512, 512), leaf.origin()); } { LeafType leaf(openvdb::Coord(2, 52, 515), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 48, 512), leaf.origin()); } } void TestLeaf::testIteratorGetCoord() { using namespace openvdb; LeafType leaf(openvdb::Coord(8, 8, 0), 2); CPPUNIT_ASSERT_EQUAL(Coord(8, 8, 0), leaf.origin()); leaf.setValueOn(Coord(1, 2, 3), -3); leaf.setValueOn(Coord(5, 2, 3), 4); LeafType::ValueOnIter iter = leaf.beginValueOn(); Coord xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(9, 10, 3), xyz); ++iter; xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(13, 10, 3), xyz); } void TestLeaf::testNegativeIndexing() { using namespace openvdb; LeafType leaf(openvdb::Coord(-9, -2, -8), 1); CPPUNIT_ASSERT_EQUAL(Coord(-16, -8, -8), leaf.origin()); leaf.setValueOn(Coord(1, 2, 3), -3); leaf.setValueOn(Coord(5, 2, 3), 4); CPPUNIT_ASSERT_EQUAL(-3, leaf.getValue(Coord(1, 2, 3))); CPPUNIT_ASSERT_EQUAL(4, leaf.getValue(Coord(5, 2, 3))); LeafType::ValueOnIter iter = leaf.beginValueOn(); Coord xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(-15, -6, -5), xyz); ++iter; xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(-11, -6, -5), xyz); } void TestLeaf::testIsConstant() { using namespace openvdb; const Coord origin(-9, -2, -8); {// check old version (v3.0 and older) with float // Acceptable range: first-value +/- tolerance const float val = 1.0f, tol = 0.01f; tree::LeafNode leaf(origin, val, true); float v = 0.0f; bool stat = false; CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val, v); leaf.setValueOff(0); CPPUNIT_ASSERT(!leaf.isConstant(v, stat, tol)); leaf.setValueOn(0); CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); leaf.setValueOn(0, val + 0.99f*tol); CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val + 0.99f*tol, v); leaf.setValueOn(0, val + 1.01f*tol); CPPUNIT_ASSERT(!leaf.isConstant(v, stat, tol)); } {// check old version (v3.0 and older) with double // Acceptable range: first-value +/- tolerance const double val = 1.0, tol = 0.00001; tree::LeafNode leaf(origin, val, true); double v = 0.0; bool stat = false; CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val, v); leaf.setValueOff(0); CPPUNIT_ASSERT(!leaf.isConstant(v, stat, tol)); leaf.setValueOn(0); CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); leaf.setValueOn(0, val + 0.99*tol); CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val + 0.99*tol, v); leaf.setValueOn(0, val + 1.01*tol); CPPUNIT_ASSERT(!leaf.isConstant(v, stat, tol)); } {// check newer version (v3.1 and never) with float // Acceptable range: (max+min)/2 +/- tolerance const float val = 1.0, tol = 0.01f; tree::LeafNode leaf(origin, val, true); float vmin = 0.0f, vmax = 0.0f; bool stat = false; CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val, vmin); CPPUNIT_ASSERT_EQUAL(val, vmax); leaf.setValueOff(0); CPPUNIT_ASSERT(!leaf.isConstant(vmin, vmax, stat, tol)); leaf.setValueOn(0); CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); leaf.setValueOn(0, val + 0.99f*tol); CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); CPPUNIT_ASSERT_EQUAL(val, vmin); CPPUNIT_ASSERT_EQUAL(val + 0.99f*tol, vmax); leaf.setValueOn(0, val + 1.99f*tol); CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); CPPUNIT_ASSERT_EQUAL(val, vmin); CPPUNIT_ASSERT_EQUAL(val + 1.99f*tol, vmax); leaf.setValueOn(0, val + 2.01f*tol); CPPUNIT_ASSERT(!leaf.isConstant(vmin, vmax, stat, tol)); } {// check newer version (v3.1 and never) with double // Acceptable range: (max+min)/2 +/- tolerance const double val = 1.0, tol = 0.000001; tree::LeafNode leaf(origin, val, true); double vmin = 0.0, vmax = 0.0; bool stat = false; CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val, vmin); CPPUNIT_ASSERT_EQUAL(val, vmax); leaf.setValueOff(0); CPPUNIT_ASSERT(!leaf.isConstant(vmin, vmax, stat, tol)); leaf.setValueOn(0); CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); leaf.setValueOn(0, val + 0.99*tol); CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); CPPUNIT_ASSERT_EQUAL(val, vmin); CPPUNIT_ASSERT_EQUAL(val + 0.99*tol, vmax); leaf.setValueOn(0, val + 1.99*tol); CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); CPPUNIT_ASSERT_EQUAL(val, vmin); CPPUNIT_ASSERT_EQUAL(val + 1.99*tol, vmax); leaf.setValueOn(0, val + 2.01*tol); CPPUNIT_ASSERT(!leaf.isConstant(vmin, vmax, stat, tol)); } } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMetadata.cc0000644000000000000000000001024012603226506015756 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestVec3Metadata.cc0000644000000000000000000001070212603226506016502 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestCpt.cc0000644000000000000000000005260712603226506015001 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include // for old GradientStencil #include "util.h" // for unittest_util::makeSphere() #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestCpt: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestCpt); CPPUNIT_TEST(testCpt); // Cpt in World Space CPPUNIT_TEST(testCptStencil); CPPUNIT_TEST(testCptTool); // Cpt tool CPPUNIT_TEST(testCptMaskedTool); CPPUNIT_TEST(testOldStyleStencils); // old stencil impl CPPUNIT_TEST_SUITE_END(); void testCpt(); void testCptStencil(); void testCptTool(); void testCptMaskedTool(); void testOldStyleStencils(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestCpt); void TestCpt::testCpt() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; { // unit voxel size tests FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); const FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const Vec3f center(35.0, 30.0f, 40.0f); const float radius=0;//point at {35,30,40} unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); AccessorType inAccessor = grid->getConstAccessor(); // this uses the gradient. Only test for a few maps, since the gradient is // tested elsewhere Coord xyz(35,30,30); math::TranslationMap translate; // Note the CPT::result is in continuous index space Vec3f P = math::CPT::result(translate, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); // CPT_RANGE::result is in the range of the map // CPT_RANGE::result = map.applyMap(CPT::result()) // for our tests, the map is an identity so in this special case // the two versions of the Cpt should exactly agree P = math::CPT_RANGE::result(translate, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); xyz.reset(35,30,35); P = math::CPT::result(translate, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); P = math::CPT_RANGE::result(translate, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); } { // NON-UNIT VOXEL SIZE double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); AccessorType inAccessor = grid->getConstAccessor(); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere math::AffineMap affine(voxel_size*math::Mat3d::identity()); Vec3f P = math::CPT::result(affine, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); P = math::CPT_RANGE::result(affine, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(10,P[2]); xyz.reset(12,16,10); P = math::CPT::result(affine, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); P = math::CPT_RANGE::result(affine, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(6,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); } { // NON-UNIFORM SCALING Vec3d voxel_sizes(0.5, 1, 0.5); math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::Ptr(new math::Transform(base_map))); CPPUNIT_ASSERT(grid->empty()); AccessorType inAccessor = grid->getConstAccessor(); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); Coord ijk = grid->transform().worldToIndexNodeCentered(Vec3d(10,8,10)); //Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere math::ScaleMap scale(voxel_sizes); Vec3f P; P = math::CPT::result(scale, inAccessor, ijk); ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); // world space result P = math::CPT_RANGE::result(scale, inAccessor, ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(16,P[0], 0.02 ); CPPUNIT_ASSERT_DOUBLES_EQUAL(8, P[1], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(10,P[2], 0.02); //xyz.reset(12,16,10); ijk = grid->transform().worldToIndexNodeCentered(Vec3d(6,8,5)); P = math::CPT::result(scale, inAccessor, ijk); ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); P = math::CPT_RANGE::result(scale, inAccessor, ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(6,P[0], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(8,P[1], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(0,P[2], 0.02); } } void TestCpt::testCptStencil() { using namespace openvdb; { // UNIT VOXEL TEST FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); const FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f ,30.0f, 40.0f); const float radius=0.0f; unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); // this uses the gradient. Only test for a few maps, since the gradient is // tested elsewhere math::SevenPointStencil sevenpt(*grid); math::SecondOrderDenseStencil dense_2nd(*grid); Coord xyz(35,30,30); CPPUNIT_ASSERT(tree.isValueOn(xyz)); sevenpt.moveTo(xyz); dense_2nd.moveTo(xyz); math::TranslationMap translate; // Note the CPT::result is in continuous index space Vec3f P = math::CPT::result(translate, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); // CPT_RANGE::result_stencil is in the range of the map // CPT_RANGE::result_stencil = map.applyMap(CPT::result_stencil()) // for our tests, the map is an identity so in this special case // the two versions of the Cpt should exactly agree P = math::CPT_RANGE::result(translate, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); xyz.reset(35,30,35); sevenpt.moveTo(xyz); dense_2nd.moveTo(xyz); CPPUNIT_ASSERT(tree.isValueOn(xyz)); P = math::CPT::result(translate, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); P = math::CPT_RANGE::result(translate, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); xyz.reset(35,30,30); sevenpt.moveTo(xyz); dense_2nd.moveTo(xyz); math::AffineMap affine; P = math::CPT::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); P = math::CPT_RANGE::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); xyz.reset(35,30,35); sevenpt.moveTo(xyz); dense_2nd.moveTo(xyz); CPPUNIT_ASSERT(tree.isValueOn(xyz)); P = math::CPT::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); CPPUNIT_ASSERT(tree.isValueOn(xyz)); P = math::CPT_RANGE::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); } { // NON-UNIT VOXEL SIZE double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); math::SecondOrderDenseStencil dense_2nd(*grid); Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere math::AffineMap affine(voxel_size*math::Mat3d::identity()); dense_2nd.moveTo(xyz); Vec3f P = math::CPT::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); P = math::CPT_RANGE::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(10,P[2]); xyz.reset(12,16,10); dense_2nd.moveTo(xyz); P = math::CPT::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); P = math::CPT_RANGE::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(6,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); } { // NON-UNIFORM SCALING Vec3d voxel_sizes(0.5, 1, 0.5); math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::Ptr(new math::Transform(base_map))); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); Coord ijk = grid->transform().worldToIndexNodeCentered(Vec3d(10,8,10)); math::SevenPointStencil sevenpt(*grid); sevenpt.moveTo(ijk); //Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere math::ScaleMap scale(voxel_sizes); Vec3f P; P = math::CPT::result(scale, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); // world space result P = math::CPT_RANGE::result(scale, sevenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(16,P[0], 0.02 ); CPPUNIT_ASSERT_DOUBLES_EQUAL(8, P[1], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(10,P[2], 0.02); //xyz.reset(12,16,10); ijk = grid->transform().worldToIndexNodeCentered(Vec3d(6,8,5)); sevenpt.moveTo(ijk); P = math::CPT::result(scale, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); P = math::CPT_RANGE::result(scale, sevenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(6,P[0], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(8,P[1], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(0,P[2], 0.02); } } void TestCpt::testCptTool() { using namespace openvdb; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); const FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=0;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); // run the tool typedef openvdb::tools::Cpt FloatCpt; FloatCpt cpt(*grid); FloatCpt::OutGridType::Ptr cptGrid = cpt.process(true/*threaded*/, false/*use world transform*/); FloatCpt::OutGridType::ConstAccessor cptAccessor = cptGrid->getConstAccessor(); Coord xyz(35,30,30); CPPUNIT_ASSERT(tree.isValueOn(xyz)); Vec3f P = cptAccessor.getValue(xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); xyz.reset(35,30,35); CPPUNIT_ASSERT(tree.isValueOn(xyz)); P = cptAccessor.getValue(xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); } void TestCpt::testCptMaskedTool() { using namespace openvdb; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); const FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=0;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); BoolGrid::Ptr maskGrid = BoolGrid::create(false); maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); // run the tool typedef openvdb::tools::Cpt FloatCpt; 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestStream.cc0000644000000000000000000002106212603226506015475 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include // for remove() #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(a, b) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((a), (b), /*tolerance=*/0.0); class TestStream: public CppUnit::TestCase { public: 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestParticlesToLevelSet.cc0000644000000000000000000005055512603226506020150 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestParticlesToLevelSet: public CppUnit::TestFixture { public: virtual void setUp() {openvdb::initialize();} virtual void tearDown() {openvdb::uninitialize();} void writeGrid(openvdb::GridBase::Ptr grid, std::string fileName) const { std::cout << "\nWriting \""<setName("TestParticlesToLevelSet"); openvdb::GridPtrVec grids; grids.push_back(grid); openvdb::io::File file(fileName + ".vdb"); file.write(grids); file.close(); } CPPUNIT_TEST_SUITE(TestParticlesToLevelSet); CPPUNIT_TEST(testMyParticleList); CPPUNIT_TEST(testRasterizeSpheres); CPPUNIT_TEST(testRasterizeSpheresAndId); CPPUNIT_TEST(testRasterizeTrails); CPPUNIT_TEST(testRasterizeTrailsAndId); CPPUNIT_TEST_SUITE_END(); void testMyParticleList(); void testRasterizeSpheres(); void testRasterizeSpheresAndId(); void testRasterizeTrails(); void testRasterizeTrailsAndId(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestParticlesToLevelSet); class MyParticleList { protected: struct MyParticle { openvdb::Vec3R p, v; openvdb::Real r; }; openvdb::Real mRadiusScale; openvdb::Real mVelocityScale; std::vector mParticleList; public: typedef openvdb::Vec3R value_type; MyParticleList(openvdb::Real rScale=1, openvdb::Real vScale=1) : mRadiusScale(rScale), mVelocityScale(vScale) {} void add(const openvdb::Vec3R &p, const openvdb::Real &r, const openvdb::Vec3R &v=openvdb::Vec3R(0,0,0)) { MyParticle pa; pa.p = p; pa.r = r; pa.v = v; mParticleList.push_back(pa); } /// @return coordinate bbox in the space of the specified transfrom openvdb::CoordBBox getBBox(const openvdb::GridBase& grid) { openvdb::CoordBBox bbox; openvdb::Coord &min= bbox.min(), &max = bbox.max(); openvdb::Vec3R pos; openvdb::Real rad, invDx = 1/grid.voxelSize()[0]; for (size_t n=0, e=this->size(); ngetPosRad(n, pos, rad); const openvdb::Vec3d xyz = grid.worldToIndex(pos); const openvdb::Real r = rad * invDx; for (int i=0; i<3; ++i) { min[i] = openvdb::math::Min(min[i], openvdb::math::Floor(xyz[i] - r)); max[i] = openvdb::math::Max(max[i], openvdb::math::Ceil( xyz[i] + r)); } } return bbox; } //typedef int AttributeType; // The methods below are only required for the unit-tests openvdb::Vec3R pos(int n) const {return mParticleList[n].p;} openvdb::Vec3R vel(int n) const {return mVelocityScale*mParticleList[n].v;} openvdb::Real radius(int n) const {return mRadiusScale*mParticleList[n].r;} ////////////////////////////////////////////////////////////////////////////// /// The methods below are the only ones required by tools::ParticleToLevelSet /// @note We return by value since the radius and velocities are modified /// by the scaling factors! Also these methods are all assumed to /// be thread-safe. /// Return the total number of particles in list. /// Always required! size_t size() const { return mParticleList.size(); } /// Get the world space position of n'th particle. /// Required by ParticledToLevelSet::rasterizeSphere(*this,radius). void getPos(size_t n, openvdb::Vec3R&pos) const { pos = mParticleList[n].p; } void getPosRad(size_t n, openvdb::Vec3R& pos, openvdb::Real& rad) const { pos = mParticleList[n].p; rad = mRadiusScale*mParticleList[n].r; } void getPosRadVel(size_t n, openvdb::Vec3R& pos, openvdb::Real& rad, openvdb::Vec3R& vel) const { pos = mParticleList[n].p; rad = mRadiusScale*mParticleList[n].r; vel = mVelocityScale*mParticleList[n].v; } // The method below is only required for attribute transfer void getAtt(size_t n, openvdb::Index32& att) const { att = openvdb::Index32(n); } }; void TestParticlesToLevelSet::testMyParticleList() { MyParticleList pa; CPPUNIT_ASSERT_EQUAL(0, int(pa.size())); pa.add(openvdb::Vec3R(10,10,10), 2, openvdb::Vec3R(1,0,0)); CPPUNIT_ASSERT_EQUAL(1, int(pa.size())); ASSERT_DOUBLES_EXACTLY_EQUAL(10, pa.pos(0)[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(10, pa.pos(0)[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(10, pa.pos(0)[2]); ASSERT_DOUBLES_EXACTLY_EQUAL(1 , pa.vel(0)[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(0)[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(0)[2]); ASSERT_DOUBLES_EXACTLY_EQUAL(2 , pa.radius(0)); pa.add(openvdb::Vec3R(20,20,20), 3); CPPUNIT_ASSERT_EQUAL(2, int(pa.size())); ASSERT_DOUBLES_EXACTLY_EQUAL(20, pa.pos(1)[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(20, pa.pos(1)[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(20, pa.pos(1)[2]); ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(1)[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(1)[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(1)[2]); ASSERT_DOUBLES_EXACTLY_EQUAL(3 , pa.radius(1)); const float voxelSize = 0.5f, halfWidth = 4.0f; openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); openvdb::CoordBBox bbox = pa.getBBox(*ls); ASSERT_DOUBLES_EXACTLY_EQUAL((10-2)/voxelSize, bbox.min()[0]); ASSERT_DOUBLES_EXACTLY_EQUAL((10-2)/voxelSize, bbox.min()[1]); ASSERT_DOUBLES_EXACTLY_EQUAL((10-2)/voxelSize, bbox.min()[2]); ASSERT_DOUBLES_EXACTLY_EQUAL((20+3)/voxelSize, bbox.max()[0]); ASSERT_DOUBLES_EXACTLY_EQUAL((20+3)/voxelSize, bbox.max()[1]); ASSERT_DOUBLES_EXACTLY_EQUAL((20+3)/voxelSize, bbox.max()[2]); } void TestParticlesToLevelSet::testRasterizeSpheres() { MyParticleList pa; pa.add(openvdb::Vec3R(10,10,10), 2); pa.add(openvdb::Vec3R(20,20,20), 2); // testing CSG pa.add(openvdb::Vec3R(31.0,31,31), 5); pa.add(openvdb::Vec3R(31.5,31,31), 5); pa.add(openvdb::Vec3R(32.0,31,31), 5); pa.add(openvdb::Vec3R(32.5,31,31), 5); pa.add(openvdb::Vec3R(33.0,31,31), 5); pa.add(openvdb::Vec3R(33.5,31,31), 5); pa.add(openvdb::Vec3R(34.0,31,31), 5); pa.add(openvdb::Vec3R(34.5,31,31), 5); pa.add(openvdb::Vec3R(35.0,31,31), 5); pa.add(openvdb::Vec3R(35.5,31,31), 5); pa.add(openvdb::Vec3R(36.0,31,31), 5); CPPUNIT_ASSERT_EQUAL(13, int(pa.size())); const float voxelSize = 1.0f, halfWidth = 2.0f; openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); openvdb::tools::ParticlesToLevelSet raster(*ls); raster.setGrainSize(1);//a value of zero disables threading raster.rasterizeSpheres(pa); raster.finalize(); //openvdb::FloatGrid::Ptr ls = raster.getSdfGrid(); //ls->tree().print(std::cout,4); //this->writeGrid(ls, "testRasterizeSpheres"); ASSERT_DOUBLES_EXACTLY_EQUAL(halfWidth * voxelSize, ls->tree().getValue(openvdb::Coord( 0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord( 6,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord( 7,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord( 8,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord( 9,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(10,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(11,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(12,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(13,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(14,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,16,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,17,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,18,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,19,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(20,20,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,21,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,22,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,23,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,24,20))); {// full but slow test of all voxels openvdb::CoordBBox bbox = pa.getBBox(*ls); bbox.expand(static_cast(halfWidth)+1); openvdb::Index64 count=0; const float outside = ls->background(), inside = -outside; const openvdb::Coord &min=bbox.min(), &max=bbox.max(); for (openvdb::Coord ijk=min; ijk[0]indexToWorld(ijk.asVec3d()); double dist = (xyz-pa.pos(0)).length()-pa.radius(0); for (int i = 1, s = int(pa.size()); i < s; ++i) { dist=openvdb::math::Min(dist,(xyz-pa.pos(i)).length()-pa.radius(i)); } const float val = ls->tree().getValue(ijk); if (dist >= outside) { CPPUNIT_ASSERT_DOUBLES_EQUAL(outside, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); } else if( dist <= inside ) { CPPUNIT_ASSERT_DOUBLES_EQUAL(inside, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); } else { CPPUNIT_ASSERT_DOUBLES_EQUAL( dist, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOn(ijk)); ++count; } } } } //std::cerr << "\nExpected active voxel count = " << count // << ", actual active voxle count = " // << ls->activeVoxelCount() << std::endl; CPPUNIT_ASSERT_EQUAL(count, ls->activeVoxelCount()); } } void TestParticlesToLevelSet::testRasterizeSpheresAndId() { MyParticleList pa(0.5f); pa.add(openvdb::Vec3R(10,10,10), 4); pa.add(openvdb::Vec3R(20,20,20), 4); // testing CSG pa.add(openvdb::Vec3R(31.0,31,31),10); pa.add(openvdb::Vec3R(31.5,31,31),10); pa.add(openvdb::Vec3R(32.0,31,31),10); pa.add(openvdb::Vec3R(32.5,31,31),10); pa.add(openvdb::Vec3R(33.0,31,31),10); pa.add(openvdb::Vec3R(33.5,31,31),10); pa.add(openvdb::Vec3R(34.0,31,31),10); pa.add(openvdb::Vec3R(34.5,31,31),10); pa.add(openvdb::Vec3R(35.0,31,31),10); pa.add(openvdb::Vec3R(35.5,31,31),10); pa.add(openvdb::Vec3R(36.0,31,31),10); CPPUNIT_ASSERT_EQUAL(13, int(pa.size())); typedef openvdb::tools::ParticlesToLevelSet RasterT; const float voxelSize = 1.0f, halfWidth = 2.0f; openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); RasterT raster(*ls); raster.setGrainSize(1);//a value of zero disables threading raster.rasterizeSpheres(pa); raster.finalize(); const RasterT::AttGridType::Ptr id = raster.attributeGrid(); int minVal = std::numeric_limits::max(), maxVal = -minVal; for (RasterT::AttGridType::ValueOnCIter i=id->cbeginValueOn(); i; ++i) { minVal = openvdb::math::Min(minVal, int(*i)); maxVal = openvdb::math::Max(maxVal, int(*i)); } CPPUNIT_ASSERT_EQUAL(0 , minVal); CPPUNIT_ASSERT_EQUAL(12, maxVal); //grid.tree().print(std::cout,4); //id->print(std::cout,4); //this->writeGrid(ls, "testRasterizeSpheres"); ASSERT_DOUBLES_EXACTLY_EQUAL(halfWidth * voxelSize, ls->tree().getValue(openvdb::Coord( 0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord( 6,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord( 7,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord( 8,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord( 9,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(10,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(11,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(12,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(13,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(14,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,16,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,17,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,18,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,19,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(20,20,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,21,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,22,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,23,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,24,20))); {// full but slow test of all voxels openvdb::CoordBBox bbox = pa.getBBox(*ls); bbox.expand(static_cast(halfWidth)+1); openvdb::Index64 count = 0; const float outside = ls->background(), inside = -outside; const openvdb::Coord &min=bbox.min(), &max=bbox.max(); for (openvdb::Coord ijk=min; ijk[0]indexToWorld(ijk.asVec3d()); double dist = (xyz-pa.pos(0)).length()-pa.radius(0); openvdb::Index32 k =0; for (int i = 1, s = int(pa.size()); i < s; ++i) { double d = (xyz-pa.pos(i)).length()-pa.radius(i); if (dtree().getValue(ijk); openvdb::Index32 m = id->tree().getValue(ijk); if (dist >= outside) { CPPUNIT_ASSERT_DOUBLES_EQUAL(outside, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); //CPPUNIT_ASSERT_EQUAL(openvdb::util::INVALID_IDX, m); CPPUNIT_ASSERT(id->tree().isValueOff(ijk)); } else if( dist <= inside ) { CPPUNIT_ASSERT_DOUBLES_EQUAL(inside, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); //CPPUNIT_ASSERT_EQUAL(openvdb::util::INVALID_IDX, m); CPPUNIT_ASSERT(id->tree().isValueOff(ijk)); } else { CPPUNIT_ASSERT_DOUBLES_EQUAL( dist, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOn(ijk)); CPPUNIT_ASSERT_EQUAL(k, m); CPPUNIT_ASSERT(id->tree().isValueOn(ijk)); ++count; } } } } //std::cerr << "\nExpected active voxel count = " << count // << ", actual active voxle count = " // << ls->activeVoxelCount() << std::endl; CPPUNIT_ASSERT_EQUAL(count, ls->activeVoxelCount()); } } /// This is not really a conventional unit-test since the result of /// the tests are written to a file and need to be visually verified! void TestParticlesToLevelSet::testRasterizeTrails() { const float voxelSize = 1.0f, halfWidth = 2.0f; openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); MyParticleList pa(1,5); // This particle radius = 1 < 1.5 i.e. it's below the Nyquist frequency and hence ignored pa.add(openvdb::Vec3R( 0, 0, 0), 1, openvdb::Vec3R( 0, 1, 0)); pa.add(openvdb::Vec3R(-10,-10,-10), 2, openvdb::Vec3R( 2, 0, 0)); pa.add(openvdb::Vec3R( 10, 10, 10), 3, openvdb::Vec3R( 0, 1, 0)); pa.add(openvdb::Vec3R( 0, 0, 0), 6, openvdb::Vec3R( 0, 0,-5)); pa.add(openvdb::Vec3R( 20, 0, 0), 2, openvdb::Vec3R( 0, 0, 0)); openvdb::tools::ParticlesToLevelSet raster(*ls); raster.rasterizeTrails(pa, 0.75);//scale offset between two instances //ls->tree().print(std::cout, 4); //this->writeGrid(ls, "testRasterizeTrails"); } void TestParticlesToLevelSet::testRasterizeTrailsAndId() { MyParticleList pa(1,5); // This particle radius = 1 < 1.5 i.e. it's below the Nyquist frequency and hence ignored pa.add(openvdb::Vec3R( 0, 0, 0), 1, openvdb::Vec3R( 0, 1, 0)); pa.add(openvdb::Vec3R(-10,-10,-10), 2, openvdb::Vec3R( 2, 0, 0)); pa.add(openvdb::Vec3R( 10, 10, 10), 3, openvdb::Vec3R( 0, 1, 0)); pa.add(openvdb::Vec3R( 0, 0, 0), 6, openvdb::Vec3R( 0, 0,-5)); typedef openvdb::tools::ParticlesToLevelSet RasterT; const float voxelSize = 1.0f, halfWidth = 2.0f; openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); RasterT raster(*ls); raster.rasterizeTrails(pa, 0.75);//scale offset between two instances raster.finalize(); const RasterT::AttGridType::Ptr id = raster.attributeGrid(); CPPUNIT_ASSERT(!ls->empty()); CPPUNIT_ASSERT(!id->empty()); CPPUNIT_ASSERT_EQUAL(ls->activeVoxelCount(),id->activeVoxelCount()); int min = std::numeric_limits::max(), max = -min; for (RasterT::AttGridType::ValueOnCIter i=id->cbeginValueOn(); i; ++i) { min = openvdb::math::Min(min, int(*i)); max = openvdb::math::Max(max, int(*i)); } CPPUNIT_ASSERT_EQUAL(1, min);//first particle is ignored because of its small rdadius! CPPUNIT_ASSERT_EQUAL(3, max); //ls->tree().print(std::cout, 4); //this->writeGrid(ls, "testRasterizeTrails"); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestPointIndexGrid.cc0000644000000000000000000002354412603226506017140 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include // for math::Random01 #include #include #include #include class TestPointIndexGrid: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestPointIndexGrid); CPPUNIT_TEST(testPointIndexGrid); CPPUNIT_TEST(testPointIndexFilter); CPPUNIT_TEST_SUITE_END(); void testPointIndexGrid(); void testPointIndexFilter(); private: // Generate random points by uniformly distributing points // on a unit-sphere. void genPoints(const int numPoints, std::vector& points) const { // init openvdb::math::Random01 randNumber(0); const int n = int(std::sqrt(double(numPoints))); const double xScale = (2.0 * M_PI) / double(n); const double yScale = M_PI / double(n); double x, y, theta, phi; openvdb::Vec3R pos; points.reserve(n*n); // loop over a [0 to n) x [0 to n) grid. for (int a = 0; a < n; ++a) { for (int b = 0; b < n; ++b) { // jitter, move to random pos. inside the current cell x = double(a) + randNumber(); y = double(b) + randNumber(); // remap to a lat/long map theta = y * yScale; // [0 to PI] phi = x * xScale; // [0 to 2PI] // convert to cartesian coordinates on a unit sphere. // spherical coordinate triplet (r=1, theta, phi) pos[0] = std::sin(theta)*std::cos(phi); pos[1] = std::sin(theta)*std::sin(phi); pos[2] = std::cos(theta); points.push_back(pos); } } } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestPointIndexGrid); //////////////////////////////////////// namespace { class PointList { public: typedef openvdb::Vec3R value_type; PointList(const std::vector& points) : mPoints(&points) { } size_t size() const { return mPoints->size(); } void getPos(size_t n, openvdb::Vec3R& xyz) const { xyz = (*mPoints)[n]; } protected: std::vector const * const mPoints; }; // PointList template bool hasDuplicates(const std::vector& items) { std::vector vec(items); std::sort(vec.begin(), vec.end()); size_t duplicates = 0; for (size_t n = 1, N = vec.size(); n < N; ++n) { if (vec[n] == vec[n-1]) ++duplicates; } return duplicates != 0; } template struct WeightedAverageAccumulator { typedef T ValueType; WeightedAverageAccumulator(T const * const array, const T radius) : mValues(array), mInvRadius(1.0/radius), mWeightSum(0.0), mValueSum(0.0) {} void reset() { mWeightSum = mValueSum = T(0.0); } void operator()(const T distSqr, const size_t pointIndex) { const T weight = T(1.0) - openvdb::math::Sqrt(distSqr) * mInvRadius; mWeightSum += weight; mValueSum += weight * mValues[pointIndex]; } T result() const { return mWeightSum > T(0.0) ? mValueSum / mWeightSum : T(0.0); } private: T const * const mValues; const T mInvRadius; T mWeightSum, mValueSum; }; // struct WeightedAverageAccumulator } // namespace //////////////////////////////////////// void TestPointIndexGrid::testPointIndexGrid() { const float voxelSize = 0.01f; const openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize); // generate points std::vector points; genPoints(40000, points); PointList pointList(points); // construct data structure typedef openvdb::tools::PointIndexGrid PointIndexGrid; PointIndexGrid::Ptr pointGridPtr = openvdb::tools::createPointIndexGrid(pointList, *transform); openvdb::CoordBBox bbox; pointGridPtr->tree().evalActiveVoxelBoundingBox(bbox); // coord bbox search typedef PointIndexGrid::ConstAccessor ConstAccessor; typedef openvdb::tools::PointIndexIterator<> PointIndexIterator; ConstAccessor acc = pointGridPtr->getConstAccessor(); PointIndexIterator it(bbox, acc); CPPUNIT_ASSERT(it.test()); CPPUNIT_ASSERT_EQUAL(points.size(), it.size()); // fractional bbox search openvdb::BBoxd region(bbox.min().asVec3d(), bbox.max().asVec3d()); // points are bucketed in a cell-centered fashion, we need to pad the // coordinate range to get the same search region in the fractional bbox. region.expand(voxelSize * 0.5); it.searchAndUpdate(region, acc, pointList, *transform); CPPUNIT_ASSERT(it.test()); CPPUNIT_ASSERT_EQUAL(points.size(), it.size()); { std::vector vec; vec.reserve(it.size()); for (; it; ++it) { vec.push_back(*it); } CPPUNIT_ASSERT_EQUAL(vec.size(), it.size()); CPPUNIT_ASSERT(!hasDuplicates(vec)); } // radial search openvdb::Vec3d center = region.getCenter(); double radius = region.extents().x() * 0.5; it.searchAndUpdate(center, radius, acc, pointList, *transform); CPPUNIT_ASSERT(it.test()); CPPUNIT_ASSERT_EQUAL(points.size(), it.size()); { std::vector vec; vec.reserve(it.size()); for (; it; ++it) { vec.push_back(*it); } CPPUNIT_ASSERT_EQUAL(vec.size(), it.size()); CPPUNIT_ASSERT(!hasDuplicates(vec)); } center = region.min(); it.searchAndUpdate(center, radius, acc, pointList, *transform); CPPUNIT_ASSERT(it.test()); { std::vector vec; vec.reserve(it.size()); for (; it; ++it) { vec.push_back(*it); } CPPUNIT_ASSERT_EQUAL(vec.size(), it.size()); CPPUNIT_ASSERT(!hasDuplicates(vec)); // check that no points where missed. std::vector indexMask(points.size(), 0); for (size_t n = 0, N = vec.size(); n < N; ++n) { indexMask[vec[n]] = 1; } const double r2 = radius * radius; openvdb::Vec3R v; for (size_t n = 0, N = indexMask.size(); n < N; ++n) { v = center - transform->worldToIndex(points[n]); if (indexMask[n] == 0) { CPPUNIT_ASSERT(!(v.lengthSqr() < r2)); } else { CPPUNIT_ASSERT(v.lengthSqr() < r2); } } } // Check partitioning CPPUNIT_ASSERT(openvdb::tools::isValidPartition(pointList, *pointGridPtr)); points[10000].x() += 1.5; // manually modify a few points. points[20000].x() += 1.5; points[30000].x() += 1.5; CPPUNIT_ASSERT(!openvdb::tools::isValidPartition(pointList, *pointGridPtr)); PointIndexGrid::Ptr pointGrid2Ptr = openvdb::tools::getValidPointIndexGrid(pointList, pointGridPtr); CPPUNIT_ASSERT(openvdb::tools::isValidPartition(pointList, *pointGrid2Ptr)); } void TestPointIndexGrid::testPointIndexFilter() { // generate points const float voxelSize = 0.01f; const size_t pointCount = 10000; const openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize); std::vector points; genPoints(pointCount, points); PointList pointList(points); // construct data structure typedef openvdb::tools::PointIndexGrid PointIndexGrid; PointIndexGrid::Ptr pointGridPtr = openvdb::tools::createPointIndexGrid(pointList, *transform); std::vector pointDensity(pointCount, 1.0); openvdb::tools::PointIndexFilter filter(pointList, pointGridPtr->tree(), pointGridPtr->transform()); const double radius = 3.0 * voxelSize; WeightedAverageAccumulator accumulator(&pointDensity.front(), radius); double sum = 0.0; for (size_t n = 0, N = points.size(); n < N; ++n) { accumulator.reset(); filter.searchAndApply(points[n], radius, accumulator); sum += accumulator.result(); } CPPUNIT_ASSERT_DOUBLES_EQUAL(sum, double(points.size()), 1e-6); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/util.h0000644000000000000000000001300112603226506014213 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_UNITTEST_UTIL_HAS_BEEN_INCLUDED #define OPENVDB_UNITTEST_UTIL_HAS_BEEN_INCLUDED #include #include // for pruneLevelSet #include namespace unittest_util { enum SphereMode { SPHERE_DENSE, SPHERE_DENSE_NARROW_BAND, SPHERE_SPARSE_NARROW_BAND }; /// @brief Generates the signed distance to a sphere located at @a center /// and with a specified @a radius (both in world coordinates). Only voxels /// in the domain [0,0,0] -> @a dim are considered. Also note that the /// level set is either dense, dense narrow-band or sparse narrow-band. /// /// @note This method is VERY SLOW and should only be used for debugging purposes! /// However it works for any transform and even with open level sets. /// A faster approch for closed narrow band generation is to only set voxels /// sparsely and then use grid::signedFloodFill to 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]((p-center).length() - radius); if (dist <= 0) acc.setValue(xyz, true); } } } } // This method will soon be replaced by the one above!!!!! template inline void makeSphere(const openvdb::Coord& dim, const openvdb::Vec3f& center, float radius, GridType &grid, float dx, SphereMode mode) { grid.setTransform(openvdb::math::Transform::createLinearTransform(/*voxel size=*/dx)); makeSphere(dim, center, radius, grid, mode); } // @todo makePlane } // namespace unittest_util #endif // OPENVDB_UNITTEST_UTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTreeVisitor.cc0000644000000000000000000003060512603226506016524 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file TestTreeVisitor.h /// /// @author Peter Cucka #include #include #include #include #include #include #include class TestTreeVisitor: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestTreeVisitor); CPPUNIT_TEST(testVisitTreeBool); CPPUNIT_TEST(testVisitTreeInt32); CPPUNIT_TEST(testVisitTreeFloat); CPPUNIT_TEST(testVisitTreeVec2I); CPPUNIT_TEST(testVisitTreeVec3S); CPPUNIT_TEST(testVisit2Trees); CPPUNIT_TEST_SUITE_END(); void testVisitTreeBool() { visitTree(); } void testVisitTreeInt32() { visitTree(); } void testVisitTreeFloat() { visitTree(); } void testVisitTreeVec2I() { visitTree(); } void testVisitTreeVec3S() { visitTree(); } void testVisit2Trees(); private: template TreeT createTestTree() const; template void visitTree(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTreeVisitor); //////////////////////////////////////// template TreeT TestTreeVisitor::createTestTree() const { typedef typename TreeT::ValueType ValueT; const ValueT zero = openvdb::zeroVal(), one = zero + 1; // Create a sparse test tree comprising the eight corners of // a 200 x 200 x 200 cube. TreeT tree(/*background=*/one); tree.setValue(openvdb::Coord( 0, 0, 0), /*value=*/zero); tree.setValue(openvdb::Coord(200, 0, 0), zero); tree.setValue(openvdb::Coord( 0, 200, 0), zero); tree.setValue(openvdb::Coord( 0, 0, 200), zero); tree.setValue(openvdb::Coord(200, 0, 200), zero); tree.setValue(openvdb::Coord( 0, 200, 200), zero); tree.setValue(openvdb::Coord(200, 200, 0), zero); tree.setValue(openvdb::Coord(200, 200, 200), zero); // Verify that the bounding box of all On values is 200 x 200 x 200. openvdb::CoordBBox bbox; CPPUNIT_ASSERT(tree.evalActiveVoxelBoundingBox(bbox)); CPPUNIT_ASSERT(bbox.min() == openvdb::Coord(0, 0, 0)); CPPUNIT_ASSERT(bbox.max() == openvdb::Coord(200, 200, 200)); return tree; } //////////////////////////////////////// namespace { /// Single-tree visitor that accumulates node counts class Visitor { public: typedef std::map > NodeMap; Visitor(): mSkipLeafNodes(false) { reset(); } void reset() { mSkipLeafNodes = false; mNodes.clear(); mNonConstIterUseCount = mConstIterUseCount = 0; } void setSkipLeafNodes(bool b) { mSkipLeafNodes = b; } template bool operator()(IterT& iter) { incrementIterUseCount(boost::is_const::value); CPPUNIT_ASSERT(iter.getParentNode() != NULL); if (mSkipLeafNodes && iter.parent().getLevel() == 1) return true; typedef typename IterT::NonConstValueType ValueT; typedef typename IterT::ChildNodeType ChildT; ValueT value; if (const ChildT* child = iter.probeChild(value)) { insertChild(child); } return false; } openvdb::Index leafCount() const { NodeMap::const_iterator it = mNodes.find(0); return openvdb::Index((it != mNodes.end()) ? it->second.size() : 0); } openvdb::Index nonLeafCount() const { openvdb::Index count = 1; // root node for (NodeMap::const_iterator i = mNodes.begin(), e = mNodes.end(); i != e; ++i) { if (i->first != 0) count = openvdb::Index(count + i->second.size()); } return count; } bool usedOnlyConstIterators() const { return (mConstIterUseCount > 0 && mNonConstIterUseCount == 0); } bool usedOnlyNonConstIterators() const { return (mConstIterUseCount == 0 && mNonConstIterUseCount > 0); } private: template void insertChild(const ChildT* child) { if (child != NULL) { const openvdb::Index level = child->getLevel(); if (!mSkipLeafNodes || level > 0) { mNodes[level].insert(child); } } } void incrementIterUseCount(bool isConst) { if (isConst) ++mConstIterUseCount; else ++mNonConstIterUseCount; } bool mSkipLeafNodes; NodeMap mNodes; int mNonConstIterUseCount, mConstIterUseCount; }; /// Specialization for LeafNode iterators, whose ChildNodeType is void /// (therefore can't call child->getLevel()) template<> inline void Visitor::insertChild(const void*) {} } // unnamed namespace template void TestTreeVisitor::visitTree() { TreeT tree = createTestTree(); { // Traverse the tree, accumulating node counts. Visitor visitor; const_cast(tree).visit(visitor); CPPUNIT_ASSERT(visitor.usedOnlyConstIterators()); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.leafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.nonLeafCount()); } { // Traverse the tree, accumulating node counts as above, // but using non-const iterators. Visitor visitor; tree.visit(visitor); CPPUNIT_ASSERT(visitor.usedOnlyNonConstIterators()); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.leafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.nonLeafCount()); } { // Traverse the tree, accumulating counts of non-leaf nodes only. Visitor visitor; visitor.setSkipLeafNodes(true); const_cast(tree).visit(visitor); CPPUNIT_ASSERT(visitor.usedOnlyConstIterators()); CPPUNIT_ASSERT_EQUAL(0U, visitor.leafCount()); // leaf nodes were skipped CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.nonLeafCount()); } } //////////////////////////////////////// namespace { /// Two-tree visitor that accumulates node counts class Visitor2 { public: typedef std::map > NodeMap; Visitor2() { reset(); } void reset() { mSkipALeafNodes = mSkipBLeafNodes = false; mANodeCount.clear(); mBNodeCount.clear(); } void setSkipALeafNodes(bool b) { mSkipALeafNodes = b; } void setSkipBLeafNodes(bool b) { mSkipBLeafNodes = b; } openvdb::Index aLeafCount() const { return leafCount(/*useA=*/true); } openvdb::Index bLeafCount() const { return leafCount(/*useA=*/false); } openvdb::Index aNonLeafCount() const { return nonLeafCount(/*useA=*/true); } openvdb::Index bNonLeafCount() const { return nonLeafCount(/*useA=*/false); } template int operator()(AIterT& aIter, BIterT& bIter) { CPPUNIT_ASSERT(aIter.getParentNode() != NULL); CPPUNIT_ASSERT(bIter.getParentNode() != NULL); typename AIterT::NodeType& aNode = aIter.parent(); typename BIterT::NodeType& bNode = bIter.parent(); const openvdb::Index aLevel = aNode.getLevel(), bLevel = bNode.getLevel(); mANodeCount[aLevel].insert(&aNode); mBNodeCount[bLevel].insert(&bNode); int skipBranch = 0; if (aLevel == 1 && mSkipALeafNodes) skipBranch = (skipBranch | 1); if (bLevel == 1 && mSkipBLeafNodes) skipBranch = (skipBranch | 2); return skipBranch; } private: openvdb::Index leafCount(bool useA) const { const NodeMap& theMap = (useA ? mANodeCount : mBNodeCount); NodeMap::const_iterator it = theMap.find(0); if (it != theMap.end()) return openvdb::Index(it->second.size()); return 0; } openvdb::Index nonLeafCount(bool useA) const { openvdb::Index count = 0; const NodeMap& theMap = (useA ? mANodeCount : mBNodeCount); for (NodeMap::const_iterator i = theMap.begin(), e = theMap.end(); i != e; ++i) { if (i->first != 0) count = openvdb::Index(count + i->second.size()); } return count; } bool mSkipALeafNodes, mSkipBLeafNodes; NodeMap mANodeCount, mBNodeCount; }; } // unnamed namespace void TestTreeVisitor::testVisit2Trees() { typedef openvdb::FloatTree TreeT; typedef openvdb::VectorTree Tree2T; typedef TreeT::ValueType ValueT; // Create a test tree. TreeT tree = createTestTree(); // Create another test tree of a different type but with the same topology. Tree2T tree2 = createTestTree(); // Traverse both trees. Visitor2 visitor; tree.visit2(tree2, visitor); //CPPUNIT_ASSERT(visitor.usedOnlyConstIterators()); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.aLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.bLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.aNonLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.bNonLeafCount()); visitor.reset(); // Change the topology of the first tree. tree.setValue(openvdb::Coord(-200, -200, -200), openvdb::zeroVal()); // Traverse both trees. tree.visit2(tree2, visitor); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.aLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.bLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.aNonLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.bNonLeafCount()); visitor.reset(); // Traverse the two trees in the opposite order. tree2.visit2(tree, visitor); CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.aLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.bLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.aNonLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.bNonLeafCount()); // Repeat, skipping leaf nodes of tree2. visitor.reset(); visitor.setSkipALeafNodes(true); tree2.visit2(tree, visitor); CPPUNIT_ASSERT_EQUAL(0U, visitor.aLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.bLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.aNonLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.bNonLeafCount()); // Repeat, skipping leaf nodes of tree. visitor.reset(); visitor.setSkipBLeafNodes(true); tree2.visit2(tree, visitor); CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.aLeafCount()); CPPUNIT_ASSERT_EQUAL(0U, visitor.bLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.aNonLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.bNonLeafCount()); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTreeCombine.cc0000644000000000000000000010007312603226506016436 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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() #include // for std::numeric_limits #include // for boost::math::isnan() and isinf() #define TEST_CSG_VERBOSE 0 #if TEST_CSG_VERBOSE #include #endif namespace { typedef openvdb::tree::Tree4::Type Float433Tree; typedef openvdb::Grid Float433Grid; } class TestTreeCombine: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); Float433Grid::registerGrid(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestTreeCombine); CPPUNIT_TEST(testCombine); CPPUNIT_TEST(testCombine2); CPPUNIT_TEST(testCompMax); CPPUNIT_TEST(testCompMin); CPPUNIT_TEST(testCompSum); CPPUNIT_TEST(testCompProd); CPPUNIT_TEST(testCompDiv); CPPUNIT_TEST(testCompDivByZero); CPPUNIT_TEST(testCompReplace); CPPUNIT_TEST(testBoolTree); #ifdef DWA_OPENVDB CPPUNIT_TEST(testCsg); #endif CPPUNIT_TEST_SUITE_END(); void testCombine(); void testCombine2(); void testCompMax(); void testCompMin(); void testCompSum(); void testCompProd(); void testCompDiv(); void testCompDivByZero(); 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&); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTreeCombine); //////////////////////////////////////// namespace { namespace Local { template struct OrderDependentCombineOp { OrderDependentCombineOp() {} void operator()(const ValueT& a, const ValueT& b, ValueT& result) const { result = a + 100 * b; // result is order-dependent on A and B } }; /// Test Tree::combine(), which takes a functor that accepts three arguments /// (the a, b and result values). template void combine(TreeT& a, TreeT& b) { a.combine(b, OrderDependentCombineOp()); } /// Test Tree::combineExtended(), which takes a functor that accepts a single /// CombineArgs argument, in which the functor can return a computed active state /// for the output value. template void extendedCombine(TreeT& a, TreeT& b) { typedef typename TreeT::ValueType ValueT; struct ArgsOp { static void order(openvdb::CombineArgs& args) { // The result is order-dependent on A and B. args.setResult(args.a() + 100 * args.b()); args.setResultIsActive(args.aIsActive() || args.bIsActive()); } }; a.combineExtended(b, ArgsOp::order); } template void compMax(TreeT& a, TreeT& b) { openvdb::tools::compMax(a, b); } template void compMin(TreeT& a, TreeT& b) { openvdb::tools::compMin(a, b); } template void compSum(TreeT& a, TreeT& b) { openvdb::tools::compSum(a, b); } template void compMul(TreeT& a, TreeT& b) { openvdb::tools::compMul(a, b); }\ template void compDiv(TreeT& a, TreeT& b) { openvdb::tools::compDiv(a, b); }\ inline float orderf(float a, float b) { return a + 100 * b; } inline float maxf(float a, float b) { return std::max(a, b); } inline float minf(float a, float b) { return std::min(a, b); } inline float sumf(float a, float b) { return a + b; } inline float mulf(float a, float b) { return a * b; } inline float divf(float a, float b) { return a / b; } inline openvdb::Vec3f orderv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a+100*b; } inline openvdb::Vec3f maxv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { const float aMag = a.lengthSqr(), bMag = b.lengthSqr(); return (aMag > bMag ? a : (bMag > aMag ? b : std::max(a, b))); } inline openvdb::Vec3f minv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { const float aMag = a.lengthSqr(), bMag = b.lengthSqr(); return (aMag < bMag ? a : (bMag < aMag ? b : std::min(a, b))); } inline openvdb::Vec3f sumv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a + b; } inline openvdb::Vec3f mulv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a * b; } inline openvdb::Vec3f divv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a / b; } } // namespace Local } // unnamed namespace void TestTreeCombine::testCombine() { testComp(Local::combine, Local::orderf); testComp(Local::combine, Local::orderv); testComp(Local::extendedCombine, Local::orderf); testComp(Local::extendedCombine, Local::orderv); } void TestTreeCombine::testCompMax() { testComp(Local::compMax, Local::maxf); testComp(Local::compMax, Local::maxv); } void TestTreeCombine::testCompMin() { testComp(Local::compMin, Local::minf); testComp(Local::compMin, Local::minv); } void TestTreeCombine::testCompSum() { testComp(Local::compSum, Local::sumf); testComp(Local::compSum, Local::sumv); } void TestTreeCombine::testCompProd() { testComp(Local::compMul, Local::mulf); testComp(Local::compMul, Local::mulv); } void TestTreeCombine::testCompDiv() { testComp(Local::compDiv, Local::divf); testComp(Local::compDiv, Local::divv); } void TestTreeCombine::testCompDivByZero() { const openvdb::Coord c0(0), c1(1), c2(2), c3(3), c4(4); // Verify that integer-valued grids behave well w.r.t. division by zero. { const openvdb::Int32 inf = std::numeric_limits::max(); openvdb::Int32Tree a(/*background=*/1), b(0); a.setValueOn(c0); a.setValueOn(c1); a.setValueOn(c2, -1); a.setValueOn(c3, -1); a.setValueOn(c4, 0); b.setValueOn(c1); b.setValueOn(c3); openvdb::tools::compDiv(a, b); CPPUNIT_ASSERT_EQUAL( inf, a.getValue(c0)); // 1 / 0 CPPUNIT_ASSERT_EQUAL( inf, a.getValue(c1)); // 1 / 0 CPPUNIT_ASSERT_EQUAL(-inf, a.getValue(c2)); // -1 / 0 CPPUNIT_ASSERT_EQUAL(-inf, a.getValue(c3)); // -1 / 0 CPPUNIT_ASSERT_EQUAL( 0, a.getValue(c4)); // 0 / 0 } { const openvdb::Index32 zero(0), inf = std::numeric_limits::max(); openvdb::UInt32Tree a(/*background=*/1), b(0); a.setValueOn(c0); a.setValueOn(c1); a.setValueOn(c2, zero); b.setValueOn(c1); openvdb::tools::compDiv(a, b); CPPUNIT_ASSERT_EQUAL( inf, a.getValue(c0)); // 1 / 0 CPPUNIT_ASSERT_EQUAL( inf, a.getValue(c1)); // 1 / 0 CPPUNIT_ASSERT_EQUAL(zero, a.getValue(c2)); // 0 / 0 } // Verify that non-integer-valued grids don't use integer division semantics. { openvdb::FloatTree a(/*background=*/1.0), b(0.0); a.setValueOn(c0); a.setValueOn(c1); a.setValueOn(c2, -1.0); a.setValueOn(c3, -1.0); a.setValueOn(c4, 0.0); b.setValueOn(c1); b.setValueOn(c3); openvdb::tools::compDiv(a, b); CPPUNIT_ASSERT(boost::math::isinf(a.getValue(c0))); // 1 / 0 CPPUNIT_ASSERT(boost::math::isinf(a.getValue(c1))); // 1 / 0 CPPUNIT_ASSERT(boost::math::isinf(a.getValue(c2))); // -1 / 0 CPPUNIT_ASSERT(boost::math::isinf(a.getValue(c3))); // -1 / 0 CPPUNIT_ASSERT(boost::math::isnan(a.getValue(c4))); // 0 / 0 } } void TestTreeCombine::testCompReplace() { testCompRepl(); testCompRepl(); } template void TestTreeCombine::testComp(const TreeComp& comp, const ValueComp& op) { typedef typename TreeT::ValueType ValueT; const ValueT zero = openvdb::zeroVal(), minusOne = zero + (-1), minusTwo = zero + (-2), one = zero + 1, three = zero + 3, four = zero + 4, five = zero + 5; { TreeT aTree(/*background=*/one); aTree.setValueOn(openvdb::Coord(0, 0, 0), three); aTree.setValueOn(openvdb::Coord(0, 0, 1), three); aTree.setValueOn(openvdb::Coord(0, 0, 2), aTree.background()); aTree.setValueOn(openvdb::Coord(0, 1, 2), aTree.background()); aTree.setValueOff(openvdb::Coord(1, 0, 0), three); aTree.setValueOff(openvdb::Coord(1, 0, 1), three); TreeT bTree(five); bTree.setValueOn(openvdb::Coord(0, 0, 0), minusOne); bTree.setValueOn(openvdb::Coord(0, 1, 0), four); bTree.setValueOn(openvdb::Coord(0, 1, 2), minusTwo); bTree.setValueOff(openvdb::Coord(1, 0, 0), minusOne); bTree.setValueOff(openvdb::Coord(1, 1, 0), four); // Call aTree.compMax(bTree), aTree.compSum(bTree), etc. comp(aTree, bTree); // a = 3 (On), b = -1 (On) CPPUNIT_ASSERT_EQUAL(op(three, minusOne), aTree.getValue(openvdb::Coord(0, 0, 0))); // a = 3 (On), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(three, five), aTree.getValue(openvdb::Coord(0, 0, 1))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 0, 1))); // a = 1 (On, = bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(one, five), aTree.getValue(openvdb::Coord(0, 0, 2))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 0, 2))); // a = 1 (On, = bg), b = -2 (On) CPPUNIT_ASSERT_EQUAL(op(one, minusTwo), aTree.getValue(openvdb::Coord(0, 1, 2))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 1, 2))); // a = 1 (bg), b = 4 (On) CPPUNIT_ASSERT_EQUAL(op(one, four), aTree.getValue(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 1, 0))); // a = 3 (Off), b = -1 (Off) CPPUNIT_ASSERT_EQUAL(op(three, minusOne), aTree.getValue(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 0, 0))); // a = 3 (Off), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(three, five), aTree.getValue(openvdb::Coord(1, 0, 1))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 0, 1))); // a = 1 (bg), b = 4 (Off) CPPUNIT_ASSERT_EQUAL(op(one, four), aTree.getValue(openvdb::Coord(1, 1, 0))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 1, 0))); // a = 1 (bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(one, five), aTree.getValue(openvdb::Coord(1000, 1, 2))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1000, 1, 2))); } // As above, but combining the A grid into the B grid { TreeT aTree(/*bg=*/one); aTree.setValueOn(openvdb::Coord(0, 0, 0), three); aTree.setValueOn(openvdb::Coord(0, 0, 1), three); aTree.setValueOn(openvdb::Coord(0, 0, 2), aTree.background()); aTree.setValueOn(openvdb::Coord(0, 1, 2), aTree.background()); aTree.setValueOff(openvdb::Coord(1, 0, 0), three); aTree.setValueOff(openvdb::Coord(1, 0, 1), three); TreeT bTree(five); bTree.setValueOn(openvdb::Coord(0, 0, 0), minusOne); bTree.setValueOn(openvdb::Coord(0, 1, 0), four); bTree.setValueOn(openvdb::Coord(0, 1, 2), minusTwo); bTree.setValueOff(openvdb::Coord(1, 0, 0), minusOne); bTree.setValueOff(openvdb::Coord(1, 1, 0), four); // Call bTree.compMax(aTree), bTree.compSum(aTree), etc. comp(bTree, aTree); // a = 3 (On), b = -1 (On) CPPUNIT_ASSERT_EQUAL(op(minusOne, three), bTree.getValue(openvdb::Coord(0, 0, 0))); // a = 3 (On), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(five, three), bTree.getValue(openvdb::Coord(0, 0, 1))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 0, 1))); // a = 1 (On, = bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(five, one), bTree.getValue(openvdb::Coord(0, 0, 2))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 0, 2))); // a = 1 (On, = bg), b = -2 (On) CPPUNIT_ASSERT_EQUAL(op(minusTwo, one), bTree.getValue(openvdb::Coord(0, 1, 2))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 1, 2))); // a = 1 (bg), b = 4 (On) CPPUNIT_ASSERT_EQUAL(op(four, one), bTree.getValue(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 1, 0))); // a = 3 (Off), b = -1 (Off) CPPUNIT_ASSERT_EQUAL(op(minusOne, three), bTree.getValue(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 0, 0))); // a = 3 (Off), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(five, three), bTree.getValue(openvdb::Coord(1, 0, 1))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 0, 1))); // a = 1 (bg), b = 4 (Off) CPPUNIT_ASSERT_EQUAL(op(four, one), bTree.getValue(openvdb::Coord(1, 1, 0))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 1, 0))); // a = 1 (bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(five, one), bTree.getValue(openvdb::Coord(1000, 1, 2))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1000, 1, 2))); } } //////////////////////////////////////// void TestTreeCombine::testCombine2() { using openvdb::Coord; using openvdb::Vec3d; struct Local { static void floatAverage(const float& a, const float& b, float& result) { result = 0.5f * (a + b); } static void vec3dAverage(const Vec3d& a, const Vec3d& b, Vec3d& result) { result = 0.5 * (a + b); } static void vec3dFloatMultiply(const Vec3d& a, const float& b, Vec3d& result) { result = a * b; } static void vec3dBoolMultiply(const Vec3d& a, const bool& b, Vec3d& result) { result = a * b; } }; const Coord c0(0, 0, 0), c1(0, 0, 1), c2(0, 1, 0), c3(1, 0, 0), c4(1000, 1, 2); openvdb::FloatTree aFloatTree(/*bg=*/1.0), bFloatTree(5.0), outFloatTree(1.0); aFloatTree.setValue(c0, 3.0); aFloatTree.setValue(c1, 3.0); bFloatTree.setValue(c0, -1.0); bFloatTree.setValue(c2, 4.0); outFloatTree.combine2(aFloatTree, bFloatTree, Local::floatAverage); const float tolerance = 0.0; // Average of set value 3 and set value -1 CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, outFloatTree.getValue(c0), tolerance); // Average of set value 3 and bg value 5 CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, outFloatTree.getValue(c1), tolerance); // Average of bg value 1 and set value 4 CPPUNIT_ASSERT_DOUBLES_EQUAL(2.5, outFloatTree.getValue(c2), tolerance); // Average of bg value 1 and bg value 5 CPPUNIT_ASSERT(outFloatTree.isValueOff(c3)); CPPUNIT_ASSERT(outFloatTree.isValueOff(c4)); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, outFloatTree.getValue(c3), tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, outFloatTree.getValue(c4), tolerance); // As above, but combining vector grids: const Vec3d zero(0), one(1), two(2), three(3), four(4), five(5); openvdb::Vec3DTree aVecTree(/*bg=*/one), bVecTree(five), outVecTree(one); aVecTree.setValue(c0, three); aVecTree.setValue(c1, three); bVecTree.setValue(c0, -1.0 * one); bVecTree.setValue(c2, four); outVecTree.combine2(aVecTree, bVecTree, Local::vec3dAverage); // Average of set value 3 and set value -1 CPPUNIT_ASSERT_EQUAL(one, outVecTree.getValue(c0)); // Average of set value 3 and bg value 5 CPPUNIT_ASSERT_EQUAL(four, outVecTree.getValue(c1)); // Average of bg value 1 and set value 4 CPPUNIT_ASSERT_EQUAL(2.5 * one, outVecTree.getValue(c2)); // Average of bg value 1 and bg value 5 CPPUNIT_ASSERT(outVecTree.isValueOff(c3)); CPPUNIT_ASSERT(outVecTree.isValueOff(c4)); CPPUNIT_ASSERT_EQUAL(three, outVecTree.getValue(c3)); CPPUNIT_ASSERT_EQUAL(three, outVecTree.getValue(c4)); // Multiply the vector tree by the scalar tree. { openvdb::Vec3DTree vecTree(one); vecTree.combine2(outVecTree, outFloatTree, Local::vec3dFloatMultiply); // Product of set value (1, 1, 1) and set value 1 CPPUNIT_ASSERT(vecTree.isValueOn(c0)); CPPUNIT_ASSERT_EQUAL(one, vecTree.getValue(c0)); // Product of set value (4, 4, 4) and set value 4 CPPUNIT_ASSERT(vecTree.isValueOn(c1)); CPPUNIT_ASSERT_EQUAL(4 * 4 * one, vecTree.getValue(c1)); // Product of set value (2.5, 2.5, 2.5) and set value 2.5 CPPUNIT_ASSERT(vecTree.isValueOn(c2)); CPPUNIT_ASSERT_EQUAL(2.5 * 2.5 * one, vecTree.getValue(c2)); // Product of bg value (3, 3, 3) and bg value 3 CPPUNIT_ASSERT(vecTree.isValueOff(c3)); CPPUNIT_ASSERT(vecTree.isValueOff(c4)); CPPUNIT_ASSERT_EQUAL(3 * 3 * one, vecTree.getValue(c3)); CPPUNIT_ASSERT_EQUAL(3 * 3 * one, vecTree.getValue(c4)); } // Multiply the vector tree by a boolean tree. { openvdb::BoolTree boolTree(0); boolTree.setValue(c0, true); boolTree.setValue(c1, false); boolTree.setValue(c2, true); openvdb::Vec3DTree vecTree(one); vecTree.combine2(outVecTree, boolTree, Local::vec3dBoolMultiply); // Product of set value (1, 1, 1) and set value 1 CPPUNIT_ASSERT(vecTree.isValueOn(c0)); CPPUNIT_ASSERT_EQUAL(one, vecTree.getValue(c0)); // Product of set value (4, 4, 4) and set value 0 CPPUNIT_ASSERT(vecTree.isValueOn(c1)); CPPUNIT_ASSERT_EQUAL(zero, vecTree.getValue(c1)); // Product of set value (2.5, 2.5, 2.5) and set value 1 CPPUNIT_ASSERT(vecTree.isValueOn(c2)); CPPUNIT_ASSERT_EQUAL(2.5 * one, vecTree.getValue(c2)); // Product of bg value (3, 3, 3) and bg value 0 CPPUNIT_ASSERT(vecTree.isValueOff(c3)); CPPUNIT_ASSERT(vecTree.isValueOff(c4)); CPPUNIT_ASSERT_EQUAL(zero, vecTree.getValue(c3)); CPPUNIT_ASSERT_EQUAL(zero, vecTree.getValue(c4)); } // Verify that a vector tree can't be combined into a scalar tree // (although the reverse is allowed). { struct Local2 { static void f(const float& a, const Vec3d&, float& result) { result = a; } }; openvdb::FloatTree floatTree(5.0), outTree; openvdb::Vec3DTree vecTree(one); CPPUNIT_ASSERT_THROW(outTree.combine2(floatTree, vecTree, Local2::f), openvdb::TypeError); } } //////////////////////////////////////// void TestTreeCombine::testBoolTree() { openvdb::BoolGrid::Ptr sphere = openvdb::BoolGrid::create(); unittest_util::makeSphere(/*dim=*/openvdb::Coord(32), /*ctr=*/openvdb::Vec3f(0), /*radius=*/20.0, *sphere, unittest_util::SPHERE_SPARSE_NARROW_BAND); openvdb::BoolGrid::Ptr aGrid = sphere->copy(), bGrid = sphere->copy(); // CSG operations work only on level sets with a nonzero inside and outside values. CPPUNIT_ASSERT_THROW(openvdb::tools::csgUnion(aGrid->tree(), bGrid->tree()), openvdb::ValueError); CPPUNIT_ASSERT_THROW(openvdb::tools::csgIntersection(aGrid->tree(), bGrid->tree()), openvdb::ValueError); CPPUNIT_ASSERT_THROW(openvdb::tools::csgDifference(aGrid->tree(), bGrid->tree()), openvdb::ValueError); openvdb::tools::compSum(aGrid->tree(), bGrid->tree()); bGrid = sphere->copy(); openvdb::tools::compMax(aGrid->tree(), bGrid->tree()); int mismatches = 0; openvdb::BoolGrid::ConstAccessor acc = sphere->getConstAccessor(); for (openvdb::BoolGrid::ValueAllCIter it = aGrid->cbeginValueAll(); it; ++it) { if (*it != acc.getValue(it.getCoord())) ++mismatches; } CPPUNIT_ASSERT_EQUAL(0, mismatches); } //////////////////////////////////////// template void TestTreeCombine::testCompRepl() { typedef typename TreeT::ValueType ValueT; const ValueT zero = openvdb::zeroVal(), minusOne = zero + (-1), one = zero + 1, three = zero + 3, four = zero + 4, five = zero + 5; { TreeT aTree(/*bg=*/one); aTree.setValueOn(openvdb::Coord(0, 0, 0), three); aTree.setValueOn(openvdb::Coord(0, 0, 1), three); aTree.setValueOn(openvdb::Coord(0, 0, 2), aTree.background()); aTree.setValueOn(openvdb::Coord(0, 1, 2), aTree.background()); aTree.setValueOff(openvdb::Coord(1, 0, 0), three); aTree.setValueOff(openvdb::Coord(1, 0, 1), three); TreeT bTree(five); bTree.setValueOn(openvdb::Coord(0, 0, 0), minusOne); bTree.setValueOn(openvdb::Coord(0, 1, 0), four); bTree.setValueOn(openvdb::Coord(0, 1, 2), minusOne); bTree.setValueOff(openvdb::Coord(1, 0, 0), minusOne); bTree.setValueOff(openvdb::Coord(1, 1, 0), four); // Copy active voxels of bTree into aTree. openvdb::tools::compReplace(aTree, bTree); // a = 3 (On), b = -1 (On) CPPUNIT_ASSERT_EQUAL(minusOne, aTree.getValue(openvdb::Coord(0, 0, 0))); // a = 3 (On), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(three, aTree.getValue(openvdb::Coord(0, 0, 1))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 0, 1))); // a = 1 (On, = bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(one, aTree.getValue(openvdb::Coord(0, 0, 2))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 0, 2))); // a = 1 (On, = bg), b = -1 (On) CPPUNIT_ASSERT_EQUAL(minusOne, aTree.getValue(openvdb::Coord(0, 1, 2))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 1, 2))); // a = 1 (bg), b = 4 (On) CPPUNIT_ASSERT_EQUAL(four, aTree.getValue(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 1, 0))); // a = 3 (Off), b = -1 (Off) CPPUNIT_ASSERT_EQUAL(three, aTree.getValue(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 0, 0))); // a = 3 (Off), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(three, aTree.getValue(openvdb::Coord(1, 0, 1))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 0, 1))); // a = 1 (bg), b = 4 (Off) CPPUNIT_ASSERT_EQUAL(one, aTree.getValue(openvdb::Coord(1, 1, 0))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 1, 0))); // a = 1 (bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(one, aTree.getValue(openvdb::Coord(1000, 1, 2))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1000, 1, 2))); } // As above, but combining the A grid into the B grid { TreeT aTree(/*background=*/one); aTree.setValueOn(openvdb::Coord(0, 0, 0), three); aTree.setValueOn(openvdb::Coord(0, 0, 1), three); aTree.setValueOn(openvdb::Coord(0, 0, 2), aTree.background()); aTree.setValueOn(openvdb::Coord(0, 1, 2), aTree.background()); aTree.setValueOff(openvdb::Coord(1, 0, 0), three); aTree.setValueOff(openvdb::Coord(1, 0, 1), three); TreeT bTree(five); bTree.setValueOn(openvdb::Coord(0, 0, 0), minusOne); bTree.setValueOn(openvdb::Coord(0, 1, 0), four); bTree.setValueOn(openvdb::Coord(0, 1, 2), minusOne); bTree.setValueOff(openvdb::Coord(1, 0, 0), minusOne); bTree.setValueOff(openvdb::Coord(1, 1, 0), four); // Copy active voxels of aTree into bTree. openvdb::tools::compReplace(bTree, aTree); // a = 3 (On), b = -1 (On) CPPUNIT_ASSERT_EQUAL(three, bTree.getValue(openvdb::Coord(0, 0, 0))); // a = 3 (On), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(three, bTree.getValue(openvdb::Coord(0, 0, 1))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 0, 1))); // a = 1 (On, = bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(one, bTree.getValue(openvdb::Coord(0, 0, 2))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 0, 2))); // a = 1 (On, = bg), b = -1 (On) CPPUNIT_ASSERT_EQUAL(one, bTree.getValue(openvdb::Coord(0, 1, 2))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 1, 2))); // a = 1 (bg), b = 4 (On) CPPUNIT_ASSERT_EQUAL(four, bTree.getValue(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 1, 0))); // a = 3 (Off), b = -1 (Off) CPPUNIT_ASSERT_EQUAL(minusOne, bTree.getValue(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 0, 0))); // a = 3 (Off), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(five, bTree.getValue(openvdb::Coord(1, 0, 1))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 0, 1))); // a = 1 (bg), b = 4 (Off) CPPUNIT_ASSERT_EQUAL(four, bTree.getValue(openvdb::Coord(1, 1, 0))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 1, 0))); // a = 1 (bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(five, bTree.getValue(openvdb::Coord(1000, 1, 2))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1000, 1, 2))); } } //////////////////////////////////////// void TestTreeCombine::testCsg() { typedef openvdb::FloatTree TreeT; typedef TreeT::Ptr TreePtr; typedef openvdb::Grid GridT; struct Local { static TreePtr readFile(const std::string& fname) { std::string filename(fname), gridName("LevelSet"); size_t space = filename.find_last_of(' '); if (space != std::string::npos) { gridName = filename.substr(space + 1); filename.erase(space); } TreePtr tree; openvdb::io::File file(filename); file.open(); if (openvdb::GridBase::Ptr basePtr = file.readGrid(gridName)) { if (GridT::Ptr gridPtr = openvdb::gridPtrCast(basePtr)) { tree = gridPtr->treePtr(); } } file.close(); return tree; } //static void writeFile(TreePtr tree, const std::string& filename) { // openvdb::io::File file(filename); // openvdb::GridPtrVec grids; // GridT::Ptr grid = openvdb::createGrid(tree); // grid->setName("LevelSet"); // grids.push_back(grid); // file.write(grids); //} static void visitorUnion(TreeT& a, TreeT& b) { openvdb::tools::csgUnion(a, b); } static void visitorIntersect(TreeT& a, TreeT& b) { openvdb::tools::csgIntersection(a, b); } static void visitorDiff(TreeT& a, TreeT& b) { openvdb::tools::csgDifference(a, b); } }; TreePtr smallTree1, smallTree2, largeTree1, largeTree2, refTree, outTree; #if TEST_CSG_VERBOSE openvdb::util::CpuTimer timer; timer.start(); #endif const std::string testDir("/work/rd/fx_tools/vdb_unittest/TestGridCombine::testCsg/"); smallTree1 = Local::readFile(testDir + "small1.vdb2 LevelSet"); CPPUNIT_ASSERT(smallTree1.get() != NULL); smallTree2 = Local::readFile(testDir + "small2.vdb2 Cylinder"); CPPUNIT_ASSERT(smallTree2.get() != NULL); largeTree1 = Local::readFile(testDir + "large1.vdb2 LevelSet"); CPPUNIT_ASSERT(largeTree1.get() != NULL); largeTree2 = Local::readFile(testDir + "large2.vdb2 LevelSet"); CPPUNIT_ASSERT(largeTree2.get() != NULL); #if TEST_CSG_VERBOSE std::cerr << "file read: " << timer.delta() << " sec\n"; #endif #if TEST_CSG_VERBOSE std::cerr << "\n\n"; #endif refTree = Local::readFile(testDir + "small_union.vdb2"); outTree = visitCsg(*smallTree1, *smallTree2, *refTree, Local::visitorUnion); //Local::writeFile(outTree, "small_union_out.vdb2"); refTree = Local::readFile(testDir + "large_union.vdb2"); outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorUnion); //Local::writeFile(outTree, "large_union_out.vdb2"); #if TEST_CSG_VERBOSE std::cerr << "\n\n"; #endif refTree = Local::readFile(testDir + "small_intersection.vdb2"); outTree = visitCsg(*smallTree1, *smallTree2, *refTree, Local::visitorIntersect); //Local::writeFile(outTree, "small_intersection_out.vdb2"); refTree = Local::readFile(testDir + "large_intersection.vdb2"); outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorIntersect); //Local::writeFile(outTree, "large_intersection_out.vdb2"); #if TEST_CSG_VERBOSE std::cerr << "\n\n"; #endif refTree = Local::readFile(testDir + "small_difference.vdb2"); outTree = visitCsg(*smallTree1, *smallTree2, *refTree, Local::visitorDiff); //Local::writeFile(outTree, "small_difference_out.vdb2"); refTree = Local::readFile(testDir + "large_difference.vdb2"); outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorDiff); //Local::writeFile(outTree, "large_difference_out.vdb2"); } template typename TreeT::Ptr TestTreeCombine::visitCsg(const TreeT& aInputTree, const TreeT& bInputTree, const TreeT& refTree, const VisitorT& visitor) { typedef typename TreeT::Ptr TreePtr; #if TEST_CSG_VERBOSE openvdb::util::CpuTimer timer; timer.start(); #endif TreePtr aTree(new TreeT(aInputTree)); TreeT bTree(bInputTree); #if TEST_CSG_VERBOSE std::cerr << "deep copy: " << timer.delta() << " ms\n"; #endif #if (TEST_CSG_VERBOSE > 1) std::cerr << "\nA grid:\n"; aTree->print(std::cerr, /*verbose=*/3); std::cerr << "\nB grid:\n"; bTree.print(std::cerr, /*verbose=*/3); std::cerr << "\nExpected:\n"; refTree.print(std::cerr, /*verbose=*/3); std::cerr << "\n"; #endif // Compute the CSG combination of the two grids. #if TEST_CSG_VERBOSE timer.start(); #endif visitor(*aTree, bTree); #if TEST_CSG_VERBOSE std::cerr << "combine: " << timer.delta() << " ms\n"; #endif #if (TEST_CSG_VERBOSE > 1) std::cerr << "\nActual:\n"; aTree->print(std::cerr, /*verbose=*/3); #endif std::ostringstream aInfo, refInfo; aTree->print(aInfo, /*verbose=*/2); refTree.print(refInfo, /*verbose=*/2); CPPUNIT_ASSERT_EQUAL(refInfo.str(), aInfo.str()); CPPUNIT_ASSERT(aTree->hasSameTopology(refTree)); return aTree; } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTree.cc0000644000000000000000000036331412603226506015152 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include // for remove() #include #include #include #include #include #include #include // for tools::setValueOnMin(), et al. #include #include #include #include // for io::RealToHalf #include // for Abs() #include #include #include #include #include #include #include "util.h" // for unittest_util::makeSphere() #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); 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(testChangeBackground); CPPUNIT_TEST(testHalf); CPPUNIT_TEST(testValues); CPPUNIT_TEST(testSetValue); CPPUNIT_TEST(testSetValueOnly); CPPUNIT_TEST(testEvalMinMax); CPPUNIT_TEST(testResize); CPPUNIT_TEST(testHasSameTopology); CPPUNIT_TEST(testTopologyCopy); CPPUNIT_TEST(testIterators); CPPUNIT_TEST(testIO); CPPUNIT_TEST(testNegativeIndexing); CPPUNIT_TEST(testDeepCopy); CPPUNIT_TEST(testMerge); CPPUNIT_TEST(testVoxelizeActiveTiles); CPPUNIT_TEST(testTopologyUnion); CPPUNIT_TEST(testTopologyIntersection); CPPUNIT_TEST(testTopologyDifference); CPPUNIT_TEST(testSignedFloodFill); CPPUNIT_TEST(testPruneInactive); CPPUNIT_TEST(testPruneLevelSet); CPPUNIT_TEST(testTouchLeaf); CPPUNIT_TEST(testProbeLeaf); CPPUNIT_TEST(testAddLeaf); CPPUNIT_TEST(testAddTile); CPPUNIT_TEST(testGetNodes); CPPUNIT_TEST(testStealNodes); CPPUNIT_TEST(testLeafManager); CPPUNIT_TEST(testNodeManager); CPPUNIT_TEST(testProcessBBox); CPPUNIT_TEST(testStealNode); CPPUNIT_TEST_SUITE_END(); void testChangeBackground(); void testHalf(); void testValues(); void testSetValue(); void testSetValueOnly(); void testEvalMinMax(); void testResize(); void testHasSameTopology(); void testTopologyCopy(); void testIterators(); void testIO(); void testNegativeIndexing(); void testDeepCopy(); void testMerge(); void testVoxelizeActiveTiles(); void testTopologyUnion(); void testTopologyIntersection(); void testTopologyDifference(); void testSignedFloodFill(); void testPruneLevelSet(); void testPruneInactive(); void testTouchLeaf(); void testProbeLeaf(); void testAddLeaf(); void testAddTile(); void testGetNodes(); void testStealNodes(); void testLeafManager(); void testNodeManager(); void testProcessBBox(); void testStealNode(); private: template void testWriteHalf(); template void doTestMerge(openvdb::MergePolicy); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTree); void TestTree::testChangeBackground() { const int dim = 128; const openvdb::Vec3f center(0.35f, 0.35f, 0.35f); const float radius = 0.15f, voxelSize = 1.0f / (dim-1), halfWidth = 4, gamma = halfWidth * voxelSize; typedef openvdb::FloatGrid GridT; const openvdb::Coord inside(int(center[0]*dim), int(center[1]*dim), int(center[2]*dim)); const openvdb::Coord outside(dim); {//changeBackground GridT::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, halfWidth); openvdb::FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(grid->tree().isValueOff(outside)); ASSERT_DOUBLES_EXACTLY_EQUAL( gamma, tree.getValue(outside)); CPPUNIT_ASSERT(tree.isValueOff(inside)); ASSERT_DOUBLES_EXACTLY_EQUAL(-gamma, tree.getValue(inside)); const float background = gamma*3.43f; openvdb::tools::changeBackground(tree, background); CPPUNIT_ASSERT(grid->tree().isValueOff(outside)); ASSERT_DOUBLES_EXACTLY_EQUAL( background, tree.getValue(outside)); CPPUNIT_ASSERT(tree.isValueOff(inside)); ASSERT_DOUBLES_EXACTLY_EQUAL(-background, tree.getValue(inside)); } {//changeLevelSetBackground GridT::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, halfWidth); openvdb::FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(grid->tree().isValueOff(outside)); ASSERT_DOUBLES_EXACTLY_EQUAL( gamma, tree.getValue(outside)); CPPUNIT_ASSERT(tree.isValueOff(inside)); ASSERT_DOUBLES_EXACTLY_EQUAL(-gamma, tree.getValue(inside)); const float v1 = gamma*3.43f, v2 = -gamma*6.457f; openvdb::tools::changeAsymmetricLevelSetBackground(tree, v1, v2); CPPUNIT_ASSERT(grid->tree().isValueOff(outside)); ASSERT_DOUBLES_EXACTLY_EQUAL( v1, tree.getValue(outside)); CPPUNIT_ASSERT(tree.isValueOff(inside)); ASSERT_DOUBLES_EXACTLY_EQUAL( v2, tree.getValue(inside)); } } void TestTree::testHalf() { testWriteHalf(); testWriteHalf(); testWriteHalf(); testWriteHalf(); testWriteHalf(); testWriteHalf(); // Verify that non-floating-point grids are saved correctly. testWriteHalf(); testWriteHalf(); testWriteHalf(); } template void TestTree::testWriteHalf() { typedef openvdb::Grid GridType; typedef typename TreeType::ValueType ValueT; ValueT background(5); GridType grid(background); unittest_util::makeSphere(openvdb::Coord(64, 64, 64), openvdb::Vec3f(35, 30, 40), /*radius=*/10, grid, /*dx=*/1.0f, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid.tree().empty()); // Write grid blocks in both float and half formats. std::ostringstream outFull(std::ios_base::binary); grid.setSaveFloatAsHalf(false); grid.writeBuffers(outFull); outFull.flush(); const size_t fullBytes = outFull.str().size(); CPPUNIT_ASSERT_MESSAGE("wrote empty full float buffers", fullBytes > 0); std::ostringstream outHalf(std::ios_base::binary); grid.setSaveFloatAsHalf(true); grid.writeBuffers(outHalf); outHalf.flush(); const size_t halfBytes = outHalf.str().size(); CPPUNIT_ASSERT_MESSAGE("wrote empty half float buffers", halfBytes > 0); if (openvdb::io::RealToHalf::isReal) { // Verify that the half float file is "significantly smaller" than the full float file. std::ostringstream ostr; ostr << "half float buffers not significantly smaller than full float (" << halfBytes << " vs. " << fullBytes << " bytes)"; CPPUNIT_ASSERT_MESSAGE(ostr.str(), halfBytes < size_t(0.75 * fullBytes)); } else { // For non-real data types, "half float" and "full float" file sizes should be the same. CPPUNIT_ASSERT_MESSAGE("full float and half float file sizes differ for data of type " + std::string(openvdb::typeNameAsString()), halfBytes == fullBytes); } // Read back the half float data (converting back to full float in the process), // then write it out again in half float format. Verify that the resulting file // is identical to the original half float file. { openvdb::Grid gridCopy(grid); gridCopy.setSaveFloatAsHalf(true); std::istringstream is(outHalf.str(), std::ios_base::binary); // Since the input stream doesn't include a VDB header with file format version info, // tag the input stream explicitly with the current version number. openvdb::io::setCurrentVersion(is); gridCopy.readBuffers(is); std::ostringstream outDiff(std::ios_base::binary); gridCopy.writeBuffers(outDiff); outDiff.flush(); CPPUNIT_ASSERT_MESSAGE("half-from-full and half-from-half buffers differ", outHalf.str() == outDiff.str()); } } void TestTree::testValues() { ValueType background=5.0f; { const openvdb::Coord c0(5,10,20), c1(50000,20000,30000); RootNodeType root_node(background); const float v0=0.234f, v1=4.5678f; CPPUNIT_ASSERT(root_node.empty()); ASSERT_DOUBLES_EXACTLY_EQUAL(root_node.getValue(c0), background); ASSERT_DOUBLES_EXACTLY_EQUAL(root_node.getValue(c1), background); root_node.setValueOn(c0, v0); root_node.setValueOn(c1, v1); ASSERT_DOUBLES_EXACTLY_EQUAL(v0,root_node.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(v1,root_node.getValue(c1)); int count=0; for (int i =0; i<256; ++i) { for (int j=0; j<256; ++j) { for (int k=0; k<256; ++k) { if (root_node.getValue(openvdb::Coord(i,j,k))<1.0f) ++count; } } } CPPUNIT_ASSERT(count == 1); } { const openvdb::Coord min(-30,-25,-60), max(60,80,100); const openvdb::Coord c0(-5,-10,-20), c1(50,20,90), c2(59,67,89); const float v0=0.234f, v1=4.5678f, v2=-5.673f; RootNodeType root_node(background); CPPUNIT_ASSERT(root_node.empty()); ASSERT_DOUBLES_EXACTLY_EQUAL(background,root_node.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background,root_node.getValue(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background,root_node.getValue(c2)); root_node.setValueOn(c0, v0); root_node.setValueOn(c1, v1); root_node.setValueOn(c2, v2); ASSERT_DOUBLES_EXACTLY_EQUAL(v0,root_node.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(v1,root_node.getValue(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(v2,root_node.getValue(c2)); int count=0; for (int i =min[0]; i void evalMinMaxTest() { 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 = "testIO.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); } } {// test union of a leaf and a tile if (openvdb::FloatTree::DEPTH > 2) { const int leafLevel = openvdb::FloatTree::DEPTH - 1; const int tileLevel = leafLevel - 1; const openvdb::Coord xyz(0); openvdb::FloatTree tree0; tree0.addTile(tileLevel, xyz, /*value=*/0, /*activeState=*/true); CPPUNIT_ASSERT(tree0.isValueOn(xyz)); openvdb::FloatTree tree1; tree1.touchLeaf(xyz)->setValuesOn(); CPPUNIT_ASSERT(tree1.isValueOn(xyz)); tree0.topologyUnion(tree1); CPPUNIT_ASSERT(tree0.isValueOn(xyz)); CPPUNIT_ASSERT_EQUAL(tree0.getValueDepth(xyz), leafLevel); } } }// testTopologyUnion void TestTree::testTopologyIntersection() { {//no overlapping voxels const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree1.activeVoxelCount()); tree1.topologyIntersection(tree0); CPPUNIT_ASSERT_EQUAL(tree1.activeVoxelCount(), openvdb::Index64(0)); CPPUNIT_ASSERT(!tree1.empty()); openvdb::tools::pruneInactive(tree1); CPPUNIT_ASSERT(tree1.empty()); } {//two overlapping voxels const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); tree1.setValue(openvdb::Coord( 500, 300, 200), 1.0f); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); tree1.topologyIntersection(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); openvdb::tools::pruneInactive(tree1); CPPUNIT_ASSERT(!tree1.empty()); } {//4 overlapping voxels const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree0.setValue(openvdb::Coord( 400, 30, 20), 2.0f); tree0.setValue(openvdb::Coord( 8, 11, 11), 3.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 6.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); tree1.topologyIntersection(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); openvdb::tools::pruneInactive(tree1); CPPUNIT_ASSERT(!tree1.empty()); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(2), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); } {//passive tile const ValueType background=0.0f; const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; openvdb::FloatTree tree0(background), tree1(background); tree0.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, false); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(0), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree1.setValue(openvdb::Coord( dim, 11, 11), 6.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); tree1.topologyIntersection(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(0), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(0), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(tree1.empty()); } {//active tile const ValueType background=0.0f; const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; openvdb::FloatTree tree0(background), tree1(background); tree1.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, true); CPPUNIT_ASSERT_EQUAL(dim*dim*dim, tree1.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree1.leafCount() ); tree0.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree0.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree0.setValue(openvdb::Coord( dim, 11, 11), 6.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); tree1.topologyIntersection(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(2), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); openvdb::tools::pruneInactive(tree1); CPPUNIT_ASSERT(!tree1.empty()); } {// use tree with different voxel type ValueType background=5.0f; openvdb::FloatTree tree0(background), tree1(background), tree2(background); CPPUNIT_ASSERT(tree2.empty()); // tree0 = tree1.topologyIntersection(tree2) tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree0.setValue(openvdb::Coord(-5, 10,-20),0.1f); tree0.setValue(openvdb::Coord( 5,-10,-20),0.2f); tree0.setValue(openvdb::Coord(-5,-10,-20),0.3f); tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree1.setValue(openvdb::Coord(-5, 10,-20),0.1f); tree1.setValue(openvdb::Coord( 5,-10,-20),0.2f); tree1.setValue(openvdb::Coord(-5,-10,-20),0.3f); tree2.setValue(openvdb::Coord( 5, 10, 20),0.4f); tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); openvdb::FloatTree tree1_copy(tree1); // tree3 has the same topology as tree2 but a different value type const openvdb::Vec3f background2(1.0f,3.4f,6.0f), vec_val(3.1f,5.3f,-9.5f); openvdb::Vec3fTree tree3(background2); for (openvdb::FloatTree::ValueOnCIter iter = tree2.cbeginValueOn(); iter; ++iter) { tree3.setValue(iter.getCoord(), vec_val); } CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree0.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree1.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree2.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree3.leafCount()); //tree1.topologyInterection(tree2);//should make tree1 = tree0 tree1.topologyIntersection(tree3);//should make tree1 = tree0 CPPUNIT_ASSERT(tree0.leafCount()==tree1.leafCount()); CPPUNIT_ASSERT(tree0.nonLeafCount()==tree1.nonLeafCount()); CPPUNIT_ASSERT(tree0.activeLeafVoxelCount()==tree1.activeLeafVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveLeafVoxelCount()==tree1.inactiveLeafVoxelCount()); CPPUNIT_ASSERT(tree0.activeVoxelCount()==tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveVoxelCount()==tree1.inactiveVoxelCount()); CPPUNIT_ASSERT(tree1.hasSameTopology(tree0)); CPPUNIT_ASSERT(tree0.hasSameTopology(tree1)); for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree1.isValueOn(p)); CPPUNIT_ASSERT(tree2.isValueOn(p)); CPPUNIT_ASSERT(tree3.isValueOn(p)); CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(p)); } for (openvdb::FloatTree::ValueOnCIter iter = tree1_copy.cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(iter.getCoord())); } for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree0.isValueOn(p)); CPPUNIT_ASSERT(tree2.isValueOn(p)); CPPUNIT_ASSERT(tree3.isValueOn(p)); CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree0.getValue(p)); } } {// test overlapping spheres const float background=5.0f, R0=10.0f, R1=5.6f; const openvdb::Vec3f C0(35.0f, 30.0f, 40.0f), C1(22.3f, 30.5f, 31.0f); const openvdb::Coord dim(32, 32, 32); openvdb::FloatGrid grid0(background); openvdb::FloatGrid grid1(background); unittest_util::makeSphere(dim, C0, R0, grid0, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); unittest_util::makeSphere(dim, C1, R1, grid1, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); openvdb::FloatTree& tree0 = grid0.tree(); openvdb::FloatTree& tree1 = grid1.tree(); openvdb::FloatTree tree0_copy(tree0); tree0.topologyIntersection(tree1); const openvdb::Index64 n0 = tree0_copy.activeVoxelCount(); const openvdb::Index64 n = tree0.activeVoxelCount(); const openvdb::Index64 n1 = tree1.activeVoxelCount(); //fprintf(stderr,"Intersection of spheres: n=%i, n0=%i n1=%i n0+n1=%i\n",n,n0,n1, n0+n1); CPPUNIT_ASSERT( n < n0 ); CPPUNIT_ASSERT( n < n1 ); for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree1.isValueOn(p)); CPPUNIT_ASSERT(tree0_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter, tree0_copy.getValue(p)); } } {// Test based on boolean grids openvdb::CoordBBox 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()); openvdb::tools::pruneInactive(tree1); CPPUNIT_ASSERT(!tree1.empty()); } {//two overlapping voxels const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); tree1.setValue(openvdb::Coord( 500, 300, 200), 1.0f); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); CPPUNIT_ASSERT( tree0.isValueOn(openvdb::Coord( 500, 300, 200))); CPPUNIT_ASSERT( tree1.isValueOn(openvdb::Coord( 500, 300, 200))); CPPUNIT_ASSERT( tree1.isValueOn(openvdb::Coord( 8, 11, 11))); tree1.topologyDifference(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); CPPUNIT_ASSERT( tree0.isValueOn(openvdb::Coord( 500, 300, 200))); CPPUNIT_ASSERT(!tree1.isValueOn(openvdb::Coord( 500, 300, 200))); CPPUNIT_ASSERT( tree1.isValueOn(openvdb::Coord( 8, 11, 11))); CPPUNIT_ASSERT(!tree1.empty()); openvdb::tools::pruneInactive(tree1); CPPUNIT_ASSERT(!tree1.empty()); } {//4 overlapping voxels const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree0.setValue(openvdb::Coord( 400, 30, 20), 2.0f); tree0.setValue(openvdb::Coord( 8, 11, 11), 3.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 6.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); tree1.topologyDifference(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); openvdb::tools::pruneInactive(tree1); CPPUNIT_ASSERT(!tree1.empty()); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(1), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); } {//passive tile const ValueType background=0.0f; const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; openvdb::FloatTree tree0(background), tree1(background); tree0.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, false); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(0), tree0.activeVoxelCount()); CPPUNIT_ASSERT(!tree0.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(0), tree0.root().onTileCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree1.setValue(openvdb::Coord( dim, 11, 11), 6.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); CPPUNIT_ASSERT(!tree1.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); tree1.topologyDifference(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(3), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); openvdb::tools::pruneInactive(tree1); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(3), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); } {//active tile const ValueType background=0.0f; const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; openvdb::FloatTree tree0(background), tree1(background); tree1.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, true); CPPUNIT_ASSERT_EQUAL(dim*dim*dim, tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree1.root().onTileCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); tree0.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree0.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree0.setValue(openvdb::Coord( int(dim), 11, 11), 6.0f); CPPUNIT_ASSERT(!tree0.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); CPPUNIT_ASSERT( tree0.isValueOn(openvdb::Coord( int(dim), 11, 11))); CPPUNIT_ASSERT(!tree1.isValueOn(openvdb::Coord( int(dim), 11, 11))); tree1.topologyDifference(tree0); CPPUNIT_ASSERT(tree1.root().onTileCount() > 1); CPPUNIT_ASSERT_EQUAL( dim*dim*dim - 2, tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); openvdb::tools::pruneInactive(tree1); CPPUNIT_ASSERT_EQUAL( dim*dim*dim - 2, tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); } {//active tile const ValueType background=0.0f; const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; openvdb::FloatTree tree0(background), tree1(background); tree1.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, true); CPPUNIT_ASSERT_EQUAL(dim*dim*dim, tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree1.root().onTileCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); tree0.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree0.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree0.setValue(openvdb::Coord( dim, 11, 11), 6.0f); CPPUNIT_ASSERT(!tree0.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); tree0.topologyDifference(tree1); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(1), tree0.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); CPPUNIT_ASSERT(!tree0.empty()); openvdb::tools::pruneInactive(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(1), tree0.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); } {// use tree with different voxel type ValueType background=5.0f; openvdb::FloatTree tree0(background), tree1(background), tree2(background); CPPUNIT_ASSERT(tree2.empty()); // tree0 = tree1.topologyIntersection(tree2) tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree0.setValue(openvdb::Coord(-5, 10,-20),0.1f); tree0.setValue(openvdb::Coord( 5,-10,-20),0.2f); tree0.setValue(openvdb::Coord(-5,-10,-20),0.3f); tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree1.setValue(openvdb::Coord(-5, 10,-20),0.1f); tree1.setValue(openvdb::Coord( 5,-10,-20),0.2f); tree1.setValue(openvdb::Coord(-5,-10,-20),0.3f); tree2.setValue(openvdb::Coord( 5, 10, 20),0.4f); tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); openvdb::FloatTree tree1_copy(tree1); // tree3 has the same topology as tree2 but a different value type const openvdb::Vec3f background2(1.0f,3.4f,6.0f), vec_val(3.1f,5.3f,-9.5f); openvdb::Vec3fTree tree3(background2); for (openvdb::FloatTree::ValueOnCIter iter = tree2.cbeginValueOn(); iter; ++iter) { tree3.setValue(iter.getCoord(), vec_val); } CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree0.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree1.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree2.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree3.leafCount()); //tree1.topologyInterection(tree2);//should make tree1 = tree0 tree1.topologyIntersection(tree3);//should make tree1 = tree0 CPPUNIT_ASSERT(tree0.leafCount()==tree1.leafCount()); CPPUNIT_ASSERT(tree0.nonLeafCount()==tree1.nonLeafCount()); CPPUNIT_ASSERT(tree0.activeLeafVoxelCount()==tree1.activeLeafVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveLeafVoxelCount()==tree1.inactiveLeafVoxelCount()); CPPUNIT_ASSERT(tree0.activeVoxelCount()==tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveVoxelCount()==tree1.inactiveVoxelCount()); CPPUNIT_ASSERT(tree1.hasSameTopology(tree0)); CPPUNIT_ASSERT(tree0.hasSameTopology(tree1)); for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree1.isValueOn(p)); CPPUNIT_ASSERT(tree2.isValueOn(p)); CPPUNIT_ASSERT(tree3.isValueOn(p)); CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(p)); } for (openvdb::FloatTree::ValueOnCIter iter = tree1_copy.cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(iter.getCoord())); } for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree0.isValueOn(p)); CPPUNIT_ASSERT(tree2.isValueOn(p)); CPPUNIT_ASSERT(tree3.isValueOn(p)); CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree0.getValue(p)); } } {// test overlapping spheres const float background=5.0f, R0=10.0f, R1=5.6f; const openvdb::Vec3f C0(35.0f, 30.0f, 40.0f), C1(22.3f, 30.5f, 31.0f); const openvdb::Coord dim(32, 32, 32); openvdb::FloatGrid grid0(background); openvdb::FloatGrid grid1(background); unittest_util::makeSphere(dim, C0, R0, grid0, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); unittest_util::makeSphere(dim, C1, R1, grid1, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); openvdb::FloatTree& tree0 = grid0.tree(); openvdb::FloatTree& tree1 = grid1.tree(); openvdb::FloatTree tree0_copy(tree0); tree0.topologyDifference(tree1); const openvdb::Index64 n0 = tree0_copy.activeVoxelCount(); const openvdb::Index64 n = tree0.activeVoxelCount(); CPPUNIT_ASSERT( n < n0 ); for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree1.isValueOff(p)); CPPUNIT_ASSERT(tree0_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter, tree0_copy.getValue(p)); } } }// testTopologyDifference void TestTree::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; {//first test flood filling of a leaf node const LeafT::ValueType fill0=5, fill1=-fill0; openvdb::tools::SignedFloodFillOp sff(fill0, fill1); int D = LeafT::dim(), C=D/2; openvdb::Coord origin(0,0,0), left(0,0,C-1), right(0,0,C); LeafT leaf(origin,fill0); for (int i=0; i::Ptr grid = openvdb::Grid::create(outside); TreeT& tree = grid->tree(); const RootT& root = tree.root(); const openvdb::Coord dim(3*16, 3*16, 3*16); const openvdb::Coord C(16+8,16+8,16+8); CPPUNIT_ASSERT(!tree.isValueOn(C)); CPPUNIT_ASSERT(root.getTableSize()==0); //make narrow band of sphere without setting sign for the background values! openvdb::Grid::Accessor acc = grid->getAccessor(); const openvdb::Vec3f center(static_cast(C[0]), static_cast(C[1]), static_cast(C[2])); openvdb::Coord xyz; for (xyz[0]=0; xyz[0]transform().indexToWorld(xyz); const float dist = float((p-center).length() - radius); if (fabs(dist) > outside) continue; acc.setValue(xyz, dist); } } } // Check narrow band with incorrect background const size_t size_before = root.getTableSize(); CPPUNIT_ASSERT(size_before>0); CPPUNIT_ASSERT(!tree.isValueOn(C)); ASSERT_DOUBLES_EXACTLY_EQUAL(outside,tree.getValue(C)); for (xyz[0]=0; xyz[0]transform().indexToWorld(xyz); const float dist = float((p-center).length() - radius); const float val = acc.getValue(xyz); if (dist < inside) { ASSERT_DOUBLES_EXACTLY_EQUAL( val, outside); } else if (dist>outside) { ASSERT_DOUBLES_EXACTLY_EQUAL( val, outside); } else { ASSERT_DOUBLES_EXACTLY_EQUAL( val, dist ); } } } } CPPUNIT_ASSERT(tree.getValueDepth(C) == -1);//i.e. background value openvdb::tools::signedFloodFill(tree); CPPUNIT_ASSERT(tree.getValueDepth(C) == 0);//added inside tile to root // Check narrow band with correct background for (xyz[0]=0; xyz[0]transform().indexToWorld(xyz); const float dist = float((p-center).length() - radius); const float val = acc.getValue(xyz); if (dist < inside) { ASSERT_DOUBLES_EXACTLY_EQUAL( val, inside); } else if (dist>outside) { ASSERT_DOUBLES_EXACTLY_EQUAL( val, outside); } else { ASSERT_DOUBLES_EXACTLY_EQUAL( val, dist ); } } } } CPPUNIT_ASSERT(root.getTableSize()>size_before);//added inside root tiles CPPUNIT_ASSERT(!tree.isValueOn(C)); ASSERT_DOUBLES_EXACTLY_EQUAL(inside,tree.getValue(C)); } void TestTree::testPruneInactive() { using openvdb::Coord; using openvdb::Index32; using openvdb::Index64; const float background = 5.0; openvdb::FloatTree tree(background); // Verify that the newly-constructed tree is empty and that pruning it has no effect. CPPUNIT_ASSERT(tree.empty()); openvdb::tools::prune(tree); CPPUNIT_ASSERT(tree.empty()); openvdb::tools::pruneInactive(tree); CPPUNIT_ASSERT(tree.empty()); // Set some active values. tree.setValue(Coord(-5, 10, 20), 0.1f); tree.setValue(Coord(-5,-10, 20), 0.4f); tree.setValue(Coord(-5, 10,-20), 0.5f); tree.setValue(Coord(-5,-10,-20), 0.7f); tree.setValue(Coord( 5, 10, 20), 0.0f); tree.setValue(Coord( 5,-10, 20), 0.2f); tree.setValue(Coord( 5,-10,-20), 0.6f); tree.setValue(Coord( 5, 10,-20), 0.3f); // Verify that the tree has the expected numbers of active voxels and leaf nodes. CPPUNIT_ASSERT_EQUAL(Index64(8), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); // Verify that prune() has no effect, since the values are all different. openvdb::tools::prune(tree); CPPUNIT_ASSERT_EQUAL(Index64(8), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); // Verify that pruneInactive() has no effect, since the values are active. openvdb::tools::pruneInactive(tree); CPPUNIT_ASSERT_EQUAL(Index64(8), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); // Make some of the active values inactive, without changing their values. tree.setValueOff(Coord(-5, 10, 20)); tree.setValueOff(Coord(-5,-10, 20)); tree.setValueOff(Coord(-5, 10,-20)); tree.setValueOff(Coord(-5,-10,-20)); CPPUNIT_ASSERT_EQUAL(Index64(4), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); // Verify that prune() has no effect, since the values are still different. openvdb::tools::prune(tree); CPPUNIT_ASSERT_EQUAL(Index64(4), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); // Verify that pruneInactive() prunes the nodes containing only inactive voxels. openvdb::tools::pruneInactive(tree); CPPUNIT_ASSERT_EQUAL(Index64(4), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(4), tree.leafCount()); // Make all of the active values inactive, without changing their values. tree.setValueOff(Coord( 5, 10, 20)); tree.setValueOff(Coord( 5,-10, 20)); tree.setValueOff(Coord( 5,-10,-20)); tree.setValueOff(Coord( 5, 10,-20)); CPPUNIT_ASSERT_EQUAL(Index64(0), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(4), tree.leafCount()); // Verify that prune() has no effect, since the values are still different. openvdb::tools::prune(tree); CPPUNIT_ASSERT_EQUAL(Index64(0), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(4), tree.leafCount()); // Verify that pruneInactive() prunes all of the remaining leaf nodes. openvdb::tools::pruneInactive(tree); CPPUNIT_ASSERT(tree.empty()); } void TestTree::testPruneLevelSet() { const float background=10.0f, R=5.6f; const openvdb::Vec3f C(12.3f, 15.5f, 10.0f); const openvdb::Coord dim(32, 32, 32); openvdb::FloatGrid grid(background); unittest_util::makeSphere(dim, C, R, grid, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); openvdb::FloatTree& tree = grid.tree(); openvdb::Index64 count = 0; openvdb::Coord xyz; for (xyz[0]=0; xyz[0]beginValueOn(); vIter; ++vIter) { if (fabs(*vIter)setValueOff(vIter.pos(), *vIter > 0.0f ? background : -background); ++removed; } } // The following version is slower since it employs // FloatTree::ValueOnIter that visits both tiles and voxels and // also uses random acceess to set the voxels off. /* for (openvdb::FloatTree::ValueOnIter i = tree.beginValueOn(); i; ++i) { if (fabs(*i) 0.0f ? background : -background); ++removed2; } */ CPPUNIT_ASSERT_EQUAL(leafCount, tree.leafCount()); //std::cerr << "Leaf count=" << tree.leafCount() << std::endl; CPPUNIT_ASSERT_EQUAL(tree.activeVoxelCount(), count-removed); CPPUNIT_ASSERT_EQUAL(tree.activeLeafVoxelCount(), count-removed); openvdb::tools::pruneLevelSet(tree); CPPUNIT_ASSERT(tree.leafCount() < leafCount); //std::cerr << "Leaf count=" << tree.leafCount() << std::endl; CPPUNIT_ASSERT_EQUAL(tree.activeVoxelCount(), count-removed); CPPUNIT_ASSERT_EQUAL(tree.activeLeafVoxelCount(), count-removed); openvdb::FloatTree::ValueOnCIter i = tree.cbeginValueOn(); for (; i; ++i) CPPUNIT_ASSERT( *i < new_width); for (xyz[0]=0; xyz[0]getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); CPPUNIT_ASSERT(tree->touchLeaf(xyz)!=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; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (lvl > 0) tree.addTile(lvl,ijk, 3.0, /*active=*/true); else tree.addTile(1,ijk, 3.0, /*active=*/true); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END CPPUNIT_ASSERT(tree.probeLeaf(ijk) == 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="< and Tree::getNodes()"); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::vector std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector and Tree::getNodes()"); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() const with std::vector std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector and Tree::getNodes() const"); const FloatTree& tmp = tree; tmp.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::vector and std::vector::reserve std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector, std::vector::reserve and Tree::getNodes"); array.reserve(tree.leafCount()); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::deque std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::getNodes"); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::deque std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::getNodes"); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(size_t(1), array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); } {//testing Tree::getNodes() with std::deque std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::getNodes"); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(size_t(1), array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); } /* {//testing Tree::getNodes() with std::deque where T is not part of the tree configuration typedef openvdb::tree::LeafNode NodeT; std::deque array; tree.getNodes(array);//should NOT compile since NodeT is not part of the FloatTree configuration } {//testing Tree::getNodes() const with std::deque where T is not part of the tree configuration typedef openvdb::tree::LeafNode NodeT; std::deque array; const FloatTree& tmp = tree; tmp.getNodes(array);//should NOT compile since NodeT is not part of the FloatTree configuration } */ }// testGetNodes void TestTree::testStealNodes() { //unittest_util::CpuTimer timer; using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Vec3f; using openvdb::FloatGrid; using openvdb::FloatTree; const Vec3f center(0.35f, 0.35f, 0.35f); const float radius = 0.15f; const int dim = 128, half_width = 5; const float voxel_size = 1.0f/dim; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/half_width*voxel_size); const FloatTree& tree = grid->tree(); grid->setTransform(openvdb::math::Transform::createLinearTransform(/*voxel size=*/voxel_size)); unittest_util::makeSphere( Coord(dim), center, radius, *grid, unittest_util::SPHERE_SPARSE_NARROW_BAND); const size_t leafCount = tree.leafCount(); const size_t voxelCount = tree.activeVoxelCount(); {//testing Tree::stealNodes() with std::vector FloatTree tree2 = tree; std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector and Tree::stealNodes()"); tree2.stealNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::stealNodes() with std::vector FloatTree tree2 = tree; std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector and Tree::stealNodes()"); tree2.stealNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::stealNodes() const with std::vector FloatTree tree2 = tree; std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector and Tree::stealNodes() const"); tree2.stealNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::stealNodes() with std::vector and std::vector::reserve FloatTree tree2 = tree; std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector, std::vector::reserve and Tree::stealNodes"); array.reserve(tree2.leafCount()); tree2.stealNodes(array, 0.0f, false); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::deque FloatTree tree2 = tree; std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::stealNodes"); tree2.stealNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::deque FloatTree tree2 = tree; std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::stealNodes"); tree2.stealNodes(array, 0.0f, true); //timer.stop(); CPPUNIT_ASSERT_EQUAL(size_t(1), array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); } {//testing Tree::getNodes() with std::deque FloatTree tree2 = tree; std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::stealNodes"); tree2.stealNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(size_t(1), array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); } /* {//testing Tree::stealNodes() with std::deque where T is not part of the tree configuration FloatTree tree2 = tree; typedef openvdb::tree::LeafNode NodeT; std::deque array; //should NOT compile since NodeT is not part of the FloatTree configuration tree2.stealNodes(array, 0.0f, true); } */ }// testStealNodes void TestTree::testLeafManager() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Vec3f; using openvdb::FloatGrid; using openvdb::FloatTree; const Vec3f center(0.35f, 0.35f, 0.35f); const float radius = 0.15f; const int dim = 128, half_width = 5; const float voxel_size = 1.0f/dim; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/half_width*voxel_size); FloatTree& tree = grid->tree(); grid->setTransform(openvdb::math::Transform::createLinearTransform(/*voxel size=*/voxel_size)); unittest_util::makeSphere( Coord(dim), center, radius, *grid, unittest_util::SPHERE_SPARSE_NARROW_BAND); const size_t leafCount = tree.leafCount(); //grid->print(std::cout, 3); {// test with no aux buffers openvdb::tree::LeafManager r(tree); CPPUNIT_ASSERT_EQUAL(leafCount, r.leafCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBufferCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBuffersPerLeaf()); size_t n = 0; for (FloatTree::LeafCIter iter=tree.cbeginLeaf(); iter; ++iter, ++n) { CPPUNIT_ASSERT(r.leaf(n) == *iter); CPPUNIT_ASSERT(r.getBuffer(n,0) == iter->buffer()); } CPPUNIT_ASSERT_EQUAL(r.leafCount(), n); CPPUNIT_ASSERT(!r.swapBuffer(0,0)); r.rebuildAuxBuffers(2); CPPUNIT_ASSERT_EQUAL(leafCount, r.leafCount()); CPPUNIT_ASSERT_EQUAL(size_t(2), r.auxBuffersPerLeaf()); CPPUNIT_ASSERT_EQUAL(size_t(2*leafCount),r.auxBufferCount()); for (n=0; n r(tree, 2); CPPUNIT_ASSERT_EQUAL(leafCount, r.leafCount()); CPPUNIT_ASSERT_EQUAL(size_t(2), r.auxBuffersPerLeaf()); CPPUNIT_ASSERT_EQUAL(size_t(2*leafCount),r.auxBufferCount()); size_t n = 0; for (FloatTree::LeafCIter iter=tree.cbeginLeaf(); iter; ++iter, ++n) { CPPUNIT_ASSERT(r.leaf(n) == *iter); CPPUNIT_ASSERT(r.getBuffer(n,0) == iter->buffer()); CPPUNIT_ASSERT(r.getBuffer(n,0) == r.getBuffer(n,1)); CPPUNIT_ASSERT(r.getBuffer(n,1) == r.getBuffer(n,2)); CPPUNIT_ASSERT(r.getBuffer(n,0) == r.getBuffer(n,2)); } CPPUNIT_ASSERT_EQUAL(r.leafCount(), n); for (n=0; n r(tree); for (size_t numAuxBuffers = 0; numAuxBuffers <= 2; ++numAuxBuffers += 2) { r.rebuildAuxBuffers(numAuxBuffers); CPPUNIT_ASSERT_EQUAL(leafCount, r.leafCount()); CPPUNIT_ASSERT_EQUAL(int(numAuxBuffers * leafCount), int(r.auxBufferCount())); CPPUNIT_ASSERT_EQUAL(numAuxBuffers, r.auxBuffersPerLeaf()); size_t n = 0; for (FloatTree::LeafCIter iter = tree.cbeginLeaf(); iter; ++iter, ++n) { CPPUNIT_ASSERT(r.leaf(n) == *iter); // Verify that each aux buffer was initialized with a copy of the leaf buffer. for (size_t bufIdx = 0; bufIdx < numAuxBuffers; ++bufIdx) { CPPUNIT_ASSERT(r.getBuffer(n, bufIdx) == iter->buffer()); } } CPPUNIT_ASSERT_EQUAL(r.leafCount(), n); for (size_t i = 0; i < numAuxBuffers; ++i) { for (size_t j = 0; j < numAuxBuffers; ++j) { // Verify that swapping buffers with themselves and swapping // leaf buffers with aux buffers have no effect. const bool canSwap = (i != j && i != 0 && j != 0); CPPUNIT_ASSERT_EQUAL(canSwap, r.swapBuffer(i, j)); } } } } } void TestTree::testNodeManager() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Vec3f; using openvdb::Index64; using openvdb::FloatGrid; using openvdb::FloatTree; const Vec3f center(0.35f, 0.35f, 0.35f); const float radius = 0.15f; const int dim = 128, half_width = 5; const float voxel_size = 1.0f/dim; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/half_width*voxel_size); FloatTree& tree = grid->tree(); grid->setTransform(openvdb::math::Transform::createLinearTransform(/*voxel size=*/voxel_size)); unittest_util::makeSphere(Coord(dim), center, radius, *grid, unittest_util::SPHERE_SPARSE_NARROW_BAND); CPPUNIT_ASSERT_EQUAL(4, int(FloatTree::DEPTH)); CPPUNIT_ASSERT_EQUAL(3, int(openvdb::tree::NodeManager::LEVELS)); std::vector nodeCount; for (openvdb::Index i=0; i #include #include 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 // for createLevelSetBox() #include // for csgDifference() class TestLevelSetUtil: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestLevelSetUtil); CPPUNIT_TEST(testSDFToFogVolume); CPPUNIT_TEST(testSDFInteriorMask); CPPUNIT_TEST(testExtractEnclosedRegion); CPPUNIT_TEST_SUITE_END(); void testSDFToFogVolume(); void testSDFInteriorMask(); void testExtractEnclosedRegion(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLevelSetUtil); //////////////////////////////////////// void TestLevelSetUtil::testSDFToFogVolume() { openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(10.0); grid->fill(openvdb::CoordBBox(openvdb::Coord(-100), openvdb::Coord(100)), 9.0); grid->fill(openvdb::CoordBBox(openvdb::Coord(-50), openvdb::Coord(50)), -9.0); openvdb::tools::sdfToFogVolume(*grid); CPPUNIT_ASSERT(grid->background() < 1e-7); openvdb::FloatGrid::ValueOnIter iter = grid->beginValueOn(); for (; iter; ++iter) { CPPUNIT_ASSERT(iter.getValue() > 0.0); CPPUNIT_ASSERT(std::abs(iter.getValue() - 1.0) < 1e-7); } } void TestLevelSetUtil::testSDFInteriorMask() { typedef openvdb::FloatGrid FloatGrid; typedef openvdb::BoolGrid BoolGrid; typedef openvdb::Vec3s Vec3s; typedef openvdb::math::BBox BBoxs; typedef openvdb::math::Transform Transform; BBoxs bbox(Vec3s(0.0, 0.0, 0.0), Vec3s(1.0, 1.0, 1.0)); Transform::Ptr transform = Transform::createLinearTransform(0.1); FloatGrid::Ptr sdfGrid = openvdb::tools::createLevelSetBox(bbox, *transform); BoolGrid::Ptr maskGrid = openvdb::tools::sdfInteriorMask(*sdfGrid); // test inside coord value openvdb::Coord ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(0.5, 0.5, 0.5)); CPPUNIT_ASSERT(maskGrid->tree().getValue(ijk) == true); // test outside coord value ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(1.5, 1.5, 1.5)); CPPUNIT_ASSERT(maskGrid->tree().getValue(ijk) == false); } void TestLevelSetUtil::testExtractEnclosedRegion() { typedef openvdb::FloatGrid FloatGrid; typedef openvdb::BoolGrid BoolGrid; typedef openvdb::Vec3s Vec3s; typedef openvdb::math::BBox BBoxs; typedef openvdb::math::Transform Transform; BBoxs regionA(Vec3s(0.0, 0.0, 0.0), Vec3s(3.0, 3.0, 3.0)); BBoxs regionB(Vec3s(1.0, 1.0, 1.0), Vec3s(2.0, 2.0, 2.0)); Transform::Ptr transform = Transform::createLinearTransform(0.1); FloatGrid::Ptr sdfGrid = openvdb::tools::createLevelSetBox(regionA, *transform); FloatGrid::Ptr sdfGridB = openvdb::tools::createLevelSetBox(regionB, *transform); openvdb::tools::csgDifference(*sdfGrid, *sdfGridB); BoolGrid::Ptr maskGrid = openvdb::tools::extractEnclosedRegion(*sdfGrid); // test inside ls region coord value openvdb::Coord ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(1.5, 1.5, 1.5)); CPPUNIT_ASSERT(maskGrid->tree().getValue(ijk) == true); // test outside coord value ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(3.5, 3.5, 3.5)); CPPUNIT_ASSERT(maskGrid->tree().getValue(ijk) == false); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLinearInterp.cc0000644000000000000000000012142212603226506016637 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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] = static_cast(::toupper(name[0])); return "TestLinearInterp" + name; } CPPUNIT_TEST_SUITE(TestLinearInterp); CPPUNIT_TEST(test); CPPUNIT_TEST(testTree); CPPUNIT_TEST(testAccessor); CPPUNIT_TEST(testConstantValues); CPPUNIT_TEST(testFillValues); CPPUNIT_TEST(testNegativeIndices); CPPUNIT_TEST_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); {//using BoxSampler // transform used for worldspace interpolation) openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); typename GridType::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); } {//using Sampler<1> // transform used for worldspace interpolation) openvdb::tools::GridSampler > interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); typename GridType::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); } } template<> void TestLinearInterp::test() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3SGrid grid(fillValue); Vec3STree& tree = grid.tree(); tree.setValue(openvdb::Coord(10, 10, 10), Vec3s(1.0, 1.0, 1.0)); tree.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(10, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 9, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(10, 9, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 9, 9), Vec3s(4.0, 4.0, 4.0)); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.375f))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.f))); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.f))); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.f))); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.f))); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.f))); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.f))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1f))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792f))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71f))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01f))); } template void TestLinearInterp::testTree() { float fillValue = 256.0f; typedef typename GridType::TreeType TreeType; TreeType tree(fillValue); tree.setValue(openvdb::Coord(10, 10, 10), 1.0); tree.setValue(openvdb::Coord(11, 10, 10), 2.0); tree.setValue(openvdb::Coord(11, 11, 10), 2.0); tree.setValue(openvdb::Coord(10, 11, 10), 2.0); tree.setValue(openvdb::Coord( 9, 11, 10), 2.0); tree.setValue(openvdb::Coord( 9, 10, 10), 2.0); tree.setValue(openvdb::Coord( 9, 9, 10), 2.0); tree.setValue(openvdb::Coord(10, 9, 10), 2.0); tree.setValue(openvdb::Coord(11, 9, 10), 2.0); tree.setValue(openvdb::Coord(10, 10, 11), 3.0); tree.setValue(openvdb::Coord(11, 10, 11), 3.0); tree.setValue(openvdb::Coord(11, 11, 11), 3.0); tree.setValue(openvdb::Coord(10, 11, 11), 3.0); tree.setValue(openvdb::Coord( 9, 11, 11), 3.0); tree.setValue(openvdb::Coord( 9, 10, 11), 3.0); tree.setValue(openvdb::Coord( 9, 9, 11), 3.0); tree.setValue(openvdb::Coord(10, 9, 11), 3.0); tree.setValue(openvdb::Coord(11, 9, 11), 3.0); tree.setValue(openvdb::Coord(10, 10, 9), 4.0); tree.setValue(openvdb::Coord(11, 10, 9), 4.0); tree.setValue(openvdb::Coord(11, 11, 9), 4.0); tree.setValue(openvdb::Coord(10, 11, 9), 4.0); tree.setValue(openvdb::Coord( 9, 11, 9), 4.0); tree.setValue(openvdb::Coord( 9, 10, 9), 4.0); tree.setValue(openvdb::Coord( 9, 9, 9), 4.0); tree.setValue(openvdb::Coord(10, 9, 9), 4.0); tree.setValue(openvdb::Coord(11, 9, 9), 4.0); // transform used for worldspace interpolation) openvdb::tools::GridSampler interpolator(tree, openvdb::math::Transform()); typename GridType::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); } template<> void TestLinearInterp::testTree() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3STree tree(fillValue); tree.setValue(openvdb::Coord(10, 10, 10), Vec3s(1.0, 1.0, 1.0)); tree.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(10, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 9, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(10, 9, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 9, 9), Vec3s(4.0, 4.0, 4.0)); openvdb::tools::GridSampler interpolator(tree, openvdb::math::Transform()); //openvdb::tools::LinearInterp interpolator(*tree); Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.375f))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.f))); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.f))); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.f))); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.f))); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.f))); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.f))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1f))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792f))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71f))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01f))); } template void TestLinearInterp::testAccessor() { float fillValue = 256.0f; GridType grid(fillValue); typedef typename GridType::Accessor AccessorType; AccessorType acc = grid.getAccessor(); acc.setValue(openvdb::Coord(10, 10, 10), 1.0); acc.setValue(openvdb::Coord(11, 10, 10), 2.0); acc.setValue(openvdb::Coord(11, 11, 10), 2.0); acc.setValue(openvdb::Coord(10, 11, 10), 2.0); acc.setValue(openvdb::Coord( 9, 11, 10), 2.0); acc.setValue(openvdb::Coord( 9, 10, 10), 2.0); acc.setValue(openvdb::Coord( 9, 9, 10), 2.0); acc.setValue(openvdb::Coord(10, 9, 10), 2.0); acc.setValue(openvdb::Coord(11, 9, 10), 2.0); acc.setValue(openvdb::Coord(10, 10, 11), 3.0); acc.setValue(openvdb::Coord(11, 10, 11), 3.0); acc.setValue(openvdb::Coord(11, 11, 11), 3.0); acc.setValue(openvdb::Coord(10, 11, 11), 3.0); acc.setValue(openvdb::Coord( 9, 11, 11), 3.0); acc.setValue(openvdb::Coord( 9, 10, 11), 3.0); acc.setValue(openvdb::Coord( 9, 9, 11), 3.0); acc.setValue(openvdb::Coord(10, 9, 11), 3.0); acc.setValue(openvdb::Coord(11, 9, 11), 3.0); acc.setValue(openvdb::Coord(10, 10, 9), 4.0); acc.setValue(openvdb::Coord(11, 10, 9), 4.0); acc.setValue(openvdb::Coord(11, 11, 9), 4.0); acc.setValue(openvdb::Coord(10, 11, 9), 4.0); acc.setValue(openvdb::Coord( 9, 11, 9), 4.0); acc.setValue(openvdb::Coord( 9, 10, 9), 4.0); acc.setValue(openvdb::Coord( 9, 9, 9), 4.0); acc.setValue(openvdb::Coord(10, 9, 9), 4.0); acc.setValue(openvdb::Coord(11, 9, 9), 4.0); // transform used for worldspace interpolation) openvdb::tools::GridSampler interpolator(acc, grid.transform()); typename GridType::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); } template<> void TestLinearInterp::testAccessor() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3SGrid grid(fillValue); typedef Vec3SGrid::Accessor AccessorType; AccessorType acc = grid.getAccessor(); acc.setValue(openvdb::Coord(10, 10, 10), Vec3s(1.0, 1.0, 1.0)); acc.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord(10, 10, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(11, 10, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(11, 11, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(10, 11, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord( 9, 11, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord( 9, 10, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord( 9, 9, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(10, 9, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(11, 9, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(10, 10, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord(11, 10, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord(11, 11, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord(10, 11, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord( 9, 11, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord( 9, 10, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord( 9, 9, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord(10, 9, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord(11, 9, 9), Vec3s(4.0, 4.0, 4.0)); openvdb::tools::GridSampler interpolator(acc, grid.transform()); Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.375f))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.0f))); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0f))); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0f))); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.0f))); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0f))); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0f))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1f))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792f))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71f))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01f))); } template void TestLinearInterp::testConstantValues() { typedef typename GridType::TreeType TreeType; float fillValue = 256.0f; GridType grid(fillValue); TreeType& tree = grid.tree(); // Add values to buffer zero. tree.setValue(openvdb::Coord(10, 10, 10), 2.0); tree.setValue(openvdb::Coord(11, 10, 10), 2.0); tree.setValue(openvdb::Coord(11, 11, 10), 2.0); tree.setValue(openvdb::Coord(10, 11, 10), 2.0); tree.setValue(openvdb::Coord( 9, 11, 10), 2.0); tree.setValue(openvdb::Coord( 9, 10, 10), 2.0); tree.setValue(openvdb::Coord( 9, 9, 10), 2.0); tree.setValue(openvdb::Coord(10, 9, 10), 2.0); tree.setValue(openvdb::Coord(11, 9, 10), 2.0); tree.setValue(openvdb::Coord(10, 10, 11), 2.0); tree.setValue(openvdb::Coord(11, 10, 11), 2.0); tree.setValue(openvdb::Coord(11, 11, 11), 2.0); tree.setValue(openvdb::Coord(10, 11, 11), 2.0); tree.setValue(openvdb::Coord( 9, 11, 11), 2.0); tree.setValue(openvdb::Coord( 9, 10, 11), 2.0); tree.setValue(openvdb::Coord( 9, 9, 11), 2.0); tree.setValue(openvdb::Coord(10, 9, 11), 2.0); tree.setValue(openvdb::Coord(11, 9, 11), 2.0); tree.setValue(openvdb::Coord(10, 10, 9), 2.0); tree.setValue(openvdb::Coord(11, 10, 9), 2.0); tree.setValue(openvdb::Coord(11, 11, 9), 2.0); tree.setValue(openvdb::Coord(10, 11, 9), 2.0); tree.setValue(openvdb::Coord( 9, 11, 9), 2.0); tree.setValue(openvdb::Coord( 9, 10, 9), 2.0); tree.setValue(openvdb::Coord( 9, 9, 9), 2.0); tree.setValue(openvdb::Coord(10, 9, 9), 2.0); tree.setValue(openvdb::Coord(11, 9, 9), 2.0); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); typename GridType::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); } template<> void TestLinearInterp::testConstantValues() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3SGrid grid(fillValue); Vec3STree& tree = grid.tree(); // Add values to buffer zero. tree.setValue(openvdb::Coord(10, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 10, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 10, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 11, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 11, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 11, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 10, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 9, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 9, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 9, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 10, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 10, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 11, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 11, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 11, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 10, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 9, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 9, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 9, 9), Vec3s(2.0, 2.0, 2.0)); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); } template void TestLinearInterp::testFillValues() { //typedef typename GridType::TreeType TreeType; float fillValue = 256.0f; GridType grid(fillValue); //typename GridType::TreeType& tree = grid.tree(); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); typename GridType::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); } template<> void TestLinearInterp::testFillValues() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3SGrid grid(fillValue); //Vec3STree& tree = grid.tree(); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); } template void TestLinearInterp::testNegativeIndices() { typedef typename GridType::TreeType TreeType; float fillValue = 256.0f; GridType grid(fillValue); TreeType& tree = grid.tree(); tree.setValue(openvdb::Coord(-10, -10, -10), 1.0); tree.setValue(openvdb::Coord(-11, -10, -10), 2.0); tree.setValue(openvdb::Coord(-11, -11, -10), 2.0); tree.setValue(openvdb::Coord(-10, -11, -10), 2.0); tree.setValue(openvdb::Coord( -9, -11, -10), 2.0); tree.setValue(openvdb::Coord( -9, -10, -10), 2.0); tree.setValue(openvdb::Coord( -9, -9, -10), 2.0); tree.setValue(openvdb::Coord(-10, -9, -10), 2.0); tree.setValue(openvdb::Coord(-11, -9, -10), 2.0); tree.setValue(openvdb::Coord(-10, -10, -11), 3.0); tree.setValue(openvdb::Coord(-11, -10, -11), 3.0); tree.setValue(openvdb::Coord(-11, -11, -11), 3.0); tree.setValue(openvdb::Coord(-10, -11, -11), 3.0); tree.setValue(openvdb::Coord( -9, -11, -11), 3.0); tree.setValue(openvdb::Coord( -9, -10, -11), 3.0); tree.setValue(openvdb::Coord( -9, -9, -11), 3.0); tree.setValue(openvdb::Coord(-10, -9, -11), 3.0); tree.setValue(openvdb::Coord(-11, -9, -11), 3.0); tree.setValue(openvdb::Coord(-10, -10, -9), 4.0); tree.setValue(openvdb::Coord(-11, -10, -9), 4.0); tree.setValue(openvdb::Coord(-11, -11, -9), 4.0); tree.setValue(openvdb::Coord(-10, -11, -9), 4.0); tree.setValue(openvdb::Coord( -9, -11, -9), 4.0); tree.setValue(openvdb::Coord( -9, -10, -9), 4.0); tree.setValue(openvdb::Coord( -9, -9, -9), 4.0); tree.setValue(openvdb::Coord(-10, -9, -9), 4.0); tree.setValue(openvdb::Coord(-11, -9, -9), 4.0); //openvdb::tools::LinearInterp interpolator(*tree); openvdb::tools::GridSampler interpolator(grid); typename GridType::ValueType val = interpolator.sampleVoxel(-10.5, -10.5, -10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); val = interpolator.sampleVoxel(-10.0, -10.0, -10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); val = interpolator.sampleVoxel(-11.0, -10.0, -10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(-11.0, -11.0, -10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(-11.0, -11.0, -11.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); val = interpolator.sampleVoxel(-9.0, -11.0, -9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(-9.0, -10.0, -9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(-10.1, -10.0, -10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); val = interpolator.sampleVoxel(-10.8, -10.8, -10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); val = interpolator.sampleVoxel(-10.1, -10.8, -10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(-10.8, -10.1, -10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(-10.5, -10.1, -10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); val = interpolator.sampleVoxel(-10.5, -10.8, -10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); } template<> void TestLinearInterp::testNegativeIndices() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3SGrid grid(fillValue); Vec3STree& tree = grid.tree(); tree.setValue(openvdb::Coord(-10, -10, -10), Vec3s(1.0, 1.0, 1.0)); tree.setValue(openvdb::Coord(-11, -10, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(-11, -11, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(-10, -11, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( -9, -11, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( -9, -10, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( -9, -9, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(-10, -9, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(-11, -9, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(-10, -10, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-11, -10, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-11, -11, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-10, -11, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( -9, -11, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( -9, -10, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( -9, -9, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-10, -9, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-11, -9, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-10, -10, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(-11, -10, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(-11, -11, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(-10, -11, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( -9, -11, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( -9, -10, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( -9, -9, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(-10, -9, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(-11, -9, -9), Vec3s(4.0, 4.0, 4.0)); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); Vec3SGrid::ValueType val = interpolator.sampleVoxel(-10.5, -10.5, -10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.375f))); val = interpolator.sampleVoxel(-10.0, -10.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.0f))); val = interpolator.sampleVoxel(-11.0, -10.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0f))); val = interpolator.sampleVoxel(-11.0, -11.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0f))); val = interpolator.sampleVoxel(-11.0, -11.0, -11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.0f))); val = interpolator.sampleVoxel(-9.0, -11.0, -9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0f))); val = interpolator.sampleVoxel(-9.0, -10.0, -9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0f))); val = interpolator.sampleVoxel(-10.1, -10.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1f))); val = interpolator.sampleVoxel(-10.8, -10.8, -10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792f))); val = interpolator.sampleVoxel(-10.1, -10.8, -10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(-10.8, -10.1, -10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(-10.5, -10.1, -10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71f))); val = interpolator.sampleVoxel(-10.5, -10.8, -10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01f))); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestValueAccessor.cc0000644000000000000000000005547312603226506017016 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); typedef float ValueType; typedef openvdb::tree::Tree< openvdb::tree::RootNode< openvdb::tree::LeafNode > > Tree2Type; typedef openvdb::tree::Tree< openvdb::tree::RootNode< openvdb::tree::InternalNode< openvdb::tree::LeafNode, 4> > > Tree3Type; typedef openvdb::tree::Tree4::Type Tree4Type; typedef openvdb::tree::Tree< openvdb::tree::RootNode< openvdb::tree::InternalNode< openvdb::tree::InternalNode< openvdb::tree::InternalNode< openvdb::tree::LeafNode, 4>, 5>, 5> > > Tree5Type; typedef Tree4Type TreeType; using namespace openvdb::tree; class TestValueAccessor: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestValueAccessor); CPPUNIT_TEST(testTree2Accessor); CPPUNIT_TEST(testTree2AccessorRW); CPPUNIT_TEST(testTree2ConstAccessor); CPPUNIT_TEST(testTree2ConstAccessorRW); CPPUNIT_TEST(testTree3Accessor); CPPUNIT_TEST(testTree3AccessorRW); CPPUNIT_TEST(testTree3ConstAccessor); CPPUNIT_TEST(testTree3ConstAccessorRW); CPPUNIT_TEST(testTree4Accessor); CPPUNIT_TEST(testTree4AccessorRW); CPPUNIT_TEST(testTree4ConstAccessor); CPPUNIT_TEST(testTree4ConstAccessorRW); CPPUNIT_TEST(testTree5Accessor); CPPUNIT_TEST(testTree5AccessorRW); CPPUNIT_TEST(testTree5ConstAccessor); CPPUNIT_TEST(testTree5ConstAccessorRW); CPPUNIT_TEST(testTree3Accessor2); CPPUNIT_TEST(testTree3ConstAccessor2); CPPUNIT_TEST(testTree4Accessor2); CPPUNIT_TEST(testTree4ConstAccessor2); CPPUNIT_TEST(testTree4Accessor1); CPPUNIT_TEST(testTree4ConstAccessor1); CPPUNIT_TEST(testTree4Accessor0); CPPUNIT_TEST(testTree4ConstAccessor0); CPPUNIT_TEST(testTree5Accessor2); CPPUNIT_TEST(testTree5ConstAccessor2); CPPUNIT_TEST(testTree4Accessor12);//cache node level 2 CPPUNIT_TEST(testTree5Accessor213);//cache node level 1 and 3 CPPUNIT_TEST(testMultithreadedAccessor); CPPUNIT_TEST(testAccessorRegistration); CPPUNIT_TEST(testGetNode); CPPUNIT_TEST_SUITE_END(); // cache all node levels void testTree2Accessor() { accessorTest >(); } void testTree2AccessorRW() { accessorTest >(); } void testTree2ConstAccessor() { constAccessorTest >(); } void testTree2ConstAccessorRW() { constAccessorTest >(); } // cache all node levels void testTree3Accessor() { accessorTest >(); } void testTree3AccessorRW() { accessorTest >(); } void testTree3ConstAccessor() { constAccessorTest >(); } void testTree3ConstAccessorRW() { constAccessorTest >(); } // cache all node levels void testTree4Accessor() { accessorTest >(); } void testTree4AccessorRW() { accessorTest >(); } void testTree4ConstAccessor() { constAccessorTest >(); } void testTree4ConstAccessorRW() { constAccessorTest >(); } // cache all node levels void testTree5Accessor() { accessorTest >(); } void testTree5AccessorRW() { accessorTest >(); } void testTree5ConstAccessor() { constAccessorTest >(); } void testTree5ConstAccessorRW() { constAccessorTest >(); } // Test odd combinations of trees and ValueAccessors // cache node level 0 and 1 void testTree3Accessor2() { accessorTest >(); accessorTest >(); } void testTree3ConstAccessor2() { constAccessorTest >(); constAccessorTest >(); } void testTree4Accessor2() { accessorTest >(); accessorTest >(); } void testTree4ConstAccessor2() { constAccessorTest >(); constAccessorTest >(); } void testTree5Accessor2() { accessorTest >(); accessorTest >(); } void testTree5ConstAccessor2() { constAccessorTest >(); constAccessorTest >(); } // only cache leaf level void testTree4Accessor1() { accessorTest >(); accessorTest >(); } void testTree4ConstAccessor1() { constAccessorTest >(); constAccessorTest >(); } // disable node caching void testTree4Accessor0() { accessorTest >(); accessorTest >(); } void testTree4ConstAccessor0() { constAccessorTest >(); constAccessorTest >(); } //cache node level 2 void testTree4Accessor12() { accessorTest >(); accessorTest >(); } //cache node level 1 and 3 void testTree5Accessor213() { accessorTest >(); accessorTest >(); } void testMultithreadedAccessor(); void testAccessorRegistration(); void testGetNode(); private: template void accessorTest(); template void constAccessorTest(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestValueAccessor); //////////////////////////////////////// namespace { struct Plus { float addend; Plus(float f): addend(f) {} inline void operator()(float& f) const { f += addend; } inline void operator()(float& f, bool& b) const { f += addend; b = false; } }; } template void TestValueAccessor::accessorTest() { typedef typename AccessorT::TreeType TreeType; const int leafDepth = int(TreeType::DEPTH) - 1; // subtract one because getValueDepth() returns 0 for values at the root const ValueType background = 5.0f, value = -9.345f; const openvdb::Coord c0(5, 10, 20), c1(500000, 200000, 300000); { TreeType tree(background); CPPUNIT_ASSERT(!tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); tree.setValue(c0, value); CPPUNIT_ASSERT(tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); } { TreeType tree(background); AccessorT acc(tree); ValueType v; CPPUNIT_ASSERT(!tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT(!acc.isCached(c1)); CPPUNIT_ASSERT(!acc.probeValue(c0,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT(!acc.probeValue(c1,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); CPPUNIT_ASSERT(!acc.isVoxel(c0)); CPPUNIT_ASSERT(!acc.isVoxel(c1)); acc.setValue(c0, value); CPPUNIT_ASSERT(tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); CPPUNIT_ASSERT(acc.probeValue(c0,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, v); CPPUNIT_ASSERT(!acc.probeValue(c1,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); // leaf-level voxel value CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); // background value CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(openvdb::Coord(7, 10, 20))); const int depth = leafDepth == 1 ? -1 : leafDepth - 1; CPPUNIT_ASSERT_EQUAL(depth, acc.getValueDepth(openvdb::Coord(8, 10, 20))); CPPUNIT_ASSERT( acc.isVoxel(c0)); // leaf-level voxel value CPPUNIT_ASSERT(!acc.isVoxel(c1)); CPPUNIT_ASSERT( acc.isVoxel(openvdb::Coord(7, 10, 20))); CPPUNIT_ASSERT(!acc.isVoxel(openvdb::Coord(8, 10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(background, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c1)); // uncached background value CPPUNIT_ASSERT(!acc.isValueOn(c1)); // inactive background value ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); CPPUNIT_ASSERT( (acc.numCacheLevels()>0) == acc.isCached(c0)); // active, leaf-level voxel value CPPUNIT_ASSERT(acc.isValueOn(c0)); acc.setValue(c1, value); CPPUNIT_ASSERT(acc.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c1)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); CPPUNIT_ASSERT(acc.isVoxel(c0)); CPPUNIT_ASSERT(acc.isVoxel(c1)); tree.setValueOff(c1); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); CPPUNIT_ASSERT( acc.isValueOn(c0)); CPPUNIT_ASSERT(!acc.isValueOn(c1)); acc.setValueOn(c1); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); CPPUNIT_ASSERT( acc.isValueOn(c0)); CPPUNIT_ASSERT( acc.isValueOn(c1)); acc.modifyValueAndActiveState(c1, Plus(-value)); // subtract value & mark inactive CPPUNIT_ASSERT(!acc.isValueOn(c1)); acc.modifyValue(c1, Plus(-value)); // subtract value again & mark active CPPUNIT_ASSERT(acc.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(-value, tree.getValue(c1)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(-value, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); CPPUNIT_ASSERT(acc.isVoxel(c0)); CPPUNIT_ASSERT(acc.isVoxel(c1)); acc.setValueOnly(c1, 3*value); CPPUNIT_ASSERT(acc.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(3*value, tree.getValue(c1)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(3*value, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); CPPUNIT_ASSERT(acc.isVoxel(c0)); CPPUNIT_ASSERT(acc.isVoxel(c1)); acc.clear(); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT(!acc.isCached(c1)); } } template void TestValueAccessor::constAccessorTest() { typedef typename boost::remove_const::type TreeType; const int leafDepth = int(TreeType::DEPTH) - 1; // subtract one because getValueDepth() returns 0 for values at the root const ValueType background = 5.0f, value = -9.345f; const openvdb::Coord c0(5, 10, 20), c1(500000, 200000, 300000); ValueType v; TreeType tree(background); AccessorT acc(tree); CPPUNIT_ASSERT(!tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT(!acc.isCached(c1)); CPPUNIT_ASSERT(!acc.probeValue(c0,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT(!acc.probeValue(c1,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); CPPUNIT_ASSERT(!acc.isVoxel(c0)); CPPUNIT_ASSERT(!acc.isVoxel(c1)); tree.setValue(c0, value); CPPUNIT_ASSERT(tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT(acc.isValueOn(c0)); CPPUNIT_ASSERT(!acc.isValueOn(c1)); CPPUNIT_ASSERT(acc.probeValue(c0,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, v); CPPUNIT_ASSERT(!acc.probeValue(c1,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); CPPUNIT_ASSERT( acc.isVoxel(c0)); CPPUNIT_ASSERT(!acc.isVoxel(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, acc.getValue(c1)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); CPPUNIT_ASSERT(!acc.isCached(c1)); CPPUNIT_ASSERT(acc.isValueOn(c0)); CPPUNIT_ASSERT(!acc.isValueOn(c1)); tree.setValue(c1, value); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); CPPUNIT_ASSERT(acc.isValueOn(c0)); CPPUNIT_ASSERT(acc.isValueOn(c1)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); CPPUNIT_ASSERT(acc.isVoxel(c0)); CPPUNIT_ASSERT(acc.isVoxel(c1)); // The next two lines should not compile, because the acc references a const tree: //acc.setValue(c1, value); //acc.setValueOff(c1); acc.clear(); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT(!acc.isCached(c1)); } void TestValueAccessor::testMultithreadedAccessor() { #define MAX_COORD 5000 typedef openvdb::tree::ValueAccessorRW AccessorT; // Substituting the following typedef typically results in assertion failures: //typedef openvdb::tree::ValueAccessor AccessorT; // Task to perform multiple reads through a shared accessor struct ReadTask: public tbb::task { AccessorT& acc; ReadTask(AccessorT& c): acc(c) {} tbb::task* execute() { for (int i = -MAX_COORD; i < MAX_COORD; ++i) { ASSERT_DOUBLES_EXACTLY_EQUAL(double(i), acc.getValue(openvdb::Coord(i))); } return NULL; } }; // Task to perform multiple writes through a shared accessor struct WriteTask: public tbb::task { AccessorT& acc; WriteTask(AccessorT& c): acc(c) {} tbb::task* execute() { for (int i = -MAX_COORD; i < MAX_COORD; ++i) { float f = acc.getValue(openvdb::Coord(i)); ASSERT_DOUBLES_EXACTLY_EQUAL(float(i), f); acc.setValue(openvdb::Coord(i), float(i)); ASSERT_DOUBLES_EXACTLY_EQUAL(float(i), acc.getValue(openvdb::Coord(i))); } return NULL; } }; // Parent task to spawn multiple parallel read and write tasks struct RootTask: public tbb::task { AccessorT& acc; RootTask(AccessorT& c): acc(c) {} tbb::task* execute() { ReadTask* r[3]; WriteTask* w[3]; for (int i = 0; i < 3; ++i) { r[i] = new(allocate_child()) ReadTask(acc); w[i] = new(allocate_child()) WriteTask(acc); } set_ref_count(6 /*children*/ + 1 /*wait*/); for (int i = 0; i < 3; ++i) { spawn(*r[i]); spawn(*w[i]); } wait_for_all(); return NULL; } }; Tree4Type tree(/*background=*/0.5); AccessorT acc(tree); // Populate the tree. for (int i = -MAX_COORD; i < MAX_COORD; ++i) { acc.setValue(openvdb::Coord(i), float(i)); } // Run multiple read and write tasks in parallel. RootTask& root = *new(tbb::task::allocate_root()) RootTask(acc); tbb::task::spawn_root_and_wait(root); #undef MAX_COORD } void TestValueAccessor::testAccessorRegistration() { using openvdb::Index; const float background = 5.0f, value = -9.345f; const openvdb::Coord c0(5, 10, 20); openvdb::FloatTree::Ptr tree(new openvdb::FloatTree(background)); openvdb::tree::ValueAccessor acc(*tree); // Set a single leaf voxel via the accessor and verify that // the cache is populated. acc.setValue(c0, value); CPPUNIT_ASSERT_EQUAL(Index(1), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(tree->root().getLevel(), tree->nonLeafCount()); CPPUNIT_ASSERT(acc.getNode() != NULL); // Reset the voxel to the background value and verify that no nodes // have been deleted and that the cache is still populated. tree->setValueOff(c0, background); CPPUNIT_ASSERT_EQUAL(Index(1), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(tree->root().getLevel(), tree->nonLeafCount()); CPPUNIT_ASSERT(acc.getNode() != NULL); // Prune the tree and verify that only the root node remains and that // the cache has been cleared. openvdb::tools::prune(*tree); //tree->prune(); CPPUNIT_ASSERT_EQUAL(Index(0), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(Index(1), tree->nonLeafCount()); // root node only CPPUNIT_ASSERT(acc.getNode() == NULL); // Set the leaf voxel again and verify that the cache is repopulated. acc.setValue(c0, value); CPPUNIT_ASSERT_EQUAL(Index(1), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(tree->root().getLevel(), tree->nonLeafCount()); CPPUNIT_ASSERT(acc.getNode() != NULL); // Delete the tree and verify that the cache has been cleared. tree.reset(); CPPUNIT_ASSERT(acc.getTree() == NULL); CPPUNIT_ASSERT(acc.getNode() == NULL); CPPUNIT_ASSERT(acc.getNode() == NULL); } void TestValueAccessor::testGetNode() { typedef Tree4Type::LeafNodeType LeafT; const ValueType background = 5.0f, value = -9.345f; const openvdb::Coord c0(5, 10, 20); Tree4Type tree(background); tree.setValue(c0, value); { openvdb::tree::ValueAccessor acc(tree); // Prime the cache. acc.getValue(c0); // Verify that the cache contains a leaf node. LeafT* node = acc.getNode(); CPPUNIT_ASSERT(node != NULL); // Erase the leaf node from the cache and verify that it is gone. acc.eraseNode(); node = acc.getNode(); CPPUNIT_ASSERT(node == NULL); } { // As above, but with a const tree. openvdb::tree::ValueAccessor acc(tree); acc.getValue(c0); const LeafT* node = acc.getNode(); CPPUNIT_ASSERT(node != NULL); acc.eraseNode(); node = acc.getNode(); CPPUNIT_ASSERT(node == NULL); } } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestQuadraticInterp.cc0000644000000000000000000003232612603226506017346 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file TestQuadraticInterp.cc #include #include #include #include // CPPUNIT_TEST_SUITE() invokes CPPUNIT_TESTNAMER_DECL() to generate a suite name // from the FixtureType. But if FixtureType is a templated type, the generated name // can become long and messy. This macro overrides the normal naming logic, // instead invoking FixtureType::testSuiteName(), which should be a static member // function that returns a std::string containing the suite name for the specific // template instantiation. #undef CPPUNIT_TESTNAMER_DECL #define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ CPPUNIT_NS::TestNamer variableName( FixtureType::testSuiteName() ) namespace { // Absolute tolerance for floating-point equality comparisons const double TOLERANCE = 1.0e-5; } //////////////////////////////////////// template class TestQuadraticInterp: public CppUnit::TestCase { public: typedef typename GridType::ValueType ValueT; typedef typename GridType::Ptr GridPtr; struct TestVal { float x, y, z; ValueT expected; }; static std::string testSuiteName() { std::string name = openvdb::typeNameAsString(); if (!name.empty()) name[0] = static_cast(::toupper(name[0])); return "TestQuadraticInterp" + name; } CPPUNIT_TEST_SUITE(TestQuadraticInterp); CPPUNIT_TEST(test); CPPUNIT_TEST(testConstantValues); CPPUNIT_TEST(testFillValues); CPPUNIT_TEST(testNegativeIndices); CPPUNIT_TEST_SUITE_END(); void test(); void testConstantValues(); void testFillValues(); void testNegativeIndices(); private: void executeTest(const GridPtr&, const TestVal*, size_t numVals) const; /// Initialize an arbitrary ValueType from a scalar. static inline ValueT constValue(double d) { return ValueT(d); } /// Compare two numeric values for equality within an absolute tolerance. static inline bool relEq(const ValueT& v1, const ValueT& v2) { return fabs(v1 - v2) <= TOLERANCE; } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestQuadraticInterp); CPPUNIT_TEST_SUITE_REGISTRATION(TestQuadraticInterp); CPPUNIT_TEST_SUITE_REGISTRATION(TestQuadraticInterp); //////////////////////////////////////// /// Specialization for Vec3s grids template<> inline openvdb::Vec3s TestQuadraticInterp::constValue(double d) { return openvdb::Vec3s(float(d), float(d), float(d)); } /// Specialization for Vec3s grids template<> inline bool TestQuadraticInterp::relEq( const openvdb::Vec3s& v1, const openvdb::Vec3s& v2) { return v1.eq(v2, float(TOLERANCE)); } /// Sample the given tree at various locations and assert if /// any of the sampled values don't match the expected values. template void TestQuadraticInterp::executeTest(const GridPtr& grid, const TestVal* testVals, size_t numVals) const { openvdb::tools::GridSampler interpolator(*grid); //openvdb::tools::QuadraticInterp interpolator(*tree); for (size_t i = 0; i < numVals; ++i) { const TestVal& val = testVals[i]; const ValueT actual = interpolator.sampleVoxel(val.x, val.y, val.z); if (!relEq(val.expected, actual)) { std::ostringstream ostr; ostr << std::setprecision(10) << "sampleVoxel(" << val.x << ", " << val.y << ", " << val.z << "): expected " << val.expected << ", got " << actual; CPPUNIT_FAIL(ostr.str()); } } } template void TestQuadraticInterp::test() { const ValueT one = constValue(1), two = constValue(2), three = constValue(3), four = constValue(4), fillValue = constValue(256); GridPtr grid(new GridType(fillValue)); typename GridType::TreeType& tree = grid->tree(); tree.setValue(openvdb::Coord(10, 10, 10), one); tree.setValue(openvdb::Coord(11, 10, 10), two); tree.setValue(openvdb::Coord(11, 11, 10), two); tree.setValue(openvdb::Coord(10, 11, 10), two); tree.setValue(openvdb::Coord( 9, 11, 10), two); tree.setValue(openvdb::Coord( 9, 10, 10), two); tree.setValue(openvdb::Coord( 9, 9, 10), two); tree.setValue(openvdb::Coord(10, 9, 10), two); tree.setValue(openvdb::Coord(11, 9, 10), two); tree.setValue(openvdb::Coord(10, 10, 11), three); tree.setValue(openvdb::Coord(11, 10, 11), three); tree.setValue(openvdb::Coord(11, 11, 11), three); tree.setValue(openvdb::Coord(10, 11, 11), three); tree.setValue(openvdb::Coord( 9, 11, 11), three); tree.setValue(openvdb::Coord( 9, 10, 11), three); tree.setValue(openvdb::Coord( 9, 9, 11), three); tree.setValue(openvdb::Coord(10, 9, 11), three); tree.setValue(openvdb::Coord(11, 9, 11), three); tree.setValue(openvdb::Coord(10, 10, 9), four); tree.setValue(openvdb::Coord(11, 10, 9), four); tree.setValue(openvdb::Coord(11, 11, 9), four); tree.setValue(openvdb::Coord(10, 11, 9), four); tree.setValue(openvdb::Coord( 9, 11, 9), four); tree.setValue(openvdb::Coord( 9, 10, 9), four); tree.setValue(openvdb::Coord( 9, 9, 9), four); tree.setValue(openvdb::Coord(10, 9, 9), four); tree.setValue(openvdb::Coord(11, 9, 9), four); const TestVal testVals[] = { { 10.5f, 10.5f, 10.5f, constValue(1.703125) }, { 10.0f, 10.0f, 10.0f, one }, { 11.0f, 10.0f, 10.0f, two }, { 11.0f, 11.0f, 10.0f, two }, { 11.0f, 11.0f, 11.0f, three }, { 9.0f, 11.0f, 9.0f, four }, { 9.0f, 10.0f, 9.0f, four }, { 10.1f, 10.0f, 10.0f, constValue(1.01) }, { 10.8f, 10.8f, 10.8f, constValue(2.513344) }, { 10.1f, 10.8f, 10.5f, constValue(1.8577) }, { 10.8f, 10.1f, 10.5f, constValue(1.8577) }, { 10.5f, 10.1f, 10.8f, constValue(2.2927) }, { 10.5f, 10.8f, 10.1f, constValue(1.6977) }, }; const size_t numVals = sizeof(testVals) / sizeof(TestVal); executeTest(grid, testVals, numVals); } template void TestQuadraticInterp::testConstantValues() { const ValueT two = constValue(2), fillValue = constValue(256); GridPtr grid(new GridType(fillValue)); typename GridType::TreeType& tree = grid->tree(); tree.setValue(openvdb::Coord(10, 10, 10), two); tree.setValue(openvdb::Coord(11, 10, 10), two); tree.setValue(openvdb::Coord(11, 11, 10), two); tree.setValue(openvdb::Coord(10, 11, 10), two); tree.setValue(openvdb::Coord( 9, 11, 10), two); tree.setValue(openvdb::Coord( 9, 10, 10), two); tree.setValue(openvdb::Coord( 9, 9, 10), two); tree.setValue(openvdb::Coord(10, 9, 10), two); tree.setValue(openvdb::Coord(11, 9, 10), two); tree.setValue(openvdb::Coord(10, 10, 11), two); tree.setValue(openvdb::Coord(11, 10, 11), two); tree.setValue(openvdb::Coord(11, 11, 11), two); tree.setValue(openvdb::Coord(10, 11, 11), two); tree.setValue(openvdb::Coord( 9, 11, 11), two); tree.setValue(openvdb::Coord( 9, 10, 11), two); tree.setValue(openvdb::Coord( 9, 9, 11), two); tree.setValue(openvdb::Coord(10, 9, 11), two); tree.setValue(openvdb::Coord(11, 9, 11), two); tree.setValue(openvdb::Coord(10, 10, 9), two); tree.setValue(openvdb::Coord(11, 10, 9), two); tree.setValue(openvdb::Coord(11, 11, 9), two); tree.setValue(openvdb::Coord(10, 11, 9), two); tree.setValue(openvdb::Coord( 9, 11, 9), two); tree.setValue(openvdb::Coord( 9, 10, 9), two); tree.setValue(openvdb::Coord( 9, 9, 9), two); tree.setValue(openvdb::Coord(10, 9, 9), two); tree.setValue(openvdb::Coord(11, 9, 9), two); const TestVal testVals[] = { { 10.5f, 10.5f, 10.5f, two }, { 10.0f, 10.0f, 10.0f, two }, { 10.1f, 10.0f, 10.0f, two }, { 10.8f, 10.8f, 10.8f, two }, { 10.1f, 10.8f, 10.5f, two }, { 10.8f, 10.1f, 10.5f, two }, { 10.5f, 10.1f, 10.8f, two }, { 10.5f, 10.8f, 10.1f, two } }; const size_t numVals = sizeof(testVals) / sizeof(TestVal); executeTest(grid, testVals, numVals); } template void TestQuadraticInterp::testFillValues() { const ValueT fillValue = constValue(256); GridPtr grid(new GridType(fillValue)); const TestVal testVals[] = { { 10.5f, 10.5f, 10.5f, fillValue }, { 10.0f, 10.0f, 10.0f, fillValue }, { 10.1f, 10.0f, 10.0f, fillValue }, { 10.8f, 10.8f, 10.8f, fillValue }, { 10.1f, 10.8f, 10.5f, fillValue }, { 10.8f, 10.1f, 10.5f, fillValue }, { 10.5f, 10.1f, 10.8f, fillValue }, { 10.5f, 10.8f, 10.1f, fillValue } }; const size_t numVals = sizeof(testVals) / sizeof(TestVal); executeTest(grid, testVals, numVals); } template void TestQuadraticInterp::testNegativeIndices() { const ValueT one = constValue(1), two = constValue(2), three = constValue(3), four = constValue(4), fillValue = constValue(256); GridPtr grid(new GridType(fillValue)); typename GridType::TreeType& tree = grid->tree(); tree.setValue(openvdb::Coord(-10, -10, -10), one); tree.setValue(openvdb::Coord(-11, -10, -10), two); tree.setValue(openvdb::Coord(-11, -11, -10), two); tree.setValue(openvdb::Coord(-10, -11, -10), two); tree.setValue(openvdb::Coord( -9, -11, -10), two); tree.setValue(openvdb::Coord( -9, -10, -10), two); tree.setValue(openvdb::Coord( -9, -9, -10), two); tree.setValue(openvdb::Coord(-10, -9, -10), two); tree.setValue(openvdb::Coord(-11, -9, -10), two); tree.setValue(openvdb::Coord(-10, -10, -11), three); tree.setValue(openvdb::Coord(-11, -10, -11), three); tree.setValue(openvdb::Coord(-11, -11, -11), three); tree.setValue(openvdb::Coord(-10, -11, -11), three); tree.setValue(openvdb::Coord( -9, -11, -11), three); tree.setValue(openvdb::Coord( -9, -10, -11), three); tree.setValue(openvdb::Coord( -9, -9, -11), three); tree.setValue(openvdb::Coord(-10, -9, -11), three); tree.setValue(openvdb::Coord(-11, -9, -11), three); tree.setValue(openvdb::Coord(-10, -10, -9), four); tree.setValue(openvdb::Coord(-11, -10, -9), four); tree.setValue(openvdb::Coord(-11, -11, -9), four); tree.setValue(openvdb::Coord(-10, -11, -9), four); tree.setValue(openvdb::Coord( -9, -11, -9), four); tree.setValue(openvdb::Coord( -9, -10, -9), four); tree.setValue(openvdb::Coord( -9, -9, -9), four); tree.setValue(openvdb::Coord(-10, -9, -9), four); tree.setValue(openvdb::Coord(-11, -9, -9), four); const TestVal testVals[] = { { -10.5f, -10.5f, -10.5f, constValue(-104.75586) }, { -10.0f, -10.0f, -10.0f, one }, { -11.0f, -10.0f, -10.0f, two }, { -11.0f, -11.0f, -10.0f, two }, { -11.0f, -11.0f, -11.0f, three }, { -9.0f, -11.0f, -9.0f, four }, { -9.0f, -10.0f, -9.0f, four }, { -10.1f, -10.0f, -10.0f, constValue(-10.28504) }, { -10.8f, -10.8f, -10.8f, constValue(-62.84878) }, { -10.1f, -10.8f, -10.5f, constValue(-65.68951) }, { -10.8f, -10.1f, -10.5f, constValue(-65.68951) }, { -10.5f, -10.1f, -10.8f, constValue(-65.40736) }, { -10.5f, -10.8f, -10.1f, constValue(-66.30510) }, }; const size_t numVals = sizeof(testVals) / sizeof(TestVal); executeTest(grid, testVals, numVals); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMat4Metadata.cc0000644000000000000000000001376412603226506016522 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestGridIO.cc0000644000000000000000000002156712603226506015371 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include // for remove() class TestGridIO: public CppUnit::TestCase { public: typedef openvdb::tree::Tree< openvdb::tree::RootNode< openvdb::tree::InternalNode< openvdb::tree::InternalNode< openvdb::tree::InternalNode< openvdb::tree::LeafNode, 3>, 4>, 5> > > Float5432Tree; typedef openvdb::Grid Float5432Grid; virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestGridIO); CPPUNIT_TEST(testReadAllBool); CPPUNIT_TEST(testReadAllFloat); CPPUNIT_TEST(testReadAllVec3S); CPPUNIT_TEST(testReadAllFloat5432); CPPUNIT_TEST_SUITE_END(); void testReadAllBool() { readAllTest(); } void testReadAllFloat() { readAllTest(); } void testReadAllVec3S() { readAllTest(); } void testReadAllFloat5432() { Float5432Grid::registerGrid(); readAllTest(); } private: template void readAllTest(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestGridIO); //////////////////////////////////////// template void TestGridIO::readAllTest() { using namespace openvdb; typedef typename GridType::TreeType TreeType; typedef typename TreeType::Ptr TreePtr; typedef typename TreeType::ValueType ValueT; typedef typename TreeType::NodeCIter NodeCIter; const ValueT zero = zeroVal(); // For each level of the tree, compute a bit mask for use in converting // global coordinates to node origins for nodes at that level. // That is, node_origin = global_coordinates & mask[node_level]. std::vector mask; TreeType::getNodeLog2Dims(mask); const size_t height = mask.size(); for (size_t i = 0; i < height; ++i) { Index dim = 0; for (size_t j = i; j < height; ++j) dim += mask[j]; mask[i] = ~((1 << dim) - 1); } const Index childDim = 1 + ~(mask[0]); // Choose sample coordinate pairs (coord0, coord1) and (coord0, coord2) // that are guaranteed to lie in different children of the root node // (because they are separated by more than the child node dimension). const Coord coord0(0, 0, 0), coord1(int(1.1 * childDim), 0, 0), coord2(0, int(1.1 * childDim), 0); // Create trees. TreePtr tree1(new TreeType(zero + 1)), tree2(new TreeType(zero + 2)); // Set some values. tree1->setValue(coord0, zero + 5); tree1->setValue(coord1, zero + 6); tree2->setValue(coord0, zero + 10); tree2->setValue(coord2, zero + 11); // Create grids with trees and assign transforms. math::Transform::Ptr trans1(math::Transform::createLinearTransform(0.1)), trans2(math::Transform::createLinearTransform(0.1)); GridBase::Ptr grid1 = createGrid(tree1), grid2 = createGrid(tree2); grid1->setTransform(trans1); grid1->setName("density"); grid2->setTransform(trans2); grid2->setName("temperature"); OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN CPPUNIT_ASSERT_EQUAL(ValueT(zero + 5), tree1->getValue(coord0)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 6), tree1->getValue(coord1)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 10), tree2->getValue(coord0)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 11), tree2->getValue(coord2)); OPENVDB_NO_FP_EQUALITY_WARNING_END // count[d] is the number of nodes already visited at depth d. // There should be exactly two nodes at each depth (apart from the root). std::vector count(height, 0); // Verify that tree1 has correct node origins. for (NodeCIter iter = tree1->cbeginNode(); iter; ++iter) { const Index depth = iter.getDepth(); const Coord expected[2] = { coord0 & mask[depth], // origin of the first node at this depth coord1 & mask[depth] // origin of the second node at this depth }; CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); ++count[depth]; } // Verify that tree2 has correct node origins. count.assign(height, 0); // reset node counts for (NodeCIter iter = tree2->cbeginNode(); iter; ++iter) { const Index depth = iter.getDepth(); const Coord expected[2] = { coord0 & mask[depth], coord2 & mask[depth] }; CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); ++count[depth]; } MetaMap::Ptr meta(new MetaMap); meta->insertMeta("author", StringMetadata("Einstein")); meta->insertMeta("year", Int32Metadata(2009)); GridPtrVecPtr grids(new GridPtrVec); grids->push_back(grid1); grids->push_back(grid2); // Write grids and metadata out to a file. { io::File vdbfile("something.vdb2"); vdbfile.write(*grids, *meta); } meta.reset(); grids.reset(); io::File vdbfile("something.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile.getGrids(), openvdb::IoError); // file has not been opened // Read the grids back in. vdbfile.open(); CPPUNIT_ASSERT(vdbfile.isOpen()); grids = vdbfile.getGrids(); meta = vdbfile.getMetadata(); // Ensure we have the metadata. CPPUNIT_ASSERT(meta.get() != NULL); CPPUNIT_ASSERT_EQUAL(2, int(meta->metaCount())); CPPUNIT_ASSERT_EQUAL(std::string("Einstein"), meta->metaValue("author")); CPPUNIT_ASSERT_EQUAL(2009, meta->metaValue("year")); // Ensure we got both grids. CPPUNIT_ASSERT(grids.get() != NULL); CPPUNIT_ASSERT_EQUAL(2, int(grids->size())); grid1.reset(); grid1 = findGridByName(*grids, "density"); CPPUNIT_ASSERT(grid1.get() != NULL); TreePtr density = gridPtrCast(grid1)->treePtr(); CPPUNIT_ASSERT(density.get() != NULL); grid2.reset(); grid2 = findGridByName(*grids, "temperature"); CPPUNIT_ASSERT(grid2.get() != NULL); TreePtr temperature = gridPtrCast(grid2)->treePtr(); CPPUNIT_ASSERT(temperature.get() != NULL); OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN CPPUNIT_ASSERT_EQUAL(ValueT(zero + 5), density->getValue(coord0)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 6), density->getValue(coord1)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 10), temperature->getValue(coord0)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 11), temperature->getValue(coord2)); OPENVDB_NO_FP_EQUALITY_WARNING_END // Check if we got the correct node origins. count.assign(height, 0); for (NodeCIter iter = density->cbeginNode(); iter; ++iter) { const Index depth = iter.getDepth(); const Coord expected[2] = { coord0 & mask[depth], coord1 & mask[depth] }; CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); ++count[depth]; } count.assign(height, 0); for (NodeCIter iter = temperature->cbeginNode(); iter; ++iter) { const Index depth = iter.getDepth(); const Coord expected[2] = { coord0 & mask[depth], coord2 & mask[depth] }; CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); ++count[depth]; } vdbfile.close(); ::remove("something.vdb2"); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestQuantizedUnitVec.cc0000644000000000000000000001332412603226506017506 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include class TestQuantizedUnitVec: public CppUnit::TestFixture { public: CPPUNIT_TEST_SUITE(TestQuantizedUnitVec); CPPUNIT_TEST(testQuantization); CPPUNIT_TEST_SUITE_END(); void testQuantization(); private: // Generate a random number in the range [0, 1]. double randNumber() { return double(rand()) / (double(RAND_MAX) + 1.0); } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestQuantizedUnitVec); //////////////////////////////////////// namespace { const uint16_t MASK_XSIGN = 0x8000, // 1000000000000000 MASK_YSIGN = 0x4000, // 0100000000000000 MASK_ZSIGN = 0x2000; // 0010000000000000 } //////////////////////////////////////// void TestQuantizedUnitVec::testQuantization() { using namespace openvdb; using namespace openvdb::math; // // Check sign bits // Vec3s unitVec = Vec3s(-1.0, -1.0, -1.0); unitVec.normalize(); uint16_t quantizedVec = QuantizedUnitVec::pack(unitVec); CPPUNIT_ASSERT((quantizedVec & MASK_XSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_ZSIGN)); unitVec[0] = -unitVec[0]; unitVec[2] = -unitVec[2]; quantizedVec = QuantizedUnitVec::pack(unitVec); CPPUNIT_ASSERT(!(quantizedVec & MASK_XSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); CPPUNIT_ASSERT(!(quantizedVec & MASK_ZSIGN)); unitVec[1] = -unitVec[1]; quantizedVec = QuantizedUnitVec::pack(unitVec); CPPUNIT_ASSERT(!(quantizedVec & MASK_XSIGN)); CPPUNIT_ASSERT(!(quantizedVec & MASK_YSIGN)); CPPUNIT_ASSERT(!(quantizedVec & MASK_ZSIGN)); QuantizedUnitVec::flipSignBits(quantizedVec); CPPUNIT_ASSERT((quantizedVec & MASK_XSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_ZSIGN)); unitVec[2] = -unitVec[2]; quantizedVec = QuantizedUnitVec::pack(unitVec); QuantizedUnitVec::flipSignBits(quantizedVec); CPPUNIT_ASSERT((quantizedVec & MASK_XSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); CPPUNIT_ASSERT(!(quantizedVec & MASK_ZSIGN)); // // Check conversion error // const double tol = 0.05; // component error tolerance const int numNormals = 40000; // init srand(0); const int n = int(std::sqrt(double(numNormals))); const double xScale = (2.0 * M_PI) / double(n); const double yScale = M_PI / double(n); double x, y, theta, phi; Vec3s n0, n1; // generate random normals, by uniformly distributing points on a unit-sphere. // loop over a [0 to n) x [0 to n) grid. for (int a = 0; a < n; ++a) { for (int b = 0; b < n; ++b) { // jitter, move to random pos. inside the current cell x = double(a) + randNumber(); y = double(b) + randNumber(); // remap to a lat/long map theta = y * yScale; // [0 to PI] phi = x * xScale; // [0 to 2PI] // convert to cartesian coordinates on a unit sphere. // spherical coordinate triplet (r=1, theta, phi) n0[0] = float(std::sin(theta)*std::cos(phi)); n0[1] = float(std::sin(theta)*std::sin(phi)); n0[2] = float(std::cos(theta)); CPPUNIT_ASSERT_DOUBLES_EQUAL(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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestDenseSparseTools.cc0000644000000000000000000003102312603226506017475 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestGridBbox.cc0000644000000000000000000001016312603226506015742 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestStats.cc0000644000000000000000000007477112603226506015357 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include // for ISGradient #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestStats: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestStats); CPPUNIT_TEST(testExtrema); CPPUNIT_TEST(testStats); CPPUNIT_TEST(testHistogram); CPPUNIT_TEST(testGridExtrema); CPPUNIT_TEST(testGridStats); CPPUNIT_TEST(testGridHistogram); CPPUNIT_TEST(testGridOperatorStats); CPPUNIT_TEST_SUITE_END(); void testExtrema(); void testStats(); void testHistogram(); void testGridExtrema(); void testGridStats(); void testGridHistogram(); void testGridOperatorStats(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestStats); void TestStats::testExtrema() { {// trivial test openvdb::math::Extrema s; s.add(0); s.add(1); CPPUNIT_ASSERT_EQUAL(2, int(s.size())); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.min(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, s.max(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, s.range(), 0.000001); //s.print("test"); } {// non-trivial test openvdb::math::Extrema s; const int data[5]={600, 470, 170, 430, 300}; for (int i=0; i<5; ++i) s.add(data[i]); CPPUNIT_ASSERT_EQUAL(5, int(s.size())); CPPUNIT_ASSERT_DOUBLES_EQUAL(data[2], s.min(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(data[0], s.max(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(data[0]-data[2], s.range(), 0.000001); //s.print("test"); } {// non-trivial test of Extrema::add(Extrema) openvdb::math::Extrema s, t; const int data[5]={600, 470, 170, 430, 300}; for (int i=0; i<3; ++i) s.add(data[i]); for (int i=3; i<5; ++i) t.add(data[i]); s.add(t); CPPUNIT_ASSERT_EQUAL(5, int(s.size())); CPPUNIT_ASSERT_DOUBLES_EQUAL(data[2], s.min(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(data[0], s.max(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(data[0]-data[2], s.range(), 0.000001); //s.print("test"); } {// Trivial test of Extrema::add(value, n) openvdb::math::Extrema s; const double val = 3.45; const uint64_t n = 57; s.add(val, 57); CPPUNIT_ASSERT_EQUAL(n, s.size()); CPPUNIT_ASSERT_DOUBLES_EQUAL(val, s.min(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(val, s.max(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.range(), 0.000001); } {// Test 1 of Extrema::add(value), Extrema::add(value, n) and Extrema::add(Extrema) openvdb::math::Extrema s, t; const double val1 = 1.0, val2 = 3.0; const uint64_t n1 = 1, n2 =1; s.add(val1, n1); CPPUNIT_ASSERT_EQUAL(uint64_t(n1), s.size()); CPPUNIT_ASSERT_DOUBLES_EQUAL(val1, s.min(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(val1, s.max(), 0.000001); for (uint64_t i=0; i= h.min(j) && data[i] < h.max(j)) bin[j]++; } for (int i=0; i<5; ++i) CPPUNIT_ASSERT_EQUAL(bin[i],int(h.count(i))); //h.print("test"); } {//Test print of Histogram openvdb::math::Stats s; const int N=500000; for (int i=0; i void operator()(const GridT::ValueOnCIter& it, StatsT& stats) const { typedef openvdb::math::ISGradient GradT; if (it.isVoxelValue()) { stats.add(GradT::result(acc, it.getCoord()).length()); } else { openvdb::CoordBBox bbox = it.getBoundingBox(); openvdb::Coord xyz; int &x = xyz[0], &y = xyz[1], &z = xyz[2]; for (x = bbox.min()[0]; x <= bbox.max()[0]; ++x) { for (y = bbox.min()[1]; y <= bbox.max()[1]; ++y) { for (z = bbox.min()[2]; z <= bbox.max()[2]; ++z) { stats.add(GradT::result(acc, xyz).length()); } } } } } }; } // unnamed namespace void TestStats::testGridExtrema() { using namespace openvdb; const int DIM = 109; { const float background = 0.0; FloatGrid grid(background); { // Compute active value statistics for a grid with a single active voxel. grid.tree().setValue(Coord(0), /*value=*/42.0); math::Extrema ex = tools::extrema(grid.cbeginValueOn()); CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, ex.max(), /*tolerance=*/0.0); // Compute inactive value statistics for a grid with only background voxels. grid.tree().setValueOff(Coord(0), background); ex = tools::extrema(grid.cbeginValueOff()); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, ex.max(), /*tolerance=*/0.0); } // Compute active value statistics for a grid with two active voxel populations // of the same size but two different values. grid.fill(CoordBBox::createCube(Coord(0), DIM), /*value=*/1.0); grid.fill(CoordBBox::createCube(Coord(-300), DIM), /*value=*/-3.0); CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), grid.activeVoxelCount()); for (int threaded = 0; threaded <= 1; ++threaded) { math::Extrema ex = tools::extrema(grid.cbeginValueOn(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(-3.0), ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), ex.max(), /*tolerance=*/0.0); } // Compute active value statistics for just the positive values. for (int threaded = 0; threaded <= 1; ++threaded) { struct Local { static void addIfPositive(const FloatGrid::ValueOnCIter& it, math::Extrema& ex) { const float f = *it; if (f > 0.0) { if (it.isVoxelValue()) ex.add(f); else ex.add(f, it.getVoxelCount()); } } }; math::Extrema ex = tools::extrema(grid.cbeginValueOn(), &Local::addIfPositive, threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), ex.max(), /*tolerance=*/0.0); } // Compute active value statistics for the first-order gradient. for (int threaded = 0; threaded <= 1; ++threaded) { // First, using a custom ValueOp... math::Extrema ex = tools::extrema(grid.cbeginValueOn(), GradOp(grid), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL( double(9.0 + 9.0 + 9.0), ex.max() * ex.max(), /*tol=*/1.0e-3); // max gradient is (dx, dy, dz) = (-3 - 0, -3 - 0, -3 - 0) // ...then using tools::opStatistics(). typedef math::ISOpMagnitude > MathOp; ex = tools::opExtrema(grid.cbeginValueOn(), MathOp(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL( double(9.0 + 9.0 + 9.0), ex.max() * ex.max(), /*tolerance=*/1.0e-3); // max gradient is (dx, dy, dz) = (-3 - 0, -3 - 0, -3 - 0) } } { const Vec3s background(0.0); Vec3SGrid grid(background); // Compute active vector magnitude statistics for a vector-valued grid // with two active voxel populations of the same size but two different values. grid.fill(CoordBBox::createCube(Coord(0), DIM), Vec3s(3.0, 0.0, 4.0)); // length = 5 grid.fill(CoordBBox::createCube(Coord(-300), DIM), Vec3s(1.0, 2.0, 2.0)); // length = 3 CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), grid.activeVoxelCount()); for (int threaded = 0; threaded <= 1; ++threaded) { math::Extrema ex = tools::extrema(grid.cbeginValueOn(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(3.0), ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(5.0), ex.max(), /*tolerance=*/0.0); } } } void TestStats::testGridStats() { using namespace openvdb; const int DIM = 109; { const float background = 0.0; FloatGrid grid(background); { // Compute active value statistics for a grid with a single active voxel. grid.tree().setValue(Coord(0), /*value=*/42.0); math::Stats stats = tools::statistics(grid.cbeginValueOn()); CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, stats.max(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, stats.mean(), /*tolerance=*/1.0e-8); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, stats.variance(), /*tolerance=*/1.0e-8); // Compute inactive value statistics for a grid with only background voxels. grid.tree().setValueOff(Coord(0), background); stats = tools::statistics(grid.cbeginValueOff()); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, stats.max(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, stats.mean(), /*tolerance=*/1.0e-8); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, stats.variance(), /*tolerance=*/1.0e-8); } // Compute active value statistics for a grid with two active voxel populations // of the same size but two different values. grid.fill(CoordBBox::createCube(Coord(0), DIM), /*value=*/1.0); grid.fill(CoordBBox::createCube(Coord(-300), DIM), /*value=*/-3.0); CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), grid.activeVoxelCount()); for (int threaded = 0; threaded <= 1; ++threaded) { math::Stats stats = tools::statistics(grid.cbeginValueOn(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(-3.0), stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.max(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(-1.0), stats.mean(), /*tolerance=*/1.0e-8); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(4.0), stats.variance(), /*tolerance=*/1.0e-8); } // Compute active value statistics for just the positive values. for (int threaded = 0; threaded <= 1; ++threaded) { struct Local { static void addIfPositive(const FloatGrid::ValueOnCIter& it, math::Stats& stats) { const float f = *it; if (f > 0.0) { if (it.isVoxelValue()) stats.add(f); else stats.add(f, it.getVoxelCount()); } } }; math::Stats stats = tools::statistics(grid.cbeginValueOn(), &Local::addIfPositive, threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.max(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.mean(), /*tolerance=*/1.0e-8); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), stats.variance(), /*tolerance=*/1.0e-8); } // Compute active value statistics for the first-order gradient. for (int threaded = 0; threaded <= 1; ++threaded) { // First, using a custom ValueOp... math::Stats stats = tools::statistics(grid.cbeginValueOn(), GradOp(grid), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL( double(9.0 + 9.0 + 9.0), stats.max() * stats.max(), /*tol=*/1.0e-3); // max gradient is (dx, dy, dz) = (-3 - 0, -3 - 0, -3 - 0) // ...then using tools::opStatistics(). typedef math::ISOpMagnitude > MathOp; stats = tools::opStatistics(grid.cbeginValueOn(), MathOp(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL( double(9.0 + 9.0 + 9.0), stats.max() * stats.max(), /*tolerance=*/1.0e-3); // max gradient is (dx, dy, dz) = (-3 - 0, -3 - 0, -3 - 0) } } { const Vec3s background(0.0); Vec3SGrid grid(background); // Compute active vector magnitude statistics for a vector-valued grid // with two active voxel populations of the same size but two different values. grid.fill(CoordBBox::createCube(Coord(0), DIM), Vec3s(3.0, 0.0, 4.0)); // length = 5 grid.fill(CoordBBox::createCube(Coord(-300), DIM), Vec3s(1.0, 2.0, 2.0)); // length = 3 CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), grid.activeVoxelCount()); for (int threaded = 0; threaded <= 1; ++threaded) { math::Stats stats = tools::statistics(grid.cbeginValueOn(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(3.0), stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(5.0), stats.max(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(4.0), stats.mean(), /*tolerance=*/1.0e-8); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.variance(), /*tolerance=*/1.0e-8); } } } namespace { template inline void doTestGridOperatorStats(const GridT& grid, const OpT& op) { openvdb::math::Stats serialStats = openvdb::tools::opStatistics(grid.cbeginValueOn(), op, /*threaded=*/false); openvdb::math::Stats parallelStats = openvdb::tools::opStatistics(grid.cbeginValueOn(), op, /*threaded=*/true); // Verify that the results from threaded and serial runs are equivalent. CPPUNIT_ASSERT_EQUAL(serialStats.size(), parallelStats.size()); ASSERT_DOUBLES_EXACTLY_EQUAL(serialStats.min(), parallelStats.min()); ASSERT_DOUBLES_EXACTLY_EQUAL(serialStats.max(), parallelStats.max()); CPPUNIT_ASSERT_DOUBLES_EQUAL(serialStats.mean(), parallelStats.mean(), /*tolerance=*/1.0e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(serialStats.variance(), parallelStats.variance(), 1.0e-6); } } void TestStats::testGridOperatorStats() { using namespace openvdb; typedef math::UniformScaleMap MapType; MapType map; const int DIM = 109; { // Test operations on a scalar grid. const float background = 0.0; FloatGrid grid(background); grid.fill(CoordBBox::createCube(Coord(0), DIM), /*value=*/1.0); grid.fill(CoordBBox::createCube(Coord(-300), DIM), /*value=*/-3.0); { // Magnitude of gradient computed via first-order differencing typedef math::MapAdapter, MapType>, double> OpT; doTestGridOperatorStats(grid, OpT(map)); } { // Magnitude of index-space gradient computed via first-order differencing typedef math::ISOpMagnitude > OpT; doTestGridOperatorStats(grid, OpT()); } { // Laplacian of index-space gradient computed via second-order central differencing typedef math::ISLaplacian OpT; doTestGridOperatorStats(grid, OpT()); } } { // Test operations on a vector grid. const Vec3s background(0.0); Vec3SGrid grid(background); grid.fill(CoordBBox::createCube(Coord(0), DIM), Vec3s(3.0, 0.0, 4.0)); // length = 5 grid.fill(CoordBBox::createCube(Coord(-300), DIM), Vec3s(1.0, 2.0, 2.0)); // length = 3 { // Divergence computed via first-order differencing typedef math::MapAdapter, double> OpT; doTestGridOperatorStats(grid, OpT(map)); } { // Magnitude of curl computed via first-order differencing typedef math::MapAdapter, MapType>, double> OpT; doTestGridOperatorStats(grid, OpT(map)); } { // Magnitude of index-space curl computed via first-order differencing typedef math::ISOpMagnitude > OpT; doTestGridOperatorStats(grid, OpT()); } } } void TestStats::testGridHistogram() { using namespace openvdb; const int DIM = 109; { const float background = 0.0; FloatGrid grid(background); { const double value = 42.0; // Compute a histogram of the active values of a grid with a single active voxel. grid.tree().setValue(Coord(0), value); math::Histogram hist = tools::histogram(grid.cbeginValueOn(), /*min=*/0.0, /*max=*/100.0); for (int i = 0, N = int(hist.numBins()); i < N; ++i) { uint64_t expected = ((hist.min(i) <= value && value <= hist.max(i)) ? 1 : 0); CPPUNIT_ASSERT_EQUAL(expected, hist.count(i)); } } // Compute a histogram of the active values of a grid with two // active voxel populations of the same size but two different values. grid.fill(CoordBBox::createCube(Coord(0), DIM), /*value=*/1.0); grid.fill(CoordBBox::createCube(Coord(-300), DIM), /*value=*/3.0); CPPUNIT_ASSERT_EQUAL(uint64_t(2 * DIM * DIM * DIM), grid.activeVoxelCount()); for (int threaded = 0; threaded <= 1; ++threaded) { math::Histogram hist = tools::histogram(grid.cbeginValueOn(), /*min=*/0.0, /*max=*/10.0, /*numBins=*/9, threaded); CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), hist.size()); for (int i = 0, N = int(hist.numBins()); i < N; ++i) { if (i == 0 || i == 2) { CPPUNIT_ASSERT_EQUAL(uint64_t(DIM * DIM * DIM), hist.count(i)); } else { CPPUNIT_ASSERT_EQUAL(uint64_t(0), hist.count(i)); } } } } { const Vec3s background(0.0); Vec3SGrid grid(background); // Compute a histogram of vector magnitudes of the active values of a // vector-valued grid with two active voxel populations of the same size // but two different values. grid.fill(CoordBBox::createCube(Coord(0), DIM), Vec3s(3.0, 0.0, 4.0)); // length = 5 grid.fill(CoordBBox::createCube(Coord(-300), DIM), Vec3s(1.0, 2.0, 2.0)); // length = 3 CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), grid.activeVoxelCount()); for (int threaded = 0; threaded <= 1; ++threaded) { math::Histogram hist = tools::histogram(grid.cbeginValueOn(), /*min=*/0.0, /*max=*/10.0, /*numBins=*/9, threaded); CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), hist.size()); for (int i = 0, N = int(hist.numBins()); i < N; ++i) { if (i == 2 || i == 4) { CPPUNIT_ASSERT_EQUAL(uint64_t(DIM * DIM * DIM), hist.count(i)); } else { CPPUNIT_ASSERT_EQUAL(uint64_t(0), hist.count(i)); } } } } } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestExceptions.cc0000644000000000000000000001142212603226506016362 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestStringMetadata.cc0000644000000000000000000000547312603226506017161 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestVec2Metadata.cc0000644000000000000000000001061712603226506016506 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTreeIterators.cc0000644000000000000000000005462012603226506017044 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include // for tools::createLevelSetSphere() #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0) class TestTreeIterators: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestTreeIterators); CPPUNIT_TEST(testLeafIterator); CPPUNIT_TEST(testEmptyLeafIterator); CPPUNIT_TEST(testOnlyNegative); CPPUNIT_TEST(testValueAllIterator); CPPUNIT_TEST(testValueOnIterator); CPPUNIT_TEST(testValueOffIterator); CPPUNIT_TEST(testModifyValue); CPPUNIT_TEST(testDepthBounds); CPPUNIT_TEST_SUITE_END(); void testLeafIterator(); void testEmptyLeafIterator(); void testOnlyNegative(); void testValueAllIterator(); void testValueOnIterator(); void testValueOffIterator(); void testModifyValue(); void testDepthBounds(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTreeIterators); typedef openvdb::FloatTree TreeType; void TestTreeIterators::testLeafIterator() { const float fillValue = 256.0f; TreeType tree(fillValue); tree.setValue(openvdb::Coord(0, 0, 0), 1.0); tree.setValue(openvdb::Coord(1, 0, 0), 1.5); tree.setValue(openvdb::Coord(0, 0, 8), 2.0); tree.setValue(openvdb::Coord(1, 0, 8), 2.5); tree.setValue(openvdb::Coord(0, 0, 16), 3.0); tree.setValue(openvdb::Coord(1, 0, 16), 3.5); tree.setValue(openvdb::Coord(0, 0, 24), 4.0); tree.setValue(openvdb::Coord(1, 0, 24), 4.5); float val = 1.f; for (TreeType::LeafCIter iter = tree.cbeginLeaf(); iter; ++iter) { const TreeType::LeafNodeType* leaf = iter.getLeaf(); CPPUNIT_ASSERT(leaf != NULL); ASSERT_DOUBLES_EXACTLY_EQUAL(val, leaf->getValue(openvdb::Coord(0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(val + 0.5, iter->getValue(openvdb::Coord(1, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue, iter->getValue(openvdb::Coord(1, 1, 1))); val = val + 1.f; } } // Test the leaf iterator over a tree without any leaf nodes. void TestTreeIterators::testEmptyLeafIterator() { using namespace openvdb; TreeType tree(/*fillValue=*/256.0); std::vector dims; tree.getNodeLog2Dims(dims); CPPUNIT_ASSERT_EQUAL(4, int(dims.size())); // Start with an iterator over an empty tree. TreeType::LeafCIter iter = tree.cbeginLeaf(); CPPUNIT_ASSERT(!iter); // Using sparse fill, add internal nodes but no leaf nodes to the tree. // Fill the region subsumed by a level-2 internal node (assuming a four-level tree). Index log2Sum = dims[1] + dims[2] + dims[3]; CoordBBox bbox(Coord(0), Coord((1 << log2Sum) - 1)); tree.fill(bbox, /*value=*/1.0); iter = tree.cbeginLeaf(); CPPUNIT_ASSERT(!iter); // Fill the region subsumed by a level-1 internal node. log2Sum = dims[2] + dims[3]; bbox.reset(Coord(0), Coord((1 << log2Sum) - 1)); tree.fill(bbox, /*value=*/2.0); iter = tree.cbeginLeaf(); CPPUNIT_ASSERT(!iter); } void TestTreeIterators::testOnlyNegative() { using openvdb::Index64; const float fillValue = 5.0f; TreeType tree(fillValue); CPPUNIT_ASSERT(tree.empty()); ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue, tree.getValue(openvdb::Coord(5, -10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue, tree.getValue(openvdb::Coord(-500, 200, 300))); tree.setValue(openvdb::Coord(-5, 10, 20), 0.1f); tree.setValue(openvdb::Coord( 5, -10, 20), 0.2f); tree.setValue(openvdb::Coord( 5, 10, -20), 0.3f); tree.setValue(openvdb::Coord(-5, -10, 20), 0.4f); tree.setValue(openvdb::Coord(-5, 10, -20), 0.5f); tree.setValue(openvdb::Coord( 5, -10, -20), 0.6f); tree.setValue(openvdb::Coord(-5, -10, -20), 0.7f); tree.setValue(openvdb::Coord(-500, 200, -300), 4.5678f); tree.setValue(openvdb::Coord( 500, -200, -300), 4.5678f); tree.setValue(openvdb::Coord(-500, -200, 300), 4.5678f); ASSERT_DOUBLES_EXACTLY_EQUAL(0.1f, tree.getValue(openvdb::Coord(-5, 10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.2f, tree.getValue(openvdb::Coord( 5, -10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.3f, tree.getValue(openvdb::Coord( 5, 10, -20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.4f, tree.getValue(openvdb::Coord(-5, -10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.5f, tree.getValue(openvdb::Coord(-5, 10, -20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.6f, tree.getValue(openvdb::Coord( 5, -10, -20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.7f, tree.getValue(openvdb::Coord(-5, -10, -20))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord(-500, 200, -300))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord( 500, -200, -300))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord(-500, -200, 300))); int count = 0; for (int i = -25; i < 25; ++i) { for (int j = -25; j < 25; ++j) { for (int k = -25; k < 25; ++k) { if (tree.getValue(openvdb::Coord(i, j, k)) < 1.0f) { //fprintf(stderr, "(%i, %i, %i) = %f\n", // i, j, k, tree.getValue(openvdb::Coord(i, j, k))); ++count; } } } } CPPUNIT_ASSERT_EQUAL(7, count); openvdb::Coord xyz; int count2 = 0; for (TreeType::ValueOnCIter iter = tree.cbeginValueOn();iter; ++iter) { ++count2; xyz = iter.getCoord(); //std::cerr << xyz << " = " << *iter << "\n"; } CPPUNIT_ASSERT_EQUAL(10, count2); CPPUNIT_ASSERT_EQUAL(Index64(10), tree.activeVoxelCount()); } void TestTreeIterators::testValueAllIterator() { const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; typedef openvdb::tree::Tree4::Type Tree323f; typedef Tree323f::RootNodeType RootT; typedef RootT::ChildNodeType Int1T; typedef Int1T::ChildNodeType Int2T; typedef Int2T::ChildNodeType LeafT; Tree323f tree(/*fillValue=*/256.0f); tree.setValue(openvdb::Coord(4), 0.0f); tree.setValue(openvdb::Coord(-4), -1.0f); const size_t expectedNumOff = 2 * ((1 << (3 * DIM2)) - 1) // 2 8x8x8 InternalNodes - 1 child pointer each + 2 * ((1 << (3 * DIM1)) - 1) // 2 4x4x4 InternalNodes - 1 child pointer each + 2 * ((1 << (3 * DIM0)) - 1); // 2 8x8x8 LeafNodes - 1 active value each { Tree323f::ValueAllIter iter = tree.beginValueAll(); CPPUNIT_ASSERT(iter.test()); // Read all tile and voxel values through a non-const value iterator. size_t numOn = 0, numOff = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(iter.getLevel() <= 3); const openvdb::Index iterLevel = iter.getLevel(); for (openvdb::Index lvl = 0; lvl <= 3; ++lvl) { RootT* root; Int1T* int1; Int2T* int2; LeafT* leaf; iter.getNode(root); CPPUNIT_ASSERT(root != NULL); iter.getNode(int1); CPPUNIT_ASSERT(iterLevel < 3 ? int1 != NULL: int1 == NULL); iter.getNode(int2); CPPUNIT_ASSERT(iterLevel < 2 ? int2 != NULL: int2 == NULL); iter.getNode(leaf); CPPUNIT_ASSERT(iterLevel < 1 ? leaf != NULL: leaf == NULL); } if (iter.isValueOn()) { ++numOn; const float f = iter.getValue(); if (openvdb::math::isZero(f)) { CPPUNIT_ASSERT(iter.getCoord() == openvdb::Coord(4)); CPPUNIT_ASSERT(iter.isVoxelValue()); } else { ASSERT_DOUBLES_EXACTLY_EQUAL(-1.0f, f); CPPUNIT_ASSERT(iter.getCoord() == openvdb::Coord(-4)); CPPUNIT_ASSERT(iter.isVoxelValue()); } } else { ++numOff; // For every tenth inactive value, check that the size of // the tile or voxel is as expected. if (numOff % 10 == 0) { const int dim[4] = { 1, 1 << DIM0, 1 << (DIM1 + DIM0), 1 << (DIM2 + DIM1 + DIM0) }; const int lvl = iter.getLevel(); CPPUNIT_ASSERT(lvl < 4); openvdb::CoordBBox bbox; iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL( bbox.extents(), openvdb::Coord(dim[lvl], dim[lvl], dim[lvl])); } } } CPPUNIT_ASSERT_EQUAL(2, int(numOn)); CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } { Tree323f::ValueAllCIter iter = tree.cbeginValueAll(); CPPUNIT_ASSERT(iter.test()); // Read all tile and voxel values through a const value iterator. size_t numOn = 0, numOff = 0; for ( ; iter.test(); iter.next()) { if (iter.isValueOn()) ++numOn; else ++numOff; } CPPUNIT_ASSERT_EQUAL(2, int(numOn)); CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } { Tree323f::ValueAllIter iter = tree.beginValueAll(); CPPUNIT_ASSERT(iter.test()); // Read all tile and voxel values through a non-const value iterator // and overwrite all active values. size_t numOn = 0, numOff = 0; for ( ; iter; ++iter) { if (iter.isValueOn()) { iter.setValue(iter.getValue() - 5); ++numOn; } else { ++numOff; } } CPPUNIT_ASSERT_EQUAL(2, int(numOn)); CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } } void TestTreeIterators::testValueOnIterator() { typedef openvdb::tree::Tree4::Type Tree323f; Tree323f tree(/*fillValue=*/256.0f); { Tree323f::ValueOnIter iter = tree.beginValueOn(); CPPUNIT_ASSERT(!iter.test()); // empty tree } const int STEP = 8/*100*/, NUM_STEPS = 10; for (int i = 0; i < NUM_STEPS; ++i) { tree.setValue(openvdb::Coord(STEP * i), 0.0f); } { Tree323f::ValueOnIter iter = tree.beginValueOn(); CPPUNIT_ASSERT(iter.test()); // Read all active tile and voxel values through a non-const value iterator. int numOn = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(iter.isVoxelValue()); CPPUNIT_ASSERT(iter.isValueOn()); ASSERT_DOUBLES_EXACTLY_EQUAL(0.0f, iter.getValue()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(STEP * numOn), iter.getCoord()); ++numOn; } CPPUNIT_ASSERT_EQUAL(NUM_STEPS, numOn); } { Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); CPPUNIT_ASSERT(iter.test()); // Read all active tile and voxel values through a const value iterator. int numOn = 0; for ( ; iter.test(); iter.next()) { CPPUNIT_ASSERT(iter.isVoxelValue()); CPPUNIT_ASSERT(iter.isValueOn()); ASSERT_DOUBLES_EXACTLY_EQUAL(0.0f, iter.getValue()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(STEP * numOn), iter.getCoord()); ++numOn; } CPPUNIT_ASSERT_EQUAL(NUM_STEPS, numOn); } { Tree323f::ValueOnIter iter = tree.beginValueOn(); CPPUNIT_ASSERT(iter.test()); // Read all active tile and voxel values through a non-const value iterator // and overwrite the values. int numOn = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(iter.isVoxelValue()); CPPUNIT_ASSERT(iter.isValueOn()); ASSERT_DOUBLES_EXACTLY_EQUAL(0.0f, iter.getValue()); iter.setValue(5.0f); ASSERT_DOUBLES_EXACTLY_EQUAL(5.0f, iter.getValue()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(STEP * numOn), iter.getCoord()); ++numOn; } CPPUNIT_ASSERT_EQUAL(NUM_STEPS, numOn); } } void TestTreeIterators::testValueOffIterator() { const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; typedef openvdb::tree::Tree4::Type Tree323f; Tree323f tree(/*fillValue=*/256.0f); tree.setValue(openvdb::Coord(4), 0.0f); tree.setValue(openvdb::Coord(-4), -1.0f); const size_t expectedNumOff = 2 * ((1 << (3 * DIM2)) - 1) // 2 8x8x8 InternalNodes - 1 child pointer each + 2 * ((1 << (3 * DIM1)) - 1) // 2 4x4x4 InternalNodes - 1 child pointer each + 2 * ((1 << (3 * DIM0)) - 1); // 2 8x8x8 LeafNodes - 1 active value each { Tree323f::ValueOffIter iter = tree.beginValueOff(); CPPUNIT_ASSERT(iter.test()); // Read all inactive tile and voxel values through a non-const value iterator. size_t numOff = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(!iter.isValueOn()); ++numOff; // For every tenth inactive value, check that the size of // the tile or voxel is as expected. if (numOff % 10 == 0) { const int dim[4] = { 1, 1 << DIM0, 1 << (DIM1 + DIM0), 1 << (DIM2 + DIM1 + DIM0) }; const int lvl = iter.getLevel(); CPPUNIT_ASSERT(lvl < 4); openvdb::CoordBBox bbox; iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(bbox.extents(), openvdb::Coord(dim[lvl], dim[lvl], dim[lvl])); } } CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } { Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); CPPUNIT_ASSERT(iter.test()); // Read all inactive tile and voxel values through a const value iterator. size_t numOff = 0; for ( ; iter.test(); iter.next(), ++numOff) { CPPUNIT_ASSERT(!iter.isValueOn()); } CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } { Tree323f::ValueOffIter iter = tree.beginValueOff(); CPPUNIT_ASSERT(iter.test()); // Read all inactive tile and voxel values through a non-const value iterator // and overwrite the values. size_t numOff = 0; for ( ; iter; ++iter, ++numOff) { iter.setValue(iter.getValue() - 5); iter.setValueOff(); } for (iter = tree.beginValueOff(); iter; ++iter, --numOff); CPPUNIT_ASSERT_EQUAL(size_t(0), numOff); } } void TestTreeIterators::testModifyValue() { using openvdb::Coord; const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; { typedef openvdb::tree::Tree4::Type IntTree323f; IntTree323f tree(/*background=*/256); tree.addTile(/*level=*/3, Coord(-1), /*value=*/ 4, /*active=*/true); tree.addTile(/*level=*/2, Coord(1 << (DIM0 + DIM1)), /*value=*/-3, /*active=*/true); tree.addTile(/*level=*/1, Coord(1 << DIM0), /*value=*/ 2, /*active=*/true); tree.addTile(/*level=*/0, Coord(0), /*value=*/-1, /*active=*/true); struct Local { static inline void negate(int32_t& n) { n = -n; } }; for (IntTree323f::ValueAllIter iter = tree.beginValueAll(); iter; ++iter) { iter.modifyValue(Local::negate); } for (IntTree323f::ValueAllCIter iter = tree.cbeginValueAll(); iter; ++iter) { const int32_t val = *iter; if (val < 0) CPPUNIT_ASSERT((-val) % 2 == 0); // negative values are even else CPPUNIT_ASSERT(val % 2 == 1); // positive values are odd } // Because modifying values through a const iterator is not allowed, // uncommenting the following line should result in a static assertion failure: //tree.cbeginValueOn().modifyValue(Local::negate); } { typedef openvdb::tree::Tree4::Type BoolTree323f; BoolTree323f tree; tree.addTile(/*level=*/3, Coord(-1), /*value=*/false, /*active=*/true); tree.addTile(/*level=*/2, Coord(1 << (DIM0 + DIM1)), /*value=*/ true, /*active=*/true); tree.addTile(/*level=*/1, Coord(1 << DIM0), /*value=*/false, /*active=*/true); tree.addTile(/*level=*/0, Coord(0), /*value=*/ true, /*active=*/true); struct Local { static inline void negate(bool& b) { b = !b; } }; for (BoolTree323f::ValueAllIter iter = tree.beginValueAll(); iter; ++iter) { iter.modifyValue(Local::negate); } CPPUNIT_ASSERT(!tree.getValue(Coord(0))); CPPUNIT_ASSERT( tree.getValue(Coord(1 << DIM0))); CPPUNIT_ASSERT(!tree.getValue(Coord(1 << (DIM0 + DIM1)))); CPPUNIT_ASSERT( tree.getValue(Coord(-1))); // Because modifying values through a const iterator is not allowed, // uncommenting the following line should result in a static assertion failure: //tree.cbeginValueOn().modifyValue(Local::negate); } { typedef openvdb::tree::Tree4::Type StringTree323f; StringTree323f tree(/*background=*/""); tree.addTile(/*level=*/3, Coord(-1), /*value=*/"abc", /*active=*/true); tree.addTile(/*level=*/2, Coord(1 << (DIM0 + DIM1)), /*value=*/"abc", /*active=*/true); tree.addTile(/*level=*/1, Coord(1 << DIM0), /*value=*/"abc", /*active=*/true); tree.addTile(/*level=*/0, Coord(0), /*value=*/"abc", /*active=*/true); struct Local { static inline void op(std::string& s) { s.append("def"); } }; for (StringTree323f::ValueOnIter iter = tree.beginValueOn(); iter; ++iter) { iter.modifyValue(Local::op); } const std::string expectedVal("abcdef"); for (StringTree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT_EQUAL(expectedVal, *iter); } for (StringTree323f::ValueOffCIter iter = tree.cbeginValueOff(); iter; ++iter) { CPPUNIT_ASSERT((*iter).empty()); } } } void TestTreeIterators::testDepthBounds() { const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; typedef openvdb::tree::Tree4::Type Tree323f; Tree323f tree(/*fillValue=*/256.0f); tree.setValue(openvdb::Coord(4), 0.0f); tree.setValue(openvdb::Coord(-4), -1.0f); const size_t numDepth1 = 2 * ((1 << (3 * DIM2)) - 1), // 2 8x8x8 InternalNodes - 1 child pointer each numDepth2 = 2 * ((1 << (3 * DIM1)) - 1), // 2 4x4x4 InternalNodes - 1 child pointer each numDepth3 = 2 * ((1 << (3 * DIM0)) - 1), // 2 8x8x8 LeafNodes - 1 active value each expectedNumOff = numDepth1 + numDepth2 + numDepth3; { Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); CPPUNIT_ASSERT(iter.test()); // Read all inactive tile and voxel values through a non-const value iterator. size_t numOff = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(!iter.isValueOn()); ++numOff; } CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } { // Repeat, setting the minimum iterator depth to 2. Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); CPPUNIT_ASSERT(iter.test()); iter.setMinDepth(2); CPPUNIT_ASSERT(iter.test()); size_t numOff = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(!iter.isValueOn()); ++numOff; const int depth = iter.getDepth(); CPPUNIT_ASSERT(depth > 1); } CPPUNIT_ASSERT_EQUAL(expectedNumOff - numDepth1, numOff); } { // Repeat, setting the minimum and maximum depths to 2. Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); CPPUNIT_ASSERT(iter.test()); iter.setMinDepth(2); CPPUNIT_ASSERT(iter.test()); iter.setMaxDepth(2); CPPUNIT_ASSERT(iter.test()); size_t numOff = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(!iter.isValueOn()); ++numOff; const int depth = iter.getDepth(); CPPUNIT_ASSERT_EQUAL(2, depth); } CPPUNIT_ASSERT_EQUAL(expectedNumOff - numDepth1 - numDepth3, numOff); } { // FX-7884 regression test using namespace openvdb; const float radius = 4.3f, voxelSize = 0.1f, width = 2.0f; const Vec3f center(15.8f, 13.2f, 16.7f); FloatGrid::Ptr sphereGrid = tools::createLevelSetSphere( radius, center, voxelSize, width); const FloatTree& sphereTree = sphereGrid->tree(); FloatGrid::ValueOffIter iter = sphereGrid->beginValueOff(); iter.setMaxDepth(2); for ( ; iter; ++iter) { const Coord ijk = iter.getCoord(); ASSERT_DOUBLES_EXACTLY_EQUAL(sphereTree.getValue(ijk), *iter); } } } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestInt32Metadata.cc0000644000000000000000000000535512603226506016611 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMaps.cc0000644000000000000000000006770412603226506015157 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestGradient.cc0000644000000000000000000007751512603226506016015 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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; 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 = float((wsLocation - wsCenter).length()); acc.setValue(ijk, dis); } } } { // test at location 10, 10, 10 in index space math::Coord xyz(10, 10, 10); math::Vec3s result = math::Gradient::result(*map, acc, xyz); // The Gradient should be unit lenght for this case CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); math::Vec3d wsVec = transform->indexToWorld(xyz); math::Vec3d direction = (wsVec - wsCenter); direction.normalize(); // test the actual direction of the gradient CPPUNIT_ASSERT(direction.eq(result, 0.01 /*tolerance*/)); } { // test at location 30, 30, 60 in index space math::Coord xyz(30, 30, 60); math::Vec3s result = math::Gradient::result(*map, acc, xyz); // The Gradient should be unit lenght for this case CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); math::Vec3d wsVec = transform->indexToWorld(xyz); math::Vec3d direction = (wsVec - wsCenter); direction.normalize(); // test the actual direction of the gradient CPPUNIT_ASSERT(direction.eq(result, 0.01 /*tolerance*/)); } } void TestGradient::testWSGradientStencil() { using namespace openvdb; double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f ,10.0f);//i.e. (12,16,20) in index space const float radius = 10; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); const Coord xyz(11, 17, 26); // try with a map math::SevenPointStencil stencil(*grid); stencil.moveTo(xyz); math::SecondOrderDenseStencil dense_2ndOrder(*grid); dense_2ndOrder.moveTo(xyz); math::FourthOrderDenseStencil dense_4thOrder(*grid); dense_4thOrder.moveTo(xyz); Vec3f result; math::MapBase::Ptr rotated_map; { math::UniformScaleMap map(voxel_size); result = math::Gradient::result( map, stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); rotated_map = map.preRotate(1.5, math::X_AXIS); // verify the new map is an affine map CPPUNIT_ASSERT(rotated_map->type() == math::AffineMap::mapType()); math::AffineMap::Ptr affine_map = 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; double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius = 10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); const Coord xyz(11, 17, 26); math::SevenPointStencil sevenpt(*grid); sevenpt.moveTo(xyz); math::ThirteenPointStencil thirteenpt(*grid); thirteenpt.moveTo(xyz); math::SecondOrderDenseStencil dense_2ndOrder(*grid); dense_2ndOrder.moveTo(xyz); math::NineteenPointStencil nineteenpt(*grid); nineteenpt.moveTo(xyz); // test gradient in index and world space using the 7-pt stencil math::UniformScaleMap uniform_scale(voxel_size); FloatTree::ValueType normsqrd; normsqrd = math::GradientNormSqrd::result( uniform_scale, sevenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.07); // test gradient in index and world space using the 13pt stencil normsqrd = math::GradientNormSqrd::result( uniform_scale, thirteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.05); math::AffineMap affine(voxel_size*math::Mat3d::identity()); normsqrd = math::GradientNormSqrd::result( affine, dense_2ndOrder); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.07); normsqrd = math::GradientNormSqrd::result( uniform_scale, nineteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.05); } void TestGradient::testGradientTool() { using namespace openvdb; FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64, 64, 64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius = 10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); const Coord xyz(10, 20, 30); Vec3SGrid::Ptr grad = tools::gradient(*grid); CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(grad->activeVoxelCount())); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, grad->getConstAccessor().getValue(xyz).length(), /*tolerance=*/0.01); } void TestGradient::testGradientMaskedTool() { using namespace openvdb; FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64, 64, 64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius = 10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); BoolGrid::Ptr maskGrid = BoolGrid::create(false); maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); Vec3SGrid::Ptr grad = tools::gradient(*grid, *maskGrid); {// outside the masked region const Coord xyz(10, 20, 30); CPPUNIT_ASSERT(!maskbbox.isInside(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, grad->getConstAccessor().getValue(xyz).length(), /*tolerance=*/0.01); } {// inside the masked region const Coord xyz(38, 35, 33); CPPUNIT_ASSERT(maskbbox.isInside(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, grad->getConstAccessor().getValue(xyz).length(), /*tolerance=*/0.01); } } void TestGradient::testIntersectsIsoValue() { using namespace openvdb; {// test zero crossing in -x FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(-1,0,0), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT( stencil.intersects( )); CPPUNIT_ASSERT( stencil.intersects( 0.0f)); CPPUNIT_ASSERT( stencil.intersects( 2.0f)); CPPUNIT_ASSERT(!stencil.intersects( 5.5f)); CPPUNIT_ASSERT(!stencil.intersects(-2.5f)); } {// test zero crossing in +x FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(1,0,0), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } {// test zero crossing in -y FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(0,-1,0), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } {// test zero crossing in y FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(0,1,0), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } {// test zero crossing in -z FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(0,0,-1), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } {// test zero crossing in z FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(0,0,1), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } {// test zero crossing in -x & z FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(-1,0,1), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(!stencil.intersects()); } {// test zero multiple crossings FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(-1, 0, 1), -1.0f); tree.setValue(xyz.offsetBy( 0, 0, 1), -2.0f); tree.setValue(xyz.offsetBy( 0, 1, 0), -3.0f); tree.setValue(xyz.offsetBy( 0, 0,-1), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } } void TestGradient::testOldStyleStencils() { using namespace openvdb; FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::createLinearTransform(/*voxel size=*/0.5)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f,8.0f,10.0f);//i.e. (12,16,20) in index space const float radius=10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); const Coord xyz(11, 17, 26); math::GradStencil gs(*grid); gs.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, gs.gradient().length(), /*tolerance=*/0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, gs.normSqGrad(), /*tolerance=*/0.10); math::WenoStencil ws(*grid); ws.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, ws.gradient().length(), /*tolerance=*/0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, ws.normSqGrad(), /*tolerance=*/0.01); math::CurvatureStencil cs(*grid); cs.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, cs.gradient().length(), /*tolerance=*/0.01); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestQuat.cc0000644000000000000000000002204312603226506015154 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include using namespace openvdb::math; class TestQuat: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE( TestQuat ); CPPUNIT_TEST( testConstructor ); CPPUNIT_TEST( testAxisAngle ); CPPUNIT_TEST( testOpPlus ); CPPUNIT_TEST( testOpMinus ); CPPUNIT_TEST( testOpMultiply ); CPPUNIT_TEST( testInvert ); CPPUNIT_TEST( testEulerAngles ); CPPUNIT_TEST_SUITE_END(); void testConstructor(); void testAxisAngle(); void testOpPlus(); void testOpMinus(); void testOpMultiply(); void testInvert(); void testEulerAngles(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestQuat); void TestQuat::testConstructor() { { Quat qq(1.23f, 2.34f, 3.45f, 4.56f); CPPUNIT_ASSERT( isExactlyEqual(qq.x(), 1.23f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.y(), 2.34f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.z(), 3.45f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.w(), 4.56f) ); } { float a[] = { 1.23f, 2.34f, 3.45f, 4.56f }; Quat qq(a); CPPUNIT_ASSERT( isExactlyEqual(qq.x(), 1.23f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.y(), 2.34f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.z(), 3.45f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.w(), 4.56f) ); } } void TestQuat::testAxisAngle() { float TOL = 1e-6f; Quat q1(1.0f, 2.0f, 3.0f, 4.0f); Quat q2(1.2f, 2.3f, 3.4f, 4.5f); Vec3s v(1, 2, 3); v.normalize(); float a = float(M_PI / 4.f); Quat q(v,a); float b = q.angle(); Vec3s vv = q.axis(); CPPUNIT_ASSERT( isApproxEqual(a, b, TOL) ); CPPUNIT_ASSERT( v.eq(vv, TOL) ); q1.setAxisAngle(v,a); b = q1.angle(); vv = q1.axis(); CPPUNIT_ASSERT( isApproxEqual(a, b, TOL) ); CPPUNIT_ASSERT( v.eq(vv, TOL) ); } void TestQuat::testOpPlus() { Quat q1(1.0f, 2.0f, 3.0f, 4.0f); Quat q2(1.2f, 2.3f, 3.4f, 4.5f); Quat q = q1 + q2; float x=q1.x()+q2.x(), y=q1.y()+q2.y(), z=q1.z()+q2.z(), w=q1.w()+q2.w(); CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); q = q1; q += q2; CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); q.add(q1,q2); CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); } void TestQuat::testOpMinus() { Quat q1(1.0f, 2.0f, 3.0f, 4.0f); Quat q2(1.2f, 2.3f, 3.4f, 4.5f); Quat q = q1 - q2; float x=q1.x()-q2.x(), y=q1.y()-q2.y(), z=q1.z()-q2.z(), w=q1.w()-q2.w(); CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); q = q1; q -= q2; CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); q.sub(q1,q2); CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); } void TestQuat::testOpMultiply() { Quat q1(1.0f, 2.0f, 3.0f, 4.0f); Quat q2(1.2f, 2.3f, 3.4f, 4.5f); Quat q = q1 * 1.5f; CPPUNIT_ASSERT( isExactlyEqual(q.x(), float(1.5f)*q1.x()) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), float(1.5f)*q1.y()) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), float(1.5f)*q1.z()) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), float(1.5f)*q1.w()) ); q = q1; q *= 1.5f; CPPUNIT_ASSERT( isExactlyEqual(q.x(), float(1.5f)*q1.x()) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), float(1.5f)*q1.y()) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), float(1.5f)*q1.z()) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), float(1.5f)*q1.w()) ); q.scale(1.5f, q1); CPPUNIT_ASSERT( isExactlyEqual(q.x(), float(1.5f)*q1.x()) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), float(1.5f)*q1.y()) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), float(1.5f)*q1.z()) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), float(1.5f)*q1.w()) ); } void TestQuat::testInvert() { float TOL = 1e-6f; Quat q1(1.0f, 2.0f, 3.0f, 4.0f); Quat q2(1.2f, 2.3f, 3.4f, 4.5f); q1 = q2; q2 = q2.inverse(); Quat q = q1*q2; CPPUNIT_ASSERT( q.eq( Quat(0,0,0,1), TOL ) ); q1.normalize(); q2 = q1.conjugate(); q = q1*q2; CPPUNIT_ASSERT( q.eq( Quat(0,0,0,1), TOL ) ); } void TestQuat::testEulerAngles() { { double TOL = 1e-7; Mat4d rx, ry, rz; const double angle1 = 20. * M_PI / 180.; const double angle2 = 64. * M_PI / 180.; const double angle3 = 125. *M_PI / 180.; rx.setToRotation(Vec3d(1,0,0), angle1); ry.setToRotation(Vec3d(0,1,0), angle2); rz.setToRotation(Vec3d(0,0,1), angle3); Mat4d r = rx * ry * rz; const Quat rot(r.getMat3()); Vec3d result = rot.eulerAngles(ZYX_ROTATION); rx.setToRotation(Vec3d(1,0,0), result[0]); ry.setToRotation(Vec3d(0,1,0), result[1]); rz.setToRotation(Vec3d(0,0,1), result[2]); Mat4d rtest = rx * ry * rz; CPPUNIT_ASSERT(r.eq(rtest, TOL)); } { double TOL = 1e-7; Mat4d rx, ry, rz; const double angle1 = 20. * M_PI / 180.; const double angle2 = 64. * M_PI / 180.; const double angle3 = 125. *M_PI / 180.; rx.setToRotation(Vec3d(1,0,0), angle1); ry.setToRotation(Vec3d(0,1,0), angle2); rz.setToRotation(Vec3d(0,0,1), angle3); Mat4d r = rz * ry * rx; const Quat rot(r.getMat3()); Vec3d result = rot.eulerAngles(XYZ_ROTATION); rx.setToRotation(Vec3d(1,0,0), result[0]); ry.setToRotation(Vec3d(0,1,0), result[1]); rz.setToRotation(Vec3d(0,0,1), result[2]); Mat4d rtest = rz * ry * rx; CPPUNIT_ASSERT(r.eq(rtest, TOL)); } { double TOL = 1e-7; Mat4d rx, ry, rz; const double angle1 = 20. * M_PI / 180.; const double angle2 = 64. * M_PI / 180.; const double angle3 = 125. *M_PI / 180.; rx.setToRotation(Vec3d(1,0,0), angle1); ry.setToRotation(Vec3d(0,1,0), angle2); rz.setToRotation(Vec3d(0,0,1), angle3); Mat4d r = rz * rx * ry; const Quat rot(r.getMat3()); Vec3d result = rot.eulerAngles(YXZ_ROTATION); rx.setToRotation(Vec3d(1,0,0), result[0]); ry.setToRotation(Vec3d(0,1,0), result[1]); rz.setToRotation(Vec3d(0,0,1), result[2]); Mat4d rtest = rz * rx * ry; CPPUNIT_ASSERT(r.eq(rtest, TOL)); } { const Quat rot(X_AXIS, 1.0); Vec3s result = rot.eulerAngles(XZY_ROTATION); CPPUNIT_ASSERT_EQUAL(result, Vec3s(1,0,0)); } } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestConjGradient.cc0000644000000000000000000001640212603226506016613 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include class TestConjGradient: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestConjGradient); CPPUNIT_TEST(testJacobi); CPPUNIT_TEST(testIncompleteCholesky); CPPUNIT_TEST(testVectorDotProduct); CPPUNIT_TEST_SUITE_END(); void testJacobi(); void testIncompleteCholesky(); void testVectorDotProduct(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestConjGradient); //////////////////////////////////////// void TestConjGradient::testJacobi() { using namespace openvdb; typedef math::pcg::SparseStencilMatrix MatrixType; const math::pcg::SizeType rows = 5; MatrixType A(rows); A.setValue(0, 0, 24.0); A.setValue(0, 2, 6.0); A.setValue(1, 1, 8.0); A.setValue(1, 2, 2.0); A.setValue(2, 0, 6.0); A.setValue(2, 1, 2.0); A.setValue(2, 2, 8.0); A.setValue(2, 3, -6.0); A.setValue(2, 4, 2.0); A.setValue(3, 2, -6.0); A.setValue(3, 3, 24.0); A.setValue(4, 2, 2.0); A.setValue(4, 4, 8.0); CPPUNIT_ASSERT(A.isFinite()); MatrixType::VectorType x(rows, 0.0), b(rows, 1.0), expected(rows); expected[0] = 0.0104167; expected[1] = 0.09375; expected[2] = 0.125; expected[3] = 0.0729167; expected[4] = 0.09375; math::pcg::JacobiPreconditioner precond(A); // Solve A * x = b for x. math::pcg::State result = math::pcg::solve( A, b, x, precond, math::pcg::terminationDefaults()); CPPUNIT_ASSERT(result.success); CPPUNIT_ASSERT(result.iterations <= 20); CPPUNIT_ASSERT(x.eq(expected, 1.0e-5)); } void TestConjGradient::testIncompleteCholesky() { using namespace openvdb; typedef math::pcg::SparseStencilMatrix MatrixType; typedef math::pcg::IncompleteCholeskyPreconditioner CholeskyPrecond; const math::pcg::SizeType rows = 5; MatrixType A(5); A.setValue(0, 0, 24.0); A.setValue(0, 2, 6.0); A.setValue(1, 1, 8.0); A.setValue(1, 2, 2.0); A.setValue(2, 0, 6.0); A.setValue(2, 1, 2.0); A.setValue(2, 2, 8.0); A.setValue(2, 3, -6.0); A.setValue(2, 4, 2.0); A.setValue(3, 2, -6.0); A.setValue(3, 3, 24.0); A.setValue(4, 2, 2.0); A.setValue(4, 4, 8.0); CPPUNIT_ASSERT(A.isFinite()); CholeskyPrecond precond(A); { const CholeskyPrecond::TriangularMatrix lower = precond.lowerMatrix(); CholeskyPrecond::TriangularMatrix expected(5); expected.setValue(0, 0, 4.89898); expected.setValue(1, 1, 2.82843); expected.setValue(2, 0, 1.22474); expected.setValue(2, 1, 0.707107); expected.setValue(2, 2, 2.44949); expected.setValue(3, 2, -2.44949); expected.setValue(3, 3, 4.24264); expected.setValue(4, 2, 0.816497); expected.setValue(4, 4, 2.70801); #if 0 std::cout << "Expected:\n"; for (int i = 0; i < 5; ++i) { std::cout << " " << expected.getConstRow(i).str() << std::endl; } std::cout << "Actual:\n"; for (int i = 0; i < 5; ++i) { std::cout << " " << lower.getConstRow(i).str() << std::endl; } #endif CPPUNIT_ASSERT(lower.eq(expected, 1.0e-5)); } { const CholeskyPrecond::TriangularMatrix upper = precond.upperMatrix(); CholeskyPrecond::TriangularMatrix expected(5); { expected.setValue(0, 0, 4.89898); expected.setValue(0, 2, 1.22474); expected.setValue(1, 1, 2.82843); expected.setValue(1, 2, 0.707107); expected.setValue(2, 2, 2.44949); expected.setValue(2, 3, -2.44949); expected.setValue(2, 4, 0.816497); expected.setValue(3, 3, 4.24264); expected.setValue(4, 4, 2.70801); } #if 0 std::cout << "Expected:\n"; for (int i = 0; i < 5; ++i) { std::cout << " " << expected.getConstRow(i).str() << std::endl; } std::cout << "Actual:\n"; for (int i = 0; i < 5; ++i) { std::cout << " " << upper.getConstRow(i).str() << std::endl; } #endif CPPUNIT_ASSERT(upper.eq(expected, 1.0e-5)); } MatrixType::VectorType x(rows, 0.0), b(rows, 1.0), expected(rows); expected[0] = 0.0104167; expected[1] = 0.09375; expected[2] = 0.125; expected[3] = 0.0729167; expected[4] = 0.09375; // Solve A * x = b for x. math::pcg::State result = math::pcg::solve( A, b, x, precond, math::pcg::terminationDefaults()); CPPUNIT_ASSERT(result.success); CPPUNIT_ASSERT(result.iterations <= 20); CPPUNIT_ASSERT(x.eq(expected, 1.0e-5)); } void TestConjGradient::testVectorDotProduct() { using namespace openvdb; typedef math::pcg::Vector VectorType; // Test small vector - runs in series { const size_t length = 1000; VectorType aVec(length, 2.f); VectorType bVec(length, 3.f); VectorType::ValueType result = aVec.dot(bVec); CPPUNIT_ASSERT_DOUBLES_EQUAL( result, 6.f * length, 1.e-7); } // Test long vector - runs in parallel { const size_t length = 10034502; VectorType aVec(length, 2.f); VectorType bVec(length, 3.f); VectorType::ValueType result = aVec.dot(bVec); CPPUNIT_ASSERT_DOUBLES_EQUAL( result, 6.f * length, 1.e-7); } } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestDense.cc0000644000000000000000000007576012603226506015316 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// //#define BENCHMARK_TEST #include #include #include #include #include #include #ifdef BENCHMARK_TEST #include #endif class TestDense: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestDense); CPPUNIT_TEST(testDenseZYX); CPPUNIT_TEST(testDenseXYZ); CPPUNIT_TEST(testCopyZYX); CPPUNIT_TEST(testCopyXYZ); CPPUNIT_TEST(testCopyBoolZYX); CPPUNIT_TEST(testCopyBoolXYZ); CPPUNIT_TEST(testCopyFromDenseWithOffsetZYX); CPPUNIT_TEST(testCopyFromDenseWithOffsetXYZ); CPPUNIT_TEST(testDense2SparseZYX); CPPUNIT_TEST(testDense2SparseXYZ); CPPUNIT_TEST(testDense2Sparse2ZYX); CPPUNIT_TEST(testDense2Sparse2XYZ); CPPUNIT_TEST(testInvalidBBoxZYX); CPPUNIT_TEST(testInvalidBBoxXYZ); CPPUNIT_TEST(testDense2Sparse2DenseZYX); CPPUNIT_TEST(testDense2Sparse2DenseXYZ); CPPUNIT_TEST_SUITE_END(); void testDenseZYX(); void testDenseXYZ(); template void testCopy(); void testCopyZYX() { this->testCopy(); } void testCopyXYZ() { this->testCopy(); } template void testCopyBool(); void testCopyBoolZYX() { this->testCopyBool(); } void testCopyBoolXYZ() { this->testCopyBool(); } template void testCopyFromDenseWithOffset(); void testCopyFromDenseWithOffsetZYX() { this->testCopyFromDenseWithOffset(); } void testCopyFromDenseWithOffsetXYZ() { this->testCopyFromDenseWithOffset(); } template void testDense2Sparse(); void testDense2SparseZYX() { this->testDense2Sparse(); } void testDense2SparseXYZ() { this->testDense2Sparse(); } template void testDense2Sparse2(); void testDense2Sparse2ZYX() { this->testDense2Sparse2(); } void testDense2Sparse2XYZ() { this->testDense2Sparse2(); } template void testInvalidBBox(); void testInvalidBBoxZYX() { this->testInvalidBBox(); } void testInvalidBBoxXYZ() { this->testInvalidBBox(); } template void testDense2Sparse2Dense(); void testDense2Sparse2DenseZYX() { this->testDense2Sparse2Dense(); } void testDense2Sparse2DenseXYZ() { this->testDense2Sparse2Dense(); } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestDense); void TestDense::testDenseZYX() { const openvdb::CoordBBox bbox(openvdb::Coord(-40,-5, 6), openvdb::Coord(-11, 7,22)); openvdb::tools::Dense dense(bbox);//LayoutZYX is the default // Check Desne::origin() CPPUNIT_ASSERT(openvdb::Coord(-40,-5, 6) == dense.origin()); // Check coordToOffset and offsetToCoord size_t offset = 0; for (openvdb::Coord P(bbox.min()); P[0] <= bbox.max()[0]; ++P[0]) { for (P[1] = bbox.min()[1]; P[1] <= bbox.max()[1]; ++P[1]) { for (P[2] = bbox.min()[2]; P[2] <= bbox.max()[2]; ++P[2]) { //std::cerr << "offset = " << offset << " P = " << P << std::endl; CPPUNIT_ASSERT_EQUAL(offset, dense.coordToOffset(P)); CPPUNIT_ASSERT_EQUAL(P - dense.origin(), dense.offsetToLocalCoord(offset)); CPPUNIT_ASSERT_EQUAL(P, dense.offsetToCoord(offset)); ++offset; } } } // Check Dense::valueCount const int size = static_cast(dense.valueCount()); CPPUNIT_ASSERT_EQUAL(30*13*17, size); // Check Dense::fill(float) and Dense::getValue(size_t) const float v = 0.234f; dense.fill(v); for (int i=0; i dense(bbox); // Check Desne::origin() CPPUNIT_ASSERT(openvdb::Coord(-40,-5, 6) == dense.origin()); // Check coordToOffset and offsetToCoord size_t offset = 0; for (openvdb::Coord P(bbox.min()); P[2] <= bbox.max()[2]; ++P[2]) { for (P[1] = bbox.min()[1]; P[1] <= bbox.max()[1]; ++P[1]) { for (P[0] = bbox.min()[0]; P[0] <= bbox.max()[0]; ++P[0]) { //std::cerr << "offset = " << offset << " P = " << P << std::endl; CPPUNIT_ASSERT_EQUAL(offset, dense.coordToOffset(P)); CPPUNIT_ASSERT_EQUAL(P - dense.origin(), dense.offsetToLocalCoord(offset)); CPPUNIT_ASSERT_EQUAL(P, dense.offsetToCoord(offset)); ++offset; } } } // Check Dense::valueCount const int size = static_cast(dense.valueCount()); CPPUNIT_ASSERT_EQUAL(30*13*17, size); // Check Dense::fill(float) and Dense::getValue(size_t) const float v = 0.234f; dense.fill(v); for (int i=0; i > class CheckDense { public: typedef typename TreeT::ValueType ValueT; CheckDense() : mTree(NULL), mDense(NULL) { CPPUNIT_ASSERT(DenseT::memoryLayout() == openvdb::tools::LayoutZYX || DenseT::memoryLayout() == openvdb::tools::LayoutXYZ ); } void check(const TreeT& tree, const DenseT& dense) { mTree = &tree; mDense = &dense; tbb::parallel_for(dense.bbox(), *this); } void operator()(const openvdb::CoordBBox& bbox) const { openvdb::tree::ValueAccessor acc(*mTree); if (DenseT::memoryLayout() == openvdb::tools::LayoutZYX) {//resolved at compiletime for (openvdb::Coord P(bbox.min()); P[0] <= bbox.max()[0]; ++P[0]) { for (P[1] = bbox.min()[1]; P[1] <= bbox.max()[1]; ++P[1]) { for (P[2] = bbox.min()[2]; P[2] <= bbox.max()[2]; ++P[2]) { CPPUNIT_ASSERT_DOUBLES_EQUAL(acc.getValue(P), mDense->getValue(P), /*tolerance=*/0.0001); } } } } else { for (openvdb::Coord P(bbox.min()); P[2] <= bbox.max()[2]; ++P[2]) { for (P[1] = bbox.min()[1]; P[1] <= bbox.max()[1]; ++P[1]) { for (P[0] = bbox.min()[0]; P[0] <= bbox.max()[0]; ++P[0]) { CPPUNIT_ASSERT_DOUBLES_EQUAL(acc.getValue(P), mDense->getValue(P), /*tolerance=*/0.0001); } } } } } private: const TreeT* mTree; const DenseT* mDense; };// CheckDense template void TestDense::testCopy() { using namespace openvdb; //std::cerr << "\nTesting testCopy with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef tools::Dense DenseT; CheckDense checkDense; const float radius = 10.0f, tolerance = 0.00001f; const Vec3f center(0.0f); // decrease the voxelSize to test larger grids #ifdef BENCHMARK_TEST const float voxelSize = 0.05f, width = 5.0f; #else const float voxelSize = 0.5f, width = 5.0f; #endif // Create a VDB containing a level set of a sphere FloatGrid::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, width); FloatTree& tree0 = grid->tree(); // Create an empty dense grid DenseT dense(grid->evalActiveVoxelBoundingBox()); #ifdef BENCHMARK_TEST std::cerr << "\nBBox = " << grid->evalActiveVoxelBoundingBox() << std::endl; #endif {//check Dense::fill dense.fill(voxelSize); #ifndef BENCHMARK_TEST checkDense.check(FloatTree(voxelSize), dense); #endif } {// parallel convert to dense #ifdef BENCHMARK_TEST util::CpuTimer ts; ts.start("CopyToDense"); #endif tools::copyToDense(*grid, dense); #ifdef BENCHMARK_TEST ts.stop(); #else checkDense.check(tree0, dense); #endif } {// Parallel create from dense #ifdef BENCHMARK_TEST util::CpuTimer ts; ts.start("CopyFromDense"); #endif FloatTree tree1(tree0.background()); tools::copyFromDense(dense, tree1, tolerance); #ifdef BENCHMARK_TEST ts.stop(); #else checkDense.check(tree1, dense); #endif } } template void TestDense::testCopyBool() { using namespace openvdb; //std::cerr << "\nTesting testCopyBool with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; const Coord bmin(-1), bmax(8); const CoordBBox bbox(bmin, bmax); BoolGrid::Ptr grid = createGrid(false); BoolGrid::ConstAccessor acc = grid->getConstAccessor(); typedef openvdb::tools::Dense DenseT; DenseT dense(bbox); dense.fill(false); // Start with sparse and dense grids both filled with false. Coord xyz; int &x = xyz[0], &y = xyz[1], &z = xyz[2]; for (x = bmin.x(); x <= bmax.x(); ++x) { for (y = bmin.y(); y <= bmax.y(); ++y) { for (z = bmin.z(); z <= bmax.z(); ++z) { CPPUNIT_ASSERT_EQUAL(false, dense.getValue(xyz)); CPPUNIT_ASSERT_EQUAL(false, acc.getValue(xyz)); } } } // Fill the dense grid with true. dense.fill(true); // Copy the contents of the dense grid to the sparse grid. tools::copyFromDense(dense, *grid, /*tolerance=*/false); // Verify that both sparse and dense grids are now filled with true. for (x = bmin.x(); x <= bmax.x(); ++x) { for (y = bmin.y(); y <= bmax.y(); ++y) { for (z = bmin.z(); z <= bmax.z(); ++z) { CPPUNIT_ASSERT_EQUAL(true, dense.getValue(xyz)); CPPUNIT_ASSERT_EQUAL(true, acc.getValue(xyz)); } } } // Fill the dense grid with false. dense.fill(false); // Copy the contents (= true) of the sparse grid to the dense grid. tools::copyToDense(*grid, dense); // Verify that the dense grid is now filled with true. for (x = bmin.x(); x <= bmax.x(); ++x) { for (y = bmin.y(); y <= bmax.y(); ++y) { for (z = bmin.z(); z <= bmax.z(); ++z) { CPPUNIT_ASSERT_EQUAL(true, dense.getValue(xyz)); } } } } // Test copying from a dense grid to a sparse grid with various bounding boxes. template void TestDense::testCopyFromDenseWithOffset() { using namespace openvdb; //std::cerr << "\nTesting testCopyFromDenseWithOffset with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef openvdb::tools::Dense DenseT; const int DIM = 20, COUNT = DIM * DIM * DIM; const float FOREGROUND = 99.0f, BACKGROUND = 5000.0f; const int OFFSET[] = { 1, -1, 1001, -1001 }; for (int offsetIdx = 0; offsetIdx < 4; ++offsetIdx) { const int offset = OFFSET[offsetIdx]; const CoordBBox bbox = CoordBBox::createCube(Coord(offset), DIM); DenseT dense(bbox, FOREGROUND); CPPUNIT_ASSERT_EQUAL(bbox, dense.bbox()); FloatGrid grid(BACKGROUND); tools::copyFromDense(dense, grid, /*tolerance=*/0.0); const CoordBBox gridBBox = grid.evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT_EQUAL(bbox, gridBBox); CPPUNIT_ASSERT_EQUAL(COUNT, int(grid.activeVoxelCount())); FloatGrid::ConstAccessor acc = grid.getConstAccessor(); for (int i = gridBBox.min()[0], ie = gridBBox.max()[0]; i < ie; ++i) { for (int j = gridBBox.min()[1], je = gridBBox.max()[1]; j < je; ++j) { for (int k = gridBBox.min()[2], ke = gridBBox.max()[2]; k < ke; ++k) { const Coord ijk(i, j, k); CPPUNIT_ASSERT_DOUBLES_EQUAL( FOREGROUND, acc.getValue(ijk), /*tolerance=*/0.0); CPPUNIT_ASSERT(acc.isValueOn(ijk)); } } } } } template void TestDense::testDense2Sparse() { // The following test revealed a bug in v2.0.0b2 using namespace openvdb; //std::cerr << "\nTesting testDense2Sparse with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef tools::Dense DenseT; // Test Domain Resolution Int32 sizeX = 8, sizeY = 8, sizeZ = 9; // Define a dense grid DenseT dense(Coord(sizeX, sizeY, sizeZ)); const CoordBBox bboxD = dense.bbox(); // std::cerr << "\nDense bbox" << bboxD << std::endl; // Verify that the CoordBBox is truely used as [inclusive, inclusive] CPPUNIT_ASSERT(int(dense.valueCount()) == int(sizeX * sizeY * sizeZ)); // Fill the dense grid with constant value 1. dense.fill(1.0f); // Create two empty float grids FloatGrid::Ptr gridS = FloatGrid::create(0.0f /*background*/); FloatGrid::Ptr gridP = FloatGrid::create(0.0f /*background*/); // Convert in serial and parallel modes tools::copyFromDense(dense, *gridS, /*tolerance*/0.0f, /*serial = */ true); tools::copyFromDense(dense, *gridP, /*tolerance*/0.0f, /*serial = */ false); float minS, maxS; float minP, maxP; gridS->evalMinMax(minS, maxS); gridP->evalMinMax(minP, maxP); const float tolerance = 0.0001f; CPPUNIT_ASSERT_DOUBLES_EQUAL(minS, minP, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(maxS, maxP, tolerance); CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), Index64(sizeX * sizeY * sizeZ)); const FloatTree& treeS = gridS->tree(); const FloatTree& treeP = gridP->tree(); // Values in Test Domain are correct for (Coord ijk(bboxD.min()); ijk[0] <= bboxD.max()[0]; ++ijk[0]) { for (ijk[1] = bboxD.min()[1]; ijk[1] <= bboxD.max()[1]; ++ijk[1]) { for (ijk[2] = bboxD.min()[2]; ijk[2] <= bboxD.max()[2]; ++ijk[2]) { const float expected = bboxD.isInside(ijk) ? 1.f : 0.f; CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, 1.f, tolerance); const float& vS = treeS.getValue(ijk); const float& vP = treeP.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); } } } CoordBBox bboxP = gridP->evalActiveVoxelBoundingBox(); const Index64 voxelCountP = gridP->activeVoxelCount(); //std::cerr << "\nParallel: bbox=" << bboxP << " voxels=" << voxelCountP << std::endl; CPPUNIT_ASSERT( bboxP == bboxD ); CPPUNIT_ASSERT_EQUAL( dense.valueCount(), voxelCountP); CoordBBox bboxS = gridS->evalActiveVoxelBoundingBox(); const Index64 voxelCountS = gridS->activeVoxelCount(); //std::cerr << "\nSerial: bbox=" << bboxS << " voxels=" << voxelCountS << std::endl; CPPUNIT_ASSERT( bboxS == bboxD ); CPPUNIT_ASSERT_EQUAL( dense.valueCount(), voxelCountS); // Topology CPPUNIT_ASSERT( bboxS.isInside(bboxS) ); CPPUNIT_ASSERT( bboxP.isInside(bboxP) ); CPPUNIT_ASSERT( bboxS.isInside(bboxP) ); CPPUNIT_ASSERT( bboxP.isInside(bboxS) ); /// Check that the two grids agree for (Coord ijk(bboxS.min()); ijk[0] <= bboxS.max()[0]; ++ijk[0]) { for (ijk[1] = bboxS.min()[1]; ijk[1] <= bboxS.max()[1]; ++ijk[1]) { for (ijk[2] = bboxS.min()[2]; ijk[2] <= bboxS.max()[2]; ++ijk[2]) { const float& vS = treeS.getValue(ijk); const float& vP = treeP.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(vS, vP, tolerance); // the value we should get based on the original domain const float expected = bboxD.isInside(ijk) ? 1.f : 0.f; CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); } } } // Verify the tree topology matches. CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), gridS->activeVoxelCount()); CPPUNIT_ASSERT(gridP->evalActiveVoxelBoundingBox() == gridS->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT(treeP.hasSameTopology(treeS) ); } template void TestDense::testDense2Sparse2() { // The following tests copying a dense grid into a VDB tree with // existing values outside the bbox of the dense grid. using namespace openvdb; //std::cerr << "\nTesting testDense2Sparse2 with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef tools::Dense DenseT; // Test Domain Resolution const int sizeX = 8, sizeY = 8, sizeZ = 9; const Coord magicVoxel(sizeX, sizeY, sizeZ); // Define a dense grid DenseT dense(Coord(sizeX, sizeY, sizeZ)); const CoordBBox bboxD = dense.bbox(); //std::cerr << "\nDense bbox" << bboxD << std::endl; // Verify that the CoordBBox is truely used as [inclusive, inclusive] CPPUNIT_ASSERT_EQUAL(sizeX * sizeY * sizeZ, static_cast(dense.valueCount())); // Fill the dense grid with constant value 1. dense.fill(1.0f); // Create two empty float grids FloatGrid::Ptr gridS = FloatGrid::create(0.0f /*background*/); FloatGrid::Ptr gridP = FloatGrid::create(0.0f /*background*/); gridS->tree().setValue(magicVoxel, 5.0f); gridP->tree().setValue(magicVoxel, 5.0f); // Convert in serial and parallel modes tools::copyFromDense(dense, *gridS, /*tolerance*/0.0f, /*serial = */ true); tools::copyFromDense(dense, *gridP, /*tolerance*/0.0f, /*serial = */ false); float minS, maxS; float minP, maxP; gridS->evalMinMax(minS, maxS); gridP->evalMinMax(minP, maxP); const float tolerance = 0.0001f; CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0f, minP, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0f, minS, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0f, maxP, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0f, maxS, tolerance); CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), Index64(1 + sizeX * sizeY * sizeZ)); const FloatTree& treeS = gridS->tree(); const FloatTree& treeP = gridP->tree(); // Values in Test Domain are correct for (Coord ijk(bboxD.min()); ijk[0] <= bboxD.max()[0]; ++ijk[0]) { for (ijk[1] = bboxD.min()[1]; ijk[1] <= bboxD.max()[1]; ++ijk[1]) { for (ijk[2] = bboxD.min()[2]; ijk[2] <= bboxD.max()[2]; ++ijk[2]) { const float expected = bboxD.isInside(ijk) ? 1.0f : 0.0f; CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, 1.0f, tolerance); const float& vS = treeS.getValue(ijk); const float& vP = treeP.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); } } } CoordBBox bboxP = gridP->evalActiveVoxelBoundingBox(); const Index64 voxelCountP = gridP->activeVoxelCount(); //std::cerr << "\nParallel: bbox=" << bboxP << " voxels=" << voxelCountP << std::endl; CPPUNIT_ASSERT( bboxP != bboxD ); CPPUNIT_ASSERT( bboxP == CoordBBox(Coord(0,0,0), magicVoxel) ); CPPUNIT_ASSERT_EQUAL( dense.valueCount()+1, voxelCountP); CoordBBox bboxS = gridS->evalActiveVoxelBoundingBox(); const Index64 voxelCountS = gridS->activeVoxelCount(); //std::cerr << "\nSerial: bbox=" << bboxS << " voxels=" << voxelCountS << std::endl; CPPUNIT_ASSERT( bboxS != bboxD ); CPPUNIT_ASSERT( bboxS == CoordBBox(Coord(0,0,0), magicVoxel) ); CPPUNIT_ASSERT_EQUAL( dense.valueCount()+1, voxelCountS); // Topology CPPUNIT_ASSERT( bboxS.isInside(bboxS) ); CPPUNIT_ASSERT( bboxP.isInside(bboxP) ); CPPUNIT_ASSERT( bboxS.isInside(bboxP) ); CPPUNIT_ASSERT( bboxP.isInside(bboxS) ); /// Check that the two grids agree for (Coord ijk(bboxS.min()); ijk[0] <= bboxS.max()[0]; ++ijk[0]) { for (ijk[1] = bboxS.min()[1]; ijk[1] <= bboxS.max()[1]; ++ijk[1]) { for (ijk[2] = bboxS.min()[2]; ijk[2] <= bboxS.max()[2]; ++ijk[2]) { const float& vS = treeS.getValue(ijk); const float& vP = treeP.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(vS, vP, tolerance); // the value we should get based on the original domain const float expected = bboxD.isInside(ijk) ? 1.0f : ijk == magicVoxel ? 5.0f : 0.0f; CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); } } } // Verify the tree topology matches. CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), gridS->activeVoxelCount()); CPPUNIT_ASSERT(gridP->evalActiveVoxelBoundingBox() == gridS->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT(treeP.hasSameTopology(treeS) ); } template void TestDense::testInvalidBBox() { using namespace openvdb; //std::cerr << "\nTesting testInvalidBBox with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef tools::Dense DenseT; const CoordBBox badBBox(Coord(1, 1, 1), Coord(-1, 2, 2)); CPPUNIT_ASSERT(badBBox.empty()); CPPUNIT_ASSERT_THROW(DenseT dense(badBBox), ValueError); } template void TestDense::testDense2Sparse2Dense() { using namespace openvdb; //std::cerr << "\nTesting testDense2Sparse2Dense with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef tools::Dense DenseT; const CoordBBox bboxBig(Coord(-12, 7, -32), Coord(12, 14, -15)); const CoordBBox bboxSmall(Coord(-10, 8, -31), Coord(10, 12, -20)); // A larger bbox CoordBBox bboxBigger = bboxBig; bboxBigger.expand(Coord(10)); // Small is in big CPPUNIT_ASSERT(bboxBig.isInside(bboxSmall)); // Big is in Bigger CPPUNIT_ASSERT(bboxBigger.isInside(bboxBig)); // Construct a small dense grid DenseT denseSmall(bboxSmall, 0.f); { // insert non-const values const int n = static_cast(denseSmall.valueCount()); float* d = denseSmall.data(); for (int i = 0; i < n; ++i) { d[i] = static_cast(i); } } // Construct large dense grid DenseT denseBig(bboxBig, 0.f); { // insert non-const values const int n = static_cast(denseBig.valueCount()); float* d = denseBig.data(); for (int i = 0; i < n; ++i) { d[i] = static_cast(i); } } // Make a sparse grid to copy this data into FloatGrid::Ptr grid = FloatGrid::create(3.3f /*background*/); tools::copyFromDense(denseBig, *grid, /*tolerance*/0.0f, /*serial = */ true); tools::copyFromDense(denseSmall, *grid, /*tolerance*/0.0f, /*serial = */ false); const FloatTree& tree = grid->tree(); // CPPUNIT_ASSERT_EQUAL(bboxBig.volume(), grid->activeVoxelCount()); // iterate over the Bigger for (Coord ijk(bboxBigger.min()); ijk[0] <= bboxBigger.max()[0]; ++ijk[0]) { for (ijk[1] = bboxBigger.min()[1]; ijk[1] <= bboxBigger.max()[1]; ++ijk[1]) { for (ijk[2] = bboxBigger.min()[2]; ijk[2] <= bboxBigger.max()[2]; ++ijk[2]) { float expected = 3.3f; if (bboxSmall.isInside(ijk)) { expected = denseSmall.getValue(ijk); } else if (bboxBig.isInside(ijk)) { expected = denseBig.getValue(ijk); } const float& value = tree.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, value, 0.0001); } } } // Convert to Dense in small bbox { DenseT denseSmall2(bboxSmall); tools::copyToDense(*grid, denseSmall2, true /* serial */); // iterate over the Bigger for (Coord ijk(bboxSmall.min()); ijk[0] <= bboxSmall.max()[0]; ++ijk[0]) { for (ijk[1] = bboxSmall.min()[1]; ijk[1] <= bboxSmall.max()[1]; ++ijk[1]) { for (ijk[2] = bboxSmall.min()[2]; ijk[2] <= bboxSmall.max()[2]; ++ijk[2]) { const float& expected = denseSmall.getValue(ijk); const float& value = denseSmall2.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, value, 0.0001); } } } } // Convert to Dense in large bbox { DenseT denseBig2(bboxBig); tools::copyToDense(*grid, denseBig2, false /* serial */); // iterate over the Bigger for (Coord ijk(bboxBig.min()); ijk[0] <= bboxBig.max()[0]; ++ijk[0]) { for (ijk[1] = bboxBig.min()[1]; ijk[1] <= bboxBig.max()[1]; ++ijk[1]) { for (ijk[2] = bboxBig.min()[2]; ijk[2] <= bboxBig.max()[2]; ++ijk[2]) { float expected = -1.f; // should never be this if (bboxSmall.isInside(ijk)) { expected = denseSmall.getValue(ijk); } else if (bboxBig.isInside(ijk)) { expected = denseBig.getValue(ijk); } const float& value = denseBig2.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, value, 0.0001); } } } } } #undef BENCHMARK_TEST // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLeafIO.cc0000644000000000000000000001445212603226506015346 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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] = static_cast(::toupper(name[0])); return "TestLeafIO" + name; } CPPUNIT_TEST_SUITE(TestLeafIO); CPPUNIT_TEST(testBuffer); CPPUNIT_TEST_SUITE_END(); void testBuffer(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); template void TestLeafIO::testBuffer() { openvdb::tree::LeafNode leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(0, 1, 0), T(1)); leaf.setValueOn(openvdb::Coord(1, 0, 0), T(1)); std::ostringstream ostr(std::ios_base::binary); leaf.writeBuffers(ostr); leaf.setValueOn(openvdb::Coord(0, 1, 0), T(0)); leaf.setValueOn(openvdb::Coord(0, 1, 1), T(1)); std::istringstream istr(ostr.str(), std::ios_base::binary); // Since the input stream doesn't include a VDB header with file format version info, // tag the input stream explicitly with the current version number. openvdb::io::setCurrentVersion(istr); leaf.readBuffers(istr); CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1), leaf.getValue(openvdb::Coord(0, 1, 0)), /*tolerance=*/0); CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1), leaf.getValue(openvdb::Coord(1, 0, 0)), /*tolerance=*/0); CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); } template<> void TestLeafIO::testBuffer() { openvdb::tree::LeafNode leaf(openvdb::Coord(0, 0, 0), std::string()); leaf.setValueOn(openvdb::Coord(0, 1, 0), std::string("test")); leaf.setValueOn(openvdb::Coord(1, 0, 0), std::string("test")); std::ostringstream ostr(std::ios_base::binary); leaf.writeBuffers(ostr); leaf.setValueOn(openvdb::Coord(0, 1, 0), std::string("douche")); leaf.setValueOn(openvdb::Coord(0, 1, 1), std::string("douche")); std::istringstream istr(ostr.str(), std::ios_base::binary); // Since the input stream doesn't include a VDB header with file format version info, // tag the input stream explicitly with the current version number. openvdb::io::setCurrentVersion(istr); leaf.readBuffers(istr); CPPUNIT_ASSERT_EQUAL(std::string("test"), leaf.getValue(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT_EQUAL(std::string("test"), leaf.getValue(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); } template<> void TestLeafIO::testBuffer() { openvdb::tree::LeafNode leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(0, 1, 0), openvdb::Vec3R(1, 1, 1)); leaf.setValueOn(openvdb::Coord(1, 0, 0), openvdb::Vec3R(1, 1, 1)); std::ostringstream ostr(std::ios_base::binary); leaf.writeBuffers(ostr); leaf.setValueOn(openvdb::Coord(0, 1, 0), openvdb::Vec3R(0, 0, 0)); leaf.setValueOn(openvdb::Coord(0, 1, 1), openvdb::Vec3R(1, 1, 1)); std::istringstream istr(ostr.str(), std::ios_base::binary); // Since the input stream doesn't include a VDB header with file format version info, // tag the input stream explicitly with the current version number. openvdb::io::setCurrentVersion(istr); leaf.readBuffers(istr); CPPUNIT_ASSERT(leaf.getValue(openvdb::Coord(0, 1, 0)) == openvdb::Vec3R(1, 1, 1)); CPPUNIT_ASSERT(leaf.getValue(openvdb::Coord(1, 0, 0)) == openvdb::Vec3R(1, 1, 1)); CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestCoord.cc0000644000000000000000000002157712603226506015323 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestInt64Metadata.cc0000644000000000000000000000535512603226506016616 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestDivergence.cc0000644000000000000000000006364012603226506016325 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); namespace { const int GRID_DIM = 10; } class TestDivergence: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestDivergence); CPPUNIT_TEST(testISDivergence); // Divergence in Index Space CPPUNIT_TEST(testISDivergenceStencil); CPPUNIT_TEST(testWSDivergence); // Divergence in World Space CPPUNIT_TEST(testWSDivergenceStencil); CPPUNIT_TEST(testDivergenceTool); // Divergence tool CPPUNIT_TEST(testDivergenceMaskedTool); // Divergence tool CPPUNIT_TEST(testStaggeredDivergence); CPPUNIT_TEST_SUITE_END(); void testISDivergence(); void testISDivergenceStencil(); void testWSDivergence(); void testWSDivergenceStencil(); void testDivergenceTool(); void testDivergenceMaskedTool(); void testStaggeredDivergence(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestDivergence); void TestDivergence::testDivergenceTool() { using namespace openvdb; VectorGrid::Ptr inGrid = VectorGrid::create(); VectorTree& inTree = inGrid->tree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; xactiveVoxelCount())); FloatGrid::ConstAccessor accessor = divGrid->getConstAccessor(); --dim;//ignore boundary divergence for (int x = -dim; xtree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; xfill(maskBBox, true /*value*/, true /*activate*/); FloatGrid::Ptr divGrid = tools::divergence(*inGrid, *maskGrid); CPPUNIT_ASSERT_EQUAL(math::Pow3(dim), int(divGrid->activeVoxelCount())); FloatGrid::ConstAccessor accessor = divGrid->getConstAccessor(); --dim;//ignore boundary divergence for (int x = -dim; xsetGridClass( GRID_STAGGERED ); VectorTree& inTree = inGrid->tree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; xactiveVoxelCount())); FloatGrid::ConstAccessor accessor = divGrid->getConstAccessor(); --dim;//ignore boundary divergence for (int x = -dim; xtree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; xgetConstAccessor(); CPPUNIT_ASSERT(!inTree.empty()); CPPUNIT_ASSERT_EQUAL(math::Pow3(2*dim), int(inTree.activeVoxelCount())); --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); } } } --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); } } } --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); d = math::ISDivergence::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); } } } } void TestDivergence::testISDivergenceStencil() { using namespace openvdb; VectorGrid::Ptr inGrid = VectorGrid::create(); VectorTree& inTree = inGrid->tree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; x sevenpt(*inGrid); math::ThirteenPointStencil thirteenpt(*inGrid); math::NineteenPointStencil nineteenpt(*inGrid); --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); } } } --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(thirteenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(thirteenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(thirteenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); } } } --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(nineteenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(nineteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); d = math::ISDivergence::result(nineteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); } } } } void TestDivergence::testWSDivergence() { using namespace openvdb; typedef VectorGrid::ConstAccessor Accessor; { // non-unit voxel size double voxel_size = 0.5; VectorGrid::Ptr inGrid = VectorGrid::create(); inGrid->setTransform(math::Transform::createLinearTransform(voxel_size)); VectorTree& inTree = inGrid->tree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; xindexToWorld(Vec3d(x,y,z)); inTree.setValue(Coord(x,y,z), VectorTree::ValueType(float(location.x()), float(location.y()), 0.f)); } } } Accessor inAccessor = inGrid->getConstAccessor(); CPPUNIT_ASSERT(!inTree.empty()); CPPUNIT_ASSERT_EQUAL(math::Pow3(2*dim), int(inTree.activeVoxelCount())); --dim;//ignore boundary divergence // test with a map // test with a map math::AffineMap map(voxel_size*math::Mat3d::identity()); math::UniformScaleMap uniform_map(voxel_size); math::UniformScaleTranslateMap uniform_translate_map(voxel_size, Vec3d(0,0,0)); for (int x = -dim; x #include #include #include #include #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))); } } {// test Buffer::data() LeafType leaf(openvdb::Coord(0, 0, 0), /*background=*/false); leaf.fill(true); LeafType::Buffer::WordType* w = leaf.buffer().data(); for (openvdb::Index n = 0; n < LeafType::Buffer::WORD_COUNT; ++n) { CPPUNIT_ASSERT_EQUAL(~LeafType::Buffer::WordType(0), w[n]); } } {// test const Buffer::data() LeafType leaf(openvdb::Coord(0, 0, 0), /*background=*/false); leaf.fill(true); const LeafType& cleaf = leaf; const LeafType::Buffer::WordType* w = cleaf.buffer().data(); for (openvdb::Index n = 0; n < LeafType::Buffer::WORD_COUNT; ++n) { CPPUNIT_ASSERT_EQUAL(~LeafType::Buffer::WordType(0), w[n]); } } } void TestLeafBool::testSetValue() { LeafType leaf(openvdb::Coord(0, 0, 0), false); openvdb::Coord xyz(0, 0, 0); CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); leaf.setValueOn(xyz); CPPUNIT_ASSERT(leaf.isValueOn(xyz)); xyz.reset(7, 7, 7); CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); leaf.setValueOn(xyz); CPPUNIT_ASSERT(leaf.isValueOn(xyz)); leaf.setValueOn(xyz, /*value=*/true); // value argument should be ignored CPPUNIT_ASSERT(leaf.isValueOn(xyz)); leaf.setValueOn(xyz, /*value=*/false); // value argument should be ignored CPPUNIT_ASSERT(leaf.isValueOn(xyz)); leaf.setValueOff(xyz); CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); xyz.reset(2, 3, 6); leaf.setValueOn(xyz); CPPUNIT_ASSERT(leaf.isValueOn(xyz)); leaf.setValueOff(xyz); CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); } void TestLeafBool::testProbeValue() { LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(1, 6, 5)); bool val; CPPUNIT_ASSERT(leaf.probeValue(openvdb::Coord(1, 6, 5), val)); CPPUNIT_ASSERT(!leaf.probeValue(openvdb::Coord(1, 6, 4), val)); } void TestLeafBool::testIterators() { LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(1, 2, 3)); leaf.setValueOn(openvdb::Coord(5, 2, 3)); openvdb::Coord sum; for (LeafType::ValueOnIter iter = leaf.beginValueOn(); iter; ++iter) { sum += iter.getCoord(); } CPPUNIT_ASSERT_EQUAL(openvdb::Coord(1 + 5, 2 + 2, 3 + 3), sum); openvdb::Index count = 0; for (LeafType::ValueOffIter iter = leaf.beginValueOff(); iter; ++iter, ++count); CPPUNIT_ASSERT_EQUAL(leaf.numValues() - 2, count); count = 0; for (LeafType::ValueAllIter iter = leaf.beginValueAll(); iter; ++iter, ++count); CPPUNIT_ASSERT_EQUAL(leaf.numValues(), count); count = 0; for (LeafType::ChildOnIter iter = leaf.beginChildOn(); iter; ++iter, ++count); CPPUNIT_ASSERT_EQUAL(openvdb::Index(0), count); count = 0; for (LeafType::ChildOffIter iter = leaf.beginChildOff(); iter; ++iter, ++count); CPPUNIT_ASSERT_EQUAL(openvdb::Index(0), count); count = 0; for (LeafType::ChildAllIter iter = leaf.beginChildAll(); iter; ++iter, ++count); CPPUNIT_ASSERT_EQUAL(leaf.numValues(), count); } void TestLeafBool::testIteratorGetCoord() { using namespace openvdb; LeafType leaf(openvdb::Coord(8, 8, 0)); CPPUNIT_ASSERT_EQUAL(Coord(8, 8, 0), leaf.origin()); leaf.setValueOn(Coord(1, 2, 3), -3); leaf.setValueOn(Coord(5, 2, 3), 4); LeafType::ValueOnIter iter = leaf.beginValueOn(); Coord xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(9, 10, 3), xyz); ++iter; xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(13, 10, 3), xyz); } void TestLeafBool::testEquivalence() { using openvdb::CoordBBox; using openvdb::Coord; LeafType leaf(Coord(0, 0, 0), false); // false and inactive LeafType leaf2(Coord(0, 0, 0), true); // true and inactive CPPUNIT_ASSERT(leaf != leaf2); leaf.fill(CoordBBox(Coord(0), Coord(LeafType::DIM - 1)), true, /*active=*/false); CPPUNIT_ASSERT(leaf == leaf2); // true and inactive leaf.setValuesOn(); // true and active leaf2.fill(CoordBBox(Coord(0), Coord(LeafType::DIM - 1)), false); // false and active CPPUNIT_ASSERT(leaf != leaf2); leaf.negate(); // false and active CPPUNIT_ASSERT(leaf == leaf2); // Set some values. leaf.setValueOn(Coord(0, 0, 0), true); leaf.setValueOn(Coord(0, 1, 0), true); leaf.setValueOn(Coord(1, 1, 0), true); leaf.setValueOn(Coord(1, 1, 2), true); leaf2.setValueOn(Coord(0, 0, 0), true); leaf2.setValueOn(Coord(0, 1, 0), true); leaf2.setValueOn(Coord(1, 1, 0), true); leaf2.setValueOn(Coord(1, 1, 2), true); CPPUNIT_ASSERT(leaf == leaf2); leaf2.setValueOn(Coord(0, 0, 1), true); CPPUNIT_ASSERT(leaf != leaf2); leaf2.setValueOff(Coord(0, 0, 1), false); CPPUNIT_ASSERT(leaf != leaf2); leaf2.setValueOn(Coord(0, 0, 1)); CPPUNIT_ASSERT(leaf == leaf2); } void TestLeafBool::testGetOrigin() { { LeafType leaf(openvdb::Coord(1, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(0, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(8, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(8, 1, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(1024, 1, 3), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(128*8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(1023, 1, 3), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(127*8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(512, 512, 512), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(512, 512, 512), leaf.origin()); } { LeafType leaf(openvdb::Coord(2, 52, 515), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 48, 512), leaf.origin()); } } void TestLeafBool::testNegativeIndexing() { using namespace openvdb; LeafType leaf(openvdb::Coord(-9, -2, -8)); CPPUNIT_ASSERT_EQUAL(Coord(-16, -8, -8), leaf.origin()); leaf.setValueOn(Coord(1, 2, 3)); leaf.setValueOn(Coord(5, 2, 3)); CPPUNIT_ASSERT(leaf.isValueOn(Coord(1, 2, 3))); CPPUNIT_ASSERT(leaf.isValueOn(Coord(5, 2, 3))); LeafType::ValueOnIter iter = leaf.beginValueOn(); Coord xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(-15, -6, -5), xyz); ++iter; xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(-11, -6, -5), xyz); } void TestLeafBool::testIO() { LeafType leaf(openvdb::Coord(1, 3, 5)); const openvdb::Coord origin = leaf.origin(); leaf.setValueOn(openvdb::Coord(0, 1, 0)); leaf.setValueOn(openvdb::Coord(1, 0, 0)); std::ostringstream ostr(std::ios_base::binary); leaf.writeBuffers(ostr); leaf.setValueOff(openvdb::Coord(0, 1, 0)); leaf.setValueOn(openvdb::Coord(0, 1, 1)); std::istringstream istr(ostr.str(), std::ios_base::binary); // Since the input stream doesn't include a VDB header with file format version info, // tag the input stream explicitly with the current version number. openvdb::io::setCurrentVersion(istr); leaf.readBuffers(istr); CPPUNIT_ASSERT_EQUAL(origin, leaf.origin()); CPPUNIT_ASSERT(leaf.isValueOn(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT(leaf.isValueOn(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); } void TestLeafBool::testTopologyCopy() { using openvdb::Coord; // LeafNode having the same Log2Dim as LeafType typedef LeafType::ValueConverter::Type FloatLeafType; FloatLeafType fleaf(Coord(10, 20, 30), /*background=*/-1.0); std::set coords; for (openvdb::Index n = 0; n < fleaf.numValues(); n += 10) { Coord xyz = fleaf.offsetToGlobalCoord(n); fleaf.setValueOn(xyz, float(n)); coords.insert(xyz); } LeafType leaf(fleaf, openvdb::TopologyCopy()); CPPUNIT_ASSERT_EQUAL(fleaf.onVoxelCount(), leaf.onVoxelCount()); CPPUNIT_ASSERT(leaf.hasSameTopology(&fleaf)); for (LeafType::ValueOnIter iter = leaf.beginValueOn(); iter; ++iter) { coords.erase(iter.getCoord()); } CPPUNIT_ASSERT(coords.empty()); } void TestLeafBool::testMerge() { LeafType leaf(openvdb::Coord(0, 0, 0)); for (openvdb::Index n = 0; n < leaf.numValues(); n += 10) { leaf.setValueOn(n); } CPPUNIT_ASSERT(!leaf.isValueMaskOn()); CPPUNIT_ASSERT(!leaf.isValueMaskOff()); bool val = false, active = false; CPPUNIT_ASSERT(!leaf.isConstant(val, active)); LeafType leaf2(leaf); leaf2.getValueMask().toggle(); CPPUNIT_ASSERT(!leaf2.isValueMaskOn()); CPPUNIT_ASSERT(!leaf2.isValueMaskOff()); val = active = false; CPPUNIT_ASSERT(!leaf2.isConstant(val, active)); leaf.merge(leaf2); CPPUNIT_ASSERT(leaf.isValueMaskOn()); CPPUNIT_ASSERT(!leaf.isValueMaskOff()); val = active = false; CPPUNIT_ASSERT(leaf.isConstant(val, active)); CPPUNIT_ASSERT(active); } void TestLeafBool::testCombine() { struct Local { static void op(openvdb::CombineArgs& args) { args.setResult(false); // result should be ignored args.setResultIsActive(args.aIsActive() ^ args.bIsActive()); } }; LeafType leaf(openvdb::Coord(0, 0, 0)); for (openvdb::Index n = 0; n < leaf.numValues(); n += 10) { leaf.setValueOn(n); } CPPUNIT_ASSERT(!leaf.isValueMaskOn()); CPPUNIT_ASSERT(!leaf.isValueMaskOff()); const LeafType::NodeMaskType savedMask = leaf.getValueMask(); OPENVDB_LOG_DEBUG_RUNTIME(leaf.str()); LeafType leaf2(leaf); for (openvdb::Index n = 0; n < leaf.numValues(); n += 4) { leaf2.setValueOn(n); } CPPUNIT_ASSERT(!leaf2.isValueMaskOn()); CPPUNIT_ASSERT(!leaf2.isValueMaskOff()); OPENVDB_LOG_DEBUG_RUNTIME(leaf2.str()); leaf.combine(leaf2, Local::op); OPENVDB_LOG_DEBUG_RUNTIME(leaf.str()); CPPUNIT_ASSERT(leaf.getValueMask() == (savedMask ^ leaf2.getValueMask())); } void TestLeafBool::testBoolTree() { using namespace openvdb; #if 0 FloatGrid::Ptr inGrid; FloatTree::Ptr inTree; { //io::File vdbFile("/work/rd/fx_tools/vdb_unittest/TestGridCombine::testCsg/large1.vdb2"); io::File vdbFile("/hosts/whitestar/usr/pic1/VDB/bunny_0256.vdb2"); vdbFile.open(); inGrid = gridPtrCast(vdbFile.readGrid("LevelSet")); CPPUNIT_ASSERT(inGrid.get() != NULL); inTree = inGrid->treePtr(); CPPUNIT_ASSERT(inTree.get() != NULL); } #else FloatGrid::Ptr inGrid = FloatGrid::create(); CPPUNIT_ASSERT(inGrid.get() != NULL); FloatTree& inTree = inGrid->tree(); inGrid->setName("LevelSet"); unittest_util::makeSphere(/*dim =*/Coord(128), /*center=*/Vec3f(0, 0, 0), /*radius=*/5, *inGrid, unittest_util::SPHERE_DENSE); #endif const Index64 floatTreeMem = inTree.memUsage(), floatTreeLeafCount = inTree.leafCount(), floatTreeVoxelCount = inTree.activeVoxelCount(); TreeBase::Ptr outTree(new BoolTree(inTree, false, true, TopologyCopy())); CPPUNIT_ASSERT(outTree.get() != NULL); BoolGrid::Ptr outGrid = BoolGrid::create(*inGrid); // copy transform and metadata outGrid->setTree(outTree); outGrid->setName("Boolean"); const Index64 boolTreeMem = outTree->memUsage(), boolTreeLeafCount = outTree->leafCount(), boolTreeVoxelCount = outTree->activeVoxelCount(); #if 0 GridPtrVec grids; grids.push_back(inGrid); grids.push_back(outGrid); io::File vdbFile("bool_tree.vdb2"); vdbFile.write(grids); vdbFile.close(); #endif CPPUNIT_ASSERT_EQUAL(floatTreeLeafCount, boolTreeLeafCount); CPPUNIT_ASSERT_EQUAL(floatTreeVoxelCount, boolTreeVoxelCount); //std::cerr << "\nboolTree mem=" << boolTreeMem << " bytes" << std::endl; //std::cerr << "floatTree mem=" << floatTreeMem << " bytes" << std::endl; // Considering only voxel buffer memory usage, the BoolTree would be expected // to use (2 mask bits/voxel / ((32 value bits + 1 mask bit)/voxel)) = ~1/16 // as much memory as the FloatTree. Considering total memory usage, verify that // the BoolTree is no more than 1/10 the size of the FloatTree. CPPUNIT_ASSERT(boolTreeMem * 10 <= floatTreeMem); } // void // TestLeafBool::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("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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestNodeIterator.cc0000644000000000000000000003370012603226506016643 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMeshToVolume.cc0000644000000000000000000001354612603226506016641 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include class TestMeshToVolume: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestMeshToVolume); CPPUNIT_TEST(testUtils); CPPUNIT_TEST(testConversion); CPPUNIT_TEST(testCreateLevelSetBox); CPPUNIT_TEST_SUITE_END(); void testUtils(); void testConversion(); void testCreateLevelSetBox(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMeshToVolume); //////////////////////////////////////// void TestMeshToVolume::testUtils() { /// Test nearestCoord openvdb::Vec3d xyz(0.7, 2.2, -2.7); openvdb::Coord ijk = openvdb::util::nearestCoord(xyz); CPPUNIT_ASSERT(ijk[0] == 0 && ijk[1] == 2 && ijk[2] == -3); xyz = openvdb::Vec3d(-22.1, 4.6, 202.34); ijk = openvdb::util::nearestCoord(xyz); CPPUNIT_ASSERT(ijk[0] == -23 && ijk[1] == 4 && ijk[2] == 202); /// Test the coordinate offset table for neghbouring voxels openvdb::Coord sum(0, 0, 0); unsigned int pX = 0, pY = 0, pZ = 0, mX = 0, mY = 0, mZ = 0; for (unsigned int i = 0; i < 26; ++i) { ijk = openvdb::util::COORD_OFFSETS[i]; sum += ijk; if (ijk[0] == 1) ++pX; else if (ijk[0] == -1) ++mX; if (ijk[1] == 1) ++pY; else if (ijk[1] == -1) ++mY; if (ijk[2] == 1) ++pZ; else if (ijk[2] == -1) ++mZ; } CPPUNIT_ASSERT(sum == openvdb::Coord(0, 0, 0)); CPPUNIT_ASSERT( pX == 9); CPPUNIT_ASSERT( pY == 9); CPPUNIT_ASSERT( pZ == 9); CPPUNIT_ASSERT( mX == 9); CPPUNIT_ASSERT( mY == 9); CPPUNIT_ASSERT( mZ == 9); } void TestMeshToVolume::testConversion() { using namespace openvdb; std::vector points; std::vector quads; // cube vertices points.push_back(Vec3s(2, 2, 2)); // 0 6--------7 points.push_back(Vec3s(5, 2, 2)); // 1 /| /| points.push_back(Vec3s(2, 5, 2)); // 2 2--------3 | points.push_back(Vec3s(5, 5, 2)); // 3 | | | | points.push_back(Vec3s(2, 2, 5)); // 4 | 4------|-5 points.push_back(Vec3s(5, 2, 5)); // 5 |/ |/ points.push_back(Vec3s(2, 5, 5)); // 6 0--------1 points.push_back(Vec3s(5, 5, 5)); // 7 // cube faces quads.push_back(Vec4I(0, 1, 3, 2)); // front quads.push_back(Vec4I(5, 4, 6, 7)); // back quads.push_back(Vec4I(0, 2, 6, 4)); // left quads.push_back(Vec4I(1, 5, 7, 3)); // right quads.push_back(Vec4I(2, 3, 7, 6)); // top quads.push_back(Vec4I(0, 4, 5, 1)); // bottom math::Transform::Ptr xform = math::Transform::createLinearTransform(); tools::QuadAndTriangleDataAdapter mesh(points, quads); FloatGrid::Ptr grid = tools::meshToVolume(mesh, *xform); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(int(GRID_LEVEL_SET), int(grid->getGridClass())); CPPUNIT_ASSERT_EQUAL(1, int(grid->baseTree().leafCount())); grid = tools::meshToLevelSet(*xform, points, quads); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(int(GRID_LEVEL_SET), int(grid->getGridClass())); CPPUNIT_ASSERT_EQUAL(1, int(grid->baseTree().leafCount())); } void TestMeshToVolume::testCreateLevelSetBox() { typedef openvdb::FloatGrid FloatGrid; typedef openvdb::Vec3s Vec3s; typedef openvdb::math::BBox BBoxs; typedef openvdb::math::Transform Transform; BBoxs bbox(Vec3s(0.0, 0.0, 0.0), Vec3s(1.0, 1.0, 1.0)); Transform::Ptr transform = Transform::createLinearTransform(0.1); FloatGrid::Ptr grid = openvdb::tools::createLevelSetBox(bbox, *transform); CPPUNIT_ASSERT(grid->tree().leafCount() > 0); // test inside coord value openvdb::Coord ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(0.5, 0.5, 0.5)); CPPUNIT_ASSERT(grid->tree().getValue(ijk) < 0.0f); // test outside coord value ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(1.5, 1.5, 1.5)); CPPUNIT_ASSERT(grid->tree().getValue(ijk) > 0.0f); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMeanCurvature.cc0000644000000000000000000007152412603226506017033 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestPoissonSolver.cc0000644000000000000000000005276312603226506017103 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include //#include // for math::isApproxEqual() #include // for JacobiPreconditioner #include // for csgDifference/Union/Intersection #include // for tools::createLevelSetSphere() #include // for tools::sdfToFogVolume() #include // for createLevelSetBox() #include // for tools::erodeVoxels() #include #include // for boost::math::constants::pi #include /// @authors D.J. Hill, Peter Cucka class TestPoissonSolver: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestPoissonSolver); CPPUNIT_TEST(testIndexTree); CPPUNIT_TEST(testTreeToVectorToTree); CPPUNIT_TEST(testLaplacian); CPPUNIT_TEST(testSolve); CPPUNIT_TEST(testSolveWithBoundaryConditions); CPPUNIT_TEST(testSolveWithSegmentDomain); CPPUNIT_TEST_SUITE_END(); void testIndexTree(); void testTreeToVectorToTree(); void testLaplacian(); void testSolve(); void testSolveWithBoundaryConditions(); void testSolveWithSegmentDomain(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestPoissonSolver); //////////////////////////////////////// void TestPoissonSolver::testIndexTree() { using namespace openvdb; using tools::poisson::VIndex; typedef FloatTree::ValueConverter::Type VIdxTree; typedef VIdxTree::LeafNodeType LeafNodeType; VIdxTree tree; /// @todo populate tree tree::LeafManager leafManager(tree); VIndex testOffset = 0; for (size_t n = 0, N = leafManager.leafCount(); n < N; ++n) { const LeafNodeType& leaf = leafManager.leaf(n); for (LeafNodeType::ValueOnCIter it = leaf.cbeginValueOn(); it; ++it, testOffset++) { CPPUNIT_ASSERT_EQUAL(testOffset, *it); } } //if (testOffset != VIndex(tree.activeVoxelCount())) { // std::cout << "--Testing offsetmap - " // << testOffset<<" != " // << tree.activeVoxelCount() // << " has active tile count " // << tree.activeTileCount()<::Type VIdxTree; FloatGrid::Ptr sphere = tools::createLevelSetSphere( /*radius=*/10.f, /*center=*/Vec3f(0.f), /*voxelSize=*/0.25f); tools::sdfToFogVolume(*sphere); FloatTree& inputTree = sphere->tree(); const Index64 numVoxels = inputTree.activeVoxelCount(); // Generate an index tree. VIdxTree::Ptr indexTree = tools::poisson::createIndexTree(inputTree); CPPUNIT_ASSERT(bool(indexTree)); // Copy the values of the active voxels of the tree into a vector. math::pcg::VectorS::Ptr vec = tools::poisson::createVectorFromTree(inputTree, *indexTree); CPPUNIT_ASSERT_EQUAL(math::pcg::SizeType(numVoxels), vec->size()); { // Convert the vector back to a tree. FloatTree::Ptr inputTreeCopy = tools::poisson::createTreeFromVector( *vec, *indexTree, /*bg=*/0.f); // Check that voxel values were preserved. FloatGrid::ConstAccessor inputAcc = sphere->getConstAccessor(); for (FloatTree::ValueOnCIter it = inputTreeCopy->cbeginValueOn(); it; ++it) { const Coord ijk = it.getCoord(); //if (!math::isApproxEqual(*it, inputTree.getValue(ijk))) { // std::cout << " value error " << *it << " " // << inputTree.getValue(ijk) << std::endl; //} CPPUNIT_ASSERT_DOUBLES_EQUAL(inputAcc.getValue(ijk), *it, /*tolerance=*/1.0e-6); } } } void TestPoissonSolver::testLaplacian() { using namespace openvdb; using tools::poisson::VIndex; typedef FloatTree::ValueConverter::Type VIdxTree; // For two different problem sizes, N = 8 and N = 20... for (int N = 8; N <= 20; N += 12) { // Construct an N x N x N volume in which the value of voxel (i, j, k) // is sin(i) * sin(j) * sin(k), using a voxel spacing of pi / N. const double delta = boost::math::constants::pi() / N; FloatTree inputTree(/*background=*/0.f); Coord ijk(0); Int32 &i = ijk[0], &j = ijk[1], &k = ijk[2]; for (i = 1; i < N; ++i) { for (j = 1; j < N; ++j) { for (k = 1; k < N; ++k) { inputTree.setValue(ijk, static_cast( std::sin(delta * i) * std::sin(delta * j) * std::sin(delta * k))); } } } const Index64 numVoxels = inputTree.activeVoxelCount(); // Generate an index tree. VIdxTree::Ptr indexTree = tools::poisson::createIndexTree(inputTree); CPPUNIT_ASSERT(bool(indexTree)); // Copy the values of the active voxels of the tree into a vector. math::pcg::VectorS::Ptr source = tools::poisson::createVectorFromTree(inputTree, *indexTree); CPPUNIT_ASSERT_EQUAL(math::pcg::SizeType(numVoxels), source->size()); // Create a mask of the interior voxels of the source tree. BoolTree interiorMask(/*background=*/false); interiorMask.fill(CoordBBox(Coord(2), Coord(N-2)), /*value=*/true, /*active=*/true); // Compute the Laplacian of the source: // D^2 sin(i) * sin(j) * sin(k) = -3 sin(i) * sin(j) * sin(k) tools::poisson::LaplacianMatrix::Ptr laplacian = tools::poisson::createISLaplacian(*indexTree, interiorMask); laplacian->scale(1.0 / (delta * delta)); // account for voxel spacing CPPUNIT_ASSERT_EQUAL(math::pcg::SizeType(numVoxels), laplacian->size()); math::pcg::VectorS result(source->size()); laplacian->vectorMultiply(*source, result); // Dividing the result by the source should produce a vector of uniform value -3. // Due to finite differencing, the actual ratio will be somewhat different, though. const math::pcg::VectorS& src = *source; const float expected = // compute the expected ratio using one of the corner voxels float((3.0 * src[1] - 6.0 * src[0]) / (delta * delta * src[0])); for (math::pcg::SizeType n = 0; n < result.size(); ++n) { result[n] /= src[n]; CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, result[n], /*tolerance=*/1.0e-4); } } } void TestPoissonSolver::testSolve() { using namespace openvdb; FloatGrid::Ptr sphere = tools::createLevelSetSphere( /*radius=*/10.f, /*center=*/Vec3f(0.f), /*voxelSize=*/0.25f); tools::sdfToFogVolume(*sphere); math::pcg::State result = math::pcg::terminationDefaults(); result.iterations = 100; result.relativeError = result.absoluteError = 1.0e-4; FloatTree::Ptr outTree = tools::poisson::solve(sphere->tree(), result); CPPUNIT_ASSERT(result.success); CPPUNIT_ASSERT(result.iterations < 60); } //////////////////////////////////////// namespace { struct BoundaryOp { void operator()(const openvdb::Coord& ijk, const openvdb::Coord& neighbor, double& source, double& diagonal) const { if (neighbor.x() == ijk.x() && neighbor.z() == ijk.z()) { // Workaround for spurious GCC 4.8 -Wstrict-overflow warning: const openvdb::Coord::ValueType dy = (ijk.y() - neighbor.y()); if (dy > 0) source -= 1.0; else diagonal -= 1.0; } } }; template void doTestSolveWithBoundaryConditions() { using namespace openvdb; typedef typename TreeType::ValueType ValueType; // Solve for the pressure in a cubic tank of liquid that is open at the top. // Boundary conditions are P = 0 at the top, dP/dy = -1 at the bottom // and dP/dx = 0 at the sides. // // P = 0 // +------+ (N,-1,N) // /| /| // (0,-1,0) +------+ | // | | | | dP/dx = 0 // dP/dx = 0 | +----|-+ // |/ |/ // (0,-N-1,0) +------+ (N,-N-1,N) // dP/dy = -1 const int N = 9; const ValueType zero = zeroVal(); const double epsilon = math::Delta::value(); TreeType source(/*background=*/zero); source.fill(CoordBBox(Coord(0, -N-1, 0), Coord(N, -1, N)), /*value=*/zero); math::pcg::State state = math::pcg::terminationDefaults(); state.iterations = 100; state.relativeError = state.absoluteError = epsilon; util::NullInterrupter interrupter; typename TreeType::Ptr solution = tools::poisson::solveWithBoundaryConditions( source, BoundaryOp(), state, interrupter); CPPUNIT_ASSERT(state.success); CPPUNIT_ASSERT(state.iterations < 60); // Verify that P = -y throughout the solution space. for (typename TreeType::ValueOnCIter it = solution->cbeginValueOn(); it; ++it) { CPPUNIT_ASSERT_DOUBLES_EQUAL( double(-it.getCoord().y()), double(*it), /*tolerance=*/10.0 * epsilon); } } } // unnamed namespace void TestPoissonSolver::testSolveWithBoundaryConditions() { doTestSolveWithBoundaryConditions(); doTestSolveWithBoundaryConditions(); } namespace { openvdb::FloatGrid::Ptr generateCubeLS(const int outer_length, // in voxels const int inner_length, // in voxels const openvdb::Vec3I& centerIS, // in index space const float dx, // grid spacing bool open_top) { using namespace openvdb; typedef math::BBox BBox; // World space dimensions and center for this box const float outerWS = dx * float(outer_length); const float innerWS = dx * float(inner_length); Vec3f centerWS(centerIS); centerWS *= dx; // Construct world space bounding boxes BBox outerBBox(Vec3f(-outerWS /2 , -outerWS / 2, -outerWS /2), Vec3f(outerWS / 2, outerWS / 2, outerWS / 2)); BBox innerBBox; if ( open_top) { innerBBox = BBox(Vec3f(-innerWS /2 , -innerWS / 2, -innerWS /2), Vec3f(innerWS / 2, innerWS / 2, outerWS )); } else { innerBBox = BBox(Vec3f(-innerWS /2 , -innerWS / 2, -innerWS /2), Vec3f(innerWS / 2, innerWS / 2, innerWS / 2)); } outerBBox.translate(centerWS); innerBBox.translate(centerWS); math::Transform::Ptr xform = math::Transform::createLinearTransform(dx); FloatGrid::Ptr cubeLS = tools::createLevelSetBox( outerBBox, *xform); FloatGrid::Ptr inside = tools::createLevelSetBox( innerBBox, *xform); tools::csgDifference(*cubeLS, *inside); return cubeLS; } class LSBoundaryOp { public: LSBoundaryOp(const openvdb::FloatTree& lsTree) : mLS(&lsTree) {} LSBoundaryOp(const LSBoundaryOp& other) :mLS(other.mLS) {} void operator()(const openvdb::Coord& ijk, const openvdb::Coord& neighbor, double& source, double& diagonal) const { // Doing nothing is equivalent to imposing dP/dn = 0 boundary condition if (neighbor.x() == ijk.x() && neighbor.y() == ijk.y()) { // on top or bottom if (mLS->getValue(neighbor) <= 0.f) { // closed boundary source -= 1.0; } else { // open boundary diagonal -= 1.0; } } } private: const openvdb::FloatTree* mLS; }; }// unnamed namespace void TestPoissonSolver::testSolveWithSegmentDomain() { using namespace openvdb; typedef math::pcg::IncompleteCholeskyPreconditioner PreconditionerType; // In fluid simulations, incomprehensibility is enforced by the pressure, which in turn is computed as a solution of a Poisson equation. // Often procedural animation of objects (e.g. characters) interacting with liquid will result in boundary conditions that describe // multiple disjoint regions: regions of free surface flow and regions of of trapped fluid. It is this second type of region // for which there may be no consistent pressure (e.g. a shrinking water tight region filled with incompressible liquid). // This unittest demonstrates how to separate the well-posed problem of a liquid with a free surface from the possibly ill-posed // fully enclosed liquid regions by using a levelset and topological tools. // For simplicity sake, the physical boundaries are idealized as three non-overlapping cubes. // One with an open top, and two that are fully closed. // // All three containing incompressible liquid(x) and one of the closed cubes one will be partially filled so // that two of the liquid regions have a free surface (Dirichlet BC on one side) // while the totally filled cube would have no free surface (Neumann BCs on all sides). // // ________________ ________________ // __ __ | __________ | | __________ | // | |x x x x x | | | | | | | |x x x x x | | // | |x x x x x | | | |x x x x x | | | |x x x x x | | // | |x x x x x | | | |x x x x x | | | |x x x x x | | // | —————————— | | —————————— | | —————————— | // |________________| |________________| |________________| // // The first two regions are clearly well-posed while the third region may have no solution (or multiple solutions). // -D.J.Hill // Grid spacing const float dx = 0.05f; // Construct the solid boundaries in a single grid. FloatGrid::Ptr solidBoundary; { const int outer_dimension = 41; const int inner_dimension = 31; FloatGrid::Ptr openDomain = generateCubeLS(outer_dimension, inner_dimension, Vec3I(0, 0, 0) /*center*/, dx, true /*=open top*/); FloatGrid::Ptr closedDomain0 = generateCubeLS(outer_dimension, inner_dimension, Vec3I(60, 0, 0) /*center*/, dx, false /*=open top*/); FloatGrid::Ptr closedDomain1 = generateCubeLS(outer_dimension, inner_dimension, Vec3I(120, 0, 0) /*center*/, dx, false /*=open top*/); // // Union the cubes into the opencube grid tools::csgUnion(*openDomain, *closedDomain0); tools::csgUnion(*openDomain, *closedDomain1); // rename. solidBoundary = openDomain; // Strictly speaking the soulidBoundary level set should be rebuilt (tools::levelSetRebuild) // after csgUnions to insure a proper signed distance field but will will forgo it in this example } // Generate the source for the Poisson solver. // For a liquid simulation this will be the divergence of the velocity field and will coincide with the liquid location. // // We activate by hand cells in distinct solution regions. FloatTree source(0.f /*background*/); const int N = 15; // The source is active the union of the following "liquid" regions. // Fill the open box CoordBBox liquidInOpenDomain(Coord(-N, -N, -N), Coord(N, N, N)); source.fill(liquidInOpenDomain, 0.f); // Totally fill the closed box 0 CoordBBox liquidInClosedDomain0(Coord(-N, -N, -N), Coord(N, N, N)); liquidInClosedDomain0.translate(Coord(60, 0, 0)); source.fill(liquidInClosedDomain0, 0.f); // Half fill the closed box 1 CoordBBox liquidInClosedDomain1(Coord(-N, -N, -N), Coord(N, N, 0)); liquidInClosedDomain1.translate(Coord(120, 0, 0)); source.fill(liquidInClosedDomain1, 0.f); // The number of voxels in the part of the source that will correspond to the well posed region Index64 expectedWellPosedVolume = liquidInOpenDomain.volume() + liquidInClosedDomain1.volume(); // Generate a mask that defines the solution domain const BoolTree totalSourceDomain( source, false/*background*/, true/*active*/, TopologyCopy()); // Extract the "interior regions" from the solidBoundary. // The result will correspond to the the walls of the boxes union-ed with inside of the full box. BoolTree::Ptr interiorMask = tools::extractEnclosedRegion(solidBoundary->tree(), float(0) /*isovalue*/, &totalSourceDomain); // Identify the well-posed part of the problem BoolTree wellPosedDomain( source, false/*inactive*/, true/*active*/, TopologyCopy()); wellPosedDomain.topologyDifference(*interiorMask); CPPUNIT_ASSERT(wellPosedDomain.activeVoxelCount() == expectedWellPosedVolume ); // Solve well posed Poisson equation const double epsilon = math::Delta::value(); math::pcg::State state = math::pcg::terminationDefaults(); state.iterations = 200; state.relativeError = state.absoluteError = epsilon; util::NullInterrupter interrupter; // Boundary conditions that are consistent with solution = 0 at liquid air boundary // and a linear response with depth. LSBoundaryOp boundaryOp(solidBoundary->tree()); // Compute the solution FloatTree::Ptr wellPosedSolutionP = tools::poisson::solveWithBoundaryConditionsAndPreconditioner(source, wellPosedDomain, boundaryOp, state, interrupter); CPPUNIT_ASSERT(wellPosedSolutionP->activeVoxelCount() == expectedWellPosedVolume ); CPPUNIT_ASSERT(state.success); CPPUNIT_ASSERT(state.iterations < 68); // Verify that solution is linear with depth. for (FloatTree::ValueOnCIter it = wellPosedSolutionP->cbeginValueOn(); it; ++it) { Index32 depth; if (liquidInOpenDomain.isInside(it.getCoord())) { depth = 1 + liquidInOpenDomain.max().z() - it.getCoord().z(); } else { depth = 1 + liquidInClosedDomain1.max().z() - it.getCoord().z(); } CPPUNIT_ASSERT_DOUBLES_EQUAL(double(depth), double(*it), /*tolerance=*/10.0 * epsilon); } #if 0 // optionally, one could attempt to compute the solution in the enclosed regions. { // Identify the potentially ill-posed part of the problem BoolTree illPosedDomain( source, false/*inactive*/, true/*active*/, TopologyCopy()); illPosedDomain.topologyIntersection(source); // Solve for Poisson equation in the two unconnected regions FloatTree::Ptr illPosedSoln = tools::poisson::solveWithBoundaryConditionsAndPreconditioner(source, illPosedDomain, LSBoundaryOp(*solidBoundary->tree()), state, interrupt); } #endif } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLevelSetRayIntersector.cc0000644000000000000000000003711512603226506020671 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #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("sphere_serial"); stats.print("First hit"); hist.print("First hit"); } } #endif // STATS_TEST #undef STATS_TEST // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMetadataIO.cc0000644000000000000000000001560312603226506016216 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include // CPPUNIT_TEST_SUITE() invokes CPPUNIT_TESTNAMER_DECL() to generate a suite name // from the FixtureType. But if FixtureType is a templated type, the generated name // can become long and messy. This macro overrides the normal naming logic, // instead invoking FixtureType::testSuiteName(), which should be a static member // function that returns a std::string containing the suite name for the specific // template instantiation. #undef CPPUNIT_TESTNAMER_DECL #define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ CPPUNIT_NS::TestNamer variableName( FixtureType::testSuiteName() ) template class TestMetadataIO: public CppUnit::TestCase { public: static std::string testSuiteName() { std::string name = openvdb::typeNameAsString(); if (!name.empty()) name[0] = static_cast(::toupper(name[0])); return "TestMetadataIO" + name; } CPPUNIT_TEST_SUITE(TestMetadataIO); CPPUNIT_TEST(test); CPPUNIT_TEST(testMultiple); CPPUNIT_TEST_SUITE_END(); void test(); void testMultiple(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); template void TestMetadataIO::test() { using namespace openvdb; TypedMetadata m(1); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm; tm.read(istr); CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1),tm.value(),0); //CPPUNIT_ASSERT(tm.value() == T(1)); } template void TestMetadataIO::testMultiple() { using namespace openvdb; TypedMetadata m(1); TypedMetadata m2(2); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); m2.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm, tm2; tm.read(istr); tm2.read(istr); CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1),tm.value(),0); //CPPUNIT_ASSERT(tm.value() == T(1)); CPPUNIT_ASSERT_DOUBLES_EQUAL(T(2),tm2.value(),0); //CPPUNIT_ASSERT(tm2.value() == T(2)); } template<> void TestMetadataIO::test() { using namespace openvdb; TypedMetadata m("test"); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm; tm.read(istr); CPPUNIT_ASSERT(tm.value() == "test"); } template<> void TestMetadataIO::testMultiple() { using namespace openvdb; TypedMetadata m("test"); TypedMetadata m2("test2"); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); m2.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm, tm2; tm.read(istr); tm2.read(istr); CPPUNIT_ASSERT(tm.value() == "test"); CPPUNIT_ASSERT(tm2.value() == "test2"); } template<> void TestMetadataIO::test() { using namespace openvdb; TypedMetadata m(Vec3R(1, 2, 3)); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm; tm.read(istr); CPPUNIT_ASSERT(tm.value() == Vec3R(1, 2, 3)); } template<> void TestMetadataIO::testMultiple() { using namespace openvdb; TypedMetadata m(Vec3R(1, 2, 3)); TypedMetadata m2(Vec3R(4, 5, 6)); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); m2.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm, tm2; tm.read(istr); tm2.read(istr); CPPUNIT_ASSERT(tm.value() == Vec3R(1, 2, 3)); CPPUNIT_ASSERT(tm2.value() == Vec3R(4, 5, 6)); } template<> void TestMetadataIO::test() { using namespace openvdb; TypedMetadata m(Vec2i(1, 2)); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm; tm.read(istr); CPPUNIT_ASSERT(tm.value() == Vec2i(1, 2)); } template<> void TestMetadataIO::testMultiple() { using namespace openvdb; TypedMetadata m(Vec2i(1, 2)); TypedMetadata m2(Vec2i(3, 4)); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); m2.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm, tm2; tm.read(istr); tm2.read(istr); CPPUNIT_ASSERT(tm.value() == Vec2i(1, 2)); CPPUNIT_ASSERT(tm2.value() == Vec2i(3, 4)); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestBBox.cc0000644000000000000000000001005112603226506015070 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestVolumeToMesh.cc0000644000000000000000000001173312603226506016635 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestGridTransformer.cc0000644000000000000000000002703312603226506017356 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestGridTransformer: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestGridTransformer); CPPUNIT_TEST(testTransformBoolPoint); CPPUNIT_TEST(testTransformFloatPoint); CPPUNIT_TEST(testTransformFloatBox); CPPUNIT_TEST(testTransformFloatQuadratic); CPPUNIT_TEST(testTransformDoubleBox); CPPUNIT_TEST(testTransformInt32Box); CPPUNIT_TEST(testTransformInt64Box); CPPUNIT_TEST(testTransformVec3SPoint); CPPUNIT_TEST(testTransformVec3DBox); CPPUNIT_TEST(testResampleToMatch); CPPUNIT_TEST_SUITE_END(); void testTransformBoolPoint() { transformGrid(); } void testTransformFloatPoint() { transformGrid(); } void testTransformFloatBox() { transformGrid(); } void testTransformFloatQuadratic() { transformGrid(); } void testTransformDoubleBox() { transformGrid(); } void testTransformInt32Box() { transformGrid(); } void testTransformInt64Box() { transformGrid(); } void testTransformVec3SPoint() { transformGrid(); } void testTransformVec3DBox() { transformGrid(); } void testResampleToMatch(); private: template void transformGrid(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestGridTransformer); //////////////////////////////////////// template void TestGridTransformer::transformGrid() { using openvdb::Coord; using openvdb::CoordBBox; using openvdb::Vec3R; typedef typename GridType::ValueType ValueT; const int radius = Sampler::radius(); const openvdb::Vec3R zeroVec(0, 0, 0), oneVec(1, 1, 1); const ValueT zero = openvdb::zeroVal(), one = zero + 1, two = one + 1, background = one; const bool transformTiles = true; // Create a sparse test grid comprising the eight corners of a 20 x 20 x 20 cube. typename GridType::Ptr inGrid = GridType::create(background); typename GridType::Accessor inAcc = inGrid->getAccessor(); inAcc.setValue(Coord( 0, 0, 0), /*value=*/zero); inAcc.setValue(Coord(20, 0, 0), zero); inAcc.setValue(Coord( 0, 20, 0), zero); inAcc.setValue(Coord( 0, 0, 20), zero); inAcc.setValue(Coord(20, 0, 20), zero); inAcc.setValue(Coord( 0, 20, 20), zero); inAcc.setValue(Coord(20, 20, 0), zero); inAcc.setValue(Coord(20, 20, 20), zero); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(8), inGrid->activeVoxelCount()); // For various combinations of scaling, rotation and translation... for (int i = 0; i < 8; ++i) { const openvdb::Vec3R scale = i & 1 ? openvdb::Vec3R(10, 4, 7.5) : oneVec, rotate = (i & 2 ? openvdb::Vec3R(30, 230, -190) : zeroVec) * (M_PI / 180), translate = i & 4 ? openvdb::Vec3R(-5, 0, 10) : zeroVec, pivot = i & 8 ? openvdb::Vec3R(0.5, 4, -3.3) : zeroVec; openvdb::tools::GridTransformer transformer(pivot, scale, rotate, translate); transformer.setTransformTiles(transformTiles); // Add a tile (either active or inactive) in the interior of the cube. const bool tileIsActive = (i % 2); inGrid->fill(CoordBBox(Coord(8), Coord(15)), two, tileIsActive); if (tileIsActive) { CPPUNIT_ASSERT_EQUAL(openvdb::Index64(512 + 8), inGrid->activeVoxelCount()); } else { CPPUNIT_ASSERT_EQUAL(openvdb::Index64(8), inGrid->activeVoxelCount()); } // Verify that a voxel outside the cube has the background value. CPPUNIT_ASSERT(openvdb::math::isExactlyEqual(inAcc.getValue(Coord(21, 0, 0)), background)); CPPUNIT_ASSERT_EQUAL(false, inAcc.isValueOn(Coord(21, 0, 0))); // Verify that a voxel inside the cube has value two. CPPUNIT_ASSERT(openvdb::math::isExactlyEqual(inAcc.getValue(Coord(12)), two)); CPPUNIT_ASSERT_EQUAL(tileIsActive, inAcc.isValueOn(Coord(12))); // Verify that the bounding box of all active values is 20 x 20 x 20. CoordBBox activeVoxelBBox = inGrid->evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT(!activeVoxelBBox.empty()); const Coord imin = activeVoxelBBox.min(), imax = activeVoxelBBox.max(); CPPUNIT_ASSERT_EQUAL(Coord(0), imin); CPPUNIT_ASSERT_EQUAL(Coord(20), imax); // Transform the corners of the input grid's bounding box // and compute the enclosing bounding box in the output grid. const openvdb::Mat4R xform = transformer.getTransform(); const Vec3R inRMin(imin.x(), imin.y(), imin.z()), inRMax(imax.x(), imax.y(), imax.z()); Vec3R outRMin, outRMax; outRMin = outRMax = inRMin * xform; for (int j = 0; j < 8; ++j) { Vec3R corner( j & 1 ? inRMax.x() : inRMin.x(), j & 2 ? inRMax.y() : inRMin.y(), j & 4 ? inRMax.z() : inRMin.z()); outRMin = openvdb::math::minComponent(outRMin, corner * xform); outRMax = openvdb::math::maxComponent(outRMax, corner * xform); } CoordBBox bbox( Coord(openvdb::tools::local_util::floorVec3(outRMin) - radius), Coord(openvdb::tools::local_util::ceilVec3(outRMax) + radius)); // Transform the test grid. typename GridType::Ptr outGrid = GridType::create(background); transformer.transformGrid(*inGrid, *outGrid); openvdb::tools::prune(outGrid->tree()); // Verify that the bounding box of the transformed grid // matches the transformed bounding box of the original grid. activeVoxelBBox = outGrid->evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT(!activeVoxelBBox.empty()); const openvdb::Vec3i omin = activeVoxelBBox.min().asVec3i(), omax = activeVoxelBBox.max().asVec3i(); const int bboxTolerance = 1; // allow for rounding #if 0 if (!omin.eq(bbox.min().asVec3i(), bboxTolerance) || !omax.eq(bbox.max().asVec3i(), bboxTolerance)) { std::cerr << "\nS = " << scale << ", R = " << rotate << ", T = " << translate << ", P = " << pivot << "\n" << xform.transpose() << "\n" << "computed bbox = " << bbox << "\nactual bbox = " << omin << " -> " << omax << "\n"; } #endif CPPUNIT_ASSERT(omin.eq(bbox.min().asVec3i(), bboxTolerance)); CPPUNIT_ASSERT(omax.eq(bbox.max().asVec3i(), bboxTolerance)); // Verify that (a voxel in) the interior of the cube was // transformed correctly. const Coord center = Coord::round(Vec3R(12) * xform); const typename GridType::TreeType& outTree = outGrid->tree(); CPPUNIT_ASSERT(openvdb::math::isExactlyEqual(transformTiles ? two : background, outTree.getValue(center))); if (transformTiles && tileIsActive) CPPUNIT_ASSERT(outTree.isValueOn(center)); else CPPUNIT_ASSERT(!outTree.isValueOn(center)); } } //////////////////////////////////////// void TestGridTransformer::testResampleToMatch() { using namespace openvdb; // Create an input grid with an identity transform. FloatGrid inGrid; // Populate it with a 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestInternalOrigin.cc0000644000000000000000000001056612603226506017175 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include class TestInternalOrigin: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestInternalOrigin); CPPUNIT_TEST(test); CPPUNIT_TEST_SUITE_END(); void test(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestInternalOrigin); void TestInternalOrigin::test() { std::set indices; indices.insert(openvdb::Coord( 0, 0, 0)); indices.insert(openvdb::Coord( 1, 0, 0)); indices.insert(openvdb::Coord( 0,100, 8)); indices.insert(openvdb::Coord(-9, 0, 8)); indices.insert(openvdb::Coord(32, 0, 16)); indices.insert(openvdb::Coord(33, -5, 16)); indices.insert(openvdb::Coord(42,707,-35)); indices.insert(openvdb::Coord(43, 17, 64)); typedef openvdb::tree::Tree4::Type FloatTree4; FloatTree4 tree(0.0f); std::set::iterator iter=indices.begin(); for (int n = 0; iter != indices.end(); ++n, ++iter) { tree.setValue(*iter, float(1.0 + float(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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLaplacian.cc0000644000000000000000000004522712603226506016137 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const Coord c(35,30,40); const openvdb::Vec3f center(static_cast(c[0]), static_cast(c[1]), static_cast(c[2])); const float radius=0.0f;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); Coord xyz(35,10,40); // Index Space Laplacian random access FloatGrid::ConstAccessor inAccessor = grid->getConstAccessor(); FloatGrid::ValueType result; result = math::ISLaplacian::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); result = math::ISLaplacian::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); result = math::ISLaplacian::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); } void TestLaplacian::testISLaplacianStencil() { using namespace openvdb; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const Coord c(35,30,40); const openvdb::Vec3f center(static_cast(c[0]), static_cast(c[1]), static_cast(c[2])); const float radius=0;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); Coord xyz(35,10,40); // Index Space Laplacian stencil access FloatGrid::ValueType result; math::SevenPointStencil sevenpt(*grid); sevenpt.moveTo(xyz); result = math::ISLaplacian::result(sevenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); math::ThirteenPointStencil thirteenpt(*grid); thirteenpt.moveTo(xyz); result = math::ISLaplacian::result(thirteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); math::NineteenPointStencil nineteenpt(*grid); nineteenpt.moveTo(xyz); result = math::ISLaplacian::result(nineteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); } void TestLaplacian::testWSLaplacian() { using namespace openvdb; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const Coord c(35,30,40); const openvdb::Vec3f center(static_cast(c[0]), static_cast(c[1]), static_cast(c[2])); const float radius=0.0f;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); Coord xyz(35,10,40); FloatGrid::ValueType result; FloatGrid::ConstAccessor inAccessor = grid->getConstAccessor(); // try with a map math::UniformScaleMap map; math::MapBase::Ptr rotated_map = map.preRotate(1.5, math::X_AXIS); // verify the new map is an affine map CPPUNIT_ASSERT(rotated_map->type() == math::AffineMap::mapType()); math::AffineMap::Ptr affine_map = 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 = float(cos(ws.x() ) * sin( ws.y()) * cos(ws.z())); tree.setValue(ijk, value); } } } const Coord testloc(16,16,16); float test_result = math::Laplacian::result( *map, tree, testloc); float expected_result = -3.f * tree.getValue(testloc); // The exact solution of Laplacian( cos(x)sin(y)cos(z) ) = -3 cos(x) sin(y) cos(z) CPPUNIT_ASSERT( math::isApproxEqual(test_result, expected_result, /*tolerance=*/0.02f) ); } void TestLaplacian::testWSLaplacianStencil() { using namespace openvdb; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const Coord c(35,30,40); const openvdb::Vec3f center(static_cast(c[0]), static_cast(c[1]), static_cast(c[2])); const float radius=0.0f;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); Coord xyz(35,10,40); FloatGrid::ValueType result; // try with a map math::UniformScaleMap map; math::MapBase::Ptr rotated_map = map.preRotate(1.5, math::X_AXIS); // verify the new map is an affine map CPPUNIT_ASSERT(rotated_map->type() == math::AffineMap::mapType()); math::AffineMap::Ptr affine_map = 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; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64, 64, 64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=0.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); FloatGrid::Ptr lap = tools::laplacian(*grid); CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(lap->activeVoxelCount())); Coord xyz(35,30,30); CPPUNIT_ASSERT_DOUBLES_EQUAL( 2.0/10.0, lap->getConstAccessor().getValue(xyz), 0.01);// 2/distance from center xyz.reset(35,10,40); CPPUNIT_ASSERT_DOUBLES_EQUAL( 2.0/20.0, lap->getConstAccessor().getValue(xyz),0.01);// 2/distance from center } void TestLaplacian::testLaplacianMaskedTool() { using namespace openvdb; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64, 64, 64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=0.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); BoolGrid::Ptr maskGrid = BoolGrid::create(false); maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); FloatGrid::Ptr lap = tools::laplacian(*grid, *maskGrid); {// outside the masked region Coord xyz(34,30,30); CPPUNIT_ASSERT(!maskbbox.isInside(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL( 0, lap->getConstAccessor().getValue(xyz), 0.01);// 2/distance from center xyz.reset(35,10,40); CPPUNIT_ASSERT_DOUBLES_EQUAL( 0, lap->getConstAccessor().getValue(xyz),0.01);// 2/distance from center } {// inside the masked region Coord xyz(35,30,30); CPPUNIT_ASSERT(maskbbox.isInside(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL( 2.0/10.0, lap->getConstAccessor().getValue(xyz), 0.01);// 2/distance from center } } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestRay.cc0000644000000000000000000004647512603226506015014 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); #define ASSERT_DOUBLES_APPROX_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/1.e-6); class TestRay : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestRay); CPPUNIT_TEST(testInfinity); CPPUNIT_TEST(testRay); CPPUNIT_TEST(testTimeSpan); CPPUNIT_TEST(testDDA); CPPUNIT_TEST_SUITE_END(); void testInfinity(); void testRay(); void testTimeSpan(); void testDDA(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestRay); // the Ray class makes use of infinity=1/0 so we test for it void TestRay::testInfinity() { // This code generates compiler warnings which is why it's not // enabled by default. /* const double one=1, zero = 0, infinity = one / zero; CPPUNIT_ASSERT_DOUBLES_EQUAL( infinity , infinity,0);//not a NAN CPPUNIT_ASSERT_DOUBLES_EQUAL( infinity , infinity+1,0);//not a NAN CPPUNIT_ASSERT_DOUBLES_EQUAL( infinity , infinity*10,0);//not a NAN CPPUNIT_ASSERT( zero < infinity); CPPUNIT_ASSERT( zero > -infinity); CPPUNIT_ASSERT_DOUBLES_EQUAL( zero , one/infinity,0); CPPUNIT_ASSERT_DOUBLES_EQUAL( zero , -one/infinity,0); CPPUNIT_ASSERT_DOUBLES_EQUAL( infinity , one/zero,0); CPPUNIT_ASSERT_DOUBLES_EQUAL(-infinity , -one/zero,0); std::cerr << "inf: " << infinity << "\n"; std::cerr << "1 / inf: " << one / infinity << "\n"; std::cerr << "1 / (-inf): " << one / (-infinity) << "\n"; std::cerr << " inf * 0: " << infinity * 0 << "\n"; std::cerr << "-inf * 0: " << (-infinity) * 0 << "\n"; std::cerr << "(inf): " << (bool)(infinity) << "\n"; std::cerr << "inf == inf: " << (infinity == infinity) << "\n"; std::cerr << "inf > 0: " << (infinity > 0) << "\n"; std::cerr << "-inf > 0: " << ((-infinity) > 0) << "\n"; */ } void TestRay::testRay() { using namespace openvdb; typedef double RealT; typedef math::Ray RayT; typedef RayT::Vec3T Vec3T; typedef math::BBox BBoxT; {//default constructor RayT ray; CPPUNIT_ASSERT(ray.eye() == Vec3T(0,0,0)); CPPUNIT_ASSERT(ray.dir() == Vec3T(1,0,0)); ASSERT_DOUBLES_APPROX_EQUAL( math::Delta::value(), ray.t0()); ASSERT_DOUBLES_APPROX_EQUAL( std::numeric_limits::max(), ray.t1()); } {// simple construction Vec3T eye(1.5,1.5,1.5), dir(1.5,1.5,1.5); dir.normalize(); RealT t0=0.1, t1=12589.0; RayT ray(eye, dir, t0, t1); CPPUNIT_ASSERT(ray.eye()==eye); CPPUNIT_ASSERT(ray.dir()==dir); ASSERT_DOUBLES_APPROX_EQUAL( t0, ray.t0()); ASSERT_DOUBLES_APPROX_EQUAL( t1, ray.t1()); } {// test transformation math::Transform::Ptr xform = math::Transform::createLinearTransform(); xform->preRotate(M_PI, math::Y_AXIS ); xform->postTranslate(math::Vec3d(1, 2, 3)); xform->preScale(Vec3R(0.1, 0.2, 0.4)); Vec3T eye(9,1,1), dir(1,2,0); dir.normalize(); RealT t0=0.1, t1=12589.0; RayT ray0(eye, dir, t0, t1); CPPUNIT_ASSERT( ray0.test(t0)); CPPUNIT_ASSERT( ray0.test(t1)); CPPUNIT_ASSERT( ray0.test(0.5*(t0+t1))); CPPUNIT_ASSERT(!ray0.test(t0-1)); CPPUNIT_ASSERT(!ray0.test(t1+1)); //std::cerr << "Ray0: " << ray0 << std::endl; RayT ray1 = ray0.applyMap( *(xform->baseMap()) ); //std::cerr << "Ray1: " << ray1 << std::endl; RayT ray2 = ray1.applyInverseMap( *(xform->baseMap()) ); //std::cerr << "Ray2: " << ray2 << std::endl; ASSERT_DOUBLES_APPROX_EQUAL( eye[0], ray2.eye()[0]); ASSERT_DOUBLES_APPROX_EQUAL( eye[1], ray2.eye()[1]); ASSERT_DOUBLES_APPROX_EQUAL( eye[2], ray2.eye()[2]); ASSERT_DOUBLES_APPROX_EQUAL( dir[0], ray2.dir()[0]); ASSERT_DOUBLES_APPROX_EQUAL( dir[1], ray2.dir()[1]); ASSERT_DOUBLES_APPROX_EQUAL( dir[2], ray2.dir()[2]); ASSERT_DOUBLES_APPROX_EQUAL( dir[0], 1.0/ray2.invDir()[0]); ASSERT_DOUBLES_APPROX_EQUAL( dir[1], 1.0/ray2.invDir()[1]); ASSERT_DOUBLES_APPROX_EQUAL( dir[2], 1.0/ray2.invDir()[2]); ASSERT_DOUBLES_APPROX_EQUAL( t0, ray2.t0()); ASSERT_DOUBLES_APPROX_EQUAL( t1, ray2.t1()); } {// test transformation // This is the index to world transform math::Transform::Ptr xform = math::Transform::createLinearTransform(); xform->postRotate(M_PI, math::Y_AXIS ); xform->postTranslate(math::Vec3d(1, 2, 3)); xform->postScale(Vec3R(0.1, 0.1, 0.1));//voxel size // Define a ray in world space Vec3T eye(9,1,1), dir(1,2,0); dir.normalize(); RealT t0=0.1, t1=12589.0; RayT ray0(eye, dir, t0, t1); //std::cerr << "\nWorld Ray0: " << ray0 << std::endl; CPPUNIT_ASSERT( ray0.test(t0)); CPPUNIT_ASSERT( ray0.test(t1)); CPPUNIT_ASSERT( ray0.test(0.5*(t0+t1))); CPPUNIT_ASSERT(!ray0.test(t0-1)); CPPUNIT_ASSERT(!ray0.test(t1+1)); Vec3T xyz0[3] = {ray0.start(), ray0.mid(), ray0.end()}; // Transform the ray to index space RayT ray1 = ray0.applyInverseMap( *(xform->baseMap()) ); //std::cerr << "\nIndex Ray1: " << ray1 << std::endl; Vec3T xyz1[3] = {ray1.start(), ray1.mid(), ray1.end()}; for (int i=0; i<3; ++i) { Vec3T pos = xform->baseMap()->applyMap(xyz1[i]); //std::cerr << "world0 ="< DDAType; const RayType::Vec3T eye( 0, 0, 0); for (int s = -1; s<=1; s+=2) { for (int a = 0; a<3; ++a) { const int d[3]={s*(a==0), s*(a==1), s*(a==2)}; const RayType::Vec3T dir(d[0], d[1], d[2]); RayType ray(eye, dir); DDAType dda(ray); //std::cerr << "\nray: "< #include #include class TestPointPartitioner: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestPointPartitioner); CPPUNIT_TEST(testPartitioner); CPPUNIT_TEST_SUITE_END(); void testPartitioner(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestPointPartitioner); //////////////////////////////////////// namespace { struct PointList { typedef openvdb::Vec3s value_type; PointList(const std::vector& points) : mPoints(&points) {} size_t size() const { return mPoints->size(); } void getPos(size_t n, value_type& xyz) const { xyz = (*mPoints)[n]; } protected: std::vector const * const mPoints; }; // PointList } // namespace //////////////////////////////////////// void TestPointPartitioner::testPartitioner() { const size_t pointCount = 10000; const float voxelSize = 0.1f; std::vector points(pointCount, openvdb::Vec3s(0.f)); for (size_t n = 1; n < pointCount; ++n) { points[n].x() = points[n-1].x() + voxelSize; } PointList pointList(points); const openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize); typedef openvdb::tools::UInt32PointPartitioner PointPartitioner; PointPartitioner::Ptr partitioner = PointPartitioner::create(pointList, *transform); CPPUNIT_ASSERT(!partitioner->empty()); const size_t expectedPageCount = pointCount / (1u << PointPartitioner::LOG2DIM); CPPUNIT_ASSERT_EQUAL(expectedPageCount, partitioner->size()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0), partitioner->origin(0)); PointPartitioner::IndexIterator it = partitioner->indices(0); CPPUNIT_ASSERT(it.test()); CPPUNIT_ASSERT_EQUAL(it.size(), size_t(1 << PointPartitioner::LOG2DIM)); PointPartitioner::IndexIterator itB = partitioner->indices(0); CPPUNIT_ASSERT_EQUAL(++it, ++itB); CPPUNIT_ASSERT(it != ++itB); std::vector indices; for (it.reset(); it; ++it) { indices.push_back(*it); } CPPUNIT_ASSERT_EQUAL(it.size(), indices.size()); size_t idx = 0; for (itB.reset(); itB; ++itB) { CPPUNIT_ASSERT_EQUAL(indices[idx++], *itB); } } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestDiagnostics.cc0000644000000000000000000003663212603226506016522 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include class TestDiagnostics: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestDiagnostics); CPPUNIT_TEST(testCheck); CPPUNIT_TEST(testDiagnose); CPPUNIT_TEST(testCheckLevelSet); CPPUNIT_TEST(testCheckFogVolume); CPPUNIT_TEST(testUniqueInactiveValues); CPPUNIT_TEST_SUITE_END(); void testCheck(); void testDiagnose(); void testCheckLevelSet(); void testCheckFogVolume(); void testUniqueInactiveValues(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestDiagnostics); //////////////////////////////////////// void TestDiagnostics::testCheck() { const float val = 1.0f; const float nan = std::numeric_limits::quiet_NaN(); const float inf1= std::numeric_limits::infinity(); const openvdb::math::Vec3 inf2(val, inf1, val); {//test CheckNan openvdb::tools::CheckNan c; CPPUNIT_ASSERT(!c(val)); CPPUNIT_ASSERT( c(nan)); CPPUNIT_ASSERT( c(nan)); CPPUNIT_ASSERT(!c(inf1)); CPPUNIT_ASSERT(!c(inf2)); } {//test CheckInf openvdb::tools::CheckInf c; CPPUNIT_ASSERT(!c(val)); CPPUNIT_ASSERT(!c(nan)); CPPUNIT_ASSERT(!c(nan)); CPPUNIT_ASSERT( c(inf1)); CPPUNIT_ASSERT( c(inf2)); } {//test CheckFinite openvdb::tools::CheckFinite c; CPPUNIT_ASSERT(!c(val)); CPPUNIT_ASSERT( c(nan)); CPPUNIT_ASSERT( c(nan)); CPPUNIT_ASSERT( c(inf1)); CPPUNIT_ASSERT( c(inf2)); } {//test CheckMin openvdb::tools::CheckMin c(0.0f); CPPUNIT_ASSERT(!c( 0.5f)); CPPUNIT_ASSERT(!c( 0.0f)); CPPUNIT_ASSERT(!c( 1.0f)); CPPUNIT_ASSERT(!c( 1.1f)); CPPUNIT_ASSERT( c(-0.1f)); } {//test CheckMax openvdb::tools::CheckMax c(0.0f); CPPUNIT_ASSERT( c( 0.5f)); CPPUNIT_ASSERT(!c( 0.0f)); CPPUNIT_ASSERT( c( 1.0f)); CPPUNIT_ASSERT( c( 1.1f)); CPPUNIT_ASSERT(!c(-0.1f)); } {//test CheckRange // first check throw on construction from an invalid range CPPUNIT_ASSERT_THROW(openvdb::tools::CheckRange c(1.0f, 0.0f), openvdb::ValueError); openvdb::tools::CheckRange c(0.0f, 1.0f); CPPUNIT_ASSERT(!c(0.5f)); CPPUNIT_ASSERT(!c(0.0f)); CPPUNIT_ASSERT(!c(1.0f)); CPPUNIT_ASSERT( c(1.1f)); CPPUNIT_ASSERT(c(-0.1f)); } }//testCheck void TestDiagnostics::testDiagnose() { using namespace openvdb; const float val = 1.0f; const float nan = std::numeric_limits::quiet_NaN(); const float inf = std::numeric_limits::infinity(); {//empty grid FloatGrid grid; tools::Diagnose d(grid); tools::CheckNan c; std::string str = d.check(c); //std::cerr << "Empty grid:\n" << str; CPPUNIT_ASSERT_EQUAL(std::string(), str); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {//non-empty grid FloatGrid grid; grid.tree().setValue(Coord(-1,3,6), val); tools::Diagnose d(grid); tools::CheckNan c; std::string str = d.check(c); //std::cerr << "Non-Empty grid:\n" << str; CPPUNIT_ASSERT_EQUAL(std::string(), str); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {//nan grid FloatGrid grid; grid.tree().setValue(Coord(-1,3,6), nan); tools::Diagnose d(grid); tools::CheckNan c; std::string str = d.check(c); //std::cerr << "NaN grid:\n" << str; CPPUNIT_ASSERT(!str.empty()); CPPUNIT_ASSERT_EQUAL(1, int(d.failureCount())); } {//nan and infinite grid FloatGrid grid; grid.tree().setValue(Coord(-1,3,6), nan); grid.tree().setValue(Coord(10,30,60), inf); tools::Diagnose d(grid); tools::CheckFinite c; std::string str = d.check(c); //std::cerr << "Not Finite grid:\n" << str; CPPUNIT_ASSERT(!str.empty()); CPPUNIT_ASSERT_EQUAL(2, int(d.failureCount())); } {//out-of-range grid FloatGrid grid(10.0f); grid.tree().setValue(Coord(-1,3,6), 1.0f); grid.tree().setValue(Coord(10,30,60), 1.5); grid.tree().fill(math::CoordBBox::createCube(math::Coord(0),8), 20.0f, true); tools::Diagnose d(grid); tools::CheckRange c(0.0f, 1.0f); std::string str = d.check(c); //std::cerr << "out-of-range grid:\n" << str; CPPUNIT_ASSERT(!str.empty()); CPPUNIT_ASSERT_EQUAL(3, int(d.failureCount())); } const float radius = 4.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.1f, width = 2.0f, gamma=voxelSize*width; FloatGrid::Ptr gridSphere = tools::createLevelSetSphere(radius, center, voxelSize, width); //gridSphere->print(std::cerr, 2); {// Check min/max of active values math::Extrema ex = tools::extrema(gridSphere->cbeginValueOn()); //std::cerr << "Min = " << ex.min() << " max = " << ex.max() << std::endl; CPPUNIT_ASSERT(ex.min() > -voxelSize*width); CPPUNIT_ASSERT(ex.max() < voxelSize*width); } {// Check min/max of all values math::Extrema ex = tools::extrema(gridSphere->cbeginValueAll()); //std::cerr << "Min = " << ex.min() << " max = " << ex.max() << std::endl; CPPUNIT_ASSERT(ex.min() >= -voxelSize*width); CPPUNIT_ASSERT(ex.max() <= voxelSize*width); } {// check range of all values in a sphere w/o mask tools::CheckRange c(-gamma, gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check range of on values in a sphere w/o mask tools::CheckRange c(-gamma, gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check range of off tiles in a sphere w/o mask tools::CheckRange c(-gamma, gamma); tools::Diagnose d(*gridSphere); {// check off tile iterator FloatGrid::ValueOffCIter i(gridSphere->tree()); i.setMaxDepth(FloatGrid::ValueOffCIter::LEAF_DEPTH - 1); for (; i; ++i) CPPUNIT_ASSERT( math::Abs(*i) <= gamma); } std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check range of sphere w/o mask tools::CheckRange c(0.0f, gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(!str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT(d.failureCount() < gridSphere->activeVoxelCount()); } {// check range of sphere w mask tools::CheckRange c(0.0f, gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c, true); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(!str.empty()); CPPUNIT_ASSERT_EQUAL(d.valueCount(), d.valueCount()); CPPUNIT_ASSERT(d.failureCount() < gridSphere->activeVoxelCount()); } {// check min of sphere w/o mask tools::CheckMin c(-gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "Min values:\n" << str; CPPUNIT_ASSERT_EQUAL(std::string(), str); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check max of sphere w/o mask tools::CheckMax c(gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "MAX values:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check norm of gradient of sphere w/o mask tools::CheckEikonal c(*gridSphere, 0.97f, 1.03f); tools::Diagnose d(*gridSphere); std::string str = d.check(c, false, true, false, false); //std::cerr << "NormGrad:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check norm of gradient of sphere w/o mask tools::CheckNormGrad c(*gridSphere, 0.75f, 1.25f); tools::Diagnose d(*gridSphere); std::string str = d.check(c, false, true, false, false); //std::cerr << "NormGrad:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check inactive values tools::CheckMagnitude c(gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "Magnitude:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } }// testDiagnose void TestDiagnostics::testCheckLevelSet() { using namespace openvdb; const float radius = 4.3f; const Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.1f, width = LEVEL_SET_HALF_WIDTH; FloatGrid::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, width); //tools::CheckLevelSet c(*grid); //std::string str = c.check(); std::string str = tools::checkLevelSet(*grid); CPPUNIT_ASSERT(str.empty()); //std::cerr << "\n" << str << std::endl; grid->tree().setValue(Coord(0,0,0), voxelSize*(width+0.5f)); //str = c.check(); str = tools::checkLevelSet(*grid); CPPUNIT_ASSERT(!str.empty()); //std::cerr << "\n" << str << std::endl; //str = c.check(6); str = tools::checkLevelSet(*grid, 6); CPPUNIT_ASSERT(str.empty()); }// testCheckLevelSet void TestDiagnostics::testCheckFogVolume() { using namespace openvdb; const float radius = 4.3f; const Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.1f, width = LEVEL_SET_HALF_WIDTH; FloatGrid::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, width); tools::sdfToFogVolume(*grid); //tools::CheckFogVolume c(*grid); //std::string str = c.check(); std::string str = tools::checkFogVolume(*grid); CPPUNIT_ASSERT(str.empty()); //std::cerr << "\n" << str << std::endl; grid->tree().setValue(Coord(0,0,0), 1.5f); //str = c.check(); str = tools::checkFogVolume(*grid); CPPUNIT_ASSERT(!str.empty()); //std::cerr << "\n" << str << std::endl; str = tools::checkFogVolume(*grid, 5); //str = c.check(5); CPPUNIT_ASSERT(str.empty()); }// testCheckFogVolume void TestDiagnostics::testUniqueInactiveValues() { openvdb::FloatGrid grid; grid.tree().setValueOff(openvdb::Coord(0,0,0), -1); grid.tree().setValueOff(openvdb::Coord(0,0,1), -2); grid.tree().setValueOff(openvdb::Coord(0,1,0), -3); grid.tree().setValue(openvdb::Coord(1,0,0), 1); std::vector values; CPPUNIT_ASSERT(openvdb::tools::uniqueInactiveValues(grid, values, 4)); CPPUNIT_ASSERT_EQUAL(4, int(values.size())); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[0], -3.0f)); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[1], -2.0f)); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[2], -1.0f)); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[3], 0.0f)); // test with level set sphere const float radius = 4.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.5f, width = 2.0f; openvdb::FloatGrid::Ptr gridSphere = openvdb::tools::createLevelSetSphere(radius, center, voxelSize, width); CPPUNIT_ASSERT(openvdb::tools::uniqueInactiveValues(*gridSphere.get(), values, 2)); CPPUNIT_ASSERT_EQUAL(2, int(values.size())); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[0], -voxelSize * width)); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[1], voxelSize * width)); // test with fog volume openvdb::tools::sdfToFogVolume(*gridSphere); CPPUNIT_ASSERT(openvdb::tools::uniqueInactiveValues(*gridSphere.get(), values, 1)); CPPUNIT_ASSERT_EQUAL(1, int(values.size())); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[0], 0.0f)); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestDoubleMetadata.cc0000644000000000000000000000564312603226506017124 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestDoubleMetadata : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestDoubleMetadata); CPPUNIT_TEST(test); CPPUNIT_TEST_SUITE_END(); void test(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestDoubleMetadata); void TestDoubleMetadata::test() { using namespace openvdb; Metadata::Ptr m(new DoubleMetadata(1.23)); Metadata::Ptr m2 = m->copy(); CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); CPPUNIT_ASSERT(m->typeName().compare("double") == 0); CPPUNIT_ASSERT(m2->typeName().compare("double") == 0); DoubleMetadata *s = dynamic_cast(m.get()); //CPPUNIT_ASSERT(s->value() == 1.23); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.23,s->value(),0); s->value() = 4.56; //CPPUNIT_ASSERT(s->value() == 4.56); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.56,s->value(),0); m2->copy(*s); s = dynamic_cast(m2.get()); //CPPUNIT_ASSERT(s->value() == 4.56); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.56,s->value(),0); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLeafOrigin.cc0000644000000000000000000001104312603226506016257 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestVolumeRayIntersector.cc0000644000000000000000000003051712603226506020414 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #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); } {//same as above but now with std::deque instead of std::vector FloatGrid grid(0.0f); grid.tree().setValue(Coord(0*8,0,0), 1.0f); grid.tree().setValue(Coord(1*8,0,0), 1.0f); grid.tree().setValue(Coord(3*8,0,0), 1.0f); grid.fill(CoordBBox(Coord(4*8,0,0), Coord(4*8+7,7,7)), 2.0f, true); const Vec3T dir( 1.0, 0.0, 0.0); const Vec3T eye(-1.0, 0.0, 0.0); const RayT ray(eye, dir);//ray in index space tools::VolumeRayIntersector inter(grid); CPPUNIT_ASSERT(inter.setIndexRay(ray)); std::deque list; inter.hits(list); CPPUNIT_ASSERT(list.size() == 2); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, list[0].t0); ASSERT_DOUBLES_APPROX_EQUAL(17.0, list[0].t1); ASSERT_DOUBLES_APPROX_EQUAL(25.0, list[1].t0); ASSERT_DOUBLES_APPROX_EQUAL(41.0, list[1].t1); } {// Test submitted by "Jan" @ GitHub FloatGrid grid(0.0f); grid.tree().setValue(Coord(0*8,0,0), 1.0f); grid.tree().setValue(Coord(1*8,0,0), 1.0f); grid.tree().setValue(Coord(3*8,0,0), 1.0f); tools::VolumeRayIntersector inter(grid); const Vec3T dir(-1.0, 0.0, 0.0); const Vec3T eye(50.0, 0.0, 0.0); const RayT ray(eye, dir); CPPUNIT_ASSERT(inter.setIndexRay(ray)); double t0=0, t1=0; CPPUNIT_ASSERT(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(18.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(26.0, t1); CPPUNIT_ASSERT(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(34.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(50.0, t1); CPPUNIT_ASSERT(!inter.march(t0, t1)); } {// Test submitted by "Trevor" @ GitHub FloatGrid::Ptr grid = createGrid(0.0f); grid->tree().setValue(Coord(0,0,0), 1.0f); tools::dilateVoxels(grid->tree()); tools::VolumeRayIntersector inter(*grid); //std::cerr << "BBox = " << inter.bbox() << std::endl; const Vec3T eye(-0.25, -0.25, 10.0); const Vec3T dir( 0.00, 0.00, -1.0); const RayT ray(eye, dir); CPPUNIT_ASSERT(inter.setIndexRay(ray));// hits bbox double t0=0, t1=0; CPPUNIT_ASSERT(!inter.march(t0, t1));// misses leafs } {// Test submitted by "Trevor" @ GitHub FloatGrid::Ptr grid = createGrid(0.0f); grid->tree().setValue(Coord(0,0,0), 1.0f); tools::dilateVoxels(grid->tree()); tools::VolumeRayIntersector inter(*grid); //GridPtrVec grids; //grids.push_back(grid); //io::File vdbfile("trevor_v1.vdb"); //vdbfile.write(grids); //std::cerr << "BBox = " << inter.bbox() << std::endl; const Vec3T eye(0.75, 0.75, 10.0); const Vec3T dir( 0.00, 0.00, -1.0); const RayT ray(eye, dir); CPPUNIT_ASSERT(inter.setIndexRay(ray));// hits bbox double t0=0, t1=0; CPPUNIT_ASSERT(inter.march(t0, t1));// misses leafs //std::cerr << "t0=" << t0 << " t1=" << t1 << std::endl; } {// Test derived from the test submitted by "Trevor" @ GitHub FloatGrid grid(0.0f); grid.fill(math::CoordBBox(Coord(-1,-1,-1),Coord(1,1,1)), 1.0f); tools::VolumeRayIntersector inter(grid); //std::cerr << "BBox = " << inter.bbox() << std::endl; const Vec3T eye(-0.25, -0.25, 10.0); const Vec3T dir( 0.00, 0.00, -1.0); const RayT ray(eye, dir); CPPUNIT_ASSERT(inter.setIndexRay(ray));// hits bbox double t0=0, t1=0; CPPUNIT_ASSERT(inter.march(t0, t1));// hits leafs //std::cerr << "t0=" << t0 << " t1=" << t1 << std::endl; } } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestGridDescriptor.cc0000644000000000000000000001467112603226506017176 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestGrid.cc0000644000000000000000000003665712603226506015147 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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); class TestGrid: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestGrid); CPPUNIT_TEST(testGridRegistry); CPPUNIT_TEST(testConstPtr); CPPUNIT_TEST(testGetGrid); CPPUNIT_TEST(testIsType); CPPUNIT_TEST(testTransform); CPPUNIT_TEST(testCopyGrid); CPPUNIT_TEST(testValueConversion); CPPUNIT_TEST(testClipping); CPPUNIT_TEST_SUITE_END(); void testGridRegistry(); void testConstPtr(); void testGetGrid(); void testIsType(); void testTransform(); void testCopyGrid(); void testValueConversion(); void testClipping(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestGrid); //////////////////////////////////////// class ProxyTree: public openvdb::TreeBase { public: 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); } #ifndef OPENVDB_2_ABI_COMPATIBLE virtual void readBuffers(std::istream& is, const openvdb::CoordBBox&, bool /*saveFloatAsHalf*/=false) { is.seekg(0); } virtual void readNonresidentBuffers() const {} #endif 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) {} void clip(const openvdb::CoordBBox&) {} #ifndef OPENVDB_2_ABI_COMPATIBLE virtual void clipUnallocatedNodes() {} #endif 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; } #ifndef OPENVDB_2_ABI_COMPATIBLE virtual openvdb::Index64 activeTileCount() const { return 0UL; } #endif }; 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); } //////////////////////////////////////// template void validateClippedGrid(const GridT& clipped, const typename GridT::ValueType& fg) { using namespace openvdb; typedef typename GridT::ValueType ValueT; const CoordBBox bbox = clipped.evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT_EQUAL(4, bbox.min().x()); CPPUNIT_ASSERT_EQUAL(4, bbox.min().y()); CPPUNIT_ASSERT_EQUAL(-6, bbox.min().z()); CPPUNIT_ASSERT_EQUAL(4, bbox.max().x()); CPPUNIT_ASSERT_EQUAL(4, bbox.max().y()); CPPUNIT_ASSERT_EQUAL(6, bbox.max().z()); CPPUNIT_ASSERT_EQUAL(6 + 6 + 1, int(clipped.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(2, int(clipped.constTree().leafCount())); typename GridT::ConstAccessor acc = clipped.getConstAccessor(); const ValueT bg = clipped.background(); Coord xyz; int &x = xyz[0], &y = xyz[1], &z = xyz[2]; for (x = -10; x <= 10; ++x) { for (y = -10; y <= 10; ++y) { for (z = -10; z <= 10; ++z) { if (x == 4 && y == 4 && z >= -6 && z <= 6) { CPPUNIT_ASSERT_EQUAL(fg, acc.getValue(Coord(4, 4, z))); } else { CPPUNIT_ASSERT_EQUAL(bg, acc.getValue(Coord(x, y, z))); } } } } } // See also TestTools::testClipping() void TestGrid::testClipping() { using namespace openvdb; const BBoxd clipBox(Vec3d(4.0, 4.0, -6.0), Vec3d(4.9, 4.9, 6.0)); { const float fg = 5.f; FloatGrid cube(0.f); cube.fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/fg, /*active=*/true); #ifdef OPENVDB_2_ABI_COMPATIBLE cube.tree().clip(cube.constTransform().worldToIndexNodeCentered(clipBox)); #else cube.clipGrid(clipBox); #endif validateClippedGrid(cube, fg); } { const bool fg = true; BoolGrid cube(false); cube.fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/fg, /*active=*/true); #ifdef OPENVDB_2_ABI_COMPATIBLE cube.tree().clip(cube.constTransform().worldToIndexNodeCentered(clipBox)); #else cube.clipGrid(clipBox); #endif validateClippedGrid(cube, fg); } { const Vec3s fg(1.f, -2.f, 3.f); Vec3SGrid cube(Vec3s(0.f)); cube.fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/fg, /*active=*/true); #ifdef OPENVDB_2_ABI_COMPATIBLE cube.tree().clip(cube.constTransform().worldToIndexNodeCentered(clipBox)); #else cube.clipGrid(clipBox); #endif validateClippedGrid(cube, fg); } /* {// Benchmark multi-threaded copy construction openvdb::util::CpuTimer timer; openvdb::initialize(); openvdb::io::File file("/usr/pic1/Data/OpenVDB/LevelSetModels/crawler.vdb"); file.open(); openvdb::GridBase::Ptr baseGrid = file.readGrid("ls_crawler"); file.close(); openvdb::FloatGrid::Ptr grid = openvdb::gridPtrCast(baseGrid); //grid->tree().print(); timer.start("\nCopy construction"); openvdb::FloatTree fTree(grid->tree()); timer.stop(); timer.start("\nBoolean topology copy construction"); openvdb::BoolTree bTree(grid->tree(), false, openvdb::TopologyCopy()); timer.stop(); timer.start("\nBoolean topology union"); bTree.topologyUnion(fTree); timer.stop(); //bTree.print(); } */ } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMetaMap.cc0000644000000000000000000002712412603226506015573 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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(testEquality); CPPUNIT_TEST_SUITE_END(); void testInsert(); void testRemove(); void testGetMetadata(); void testIO(); void testEmptyIO(); void testCopyConstructor(); void testCopyConstructorEmpty(); void testAssignment(); void testEquality(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMetaMap); void TestMetaMap::testInsert() { using namespace openvdb; MetaMap meta; meta.insertMeta("meta1", StringMetadata("testing")); meta.insertMeta("meta2", Int32Metadata(20)); meta.insertMeta("meta3", FloatMetadata(2.0)); MetaMap::MetaIterator iter = meta.beginMeta(); int i = 1; for( ; iter != meta.endMeta(); ++iter, ++i) { if(i == 1) { CPPUNIT_ASSERT(iter->first.compare("meta1") == 0); std::string val = meta.metaValue("meta1"); CPPUNIT_ASSERT(val == "testing"); } else if(i == 2) { CPPUNIT_ASSERT(iter->first.compare("meta2") == 0); int32_t val = meta.metaValue("meta2"); CPPUNIT_ASSERT(val == 20); } else if(i == 3) { CPPUNIT_ASSERT(iter->first.compare("meta3") == 0); float val = meta.metaValue("meta3"); //CPPUNIT_ASSERT(val == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,val,0); } } } void TestMetaMap::testRemove() { using namespace openvdb; MetaMap meta; meta.insertMeta("meta1", StringMetadata("testing")); meta.insertMeta("meta2", Int32Metadata(20)); meta.insertMeta("meta3", FloatMetadata(2.0)); meta.removeMeta("meta2"); MetaMap::MetaIterator iter = meta.beginMeta(); int i = 1; for( ; iter != meta.endMeta(); ++iter, ++i) { if(i == 1) { CPPUNIT_ASSERT(iter->first.compare("meta1") == 0); std::string val = meta.metaValue("meta1"); CPPUNIT_ASSERT(val == "testing"); } else if(i == 2) { CPPUNIT_ASSERT(iter->first.compare("meta3") == 0); float val = meta.metaValue("meta3"); //CPPUNIT_ASSERT(val == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,val,0); } } meta.removeMeta("meta1"); iter = meta.beginMeta(); for( ; iter != meta.endMeta(); ++iter, ++i) { CPPUNIT_ASSERT(iter->first.compare("meta3") == 0); float val = meta.metaValue("meta3"); //CPPUNIT_ASSERT(val == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,val,0); } meta.removeMeta("meta3"); CPPUNIT_ASSERT_EQUAL(0, int(meta.metaCount())); } void TestMetaMap::testGetMetadata() { using namespace openvdb; MetaMap meta; meta.insertMeta("meta1", StringMetadata("testing")); meta.insertMeta("meta2", Int32Metadata(20)); meta.insertMeta("meta3", DoubleMetadata(2.0)); Metadata::Ptr metadata = meta["meta2"]; CPPUNIT_ASSERT(metadata); CPPUNIT_ASSERT(metadata->typeName().compare("int32") == 0); DoubleMetadata::Ptr dm = meta.getMetadata("meta3"); //CPPUNIT_ASSERT(dm->value() == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0,dm->value(),0); const DoubleMetadata::Ptr cdm = meta.getMetadata("meta3"); //CPPUNIT_ASSERT(dm->value() == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0,cdm->value(),0); CPPUNIT_ASSERT(!meta.getMetadata("meta2")); CPPUNIT_ASSERT_THROW(meta.metaValue("meta3"), openvdb::TypeError); CPPUNIT_ASSERT_THROW(meta.metaValue("meta5"), openvdb::LookupError); } void TestMetaMap::testIO() { using namespace openvdb; 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_EQUAL(0, int(meta2.metaCount())); // 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_EQUAL(0, int(meta2.metaCount())); // Copy the first map to the second. meta2 = meta; CPPUNIT_ASSERT_EQUAL(meta.metaCount(), meta2.metaCount()); // Verify that the contents of the two maps are the same. CPPUNIT_ASSERT_EQUAL( meta.metaValue("meta1"), meta2.metaValue("meta1")); CPPUNIT_ASSERT_EQUAL(meta.metaValue("meta2"), meta2.metaValue("meta2")); CPPUNIT_ASSERT_DOUBLES_EQUAL( meta.metaValue("meta3"), meta2.metaValue("meta3"), /*tolerance=*/0); // Verify that changing one map doesn't affect the other. meta.insertMeta("meta1", StringMetadata("changed")); std::string str = meta.metaValue("meta1"); CPPUNIT_ASSERT_EQUAL(std::string("testing"), meta2.metaValue("meta1")); } void TestMetaMap::testEquality() { using namespace openvdb; // Populate a map with data. MetaMap meta; meta.insertMeta("meta1", StringMetadata("testing")); meta.insertMeta("meta2", Int32Metadata(20)); meta.insertMeta("meta3", FloatMetadata(3.14159f)); // Create an empty map. MetaMap meta2; // Verify that the two maps differ. CPPUNIT_ASSERT(meta != meta2); CPPUNIT_ASSERT(meta2 != meta); // Copy the first map to the second. meta2 = meta; // Verify that the two maps are equivalent. CPPUNIT_ASSERT(meta == meta2); CPPUNIT_ASSERT(meta2 == meta); // Modify the first map. meta.removeMeta("meta1"); meta.insertMeta("abc", DoubleMetadata(2.0)); // Verify that the two maps differ. CPPUNIT_ASSERT(meta != meta2); CPPUNIT_ASSERT(meta2 != meta); // Modify the second map and verify that the two maps differ. meta2 = meta; meta2.insertMeta("meta2", Int32Metadata(42)); CPPUNIT_ASSERT(meta != meta2); CPPUNIT_ASSERT(meta2 != meta); meta2 = meta; meta2.insertMeta("meta3", FloatMetadata(2.0001f)); CPPUNIT_ASSERT(meta != meta2); CPPUNIT_ASSERT(meta2 != meta); meta2 = meta; meta2.insertMeta("abc", DoubleMetadata(2.0001)); CPPUNIT_ASSERT(meta != meta2); CPPUNIT_ASSERT(meta2 != meta); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTreeGetSetValues.cc0000644000000000000000000004476612603226506017455 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include // for tools::setValueOnMin() et al. #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestTreeGetSetValues: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestTreeGetSetValues); CPPUNIT_TEST(testGetValues); CPPUNIT_TEST(testSetValues); CPPUNIT_TEST(testUnsetValues); CPPUNIT_TEST(testFill); CPPUNIT_TEST(testSetActiveStates); CPPUNIT_TEST(testHasActiveTiles); CPPUNIT_TEST_SUITE_END(); void testGetBackground(); void testGetValues(); void testSetValues(); void testUnsetValues(); void testFill(); void testSetActiveStates(); void testHasActiveTiles(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTreeGetSetValues); namespace { typedef openvdb::tree::Tree4::Type Tree323f; // 8^3 x 4^3 x 8^3 } void TestTreeGetSetValues::testGetBackground() { const float background = 256.0f; Tree323f tree(background); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.background()); } void TestTreeGetSetValues::testGetValues() { Tree323f tree(/*background=*/256.0f); tree.setValue(openvdb::Coord(0, 0, 0), 1.0); tree.setValue(openvdb::Coord(1, 0, 0), 1.5); tree.setValue(openvdb::Coord(0, 0, 8), 2.0); tree.setValue(openvdb::Coord(1, 0, 8), 2.5); tree.setValue(openvdb::Coord(0, 0, 16), 3.0); tree.setValue(openvdb::Coord(1, 0, 16), 3.5); tree.setValue(openvdb::Coord(0, 0, 24), 4.0); tree.setValue(openvdb::Coord(1, 0, 24), 4.5); ASSERT_DOUBLES_EXACTLY_EQUAL(1.0, tree.getValue(openvdb::Coord(0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(1.5, tree.getValue(openvdb::Coord(1, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(2.0, tree.getValue(openvdb::Coord(0, 0, 8))); ASSERT_DOUBLES_EXACTLY_EQUAL(2.5, tree.getValue(openvdb::Coord(1, 0, 8))); ASSERT_DOUBLES_EXACTLY_EQUAL(3.0, tree.getValue(openvdb::Coord(0, 0, 16))); ASSERT_DOUBLES_EXACTLY_EQUAL(3.5, tree.getValue(openvdb::Coord(1, 0, 16))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.0, tree.getValue(openvdb::Coord(0, 0, 24))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.5, tree.getValue(openvdb::Coord(1, 0, 24))); } void TestTreeGetSetValues::testSetValues() { using namespace openvdb; const float background = 256.0; Tree323f tree(background); for (int activeTile = 0; activeTile < 2; ++activeTile) { if (activeTile) tree.fill(CoordBBox(Coord(0), Coord(31)), background, /*active=*/true); tree.setValue(openvdb::Coord(0, 0, 0), 1.0); tree.setValue(openvdb::Coord(1, 0, 0), 1.5); tree.setValue(openvdb::Coord(0, 0, 8), 2.0); tree.setValue(openvdb::Coord(1, 0, 8), 2.5); tree.setValue(openvdb::Coord(0, 0, 16), 3.0); tree.setValue(openvdb::Coord(1, 0, 16), 3.5); tree.setValue(openvdb::Coord(0, 0, 24), 4.0); tree.setValue(openvdb::Coord(1, 0, 24), 4.5); const int expectedActiveCount = (!activeTile ? 8 : 32 * 32 * 32); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); float val = 1.f; for (Tree323f::LeafCIter iter = tree.cbeginLeaf(); iter; ++iter) { ASSERT_DOUBLES_EXACTLY_EQUAL(val, iter->getValue(openvdb::Coord(0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(val+0.5, iter->getValue(openvdb::Coord(1, 0, 0))); val = val + 1.f; } } } void TestTreeGetSetValues::testUnsetValues() { using namespace openvdb; const float background = 256.0; Tree323f tree(background); for (int activeTile = 0; activeTile < 2; ++activeTile) { if (activeTile) tree.fill(CoordBBox(Coord(0), Coord(31)), background, /*active=*/true); Coord setCoords[8] = { Coord(0, 0, 0), Coord(1, 0, 0), Coord(0, 0, 8), Coord(1, 0, 8), Coord(0, 0, 16), Coord(1, 0, 16), Coord(0, 0, 24), Coord(1, 0, 24) }; for (int i = 0; i < 8; ++i) { tree.setValue(setCoords[i], 1.0); } const int expectedActiveCount = (!activeTile ? 8 : 32 * 32 * 32); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); // Unset some voxels. for (int i = 0; i < 8; i += 2) { tree.setValueOff(setCoords[i]); } CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 4, int(tree.activeVoxelCount())); // Unset some voxels, but change their values. for (int i = 0; i < 8; i += 2) { tree.setValueOff(setCoords[i], background); } CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 4, int(tree.activeVoxelCount())); for (int i = 0; i < 8; i += 2) { ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(setCoords[i])); } } } void TestTreeGetSetValues::testFill() { using openvdb::CoordBBox; using openvdb::Coord; const float background = 256.0; Tree323f tree(background); // Fill from (-2,-2,-2) to (2,2,2) with active value 2. tree.fill(CoordBBox(Coord(-2), Coord(2)), 2.0); Coord xyz, xyzMin = Coord::max(), xyzMax = Coord::min(); for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { xyz = iter.getCoord(); xyzMin = std::min(xyzMin, xyz); xyzMax = std::max(xyz, xyzMax); ASSERT_DOUBLES_EXACTLY_EQUAL(2.0, *iter); } CPPUNIT_ASSERT_EQUAL(openvdb::Index64(5*5*5), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); CPPUNIT_ASSERT_EQUAL(Coord( 2), xyzMax); // Fill from (1,1,1) to (3,3,3) with active value 3. tree.fill(CoordBBox(Coord(1), Coord(3)), 3.0); xyzMin = Coord::max(); xyzMax = Coord::min(); for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { xyz = iter.getCoord(); xyzMin = std::min(xyzMin, xyz); xyzMax = std::max(xyz, xyzMax); const float expectedValue = (xyz[0] >= 1 && xyz[1] >= 1 && xyz[2] >= 1 && xyz[0] <= 3 && xyz[1] <= 3 && xyz[2] <= 3) ? 3.0 : 2.0; ASSERT_DOUBLES_EXACTLY_EQUAL(expectedValue, *iter); } openvdb::Index64 expectedCount = 5*5*5 // (-2,-2,-2) to (2,2,2) + 3*3*3 // (1,1,1) to (3,3,3) - 2*2*2; // (1,1,1) to (2,2,2) overlap CPPUNIT_ASSERT_EQUAL(expectedCount, tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); CPPUNIT_ASSERT_EQUAL(Coord( 3), xyzMax); // Fill from (10,10,10) to (20,20,20) with active value 10. tree.fill(CoordBBox(Coord(10), Coord(20)), 10.0); xyzMin = Coord::max(); xyzMax = Coord::min(); for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { xyz = iter.getCoord(); xyzMin = std::min(xyzMin, xyz); xyzMax = std::max(xyz, xyzMax); float expectedValue = 2.0; if (xyz[0] >= 1 && xyz[1] >= 1 && xyz[2] >= 1 && xyz[0] <= 3 && xyz[1] <= 3 && xyz[2] <= 3) { expectedValue = 3.0; } else if (xyz[0] >= 10 && xyz[1] >= 10 && xyz[2] >= 10 && xyz[0] <= 20 && xyz[1] <= 20 && xyz[2] <= 20) { expectedValue = 10.0; } ASSERT_DOUBLES_EXACTLY_EQUAL(expectedValue, *iter); } expectedCount = 5*5*5 // (-2,-2,-2) to (2,2,2) + 3*3*3 // (1,1,1) to (3,3,3) - 2*2*2 // (1,1,1) to (2,2,2) overlap + 11*11*11; // (10,10,10) to (20,20,20) CPPUNIT_ASSERT_EQUAL(expectedCount, tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); CPPUNIT_ASSERT_EQUAL(Coord(20), xyzMax); // "Undo" previous fill from (10,10,10) to (20,20,20). tree.fill(CoordBBox(Coord(10), Coord(20)), background, /*active=*/false); xyzMin = Coord::max(); xyzMax = Coord::min(); for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { xyz = iter.getCoord(); xyzMin = std::min(xyzMin, xyz); xyzMax = std::max(xyz, xyzMax); const float expectedValue = (xyz[0] >= 1 && xyz[1] >= 1 && xyz[2] >= 1 && xyz[0] <= 3 && xyz[1] <= 3 && xyz[2] <= 3) ? 3.0 : 2.0; ASSERT_DOUBLES_EXACTLY_EQUAL(expectedValue, *iter); } expectedCount = 5*5*5 // (-2,-2,-2) to (2,2,2) + 3*3*3 // (1,1,1) to (3,3,3) - 2*2*2; // (1,1,1) to (2,2,2) overlap CPPUNIT_ASSERT_EQUAL(expectedCount, tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); CPPUNIT_ASSERT_EQUAL(Coord( 3), xyzMax); // The following tests assume a [3,2,3] tree configuration. tree.clear(); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node // Partially fill a single leaf node. tree.fill(CoordBBox(Coord(8), Coord(14)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); // Completely fill the leaf node, replacing it with a tile. tree.fill(CoordBBox(Coord(8), Coord(15)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); { const int activeVoxelCount = int(tree.activeVoxelCount()); // Fill a single voxel of the tile with a different (active) value. tree.fill(CoordBBox(Coord(10), Coord(10)), 1.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); CPPUNIT_ASSERT_EQUAL(activeVoxelCount, int(tree.activeVoxelCount())); // Fill the voxel with an inactive value. tree.fill(CoordBBox(Coord(10), Coord(10)), 1.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); CPPUNIT_ASSERT_EQUAL(activeVoxelCount - 1, int(tree.activeVoxelCount())); // Completely fill the leaf node, replacing it with a tile again. tree.fill(CoordBBox(Coord(8), Coord(15)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); } // Expand by one voxel, creating seven neighboring leaf nodes. tree.fill(CoordBBox(Coord(8), Coord(16)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); // Completely fill the internal node containing the tile, replacing it with // a tile at the next level of the tree. tree.fill(CoordBBox(Coord(0), Coord(31)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2), tree.nonLeafCount()); // Expand by one voxel, creating a layer of leaf nodes on three faces. tree.fill(CoordBBox(Coord(0), Coord(32)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(5*5 + 4*5 + 4*4), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2 + 7), tree.nonLeafCount()); // +7 internal nodes // Completely fill the second-level internal node, replacing it with a root-level tile. tree.fill(CoordBBox(Coord(0), Coord(255)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // Repeat, filling with an inactive value. tree.clear(); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node // Partially fill a single leaf node. tree.fill(CoordBBox(Coord(8), Coord(14)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); // Completely fill the leaf node, replacing it with a tile. tree.fill(CoordBBox(Coord(8), Coord(15)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); // Expand by one voxel, creating seven neighboring leaf nodes. tree.fill(CoordBBox(Coord(8), Coord(16)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); // Completely fill the internal node containing the tile, replacing it with // a tile at the next level of the tree. tree.fill(CoordBBox(Coord(0), Coord(31)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2), tree.nonLeafCount()); // Expand by one voxel, creating a layer of leaf nodes on three faces. tree.fill(CoordBBox(Coord(0), Coord(32)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(5*5 + 4*5 + 4*4), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2 + 7), tree.nonLeafCount()); // +7 internal nodes // Completely fill the second-level internal node, replacing it with a root-level tile. tree.fill(CoordBBox(Coord(0), Coord(255)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); tree.clear(); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node CPPUNIT_ASSERT(tree.empty()); // Partially fill a region with inactive background values. tree.fill(CoordBBox(Coord(27), Coord(254)), background, /*active=*/false); // Confirm that after pruning, the tree is empty. openvdb::tools::prune(tree); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node CPPUNIT_ASSERT(tree.empty()); } // Verify that setting voxels inside active tiles works correctly. // In particular, it should preserve the active states of surrounding voxels. void TestTreeGetSetValues::testSetActiveStates() { using namespace openvdb; const float background = 256.0; Tree323f tree(background); const Coord xyz(10); const float val = 42.0; const int expectedActiveCount = 32 * 32 * 32; #define RESET_TREE() \ tree.fill(CoordBBox(Coord(0), Coord(31)), background, /*active=*/true) // create an active tile RESET_TREE(); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); tree.setValueOff(xyz); CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 1, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); RESET_TREE(); tree.setValueOn(xyz); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); RESET_TREE(); tree.setValueOff(xyz, val); CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 1, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(val, tree.getValue(xyz)); RESET_TREE(); tree.setActiveState(xyz, true); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); RESET_TREE(); tree.setActiveState(xyz, false); CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 1, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); RESET_TREE(); tree.setValueOn(xyz, val); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(val, tree.getValue(xyz)); RESET_TREE(); tools::setValueOnMin(tree, xyz, val); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(std::min(val, background), tree.getValue(xyz)); RESET_TREE(); tools::setValueOnMax(tree, xyz, val); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(std::max(val, background), tree.getValue(xyz)); RESET_TREE(); tools::setValueOnSum(tree, xyz, val); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(val + background, tree.getValue(xyz)); #undef RESET_TREE } void TestTreeGetSetValues::testHasActiveTiles() { Tree323f tree(/*background=*/256.0f); CPPUNIT_ASSERT(!tree.hasActiveTiles()); // Fill from (-2,-2,-2) to (2,2,2) with active value 2. tree.fill(openvdb::CoordBBox(openvdb::Coord(-2), openvdb::Coord(2)), 2.0f); CPPUNIT_ASSERT(!tree.hasActiveTiles()); // Fill from (-200,-200,-200) to (-4,-4,-4) with active value 3. tree.fill(openvdb::CoordBBox(openvdb::Coord(-200), openvdb::Coord(-4)), 3.0f); CPPUNIT_ASSERT(tree.hasActiveTiles()); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestName.cc0000644000000000000000000000662012603226506015125 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMath.cc0000644000000000000000000002047612603226506015143 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include class TestMath: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestMath); CPPUNIT_TEST(testAll); CPPUNIT_TEST(testRandomInt); CPPUNIT_TEST(testRandom01); CPPUNIT_TEST(testMinMaxIndex); CPPUNIT_TEST_SUITE_END(); void testAll(); void testRandomInt(); void testRandom01(); void testMinMaxIndex(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMath); // This suite of tests obviously needs to be expanded! void TestMath::testAll() { using namespace openvdb; {// Sign CPPUNIT_ASSERT_EQUAL(math::Sign( 3 ), 1); CPPUNIT_ASSERT_EQUAL(math::Sign(-1.0 ),-1); CPPUNIT_ASSERT_EQUAL(math::Sign( 0.0f), 0); } {// SignChange CPPUNIT_ASSERT( math::SignChange( -1, 1)); CPPUNIT_ASSERT(!math::SignChange( 0.0f, 0.5f)); CPPUNIT_ASSERT( math::SignChange( 0.0f,-0.5f)); CPPUNIT_ASSERT( math::SignChange(-0.1, 0.0001)); } {// isApproxZero CPPUNIT_ASSERT( math::isApproxZero( 0.0f)); CPPUNIT_ASSERT(!math::isApproxZero( 9.0e-6f)); CPPUNIT_ASSERT(!math::isApproxZero(-9.0e-6f)); CPPUNIT_ASSERT( math::isApproxZero( 9.0e-9f)); CPPUNIT_ASSERT( math::isApproxZero(-9.0e-9f)); CPPUNIT_ASSERT( math::isApproxZero( 0.01, 0.1)); } {// Cbrt const double a = math::Cbrt(3.0); CPPUNIT_ASSERT(math::isApproxEqual(a*a*a, 3.0, 1e-6)); } {// isNegative CPPUNIT_ASSERT(!boost::is_signed::value); CPPUNIT_ASSERT(boost::is_signed::value); CPPUNIT_ASSERT(!boost::is_signed::value); //CPPUNIT_ASSERT(boost::is_signed::value);//fails! //CPPUNIT_ASSERT(boost::is_signed::value);//fails! CPPUNIT_ASSERT( math::isNegative(-1.0f)); CPPUNIT_ASSERT(!math::isNegative( 1.0f)); CPPUNIT_ASSERT( math::isNegative(-1.0)); CPPUNIT_ASSERT(!math::isNegative( 1.0)); CPPUNIT_ASSERT(!math::isNegative(true)); CPPUNIT_ASSERT(!math::isNegative(false)); CPPUNIT_ASSERT(!math::isNegative(1u)); CPPUNIT_ASSERT( math::isNegative(-1)); CPPUNIT_ASSERT(!math::isNegative( 1)); } } void TestMath::testRandomInt() { using openvdb::math::RandomInt; int imin = -3, imax = 11; RandomInt rnd(/*seed=*/42, imin, imax); // Generate a sequence of random integers and verify that they all fall // in the interval [imin, imax]. std::vector seq(100); for (int i = 0; i < 100; ++i) { seq[i] = rnd(); CPPUNIT_ASSERT(seq[i] >= imin); CPPUNIT_ASSERT(seq[i] <= imax); } // Verify that generators with the same seed produce the same sequence. rnd = RandomInt(42, imin, imax); for (int i = 0; i < 100; ++i) { int r = rnd(); CPPUNIT_ASSERT_EQUAL(seq[i], r); } // Verify that generators with different seeds produce different sequences. rnd = RandomInt(101, imin, imax); std::vector newSeq(100); for (int i = 0; i < 100; ++i) newSeq[i] = rnd(); CPPUNIT_ASSERT(newSeq != seq); // Temporarily change the range. imin = -5; imax = 6; for (int i = 0; i < 100; ++i) { int r = rnd(imin, imax); CPPUNIT_ASSERT(r >= imin); CPPUNIT_ASSERT(r <= imax); } // Verify that the range change was temporary. imin = -3; imax = 11; for (int i = 0; i < 100; ++i) { int r = rnd(); CPPUNIT_ASSERT(r >= imin); CPPUNIT_ASSERT(r <= imax); } // Permanently change the range. imin = -5; imax = 6; rnd.setRange(imin, imax); for (int i = 0; i < 100; ++i) { int r = rnd(); CPPUNIT_ASSERT(r >= imin); CPPUNIT_ASSERT(r <= imax); } // Verify that it is OK to specify imin > imax (they are automatically swapped). imin = 5; imax = -6; rnd.setRange(imin, imax); rnd = RandomInt(42, imin, imax); } void TestMath::testRandom01() { using openvdb::math::Random01; using openvdb::math::isApproxEqual; Random01 rnd(/*seed=*/42); // Generate a sequence of random numbers and verify that they all fall // in the interval [0, 1). std::vector seq(100); for (int i = 0; i < 100; ++i) { seq[i] = rnd(); CPPUNIT_ASSERT(seq[i] >= 0.0); CPPUNIT_ASSERT(seq[i] < 1.0); } // Verify that generators with the same seed produce the same sequence. rnd = Random01(42); for (int i = 0; i < 100; ++i) { CPPUNIT_ASSERT_DOUBLES_EQUAL(seq[i], rnd(), /*tolerance=*/1.0e-6); } // Verify that generators with different seeds produce different sequences. rnd = Random01(101); bool allEqual = true; for (int i = 0; allEqual && i < 100; ++i) { if (!isApproxEqual(rnd(), seq[i])) allEqual = false; } CPPUNIT_ASSERT(!allEqual); } void TestMath::testMinMaxIndex() { const openvdb::Vec3R a(-1, 2, 0); CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MinIndex(a)); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MaxIndex(a)); const openvdb::Vec3R b(-1, -2, 0); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MinIndex(b)); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(b)); const openvdb::Vec3R c(5, 2, 1); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(c)); CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MaxIndex(c)); const openvdb::Vec3R d(0, 0, 1); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MinIndex(d)); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(d)); const openvdb::Vec3R e(1, 0, 0); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(e)); CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MaxIndex(e)); const openvdb::Vec3R f(0, 1, 0); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(f)); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MaxIndex(f)); const openvdb::Vec3R g(1, 1, 0); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(g)); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MaxIndex(g)); const openvdb::Vec3R h(1, 0, 1); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MinIndex(h)); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(h)); const openvdb::Vec3R i(0, 1, 1); CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MinIndex(i)); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(i)); const openvdb::Vec3R j(1, 1, 1); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(j)); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(j)); } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestUtil.cc0000644000000000000000000006104712603226506015166 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 BENCHMARK_PAGED_ARRAY // For benchmark comparisons #ifdef BENCHMARK_PAGED_ARRAY #include // for std::vector #include // for std::deque #include // for tbb::concurrent_vector #endif class TestUtil: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestUtil); CPPUNIT_TEST(testCpuTimer); CPPUNIT_TEST(testPagedArray); CPPUNIT_TEST_SUITE_END(); void testCpuTimer(); void testPagedArray(); // Multi-threading T::push_back template struct Functor { Functor(size_t begin, size_t end, size_t grainSize, T& _d, bool threaded = true) : d(&_d) { if (threaded) { tbb::parallel_for(tbb::blocked_range(begin, end, grainSize), *this); } else { (*this)(tbb::blocked_range(begin, end)); } } void operator()(const tbb::blocked_range& r) const { for (size_t i=r.begin(), n=r.end(); i!=n; ++i) d->push_back(i); } T* d; }; // Multi-threading T::ValueBuffer::push_back template struct Functor2 { Functor2(size_t begin, size_t end, size_t grainSize, T& d, bool threaded = true) : buffer(d) { if (threaded) { tbb::parallel_for(tbb::blocked_range(begin, end, grainSize), *this); } else { (*this)(tbb::blocked_range(begin, end)); } } void operator()(const tbb::blocked_range& r) const { for (size_t i=r.begin(), n=r.end(); i!=n; ++i) buffer.push_back(i); } mutable typename T::ValueBuffer buffer; }; // Thread Local Storage version template struct Functor3 { Functor3(size_t begin, size_t end, size_t grainSize, T& _pool, bool threaded = true) : pool(&_pool) { if (threaded) { tbb::parallel_for(tbb::blocked_range(begin, end, grainSize), *this); } else { (*this)(tbb::blocked_range(begin, end)); } } void operator()(const tbb::blocked_range& r) const { typename T::reference buffer = pool->local(); for (size_t i=r.begin(), n=r.end(); i!=n; ++i) buffer.push_back(i); } T* pool; }; }; CPPUNIT_TEST_SUITE_REGISTRATION(TestUtil); void TestUtil::testCpuTimer() { const int expected = 259, tolerance = 10;//milliseconds const tbb::tick_count::interval_t sec(expected/1000.0); openvdb::util::CpuTimer timer; tbb::this_tbb_thread::sleep(sec); const int actual1 = static_cast(timer.delta()); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual1, tolerance); tbb::this_tbb_thread::sleep(sec); const int actual2 = static_cast(timer.delta()); CPPUNIT_ASSERT_DOUBLES_EQUAL(2*expected, actual2, tolerance); } void TestUtil::testPagedArray() { #ifdef BENCHMARK_PAGED_ARRAY const size_t problemSize = 2560000; openvdb::util::CpuTimer timer; std::cerr << "\nProblem size for benchmark: " << problemSize << std::endl; #else const size_t problemSize = 256000; #endif {//serial PagedArray::push_back (manual) openvdb::util::PagedArray d; CPPUNIT_ASSERT(d.isEmpty()); CPPUNIT_ASSERT_EQUAL(0UL, d.size()); CPPUNIT_ASSERT_EQUAL(8UL, d.log2PageSize()); CPPUNIT_ASSERT_EQUAL(1UL< ArrayT; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("Serial test 1: Default page size"); #endif Functor t(0, problemSize, ArrayT::pageSize(), d, false); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(10UL, d.log2PageSize()); CPPUNIT_ASSERT_EQUAL(1UL<> log2PageSize CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); for (size_t i=0, n=d.size(); i ArrayT; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel test 1: Default page size"); #endif Functor t(0, problemSize, ArrayT::pageSize(), d); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(10UL, d.log2PageSize()); CPPUNIT_ASSERT_EQUAL(1UL<> log2PageSize CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel sort with a default page size"); #endif d.sort(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif for (size_t i=0, n=d.size(); i> log2PageSize CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); } {//parallel PagedArray::push_back typedef openvdb::util::PagedArray ArrayT; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel test 1: Page size of only 8"); #endif Functor t(0, problemSize, ArrayT::pageSize(), d); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(3UL, d.log2PageSize()); CPPUNIT_ASSERT_EQUAL(1UL<> log2PageSize CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel sort with a page size of only 8"); #endif d.sort(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif for (size_t i=0, n=d.size(); i> log2PageSize CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); } #ifdef BENCHMARK_PAGED_ARRAY {//benchmark against a std::vector timer.start("std::vector"); std::vector v; for (size_t i=0; i d; for (size_t i=0; i d2; CPPUNIT_ASSERT_EQUAL(0UL, d2.size()); d2.resize(1234); CPPUNIT_ASSERT_EQUAL(1234UL, d2.size()); } {//benchmark against a tbb::concurrent_vector::push_back timer.start("serial tbb::concurrent_vector::push_back"); tbb::concurrent_vector v; for (size_t i=0; i ArrayT; Functor > t(0, problemSize, ArrayT::pageSize(), v); timer.stop(); } #endif {//benchmark against a PagedArray::push_back #ifdef BENCHMARK_PAGED_ARRAY timer.start("serial PagedArray::push_back"); #endif openvdb::util::PagedArray d; for (size_t i=0; i d; for (size_t i=0; i ArrayT; ArrayT d; CPPUNIT_ASSERT_EQUAL(0UL, d.size()); d.resize(problemSize); CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(1UL<> log2PageSize CPPUNIT_ASSERT_EQUAL((problemSize-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); d.clear(); #ifdef BENCHMARK_PAGED_ARRAY timer.start("serial PagedArray::push_back"); #endif Functor t(0, problemSize, ArrayT::pageSize(), d, false); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(1UL<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); d.clear(); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel PagedArray::push_back"); #endif Functor tmp1(0, problemSize, ArrayT::pageSize(), d, true); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(1UL<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); d.clear(); #ifdef BENCHMARK_PAGED_ARRAY timer.start("serial PagedArray::ValueBuffer::push_back"); #endif Functor2 tmp2(0, problemSize, ArrayT::pageSize(), d, false); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(1UL<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); d.clear(); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel PagedArray::ValueBuffer::push_back"); #endif Functor2 tmp3(0, problemSize, ArrayT::pageSize(), d, true); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(1UL<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); CPPUNIT_ASSERT_EQUAL(problemSize, d.push_back(1)); CPPUNIT_ASSERT_EQUAL(problemSize+1, d.size()); CPPUNIT_ASSERT_EQUAL(1UL<> log2PageSize CPPUNIT_ASSERT_EQUAL(1UL+(problemSize>>d.log2PageSize()), d.pageCount()); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); const size_t v = 13; d.fill(v); for (size_t i=0, n=d.capacity(); i ArrayT; ArrayT d; CPPUNIT_ASSERT_EQUAL(0UL, d.size()); { ArrayT::ValueBuffer vc(d); vc.push_back(1); vc.push_back(2); CPPUNIT_ASSERT_EQUAL(0UL, d.size()); vc.flush(); CPPUNIT_ASSERT_EQUAL(2UL, d.size()); CPPUNIT_ASSERT_EQUAL(1UL, d[0]); CPPUNIT_ASSERT_EQUAL(2UL, d[1]); } CPPUNIT_ASSERT_EQUAL(2UL, d.size()); CPPUNIT_ASSERT_EQUAL(1UL, d[0]); CPPUNIT_ASSERT_EQUAL(2UL, d[1]); } {//parallel PagedArray::push_back followed by parallel sort typedef openvdb::util::PagedArray ArrayT; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel PagedArray::push_back"); #endif Functor t(0, problemSize, ArrayT::pageSize(), d); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(1UL<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); // Not guaranteed to pass //size_t unsorted = 0; //for (size_t i=0, n=d.size(); i 0 ); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel sort"); #endif d.sort(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif for (size_t i=0, n=d.size(); i ArrayT; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("serial PagedArray::ValueBuffer::push_back"); #endif Functor2 t(0, problemSize, ArrayT::pageSize(), d, false); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(1UL<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); size_t unsorted = 0; for (size_t i=0, n=d.size(); i ArrayT; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel PagedArray::ValueBuffer::push_back"); #endif Functor2 t(0, problemSize, ArrayT::pageSize(), d, true); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(1UL<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); // Not guaranteed to pass //size_t unsorted = 0; //for (size_t i=0, n=d.size(); i 0 ); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel sort"); #endif d.sort(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif for (size_t i=0, n=d.size(); i ArrayT; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel TLS PagedArray::ValueBuffer::push_back"); #endif ArrayT::ValueBuffer tmp(d); typedef tbb::enumerable_thread_specific BufferT; BufferT buffer(tmp); Functor3 t(0, problemSize, ArrayT::pageSize(), buffer, true); for (BufferT::iterator i = buffer.begin(); i != buffer.end(); ++i) i->flush(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif //std::cerr << "Number of threads for TLS = " << (buffer.end()-buffer.begin()) << std::endl; //d.print(); CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(1UL<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); // Not guaranteed to pass //size_t unsorted = 0; //for (size_t i=0, n=d.size(); i 0 ); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel sort"); #endif d.sort(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif for (size_t i=0, n=d.size(); i ArrayT; ArrayT d, d2; Functor2 t1(0, problemSize, ArrayT::pageSize(), d, true); CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(1UL<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); CPPUNIT_ASSERT(!d.isPartiallyFull()); d.push_back(problemSize); CPPUNIT_ASSERT(d.isPartiallyFull()); Functor2 t2(problemSize+1, 2*problemSize+1, ArrayT::pageSize(), d2, true); //for (size_t i=d.size(), n=i+problemSize; i>d2.log2PageSize(), d2.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d2.pageCount()*d2.pageSize(), d2.capacity()); //d.print(); //d2.print(); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel PagedArray::merge"); #endif d.merge(d2); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT(d.isPartiallyFull()); //d.print(); //d2.print(); CPPUNIT_ASSERT_EQUAL(2*problemSize+1, d.size()); CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(0UL, d2.size()); CPPUNIT_ASSERT_EQUAL(0UL, d2.pageCount()); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel sort of merged array"); #endif d.sort(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif for (size_t i=0, n=d.size(); i array; for (int i=0; i<100000; ++i) array.push_back(i); for (int i=0; i<100000; ++i) CPPUNIT_ASSERT_EQUAL(i, array[i]); } {//2A openvdb::util::PagedArray array; openvdb::util::PagedArray::ValueBuffer buffer(array); for (int i=0; i<100000; ++i) buffer.push_back(i); buffer.flush(); for (int i=0; i<100000; ++i) CPPUNIT_ASSERT_EQUAL(i, array[i]); } {//2B openvdb::util::PagedArray array; {//local scope of a single thread openvdb::util::PagedArray::ValueBuffer buffer(array); for (int i=0; i<100000; ++i) buffer.push_back(i); } for (int i=0; i<100000; ++i) CPPUNIT_ASSERT_EQUAL(i, array[i]); } {//3A openvdb::util::PagedArray array; array.resize(100000); for (int i=0; i<100000; ++i) array[i] = i; for (int i=0; i<100000; ++i) CPPUNIT_ASSERT_EQUAL(i, array[i]); } {//3B typedef openvdb::util::PagedArray ArrayT; ArrayT array; array.resize(100000); for (ArrayT::Iterator i=array.begin(); i!=array.end(); ++i) *i = int(i.pos()); for (int i=0; i<100000; ++i) CPPUNIT_ASSERT_EQUAL(i, array[i]); } } } // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/main.cc0000644000000000000000000001615312603226506014333 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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_SUCCESS #include // for strrchr() #include #include #include #include #include #include #include #include #include #include #ifdef OPENVDB_USE_LOG4CPLUS #include #include #include #endif void usage(const char* progName) { std::cerr << "Usage: " << progName << " [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 } 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[]) { const char* progName = argv[0]; if (const char* ptr = ::strrchr(progName, '/')) progName = ptr + 1; bool verbose = false; std::vector tests; for (int i = 1; i < argc; ++i) { const std::string arg = argv[i]; if (arg == "-l") { dump(CppUnit::TestFactoryRegistry::getRegistry().makeTest()); return EXIT_SUCCESS; } else if (arg == "-v") { verbose = true; } else if (arg == "-t") { if (i + 1 < argc) { ++i; tests.push_back(argv[i]); } else { usage(progName); return EXIT_FAILURE; } } else if (arg == "-h" || arg == "-help" || arg == "--help") { usage(progName); return EXIT_SUCCESS; } else { std::cerr << progName << ": unrecognized option '" << arg << "'\n"; usage(progName); return EXIT_FAILURE; } } if (tests.empty()) tests.push_back(""); // run all tests 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); } for (size_t i = 0; i < tests.size(); ++i) { runner.run(controller, tests[i]); } CppUnit::CompilerOutputter outputter(&result, std::cerr); outputter.write(); return result.wasSuccessful() ? EXIT_SUCCESS : EXIT_FAILURE; } catch (std::exception& e) { 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()); } const std::string quietArg("-quiet"); std::vector args(argv, argv + argc); if (quiet) args.insert(++args.begin(), quietArg.c_str()); int numArgs = int(args.size()); logging_base::Config config(numArgs, &args[0]); logging_base::configure(config); return pdevunit::run(numArgs, const_cast(&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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestCurl.cc0000644000000000000000000005367612603226506015167 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/1e-6); namespace { const int GRID_DIM = 10; } class TestCurl: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestCurl); CPPUNIT_TEST(testISCurl); // Gradient in Index Space CPPUNIT_TEST(testISCurlStencil); CPPUNIT_TEST(testWSCurl); // Gradient in World Space CPPUNIT_TEST(testWSCurlStencil); CPPUNIT_TEST(testCurlTool); // Gradient tool CPPUNIT_TEST(testCurlMaskedTool); // Gradient tool CPPUNIT_TEST_SUITE_END(); void testISCurl(); void testISCurlStencil(); void testWSCurl(); void testWSCurlStencil(); void testCurlTool(); void testCurlMaskedTool(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestCurl); void TestCurl::testCurlTool() { using namespace openvdb; VectorGrid::Ptr inGrid = VectorGrid::create(); const VectorTree& inTree = inGrid->tree(); CPPUNIT_ASSERT(inTree.empty()); VectorGrid::Accessor inAccessor = inGrid->getAccessor(); int dim = GRID_DIM; for (int x = -dim; xactiveVoxelCount())); VectorGrid::ConstAccessor curlAccessor = curl_grid->getConstAccessor(); --dim;//ignore boundary curl vectors for (int x = -dim; x #include #include #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; #ifndef OPENVDB_2_ABI_COMPATIBLE /// @brief Clip this grid to the given world-space bounding box. /// @details Voxels that lie outside the bounding box are set to the background. /// @warning Clipping a level set will likely produce a grid that is /// no longer a valid level set. void clipGrid(const BBoxd&); /// @brief Clip this grid to the given index-space bounding box. /// @details Voxels that lie outside the bounding box are set to the background. /// @warning Clipping a level set will likely produce a grid that is /// no longer a valid level set. virtual void clip(const CoordBBox&) = 0; #endif // // 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; #ifndef OPENVDB_2_ABI_COMPATIBLE /// Read all of this grid's data buffers that intersect the given index-space bounding box. virtual void readBuffers(std::istream&, const CoordBBox&) = 0; /// @brief Read all of this grid's data buffers that are not yet resident in memory /// (because delayed loading is in effect). /// @details If this grid was read from a memory-mapped file, this operation /// disconnects the grid from the file. /// @sa io::File::open, io::MappedFile virtual void readNonresidentBuffers() const = 0; #endif /// 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 _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; typedef typename tree::ValueAccessor<_TreeType, true> Accessor; typedef typename tree::ValueAccessor ConstAccessor; typedef typename tree::ValueAccessor<_TreeType, false> UnsafeAccessor; typedef typename tree::ValueAccessor ConstUnsafeAccessor; /// @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(); } /// @brief Return this grid's background value. /// /// @note Use tools::changeBackground to efficiently modify the background values. const ValueType& background() const { return mTree->background(); } /// 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(); } /// @brief Return an accessor that provides random read and write access /// to this grid's voxels. The accessor is safe in the sense that /// it is registered by the tree of this grid. Accessor getAccessor() { return Accessor(tree()); } /// @brief Return an accessor that provides random read and write access /// to this grid's voxels. The accessor is unsafe in the sense that /// it is not registered by the tree of this grid. In some rare /// cases this can give a performance advantage over a registered /// accessor but it is unsafe if the tree topology is modified. /// /// @warning Only use this method if you're an expert and know the /// risks of using an unregistered accessor (see tree/ValueAccessor.h) Accessor getUnsafeAccessor() { return UnsafeAccessor(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()); } //@} /// @brief Return an accessor that provides random read-only access /// to this grid's voxels. The accessor is unsafe in the sense that /// it is not registered by the tree of this grid. In some rare /// cases this can give a performance advantage over a registered /// accessor but it is unsafe if the tree topology is modified. /// /// @warning Only use this method if you're an expert and know the /// risks of using an unregistered accessor (see tree/ValueAccessor.h) ConstAccessor getConstUnsafeAccessor() const { return ConstUnsafeAccessor(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); /// Reduce the memory footprint of this grid by increasing its sparseness. virtual void pruneGrid(float tolerance = 0.0); #ifndef OPENVDB_2_ABI_COMPATIBLE /// @brief Clip this grid to the given index-space bounding box. /// @details Voxels that lie outside the bounding box are set to the background. /// @warning Clipping a level set will likely produce a grid that is /// no longer a valid level set. virtual void clip(const CoordBBox&); #endif /// @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&); #ifndef OPENVDB_2_ABI_COMPATIBLE /// Read all of this grid's data buffers that intersect the given index-space bounding box. virtual void readBuffers(std::istream&, const CoordBBox&); /// @brief Read all of this grid's data buffers that are not yet resident in memory /// (because delayed loading is in effect). /// @details If this grid was read from a memory-mapped file, this operation /// disconnects the grid from the file. /// @sa io::File::open, io::MappedFile virtual void readNonresidentBuffers() const; #endif /// 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::pruneGrid(float tolerance) { this->tree().prune(ValueType(zeroVal() + tolerance)); } #ifndef OPENVDB_2_ABI_COMPATIBLE template inline void Grid::clip(const CoordBBox& bbox) { tree().clip(bbox); } #endif 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()); } #ifndef OPENVDB_2_ABI_COMPATIBLE template inline void Grid::readBuffers(std::istream& is, const CoordBBox& bbox) { tree().readBuffers(is, bbox, saveFloatAsHalf()); } template inline void Grid::readNonresidentBuffers() const { tree().readNonresidentBuffers(); } #endif // !OPENVDB_2_ABI_COMPATIBLE 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/0000755000000000000000000000000012603226506011622 5ustar rootrootopenvdb/io/GridDescriptor.cc0000644000000000000000000001432112603226506015056 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "GridDescriptor.h" #include #include // for boost::ends_with() #include // for boost::erase_last() #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { namespace { // In order not to break backward compatibility with existing VDB files, // grids stored using 16-bit half floats are flagged by adding the following // suffix to the grid's type name on output. The suffix is removed on input // and the grid's "save float as half" flag set accordingly. const char* HALF_FLOAT_TYPENAME_SUFFIX = "_HalfFloat"; const char* SEP = "\x1e"; // ASCII "record separator" } GridDescriptor::GridDescriptor(): mSaveFloatAsHalf(false), mGridPos(0), mBlockPos(0), mEndPos(0) { } GridDescriptor::GridDescriptor(const Name &name, const Name &type, bool half): mGridName(stripSuffix(name)), mUniqueName(name), mGridType(type), mSaveFloatAsHalf(half), mGridPos(0), mBlockPos(0), mEndPos(0) { } GridDescriptor::~GridDescriptor() { } void GridDescriptor::writeHeader(std::ostream &os) const { writeString(os, mUniqueName); Name gridType = mGridType; if (mSaveFloatAsHalf) gridType += HALF_FLOAT_TYPENAME_SUFFIX; writeString(os, gridType); writeString(os, mInstanceParentName); } void GridDescriptor::writeStreamPos(std::ostream &os) const { os.write(reinterpret_cast(&mGridPos), sizeof(boost::int64_t)); os.write(reinterpret_cast(&mBlockPos), sizeof(boost::int64_t)); os.write(reinterpret_cast(&mEndPos), sizeof(boost::int64_t)); } GridBase::Ptr GridDescriptor::read(std::istream &is) { // Read in the name. mUniqueName = readString(is); mGridName = stripSuffix(mUniqueName); // Read in the grid type. mGridType = readString(is); if (boost::ends_with(mGridType, HALF_FLOAT_TYPENAME_SUFFIX)) { mSaveFloatAsHalf = true; boost::erase_last(mGridType, HALF_FLOAT_TYPENAME_SUFFIX); } if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_GRID_INSTANCING) { mInstanceParentName = readString(is); } // Create the grid of the type if it has been registered. if (!GridBase::isRegistered(mGridType)) { OPENVDB_THROW(LookupError, "Cannot read grid." << " Grid type " << mGridType << " is not registered."); } // else GridBase::Ptr grid = GridBase::createGrid(mGridType); if (grid) grid->setSaveFloatAsHalf(mSaveFloatAsHalf); // Read in the offsets. is.read(reinterpret_cast(&mGridPos), sizeof(boost::int64_t)); is.read(reinterpret_cast(&mBlockPos), sizeof(boost::int64_t)); is.read(reinterpret_cast(&mEndPos), sizeof(boost::int64_t)); return grid; } void GridDescriptor::seekToGrid(std::istream &is) const { is.seekg(mGridPos, std::ios_base::beg); } void GridDescriptor::seekToBlocks(std::istream &is) const { is.seekg(mBlockPos, std::ios_base::beg); } void GridDescriptor::seekToEnd(std::istream &is) const { is.seekg(mEndPos, std::ios_base::beg); } void GridDescriptor::seekToGrid(std::ostream &os) const { os.seekp(mGridPos, std::ios_base::beg); } void GridDescriptor::seekToBlocks(std::ostream &os) const { os.seekp(mBlockPos, std::ios_base::beg); } void GridDescriptor::seekToEnd(std::ostream &os) const { os.seekp(mEndPos, std::ios_base::beg); } //////////////////////////////////////// // static Name GridDescriptor::addSuffix(const Name& name, int n) { std::ostringstream ostr; ostr << name << SEP << n; return ostr.str(); } // static Name GridDescriptor::stripSuffix(const Name& name) { return name.substr(0, name.find(SEP)); } // static std::string GridDescriptor::nameAsString(const Name& name) { std::string::size_type pos = name.find(SEP); if (pos == std::string::npos) return name; return name.substr(0, pos) + "[" + name.substr(pos + 1) + "]"; } //static Name GridDescriptor::stringAsUniqueName(const std::string& s) { Name ret = s; if (!ret.empty() && *ret.rbegin() == ']') { // found trailing ']' std::string::size_type pos = ret.find("["); // Replace "[N]" with SEP "N". if (pos != std::string::npos) { ret.resize(ret.size() - 1); // drop trailing ']' ret.replace(ret.find("["), 1, SEP); } } return ret; } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Compression.h0000644000000000000000000006513512603226506014306 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_IO_COMPRESSION_HAS_BEEN_INCLUDED #define OPENVDB_IO_COMPRESSION_HAS_BEEN_INCLUDED #include #include // for negative() #include "io.h" // for getDataCompression(), etc. #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { /// @brief OR-able bit flags for compression options on input and output streams /// @details ///
///
COMPRESS_NONE ///
On write, don't compress data.
/// On read, the input stream contains uncompressed data. /// ///
COMPRESS_ZIP ///
When writing grids other than level sets or fog volumes, apply /// ZLIB compression to internal and leaf node value buffers.
/// When reading grids other than level sets or fog volumes, indicate that /// the value buffers of internal and leaf nodes are ZLIB-compressed.
/// ZLIB compresses well but is slow. /// ///
COMPRESS_ACTIVE_MASK ///
When writing a grid of any class, don't output a node's inactive values /// if it has two or fewer distinct values. Instead, output minimal information /// to permit the lossless reconstruction of inactive values.
/// On read, nodes might have been stored without inactive values. /// Where necessary, reconstruct inactive values from available information. /// ///
COMPRESS_BLOSC ///
When writing grids other than level sets or fog volumes, apply /// Blosc compression to internal and leaf node value buffers.
/// When reading grids other than level sets or fog volumes, indicate that /// the value buffers of internal and leaf nodes are Blosc-compressed.
/// Blosc is much faster than ZLIB and produces comparable file sizes. ///
enum { COMPRESS_NONE = 0, COMPRESS_ZIP = 0x1, COMPRESS_ACTIVE_MASK = 0x2, COMPRESS_BLOSC = 0x4 }; /// Return a string describing the given compression flags. OPENVDB_API std::string compressionToString(uint32_t flags); //////////////////////////////////////// /// @internal Per-node indicator byte that specifies what additional metadata /// is stored to permit reconstruction of inactive values enum { /*0*/ NO_MASK_OR_INACTIVE_VALS, // no inactive vals, or all inactive vals are +background /*1*/ NO_MASK_AND_MINUS_BG, // all inactive vals are -background /*2*/ NO_MASK_AND_ONE_INACTIVE_VAL, // all inactive vals have the same non-background val /*3*/ MASK_AND_NO_INACTIVE_VALS, // mask selects between -background and +background /*4*/ MASK_AND_ONE_INACTIVE_VAL, // mask selects between backgd and one other inactive val /*5*/ MASK_AND_TWO_INACTIVE_VALS, // mask selects between two non-background inactive vals /*6*/ NO_MASK_AND_ALL_VALS // > 2 inactive vals, so no mask compression at all }; //////////////////////////////////////// /// @brief RealToHalf and its specializations define a mapping from /// floating-point data types to analogous half float types. template struct RealToHalf { enum { isReal = false }; // unless otherwise specified, type T is not a floating-point type typedef T HalfT; // type T's half float analogue is T itself static HalfT convert(const T& val) { return val; } }; template<> struct RealToHalf { enum { isReal = true }; typedef half HalfT; static HalfT convert(float val) { return HalfT(val); } }; template<> struct RealToHalf { enum { isReal = true }; typedef half HalfT; // A half can only be constructed from a float, so cast the value to a float first. static HalfT convert(double val) { return HalfT(float(val)); } }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec2H HalfT; static HalfT convert(const Vec2s& val) { return HalfT(val); } }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec2H HalfT; // A half can only be constructed from a float, so cast the vector's elements to floats first. static HalfT convert(const Vec2d& val) { return HalfT(Vec2s(val)); } }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec3H HalfT; static HalfT convert(const Vec3s& val) { return HalfT(val); } }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec3H HalfT; // A half can only be constructed from a float, so cast the vector's elements to floats first. static HalfT convert(const Vec3d& val) { return HalfT(Vec3s(val)); } }; /// Return the given value truncated to 16-bit float precision. template inline T truncateRealToHalf(const T& val) { return T(RealToHalf::convert(val)); } //////////////////////////////////////// OPENVDB_API void zipToStream(std::ostream&, const char* data, size_t numBytes); OPENVDB_API void unzipFromStream(std::istream&, char* data, size_t numBytes); OPENVDB_API void bloscToStream(std::ostream&, const char* data, size_t valSize, size_t numVals); OPENVDB_API void bloscFromStream(std::istream&, char* data, size_t numBytes); /// @brief Read data from a stream. /// @param is the input stream /// @param data the contiguous array of data to read in /// @param count the number of elements to read in /// @param compression whether and how the data is compressed (either COMPRESS_NONE, /// COMPRESS_ZIP, COMPRESS_ACTIVE_MASK or COMPRESS_BLOSC) /// @throw IoError if @a compression is COMPRESS_BLOSC but OpenVDB was compiled /// without Blosc support. /// @details This default implementation is instantiated only for types /// whose size can be determined by the sizeof() operator. template inline void readData(std::istream& is, T* data, Index count, uint32_t compression) { if (compression & COMPRESS_BLOSC) { bloscFromStream(is, reinterpret_cast(data), sizeof(T) * count); } else if (compression & COMPRESS_ZIP) { unzipFromStream(is, reinterpret_cast(data), sizeof(T) * count); } else { is.read(reinterpret_cast(data), sizeof(T) * count); } } /// Specialization for std::string input template<> inline void readData(std::istream& is, std::string* data, Index count, uint32_t /*compression*/) { for (Index i = 0; i < count; ++i) { size_t len = 0; is >> len; //data[i].resize(len); //is.read(&(data[i][0]), len); std::string buffer(len+1, ' '); is.read(&buffer[0], len+1 ); data[i].assign(buffer, 0, len); } } /// HalfReader wraps a static function, read(), that is analogous to readData(), above, /// except that it is partially specialized for floating-point types in order to promote /// 16-bit half float values to full float. A wrapper class is required because /// only classes, not functions, can be partially specialized. template struct HalfReader; /// Partial specialization for non-floating-point types (no half to float promotion) template struct HalfReader { static inline void read(std::istream& is, T* data, Index count, uint32_t compression) { readData(is, data, count, compression); } }; /// Partial specialization for floating-point types template struct HalfReader { typedef typename RealToHalf::HalfT HalfT; static inline void read(std::istream& is, T* data, Index count, uint32_t compression) { if (count < 1) return; std::vector halfData(count); // temp buffer into which to read half float values readData(is, reinterpret_cast(&halfData[0]), count, compression); // Copy half float values from the temporary buffer to the full float output array. std::copy(halfData.begin(), halfData.end(), data); } }; /// Write data to a stream. /// @param os the output stream /// @param data the contiguous array of data to write /// @param count the number of elements to write out /// @param compression whether and how to compress the data (either COMPRESS_NONE, /// COMPRESS_ZIP, COMPRESS_ACTIVE_MASK or COMPRESS_BLOSC) /// @throw IoError if @a compression is COMPRESS_BLOSC but OpenVDB was compiled /// without Blosc support. /// @details This default implementation is instantiated only for types /// whose size can be determined by the sizeof() operator. template inline void writeData(std::ostream &os, const T *data, Index count, uint32_t compression) { if (compression & COMPRESS_BLOSC) { bloscToStream(os, reinterpret_cast(data), sizeof(T), count); } else if (compression & COMPRESS_ZIP) { zipToStream(os, reinterpret_cast(data), sizeof(T) * count); } else { os.write(reinterpret_cast(data), sizeof(T) * count); } } /// Specialization for std::string output template<> inline void writeData(std::ostream& os, const std::string* data, Index count, uint32_t /*compression*/) ///< @todo add compression { for (Index i = 0; i < count; ++i) { const size_t len = data[i].size(); os << len; os.write(data[i].c_str(), len+1); //os.write(&(data[i][0]), len ); } } /// HalfWriter wraps a static function, write(), that is analogous to writeData(), above, /// except that it is partially specialized for floating-point types in order to quantize /// floating-point values to 16-bit half float. A wrapper class is required because /// only classes, not functions, can be partially specialized. template struct HalfWriter; /// Partial specialization for non-floating-point types (no float to half quantization) template struct HalfWriter { static inline void write(std::ostream& os, const T* data, Index count, uint32_t compression) { writeData(os, data, count, compression); } }; /// Partial specialization for floating-point types template struct HalfWriter { typedef typename RealToHalf::HalfT HalfT; static inline void write(std::ostream& os, const T* data, Index count, uint32_t compression) { if (count < 1) return; // Convert full float values to half float, then output the half float array. std::vector halfData(count); for (Index i = 0; i < count; ++i) halfData[i] = RealToHalf::convert(data[i]); writeData(os, reinterpret_cast(&halfData[0]), count, compression); } }; #ifdef _MSC_VER /// Specialization to avoid double to float warnings in MSVC template<> struct HalfWriter { typedef RealToHalf::HalfT HalfT; static inline void write(std::ostream& os, const double* data, Index count, uint32_t compression) { if (count < 1) return; // Convert full float values to half float, then output the half float array. std::vector halfData(count); for (Index i = 0; i < count; ++i) halfData[i] = RealToHalf::convert(data[i]); writeData(os, reinterpret_cast(&halfData[0]), count, compression); } }; #endif // _MSC_VER //////////////////////////////////////// /// Populate the given buffer with @a destCount values of type @c ValueT /// read from the given stream, taking into account that the stream might /// have been compressed via one of several supported schemes. /// [Mainly for internal use] /// @param is a stream from which to read data (possibly compressed, /// depending on the stream's compression settings) /// @param destBuf a buffer into which to read values of type @c ValueT /// @param destCount the number of values to be stored in the buffer /// @param valueMask a bitmask (typically, a node's value mask) indicating /// which positions in the buffer correspond to active values /// @param fromHalf if true, read 16-bit half floats from the input stream /// and convert them to full floats template inline void readCompressedValues(std::istream& is, ValueT* destBuf, Index destCount, const MaskT& valueMask, bool fromHalf) { // Get the stream's compression settings. const uint32_t compression = getDataCompression(is); const bool maskCompressed = compression & COMPRESS_ACTIVE_MASK; 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, compression); } else { readData(is, tempBuf, tempCount, compression); } // If mask compression is enabled and the number of active values read into // the temp buffer is smaller than the size of the destination buffer, // then there are missing (inactive) values. if (maskCompressed && tempCount != destCount) { // Restore inactive values, using the background value and, if available, // the inside/outside mask. (For fog volumes, the destination buffer is assumed // to be initialized to background value zero, so inactive values can be ignored.) for (Index destIdx = 0, tempIdx = 0; destIdx < MaskT::SIZE; ++destIdx) { if (valueMask.isOn(destIdx)) { // Copy a saved active value into this node's buffer. destBuf[destIdx] = tempBuf[tempIdx]; ++tempIdx; } else { // Reconstruct an unsaved inactive value and copy it into this node's buffer. destBuf[destIdx] = (selectionMask.isOn(destIdx) ? inactiveVal1 : inactiveVal0); } } } } /// Write @a srcCount values of type @c ValueT to the given stream, optionally /// after compressing the values via one of several supported schemes. /// [Mainly for internal use] /// @param os a stream to which to write data (possibly compressed, depending /// on the stream's compression settings) /// @param srcBuf a buffer containing values of type @c ValueT to be written /// @param srcCount the number of values stored in the buffer /// @param valueMask a bitmask (typically, a node's value mask) indicating /// which positions in the buffer correspond to active values /// @param childMask a bitmask (typically, a node's child mask) indicating /// which positions in the buffer correspond to child node pointers /// @param toHalf if true, convert floating-point values to 16-bit half floats template inline void writeCompressedValues(std::ostream& os, ValueT* srcBuf, Index srcCount, const MaskT& valueMask, const MaskT& childMask, bool toHalf) { struct Local { // Comparison function for values static inline bool eq(const ValueT& a, const ValueT& b) { return math::isExactlyEqual(a, b); } }; // Get the stream's compression settings. const uint32_t compress = getDataCompression(os); const bool maskCompress = compress & COMPRESS_ACTIVE_MASK; Index tempCount = srcCount; ValueT* tempBuf = srcBuf; boost::scoped_array scopedTempBuf; int8_t metadata = NO_MASK_AND_ALL_VALS; if (!maskCompress) { os.write(reinterpret_cast(&metadata), /*bytes=*/1); } else { // A valid level set's inactive values are either +background (outside) // or -background (inside), and a fog volume's inactive values are all zero. // Rather than write out all of these values, we can store just the active values // (given that the value mask specifies their positions) and, if necessary, // an inside/outside bitmask. const ValueT zero = zeroVal(); ValueT background = zero; if (const void* bgPtr = getGridBackgroundValuePtr(os)) { background = *static_cast(bgPtr); } /// @todo Consider all values, not just inactive values? ValueT inactiveVal[2] = { background, background }; int numUniqueInactiveVals = 0; for (typename MaskT::OffIterator it = valueMask.beginOff(); numUniqueInactiveVals < 3 && it; ++it) { const Index32 idx = it.pos(); // Skip inactive values that are actually child node pointers. if (childMask.isOn(idx)) continue; const ValueT& val = srcBuf[idx]; const bool unique = !( (numUniqueInactiveVals > 0 && Local::eq(val, inactiveVal[0])) || (numUniqueInactiveVals > 1 && Local::eq(val, inactiveVal[1])) ); if (unique) { if (numUniqueInactiveVals < 2) inactiveVal[numUniqueInactiveVals] = val; ++numUniqueInactiveVals; } } metadata = NO_MASK_OR_INACTIVE_VALS; if (numUniqueInactiveVals == 1) { if (!Local::eq(inactiveVal[0], background)) { if (Local::eq(inactiveVal[0], math::negative(background))) { metadata = NO_MASK_AND_MINUS_BG; } else { metadata = NO_MASK_AND_ONE_INACTIVE_VAL; } } } else if (numUniqueInactiveVals == 2) { metadata = NO_MASK_OR_INACTIVE_VALS; if (!Local::eq(inactiveVal[0], background) && !Local::eq(inactiveVal[1], background)) { // If neither inactive value is equal to the background, both values // need to be saved, along with a mask that selects between them. metadata = MASK_AND_TWO_INACTIVE_VALS; } else if (Local::eq(inactiveVal[1], background)) { if (Local::eq(inactiveVal[0], math::negative(background))) { // If the second inactive value is equal to the background and // the first is equal to -background, neither value needs to be saved, // but save a mask that selects between -background and +background. metadata = MASK_AND_NO_INACTIVE_VALS; } else { // If the second inactive value is equal to the background, only // the first value needs to be saved, along with a mask that selects // between it and the background. metadata = MASK_AND_ONE_INACTIVE_VAL; } } else if (Local::eq(inactiveVal[0], background)) { if (Local::eq(inactiveVal[1], math::negative(background))) { // If the first inactive value is equal to the background and // the second is equal to -background, neither value needs to be saved, // but save a mask that selects between -background and +background. metadata = MASK_AND_NO_INACTIVE_VALS; std::swap(inactiveVal[0], inactiveVal[1]); } else { // If the first inactive value is equal to the background, swap it // with the second value and save only that value, along with a mask // that selects between it and the background. std::swap(inactiveVal[0], inactiveVal[1]); metadata = MASK_AND_ONE_INACTIVE_VAL; } } } else if (numUniqueInactiveVals > 2) { metadata = NO_MASK_AND_ALL_VALS; } os.write(reinterpret_cast(&metadata), /*bytes=*/1); if (metadata == NO_MASK_AND_ONE_INACTIVE_VAL || metadata == MASK_AND_ONE_INACTIVE_VAL || metadata == MASK_AND_TWO_INACTIVE_VALS) { if (!toHalf) { // Write one of at most two distinct inactive values. os.write(reinterpret_cast(&inactiveVal[0]), sizeof(ValueT)); if (metadata == MASK_AND_TWO_INACTIVE_VALS) { // Write the second of two distinct inactive values. os.write(reinterpret_cast(&inactiveVal[1]), sizeof(ValueT)); } } else { // Write one of at most two distinct inactive values. ValueT truncatedVal = static_cast(truncateRealToHalf(inactiveVal[0])); os.write(reinterpret_cast(&truncatedVal), sizeof(ValueT)); if (metadata == MASK_AND_TWO_INACTIVE_VALS) { // Write the second of two distinct inactive values. truncatedVal = truncateRealToHalf(inactiveVal[1]); os.write(reinterpret_cast(&truncatedVal), sizeof(ValueT)); } } } if (metadata == NO_MASK_AND_ALL_VALS) { // If there are more than two unique inactive values, the entire input buffer // needs to be saved (both active and inactive values). /// @todo Save the selection mask as long as most of the inactive values /// are one of two values? } else { // Create a new array to hold just the active values. scopedTempBuf.reset(new ValueT[srcCount]); tempBuf = scopedTempBuf.get(); if (metadata == NO_MASK_OR_INACTIVE_VALS || metadata == NO_MASK_AND_MINUS_BG || metadata == NO_MASK_AND_ONE_INACTIVE_VAL) { // Copy active values to the contiguous array. tempCount = 0; for (typename MaskT::OnIterator it = valueMask.beginOn(); it; ++it, ++tempCount) { tempBuf[tempCount] = srcBuf[it.pos()]; } } else { // Copy active values to a new, contiguous array and populate a bitmask // that selects between two distinct inactive values. MaskT selectionMask; tempCount = 0; for (Index srcIdx = 0; srcIdx < srcCount; ++srcIdx) { if (valueMask.isOn(srcIdx)) { // active value tempBuf[tempCount] = srcBuf[srcIdx]; ++tempCount; } else { // inactive value if (Local::eq(srcBuf[srcIdx], inactiveVal[1])) { selectionMask.setOn(srcIdx); // inactive value 1 } // else inactive value 0 } } assert(tempCount == valueMask.countOn()); // Write out the mask that selects between two inactive values. selectionMask.save(os); } } } // Write out the buffer. if (toHalf) { HalfWriter::isReal, ValueT>::write(os, tempBuf, tempCount, compress); } else { writeData(os, tempBuf, tempCount, compress); } } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_COMPRESSION_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/TempFile.h0000644000000000000000000000577412603226506013515 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file TempFile.h #ifndef OPENVDB_IO_TEMPFILE_HAS_BEEN_INCLUDED #define OPENVDB_IO_TEMPFILE_HAS_BEEN_INCLUDED #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { /// Output stream to a unique temporary file class OPENVDB_API TempFile: public std::ostream { public: /// @brief Create and open a unique file. /// @details On UNIX systems, the file is created in the directory specified by /// the environment variable @c OPENVDB_TEMP_DIR, if that variable is defined, /// or else in the directory specified by @c TMPDIR, if that variable is defined. /// Otherwise (and on non-UNIX systems), the file is created in the system default /// temporary directory. TempFile(); ~TempFile(); /// Return the path to the temporary file. const std::string& filename() const; /// Return @c true if the file is open for writing. bool is_open() const; /// Close the file. void close(); private: struct TempFileImpl; boost::scoped_ptr mImpl; }; } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_TEMPFILE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/io.h0000644000000000000000000002627012603226506012411 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_IO_IO_HAS_BEEN_INCLUDED #define OPENVDB_IO_IO_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include // for std::ios_base #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { class MetaMap; namespace io { /// @brief Container for metadata describing how to unserialize grids from and/or /// serialize grids to a stream (which file format, compression scheme, etc. to use) /// @details This class is mainly for internal use. class OPENVDB_API StreamMetadata { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; StreamMetadata(); StreamMetadata(const StreamMetadata&); explicit StreamMetadata(std::ios_base&); ~StreamMetadata(); StreamMetadata& operator=(const StreamMetadata&); /// @brief Transfer metadata items directly to the given stream. /// @todo Deprecate direct transfer; use StreamMetadata structs everywhere. void transferTo(std::ios_base&) const; uint32_t fileVersion() const; void setFileVersion(uint32_t); VersionId libraryVersion() const; void setLibraryVersion(VersionId); uint32_t compression() const; void setCompression(uint32_t); uint32_t gridClass() const; void setGridClass(uint32_t); const void* backgroundPtr() const; void setBackgroundPtr(const void*); bool halfFloat() const; void setHalfFloat(bool); bool writeGridStats() const; void setWriteGridStats(bool); //@{ /// @brief Return a (reference to a) copy of the metadata of the grid currently /// being read or written. /// @details Some grid metadata might duplicate information returned by /// gridClass(), backgroundPtr() and other accessors, but those values /// are not guaranteed to be kept in sync. MetaMap& gridMetadata(); const MetaMap& gridMetadata() const; //@} typedef std::map AuxDataMap; //@{ /// @brief Return a map that can be populated with arbitrary user data. AuxDataMap& auxData(); const AuxDataMap& auxData() const; //@} /// Return a string describing this stream metadata. std::string str() const; private: struct Impl; boost::scoped_ptr mImpl; }; // class StreamMetadata /// Write a description of the given metadata to an output stream. std::ostream& operator<<(std::ostream&, const StreamMetadata&); std::ostream& operator<<(std::ostream&, const StreamMetadata::AuxDataMap&); //////////////////////////////////////// class File; /// @brief Handle to control the lifetime of a memory-mapped .vdb file class OPENVDB_API MappedFile { public: typedef boost::shared_ptr Ptr; ~MappedFile(); /// Return the filename of the mapped file. std::string filename() const; /// @brief Return a new stream buffer for the mapped file. /// @details Typical usage is /// @code /// openvdb::io::MappedFile::Ptr mappedFile = ...; /// boost::shared_ptr buf = mappedFile->createBuffer(); /// std::istream istrm(buf.get()); /// // Read from istrm... /// @endcode /// The buffer must persist as long as the stream is open. boost::shared_ptr createBuffer() const; typedef boost::function Notifier; /// @brief Register a function that will be called with this file's name /// when the file is unmapped. void setNotifier(const Notifier&); /// Deregister the notifier. void clearNotifier(); private: friend class File; explicit MappedFile(const std::string& filename, bool autoDelete = false); MappedFile(const MappedFile&); // not copyable MappedFile& operator=(const MappedFile&); // not copyable class Impl; boost::scoped_ptr mImpl; }; // class MappedFile //////////////////////////////////////// /// Return a string (possibly empty) describing the given system error code. std::string getErrorString(int errorNum); /// Return a string (possibly empty) describing the most recent system error. std::string getErrorString(); //////////////////////////////////////// /// @brief Return the file format version number associated with the given input stream. /// @sa File::setFormatVersion() OPENVDB_API uint32_t getFormatVersion(std::ios_base&); /// @brief Return the (major, minor) library version number associated with the given input stream. /// @sa File::setLibraryVersion() OPENVDB_API VersionId getLibraryVersion(std::ios_base&); /// @brief Return a string of the form "./", giving the library /// and file format version numbers associated with the given input stream. OPENVDB_API std::string getVersion(std::ios_base&); /// Associate the current file format and library version numbers with the given input stream. OPENVDB_API void setCurrentVersion(std::istream&); /// @brief Associate specific file format and library version numbers with the given stream. /// @details This is typically called immediately after reading a header that contains /// the version numbers. Data read subsequently can then be interpreted appropriately. OPENVDB_API void setVersion(std::ios_base&, const VersionId& libraryVersion, uint32_t fileVersion); /// @brief Return a bitwise OR of compression option flags (COMPRESS_ZIP, /// COMPRESS_ACTIVE_MASK, etc.) specifying whether and how input data is compressed /// or output data should be compressed. OPENVDB_API uint32_t getDataCompression(std::ios_base&); /// @brief Associate with the given stream a bitwise OR of compression option flags /// (COMPRESS_ZIP, COMPRESS_ACTIVE_MASK, etc.) specifying whether and how input data /// is compressed or output data should be compressed. OPENVDB_API void setDataCompression(std::ios_base&, uint32_t compressionFlags); /// @brief Return the class (GRID_LEVEL_SET, GRID_UNKNOWN, etc.) of the grid /// currently being read from or written to the given stream. OPENVDB_API uint32_t getGridClass(std::ios_base&); /// @brief Associate with the given stream the class (GRID_LEVEL_SET, GRID_UNKNOWN, etc.) /// of the grid currently being read or written. OPENVDB_API void setGridClass(std::ios_base&, uint32_t); /// @brief Return true if floating-point values should be quantized to 16 bits when writing /// to the given stream or promoted back from 16-bit to full precision when reading from it. OPENVDB_API bool getHalfFloat(std::ios_base&); /// @brief Specify whether floating-point values should be quantized to 16 bits when writing /// to the given stream or promoted back from 16-bit to full precision when reading from it. OPENVDB_API void setHalfFloat(std::ios_base&, bool); /// @brief Return a pointer to the background value of the grid /// currently being read from or written to the given stream. OPENVDB_API const void* getGridBackgroundValuePtr(std::ios_base&); /// @brief Specify (a pointer to) the background value of the grid /// currently being read from or written to the given stream. /// @note The pointer must remain valid until the entire grid has been read or written. OPENVDB_API void setGridBackgroundValuePtr(std::ios_base&, const void* background); /// @brief Return @c true if grid statistics (active voxel count and bounding box, etc.) /// should be computed and stored as grid metadata when writing to the given stream. OPENVDB_API bool getWriteGridStatsMetadata(std::ios_base&); /// @brief Specify whether to compute grid statistics (active voxel count and bounding box, etc.) /// and store them as grid metadata when writing to the given stream. OPENVDB_API void setWriteGridStatsMetadata(std::ios_base&, bool writeGridStats); /// @brief Return a shared pointer to the memory-mapped file with which the given stream /// is associated, or a null pointer if the stream is not associated with a memory-mapped file. OPENVDB_API boost::shared_ptr getMappedFilePtr(std::ios_base&); /// @brief Associate the given stream with (a shared pointer to) a memory-mapped file. /// @note The shared pointer object (not just the io::MappedFile object to which it points) /// must remain valid until the file is closed. OPENVDB_API void setMappedFilePtr(std::ios_base&, boost::shared_ptr&); /// @brief Return a shared pointer to an object that stores metadata (file format, /// compression scheme, etc.) for use when reading from or writing to the given stream. OPENVDB_API boost::shared_ptr getStreamMetadataPtr(std::ios_base&); /// @brief Associate the given stream with (a shared pointer to) an object that stores /// metadata (file format, compression scheme, etc.) for use when reading from /// or writing to the stream. /// @details If @a transfer is true, copy metadata from the object directly to the stream /// (for backward compatibility with older versions of the library). /// @note The shared pointer object (not just the io::StreamMetadata object to which it points) /// must remain valid until the file is closed. OPENVDB_API void setStreamMetadataPtr(std::ios_base&, boost::shared_ptr&, bool transfer = true); /// @brief Dissociate the given stream from its metadata object (if it has one) /// and return a shared pointer to the object. OPENVDB_API boost::shared_ptr clearStreamMetadataPtr(std::ios_base&); } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_IO_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Stream.cc0000644000000000000000000001737412603226506013400 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Stream.h" #include "File.h" ///< @todo refactor #include "GridDescriptor.h" #include "TempFile.h" #include #include #include #include #include // for remove() #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { struct Stream::Impl { Impl(): mOutputStream(NULL) {} Impl(const Impl& other) { *this = other; } Impl& operator=(const Impl& other) { if (&other != this) { mMeta = other.mMeta; ///< @todo deep copy? mGrids = other.mGrids; ///< @todo deep copy? mOutputStream = other.mOutputStream; mFile.reset(); } return *this; } MetaMap::Ptr mMeta; GridPtrVecPtr mGrids; std::ostream* mOutputStream; boost::scoped_ptr mFile; }; //////////////////////////////////////// namespace { /// @todo Use MappedFile auto-deletion instead. void removeTempFile(const std::string expectedFilename, const std::string& filename) { if (filename == expectedFilename) { if (0 != std::remove(filename.c_str())) { std::string mesg = getErrorString(); if (!mesg.empty()) mesg = " (" + mesg + ")"; OPENVDB_LOG_WARN("failed to remove temporary file " << filename << mesg); } } } } Stream::Stream(std::istream& is, bool delayLoad): mImpl(new Impl) { if (!is) return; if (delayLoad && Archive::isDelayedLoadingEnabled()) { // Copy the contents of the stream to a temporary private file // and open the file instead. boost::scoped_ptr tempFile; try { tempFile.reset(new TempFile); } catch (std::exception& e) { std::string mesg; if (e.what()) mesg = std::string(" (") + e.what() + ")"; OPENVDB_LOG_WARN("failed to create a temporary file for delayed loading" << mesg << "; will read directly from the input stream instead"); } if (tempFile) { boost::iostreams::copy(is, *tempFile); const std::string& filename = tempFile->filename(); mImpl->mFile.reset(new File(filename)); mImpl->mFile->setCopyMaxBytes(0); // don't make a copy of the temporary file /// @todo Need to pass auto-deletion flag to MappedFile. mImpl->mFile->open(delayLoad, boost::bind(&removeTempFile, filename, _1)); } } if (!mImpl->mFile) { readHeader(is); // Tag the input stream with the library and file format version numbers // and the compression options specified in the header. StreamMetadata::Ptr streamMetadata(new StreamMetadata); io::setStreamMetadataPtr(is, streamMetadata, /*transfer=*/false); io::setVersion(is, libraryVersion(), fileVersion()); io::setDataCompression(is, compression()); // Read in the VDB metadata. mImpl->mMeta.reset(new MetaMap); mImpl->mMeta->readMeta(is); // Read in the number of grids. const boost::int32_t gridCount = readGridCount(is); // Read in all grids and insert them into mGrids. mImpl->mGrids.reset(new GridPtrVec); std::vector descriptors; descriptors.reserve(gridCount); Archive::NamedGridMap namedGrids; for (boost::int32_t i = 0; i < gridCount; ++i) { GridDescriptor gd; gd.read(is); descriptors.push_back(gd); GridBase::Ptr grid = readGrid(gd, is); mImpl->mGrids->push_back(grid); namedGrids[gd.uniqueName()] = grid; } // Connect instances (grids that share trees with other grids). for (size_t i = 0, N = descriptors.size(); i < N; ++i) { Archive::connectInstance(descriptors[i], namedGrids); } } } Stream::Stream(): mImpl(new Impl) { } Stream::Stream(std::ostream& os): mImpl(new Impl) { mImpl->mOutputStream = &os; } Stream::~Stream() { } Stream::Stream(const Stream& other): Archive(other), mImpl(new Impl(*other.mImpl)) { } Stream& Stream::operator=(const Stream& other) { if (&other != this) { mImpl.reset(new Impl(*other.mImpl)); } return *this; } 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::write(const GridCPtrVec& grids, const MetaMap& metadata) const { if (mImpl->mOutputStream == NULL) { OPENVDB_THROW(ValueError, "no output stream was specified"); } this->writeGrids(*mImpl->mOutputStream, grids, metadata); } void Stream::writeGrids(std::ostream& os, const GridCPtrVec& grids, const MetaMap& metadata) const { Archive::write(os, grids, /*seekable=*/false, metadata); } //////////////////////////////////////// MetaMap::Ptr Stream::getMetadata() const { MetaMap::Ptr result; if (mImpl->mFile) { result = mImpl->mFile->getMetadata(); } else if (mImpl->mMeta) { // Return a deep copy of the file-level metadata // that was read when this object was constructed. result.reset(new MetaMap(*mImpl->mMeta)); } return result; } GridPtrVecPtr Stream::getGrids() { if (mImpl->mFile) { return mImpl->mFile->getGrids(); } return mImpl->mGrids; } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Compression.cc0000644000000000000000000002137412603226506014441 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Compression.h" #include #include #include #include #include #ifdef OPENVDB_USE_BLOSC #include #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { std::string compressionToString(uint32_t flags) { if (flags == COMPRESS_NONE) return "none"; std::vector words; if (flags & COMPRESS_ZIP) words.push_back("zip"); if (flags & COMPRESS_BLOSC) words.push_back("blosc"); if (flags & COMPRESS_ACTIVE_MASK) words.push_back("active values"); return boost::join(words, " + "); } //////////////////////////////////////// namespace { const int ZIP_COMPRESSION_LEVEL = Z_DEFAULT_COMPRESSION; ///< @todo use Z_BEST_SPEED? } void zipToStream(std::ostream& os, const char* data, size_t numBytes) { // Get an upper bound on the size of the compressed data. uLongf numZippedBytes = compressBound(numBytes); // Compress the data. boost::shared_array zippedData(new Bytef[numZippedBytes]); int status = compress2( /*dest=*/zippedData.get(), &numZippedBytes, /*src=*/reinterpret_cast(data), numBytes, /*level=*/ZIP_COMPRESSION_LEVEL); if (status != Z_OK) { std::string errDescr; if (const char* s = zError(status)) errDescr = s; if (!errDescr.empty()) errDescr = " (" + errDescr + ")"; OPENVDB_LOG_DEBUG("zlib compress2() returned error code " << status << errDescr); } if (status == Z_OK && numZippedBytes < numBytes) { // Write the size of the compressed data. Int64 outZippedBytes = numZippedBytes; os.write(reinterpret_cast(&outZippedBytes), 8); // Write the compressed data. os.write(reinterpret_cast(zippedData.get()), outZippedBytes); } else { // Write the size of the uncompressed data. Int64 negBytes = -numBytes; os.write(reinterpret_cast(&negBytes), 8); // Write the uncompressed data. os.write(reinterpret_cast(data), numBytes); } } void unzipFromStream(std::istream& is, char* data, size_t numBytes) { // Read the size of the compressed data. // A negative size indicates uncompressed data. Int64 numZippedBytes; is.read(reinterpret_cast(&numZippedBytes), 8); if (numZippedBytes <= 0) { // Read the uncompressed data. 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")); } } } #ifndef OPENVDB_USE_BLOSC void bloscToStream(std::ostream&, const char*, size_t, size_t) { OPENVDB_THROW(IoError, "Blosc encoding is not supported"); } #else void bloscToStream(std::ostream& os, const char* data, size_t valSize, size_t numVals) { const size_t inBytes = valSize * numVals; int outBytes = int(inBytes) + BLOSC_MAX_OVERHEAD; boost::shared_array compressedData(new char[outBytes]); outBytes = blosc_compress_ctx( /*clevel=*/9, // 0 (no compression) to 9 (maximum compression) /*doshuffle=*/true, /*typesize=*/valSize, ///< @todo use size that gives best compression? /*srcsize=*/inBytes, /*src=*/data, /*dest=*/compressedData.get(), /*destsize=*/outBytes, BLOSC_LZ4_COMPNAME, /*blocksize=*/256, /*numthreads=*/1); if (outBytes <= 0) { std::ostringstream ostr; ostr << "Blosc failed to compress " << inBytes << " byte" << (inBytes == 1 ? "" : "s"); if (outBytes < 0) ostr << " (internal error " << outBytes << ")"; OPENVDB_LOG_DEBUG(ostr.str()); // Write the size of the uncompressed data. Int64 negBytes = -inBytes; os.write(reinterpret_cast(&negBytes), 8); // Write the uncompressed data. os.write(reinterpret_cast(data), inBytes); } else { // Write the size of the compressed data. Int64 numBytes = outBytes; os.write(reinterpret_cast(&numBytes), 8); // Write the compressed data. os.write(reinterpret_cast(compressedData.get()), outBytes); } } #endif #ifndef OPENVDB_USE_BLOSC void bloscFromStream(std::istream&, char*, size_t) { OPENVDB_THROW(IoError, "Blosc decoding is not supported"); } #else void bloscFromStream(std::istream& is, char* data, size_t numBytes) { // Read the size of the compressed data. // A negative size indicates uncompressed data. Int64 numCompressedBytes; is.read(reinterpret_cast(&numCompressedBytes), 8); if (numCompressedBytes <= 0) { // Read the uncompressed data. is.read(data, -numCompressedBytes); if (size_t(-numCompressedBytes) != numBytes) { OPENVDB_THROW(RuntimeError, "Expected to read a " << numBytes << "-byte uncompressed chunk, got a " << -numCompressedBytes << "-byte chunk"); } } else { // Read the compressed data. boost::shared_array compressedData(new char[numCompressedBytes]); is.read(reinterpret_cast(compressedData.get()), numCompressedBytes); // Uncompress the data. const int numUncompressedBytes = blosc_decompress_ctx( /*src=*/compressedData.get(), /*dest=*/data, numBytes, /*numthreads=*/1); if (numUncompressedBytes < 1) { OPENVDB_LOG_DEBUG("blosc_decompress() returned error code " << numUncompressedBytes); } if (numUncompressedBytes != Int64(numBytes)) { OPENVDB_THROW(RuntimeError, "Expected to decompress " << numBytes << " byte" << (numBytes == 1 ? "" : "s") << ", got " << numUncompressedBytes << " byte" << (numUncompressedBytes == 1 ? "" : "s")); } } } #endif } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/TempFile.cc0000644000000000000000000001416312603226506013643 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file TempFile.cc #include "TempFile.h" #include #ifndef _MSC_VER #include #include #include // for BOOST_VERSION #include // for std::getenv(), mkstemp() #include // for mode_t #include // for mkdir(), umask() #include // for access() #else #include // for std::filebuf #endif #include // for std::tmpnam(), L_tmpnam, P_tmpdir #include #include #include #include #ifndef DWA_BOOST_VERSION #define DWA_BOOST_VERSION (10 * BOOST_VERSION) #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { struct TempFile::TempFileImpl { const std::string& filename() const { return mPath; } bool is_open() const { return mBuffer.is_open(); } /// @internal boost::filesystem::unique_path(), etc. might be useful here, /// but as of 9/2014, Houdini ships without the Boost.Filesystem library, /// which makes it much less convenient to use that library. #ifndef _MSC_VER TempFileImpl(std::ostream& os): mFileDescr(-1) { this->init(os); } void init(std::ostream& os) { std::string fn = this->getTempDir() + "/openvdb_temp_XXXXXX"; std::vector fnbuf(fn.begin(), fn.end()); fnbuf.push_back(char(0)); //const mode_t savedMode = ::umask(~(S_IRUSR | S_IWUSR)); mFileDescr = ::mkstemp(&fnbuf[0]); //::umask(savedMode); if (mFileDescr < 0) { OPENVDB_THROW(IoError, "failed to generate temporary file"); } mPath.assign(&fnbuf[0]); #if DWA_BOOST_VERSION >= 1046000 mDevice = DeviceType(mFileDescr, boost::iostreams::never_close_handle); #else mDevice = DeviceType(mFileDescr, /*closeOnExit=*/false); #endif mBuffer.open(mDevice); os.rdbuf(&mBuffer); if (!os.good()) { OPENVDB_THROW(IoError, "failed to open temporary file " + mPath); } } void close() { mBuffer.close(); if (mFileDescr >= 0) ::close(mFileDescr); } static std::string getTempDir() { if (const char* dir = std::getenv("OPENVDB_TEMP_DIR")) { if (0 != ::access(dir, F_OK)) { ::mkdir(dir, S_IRUSR | S_IWUSR | S_IXUSR); if (0 != ::access(dir, F_OK)) { OPENVDB_THROW(IoError, "failed to create OPENVDB_TEMP_DIR (" + std::string(dir) + ")"); } } return dir; } if (const char* dir = std::getenv("TMPDIR")) return dir; return P_tmpdir; } typedef boost::iostreams::file_descriptor_sink DeviceType; typedef boost::iostreams::stream_buffer BufferType; std::string mPath; DeviceType mDevice; BufferType mBuffer; int mFileDescr; #else // _MSC_VER // Use only standard library routines; no POSIX. TempFileImpl(std::ostream& os) { this->init(os); } void init(std::ostream& os) { char fnbuf[L_tmpnam]; const char* filename = std::tmpnam(fnbuf); if (!filename) { OPENVDB_THROW(IoError, "failed to generate name for temporary file"); } /// @todo This is not safe, since another process could open a file /// with this name before we do. Unfortunately, there is no safe, /// portable way to create a temporary file. mPath = filename; const std::ios_base::openmode mode = (std::ios_base::out | std::ios_base::binary); os.rdbuf(mBuffer.open(mPath.c_str(), mode)); if (!os.good()) { OPENVDB_THROW(IoError, "failed to open temporary file " + mPath); } } void close() { mBuffer.close(); } std::string mPath; std::filebuf mBuffer; #endif // _MSC_VER private: TempFileImpl(const TempFileImpl&); // disable copying TempFileImpl& operator=(const TempFileImpl&); // disable assignment }; TempFile::TempFile(): std::ostream(NULL), mImpl(new TempFileImpl(*this)) {} TempFile::~TempFile() { this->close(); } const std::string& TempFile::filename() const { return mImpl->filename(); } bool TempFile::is_open() const { return mImpl->is_open(); } void TempFile::close() { mImpl->close(); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Stream.h0000644000000000000000000001067012603226506013232 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_IO_STREAM_HAS_BEEN_INCLUDED #define OPENVDB_IO_STREAM_HAS_BEEN_INCLUDED #include "Archive.h" #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { class GridDescriptor; /// Grid archive associated with arbitrary input and output streams (not necessarily files) class OPENVDB_API Stream: public Archive { public: /// @brief Read grids from an input stream. /// @details If @a delayLoad is true, map the contents of the input stream /// into memory and enable delayed loading of grids. /// @note Define the environment variable @c OPENVDB_DISABLE_DELAYED_LOAD /// to disable delayed loading unconditionally. explicit Stream(std::istream&, bool delayLoad = true); /// Construct an archive for stream output. Stream(); /// Construct an archive for output to the given stream. explicit Stream(std::ostream&); Stream(const Stream&); Stream& operator=(const Stream&); virtual ~Stream(); /// @brief Return a copy of this archive. virtual Archive::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(); /// @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; private: /// Create a new grid of the type specified by the given descriptor, /// then populate the grid from the given input stream. /// @return the newly created grid. GridBase::Ptr readGrid(const GridDescriptor&, std::istream&) const; void writeGrids(std::ostream&, const GridCPtrVec&, const MetaMap&) const; struct Impl; boost::scoped_ptr mImpl; }; //////////////////////////////////////// template inline void Stream::write(const GridPtrContainerT& container, const MetaMap& metadata) const { GridCPtrVec grids; std::copy(container.begin(), container.end(), std::back_inserter(grids)); this->write(grids, metadata); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_STREAM_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Queue.cc0000644000000000000000000002403012603226506013214 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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); try { mImpl->enqueue(*task); } catch (openvdb::RuntimeError&) { // Destroy the task if it could not be enqueued, then rethrow the exception. tbb::task::destroy(*task); throw; } return task->id(); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/File.h0000644000000000000000000002641412603226506012661 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 "io.h" // for MappedFile::Notifier #include "Archive.h" #include "GridDescriptor.h" #include #include #include #include class TestFile; class TestStream; namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { /// Grid archive associated with a file on disk class OPENVDB_API File: public Archive { public: 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; /// @brief Open the file, read the file header and the file-level metadata, /// and populate the grid descriptors, but do not load any grids into memory. /// @details If @a delayLoad is true, map the file into memory and enable delayed loading /// of grids, and if a notifier is provided, call it when the file gets unmapped. /// @note Define the environment variable @c OPENVDB_DISABLE_DELAYED_LOAD to disable /// delayed loading unconditionally. /// @throw IoError if the file is not a valid VDB file. /// @return @c true if the file's UUID has changed since it was last read. /// @see setCopyMaxBytes bool open(bool delayLoad = true, const MappedFile::Notifier& = MappedFile::Notifier()); /// Return @c true if the file has been opened for reading. bool isOpen() const; /// Close the file once we are done reading from it. void close(); /// @brief Return this file's current size on disk in bytes. /// @throw IoError if the file size cannot be determined. Index64 getSize() const; /// @brief Return the size in bytes above which this file will not be /// automatically copied during delayed loading. Index64 copyMaxBytes() const; /// @brief If this file is opened with delayed loading enabled, make a private copy /// of the file if its size in bytes is less than the specified value. /// @details Making a private copy ensures that the file can't change on disk /// before it has been fully read. /// @warning If the file is larger than this size, it is the user's responsibility /// to ensure that it does not change on disk before it has been fully read. /// Undefined behavior and/or a crash might result otherwise. /// @note Copying is enabled by default, but it can be disabled for individual files /// by setting the maximum size to zero bytes. A default size limit can be specified /// by setting the environment variable @c OPENVDB_DELAYED_LOAD_COPY_MAX_BYTES /// to the desired number of bytes. void setCopyMaxBytes(Index64 bytes); /// Return @c true if a grid of the given name exists in this file. bool hasGrid(const Name&) const; /// Return (in a newly created MetaMap) the file-level metadata. MetaMap::Ptr getMetadata() const; /// Read the entire contents of the file and return a list of grid pointers. GridPtrVecPtr getGrids() const; /// @brief Read just the grid metadata and transforms from the file and return a list /// of pointers to grids that are empty except for their metadata and transforms. /// @throw IoError if this file is not open for reading. GridPtrVecPtr readAllGridMetadata(); /// @brief Read a grid's metadata and transform only. /// @return A pointer to a grid that is empty except for its metadata and transform. /// @throw IoError if this file is not open for reading. /// @throw KeyError if no grid with the given name exists in this file. GridBase::Ptr readGridMetadata(const Name&); /// @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&); #ifndef OPENVDB_2_ABI_COMPATIBLE /// @brief Read a grid, including its data blocks, but only where it /// intersects the given world-space bounding box. GridBase::Ptr readGrid(const Name&, const BBoxd&); #endif /// @todo GridPtrVec 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: /// Read in all grid descriptors that are stored in the given stream. void readGridDescriptors(std::istream&); /// @brief Return an iterator to the descriptor for the grid with the given name. /// If the name is non-unique, return an iterator to the first matching descriptor. NameMapCIter findDescriptor(const Name&) const; /// Return a newly created, empty grid of the type specified by the given grid descriptor. GridBase::Ptr createGrid(const GridDescriptor&) const; /// @brief Read a grid, including its data blocks, but only where it /// intersects the given world-space bounding box. GridBase::Ptr readGridByName(const Name&, const BBoxd&); /// Read in and return the partially-populated grid specified by the given grid descriptor. GridBase::ConstPtr readGridPartial(const GridDescriptor&, bool readTopology) const; /// Read in and return the grid specified by the given grid descriptor. GridBase::Ptr readGrid(const GridDescriptor&) const; #ifndef OPENVDB_2_ABI_COMPATIBLE /// Read in and return the region of the grid specified by the given grid descriptor /// that intersects the given world-space bounding box. GridBase::Ptr readGrid(const GridDescriptor&, const BBoxd&) const; /// Read in and return the region of the grid specified by the given grid descriptor /// that intersects the given index-space bounding box. GridBase::Ptr readGrid(const GridDescriptor&, const CoordBBox&) const; #endif /// @brief Partially populate the given grid by reading its metadata and transform and, /// if the grid is not an instance, its tree structure, but not the tree's leaf nodes. void readGridPartial(GridBase::Ptr, std::istream&, bool isInstance, bool readTopology) const; /// @brief Retrieve a grid from @c mNamedGrids. Return a null pointer /// if @c mNamedGrids was not populated (because this file is random-access). /// @throw KeyError if no grid with the given name exists in this file. GridBase::Ptr retrieveCachedGrid(const Name&) const; void writeGrids(const GridCPtrVec&, const MetaMap&) const; MetaMap::Ptr fileMetadata(); MetaMap::ConstPtr fileMetadata() const; const NameMap& gridDescriptors() const; NameMap& gridDescriptors(); std::istream& inputStream() const; friend class ::TestFile; friend class ::TestStream; struct Impl; boost::scoped_ptr mImpl; }; //////////////////////////////////////// inline void File::write(const GridCPtrVec& grids, const MetaMap& meta) const { this->writeGrids(grids, meta); } template inline void File::write(const GridPtrContainerT& container, const MetaMap& meta) const { GridCPtrVec grids; std::copy(container.begin(), container.end(), std::back_inserter(grids)); this->writeGrids(grids, meta); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_FILE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Queue.h0000644000000000000000000002464212603226506013067 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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&); struct 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Archive.cc0000644000000000000000000012560712603226506013525 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Archive.h" #include "GridDescriptor.h" #include "io.h" #include #include #include // Boost.Interprocess uses a header-only portion of Boost.DateTime #define BOOST_DATE_TIME_NO_LIB #include #include #include #include #include #include #include #include // for BOOST_VERSION #include #ifdef _MSC_VER #include // open_existing_file(), close_file() extern "C" __declspec(dllimport) bool __stdcall GetFileTime( void* fh, void* ctime, void* atime, void* mtime); // boost::interprocess::detail was renamed to boost::interprocess::ipcdetail in Boost 1.48. // Ensure that both namespaces exist. namespace boost { namespace interprocess { namespace detail {} namespace ipcdetail {} } } #else #include // for struct stat #include // for stat() #include // for unlink() #endif #include // for std::find_if() #include // for errno #include // for getenv() #include // for std::memcpy() #include #include #include #ifndef DWA_BOOST_VERSION #define DWA_BOOST_VERSION (10 * BOOST_VERSION) #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { #ifdef OPENVDB_USE_BLOSC const uint32_t Archive::DEFAULT_COMPRESSION_FLAGS = (COMPRESS_BLOSC | COMPRESS_ACTIVE_MASK); #else const uint32_t Archive::DEFAULT_COMPRESSION_FLAGS = (COMPRESS_ZIP | COMPRESS_ACTIVE_MASK); #endif namespace { // Indices into a stream's internal extensible array of values used by readers and writers struct StreamState { static const long MAGIC_NUMBER; StreamState(); ~StreamState(); int magicNumber; int fileVersion; int libraryMajorVersion; int libraryMinorVersion; int dataCompression; int writeGridStatsMetadata; int gridBackground; int gridClass; int halfFloat; int mappedFile; int metadata; } sStreamState; const long StreamState::MAGIC_NUMBER = long((uint64_t(OPENVDB_MAGIC) << 32) | (uint64_t(OPENVDB_MAGIC))); //////////////////////////////////////// StreamState::StreamState(): magicNumber(std::ios_base::xalloc()) { // Having reserved an entry (the one at index magicNumber) in the extensible array // associated with every stream, store a magic number at that location in the // array belonging to the cout stream. std::cout.iword(magicNumber) = MAGIC_NUMBER; std::cout.pword(magicNumber) = this; // Search for a lower-numbered entry in cout's array that already contains the magic number. /// @todo This assumes that the indices returned by xalloc() increase monotonically. int existingArray = -1; for (int i = 0; i < magicNumber; ++i) { if (std::cout.iword(i) == MAGIC_NUMBER) { existingArray = i; break; } } if (existingArray >= 0 && std::cout.pword(existingArray) != 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; if (other.mappedFile != 0) { // memory-mapped file support was added in OpenVDB 3.0.0 mappedFile = other.mappedFile; metadata = other.metadata; halfFloat = other.halfFloat; } else { mappedFile = std::ios_base::xalloc(); metadata = std::ios_base::xalloc(); halfFloat = std::ios_base::xalloc(); } } else { // Reserve storage for per-stream file format and library version numbers // and other values of use to readers and writers. Each of the following // values is an index into the extensible arrays associated with all streams. // The indices are common to all streams, but the values stored at those indices // are unique to each stream. fileVersion = std::ios_base::xalloc(); libraryMajorVersion = std::ios_base::xalloc(); libraryMinorVersion = std::ios_base::xalloc(); dataCompression = std::ios_base::xalloc(); writeGridStatsMetadata = std::ios_base::xalloc(); gridBackground = std::ios_base::xalloc(); gridClass = std::ios_base::xalloc(); mappedFile = std::ios_base::xalloc(); metadata = std::ios_base::xalloc(); halfFloat = std::ios_base::xalloc(); } } StreamState::~StreamState() { // Ensure that this StreamState struct can no longer be accessed. std::cout.iword(magicNumber) = 0; std::cout.pword(magicNumber) = NULL; } } // unnamed namespace //////////////////////////////////////// struct StreamMetadata::Impl { Impl() : mFileVersion(OPENVDB_FILE_VERSION) , mLibraryVersion(OPENVDB_LIBRARY_MAJOR_VERSION, OPENVDB_LIBRARY_MINOR_VERSION) , mCompression(COMPRESS_NONE) , mGridClass(GRID_UNKNOWN) , mBackgroundPtr(NULL) , mHalfFloat(false) , mWriteGridStats(false) { } uint32_t mFileVersion; VersionId mLibraryVersion; uint32_t mCompression; uint32_t mGridClass; const void* mBackgroundPtr; ///< @todo use Metadata::Ptr? bool mHalfFloat; bool mWriteGridStats; MetaMap mGridMetadata; AuxDataMap mAuxData; }; // struct StreamMetadata StreamMetadata::StreamMetadata(): mImpl(new Impl) { } StreamMetadata::StreamMetadata(const StreamMetadata& other): mImpl(new Impl(*other.mImpl)) { } StreamMetadata::StreamMetadata(std::ios_base& strm): mImpl(new Impl) { mImpl->mFileVersion = getFormatVersion(strm); mImpl->mLibraryVersion = getLibraryVersion(strm); mImpl->mCompression = getDataCompression(strm); mImpl->mGridClass = getGridClass(strm); mImpl->mHalfFloat = getHalfFloat(strm); mImpl->mWriteGridStats = getWriteGridStatsMetadata(strm); } StreamMetadata::~StreamMetadata() { } StreamMetadata& StreamMetadata::operator=(const StreamMetadata& other) { if (&other != this) { mImpl.reset(new Impl(*other.mImpl)); } return *this; } void StreamMetadata::transferTo(std::ios_base& strm) const { io::setVersion(strm, mImpl->mLibraryVersion, mImpl->mFileVersion); io::setDataCompression(strm, mImpl->mCompression); io::setGridBackgroundValuePtr(strm, mImpl->mBackgroundPtr); io::setGridClass(strm, mImpl->mGridClass); io::setHalfFloat(strm, mImpl->mHalfFloat); io::setWriteGridStatsMetadata(strm, mImpl->mWriteGridStats); } uint32_t StreamMetadata::fileVersion() const { return mImpl->mFileVersion; } VersionId StreamMetadata::libraryVersion() const { return mImpl->mLibraryVersion; } uint32_t StreamMetadata::compression() const { return mImpl->mCompression; } uint32_t StreamMetadata::gridClass() const { return mImpl->mGridClass; } const void* StreamMetadata::backgroundPtr() const { return mImpl->mBackgroundPtr; } bool StreamMetadata::halfFloat() const { return mImpl->mHalfFloat; } bool StreamMetadata::writeGridStats() const { return mImpl->mWriteGridStats; } MetaMap& StreamMetadata::gridMetadata() { return mImpl->mGridMetadata; } const MetaMap& StreamMetadata::gridMetadata() const { return mImpl->mGridMetadata; } StreamMetadata::AuxDataMap& StreamMetadata::auxData() { return mImpl->mAuxData; } const StreamMetadata::AuxDataMap& StreamMetadata::auxData() const { return mImpl->mAuxData; } void StreamMetadata::setFileVersion(uint32_t v) { mImpl->mFileVersion = v; } void StreamMetadata::setLibraryVersion(VersionId v) { mImpl->mLibraryVersion = v; } void StreamMetadata::setCompression(uint32_t c) { mImpl->mCompression = c; } void StreamMetadata::setGridClass(uint32_t c) { mImpl->mGridClass = c; } void StreamMetadata::setBackgroundPtr(const void* ptr) { mImpl->mBackgroundPtr = ptr; } void StreamMetadata::setHalfFloat(bool b) { mImpl->mHalfFloat = b; } void StreamMetadata::setWriteGridStats(bool b) { mImpl->mWriteGridStats = b; } std::string StreamMetadata::str() const { std::ostringstream ostr; ostr << "version: " << libraryVersion().first << "." << libraryVersion().second << "/" << fileVersion() << "\n"; ostr << "class: " << GridBase::gridClassToString(static_cast(gridClass())) << "\n"; ostr << "compression: " << compressionToString(compression()) << "\n"; ostr << "half_float: " << (halfFloat() ? "true" : "false") << "\n"; ostr << "write_grid_stats_metadata: " << (writeGridStats() ? "true" : "false") << "\n"; if (!auxData().empty()) ostr << auxData(); if (gridMetadata().metaCount() != 0) { ostr << "grid_metadata:\n" << gridMetadata().str(/*indent=*/" "); } return ostr.str(); } std::ostream& operator<<(std::ostream& os, const StreamMetadata& meta) { os << meta.str(); return os; } namespace { template inline bool writeAsType(std::ostream& os, const boost::any& val) { if (val.type() == typeid(T)) { os << boost::any_cast(val); return true; } return false; } } // unnamed namespace std::ostream& operator<<(std::ostream& os, const StreamMetadata::AuxDataMap& auxData) { for (StreamMetadata::AuxDataMap::const_iterator it = auxData.begin(), end = auxData.end(); it != end; ++it) { os << it->first << ": "; // Note: boost::any doesn't support serialization. const boost::any& val = it->second; if (!writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val)) { os << val.type().name() << "(...)"; } os << "\n"; } return os; } //////////////////////////////////////// // Memory-mapping a VDB file permits threaded input (and output, potentially, // though that might not be practical for compressed files or files containing // multiple grids). In particular, a memory-mapped file can be loaded lazily, // meaning that the voxel buffers of the leaf nodes of a grid's tree are not allocated // until they are actually accessed. When access to its buffer is requested, // a leaf node allocates memory for the buffer and then streams in (and decompresses) // its contents from the memory map, starting from a stream offset that was recorded // at the time the node was constructed. The memory map must persist as long as // there are unloaded leaf nodes; this is ensured by storing a shared pointer // to the map in each unloaded node. class MappedFile::Impl { public: Impl(const std::string& filename, bool autoDelete) : mMap(filename.c_str(), boost::interprocess::read_only) , mRegion(mMap, boost::interprocess::read_only) , mAutoDelete(autoDelete) { mLastWriteTime = this->getLastWriteTime(); if (mAutoDelete) { #ifndef _MSC_VER // On Unix systems, unlink the file so that it gets deleted once it is closed. ::unlink(mMap.get_name()); #endif } } ~Impl() { std::string filename; if (const char* s = mMap.get_name()) filename = s; OPENVDB_LOG_DEBUG_RUNTIME("closing memory-mapped file " << filename); if (mNotifier) mNotifier(filename); if (mAutoDelete) { if (!boost::interprocess::file_mapping::remove(filename.c_str())) { if (errno != ENOENT) { // Warn if the file exists but couldn't be removed. std::string mesg = getErrorString(); if (!mesg.empty()) mesg = " (" + mesg + ")"; OPENVDB_LOG_WARN("failed to remove temporary file " << filename << mesg); } } } } Index64 getLastWriteTime() const { Index64 result = 0; const char* filename = mMap.get_name(); #ifdef _MSC_VER // boost::interprocess::detail was renamed to boost::interprocess::ipcdetail in Boost 1.48. using namespace boost::interprocess::detail; using namespace boost::interprocess::ipcdetail; if (void* fh = open_existing_file(filename, boost::interprocess::read_only)) { struct { unsigned long lo, hi; } mtime; // Windows FILETIME struct if (GetFileTime(fh, NULL, NULL, &mtime)) { result = (Index64(mtime.hi) << 32) | mtime.lo; } close_file(fh); } #else struct stat info; if (0 == ::stat(filename, &info)) { result = Index64(info.st_mtime); } #endif return result; } boost::interprocess::file_mapping mMap; boost::interprocess::mapped_region mRegion; bool mAutoDelete; Notifier mNotifier; mutable tbb::atomic mLastWriteTime; private: Impl(const Impl&); // not copyable Impl& operator=(const Impl&); // not copyable }; MappedFile::MappedFile(const std::string& filename, bool autoDelete): mImpl(new Impl(filename, autoDelete)) { } MappedFile::~MappedFile() { } std::string MappedFile::filename() const { std::string result; if (const char* s = mImpl->mMap.get_name()) result = s; return result; } boost::shared_ptr MappedFile::createBuffer() const { if (!mImpl->mAutoDelete && mImpl->mLastWriteTime > 0) { // Warn if the file has been modified since it was opened // (but don't bother checking if it is a private, temporary file). if (mImpl->getLastWriteTime() > mImpl->mLastWriteTime) { OPENVDB_LOG_WARN("file " << this->filename() << " might have changed on disk" << " since it was opened"); mImpl->mLastWriteTime = 0; // suppress further warnings } } return boost::shared_ptr( new boost::iostreams::stream_buffer( static_cast(mImpl->mRegion.get_address()), mImpl->mRegion.get_size())); } void MappedFile::setNotifier(const Notifier& notifier) { mImpl->mNotifier = notifier; } void MappedFile::clearNotifier() { mImpl->mNotifier.clear(); } //////////////////////////////////////// std::string getErrorString(int errorNum) { #if DWA_BOOST_VERSION >= 1044000 return boost::system::error_code(errorNum, boost::system::generic_category()).message(); #else return boost::system::error_code(errorNum, boost::system::get_generic_category()).message(); #endif } std::string getErrorString() { return getErrorString(errno); } //////////////////////////////////////// Archive::Archive() : mFileVersion(OPENVDB_FILE_VERSION) , mLibraryVersion(OPENVDB_LIBRARY_MAJOR_VERSION, OPENVDB_LIBRARY_MINOR_VERSION) , mUuid(boost::uuids::nil_uuid()) , mInputHasGridOffsets(false) , mEnableInstancing(true) , mCompression(DEFAULT_COMPRESSION_FLAGS) , mEnableGridStats(true) { } Archive::~Archive() { } Archive::Ptr Archive::copy() const { return Archive::Ptr(new Archive(*this)); } //////////////////////////////////////// std::string Archive::getUniqueTag() const { /// @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::ios_base& is) { /// @todo get from StreamMetadata return static_cast(is.iword(sStreamState.fileVersion)); } void Archive::setFormatVersion(std::istream& is) { is.iword(sStreamState.fileVersion) = mFileVersion; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(is)) { meta->setFileVersion(mFileVersion); } } VersionId getLibraryVersion(std::ios_base& is) { /// @todo get from StreamMetadata VersionId version; version.first = static_cast(is.iword(sStreamState.libraryMajorVersion)); version.second = static_cast(is.iword(sStreamState.libraryMinorVersion)); return version; } void Archive::setLibraryVersion(std::istream& is) { is.iword(sStreamState.libraryMajorVersion) = mLibraryVersion.first; ///< @todo remove is.iword(sStreamState.libraryMinorVersion) = mLibraryVersion.second; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(is)) { meta->setLibraryVersion(mLibraryVersion); } } std::string getVersion(std::ios_base& is) { VersionId version = getLibraryVersion(is); std::ostringstream ostr; ostr << version.first << "." << version.second << "/" << getFormatVersion(is); return ostr.str(); } void setCurrentVersion(std::istream& is) { is.iword(sStreamState.fileVersion) = OPENVDB_FILE_VERSION; ///< @todo remove is.iword(sStreamState.libraryMajorVersion) = OPENVDB_LIBRARY_MAJOR_VERSION; ///< @todo remove is.iword(sStreamState.libraryMinorVersion) = OPENVDB_LIBRARY_MINOR_VERSION; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(is)) { meta->setFileVersion(OPENVDB_FILE_VERSION); meta->setLibraryVersion(VersionId( OPENVDB_LIBRARY_MAJOR_VERSION, OPENVDB_LIBRARY_MINOR_VERSION)); } } void setVersion(std::ios_base& strm, const VersionId& libraryVersion, uint32_t fileVersion) { strm.iword(sStreamState.fileVersion) = fileVersion; ///< @todo remove strm.iword(sStreamState.libraryMajorVersion) = libraryVersion.first; ///< @todo remove strm.iword(sStreamState.libraryMinorVersion) = libraryVersion.second; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setFileVersion(fileVersion); meta->setLibraryVersion(libraryVersion); } } std::string Archive::version() const { std::ostringstream ostr; ostr << mLibraryVersion.first << "." << mLibraryVersion.second << "/" << mFileVersion; return ostr.str(); } //////////////////////////////////////// uint32_t getDataCompression(std::ios_base& strm) { /// @todo get from StreamMetadata return uint32_t(strm.iword(sStreamState.dataCompression)); } void setDataCompression(std::ios_base& strm, uint32_t c) { strm.iword(sStreamState.dataCompression) = c; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setCompression(c); } } void Archive::setDataCompression(std::istream& is) { io::setDataCompression(is, mCompression); ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(is)) { meta->setCompression(mCompression); } } //static bool Archive::hasBloscCompression() { #ifdef OPENVDB_USE_BLOSC return true; #else return false; #endif } void Archive::setGridCompression(std::ostream& os, const GridBase& grid) const { // Start with the options that are enabled globally for this archive. uint32_t c = compression(); // Disable options that are inappropriate for the given grid. switch (grid.getGridClass()) { case GRID_LEVEL_SET: case GRID_FOG_VOLUME: // ZLIB compression is not used on level sets or fog volumes. c = c & ~COMPRESS_ZIP; break; default: break; } io::setDataCompression(os, c); os.write(reinterpret_cast(&c), sizeof(uint32_t)); } void Archive::readGridCompression(std::istream& is) { if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { uint32_t c = COMPRESS_NONE; is.read(reinterpret_cast(&c), sizeof(uint32_t)); io::setDataCompression(is, c); } } //////////////////////////////////////// bool getWriteGridStatsMetadata(std::ios_base& strm) { /// @todo get from StreamMetadata return strm.iword(sStreamState.writeGridStatsMetadata) != 0; } void setWriteGridStatsMetadata(std::ios_base& strm, bool writeGridStats) { strm.iword(sStreamState.writeGridStatsMetadata) = writeGridStats; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setWriteGridStats(writeGridStats); } } //////////////////////////////////////// uint32_t getGridClass(std::ios_base& strm) { /// @todo get from StreamMetadata const uint32_t val = static_cast(strm.iword(sStreamState.gridClass)); if (val >= NUM_GRID_CLASSES) return GRID_UNKNOWN; return val; } void setGridClass(std::ios_base& strm, uint32_t cls) { strm.iword(sStreamState.gridClass) = long(cls); ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setGridClass(cls); } } bool getHalfFloat(std::ios_base& strm) { /// @todo get from StreamMetadata return strm.iword(sStreamState.halfFloat) != 0; } void setHalfFloat(std::ios_base& strm, bool halfFloat) { strm.iword(sStreamState.halfFloat) = halfFloat; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setHalfFloat(halfFloat); } } const void* getGridBackgroundValuePtr(std::ios_base& strm) { /// @todo get from StreamMetadata return strm.pword(sStreamState.gridBackground); } void setGridBackgroundValuePtr(std::ios_base& strm, const void* background) { strm.pword(sStreamState.gridBackground) = const_cast(background); ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setBackgroundPtr(background); } } MappedFile::Ptr getMappedFilePtr(std::ios_base& strm) { if (const void* ptr = strm.pword(sStreamState.mappedFile)) { return *static_cast(ptr); } return MappedFile::Ptr(); } void setMappedFilePtr(std::ios_base& strm, io::MappedFile::Ptr& mappedFile) { strm.pword(sStreamState.mappedFile) = &mappedFile; } StreamMetadata::Ptr getStreamMetadataPtr(std::ios_base& strm) { if (const void* ptr = strm.pword(sStreamState.metadata)) { return *static_cast(ptr); } return StreamMetadata::Ptr(); } void setStreamMetadataPtr(std::ios_base& strm, StreamMetadata::Ptr& meta, bool transfer) { strm.pword(sStreamState.metadata) = &meta; if (transfer && meta) meta->transferTo(strm); } StreamMetadata::Ptr clearStreamMetadataPtr(std::ios_base& strm) { StreamMetadata::Ptr result = getStreamMetadataPtr(strm); strm.pword(sStreamState.metadata) = NULL; return result; } //////////////////////////////////////// bool Archive::readHeader(std::istream& is) { // 1) Read the magic number for VDB. int64_t magic; is.read(reinterpret_cast(&magic), sizeof(int64_t)); if (magic != OPENVDB_MAGIC) { OPENVDB_THROW(IoError, "not a VDB file"); } // 2) Read the file format version number. is.read(reinterpret_cast(&mFileVersion), sizeof(uint32_t)); if (mFileVersion > OPENVDB_FILE_VERSION) { OPENVDB_LOG_WARN("unsupported VDB file format (expected version " << OPENVDB_FILE_VERSION << " or earlier, got version " << mFileVersion << ")"); } else if (mFileVersion < 211) { // Versions prior to 211 stored separate major, minor and patch numbers. uint32_t version; is.read(reinterpret_cast(&version), sizeof(uint32_t)); mFileVersion = 100 * mFileVersion + 10 * version; is.read(reinterpret_cast(&version), sizeof(uint32_t)); mFileVersion += version; } // 3) Read the library version numbers (not stored prior to file format version 211). mLibraryVersion.first = mLibraryVersion.second = 0; if (mFileVersion >= 211) { uint32_t version; is.read(reinterpret_cast(&version), sizeof(uint32_t)); mLibraryVersion.first = version; // major version is.read(reinterpret_cast(&version), sizeof(uint32_t)); mLibraryVersion.second = version; // minor version } // 4) Read the flag indicating whether the stream supports partial reading. // (Versions prior to 212 have no flag because they always supported partial reading.) mInputHasGridOffsets = true; if (mFileVersion >= 212) { char hasGridOffsets; is.read(&hasGridOffsets, sizeof(char)); mInputHasGridOffsets = hasGridOffsets; } // 5) Read the flag that indicates whether data is compressed. // (From version 222 on, compression information is stored per grid.) mCompression = DEFAULT_COMPRESSION_FLAGS; if (mFileVersion < OPENVDB_FILE_VERSION_BLOSC_COMPRESSION) { // Prior to the introduction of Blosc, ZLIB was the default compression scheme. mCompression = (COMPRESS_ZIP | COMPRESS_ACTIVE_MASK); } if (mFileVersion >= OPENVDB_FILE_VERSION_SELECTIVE_COMPRESSION && mFileVersion < OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { char isCompressed; is.read(&isCompressed, sizeof(char)); mCompression = (isCompressed != 0 ? COMPRESS_ZIP : COMPRESS_NONE); } // 6) Read the 16-byte (128-bit) uuid. boost::uuids::uuid oldUuid = mUuid; if (mFileVersion >= OPENVDB_FILE_VERSION_BOOST_UUID) { // UUID is stored as an ASCII string. is >> mUuid; } else { // Older versions stored the UUID as a byte string. char uuidBytes[16]; is.read(uuidBytes, 16); std::memcpy(&mUuid.data[0], uuidBytes, std::min(16, mUuid.size())); } return oldUuid != mUuid; // true if UUID in input stream differs from old UUID } void Archive::writeHeader(std::ostream& os, bool seekable) const { using boost::uint32_t; using boost::int64_t; // 1) Write the magic number for VDB. int64_t magic = OPENVDB_MAGIC; os.write(reinterpret_cast(&magic), sizeof(int64_t)); // 2) Write the file format version number. uint32_t version = OPENVDB_FILE_VERSION; os.write(reinterpret_cast(&version), sizeof(uint32_t)); // 3) Write the library version numbers. version = OPENVDB_LIBRARY_MAJOR_VERSION; os.write(reinterpret_cast(&version), sizeof(uint32_t)); version = OPENVDB_LIBRARY_MINOR_VERSION; os.write(reinterpret_cast(&version), sizeof(uint32_t)); // 4) Write a flag indicating that this stream contains no grid offsets. char hasGridOffsets = seekable; os.write(&hasGridOffsets, sizeof(char)); // 5) Write a flag indicating that this stream contains compressed leaf data. // (Omitted as of version 222) // 6) Generate a new random 16-byte (128-bit) uuid and write it to the stream. boost::mt19937 ran; ran.seed(static_cast(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())); } } //////////////////////////////////////// //static bool Archive::isDelayedLoadingEnabled() { #ifdef OPENVDB_2_ABI_COMPATIBLE return false; #else return (NULL == std::getenv("OPENVDB_DISABLE_DELAYED_LOAD")); #endif } namespace { struct NoBBox {}; template void doReadGrid(GridBase::Ptr grid, const GridDescriptor& gd, std::istream& is, const BoxType& bbox) { struct Local { static void readBuffers(GridBase& g, std::istream& istrm, NoBBox) { g.readBuffers(istrm); } #ifndef OPENVDB_2_ABI_COMPATIBLE static void readBuffers(GridBase& g, std::istream& istrm, const CoordBBox& indexBBox) { g.readBuffers(istrm, indexBBox); } static void readBuffers(GridBase& g, std::istream& istrm, const BBoxd& worldBBox) { g.readBuffers(istrm, g.constTransform().worldToIndexNodeCentered(worldBBox)); } #endif }; // Restore the file-level stream metadata on exit. struct OnExit { OnExit(std::ios_base& strm_): strm(&strm_), ptr(strm_.pword(sStreamState.metadata)) {} ~OnExit() { strm->pword(sStreamState.metadata) = ptr; } std::ios_base* strm; void* ptr; }; OnExit restore(is); // Stream metadata varies per grid, and it needs to persist // in case delayed load is in effect. io::StreamMetadata::Ptr streamMetadata; if (io::StreamMetadata::Ptr meta = io::getStreamMetadataPtr(is)) { // Make a grid-level copy of the file-level stream metadata. streamMetadata.reset(new StreamMetadata(*meta)); } else { streamMetadata.reset(new StreamMetadata); } streamMetadata->setHalfFloat(grid->saveFloatAsHalf()); io::setStreamMetadataPtr(is, streamMetadata, /*transfer=*/false); io::setGridClass(is, GRID_UNKNOWN); io::setGridBackgroundValuePtr(is, NULL); grid->readMeta(is); // Add a description of the compression settings to the grid as metadata. /// @todo Would this be useful? //const uint32_t c = getDataCompression(is); //grid->insertMeta(GridBase::META_FILE_COMPRESSION, // StringMetadata(compressionToString(c))); streamMetadata->gridMetadata() = static_cast(*grid); const GridClass gridClass = grid->getGridClass(); io::setGridClass(is, gridClass); if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_GRID_INSTANCING) { grid->readTransform(is); if (!gd.isInstance()) { grid->readTopology(is); Local::readBuffers(*grid, is, bbox); } } else { // Older versions of the library stored the transform after the topology. grid->readTopology(is); grid->readTransform(is); Local::readBuffers(*grid, is, bbox); } if (getFormatVersion(is) < OPENVDB_FILE_VERSION_NO_GRIDMAP) { // Older versions of the library didn't store grid names as metadata, // so when reading older files, copy the grid name from the descriptor // to the grid's metadata. if (grid->getName().empty()) { grid->setName(gd.gridName()); } } } } // unnamed namespace void Archive::readGrid(GridBase::Ptr grid, const GridDescriptor& gd, std::istream& is) { // Read the compression settings for this grid and tag the stream with them // so that downstream functions can reference them. readGridCompression(is); doReadGrid(grid, gd, is, NoBBox()); } #ifndef OPENVDB_2_ABI_COMPATIBLE void Archive::readGrid(GridBase::Ptr grid, const GridDescriptor& gd, std::istream& is, const BBoxd& worldBBox) { readGridCompression(is); doReadGrid(grid, gd, is, worldBBox); } void Archive::readGrid(GridBase::Ptr grid, const GridDescriptor& gd, std::istream& is, const CoordBBox& indexBBox) { readGridCompression(is); doReadGrid(grid, gd, is, indexBBox); } #endif //////////////////////////////////////// void Archive::write(std::ostream& os, const GridPtrVec& grids, bool seekable, const MetaMap& metadata) const { this->write(os, GridCPtrVec(grids.begin(), grids.end()), seekable, metadata); } void Archive::write(std::ostream& os, const GridCPtrVec& grids, bool seekable, const MetaMap& metadata) const { // Set stream flags so that downstream functions can reference them. io::StreamMetadata::Ptr streamMetadata = io::getStreamMetadataPtr(os); if (!streamMetadata) { streamMetadata.reset(new StreamMetadata); io::setStreamMetadataPtr(os, streamMetadata, /*transfer=*/false); } io::setDataCompression(os, compression()); io::setWriteGridStatsMetadata(os, isGridStatsMetadataEnabled()); this->writeHeader(os, seekable); metadata.writeMeta(os); // Write the number of non-null grids. int32_t gridCount = 0; for (GridCPtrVecCIter i = grids.begin(), e = grids.end(); i != e; ++i) { if (*i) ++gridCount; } os.write(reinterpret_cast(&gridCount), sizeof(int32_t)); typedef std::map TreeMap; typedef TreeMap::iterator TreeMapIter; TreeMap treeMap; // Determine which grid names are unique and which are not. typedef std::map NameHistogram; NameHistogram nameCount; for (GridCPtrVecCIter i = grids.begin(), e = grids.end(); i != e; ++i) { if (const GridBase::ConstPtr& grid = *i) { const std::string name = grid->getName(); NameHistogram::iterator it = nameCount.find(name); if (it != nameCount.end()) it->second++; else nameCount[name] = 1; } } std::set uniqueNames; // Write out the non-null grids. for (GridCPtrVecCIter i = grids.begin(), e = grids.end(); i != e; ++i) { if (const GridBase::ConstPtr& grid = *i) { // Ensure that the grid's descriptor has a unique grid name, by appending // a number to it if a grid with the same name was already written. // Always add a number if the grid name is empty, so that the grid can be // properly identified as an instance parent, if necessary. std::string name = grid->getName(); if (name.empty() || nameCount[name] > 1) { name = GridDescriptor::addSuffix(name, 0); } for (int n = 1; uniqueNames.find(name) != uniqueNames.end(); ++n) { name = GridDescriptor::addSuffix(grid->getName(), n); } uniqueNames.insert(name); // Create a grid descriptor. GridDescriptor gd(name, grid->type(), grid->saveFloatAsHalf()); // Check if this grid's tree is shared with a grid that has already been written. const TreeBase* treePtr = &(grid->baseTree()); TreeMapIter mapIter = treeMap.find(treePtr); bool isInstance = ((mapIter != treeMap.end()) && (mapIter->second.saveFloatAsHalf() == gd.saveFloatAsHalf())); if (mEnableInstancing && isInstance) { // This grid's tree is shared with another grid that has already been written. // Get the name of the other grid. gd.setInstanceParentName(mapIter->second.uniqueName()); // Write out this grid's descriptor and metadata, but not its tree. writeGridInstance(gd, grid, os, seekable); OPENVDB_LOG_DEBUG_RUNTIME("io::Archive::write(): " << GridDescriptor::nameAsString(gd.uniqueName()) << " (" << std::hex << treePtr << std::dec << ")" << " is an instance of " << GridDescriptor::nameAsString(gd.instanceParentName())); } else { // Write out the grid descriptor and its associated grid. writeGrid(gd, grid, os, seekable); // Record the grid's tree pointer so that the tree doesn't get written // more than once. treeMap[treePtr] = gd; } } // Some compression options (e.g., mask compression) are set per grid. // Restore the original settings before writing the next grid. io::setDataCompression(os, compression()); } } void Archive::writeGrid(GridDescriptor& gd, GridBase::ConstPtr grid, std::ostream& os, bool seekable) const { // Restore file-level stream metadata on exit. struct OnExit { OnExit(std::ios_base& strm_): strm(&strm_), ptr(strm_.pword(sStreamState.metadata)) {} ~OnExit() { strm->pword(sStreamState.metadata) = ptr; } std::ios_base* strm; void* ptr; }; OnExit restore(os); // Stream metadata varies per grid, so make a copy of the file-level stream metadata. io::StreamMetadata::Ptr streamMetadata; if (io::StreamMetadata::Ptr meta = io::getStreamMetadataPtr(os)) { streamMetadata.reset(new StreamMetadata(*meta)); } else { streamMetadata.reset(new StreamMetadata); } streamMetadata->setHalfFloat(grid->saveFloatAsHalf()); streamMetadata->gridMetadata() = static_cast(*grid); io::setStreamMetadataPtr(os, streamMetadata, /*transfer=*/false); // Write out the Descriptor's header information (grid name and type) gd.writeHeader(os); // Save the curent stream position as postion to where the offsets for // this GridDescriptor will be written to. int64_t offsetPos = (seekable ? int64_t(os.tellp()) : 0); // Write out the offset information. At this point it will be incorrect. // But we need to write it out to move the stream head forward. gd.writeStreamPos(os); // Now we know the starting grid storage position. if (seekable) gd.setGridPos(os.tellp()); // Save the compression settings for this grid. setGridCompression(os, *grid); // Save the grid's metadata and transform. if (!getWriteGridStatsMetadata(os)) { grid->writeMeta(os); } else { // Compute and add grid statistics metadata. GridBase::Ptr copyOfGrid = grid->copyGrid(); // shallow copy copyOfGrid->addStatsMetadata(); copyOfGrid->insertMeta(GridBase::META_FILE_COMPRESSION, StringMetadata(compressionToString(getDataCompression(os)))); copyOfGrid->writeMeta(os); } grid->writeTransform(os); // Save the grid's structure. grid->writeTopology(os); // Now we know the grid block storage position. if (seekable) gd.setBlockPos(os.tellp()); // Save out the data blocks of the grid. grid->writeBuffers(os); // Now we know the end position of this grid. if (seekable) gd.setEndPos(os.tellp()); if (seekable) { // Now, go back to where the Descriptor's offset information is written // and write the offsets again. os.seekp(offsetPos, std::ios_base::beg); gd.writeStreamPos(os); // Now seek back to the end. gd.seekToEnd(os); } } void Archive::writeGridInstance(GridDescriptor& gd, GridBase::ConstPtr grid, std::ostream& os, bool seekable) const { // Write out the Descriptor's header information (grid name, type // and instance parent name). gd.writeHeader(os); // Save the curent stream position as postion to where the offsets for // this GridDescriptor will be written to. int64_t offsetPos = (seekable ? int64_t(os.tellp()) : 0); // Write out the offset information. At this point it will be incorrect. // But we need to write it out to move the stream head forward. gd.writeStreamPos(os); // Now we know the starting grid storage position. if (seekable) gd.setGridPos(os.tellp()); // Save the compression settings for this grid. setGridCompression(os, *grid); // Save the grid's metadata and transform. grid->writeMeta(os); grid->writeTransform(os); // Now we know the end position of this grid. if (seekable) gd.setEndPos(os.tellp()); if (seekable) { // Now, go back to where the Descriptor's offset information is written // and write the offsets again. os.seekp(offsetPos, std::ios_base::beg); gd.writeStreamPos(os); // Now seek back to the end. gd.seekToEnd(os); } } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/File.cc0000644000000000000000000006577212603226506013031 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 "TempFile.h" #include #include #include #include #ifndef _MSC_VER #include #include #include #endif #include #include // for getenv(), strtoul() #include // for strerror_r() #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { // Implementation details of the File class struct File::Impl { enum { DEFAULT_COPY_MAX_BYTES = 500000000 }; // 500 MB struct NoBBox {}; // Common implementation of the various File::readGrid() overloads, // with and without bounding box clipping template static GridBase::Ptr readGrid(const File& file, const GridDescriptor& gd, const BoxType& bbox) { // This method should not be called for files that don't contain grid offsets. assert(file.inputHasGridOffsets()); GridBase::Ptr grid = file.createGrid(gd); gd.seekToGrid(file.inputStream()); unarchive(file, grid, gd, bbox); return grid; } static void unarchive(const File& file, GridBase::Ptr& grid, const GridDescriptor& gd, NoBBox) { file.Archive::readGrid(grid, gd, file.inputStream()); } #ifndef OPENVDB_2_ABI_COMPATIBLE static void unarchive(const File& file, GridBase::Ptr& grid, const GridDescriptor& gd, const CoordBBox& indexBBox) { file.Archive::readGrid(grid, gd, file.inputStream(), indexBBox); } static void unarchive(const File& file, GridBase::Ptr& grid, const GridDescriptor& gd, const BBoxd& worldBBox) { file.Archive::readGrid(grid, gd, file.inputStream(), worldBBox); } #endif static Index64 getDefaultCopyMaxBytes() { Index64 result = DEFAULT_COPY_MAX_BYTES; if (const char* s = std::getenv("OPENVDB_DELAYED_LOAD_COPY_MAX_BYTES")) { char* endptr = NULL; result = std::strtoul(s, &endptr, /*base=*/10); } return result; } std::string mFilename; // The file-level metadata MetaMap::Ptr mMeta; // The memory-mapped file MappedFile::Ptr mFileMapping; // The buffer for the input stream, if it is a memory-mapped file boost::shared_ptr mStreamBuf; // The file stream that is open for reading boost::scoped_ptr mInStream; // File-level stream metadata (file format, compression, etc.) StreamMetadata::Ptr mStreamMetadata; // Flag indicating if we have read in the global information (header, // metadata, and grid descriptors) for this VDB file bool mIsOpen; // File size limit for copying during delayed loading Index64 mCopyMaxBytes; // Grid descriptors for all grids stored in the file, indexed by grid name NameMap mGridDescriptors; // All grids, indexed by unique name (used only when mHasGridOffsets is false) Archive::NamedGridMap mNamedGrids; // All grids stored in the file (used only when mHasGridOffsets is false) GridPtrVecPtr mGrids; }; // class File::Impl //////////////////////////////////////// File::File(const std::string& filename): mImpl(new Impl) { mImpl->mFilename = filename; mImpl->mIsOpen = false; mImpl->mCopyMaxBytes = Impl::getDefaultCopyMaxBytes(); setInputHasGridOffsets(true); } File::~File() { } File::File(const File& other) : Archive(other) , mImpl(new Impl) { *this = other; } File& File::operator=(const File& other) { if (&other != this) { Archive::operator=(other); const Impl& otherImpl = *other.mImpl; mImpl->mFilename = otherImpl.mFilename; mImpl->mMeta = otherImpl.mMeta; mImpl->mIsOpen = false; // don't want two file objects reading from the same stream mImpl->mCopyMaxBytes = otherImpl.mCopyMaxBytes; mImpl->mGridDescriptors = otherImpl.mGridDescriptors; mImpl->mNamedGrids = otherImpl.mNamedGrids; mImpl->mGrids = otherImpl.mGrids; } return *this; } boost::shared_ptr File::copy() const { return boost::shared_ptr(new File(*this)); } //////////////////////////////////////// const std::string& File::filename() const { return mImpl->mFilename; } MetaMap::Ptr File::fileMetadata() { return mImpl->mMeta; } MetaMap::ConstPtr File::fileMetadata() const { return mImpl->mMeta; } const File::NameMap& File::gridDescriptors() const { return mImpl->mGridDescriptors; } File::NameMap& File::gridDescriptors() { return mImpl->mGridDescriptors; } std::istream& File::inputStream() const { if (!mImpl->mInStream) { OPENVDB_THROW(IoError, filename() << " is not open for reading"); } return *mImpl->mInStream; } //////////////////////////////////////// Index64 File::getSize() const { /// @internal boost::filesystem::file_size() would be a more portable alternative, /// but as of 9/2014, Houdini ships without the Boost.Filesystem library, /// which makes it much less convenient to use that library. Index64 result = std::numeric_limits::max(); std::string mesg = "could not get size of file " + filename(); #ifdef _MSC_VER // Get the file size by seeking to the end of the file. std::ifstream fstrm(filename()); if (fstrm) { fstrm.seekg(0, fstrm.end); result = static_cast(fstrm.tellg()); } else { OPENVDB_THROW(IoError, mesg); } #else // Get the file size using the stat() system call. struct stat info; if (0 != ::stat(filename().c_str(), &info)) { std::string s = getErrorString(); if (!s.empty()) mesg += " (" + s + ")"; OPENVDB_THROW(IoError, mesg); } if (!S_ISREG(info.st_mode)) { mesg += " (not a regular file)"; OPENVDB_THROW(IoError, mesg); } result = static_cast(info.st_size); #endif return result; } Index64 File::copyMaxBytes() const { return mImpl->mCopyMaxBytes; } void File::setCopyMaxBytes(Index64 bytes) { mImpl->mCopyMaxBytes = bytes; } //////////////////////////////////////// bool File::isOpen() const { return mImpl->mIsOpen; } bool File::open(bool delayLoad, const MappedFile::Notifier& notifier) { if (isOpen()) { OPENVDB_THROW(IoError, filename() << " is already open"); } mImpl->mInStream.reset(); // Open the file. boost::scoped_ptr newStream; boost::shared_ptr newStreamBuf; MappedFile::Ptr newFileMapping; if (!delayLoad || !Archive::isDelayedLoadingEnabled()) { newStream.reset(new std::ifstream( filename().c_str(), std::ios_base::in | std::ios_base::binary)); } else { bool isTempFile = false; std::string fname = filename(); if (getSize() < copyMaxBytes()) { // If the file is not too large, make a temporary private copy of it // and open the copy instead. The original file can then be modified // or removed without affecting delayed load. try { TempFile tempFile; std::ifstream fstrm(filename().c_str(), std::ios_base::in | std::ios_base::binary); boost::iostreams::copy(fstrm, tempFile); fname = tempFile.filename(); isTempFile = true; } catch (std::exception& e) { std::string mesg; if (e.what()) mesg = std::string(" (") + e.what() + ")"; OPENVDB_LOG_WARN("failed to create a temporary copy of " << filename() << " for delayed loading" << mesg << "; will read directly from " << filename() << " instead"); } } // While the file is open, its mapping, stream buffer and stream // must all be maintained. Once the file is closed, the buffer and // the stream can be discarded, but the mapping needs to persist // if any grids were lazily loaded. try { newFileMapping.reset(new MappedFile(fname, /*autoDelete=*/isTempFile)); newStreamBuf = newFileMapping->createBuffer(); newStream.reset(new std::istream(newStreamBuf.get())); } catch (std::exception& e) { std::ostringstream ostr; ostr << "could not open file " << filename(); if (e.what() != NULL) ostr << " (" << e.what() << ")"; OPENVDB_THROW(IoError, ostr.str()); } } if (newStream->fail()) { OPENVDB_THROW(IoError, "could not open file " << filename()); } // Read in the file header. bool newFile = false; try { newFile = Archive::readHeader(*newStream); } catch (IoError& e) { if (e.what() && std::string("not a VDB file") == e.what()) { // Rethrow, adding the filename. OPENVDB_THROW(IoError, filename() << " is not a VDB file"); } throw; } mImpl->mFileMapping = newFileMapping; if (mImpl->mFileMapping) mImpl->mFileMapping->setNotifier(notifier); mImpl->mStreamBuf = newStreamBuf; mImpl->mInStream.swap(newStream); // Tag the input stream with the file format and library version numbers // and other metadata. mImpl->mStreamMetadata.reset(new StreamMetadata); io::setStreamMetadataPtr(inputStream(), mImpl->mStreamMetadata, /*transfer=*/false); Archive::setFormatVersion(inputStream()); Archive::setLibraryVersion(inputStream()); Archive::setDataCompression(inputStream()); io::setMappedFilePtr(inputStream(), mImpl->mFileMapping); // Read in the VDB metadata. mImpl->mMeta = MetaMap::Ptr(new MetaMap); mImpl->mMeta->readMeta(inputStream()); if (!inputHasGridOffsets()) { OPENVDB_LOG_DEBUG_RUNTIME("file " << filename() << " does not support partial reading"); mImpl->mGrids.reset(new GridPtrVec); mImpl->mNamedGrids.clear(); // Stream in the entire contents of the file and append all grids to mGrids. const boost::int32_t gridCount = readGridCount(inputStream()); for (boost::int32_t i = 0; i < gridCount; ++i) { GridDescriptor gd; gd.read(inputStream()); GridBase::Ptr grid = createGrid(gd); Archive::readGrid(grid, gd, inputStream()); gridDescriptors().insert(std::make_pair(gd.gridName(), gd)); mImpl->mGrids->push_back(grid); mImpl->mNamedGrids[gd.uniqueName()] = grid; } // Connect instances (grids that share trees with other grids). for (NameMapCIter it = gridDescriptors().begin(); it != gridDescriptors().end(); ++it) { Archive::connectInstance(it->second, mImpl->mNamedGrids); } } else { // Read in just the grid descriptors. readGridDescriptors(inputStream()); } mImpl->mIsOpen = true; return newFile; // true if file is not identical to opened file } void File::close() { // Reset all data. mImpl->mMeta.reset(); mImpl->mGridDescriptors.clear(); mImpl->mGrids.reset(); mImpl->mNamedGrids.clear(); mImpl->mInStream.reset(); mImpl->mStreamBuf.reset(); mImpl->mStreamMetadata.reset(); mImpl->mFileMapping.reset(); mImpl->mIsOpen = false; setInputHasGridOffsets(true); } //////////////////////////////////////// bool File::hasGrid(const Name& name) const { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " is not open for reading"); } return (findDescriptor(name) != gridDescriptors().end()); } MetaMap::Ptr File::getMetadata() const { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " is not open for reading"); } // Return a deep copy of the file-level metadata, which was read // when the file was opened. return MetaMap::Ptr(new MetaMap(*mImpl->mMeta)); } GridPtrVecPtr File::getGrids() const { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " is not open for reading"); } GridPtrVecPtr ret; if (!inputHasGridOffsets()) { // If the input file doesn't have grid offsets, then all of the grids // have already been streamed in and stored in mGrids. ret = mImpl->mGrids; } else { ret.reset(new GridPtrVec); Archive::NamedGridMap namedGrids; // Read all grids represented by the GridDescriptors. for (NameMapCIter i = gridDescriptors().begin(), e = gridDescriptors().end(); i != e; ++i) { const GridDescriptor& gd = i->second; GridBase::Ptr grid = readGrid(gd); ret->push_back(grid); namedGrids[gd.uniqueName()] = grid; } // Connect instances (grids that share trees with other grids). for (NameMapCIter i = gridDescriptors().begin(), e = gridDescriptors().end(); i != e; ++i) { Archive::connectInstance(i->second, namedGrids); } } return ret; } GridBase::Ptr File::retrieveCachedGrid(const Name& name) const { // If the file has grid offsets, grids are read on demand // and not cached in mNamedGrids. if (inputHasGridOffsets()) return GridBase::Ptr(); // If the file does not have grid offsets, mNamedGrids should already // contain the entire contents of the file. // Search by unique name. Archive::NamedGridMap::const_iterator it = mImpl->mNamedGrids.find(GridDescriptor::stringAsUniqueName(name)); // If not found, search by grid name. if (it == mImpl->mNamedGrids.end()) it = mImpl->mNamedGrids.find(name); if (it == mImpl->mNamedGrids.end()) { OPENVDB_THROW(KeyError, filename() << " has no grid named \"" << name << "\""); } return it->second; } //////////////////////////////////////// GridPtrVecPtr File::readAllGridMetadata() { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " is not open for reading"); } GridPtrVecPtr ret(new GridPtrVec); if (!inputHasGridOffsets()) { // If the input file doesn't have grid offsets, then all of the grids // have already been streamed in and stored in mGrids. for (size_t i = 0, N = mImpl->mGrids->size(); i < N; ++i) { // Return copies of the grids, but with empty trees. ret->push_back((*mImpl->mGrids)[i]->copyGrid(/*treePolicy=*/CP_NEW)); } } else { // Read just the metadata and transforms for all grids. for (NameMapCIter i = gridDescriptors().begin(), e = gridDescriptors().end(); i != e; ++i) { const GridDescriptor& gd = i->second; GridBase::ConstPtr grid = readGridPartial(gd, /*readTopology=*/false); // Return copies of the grids, but with empty trees. // (As of 0.98.0, at least, it would suffice to just const cast // the grid pointers returned by readGridPartial(), but shallow // copying the grids helps to ensure future compatibility.) ret->push_back(grid->copyGrid(/*treePolicy=*/CP_NEW)); } } return ret; } GridBase::Ptr File::readGridMetadata(const Name& name) { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " is not open for reading."); } GridBase::ConstPtr ret; if (!inputHasGridOffsets()) { // Retrieve the grid from mGrids, which should already contain // the entire contents of the file. ret = readGrid(name); } else { NameMapCIter it = findDescriptor(name); if (it == gridDescriptors().end()) { OPENVDB_THROW(KeyError, filename() << " has no grid named \"" << name << "\""); } // Seek to and read in the grid from the file. const GridDescriptor& gd = it->second; ret = readGridPartial(gd, /*readTopology=*/false); } return ret->copyGrid(/*treePolicy=*/CP_NEW); } //////////////////////////////////////// GridBase::ConstPtr File::readGridPartial(const Name& name) { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " is not open for reading."); } GridBase::ConstPtr ret; if (!inputHasGridOffsets()) { // Retrieve the grid from mGrids, which should already contain // the entire contents of the file. if (GridBase::Ptr grid = readGrid(name)) { ret = boost::const_pointer_cast(grid); } } else { NameMapCIter it = findDescriptor(name); if (it == gridDescriptors().end()) { OPENVDB_THROW(KeyError, filename() << " has no grid named \"" << name << "\""); } // Seek to and read in the grid from the file. const GridDescriptor& gd = it->second; ret = readGridPartial(gd, /*readTopology=*/true); if (gd.isInstance()) { NameMapCIter parentIt = findDescriptor(GridDescriptor::nameAsString(gd.instanceParentName())); if (parentIt == gridDescriptors().end()) { OPENVDB_THROW(KeyError, "missing instance parent \"" << GridDescriptor::nameAsString(gd.instanceParentName()) << "\" for grid " << GridDescriptor::nameAsString(gd.uniqueName()) << " in file " << filename()); } if (GridBase::ConstPtr parent = readGridPartial(parentIt->second, /*readTopology=*/true)) { boost::const_pointer_cast(ret)->setTree( boost::const_pointer_cast(parent)->baseTreePtr()); } } } return ret; } //////////////////////////////////////// GridBase::Ptr File::readGrid(const Name& name) { return readGridByName(name, BBoxd()); } #ifndef OPENVDB_2_ABI_COMPATIBLE GridBase::Ptr File::readGrid(const Name& name, const BBoxd& bbox) { return readGridByName(name, bbox); } #endif #ifdef OPENVDB_2_ABI_COMPATIBLE GridBase::Ptr File::readGridByName(const Name& name, const BBoxd&) #else GridBase::Ptr File::readGridByName(const Name& name, const BBoxd& bbox) #endif { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " is not open for reading."); } #ifndef OPENVDB_2_ABI_COMPATIBLE const bool clip = bbox.isSorted(); #endif // If a grid with the given name was already read and cached // (along with the entire contents of the file, because the file // doesn't support random access), retrieve and return it. GridBase::Ptr grid = retrieveCachedGrid(name); if (grid) { #ifndef OPENVDB_2_ABI_COMPATIBLE if (clip) { grid = grid->deepCopyGrid(); grid->clipGrid(bbox); } #endif return grid; } NameMapCIter it = findDescriptor(name); if (it == gridDescriptors().end()) { OPENVDB_THROW(KeyError, filename() << " has no grid named \"" << name << "\""); } // Seek to and read in the grid from the file. const GridDescriptor& gd = it->second; #ifdef OPENVDB_2_ABI_COMPATIBLE grid = readGrid(gd); #else grid = (clip ? readGrid(gd, bbox) : readGrid(gd)); #endif if (gd.isInstance()) { /// @todo Refactor to share code with Archive::connectInstance()? NameMapCIter parentIt = findDescriptor(GridDescriptor::nameAsString(gd.instanceParentName())); if (parentIt == gridDescriptors().end()) { OPENVDB_THROW(KeyError, "missing instance parent \"" << GridDescriptor::nameAsString(gd.instanceParentName()) << "\" for grid " << GridDescriptor::nameAsString(gd.uniqueName()) << " in file " << filename()); } GridBase::Ptr parent; #ifdef OPENVDB_2_ABI_COMPATIBLE parent = readGrid(parentIt->second); #else if (clip) { const CoordBBox indexBBox = grid->constTransform().worldToIndexNodeCentered(bbox); parent = readGrid(parentIt->second, indexBBox); } else { parent = readGrid(parentIt->second); } #endif if (parent) grid->setTree(parent->baseTreePtr()); } return grid; } //////////////////////////////////////// void File::writeGrids(const GridCPtrVec& grids, const MetaMap& meta) const { if (isOpen()) { OPENVDB_THROW(IoError, filename() << " cannot be written because it is open for reading"); } // Create a file stream and write it out. std::ofstream file; file.open(filename().c_str(), std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); if (file.fail()) { OPENVDB_THROW(IoError, "could not open " << filename() << " for writing"); } // Write out the vdb. Archive::write(file, grids, /*seekable=*/true, meta); file.close(); } //////////////////////////////////////// void File::readGridDescriptors(std::istream& is) { // This method should not be called for files that don't contain grid offsets. assert(inputHasGridOffsets()); gridDescriptors().clear(); for (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. gridDescriptors().insert(std::make_pair(gd.gridName(), gd)); // Skip forward to the next descriptor. gd.seekToEnd(is); } } //////////////////////////////////////// File::NameMapCIter File::findDescriptor(const Name& name) const { const Name uniqueName = GridDescriptor::stringAsUniqueName(name); // Find all descriptors with the given grid name. std::pair range = gridDescriptors().equal_range(name); if (range.first == range.second) { // If no descriptors were found with the given grid name, the name might have // a suffix ("name[N]"). In that case, remove the "[N]" suffix and search again. range = gridDescriptors().equal_range(GridDescriptor::stripSuffix(uniqueName)); } const size_t count = size_t(std::distance(range.first, range.second)); if (count > 1 && name == uniqueName) { OPENVDB_LOG_WARN(filename() << " has more than one grid named \"" << name << "\""); } NameMapCIter ret = gridDescriptors().end(); if (count > 0) { if (name == uniqueName) { // If the given grid name is unique or if no "[N]" index was given, // use the first matching descriptor. ret = range.first; } else { // If the given grid name has a "[N]" index, find the descriptor // with a matching unique name. for (NameMapCIter it = range.first; it != range.second; ++it) { const Name candidateName = it->second.uniqueName(); if (candidateName == uniqueName || candidateName == name) { ret = it; break; } } } } return ret; } //////////////////////////////////////// GridBase::Ptr File::createGrid(const GridDescriptor& gd) const { // Create the grid. if (!GridBase::isRegistered(gd.gridType())) { OPENVDB_THROW(KeyError, "Cannot read grid " << GridDescriptor::nameAsString(gd.uniqueName()) << " from " << filename() << ": grid type " << gd.gridType() << " is not registered"); } GridBase::Ptr grid = GridBase::createGrid(gd.gridType()); if (grid) grid->setSaveFloatAsHalf(gd.saveFloatAsHalf()); return grid; } GridBase::ConstPtr File::readGridPartial(const GridDescriptor& gd, bool readTopology) const { // This method should not be called for files that don't contain grid offsets. assert(inputHasGridOffsets()); GridBase::Ptr grid = createGrid(gd); // Seek to grid. gd.seekToGrid(inputStream()); // Read the grid partially. readGridPartial(grid, inputStream(), gd.isInstance(), readTopology); // Promote to a const grid. GridBase::ConstPtr constGrid = grid; return constGrid; } GridBase::Ptr File::readGrid(const GridDescriptor& gd) const { return Impl::readGrid(*this, gd, Impl::NoBBox()); } #ifndef OPENVDB_2_ABI_COMPATIBLE GridBase::Ptr File::readGrid(const GridDescriptor& gd, const BBoxd& bbox) const { return Impl::readGrid(*this, gd, bbox); } GridBase::Ptr File::readGrid(const GridDescriptor& gd, const CoordBBox& bbox) const { return Impl::readGrid(*this, gd, bbox); } #endif void File::readGridPartial(GridBase::Ptr grid, std::istream& is, bool isInstance, bool readTopology) const { // This method should not be called for files that don't contain grid offsets. assert(inputHasGridOffsets()); // This code needs to stay in sync with io::Archive::readGrid(), in terms of // the order of operations. readGridCompression(is); grid->readMeta(is); if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_GRID_INSTANCING) { grid->readTransform(is); if (!isInstance && readTopology) { grid->readTopology(is); } } else { if (readTopology) { grid->readTopology(is); grid->readTransform(is); } } } //////////////////////////////////////// File::NameIterator File::beginName() const { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " is not open for reading"); } return File::NameIterator(gridDescriptors().begin()); } File::NameIterator File::endName() const { return File::NameIterator(gridDescriptors().end()); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/GridDescriptor.h0000644000000000000000000001314512603226506014723 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Archive.h0000644000000000000000000002430212603226506013355 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #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; /// Grid serializer/unserializer class OPENVDB_API Archive { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; static const uint32_t DEFAULT_COMPRESSION_FLAGS; Archive(); virtual ~Archive(); /// @brief Return a copy of this archive. virtual Ptr copy() const; /// @brief Return the UUID that was most recently written (or read, /// if no UUID has been written yet). std::string getUniqueTag() const; /// @brief Return @c true if the given UUID matches this archive's UUID. bool isIdentical(const std::string& uuidStr) const; /// @brief Return the file format version number of the input stream. uint32_t fileVersion() const { return mFileVersion; } /// @brief Return the (major, minor) version number of the library that was /// used to write the input stream. VersionId libraryVersion() const { return mLibraryVersion; } /// @brief Return a string of the form "./", giving the /// library and file format version numbers associated with the input stream. std::string version() const; /// @brief Return @c true if trees shared by multiple grids are written out /// only once, @c false if they are written out once per grid. bool isInstancingEnabled() const { return mEnableInstancing; } /// @brief Specify whether trees shared by multiple grids should be /// written out only once (@c true) or once per grid (@c false). /// @note Instancing is enabled by default. void setInstancingEnabled(bool b) { mEnableInstancing = b; } /// Return @c true if the OpenVDB library includes support for the Blosc compressor. static bool hasBloscCompression(); /// Return a bit mask specifying compression options for the data stream. uint32_t compression() const { return mCompression; } /// @brief Specify whether and how the data stream should be compressed. /// @param c bitwise OR (e.g., COMPRESS_ZIP | COMPRESS_ACTIVE_MASK) of /// compression option flags (see Compression.h for the available flags) /// @note Not all combinations of compression options are supported. void setCompression(uint32_t c) { mCompression = c; } /// @brief Return @c true if grid statistics (active voxel count and /// bounding box, etc.) are computed and written as grid metadata. bool isGridStatsMetadataEnabled() const { return mEnableGridStats; } /// @brief Specify whether grid statistics (active voxel count and /// bounding box, etc.) should be computed and written as grid metadata. void setGridStatsMetadataEnabled(bool b) { mEnableGridStats = b; } /// @brief Write the grids in the given container to this archive's output stream. virtual void write(const GridCPtrVec&, const MetaMap& = MetaMap()) const {} /// @brief Return @c true if delayed loading is enabled. /// @details If enabled, delayed loading can be disabled for individual files, /// but not vice-versa. /// @note Define the environment variable @c OPENVDB_DISABLE_DELAYED_LOAD /// to disable delayed loading unconditionally. static bool isDelayedLoadingEnabled(); protected: /// @brief Return @c true if the input stream contains grid offsets /// that allow for random access or partial reading. bool inputHasGridOffsets() const { return mInputHasGridOffsets; } void setInputHasGridOffsets(bool b) { mInputHasGridOffsets = b; } /// @brief Tag the given input stream with the input file format version number. /// /// The tag can be retrieved with getFormatVersion(). /// @sa getFormatVersion() void setFormatVersion(std::istream&); /// @brief Tag the given input stream with the version number of /// the library with which the input stream was created. /// /// The tag can be retrieved with getLibraryVersion(). /// @sa getLibraryVersion() void setLibraryVersion(std::istream&); /// @brief Tag the given input stream with flags indicating whether /// the input stream contains compressed data and how it is compressed. void setDataCompression(std::istream&); /// @brief Tag an output stream with flags specifying only those /// compression options that are applicable to the given grid. void setGridCompression(std::ostream&, const GridBase&) const; /// @brief Read in the compression flags for a grid and /// tag the given input stream with those flags. static void readGridCompression(std::istream&); /// Read in and return the number of grids on the input stream. static int32_t readGridCount(std::istream&); /// Populate the given grid from the input stream. static void readGrid(GridBase::Ptr, const GridDescriptor&, std::istream&); #ifndef OPENVDB_2_ABI_COMPATIBLE /// @brief Populate the given grid from the input stream, but only where it /// intersects the given world-space bounding box. static void readGrid(GridBase::Ptr, const GridDescriptor&, std::istream&, const BBoxd&); /// @brief Populate the given grid from the input stream, but only where it /// intersects the given index-space bounding box. static void readGrid(GridBase::Ptr, const GridDescriptor&, std::istream&, const CoordBBox&); #endif 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/doxygen-config0000644000000000000000000014762112603226302014063 0ustar rootroot# Doxyfile 1.4.7 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = OpenVDB # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = 3.1.0 ALIASES += vdbnamespace="openvdb::v3_1_0" PREDEFINED = OPENVDB_VERSION_NAME=v3_1_0 PREDEFINED += __declspec(x):= __attribute__(x):= #EXPAND_AS_DEFINED = \ # OPENVDB_API \ # OPENVDB_HOUDINI_API \ # OPENVDB_EXPORT \ # OPENVDB_IMPORT \ # OPENVDB_DEPRECATED # OPENVDB_STATIC_SPECIALIZATION # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = obj/doc # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, # Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, # Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, # Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, # Swedish, and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = YES # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = NO # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like the Qt-style comments (thus requiring an # explicit @brief command for a brief description. JAVADOC_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES += ijk="@f$(i,j,k)@f$" ALIASES += xyz="@f$(x,y,z)@f$" ALIASES += const="const" # Use this command to create a link to an OpenVDB class, function, etc. # Usage is "@vdblink:: @endlink", where is a fully # namespace-qualified symbol minus the openvdb and version number namespaces # and is the text of the link. # Example: @vdblink::tree::RootNode root node@endlink ALIASES += vdblink="@link @vdbnamespace" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for Java. # For instance, namespaces will be presented as packages, qualified scopes # will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to # include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = YES # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = NO # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = YES # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = YES # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = NO # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from the # version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = . \ io \ math \ metadata \ python/pyopenvdb.h \ tools \ tree \ util \ doc/doc.txt \ doc/faq.txt \ doc/math.txt \ doc/changes.txt \ doc/codingstyle.txt \ doc/api_0_98_0.txt \ doc/examplecode.txt \ doc/python.txt # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py FILE_PATTERNS = *.h # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix file system 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 # style sheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that # the files will be copied as-is; there are no commands or markers available. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. # Doxygen will adjust the colors in the style sheet and background images # according to this color. Hue is specified as an angle on a colorwheel, # see http://en.wikipedia.org/wiki/Hue for more information. # For instance the value 0 represents red, 60 is yellow, 120 is green, # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. # The allowed range is 0 to 359. HTML_COLORSTYLE_HUE = 4 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of # the colors in the HTML output. For a value of 0 the output will use # grayscales only. A value of 255 will produce the most vivid colors. HTML_COLORSTYLE_SAT = 222 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to # the luminance component of the colors in the HTML output. Values below # 100 gradually make the output lighter, whereas values above 100 make # the output darker. The value divided by 100 is the actual gamma applied, # so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, # and 100 does not change the gamma. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of # entries shown in the various tree structured indices initially; the user # can expand and collapse entries dynamically later on. Doxygen will expand # the tree to such a level that at most the specified number of entries are # visible (unless a fully collapsed tree already exceeds this amount). # So setting the number of entries 1 will produce a full collapsed tree by # default. 0 is a special value representing an infinite number of entries # and will result in a full expanded tree by default. HTML_INDEX_NUM_ENTRIES = 100 # 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 style sheet 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 # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. #PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = NO # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a call dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected # functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a caller dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected # functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that a graph may be further truncated if the graph's # image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH # and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), # the graph is not depth-constrained. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, which results in a white background. # Warning: Depending on the platform used, enabling this option may lead to # badly anti-aliased labels on the edges of a graph (i.e. they become hard to # read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO openvdb/COPYRIGHT0000644000000000000000000000255012603226302012502 0ustar rootrootCopyright (c) 2012-2013 DreamWorks Animation LLC All rights reserved. This software is distributed under the Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) Redistributions of source code must retain the above copyright and license notice and the following restrictions and disclaimer. * Neither the name of DreamWorks Animation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. openvdb/tools/0000755000000000000000000000000012603226506012353 5ustar rootrootopenvdb/tools/RayTracer.h0000644000000000000000000012416312603226506014427 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-visualization /// or as a starting point 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 #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 its 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 than 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 directional 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 = static_cast(1.0)): 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); if (name.find_last_of(".") == std::string::npos) name.append(".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 * double(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 / double(mFilm->width()) - 1) * mScaleWidth, (1 - 2 * j / double(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(Real(i) + iOffset, Real(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(Real(i) + iOffset, Real(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; }; /// @brief Shader that produces a simple matte. /// /// @details The color can either be constant (if GridT = /// Film::RGBA which is the default) or defined in a separate Vec3 /// color grid. Use SamplerType to define the order of interpolation /// (default is zero order, i.e. closes-point). template class MatteShader: public BaseShader { public: MatteShader(const GridT& grid) : mAcc(grid.getAccessor()), mXform(&grid.transform()) {} virtual ~MatteShader() {} virtual Film::RGBA operator()(const Vec3R& xyz, const Vec3R&, const Vec3R&) const { typename GridT::ValueType v = zeroVal(); SamplerType::sample(mAcc, mXform->worldToIndex(xyz), v); return Film::RGBA( static_cast(v[0]), static_cast(v[1]), static_cast(v[2])); } virtual BaseShader* copy() const { return new MatteShader(*this); } private: typename GridT::ConstAccessor mAcc; const math::Transform* mXform; }; // Template specialization using a constant color of the material. template 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; }; /// @brief Color shader that treats the surface normal (x, y, z) as an /// RGB color. /// /// @details The color can either be constant (if GridT = /// Film::RGBA which is the default) or defined in a separate Vec3 /// color grid. Use SamplerType to define the order of interpolation /// (default is zero order, i.e. closes-point). template class NormalShader: public BaseShader { public: NormalShader(const GridT& grid) : mAcc(grid.getAccessor()), mXform(&grid.transform()) {} virtual ~NormalShader() {} virtual Film::RGBA operator()(const Vec3R& xyz, const Vec3R& normal, const Vec3R&) const { typename GridT::ValueType v = zeroVal(); SamplerType::sample(mAcc, mXform->worldToIndex(xyz), v); return Film::RGBA(v[0]*(normal[0]+1.0f), v[1]*(normal[1]+1.0f), v[2]*(normal[2]+1.0f)); } virtual BaseShader* copy() const { return new NormalShader(*this); } private: typename GridT::ConstAccessor mAcc; const math::Transform* mXform; }; // Template specialization using a constant color of the material. template 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; }; /// @brief 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. /// /// @details The color can either be constant (if GridT = /// Film::RGBA which is the default) or defined in a separate Vec3 /// color grid. Use SamplerType to define the order of interpolation /// (default is zero order, i.e. closes-point). template class PositionShader: public BaseShader { public: PositionShader(const math::BBox& bbox, const GridT& grid) : mMin(bbox.min()) , mInvDim(1.0/bbox.extents()) , mAcc(grid.getAccessor()) , mXform(&grid.transform()) { } virtual ~PositionShader() {} virtual Film::RGBA operator()(const Vec3R& xyz, const Vec3R&, const Vec3R&) const { typename GridT::ValueType v = zeroVal(); SamplerType::sample(mAcc, mXform->worldToIndex(xyz), v); const Vec3R rgb = (xyz - mMin)*mInvDim; return Film::RGBA(v[0],v[1],v[2]) * Film::RGBA(rgb[0], rgb[1], rgb[2]); } virtual BaseShader* copy() const { return new PositionShader(*this); } private: const Vec3R mMin, mInvDim; typename GridT::ConstAccessor mAcc; const math::Transform* mXform; }; // Template specialization using a constant color of the material. template 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 The diffuse color can either be constant (if GridT = /// Film::RGBA which is the default) or defined in a separate Vec3 /// color grid. 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. Use /// SamplerType to define the order of interpolation (default is /// zero order, i.e. closes-point). template class DiffuseShader: public BaseShader { public: DiffuseShader(const GridT& grid): mAcc(grid.getAccessor()), mXform(&grid.transform()) {} virtual ~DiffuseShader() {} virtual Film::RGBA operator()(const Vec3R& xyz, const Vec3R& normal, const Vec3R& rayDir) const { typename GridT::ValueType v = zeroVal(); SamplerType::sample(mAcc, mXform->worldToIndex(xyz), v); // We take the abs of the dot product corresponding to having // light sources at +/- rayDir, i.e., two-sided shading. return Film::RGBA(v[0],v[1],v[2]) * math::Abs(normal.dot(rayDir)); } virtual BaseShader* copy() const { return new DiffuseShader(*this); } private: typename GridT::ConstAccessor mAcc; const math::Transform* mXform; }; // Template specialization using a constant color of the material. template class DiffuseShader: public BaseShader { public: DiffuseShader(const Film::RGBA& d = Film::RGBA(1.0f)): mRGBA(d) {} virtual ~DiffuseShader() {} 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 than 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 associated with a primary (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; //std::deque 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()(pLumi[0]); bg.g = static_cast(pLumi[1]); bg.b = static_cast(pLumi[2]); bg.a = static_cast(1.0f - pTrans.sum()/3.0f); }//Horizontal pixel scan }//Vertical pixel scan } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_RAYTRACER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetUtil.h0000644000000000000000000014001512603226506015106 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 /// /// @author Mihai Alden #ifndef OPENVDB_TOOLS_LEVEL_SET_UTIL_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_LEVEL_SET_UTIL_HAS_BEEN_INCLUDED #include "MeshToVolume.h" // for traceExteriorBoundaries #include "SignedFloodFill.h" // for signedFloodFill #include #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 construct a boolean mask that represents interior regions /// in a signed distance field. /// /// @return A shared pointer to either a boolean grid or tree with the same tree /// configuration and potentially transform as the input @c volume and whose active /// and @c true values correspond to the interior of the input signed distance field. /// /// @param volume Signed distance field / level set volume. /// @param isovalue Threshold below which values are considered part of the /// interior region. template inline typename GridOrTreeType::template ValueConverter::Type::Ptr sdfInteriorMask( const GridOrTreeType& volume, typename GridOrTreeType::ValueType isovalue = lsutilGridZero()); /// @brief Extracts the interior regions of a signed distance field and topologically enclosed /// (watertight) regions of value greater than the @a isovalue (cavities) that can arise /// as the result of CSG union operations between different shapes where at least one of /// the shapes has a concavity that is capped. /// /// For example the enclosed region of a capped bottle would include the walls and /// the interior cavity. /// /// @return A shared pointer to either a boolean grid or tree with the same tree configuration /// and potentially transform as the input @c volume and whose active and @c true values /// correspond to the interior and enclosed regions in the input signed distance field. /// /// @param volume Signed distance field / level set volume. /// @param isovalue Threshold below which values are considered part of the interior region. /// @param fillMask Optional boolean tree, when provided enclosed cavity regions that are not /// completely filled by this mask are ignored. /// /// For instance if the fill mask does not completely fill the bottle in the /// previous example only the walls and cap are returned and the interior /// cavity will be ignored. template inline typename GridOrTreeType::template ValueConverter::Type::Ptr extractEnclosedRegion(const GridOrTreeType& volume, typename GridOrTreeType::ValueType isovalue = lsutilGridZero(), const typename TreeAdapter::TreeType::template ValueConverter::Type* fillMask = NULL); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Internal utility objects and implementation details namespace level_set_util_internal { template struct MaskInteriorVoxels { typedef typename LeafNodeType::ValueType ValueType; typedef tree::LeafNode BoolLeafNodeType; MaskInteriorVoxels( ValueType isovalue, const LeafNodeType ** nodes, BoolLeafNodeType ** maskNodes) : mNodes(nodes), mMaskNodes(maskNodes), mIsovalue(isovalue) { } void operator()(const tbb::blocked_range& range) const { BoolLeafNodeType * maskNodePt = NULL; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { mMaskNodes[n] = NULL; const LeafNodeType& node = *mNodes[n]; if (!maskNodePt) { maskNodePt = new BoolLeafNodeType(node.origin(), false); } else { maskNodePt->setOrigin(node.origin()); } const ValueType* values = &node.getValue(0); for (Index i = 0; i < LeafNodeType::SIZE; ++i) { if (values[i] < mIsovalue) maskNodePt->setValueOn(i, true); } if (maskNodePt->onVoxelCount() > 0) { mMaskNodes[n] = maskNodePt; maskNodePt = NULL; } } if (maskNodePt) delete maskNodePt; } LeafNodeType const * const * const mNodes; BoolLeafNodeType ** const mMaskNodes; ValueType const mIsovalue; }; // MaskInteriorVoxels template struct MaskInteriorTiles { typedef typename TreeType::ValueType ValueType; MaskInteriorTiles(ValueType isovalue, const TreeType& tree, InternalNodeType ** maskNodes) : mTree(&tree), mMaskNodes(maskNodes), mIsovalue(isovalue) { } void operator()(const tbb::blocked_range& range) const { tree::ValueAccessor acc(*mTree); for (size_t n = range.begin(), N = range.end(); n < N; ++n) { typename InternalNodeType::ValueAllIter it = mMaskNodes[n]->beginValueAll(); for (; it; ++it) { if (acc.getValue(it.getCoord()) < mIsovalue) { it.setValue(true); it.setValueOn(true); } } } } TreeType const * const mTree; InternalNodeType ** const mMaskNodes; ValueType const mIsovalue; }; // MaskInteriorTiles template struct PopulateTree { typedef typename TreeType::ValueType ValueType; typedef typename TreeType::LeafNodeType LeafNodeType; PopulateTree(TreeType& tree, LeafNodeType** leafnodes, const size_t * nodexIndexMap, ValueType background) : mNewTree(background) , mTreePt(&tree) , mNodes(leafnodes) , mNodeIndexMap(nodexIndexMap) { } PopulateTree(PopulateTree& rhs, tbb::split) : mNewTree(rhs.mNewTree.background()) , mTreePt(&mNewTree) , mNodes(rhs.mNodes) , mNodeIndexMap(rhs.mNodeIndexMap) { } void operator()(const tbb::blocked_range& range) { tree::ValueAccessor acc(*mTreePt); for (size_t n = range.begin(), N = range.end(); n < N; ++n) { for (size_t i = mNodeIndexMap[n], I = mNodeIndexMap[n + 1]; i < I; ++i) { if (mNodes[i] != NULL) acc.addLeaf(mNodes[i]); } } } void join(PopulateTree& rhs) { mTreePt->merge(*rhs.mTreePt); } private: TreeType mNewTree; TreeType * const mTreePt; LeafNodeType ** const mNodes; size_t const * const mNodeIndexMap; }; // PopulateTree /// @brief Negative active values are set @c 0, everything else is set to @c 1. template struct LabelBoundaryVoxels { typedef typename LeafNodeType::ValueType ValueType; typedef tree::LeafNode CharLeafNodeType; LabelBoundaryVoxels( ValueType isovalue, const LeafNodeType ** nodes, CharLeafNodeType ** maskNodes) : mNodes(nodes), mMaskNodes(maskNodes), mIsovalue(isovalue) { } void operator()(const tbb::blocked_range& range) const { CharLeafNodeType * maskNodePt = NULL; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { mMaskNodes[n] = NULL; const LeafNodeType& node = *mNodes[n]; if (!maskNodePt) { maskNodePt = new CharLeafNodeType(node.origin(), 1); } else { maskNodePt->setOrigin(node.origin()); } typename LeafNodeType::ValueOnCIter it; for (it = node.cbeginValueOn(); it; ++it) { maskNodePt->setValueOn(it.pos(), ((*it - mIsovalue) < 0.0) ? 0 : 1); } if (maskNodePt->onVoxelCount() > 0) { mMaskNodes[n] = maskNodePt; maskNodePt = NULL; } } if (maskNodePt) delete maskNodePt; } LeafNodeType const * const * const mNodes; CharLeafNodeType ** const mMaskNodes; ValueType const mIsovalue; }; // LabelBoundaryVoxels template struct FlipRegionSign { typedef typename LeafNodeType::ValueType ValueType; FlipRegionSign(LeafNodeType ** nodes) : mNodes(nodes) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { ValueType* values = const_cast(&mNodes[n]->getValue(0)); for (Index i = 0; i < LeafNodeType::SIZE; ++i) { values[i] = values[i] < 0 ? 1 : -1; } } } LeafNodeType ** const mNodes; }; // FlipRegionSign template struct FindMinVoxelValue { typedef typename LeafNodeType::ValueType ValueType; FindMinVoxelValue(LeafNodeType const * const * const leafnodes) : minValue(std::numeric_limits::max()) , mNodes(leafnodes) { } FindMinVoxelValue(FindMinVoxelValue& rhs, tbb::split) : minValue(std::numeric_limits::max()) , mNodes(rhs.mNodes) { } void operator()(const tbb::blocked_range& range) { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { const ValueType* data = mNodes[n]->buffer().data(); for (Index i = 0; i < LeafNodeType::SIZE; ++i) { minValue = std::min(minValue, data[i]); } } } void join(FindMinVoxelValue& rhs) { minValue = std::min(minValue, rhs.minValue); } ValueType minValue; LeafNodeType const * const * const mNodes; }; // FindMinVoxelValue template struct FindMinTileValue { typedef typename InternalNodeType::ValueType ValueType; FindMinTileValue(InternalNodeType const * const * const nodes) : minValue(std::numeric_limits::max()) , mNodes(nodes) { } FindMinTileValue(FindMinTileValue& rhs, tbb::split) : minValue(std::numeric_limits::max()) , mNodes(rhs.mNodes) { } void operator()(const tbb::blocked_range& range) { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { typename InternalNodeType::ValueAllCIter it = mNodes[n]->beginValueAll(); for (; it; ++it) { minValue = std::min(minValue, *it); } } } void join(FindMinTileValue& rhs) { minValue = std::min(minValue, rhs.minValue); } ValueType minValue; InternalNodeType const * const * const mNodes; }; // FindMinTileValue template struct SDFVoxelsToFogVolume { typedef typename LeafNodeType::ValueType ValueType; SDFVoxelsToFogVolume(LeafNodeType ** nodes, ValueType cutoffDistance) : mNodes(nodes), mWeight(ValueType(1.0) / cutoffDistance) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { LeafNodeType& node = *mNodes[n]; node.setValuesOff(); ValueType* values = node.buffer().data(); for (Index i = 0; i < LeafNodeType::SIZE; ++i) { values[i] = values[i] > ValueType(0.0) ? ValueType(0.0) : values[i] * mWeight; if (values[i] > ValueType(0.0)) node.setValueOn(i); } if (node.onVoxelCount() == 0) { delete mNodes[n]; mNodes[n] = NULL; } } } LeafNodeType ** const mNodes; ValueType const mWeight; }; // SDFVoxelsToFogVolume template struct SDFTilesToFogVolume { SDFTilesToFogVolume(const TreeType& tree, InternalNodeType ** nodes) : mTree(&tree), mNodes(nodes) { } void operator()(const tbb::blocked_range& range) const { typedef typename TreeType::ValueType ValueType; tree::ValueAccessor acc(*mTree); for (size_t n = range.begin(), N = range.end(); n < N; ++n) { typename InternalNodeType::ValueAllIter it = mNodes[n]->beginValueAll(); for (; it; ++it) { if (acc.getValue(it.getCoord()) < ValueType(0.0)) { it.setValue(ValueType(1.0)); it.setValueOn(true); } } } } TreeType const * const mTree; InternalNodeType ** const mNodes; }; // SDFTilesToFogVolume template struct FillMaskBoundary { typedef typename TreeType::ValueType ValueType; typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::template ValueConverter::Type BoolTreeType; typedef typename BoolTreeType::LeafNodeType BoolLeafNodeType; FillMaskBoundary(const TreeType& tree, ValueType isovalue, const BoolTreeType& fillMask, const BoolLeafNodeType ** fillNodes, BoolLeafNodeType ** newNodes) : mTree(&tree), mFillMask(&fillMask), mFillNodes(fillNodes), mNewNodes(newNodes), mIsovalue(isovalue) { } void operator()(const tbb::blocked_range& range) const { tree::ValueAccessor maskAcc(*mFillMask); tree::ValueAccessor distAcc(*mTree); boost::scoped_array valueMask(new char[BoolLeafNodeType::SIZE]); for (size_t n = range.begin(), N = range.end(); n < N; ++n) { mNewNodes[n] = NULL; const BoolLeafNodeType& node = *mFillNodes[n]; const Coord& origin = node.origin(); const bool denseNode = node.isDense(); // possible early out if the fill mask is dense if (denseNode) { int denseNeighbors = 0; const BoolLeafNodeType* neighborNode = maskAcc.probeConstLeaf(origin.offsetBy(-1, 0, 0)); if (neighborNode && neighborNode->isDense()) ++denseNeighbors; neighborNode = maskAcc.probeConstLeaf(origin.offsetBy(BoolLeafNodeType::DIM, 0, 0)); if (neighborNode && neighborNode->isDense()) ++denseNeighbors; neighborNode = maskAcc.probeConstLeaf(origin.offsetBy(0, -1, 0)); if (neighborNode && neighborNode->isDense()) ++denseNeighbors; neighborNode = maskAcc.probeConstLeaf(origin.offsetBy(0, BoolLeafNodeType::DIM, 0)); if (neighborNode && neighborNode->isDense()) ++denseNeighbors; neighborNode = maskAcc.probeConstLeaf(origin.offsetBy(0, 0, -1)); if (neighborNode && neighborNode->isDense()) ++denseNeighbors; neighborNode = maskAcc.probeConstLeaf(origin.offsetBy(0, 0, BoolLeafNodeType::DIM)); if (neighborNode && neighborNode->isDense()) ++denseNeighbors; if (denseNeighbors == 6) continue; } // rest value mask memset(valueMask.get(), 0, sizeof(char) * BoolLeafNodeType::SIZE); const typename TreeType::LeafNodeType* distNode = distAcc.probeConstLeaf(origin); // check internal voxel neighbors bool earlyTermination = false; if (!denseNode) { if (distNode) { evalInternalNeighborsP(valueMask.get(), node, *distNode); evalInternalNeighborsN(valueMask.get(), node, *distNode); } else if (distAcc.getValue(origin) > mIsovalue) { earlyTermination = evalInternalNeighborsP(valueMask.get(), node); if (!earlyTermination) earlyTermination = evalInternalNeighborsN(valueMask.get(), node); } } // check external voxel neighbors if (!earlyTermination) { evalExternalNeighborsX(valueMask.get(), node, maskAcc, distAcc); evalExternalNeighborsX(valueMask.get(), node, maskAcc, distAcc); evalExternalNeighborsY(valueMask.get(), node, maskAcc, distAcc); evalExternalNeighborsY(valueMask.get(), node, maskAcc, distAcc); evalExternalNeighborsZ(valueMask.get(), node, maskAcc, distAcc); evalExternalNeighborsZ(valueMask.get(), node, maskAcc, distAcc); } // Export marked boundary voxels. int numBoundaryValues = 0; for (Index i = 0, I = BoolLeafNodeType::SIZE; i < I; ++i) { numBoundaryValues += valueMask[i] == 1; } if (numBoundaryValues > 0) { mNewNodes[n] = new BoolLeafNodeType(origin, false); for (Index i = 0, I = BoolLeafNodeType::SIZE; i < I; ++i) { if (valueMask[i] == 1) mNewNodes[n]->setValueOn(i); } } } } private: // Check internal voxel neighbors in positive {x, y, z} directions. void evalInternalNeighborsP(char* valueMask, const BoolLeafNodeType& node, const LeafNodeType& distNode) const { for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 0; z < BoolLeafNodeType::DIM - 1; ++z) { const Index pos = yPos + z; if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue; if (!node.isValueOn(pos + 1) && distNode.getValue(pos + 1) > mIsovalue) { valueMask[pos] = 1; } } } } for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 0; y < BoolLeafNodeType::DIM - 1; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) { const Index pos = yPos + z; if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue; if (!node.isValueOn(pos + BoolLeafNodeType::DIM) && distNode.getValue(pos + BoolLeafNodeType::DIM) > mIsovalue) { valueMask[pos] = 1; } } } } for (Index x = 0; x < BoolLeafNodeType::DIM - 1; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) { const Index pos = yPos + z; if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue; if (!node.isValueOn(pos + BoolLeafNodeType::DIM * BoolLeafNodeType::DIM) && distNode.getValue(pos + BoolLeafNodeType::DIM * BoolLeafNodeType::DIM) > mIsovalue) { valueMask[pos] = 1; } } } } } bool evalInternalNeighborsP(char* valueMask, const BoolLeafNodeType& node) const { for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 0; z < BoolLeafNodeType::DIM - 1; ++z) { const Index pos = yPos + z; if (node.isValueOn(pos) && !node.isValueOn(pos + 1)) { valueMask[pos] = 1; return true; } } } } for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 0; y < BoolLeafNodeType::DIM - 1; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) { const Index pos = yPos + z; if (node.isValueOn(pos) && !node.isValueOn(pos + BoolLeafNodeType::DIM)) { valueMask[pos] = 1; return true; } } } } for (Index x = 0; x < BoolLeafNodeType::DIM - 1; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) { const Index pos = yPos + z; if (node.isValueOn(pos) && !node.isValueOn(pos + BoolLeafNodeType::DIM * BoolLeafNodeType::DIM)) { valueMask[pos] = 1; return true; } } } } return false; } // Check internal voxel neighbors in negative {x, y, z} directions. void evalInternalNeighborsN(char* valueMask, const BoolLeafNodeType& node, const LeafNodeType& distNode) const { for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 1; z < BoolLeafNodeType::DIM; ++z) { const Index pos = yPos + z; if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue; if (!node.isValueOn(pos - 1) && distNode.getValue(pos - 1) > mIsovalue) { valueMask[pos] = 1; } } } } for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 1; y < BoolLeafNodeType::DIM; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) { const Index pos = yPos + z; if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue; if (!node.isValueOn(pos - BoolLeafNodeType::DIM) && distNode.getValue(pos - BoolLeafNodeType::DIM) > mIsovalue) { valueMask[pos] = 1; } } } } for (Index x = 1; x < BoolLeafNodeType::DIM; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) { const Index pos = yPos + z; if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue; if (!node.isValueOn(pos - BoolLeafNodeType::DIM * BoolLeafNodeType::DIM) && distNode.getValue(pos - BoolLeafNodeType::DIM * BoolLeafNodeType::DIM) > mIsovalue) { valueMask[pos] = 1; } } } } } bool evalInternalNeighborsN(char* valueMask, const BoolLeafNodeType& node) const { for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 1; z < BoolLeafNodeType::DIM; ++z) { const Index pos = yPos + z; if (node.isValueOn(pos) && !node.isValueOn(pos - 1)) { valueMask[pos] = 1; return true; } } } } for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 1; y < BoolLeafNodeType::DIM; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) { const Index pos = yPos + z; if (node.isValueOn(pos) && !node.isValueOn(pos - BoolLeafNodeType::DIM)) { valueMask[pos] = 1; return true; } } } } for (Index x = 1; x < BoolLeafNodeType::DIM; ++x) { const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM); for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) { const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM); for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) { const Index pos = yPos + z; if (node.isValueOn(pos) && !node.isValueOn(pos - BoolLeafNodeType::DIM * BoolLeafNodeType::DIM)) { valueMask[pos] = 1; return true; } } } } return false; } // Check external voxel neighbors // If UpWind is true check the X+ oriented node face, else the X- oriented face. template void evalExternalNeighborsX(char* valueMask, const BoolLeafNodeType& node, const tree::ValueAccessor& maskAcc, const tree::ValueAccessor& distAcc) const { const Coord& origin = node.origin(); Coord ijk(0, 0, 0), nijk; int step = -1; if (UpWind) { step = 1; ijk[0] = int(BoolLeafNodeType::DIM) - 1; } const Index xPos = ijk[0] << (2 * int(BoolLeafNodeType::LOG2DIM)); for (ijk[1] = 0; ijk[1] < int(BoolLeafNodeType::DIM); ++ijk[1]) { const Index yPos = xPos + (ijk[1] << int(BoolLeafNodeType::LOG2DIM)); for (ijk[2] = 0; ijk[2] < int(BoolLeafNodeType::DIM); ++ijk[2]) { const Index pos = yPos + ijk[2]; if (valueMask[pos] == 0 && node.isValueOn(pos)) { nijk = origin + ijk.offsetBy(step, 0, 0); if (!maskAcc.isValueOn(nijk) && distAcc.getValue(nijk) > mIsovalue) { valueMask[pos] = 1; } } } } } // If UpWind is true check the Y+ oriented node face, else the Y- oriented face. template void evalExternalNeighborsY(char* valueMask, const BoolLeafNodeType& node, const tree::ValueAccessor& maskAcc, const tree::ValueAccessor& distAcc) const { const Coord& origin = node.origin(); Coord ijk(0, 0, 0), nijk; int step = -1; if (UpWind) { step = 1; ijk[1] = int(BoolLeafNodeType::DIM) - 1; } const Index yPos = ijk[1] << int(BoolLeafNodeType::LOG2DIM); for (ijk[0] = 0; ijk[0] < int(BoolLeafNodeType::DIM); ++ijk[0]) { const Index xPos = yPos + (ijk[0] << (2 * int(BoolLeafNodeType::LOG2DIM))); for (ijk[2] = 0; ijk[2] < int(BoolLeafNodeType::DIM); ++ijk[2]) { const Index pos = xPos + ijk[2]; if (valueMask[pos] == 0 && node.isValueOn(pos)) { nijk = origin + ijk.offsetBy(0, step, 0); if (!maskAcc.isValueOn(nijk) && distAcc.getValue(nijk) > mIsovalue) { valueMask[pos] = 1; } } } } } // If UpWind is true check the Z+ oriented node face, else the Z- oriented face. template void evalExternalNeighborsZ(char* valueMask, const BoolLeafNodeType& node, const tree::ValueAccessor& maskAcc, const tree::ValueAccessor& distAcc) const { const Coord& origin = node.origin(); Coord ijk(0, 0, 0), nijk; int step = -1; if (UpWind) { step = 1; ijk[2] = int(BoolLeafNodeType::DIM) - 1; } for (ijk[0] = 0; ijk[0] < int(BoolLeafNodeType::DIM); ++ijk[0]) { const Index xPos = ijk[0] << (2 * int(BoolLeafNodeType::LOG2DIM)); for (ijk[1] = 0; ijk[1] < int(BoolLeafNodeType::DIM); ++ijk[1]) { const Index pos = ijk[2] + xPos + (ijk[1] << int(BoolLeafNodeType::LOG2DIM)); if (valueMask[pos] == 0 && node.isValueOn(pos)) { nijk = origin + ijk.offsetBy(0, 0, step); if (!maskAcc.isValueOn(nijk) && distAcc.getValue(nijk) > mIsovalue) { valueMask[pos] = 1; } } } } } ////////// TreeType const * const mTree; BoolTreeType const * const mFillMask; BoolLeafNodeType const * const * const mFillNodes; BoolLeafNodeType ** const mNewNodes; ValueType const mIsovalue; }; // FillMaskBoundary /// @brief Constructs a memory light char tree that represents the exterior region with @c +1 /// and the interior regions with @c -1. template inline typename TreeType::template ValueConverter::Type::Ptr computeEnclosedRegionMask(const TreeType& tree, typename TreeType::ValueType isovalue, const typename TreeType::template ValueConverter::Type* fillMask) { typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::RootNodeType RootNodeType; typedef typename RootNodeType::NodeChainType NodeChainType; typedef typename boost::mpl::at >::type InternalNodeType; typedef typename TreeType::template ValueConverter::Type CharTreeType; typedef typename CharTreeType::LeafNodeType CharLeafNodeType; typedef typename CharTreeType::RootNodeType CharRootNodeType; typedef typename CharRootNodeType::NodeChainType CharNodeChainType; typedef typename TreeType::template ValueConverter::Type BoolTreeType; typedef typename BoolTreeType::LeafNodeType BoolLeafNodeType; ///// const TreeType* treePt = &tree; size_t numLeafNodes = 0, numInternalNodes = 0; std::vector nodes; std::vector leafnodeCount; { // compute the prefix sum of the leafnode count in each internal node. std::vector internalNodes; treePt->getNodes(internalNodes); numInternalNodes = internalNodes.size(); leafnodeCount.push_back(0); for (size_t n = 0; n < numInternalNodes; ++n) { leafnodeCount.push_back(leafnodeCount.back() + internalNodes[n]->leafCount()); } numLeafNodes = leafnodeCount.back(); // extract all leafnodes nodes.reserve(numLeafNodes); for (size_t n = 0; n < numInternalNodes; ++n) { internalNodes[n]->getNodes(nodes); } } // create mask leafnodes boost::scoped_array maskNodes(new CharLeafNodeType*[numLeafNodes]); tbb::parallel_for(tbb::blocked_range(0, numLeafNodes), LabelBoundaryVoxels(isovalue, &nodes[0], maskNodes.get())); // create mask grid typename CharTreeType::Ptr maskTree(new CharTreeType(1)); PopulateTree populate(*maskTree, maskNodes.get(), &leafnodeCount[0], 1); tbb::parallel_reduce(tbb::blocked_range(0, numInternalNodes), populate); // optionally evaluate the fill mask std::vector extraMaskNodes; if (fillMask) { std::vector fillMaskNodes; fillMask->getNodes(fillMaskNodes); boost::scoped_array boundaryMaskNodes(new BoolLeafNodeType*[fillMaskNodes.size()]); tbb::parallel_for(tbb::blocked_range(0, fillMaskNodes.size()), FillMaskBoundary(tree, isovalue, *fillMask, &fillMaskNodes[0], boundaryMaskNodes.get())); tree::ValueAccessor maskAcc(*maskTree); for (size_t n = 0, N = fillMaskNodes.size(); n < N; ++n) { if (boundaryMaskNodes[n] == NULL) continue; const BoolLeafNodeType& boundaryNode = *boundaryMaskNodes[n]; const Coord& origin = boundaryNode.origin(); CharLeafNodeType* maskNodePt = maskAcc.probeLeaf(origin); if (!maskNodePt) { maskNodePt = maskAcc.touchLeaf(origin); extraMaskNodes.push_back(maskNodePt); } char* data = maskNodePt->buffer().data(); typename BoolLeafNodeType::ValueOnCIter it = boundaryNode.cbeginValueOn(); for (; it; ++it) { if (data[it.pos()] != 0) data[it.pos()] = -1; } delete boundaryMaskNodes[n]; } } // eliminate enclosed regions tools::traceExteriorBoundaries(*maskTree); // flip voxel sign to negative inside and positive outside. tbb::parallel_for(tbb::blocked_range(0, numLeafNodes), FlipRegionSign(maskNodes.get())); if (!extraMaskNodes.empty()) { tbb::parallel_for(tbb::blocked_range(0, extraMaskNodes.size()), FlipRegionSign(&extraMaskNodes[0])); } // propagate sign information into tile region tools::signedFloodFill(*maskTree); return maskTree; } // computeEnclosedRegionMask() template inline typename TreeType::template ValueConverter::Type::Ptr computeInteriorMask(const TreeType& tree, typename TreeType::ValueType iso) { typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::RootNodeType RootNodeType; typedef typename RootNodeType::NodeChainType NodeChainType; typedef typename boost::mpl::at >::type InternalNodeType; typedef typename TreeType::template ValueConverter::Type BoolTreeType; typedef typename BoolTreeType::LeafNodeType BoolLeafNodeType; typedef typename BoolTreeType::RootNodeType BoolRootNodeType; typedef typename BoolRootNodeType::NodeChainType BoolNodeChainType; typedef typename boost::mpl::at >::type BoolInternalNodeType; ///// size_t numLeafNodes = 0, numInternalNodes = 0; std::vector nodes; std::vector leafnodeCount; { // compute the prefix sum of the leafnode count in each internal node. std::vector internalNodes; tree.getNodes(internalNodes); numInternalNodes = internalNodes.size(); leafnodeCount.push_back(0); for (size_t n = 0; n < numInternalNodes; ++n) { leafnodeCount.push_back(leafnodeCount.back() + internalNodes[n]->leafCount()); } numLeafNodes = leafnodeCount.back(); // extract all leafnodes nodes.reserve(numLeafNodes); for (size_t n = 0; n < numInternalNodes; ++n) { internalNodes[n]->getNodes(nodes); } } // create mask leafnodes boost::scoped_array maskNodes(new BoolLeafNodeType*[numLeafNodes]); tbb::parallel_for(tbb::blocked_range(0, numLeafNodes), MaskInteriorVoxels(iso, &nodes[0], maskNodes.get())); // create mask grid typename BoolTreeType::Ptr maskTree(new BoolTreeType(false)); PopulateTree populate(*maskTree, maskNodes.get(), &leafnodeCount[0], false); tbb::parallel_reduce(tbb::blocked_range(0, numInternalNodes), populate); // evaluate tile values std::vector internalMaskNodes; maskTree->getNodes(internalMaskNodes); tbb::parallel_for(tbb::blocked_range(0, internalMaskNodes.size()), MaskInteriorTiles(iso, tree, &internalMaskNodes[0])); tree::ValueAccessor acc(tree); typename BoolTreeType::ValueAllIter it(*maskTree); it.setMaxDepth(BoolTreeType::ValueAllIter::LEAF_DEPTH - 2); for ( ; it; ++it) { if (acc.getValue(it.getCoord()) < iso) { it.setValue(true); it.setActiveState(true); } } return maskTree; } // computeInteriorMask() template struct GridOrTreeConstructor { typedef typename TreeType::template ValueConverter::Type::Ptr BoolTreePtrType; static BoolTreePtrType construct(const TreeType&, BoolTreePtrType& maskTree) { return maskTree; } }; template struct GridOrTreeConstructor > { typedef Grid GridType; typedef typename TreeType::template ValueConverter::Type BoolTreeType; typedef typename BoolTreeType::Ptr BoolTreePtrType; typedef Grid BoolGridType; typedef typename BoolGridType::Ptr BoolGridPtrType; static BoolGridPtrType construct(const GridType& grid, BoolTreePtrType& maskTree) { BoolGridPtrType maskGrid(BoolGridType::create(maskTree)); maskGrid->setTransform(grid.transform().copy()); return maskGrid; } }; } // namespace level_set_util_internal //////////////////////////////////////// template inline void sdfToFogVolume(GridType& grid, typename GridType::ValueType cutoffDistance) { typedef typename GridType::ValueType ValueType; typedef typename GridType::TreeType TreeType; typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::RootNodeType RootNodeType; typedef typename RootNodeType::NodeChainType NodeChainType; typedef typename boost::mpl::at >::type InternalNodeType; ////////// TreeType& tree = grid.tree(); size_t numLeafNodes = 0, numInternalNodes = 0; std::vector nodes; std::vector leafnodeCount; { // Compute the prefix sum of the leafnode count in each internal node. std::vector internalNodes; tree.getNodes(internalNodes); numInternalNodes = internalNodes.size(); leafnodeCount.push_back(0); for (size_t n = 0; n < numInternalNodes; ++n) { leafnodeCount.push_back(leafnodeCount.back() + internalNodes[n]->leafCount()); } numLeafNodes = leafnodeCount.back(); // Steal all leafnodes (Removes them from the tree and transfers ownership.) nodes.reserve(numLeafNodes); for (size_t n = 0; n < numInternalNodes; ++n) { internalNodes[n]->stealNodes(nodes, tree.background(), false); } // Clamp cutoffDistance to min sdf value ValueType minSDFValue = std::numeric_limits::max(); { level_set_util_internal::FindMinTileValue minOp(&internalNodes[0]); tbb::parallel_reduce(tbb::blocked_range(0, internalNodes.size()), minOp); minSDFValue = std::min(minSDFValue, minOp.minValue); } if (minSDFValue > ValueType(0.0)) { level_set_util_internal::FindMinVoxelValue minOp(&nodes[0]); tbb::parallel_reduce(tbb::blocked_range(0, nodes.size()), minOp); minSDFValue = std::min(minSDFValue, minOp.minValue); } cutoffDistance = -std::abs(cutoffDistance); cutoffDistance = minSDFValue > cutoffDistance ? minSDFValue : cutoffDistance; } // Transform voxel values and delete leafnodes that are uniformly zero after the transformation. // (Positive values are set to zero with inactive state and negative values are remapped // from zero to one with active state.) tbb::parallel_for(tbb::blocked_range(0, nodes.size()), level_set_util_internal::SDFVoxelsToFogVolume(&nodes[0], cutoffDistance)); // Populate a new tree with the remaining leafnodes typename TreeType::Ptr newTree(new TreeType(ValueType(0.0))); level_set_util_internal::PopulateTree populate(*newTree, &nodes[0], &leafnodeCount[0], 0); tbb::parallel_reduce(tbb::blocked_range(0, numInternalNodes), populate); // Transform tile values (Negative valued tiles are set to 1.0 with active state.) std::vector internalNodes; newTree->getNodes(internalNodes); tbb::parallel_for(tbb::blocked_range(0, internalNodes.size()), level_set_util_internal::SDFTilesToFogVolume(tree, &internalNodes[0])); { tree::ValueAccessor acc(tree); typename TreeType::ValueAllIter it(*newTree); it.setMaxDepth(TreeType::ValueAllIter::LEAF_DEPTH - 2); for ( ; it; ++it) { if (acc.getValue(it.getCoord()) < ValueType(0.0)) { it.setValue(ValueType(1.0)); it.setActiveState(true); } } } // Insert missing root level tiles. (The new tree is constructed from the remaining leafnodes // and will therefore not contain any root level tiles that may exist in the original tree.) { typename TreeType::ValueAllIter it(tree); it.setMaxDepth(TreeType::ValueAllIter::ROOT_DEPTH); for ( ; it; ++it) { if (it.getValue() < ValueType(0.0)) { newTree->addTile(TreeType::ValueAllIter::ROOT_LEVEL, it.getCoord(), ValueType(1.0), true); } } } grid.setTree(newTree); grid.setGridClass(GRID_FOG_VOLUME); } //////////////////////////////////////// template inline typename GridOrTreeType::template ValueConverter::Type::Ptr sdfInteriorMask(const GridOrTreeType& volume, typename GridOrTreeType::ValueType isovalue) { typedef typename TreeAdapter::TreeType TreeType; const TreeType& tree = TreeAdapter::tree(volume); typedef typename TreeType::template ValueConverter::Type::Ptr BoolTreePtrType; BoolTreePtrType mask = level_set_util_internal::computeInteriorMask(tree, isovalue); return level_set_util_internal::GridOrTreeConstructor::construct(volume, mask); } template inline typename GridOrTreeType::template ValueConverter::Type::Ptr extractEnclosedRegion(const GridOrTreeType& volume, typename GridOrTreeType::ValueType isovalue, const typename TreeAdapter::TreeType::template ValueConverter::Type* fillMask) { typedef typename TreeAdapter::TreeType TreeType; const TreeType& tree = TreeAdapter::tree(volume); typedef typename TreeType::template ValueConverter::Type::Ptr CharTreePtrType; CharTreePtrType regionMask = level_set_util_internal::computeEnclosedRegionMask(tree, isovalue, fillMask); typedef typename TreeType::template ValueConverter::Type::Ptr BoolTreePtrType; BoolTreePtrType mask = level_set_util_internal::computeInteriorMask(*regionMask, 0); return level_set_util_internal::GridOrTreeConstructor::construct(volume, mask); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVEL_SET_UTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/PointAdvect.h0000644000000000000000000004021612603226506014747 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 "VelocityFields.h" // VelocityIntegrator #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 //////////////////////////////////////// /// 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 size_t 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/VelocityFields.h0000644000000000000000000002622412603226506015457 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 VelocityFields.h /// /// @brief Defines two simple wrapper classes for advection velocity /// fields as well as VelocitySampler and VelocityIntegrator /// /// /// @details 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 in LevelSetAdvect.h. Thus, any /// class with this API should work with LevelSetAdvection. /// /// @warning 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. #ifndef OPENVDB_TOOLS_VELOCITY_FIELDS_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_VELOCITY_FIELDS_HAS_BEEN_INCLUDED #include #include #include "Interpolation.h" // for Sampler, etc. #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @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 ValueType; BOOST_STATIC_ASSERT(boost::is_floating_point::value); DiscreteField(const VelGridT &vel): mAccessor(vel.tree()), mTransform(&vel.transform()) { } /// @return const reference to the transform 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, ValueType) const { return Interpolator::sample(mAccessor, mTransform->worldToIndex(xyz)); } /// @return the velocity at the coordinate space position ijk inline VectorType operator() (const Coord& ijk, ValueType) 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 velocity field /// @note Primarily intended for debugging! /// @warning This analytical velocity only produce meaningful values /// in the unit box in world space. In other words make sure any level /// set surface is fully enclosed in the axis aligned bounding box /// spanning 0->1 in world units. template class EnrightField { public: typedef ScalarT ValueType; typedef math::Vec3 VectorType; BOOST_STATIC_ASSERT(boost::is_floating_point::value); EnrightField() {} /// @return const reference to the identity transform 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, ValueType time) const; /// @return the velocity at the coordinate space position ijk inline VectorType operator() (const Coord& ijk, ValueType time) const { return (*this)(ijk.asVec3d(), time); } }; // end of EnrightField template inline math::Vec3 EnrightField::operator() (const Vec3d& xyz, ValueType time) const { const ScalarT pi = boost::math::constants::pi(); const ScalarT 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)) )); } /////////////////////////////////////////////////////////////////////// /// Class to hold a Vec3 field interpreted 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 AccessorType; typedef typename GridT::ValueType ValueType; /// @brief Constructor from a grid VelocitySampler(const GridT& grid): mGrid(&grid), mAcc(grid.getAccessor()) { } /// @brief Copy-constructor VelocitySampler(const VelocitySampler& other): mGrid(other.mGrid), mAcc(mGrid->getAccessor()) { } /// @brief Samples the velocity at world position onto result. Supports both /// staggered (i.e. MAC) and collocated velocity grids. /// @return @c true if any one of the sampled values is active. template inline bool sample(const LocationType& world, ValueType& result) const { const Vec3R xyz = mGrid->worldToIndex(Vec3R(world[0], world[1], world[2])); bool active = Sampler::sample(mAcc, xyz, result); return active; } /// @brief Samples the velocity at world position onto result. Supports both /// staggered (i.e. MAC) and co-located velocity grids. template inline ValueType sample(const LocationType& world) const { const Vec3R xyz = mGrid->worldToIndex(Vec3R(world[0], world[1], world[2])); return Sampler::sample(mAcc, xyz); } private: // holding the Grids for the transforms const GridT* mGrid; // Velocity vector field AccessorType mAcc; };// end of VelocitySampler class /////////////////////////////////////////////////////////////////////// /// @brief Performs Runge-Kutta time integration of variable order in /// a static velocity field. /// /// @note Note that the order of the velocity sampling is controlled /// with the SampleOrder template parameter, which defaults /// to one, i.e. a tri-linear interpolation kernel. template class VelocityIntegrator { public: typedef typename GridT::ValueType VecType; typedef typename VecType::ValueType ElementType; VelocityIntegrator(const GridT& velGrid): mVelSampler(velGrid) { } /// @brief Variable order Runge-Kutta time integration for a single time step /// /// @param dt Time sub-step for the Runge-Kutte integrator of order OrderRK /// @param world Location in world space coordinates (both input and output) template inline void rungeKutta(const ElementType dt, LocationType& world) const { BOOST_STATIC_ASSERT(OrderRK <= 4); VecType P(static_cast(world[0]), static_cast(world[1]), static_cast(world[2])); // Note the if-branching below is optimized away at compile time if (OrderRK == 0) { return;// do nothing } else if (OrderRK == 1) { VecType V0; mVelSampler.sample(P, V0); P = dt * V0; } else if (OrderRK == 2) { VecType V0, V1; mVelSampler.sample(P, V0); mVelSampler.sample(P + ElementType(0.5) * dt * V0, V1); P = dt * V1; } else if (OrderRK == 3) { VecType V0, V1, V2; mVelSampler.sample(P, V0); mVelSampler.sample(P + ElementType(0.5) * dt * V0, V1); mVelSampler.sample(P + dt * (ElementType(2.0) * V1 - V0), V2); P = dt * (V0 + ElementType(4.0) * V1 + V2) * ElementType(1.0 / 6.0); } else if (OrderRK == 4) { VecType V0, V1, V2, V3; mVelSampler.sample(P, V0); mVelSampler.sample(P + ElementType(0.5) * dt * V0, V1); mVelSampler.sample(P + ElementType(0.5) * dt * V1, V2); mVelSampler.sample(P + dt * V2, V3); P = dt * (V0 + ElementType(2.0) * (V1 + V2) + V3) * ElementType(1.0 / 6.0); } typedef typename LocationType::ValueType OutType; world += LocationType(static_cast(P[0]), static_cast(P[1]), static_cast(P[2])); } private: VelocitySampler mVelSampler; };// end of VelocityIntegrator class } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_VELOCITY_FIELDS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetAdvect.h0000644000000000000000000006243612603226506015411 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #include #include #include "LevelSetTracker.h" #include "VelocityFields.h" // for EnrightField #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @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, ValueType 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::LeafRange LeafRange; typedef typename TrackerT::LeafType LeafType; typedef typename TrackerT::BufferType BufferType; typedef typename TrackerT::ValueType ValueType; 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 grain size of 0 or less disables multi-threading! void setGrainSize(int grainsize) { mTracker.setGrainSize(grainsize); } /// Advect the level set from its current time, time0, to its /// 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(ValueType time0, ValueType time1); private: // disallow copy construction and copy by assinment! LevelSetAdvection(const LevelSetAdvection&);// not implemented LevelSetAdvection& operator=(const LevelSetAdvection&);// not implemented // This templated private struct implements all the level set magic. template struct Advect { /// Main constructor Advect(LevelSetAdvection& parent); /// Shallow copy constructor called by tbb::parallel_for() threads Advect(const Advect& other); /// Shallow copy constructor called by tbb::parallel_reduce() threads Advect(Advect& other, tbb::split); /// destructor virtual ~Advect() { if (mIsMaster) this->clearField(); } /// Advect the level set from its current time, time0, to its final time, time1. /// @return number of CFL iterations size_t advect(ValueType time0, ValueType 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 Advect& other) { mMaxAbsV = math::Max(mMaxAbsV, other.mMaxAbsV); } /// Enum to define multi-threading type 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(ValueType time0, ValueType time1); void clearField(); void sampleXformedField(const LeafRange& r, ValueType time0, ValueType time1); void sampleAlignedField(const LeafRange& r, ValueType time0, ValueType time1); // 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)]|); template void euler(const LeafRange&, ValueType, Index, Index); inline void euler01(const LeafRange& r, ValueType t) {this->euler<0,1>(r, t, 0, 1);} inline void euler12(const LeafRange& r, ValueType t) {this->euler<1,2>(r, t, 1, 1);} inline void euler34(const LeafRange& r, ValueType t) {this->euler<3,4>(r, t, 1, 2);} inline void euler13(const LeafRange& r, ValueType t) {this->euler<1,3>(r, t, 1, 2);} LevelSetAdvection& mParent; VectorType** mVec; const ValueType mMinAbsV; ValueType mMaxAbsV; const MapT* mMap; typename boost::function mTask; const bool mIsMaster; }; // end of private Advect struct template size_t advect1(ValueType time0, ValueType time1); template size_t advect2(ValueType time0, ValueType time1); template size_t advect3(ValueType time0, ValueType 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; };//end of LevelSetAdvection template inline size_t LevelSetAdvection::advect(ValueType time0, ValueType 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(ValueType time0, ValueType 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(ValueType time0, ValueType 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(ValueType time0, ValueType time1) { Advect tmp(*this); return tmp.advect(time0, time1); } /////////////////////////////////////////////////////////////////////// template template inline LevelSetAdvection:: Advect:: Advect(LevelSetAdvection& parent): mParent(parent), mVec(NULL), mMinAbsV(ValueType(1e-6)), mMap(parent.mTracker.grid().transform().template constMap().get()), mTask(0), mIsMaster(true) { } template template inline LevelSetAdvection:: Advect:: Advect(const Advect& 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:: Advect:: Advect(Advect& 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:: Advect:: advect(ValueType time0, ValueType 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 ValueType 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(&Advect::euler01, _1, _2, dt); // 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(&Advect::euler01, _1, _2, dt); // 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(&Advect::euler12, _1, _2, dt); // 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(&Advect::euler01, _1, _2, dt); // 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(&Advect::euler34, _1, _2, dt); // 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(&Advect::euler13, _1, _2, dt); // 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:: Advect:: sampleField(ValueType time0, ValueType time1) { mMaxAbsV = mMinAbsV; const size_t leafCount = mParent.mTracker.leafs().leafCount(); if (leafCount==0) return ValueType(0.0); mVec = new VectorType*[leafCount]; if (mParent.mField.transform() == mParent.mTracker.grid().transform()) { mTask = boost::bind(&Advect::sampleAlignedField, _1, _2, time0, time1); } else { mTask = boost::bind(&Advect::sampleXformedField, _1, _2, time0, time1); } this->cook(PARALLEL_REDUCE); if (math::isExactlyEqual(mMinAbsV, mMaxAbsV)) return ValueType(0.0);//V is essentially zero #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif const ValueType CFL = (TemporalScheme == math::TVD_RK1 ? ValueType(0.3) : TemporalScheme == math::TVD_RK2 ? ValueType(0.9) : ValueType(1.0))/math::Sqrt(ValueType(3.0)); const ValueType dt = math::Abs(time1 - time0), dx = mParent.mTracker.voxelSize(); return math::Min(dt, ValueType(CFL*dx/math::Sqrt(mMaxAbsV))); } template template inline void LevelSetAdvection:: Advect:: sampleXformedField(const LeafRange& range, ValueType time0, ValueType time1) { const bool isForward = time0 < time1; typedef typename LeafType::ValueOnCIter VoxelIterT; const MapT& map = *mMap; mParent.mTracker.checkInterrupter(); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { VectorType* vec = new VectorType[leafIter->onVoxelCount()]; mVec[leafIter.pos()] = vec; for (VoxelIterT iter = leafIter->cbeginValueOn(); iter; ++iter, ++vec) { const VectorType v = mParent.mField(map.applyMap(iter.getCoord().asVec3d()), time0); mMaxAbsV = math::Max(mMaxAbsV, ValueType(math::Pow2(v[0])+math::Pow2(v[1])+math::Pow2(v[2]))); *vec = isForward ? v : -v; } } } template template inline void LevelSetAdvection:: Advect:: sampleAlignedField(const LeafRange& range, ValueType time0, ValueType time1) { const bool isForward = time0 < time1; typedef typename LeafType::ValueOnCIter VoxelIterT; mParent.mTracker.checkInterrupter(); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { VectorType* vec = new VectorType[leafIter->onVoxelCount()]; mVec[leafIter.pos()] = vec; for (VoxelIterT iter = leafIter->cbeginValueOn(); iter; ++iter, ++vec) { const VectorType v = mParent.mField(iter.getCoord(), time0); mMaxAbsV = math::Max(mMaxAbsV, ValueType(math::Pow2(v[0])+math::Pow2(v[1])+math::Pow2(v[2]))); *vec = isForward ? v : -v; } } } template template inline void LevelSetAdvection:: Advect:: clearField() { if (mVec == NULL) return; for (size_t n=0, e=mParent.mTracker.leafs().leafCount(); n template inline void LevelSetAdvection:: Advect:: cook(ThreadingMode mode, size_t swapBuffer) { mParent.mTracker.startInterrupter("Advecting level set"); const int grainSize = mParent.mTracker.getGrainSize(); const LeafRange range = mParent.mTracker.leafs().leafRange(grainSize); if (grainSize == 0) { (*this)(range); } else if (mode == PARALLEL_FOR) { tbb::parallel_for(range, *this); } else if (mode == PARALLEL_REDUCE) { tbb::parallel_reduce(range, *this); } else { OPENVDB_THROW(ValueError,"Undefined threading mode"); } mParent.mTracker.leafs().swapLeafBuffer(swapBuffer, grainSize == 0); mParent.mTracker.endInterrupter(); } // 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 template inline void LevelSetAdvection:: Advect:: euler(const LeafRange& range, ValueType dt, Index phiBuffer, Index resultBuffer) { typedef math::BIAS_SCHEME SchemeT; typedef typename SchemeT::template ISStencil::StencilType StencilT; typedef typename LeafType::ValueOnCIter VoxelIterT; typedef math::GradientBiased GradT; static const ValueType Alpha = ValueType(Nominator)/ValueType(Denominator); static const ValueType Beta = ValueType(1) - Alpha; mParent.mTracker.checkInterrupter(); const MapT& map = *mMap; StencilT stencil(mParent.mTracker.grid()); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { const VectorType* v = mVec[leafIter.pos()]; const ValueType* phi = leafIter.buffer(phiBuffer).data(); ValueType* result = leafIter.buffer(resultBuffer).data(); for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter, ++v) { const Index i = voxelIter.pos(); stencil.moveTo(voxelIter); const ValueType a = stencil.getValue() - dt * v->dot(GradT::result(map, stencil,*v)); result[i] = Nominator ? Alpha * phi[i] + Beta * a : a; }//loop over active voxels in the leaf of the mask }//loop over leafs of the level set } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVEL_SET_ADVECT_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/ValueTransformer.h0000644000000000000000000006015612603226506016033 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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: const bool mIsRoot; const 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/ParticlesToLevelSet.h0000644000000000000000000010704612603226506016431 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 signed 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 blobby. 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 the nth particle. /// // Required by ParticledToLevelSet::rasterizeSphere(*this,radius). /// void getPos(size_t n, Vec3R& xyz) const; /// /// // Get the world space position and radius of the nth 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 the nth particle. /// // Required by ParticledToLevelSet::rasterizeSphere(*this,radius). /// void getPosRadVel(size_t n, Vec3R& xyz, Real& rad, Vec3R& vel) const; /// /// // Get the attribute of the nth particle. AttributeType is user-defined! /// // Only required if attribute transfer is enabled in ParticlesToLevelSet. /// void getAtt(size_t n, 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 "Composite.h" // for csgUnion() #include "PointPartitioner.h" #include "Prune.h" #include "SignedFloodFill.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { namespace p2ls_internal { // 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 member class defined below. template class BlindData; }// namespace p2ls_internal 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) particles are unioned /// onto the existing level set surface. However, if attribute transfer /// 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 than once and only after /// all the particles have been rasterized. It has no effect or /// overhead if attribute transfer is disabled, i.e. AttributeT = /// void and prune is false. void finalize(bool prune = false); /// @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 Returns 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 p2ls_internal::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(bool prune) { if (mBlindGrid==NULL) { if (prune) tools::pruneLevelSet(mSdfGrid->tree()); return; } else { if (prune) tools::prune(mBlindGrid->tree()); } 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. level sets have no active tiles! 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 construction! SdfLeafT* sdfLeaf = sdfTree->probeLeaf(xyz); AttLeafT* attLeaf = attTree->probeLeaf(xyz); // Use linear offset (vs coordinate) access for better performance! typename LeafT::ValueOnCIter m=leaf.cbeginValueOn(); if (!m) {//no active values in leaf node so copy everything for (openvdb::Index k = 0; k!=LeafT::SIZE; ++k) { const BlindType& v = leaf.getValue(k); sdfLeaf->setValueOnly(k, v.visible()); attLeaf->setValueOnly(k, v.blind()); } } else {//only copy active values (using flood fill for the inactive values) for(; m; ++m) { const openvdb::Index k = m.pos(); const BlindType& v = *m; sdfLeaf->setValueOnly(k, v.visible()); attLeaf->setValueOnly(k, v.blind()); } } } tools::signedFloodFill(*sdfTree);//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; typedef typename GridT::TreeType TreeT; typedef typename TreeT::LeafNodeType LeafNodeT; typedef PointPartitioner PointPartitionerT; /// @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) , mIsCopy(false) { mPointPartitioner = new PointPartitionerT(); mPointPartitioner->construct(particles, mGrid->transform()); } /// @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) , mIsCopy(true) , mPointPartitioner(other.mPointPartitioner) { mGrid->newTree(); } virtual ~Raster() { // Copies construct temporary grids that have to be deleted // but the original has ownership of the bucket array if (mIsCopy) { delete mGrid; } else { delete mPointPartitioner; } } /// @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&) { 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 = SdfT(1/mParent.mDx); AttT att; Vec3R pos; Real rad; // Loop over buckets for (size_t n = r.begin(), N = r.end(); n < N; ++n) { // Loop over particles in bucket n. typename PointPartitionerT::IndexIterator iter = mPointPartitioner->indices(n); for ( ; run && iter; ++iter) { const Index32& id = *iter; mParticles.getPosRad(id, pos, rad); const SdfT R = SdfT(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 }//end loop over buckets } /// @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 = static_cast(mParent.mDx), w = static_cast(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; // Loop over buckets for (size_t n = r.begin(), N = r.end(); n < N; ++n) { // Loop over particles in bucket n. typename PointPartitionerT::IndexIterator iter = mPointPartitioner->indices(n); for ( ; iter; ++iter) { const Index32& id = *iter; 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 = static_cast(math::Pow2(c.x() - P[0])); for (c.y() = a.y(); c.y() <= b.y(); ++c.y()) { SdfT x2y2 = static_cast(x2 + math::Pow2(c.y() - P[1])); for (c.z() = a.z(); c.z() <= b.z(); ++c.z()) { SdfT x2y2z2 = static_cast( 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 = SdfT(mParent.mRmin), invDx = SdfT(1/mParent.mDx); // Loop over buckets for (size_t n = r.begin(), N = r.end(); n < N; ++n) { // Loop over particles in bucket n. typename PointPartitionerT::IndexIterator iter = mPointPartitioner->indices(n); for ( ; run && iter; ++iter) { const Index32& id = *iter; mParticles.getPosRadVel(id, pos, rad, vel); const SdfT R0 = SdfT(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 = SdfT(V.length()), inv_speed = SdfT(1.0/speed); const Vec3R Nrml = -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*Nrml;// adaptive offset along inverse velocity direction d = SdfT((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 }//end loop over buckets } void cook() { // parallelize over the point buckets const Index32 bucketCount = Index32(mPointPartitioner->size()); if (mParent.mGrainSize>0) { tbb::parallel_reduce( tbb::blocked_range(0, bucketCount, mParent.mGrainSize), *this); } else { (*this)(tbb::blocked_range(0, bucketCount)); } } /// @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 Euclidean signed distances measured in world /// units). Also note we use the convention of positive distances /// outside the surface and 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 = SdfT(mParent.mDx), w = SdfT(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 = SdfT(math::Pow2(c.x() - P[0])); for (c.y() = a.y(); c.y() <= b.y(); ++c.y()) { SdfT x2y2 = SdfT(x2 + math::Pow2(c.y() - P[1])); for (c.z() = a.z(); c.z() <= b.z(); ++c.z()) { SdfT x2y2z2 = SdfT(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 mIsCopy; PointPartitionerT* mPointPartitioner; };//end of Raster struct ///////////////////// YOU CAN SAFELY IGNORE THIS SECTION ///////////////////// namespace p2ls_internal { // 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; BlindData() {} explicit BlindData(VisibleT v) : mVisible(v), mBlind(zeroVal()) {} 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; } OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN bool operator==(const BlindData& rhs) const { return mVisible == rhs.mVisible; } OPENVDB_NO_FP_EQUALITY_WARNING_END 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()); } } // namespace p2ls_internal ////////////////////////////////////////////////////////////////////////////// } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_PARTICLES_TO_LEVELSET_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Diagnostics.h0000644000000000000000000013677712603226506015020 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 Diagnostics.h /// /// @author Ken Museth /// /// @brief Various diagnostic tools to identify potential issues with /// for example narrow-band level sets or fog volumes /// #ifndef OPENVDB_TOOLS_DIAGNOSTICS_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_DIAGNOSTICS_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { //////////////////////////////////////////////////////////////////////////////// /// @brief Perform checks on a grid to see if it is a valid symmetric, /// narrow-band level set. /// /// @param grid Grid to be checked /// @param number Number of the checks to be performed (see below) /// @return string with a message indicating the nature of the /// issue. If no issue is detected the return string is empty. /// /// @details @a number refers to the following ordered list of /// checks - always starting from the top. /// Fast checks /// 1: value type is floating point /// 2: has level set class type /// 3: has uniform scale /// 4: background value is positive and n*dx /// /// Slower checks /// 5: no active tiles /// 6: all the values are finite, i.e not NaN or infinite /// 7: active values in range between +-background /// 8: abs of inactive values = background, i.e. assuming a symmetric /// narrow band! /// /// Relatively slow check (however multithreaded) /// 9: norm gradient is close to one, i.e. satisfied the Eikonal equation. template std::string checkLevelSet(const GridType& grid, size_t number=9); //////////////////////////////////////////////////////////////////////////////// /// @brief Perform checks on a grid to see if it is a valid fog volume. /// /// @param grid Grid to be checked /// @param number Number of the checks to be performed (see below) /// @return string with a message indicating the nature of the /// issue. If no issue is detected the return string is empty. /// /// @details @a number refers to the following ordered list of /// checks - always starting from the top. /// Fast checks /// 1: value type is floating point /// 2: has FOG volume class type /// 3: background value is zero /// /// Slower checks /// 4: all the values are finite, i.e not NaN or infinite /// 5: inactive values are zero /// 6: active values are in the range [0,1] template std::string checkFogVolume(const GridType& grid, size_t number=6); //////////////////////////////////////////////////////////////////////////////// /// @brief Threaded method to find unique inactive values. /// /// @param grid A VDB volume. /// @param values List of unique inactive values, returned by this method. /// @param numValues Number of values to look for. /// @return @c false if the @a grid has more than @a numValues inactive values. template bool uniqueInactiveValues(const GridType& grid, std::vector& values, size_t numValues); //////////////////////////////////////////////////////////////////////////////// /// @brief Checks NaN values template struct CheckNan { typedef typename VecTraits::ElementType ElementType; typedef TreeIterT TileIterT; typedef typename tree::IterTraits ::template NodeConverter::Type VoxelIterT; /// @brief Default constructor CheckNan() {} /// Return true if the scalar value is NaN inline bool operator()(const ElementType& v) const { return boost::math::isnan(v); } /// @brief This allows for vector values to be checked component-wise template inline typename boost::enable_if_c::IsVec, bool>::type operator()(const T& v) const { for (int i=0; i::Size; ++i) if ((*this)(v[i])) return true;//should unroll return false; } /// @brief Return true if the tile at the iterator location is NaN bool operator()(const TreeIterT &iter) const { return (*this)(*iter); } /// @brief Return true if the voxel at the iterator location is NaN bool operator()(const VoxelIterT &iter) const { return (*this)(*iter); } /// @brief Return a string describing a failed check. std::string str() const { return "NaN"; } };// CheckNan //////////////////////////////////////////////////////////////////////////////// /// @brief Checks for infinite values, e.g. 1/0 or -1/0 template struct CheckInf { typedef typename VecTraits::ElementType ElementType; typedef TreeIterT TileIterT; typedef typename tree::IterTraits ::template NodeConverter::Type VoxelIterT; /// @brief Default constructor CheckInf() {} /// Return true if the value is infinite inline bool operator()(const ElementType& v) const { return boost::math::isinf(v); } /// Return true if any of the vector components are infinite. template inline typename boost::enable_if_c::IsVec, bool>::type operator()(const T& v) const { for (int i=0; i::Size; ++i) if ((*this)(v[i])) return true; return false; } /// @brief Return true if the tile at the iterator location is infinite bool operator()(const TreeIterT &iter) const { return (*this)(*iter); } /// @brief Return true if the tile at the iterator location is infinite bool operator()(const VoxelIterT &iter) const { return (*this)(*iter); } /// @brief Return a string describing a failed check. std::string str() const { return "infinite"; } };// CheckInf //////////////////////////////////////////////////////////////////////////////// /// @brief Checks for both NaN and inf values, i.e. any value that is not finite. template struct CheckFinite { typedef typename VecTraits::ElementType ElementType; typedef TreeIterT TileIterT; typedef typename tree::IterTraits ::template NodeConverter::Type VoxelIterT; /// @brief Default constructor CheckFinite() {} /// Return true if the value is NOT finite, i.e. it's NaN or infinite inline bool operator()(const ElementType& v) const { return !boost::math::isfinite(v); } /// Return true if any of the vector components are NaN or infinite. template inline typename boost::enable_if_c::IsVec, bool>::type operator()(const T& v) const { for (int i=0; i::Size; ++i) if ((*this)(v[i])) return true; return false; } /// @brief Return true if the tile at the iterator location is NaN or infinite. bool operator()(const TreeIterT &iter) const { return (*this)(*iter); } /// @brief Return true if the tile at the iterator location is NaN or infinite. bool operator()(const VoxelIterT &iter) const { return (*this)(*iter); } /// @brief Return a string describing a failed check. std::string str() const { return "not finite"; } };// CheckFinite //////////////////////////////////////////////////////////////////////////////// /// @brief Check that the magnitude of a value, a, is close to a fixed /// magnitude, b, given a fixed tolerance c. That is | |a| - |b| | <= c template struct CheckMagnitude { typedef typename VecTraits::ElementType ElementType; typedef TreeIterT TileIterT; typedef typename tree::IterTraits ::template NodeConverter::Type VoxelIterT; /// @brief Default constructor CheckMagnitude(const ElementType& a, const ElementType& t = math::Tolerance::value()) : absVal(math::Abs(a)), tolVal(math::Abs(t)) { } /// Return true if the magnitude of the value is not approximately /// equal to totVal. inline bool operator()(const ElementType& v) const { return math::Abs(math::Abs(v) - absVal) > tolVal; } /// Return true if any of the vector components are infinite. template inline typename boost::enable_if_c::IsVec, bool>::type operator()(const T& v) const { for (int i=0; i::Size; ++i) if ((*this)(v[i])) return true; return false; } /// @brief Return true if the tile at the iterator location is infinite bool operator()(const TreeIterT &iter) const { return (*this)(*iter); } /// @brief Return true if the tile at the iterator location is infinite bool operator()(const VoxelIterT &iter) const { return (*this)(*iter); } /// @brief Return a string describing a failed check. std::string str() const { std::ostringstream ss; ss << "not equal to +/-"< struct CheckRange { typedef typename VecTraits::ElementType ElementType; typedef TreeIterT TileIterT; typedef typename tree::IterTraits ::template NodeConverter::Type VoxelIterT; // @brief Constructor taking a range to be tested against. CheckRange(const ElementType& _min, const ElementType& _max) : minVal(_min), maxVal(_max) { if (minVal > maxVal) { OPENVDB_THROW(ValueError, "CheckRange: Invalid range (min > max)"); } } /// Return true if the value is smaller than min or larger than max. inline bool operator()(const ElementType& v) const { return (MinInclusive ? vmaxVal : v>=maxVal); } /// Return true if any of the vector components are out of range. template inline typename boost::enable_if_c::IsVec, bool>::type operator()(const T& v) const { for (int i=0; i::Size; ++i) if ((*this)(v[i])) return true; return false; } /// @brief Return true if the voxel at the iterator location is out of range. bool operator()(const TreeIterT &iter) const { return (*this)(*iter); } /// @brief Return true if the tile at the iterator location is out of range. bool operator()(const VoxelIterT &iter) const { return (*this)(*iter); } /// @brief Return a string describing a failed check. std::string str() const { std::ostringstream ss; ss << "outside the value range " << (MinInclusive ? "[" : "]") << minVal << "," << maxVal << (MaxInclusive ? "]" : "["); return ss.str(); } const ElementType minVal, maxVal; };// CheckRange //////////////////////////////////////////////////////////////////////////////// /// @brief Checks a value against a minimum template struct CheckMin { typedef typename VecTraits::ElementType ElementType; typedef TreeIterT TileIterT; typedef typename tree::IterTraits ::template NodeConverter::Type VoxelIterT; // @brief Constructor taking a minimum to be tested against. CheckMin(const ElementType& _min) : minVal(_min) {} /// Return true if the value is smaller than min. inline bool operator()(const ElementType& v) const { return v inline typename boost::enable_if_c::IsVec, bool>::type operator()(const T& v) const { for (int i=0; i::Size; ++i) if ((*this)(v[i])) return true; return false; } /// @brief Return true if the voxel at the iterator location is smaller than min. bool operator()(const TreeIterT &iter) const { return (*this)(*iter); } /// @brief Return true if the tile at the iterator location is smaller than min. bool operator()(const VoxelIterT &iter) const { return (*this)(*iter); } /// @brief Return a string describing a failed check. std::string str() const { std::ostringstream ss; ss << "smaller than "< struct CheckMax { typedef typename VecTraits::ElementType ElementType; typedef TreeIterT TileIterT; typedef typename tree::IterTraits ::template NodeConverter::Type VoxelIterT; /// @brief Constructor taking a maximum to be tested against. CheckMax(const ElementType& _max) : maxVal(_max) {} /// Return true if the value is larger than max. inline bool operator()(const ElementType& v) const { return v>maxVal; } /// Return true if any of the vector components are larger than max. template inline typename boost::enable_if_c::IsVec, bool>::type operator()(const T& v) const { for (int i=0; i::Size; ++i) if ((*this)(v[i])) return true; return false; } /// @brief Return true if the tile at the iterator location is larger than max. bool operator()(const TreeIterT &iter) const { return (*this)(*iter); } /// @brief Return true if the voxel at the iterator location is larger than max. bool operator()(const VoxelIterT &iter) const { return (*this)(*iter); } /// @brief Return a string describing a failed check. std::string str() const { std::ostringstream ss; ss << "larger than "<//math::WENO5_BIAS> struct CheckNormGrad { typedef typename GridT::ValueType ValueType; BOOST_STATIC_ASSERT(boost::is_floating_point::value); typedef TreeIterT TileIterT; typedef typename tree::IterTraits ::template NodeConverter::Type VoxelIterT; typedef typename GridT::ConstAccessor AccT; /// @brief Constructor taking a grid and a range to be tested against. CheckNormGrad(const GridT& grid, const ValueType& _min, const ValueType& _max) : acc(grid.getConstAccessor()) , invdx2(ValueType(1.0/math::Pow2(grid.voxelSize()[0]))) , minVal2(_min*_min) , maxVal2(_max*_max) { if ( !grid.hasUniformVoxels() ) { OPENVDB_THROW(ValueError, "CheckNormGrad: The transform must have uniform scale"); } if (_min > _max) { OPENVDB_THROW(ValueError, "CheckNormGrad: Invalid range (min > max)"); } } CheckNormGrad(const CheckNormGrad& other) : acc(other.acc.tree()) , invdx2(other.invdx2) , minVal2(other.minVal2) , maxVal2(other.maxVal2) { } /// Return true if the value is smaller than min or larger than max. inline bool operator()(const ValueType& v) const { return vmaxVal2; } /// @brief Return true if zero is outside the range. /// @note We assume that the norm of the gradient of a tile is always zero. inline bool operator()(const TreeIterT&) const { return (*this)(ValueType(0)); } /// @brief Return true if the norm of the gradient at a voxel /// location of the iterator is out of range. inline bool operator()(const VoxelIterT &iter) const { const Coord ijk = iter.getCoord(); return (*this)(invdx2 * math::ISGradientNormSqrd::result(acc, ijk)); } /// @brief Return a string describing a failed check. std::string str() const { std::ostringstream ss; ss << "outside the range of NormGrad ["< >//math::GradStencil struct CheckEikonal { typedef typename GridT::ValueType ValueType; BOOST_STATIC_ASSERT(boost::is_floating_point::value); typedef TreeIterT TileIterT; typedef typename tree::IterTraits ::template NodeConverter::Type VoxelIterT; /// @brief Constructor taking a grid and a range to be tested against. CheckEikonal(const GridT& grid, const ValueType& _min, const ValueType& _max) : stencil(grid), minVal(_min), maxVal(_max) { if ( !grid.hasUniformVoxels() ) { OPENVDB_THROW(ValueError, "CheckEikonal: The transform must have uniform scale"); } if (minVal > maxVal) { OPENVDB_THROW(ValueError, "CheckEikonal: Invalid range (min > max)"); } } CheckEikonal(const CheckEikonal& other) : stencil(other.stencil.grid()), minVal(other.minVal), maxVal(other.maxVal) { } /// Return true if the value is smaller than min or larger than max. inline bool operator()(const ValueType& v) const { return vmaxVal; } /// @brief Return true if zero is outside the range. /// @note We assume that the norm of the gradient of a tile is always zero. inline bool operator()(const TreeIterT&) const { return (*this)(ValueType(0)); } /// @brief Return true if the norm of the gradient at a /// zero-crossing voxel location of the iterator is out of range. inline bool operator()(const VoxelIterT &iter) const { stencil.moveTo(iter); if (!stencil.zeroCrossing()) return false; return (*this)(stencil.normSqGrad()); } /// @brief Return a string describing a failed check. std::string str() const { std::ostringstream ss; ss << "outside the range of NormGrad ["< struct CheckDivergence { typedef typename GridT::ValueType ValueType; typedef typename VecTraits::ElementType ElementType; BOOST_STATIC_ASSERT(boost::is_floating_point::value); typedef TreeIterT TileIterT; typedef typename tree::IterTraits ::template NodeConverter::Type VoxelIterT; typedef typename GridT::ConstAccessor AccT; /// @brief Constructor taking a grid and a range to be tested against. CheckDivergence(const GridT& grid, const ValueType& _min, const ValueType& _max) : acc(grid.getConstAccessor()) , invdx(ValueType(1.0/grid.voxelSize()[0])) , minVal(_min) , maxVal(_max) { if ( !grid.hasUniformVoxels() ) { OPENVDB_THROW(ValueError, "CheckDivergence: The transform must have uniform scale"); } if (minVal > maxVal) { OPENVDB_THROW(ValueError, "CheckDivergence: Invalid range (min > max)"); } } /// Return true if the value is smaller than min or larger than max. inline bool operator()(const ElementType& v) const { return vmaxVal; } /// @brief Return true if zero is outside the range. /// @note We assume that the divergence of a tile is always zero. inline bool operator()(const TreeIterT&) const { return (*this)(ElementType(0)); } /// @brief Return true if the divergence at a voxel location of /// the iterator is out of range. inline bool operator()(const VoxelIterT &iter) const { const Coord ijk = iter.getCoord(); return (*this)(invdx * math::ISDivergence::result(acc, ijk)); } /// @brief Return a string describing a failed check. std::string str() const { std::ostringstream ss; ss << "outside the range of divergence ["< class Diagnose { public: typedef typename GridT::template ValueConverter::Type MaskType; Diagnose(const GridT& grid) : mGrid(&grid), mMask(new MaskType()), mCount(0) { mMask->setTransform(grid.transformPtr()->copy()); } template std::string check(const CheckT& check, bool updateMask = false, bool checkVoxels = true, bool checkTiles = true, bool checkBackground = true) { typename MaskType::TreeType* mask = updateMask ? &(mMask->tree()) : NULL; CheckValues cc(mask, mGrid, check); std::ostringstream ss; if (checkBackground) ss << cc.checkBackground(); if (checkTiles) ss << cc.checkTiles(); if (checkVoxels) ss << cc.checkVoxels(); mCount += cc.mCount; return ss.str(); } //@{ /// @brief Return a boolean mask of all the values /// (i.e. tiles and/or voxels) that have failed one or /// more checks. typename MaskType::ConstPtr mask() const { return mMask; } typename MaskType::Ptr mask() { return mMask; } //@} /// @brief Return the number of values (i.e. background, tiles or /// voxels) that have failed one or more checks. Index64 valueCount() const { return mMask->activeVoxelCount(); } /// @brief Return total number of failed checks /// @note If only one check was performed and the mask was updated /// failureCount equals valueCount. Index64 failureCount() const { return mCount; } /// @brief Return a const reference to the grid const GridT& grid() const { return *mGrid; } /// @brief Clear the mask and error counter void clear() { mMask = new MaskType(); mCount = 0; } private: // disallow copy construction and copy by assignment! Diagnose(const Diagnose&);// not implemented Diagnose& operator=(const Diagnose&);// not implemented const GridT* mGrid; typename MaskType::Ptr mMask; Index64 mCount; /// @brief Private class that performs the multithreaded checks template struct CheckValues { typedef typename MaskType::TreeType MaskT; typedef typename GridT::TreeType::LeafNodeType LeafT; typedef typename tree::LeafManager LeafManagerT; const bool mOwnsMask; MaskT* mMask; const GridT* mGrid; const CheckT mCheck; Index64 mCount; CheckValues(MaskT* mask, const GridT* grid, const CheckT& check) : mOwnsMask(false) , mMask(mask) , mGrid(grid) , mCheck(check) , mCount(0) { } CheckValues(CheckValues& other, tbb::split) : mOwnsMask(true) , mMask(other.mMask ? new MaskT() : NULL) , mGrid(other.mGrid) , mCheck(other.mCheck) , mCount(0) { } ~CheckValues() { if (mOwnsMask) delete mMask; } std::string checkBackground() { std::ostringstream ss; if (mCheck(mGrid->background())) { ++mCount; ss << "Background is " + mCheck.str() << std::endl; } return ss.str(); } std::string checkTiles() { std::ostringstream ss; const Index64 n = mCount; typename CheckT::TileIterT i(mGrid->tree()); for (i.setMaxDepth(GridT::TreeType::RootNodeType::LEVEL - 1); i; ++i) { if (mCheck(i)) { ++mCount; if (mMask) mMask->fill(i.getBoundingBox(), true, true); } } if (const Index64 m = mCount - n) { ss << m << " tile" << (m==1 ? " is " : "s are ") + mCheck.str() << std::endl; } return ss.str(); } std::string checkVoxels() { std::ostringstream ss; LeafManagerT leafs(mGrid->tree()); const Index64 n = mCount; tbb::parallel_reduce(leafs.leafRange(), *this); if (const Index64 m = mCount - n) { ss << m << " voxel" << (m==1 ? " is " : "s are ") + mCheck.str() << std::endl; } return ss.str(); } void operator()(const typename LeafManagerT::LeafRange& r) { typedef typename CheckT::VoxelIterT VoxelIterT; if (mMask) { for (typename LeafManagerT::LeafRange::Iterator i=r.begin(); i; ++i) { typename MaskT::LeafNodeType* maskLeaf = NULL; for (VoxelIterT j = tree::IterTraits::begin(*i); j; ++j) { if (mCheck(j)) { ++mCount; if (maskLeaf == NULL) maskLeaf = mMask->touchLeaf(j.getCoord()); maskLeaf->setValueOn(j.pos(), true); } } } } else { for (typename LeafManagerT::LeafRange::Iterator i=r.begin(); i; ++i) { for (VoxelIterT j = tree::IterTraits::begin(*i); j; ++j) { if (mCheck(j)) ++mCount; } } } } void join(const CheckValues& other) { if (mMask) mMask->merge(*(other.mMask), openvdb::MERGE_ACTIVE_STATES_AND_NODES); mCount += other.mCount; } };//End of private class CheckValues };// End of public class Diagnose //////////////////////////////////////////////////////////////////////////////// /// @brief Class that performs various types of checks on narrow-band level sets. /// /// @note The most common usage is to simply call CheckLevelSet::check() template class CheckLevelSet { public: typedef typename GridType::ValueType ValueType; typedef typename GridType::template ValueConverter::Type MaskType; CheckLevelSet(const GridType& grid) : mDiagnose(grid) {} //@{ /// @brief Return a boolean mask of all the values /// (i.e. tiles and/or voxels) that have failed one or /// more checks. typename MaskType::ConstPtr mask() const { return mDiagnose.mask(); } typename MaskType::Ptr mask() { return mDiagnose.mask(); } //@} /// @brief Return the number of values (i.e. background, tiles or /// voxels) that have failed one or more checks. Index64 valueCount() const { return mDiagnose.valueCount(); } /// @brief Return total number of failed checks /// @note If only one check was performed and the mask was updated /// failureCount equals valueCount. Index64 failureCount() const { return mDiagnose.failureCount(); } /// @brief Return a const reference to the grid const GridType& grid() const { return mDiagnose.grid(); } /// @brief Clear the mask and error counter void clear() { mDiagnose.clear(); } /// @brief Return a nonempty message if the grid's value type is a floating point. /// /// @note No run-time overhead static std::string checkValueType() { static const bool test = boost::is_floating_point::value; return test ? "" : "Value type is not floating point\n"; } /// @brief Return message if the grid's class is a level set. /// /// @note Small run-time overhead std::string checkClassType() const { const bool test = mDiagnose.grid().getGridClass() == GRID_LEVEL_SET; return test ? "" : "Class type is not \"GRID_LEVEL_SET\"\n"; } /// @brief Return a nonempty message if the grid's transform does not have uniform scaling. /// /// @note Small run-time overhead std::string checkTransform() const { return mDiagnose.grid().hasUniformVoxels() ? "" : "Does not have uniform voxels\n"; } /// @brief Return a nonempty message if the background value is larger than or /// equal to the halfWidth*voxelSize. /// /// @note Small run-time overhead std::string checkBackground(Real halfWidth = LEVEL_SET_HALF_WIDTH) const { const Real w = mDiagnose.grid().background() / mDiagnose.grid().voxelSize()[0]; if (w < halfWidth) { std::ostringstream ss; ss << "The background value ("<< mDiagnose.grid().background()<<") is less than " << halfWidth << " voxel units\n"; return ss.str(); } return ""; } /// @brief Return a nonempty message if the grid has no active tile values. /// /// @note Medium run-time overhead std::string checkTiles() const { const bool test = mDiagnose.grid().tree().hasActiveTiles(); return test ? "Has active tile values\n" : ""; } /// @brief Return a nonempty message if any of the values are not finite. i.e. NaN or inf. /// /// @note Medium run-time overhead std::string checkFinite(bool updateMask = false) { CheckFinite c; return mDiagnose.check(c, updateMask, /*voxel*/true, /*tiles*/true, /*background*/true); } /// @brief Return a nonempty message if the active voxel values are out-of-range. /// /// @note Medium run-time overhead std::string checkRange(bool updateMask = false) { const ValueType& background = mDiagnose.grid().background(); CheckRange c(-background, background); return mDiagnose.check(c, updateMask, /*voxel*/true, /*tiles*/false, /*background*/false); } /// @brief Return a nonempty message if the the inactive values do not have a /// magnitude equal to the background value. /// /// @note Medium run-time overhead std::string checkInactiveValues(bool updateMask = false) { const ValueType& background = mDiagnose.grid().background(); CheckMagnitude c(background); return mDiagnose.check(c, updateMask, /*voxel*/true, /*tiles*/true, /*background*/false); } /// @brief Return a nonempty message if the norm of the gradient of the /// active voxels is out of the range minV to maxV. /// /// @note Significant run-time overhead std::string checkEikonal(bool updateMask = false, ValueType minV = 0.5, ValueType maxV = 1.5) { CheckEikonal c(mDiagnose.grid(), minV, maxV); return mDiagnose.check(c, updateMask, /*voxel*/true, /*tiles*/false, /*background*/false); } /// @brief Return a nonempty message if an error or issue is detected. Only /// runs tests with a number lower than or equal to n, where: /// /// Fast checks /// 1: value type is floating point /// 2: has level set class type /// 3: has uniform scale /// 4: background value is positive and n*dx /// /// Slower checks /// 5: no active tiles /// 6: all the values are finite, i.e not NaN or infinite /// 7: active values in range between +-background /// 8: abs of inactive values = background, i.e. assuming a symmetric narrow band! /// /// Relatively slow check (however multi-threaded) /// 9: norm of gradient at zero-crossings is one, i.e. satisfied the Eikonal equation. std::string check(size_t n=9, bool updateMask = false) { std::string str = this->checkValueType(); if (str.empty() && n>1) str = this->checkClassType(); if (str.empty() && n>2) str = this->checkTransform(); if (str.empty() && n>3) str = this->checkBackground(); if (str.empty() && n>4) str = this->checkTiles(); if (str.empty() && n>5) str = this->checkFinite(updateMask); if (str.empty() && n>6) str = this->checkRange(updateMask); if (str.empty() && n>7) str = this->checkInactiveValues(updateMask); if (str.empty() && n>8) str = this->checkEikonal(updateMask); return str; } private: // disallow copy construction and copy by assignment! CheckLevelSet(const CheckLevelSet&);// not implemented CheckLevelSet& operator=(const CheckLevelSet&);// not implemented // Member data Diagnose mDiagnose; };// CheckLevelSet template std::string checkLevelSet(const GridType& grid, size_t n) { CheckLevelSet c(grid); return c.check(n, false); } //////////////////////////////////////////////////////////////////////////////// /// @brief Class that performs various types of checks on fog volumes. /// /// @note The most common usage is to simply call CheckFogVolume::check() template class CheckFogVolume { public: typedef typename GridType::ValueType ValueType; typedef typename GridType::template ValueConverter::Type MaskType; CheckFogVolume(const GridType& grid) : mDiagnose(grid) {} //@{ /// @brief Return a boolean mask of all the values /// (i.e. tiles and/or voxels) that have failed one or /// more checks. typename MaskType::ConstPtr mask() const { return mDiagnose.mask(); } typename MaskType::Ptr mask() { return mDiagnose.mask(); } //@} /// @brief Return the number of values (i.e. background, tiles or /// voxels) that have failed one or more checks. Index64 valueCount() const { return mDiagnose.valueCount(); } /// @brief Return total number of failed checks /// @note If only one check was performed and the mask was updated /// failureCount equals valueCount. Index64 failureCount() const { return mDiagnose.failureCount(); } /// @brief Return a const reference to the grid const GridType& grid() const { return mDiagnose.grid(); } /// @brief Clear the mask and error counter void clear() { mDiagnose.clear(); } /// @brief Return a nonempty message if the grid's value type is a floating point. /// /// @note No run-time overhead static std::string checkValueType() { static const bool test = boost::is_floating_point::value; return test ? "" : "Value type is not floating point"; } /// @brief Return a nonempty message if the grid's class is a level set. /// /// @note Small run-time overhead std::string checkClassType() const { const bool test = mDiagnose.grid().getGridClass() == GRID_FOG_VOLUME; return test ? "" : "Class type is not \"GRID_LEVEL_SET\""; } /// @brief Return a nonempty message if the background value is not zero. /// /// @note Small run-time overhead std::string checkBackground() const { if (!math::isApproxZero(mDiagnose.grid().background())) { std::ostringstream ss; ss << "The background value ("<< mDiagnose.grid().background()<<") is not zero"; return ss.str(); } return ""; } /// @brief Return a nonempty message if any of the values are not finite. i.e. NaN or inf. /// /// @note Medium run-time overhead std::string checkFinite(bool updateMask = false) { CheckFinite c; return mDiagnose.check(c, updateMask, /*voxel*/true, /*tiles*/true, /*background*/true); } /// @brief Return a nonempty message if any of the inactive values are not zero. /// /// @note Medium run-time overhead std::string checkInactiveValues(bool updateMask = false) { CheckMagnitude c(0); return mDiagnose.check(c, updateMask, /*voxel*/true, /*tiles*/true, /*background*/true); } /// @brief Return a nonempty message if the active voxel values /// are out-of-range, i.e. not in the range [0,1]. /// /// @note Medium run-time overhead std::string checkRange(bool updateMask = false) { CheckRange c(0, 1); return mDiagnose.check(c, updateMask, /*voxel*/true, /*tiles*/true, /*background*/false); } /// @brief Return a nonempty message if an error or issue is detected. Only /// runs tests with a number lower than or equal to n, where: /// /// Fast checks /// 1: value type is floating point /// 2: has FOG volume class type /// 3: background value is zero /// /// Slower checks /// 4: all the values are finite, i.e not NaN or infinite /// 5: inactive values are zero /// 6: active values are in the range [0,1] std::string check(size_t n=6, bool updateMask = false) { std::string str = this->checkValueType(); if (str.empty() && n>1) str = this->checkClassType(); if (str.empty() && n>2) str = this->checkBackground(); if (str.empty() && n>3) str = this->checkFinite(updateMask); if (str.empty() && n>4) str = this->checkInactiveValues(updateMask); if (str.empty() && n>5) str = this->checkRange(updateMask); return str; } private: // disallow copy construction and copy by assignment! CheckFogVolume(const CheckFogVolume&);// not implemented CheckFogVolume& operator=(const CheckFogVolume&);// not implemented // Member data Diagnose mDiagnose; };// CheckFogVolume template std::string checkFogVolume(const GridType& grid, size_t n) { CheckFogVolume c(grid); return c.check(n, false); } //////////////////////////////////////////////////////////////////////////////// // Internal utility objects and implementation details namespace diagnostics_internal { template class InactiveVoxelValues { public: typedef tree::LeafManager LeafArray; typedef typename TreeType::ValueType ValueType; typedef std::set SetType; InactiveVoxelValues(LeafArray&, size_t numValues); void runParallel(); void runSerial(); void getInactiveValues(SetType&) const; inline InactiveVoxelValues(const InactiveVoxelValues&, tbb::split); inline void operator()(const tbb::blocked_range&); inline void join(const InactiveVoxelValues&); private: LeafArray& mLeafArray; SetType mInactiveValues; size_t mNumValues; };// InactiveVoxelValues template InactiveVoxelValues::InactiveVoxelValues(LeafArray& leafs, size_t numValues) : mLeafArray(leafs) , mInactiveValues() , mNumValues(numValues) { } template inline InactiveVoxelValues::InactiveVoxelValues( const InactiveVoxelValues& rhs, tbb::split) : mLeafArray(rhs.mLeafArray) , mInactiveValues() , mNumValues(rhs.mNumValues) { } template void InactiveVoxelValues::runParallel() { tbb::parallel_reduce(mLeafArray.getRange(), *this); } template void InactiveVoxelValues::runSerial() { (*this)(mLeafArray.getRange()); } template inline void InactiveVoxelValues::operator()(const tbb::blocked_range& range) { typename TreeType::LeafNodeType::ValueOffCIter iter; for (size_t n = range.begin(); n < range.end() && !tbb::task::self().is_cancelled(); ++n) { for (iter = mLeafArray.leaf(n).cbeginValueOff(); iter; ++iter) { mInactiveValues.insert(iter.getValue()); } if (mInactiveValues.size() > mNumValues) { tbb::task::self().cancel_group_execution(); } } } template inline void InactiveVoxelValues::join(const InactiveVoxelValues& rhs) { mInactiveValues.insert(rhs.mInactiveValues.begin(), rhs.mInactiveValues.end()); } template inline void InactiveVoxelValues::getInactiveValues(SetType& values) const { values.insert(mInactiveValues.begin(), mInactiveValues.end()); } //////////////////////////////////////// template class InactiveTileValues { public: typedef tree::IteratorRange IterRange; typedef typename TreeType::ValueType ValueType; typedef std::set SetType; InactiveTileValues(size_t numValues); void runParallel(IterRange&); void runSerial(IterRange&); void getInactiveValues(SetType&) const; inline InactiveTileValues(const InactiveTileValues&, tbb::split); inline void operator()(IterRange&); inline void join(const InactiveTileValues&); private: SetType mInactiveValues; size_t mNumValues; }; template InactiveTileValues::InactiveTileValues(size_t numValues) : mInactiveValues() , mNumValues(numValues) { } template inline InactiveTileValues::InactiveTileValues( const InactiveTileValues& rhs, tbb::split) : mInactiveValues() , mNumValues(rhs.mNumValues) { } template void InactiveTileValues::runParallel(IterRange& range) { tbb::parallel_reduce(range, *this); } template void InactiveTileValues::runSerial(IterRange& range) { (*this)(range); } template inline void InactiveTileValues::operator()(IterRange& range) { for (; range && !tbb::task::self().is_cancelled(); ++range) { typename TreeType::ValueOffCIter iter = range.iterator(); for (; iter; ++iter) { mInactiveValues.insert(iter.getValue()); } if (mInactiveValues.size() > mNumValues) { tbb::task::self().cancel_group_execution(); } } } template inline void InactiveTileValues::join(const InactiveTileValues& rhs) { mInactiveValues.insert(rhs.mInactiveValues.begin(), rhs.mInactiveValues.end()); } template inline void InactiveTileValues::getInactiveValues(SetType& values) const { values.insert(mInactiveValues.begin(), mInactiveValues.end()); } } // namespace diagnostics_internal //////////////////////////////////////// template bool uniqueInactiveValues(const GridType& grid, std::vector& values, size_t numValues) { typedef typename GridType::TreeType TreeType; typedef typename GridType::ValueType ValueType; typedef std::set SetType; SetType uniqueValues; { // Check inactive voxels TreeType& tree = const_cast(grid.tree()); tree::LeafManager leafs(tree); diagnostics_internal::InactiveVoxelValues voxelOp(leafs, numValues); voxelOp.runParallel(); voxelOp.getInactiveValues(uniqueValues); } // Check inactive tiles if (uniqueValues.size() <= numValues) { typename TreeType::ValueOffCIter iter(grid.tree()); iter.setMaxDepth(TreeType::ValueAllIter::LEAF_DEPTH - 1); diagnostics_internal::InactiveTileValues tileOp(numValues); tree::IteratorRange range(iter); tileOp.runParallel(range); tileOp.getInactiveValues(uniqueValues); } values.clear(); values.reserve(uniqueValues.size()); typename SetType::iterator it = uniqueValues.begin(); for ( ; it != uniqueValues.end(); ++it) { values.push_back(*it); } return values.size() <= numValues; } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_DIAGNOSTICS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Interpolation.h0000644000000000000000000011206612603226506015361 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 SmoothUnitStep #include // for Transform #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Provises a unified interface for sampling, i.e. interpolation. /// @details Order = 0: closest point /// Order = 1: tri-linear /// Order = 2: tri-quadratic /// Staggered: Set to true for MAC grids template struct Sampler { BOOST_STATIC_ASSERT(Order < 3); static const char* name(); static int radius(); static bool mipmap(); static bool consistent(); static bool staggered(); static size_t order(); /// @brief Sample @a inTree at the floating-point index coordinate @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); /// @brief Sample @a inTree at the floating-point index coordinate @a inCoord. /// /// @return the reconstructed value template static typename TreeT::ValueType sample(const TreeT& inTree, const Vec3R& inCoord); }; //////////////////////////////////////// Non-Staggered Samplers // 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; } static bool staggered() { return false; } static size_t order() { return 0; } /// @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); /// @brief Sample @a inTree at the nearest neighbor to @a inCoord /// @return the reconstructed value template static typename TreeT::ValueType sample(const TreeT& inTree, const Vec3R& inCoord); }; struct BoxSampler { static const char* name() { return "box"; } static int radius() { return 1; } static bool mipmap() { return true; } static bool consistent() { return true; } static bool staggered() { return false; } static size_t order() { return 1; } /// @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); /// @brief Import all eight values from @a inTree to support /// tri-linear interpolation. template static inline void getValues(ValueT (&data)[N][N][N], const TreeT& inTree, Coord ijk); /// @brief Import all eight values from @a inTree to support /// tri-linear interpolation. /// @return @c true if any of the eight values are active template static inline bool probeValues(ValueT (&data)[N][N][N], const TreeT& inTree, Coord ijk); /// @brief Find the minimum and maximum values of the eight cell /// values in @ data. template static inline void extrema(ValueT (&data)[N][N][N], ValueT& vMin, ValueT& vMax); /// @return the tri-linear interpolation with the unit cell coordinates @a uvw 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; } static bool staggered() { return false; } static size_t order() { return 2; } /// @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); /// @brief Triquadratically reconstruct @a inTree at to @a inCoord. /// @return the reconstructed value template static typename TreeT::ValueType sample(const TreeT& inTree, const Vec3R& inCoord); template static inline ValueT triquadraticInterpolation(ValueT (&data)[N][N][N], const Vec3R& uvw); }; //////////////////////////////////////// Staggered Samplers // 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; } static bool staggered() { return true; } static size_t order() { return 0; } /// @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); /// @brief Sample @a inTree at the nearest neighbor to @a inCoord /// @return the reconstructed value template static typename TreeT::ValueType sample(const TreeT& inTree, const Vec3R& inCoord); }; struct StaggeredBoxSampler { static const char* name() { return "box"; } static int radius() { return 1; } static bool mipmap() { return true; } static bool consistent() { return false; } static bool staggered() { return true; } static size_t order() { return 1; } /// @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); /// @brief Trilinearly reconstruct @a inTree at @a inCoord. /// @return the reconstructed value template static typename TreeT::ValueType sample(const TreeT& inTree, const Vec3R& inCoord); }; struct StaggeredQuadraticSampler { static const char* name() { return "quadratic"; } static int radius() { return 1; } static bool mipmap() { return true; } static bool consistent() { return false; } static bool staggered() { return true; } static size_t order() { return 2; } /// @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 Triquadratically reconstruct @a inTree at to @a inCoord. /// @return the reconstructed value template static typename TreeT::ValueType sample(const TreeT& inTree, const Vec3R& inCoord); }; //////////////////////////////////////// GridSampler /// @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 favored 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 flavor 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 its 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 //////////////////////////////////////// DualGridSampler /// @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 //////////////////////////////////////// AlphaMask // Class to derive the normalized alpha mask template class AlphaMask { public: BOOST_STATIC_ASSERT(boost::is_floating_point::value); typedef GridT GridType; typedef MaskT MaskType; typedef SamplerT SamlerType; typedef FloatT FloatType; AlphaMask(const GridT& grid, const MaskT& mask, FloatT min, FloatT 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, FloatT& a, FloatT& b) const { a = math::SmoothUnitStep( (mSampler(xyz) - mMin) * mInvNorm );//smooth mapping to 0->1 b = 1 - a; if (mInvert) std::swap(a,b); return a>0; } protected: typedef typename MaskType::ConstAccessor AccT; AccT mAcc; tools::DualGridSampler mSampler; const FloatT mMin, mInvNorm; const bool mInvert; };// AlphaMask //////////////////////////////////////// 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 //////////////////////////////////////// PointSampler template inline bool PointSampler::sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result) { return inTree.probeValue(Coord(local_util::roundVec3(inCoord)), result); } template inline typename TreeT::ValueType PointSampler::sample(const TreeT& inTree, const Vec3R& inCoord) { return inTree.getValue(Coord(local_util::roundVec3(inCoord))); } //////////////////////////////////////// BoxSampler template inline void BoxSampler::getValues(ValueT (&data)[N][N][N], const TreeT& inTree, Coord ijk) { 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] -= 1; data[0][1][0] = inTree.getValue(ijk); // i, j+1, k ijk[0] += 1; ijk[1] -= 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] -= 1; data[1][1][0] = inTree.getValue(ijk); // i+1, j+1, k } template inline bool BoxSampler::probeValues(ValueT (&data)[N][N][N], const TreeT& inTree, Coord ijk) { bool hasActiveValues = false; 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] -= 1; hasActiveValues |= inTree.probeValue(ijk, data[0][1][0]); // i, j+1, k ijk[0] += 1; ijk[1] -= 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] -= 1; hasActiveValues |= inTree.probeValue(ijk, data[1][1][0]); // i+1, j+1, k return hasActiveValues; } template inline void BoxSampler::extrema(ValueT (&data)[N][N][N], ValueT& vMin, ValueT &vMax) { vMin = vMax = data[0][0][0]; vMin = math::Min(vMin, data[0][0][1]); vMax = math::Max(vMax, data[0][0][1]); vMin = math::Min(vMin, data[0][1][0]); vMax = math::Max(vMax, data[0][1][0]); vMin = math::Min(vMin, data[0][1][1]); vMax = math::Max(vMax, data[0][1][1]); vMin = math::Min(vMin, data[1][0][0]); vMax = math::Max(vMax, data[1][0][0]); vMin = math::Min(vMin, data[1][0][1]); vMax = math::Max(vMax, data[1][0][1]); vMin = math::Min(vMin, data[1][1][0]); vMax = math::Max(vMax, data[1][1][0]); vMin = math::Min(vMin, data[1][1][1]); vMax = math::Max(vMax, data[1][1][1]); } 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; const Vec3i inIdx = local_util::floorVec3(inCoord); const Vec3R uvw = inCoord - inIdx; // Retrieve the values of the eight voxels surrounding the // fractional source coordinates. ValueT data[2][2][2]; const bool hasActiveValues = BoxSampler::probeValues(data, inTree, Coord(inIdx)); result = BoxSampler::trilinearInterpolation(data, uvw); return hasActiveValues; } template inline typename TreeT::ValueType BoxSampler::sample(const TreeT& inTree, const Vec3R& inCoord) { typedef typename TreeT::ValueType ValueT; const Vec3i inIdx = local_util::floorVec3(inCoord); const Vec3R uvw = inCoord - inIdx; // Retrieve the values of the eight voxels surrounding the // fractional source coordinates. ValueT data[2][2][2]; BoxSampler::getValues(data, inTree, Coord(inIdx)); return BoxSampler::trilinearInterpolation(data, uvw); } //////////////////////////////////////// QuadraticSampler template inline ValueT QuadraticSampler::triquadraticInterpolation(ValueT (&data)[N][N][N], const Vec3R& uvw) { /// @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 = &data[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(uvw.z() * (uvw.z() * az + bz) + cz); }//loop over y // 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(uvw.y() * (uvw.y() * ay + by) + cy); }//loop over x // 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]); return static_cast(uvw.x() * (uvw.x() * ax + bx) + cx); } template inline bool QuadraticSampler::sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result) { typedef typename TreeT::ValueType ValueT; const Vec3i inIdx = local_util::floorVec3(inCoord), inLoIdx = inIdx - Vec3i(1, 1, 1); const Vec3R uvw = inCoord - inIdx; // Retrieve the values of the 27 voxels surrounding the // fractional source coordinates. bool active = false; ValueT data[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), data[dx][dy][dz])) active = true; } } } result = QuadraticSampler::triquadraticInterpolation(data, uvw); return active; } template inline typename TreeT::ValueType QuadraticSampler::sample(const TreeT& inTree, const Vec3R& inCoord) { typedef typename TreeT::ValueType ValueT; const Vec3i inIdx = local_util::floorVec3(inCoord), inLoIdx = inIdx - Vec3i(1, 1, 1); const Vec3R uvw = inCoord - inIdx; // Retrieve the values of the 27 voxels surrounding the // fractional source coordinates. ValueT data[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) { data[dx][dy][dz] = inTree.getValue(Coord(ix, iy, iz)); } } } return QuadraticSampler::triquadraticInterpolation(data, uvw); } //////////////////////////////////////// StaggeredPointSampler 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 typename TreeT::ValueType StaggeredPointSampler::sample(const TreeT& inTree, const Vec3R& inCoord) { typedef typename TreeT::ValueType ValueT; const ValueT tempX = PointSampler::sample(inTree, inCoord + Vec3R(0.5, 0.0, 0.0)); const ValueT tempY = PointSampler::sample(inTree, inCoord + Vec3R(0.0, 0.5, 0.0)); const ValueT tempZ = PointSampler::sample(inTree, inCoord + Vec3R(0.0, 0.0, 0.5)); return ValueT(tempX.x(), tempY.y(), tempZ.z()); } //////////////////////////////////////// StaggeredBoxSampler 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 typename TreeT::ValueType StaggeredBoxSampler::sample(const TreeT& inTree, const Vec3R& inCoord) { typedef typename TreeT::ValueType ValueT; const ValueT tempX = BoxSampler::sample(inTree, inCoord + Vec3R(0.5, 0.0, 0.0)); const ValueT tempY = BoxSampler::sample(inTree, inCoord + Vec3R(0.0, 0.5, 0.0)); const ValueT tempZ = BoxSampler::sample(inTree, inCoord + Vec3R(0.0, 0.0, 0.5)); return ValueT(tempX.x(), tempY.y(), tempZ.z()); } //////////////////////////////////////// StaggeredQuadraticSampler 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; } template inline typename TreeT::ValueType StaggeredQuadraticSampler::sample(const TreeT& inTree, const Vec3R& inCoord) { typedef typename TreeT::ValueType ValueT; const ValueT tempX = QuadraticSampler::sample(inTree, inCoord + Vec3R(0.5, 0.0, 0.0)); const ValueT tempY = QuadraticSampler::sample(inTree, inCoord + Vec3R(0.0, 0.5, 0.0)); const ValueT tempZ = QuadraticSampler::sample(inTree, inCoord + Vec3R(0.0, 0.0, 0.5)); return ValueT(tempX.x(), tempY.y(), tempZ.z()); } //////////////////////////////////////// Sampler template <> struct Sampler<0, false> : public PointSampler {}; template <> struct Sampler<1, false> : public BoxSampler {}; template <> struct Sampler<2, false> : public QuadraticSampler {}; template <> struct Sampler<0, true> : public StaggeredPointSampler {}; template <> struct Sampler<1, true> : public StaggeredBoxSampler {}; template <> struct Sampler<2, true> : public StaggeredQuadraticSampler {}; } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_INTERPOLATION_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/VolumeToMesh.h0000644000000000000000000042620412603226506015123 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 "Prune.h" // for pruneInactive #include #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 /// /// @throw TypeError if @a grid does not have a scalar value type template inline 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 triangle index list /// @param quads output quad index list /// @param isovalue determines which isosurface to mesh /// @param adaptivity surface adaptivity threshold [0 to 1] /// /// @throw TypeError if @a grid does not have a scalar value type template inline 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 a 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 uint8_t(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 = uint8_t(~signs); } else if (face == 3) { ijk[2] += 1; if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 1) signs = uint8_t(~signs); } else if (face == 2) { ijk[0] += 1; if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 4) signs = uint8_t(~signs); } else if (face == 4) { ijk[0] -= 1; if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 2) signs = uint8_t(~signs); } else if (face == 5) { ijk[1] -= 1; if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 6) signs = uint8_t(~signs); } else if (face == 6) { ijk[1] += 1; if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 5) signs = uint8_t(~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]) { leaf.setValueOnly(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(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(static_cast(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, uint8_t(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(uint8_t(n), lhsSigns, rhsSigns); if (id != -1) { const unsigned char e = uint8_t(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, uint8_t(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(static_cast(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 = uint8_t(SIGNS & flags); refSigns = 0; if ((flags & SEAM) && refSignLeafPt && refIdxLeafPt) { if (refSignLeafPt->isValueOn(offset)) { refSigns = uint8_t(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(static_cast(ptnIdx)); iter.setValueOff(); --onVoxelCount; ijk = iter.getCoord(); offset = iter.pos(); signs = uint8_t(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 = uint8_t(SIGNS & iter.getValue()); rhsSigns = uint8_t(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(uint8_t(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, uint8_t(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* mAdaptivityMask; 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) , mAdaptivityMask(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) { mAdaptivityMask = 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 (mAdaptivityMask) { maskAcc.reset(new BoolTreeCAccessorT(*mAdaptivityMask)); } BoolLeafT mask; Vec3LeafT gradients; Coord ijk, end; for (size_t n = range.begin(); n != range.end(); ++n) { mask.setValuesOff(); const Coord& origin = mSignLeafs.leaf(n).origin(); ValueT adaptivity = (refAcc && !refAcc->probeConstLeaf(origin)) ? mInternalAdaptivity : mSurfaceAdaptivity; IntLeafT& idxLeaf = *idxAcc.probeLeaf(origin); end[0] = origin[0] + LeafDim; end[1] = origin[1] + LeafDim; end[2] = origin[2] + LeafDim; // 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) { mask.setActiveState(it.getCoord() & ~1u, true); } } } // Set region adaptivity 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) { unsigned char signs = static_cast(SIGNS & int(iter.getValue())); if (!sAdaptable[signs] || sEdgeGroupTable[signs][0] > 1) { mask.setActiveState(iter.getCoord() & ~1u, true); } } // Mask off topologically ambiguous 2x2x2 voxel sub-blocks int dim = 2; 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 (!mask.isValueOn(ijk) & isNonManifold(mDistAcc, ijk, mIsovalue, dim)) { mask.setActiveState(ijk, true); } } } } // Compute the gradient for the remaining voxels gradients.setValuesOff(); for (iter = mSignLeafs.leaf(n).cbeginValueOn(); iter; ++iter) { ijk = iter.getCoord(); if(!mask.isValueOn(ijk & ~1u)) { Vec3T dir(math::ISGradient::result(mDistAcc, ijk)); dir.normalize(); gradients.setValueOn(iter.pos(), dir); } } // Merge regions int regionId = 1; for ( ; dim <= LeafDim; dim = dim << 1) { const unsigned coordMask = ~((dim << 1) - 1); 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) { adaptivity = adaptivityLeaf.getValue(ijk); if (mask.isValueOn(ijk) || isNonManifold(mDistAcc, ijk, mIsovalue, dim) || !isMergable(gradients, ijk, dim, adaptivity)) { mask.setActiveState(ijk & coordMask, true); } else { mergeVoxels(idxLeaf, ijk, dim, 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 = uint8_t(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 = uint8_t(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 = uint8_t(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 = uint8_t(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 = uint8_t(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 = uint8_t(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 = uint8_t(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 = uint8_t(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 = uint8_t(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 = uint8_t(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 = uint8_t(acc.getValue(ijk) & SIGNS); if (sEdgeGroupTable[rhsSigns][0] > 0) { unsigned char lhsSigns = uint8_t(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)); tools::pruneInactive(valueMask); } } 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, static_cast(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] = static_cast(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] = static_cast(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] = static_cast(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] = static_cast(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] = static_cast(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); } } } //////////////////////////////////////// /// @internal This overload is enabled only for grids with a scalar ValueType. template inline typename boost::enable_if, void>::type doVolumeToMesh( 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); } } } /// @internal This overload is enabled only for grids that do not have a scalar ValueType. template inline typename boost::disable_if, void>::type doVolumeToMesh( const GridType&, std::vector&, std::vector&, std::vector&, double, double) { OPENVDB_THROW(TypeError, "volume to mesh conversion is supported only for scalar grids"); } template inline void volumeToMesh( const GridType& grid, std::vector& points, std::vector& triangles, std::vector& quads, double isovalue, double adaptivity) { doVolumeToMesh(grid, points, triangles, quads, isovalue, adaptivity); } template inline void volumeToMesh( const GridType& grid, std::vector& points, std::vector& quads, double isovalue) { std::vector triangles; doVolumeToMesh(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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Dense.h0000644000000000000000000005505312603226506013572 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #include "Prune.h" 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. /// /// @warning The input coordinates are assume to be relative to /// the grid's origin, i.e. minimum of its index 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 local coordinate corresponding to the specified linear offset. /// /// @warning The returned coordinate is relative to the origin of this /// grid's bounding box so add dense.origin() to get absolute coordinates. inline Coord offsetToLocalCoord(size_t n) const { const size_t x = n / mX; n -= mX*x; const size_t y = n / mY; return Coord(Coord::ValueType(x), Coord::ValueType(y), Coord::ValueType(n - mY*y)); } /// @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 employed 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. /// /// @warning The input coordinates are assume to be relative to /// the grid's origin, i.e. minimum of its index 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 index coordinate corresponding to the specified linear offset. /// /// @warning The returned coordinate is relative to the origin of this /// grid's bounding box so add dense.origin() to get absolute coordinates. inline Coord offsetToLocalCoord(size_t n) const { const size_t z = n / mZ; n -= mZ*z; const size_t y = n / mY; return Coord(Coord::ValueType(n - mY*y), Coord::ValueType(y), Coord::ValueType(z)); } /// @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; } /// Return the grid's origin in index coordinates. inline const Coord& origin() const { return BaseT::mBBox.min(); } /// @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(const 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 global coordinate corresponding to the specified linear offset. inline Coord offsetToCoord(size_t n) const { return this->offsetToLocalCoord(n) + BaseT::mBBox.min(); } /// @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 topology. 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 aligned 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(); m &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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/PointIndexGrid.h0000644000000000000000000017423312603226506015425 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 PointIndexGrid.h /// /// @brief Space-partitioning acceleration structure for points. Partitions /// the points into voxels to accelerate range and nearest neighbor /// searches. /// /// @note Leaf nodes store a single point-index array and the voxels are only /// integer offsets into that array. The actual points are never stored /// in the acceleration structure, only offsets into an external array. /// /// @author Mihai Alden #ifndef OPENVDB_TOOLS_POINT_INDEX_GRID_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_POINT_INDEX_GRID_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include "PointPartitioner.h" #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { template struct SameLeafConfig; // forward declaration } namespace tools { template struct PointIndexLeafNode; // forward declaration /// Point index tree configured to match the default OpenVDB tree configuration typedef tree::Tree, 4>, 5> > > PointIndexTree; /// Point index grid typedef Grid PointIndexGrid; //////////////////////////////////////// /// @interface PointArray /// Expected interface for the PointArray container: /// @code /// template /// struct PointArray /// { /// // The type used to represent world-space point positions /// typedef VectorType value_type; /// /// // Return the number of points in the array /// size_t size() const; /// /// // Return the world-space position of the nth point in the array. /// void getPos(size_t n, VectorType& xyz) const; /// }; /// @endcode //////////////////////////////////////// /// @brief Partition points into a point index grid to accelerate range and /// nearest-neighbor searches. /// /// @param points world-space point array conforming to the PointArray interface /// @param xform world-to-index-space transform template inline typename GridT::Ptr createPointIndexGrid(const PointArrayT& points, const math::Transform& xform); /// @brief Return @c true if the given point index grid represents a valid partitioning /// of the given point array. /// /// @param points world-space point array conforming to the PointArray interface /// @param grid point index grid to validate template inline bool isValidPartition(const PointArrayT& points, const GridT& grid); /// Repartition the @a points if needed, otherwise return the input @a grid. template inline typename GridT::ConstPtr getValidPointIndexGrid(const PointArrayT& points, const typename GridT::ConstPtr& grid); /// Repartition the @a points if needed, otherwise return the input @a grid. template inline typename GridT::Ptr getValidPointIndexGrid(const PointArrayT& points, const typename GridT::Ptr& grid); //////////////////////////////////////// /// Accelerated range and nearest-neighbor searches for point index grids template struct PointIndexIterator { typedef tree::ValueAccessor ConstAccessor; typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::ValueType ValueType; PointIndexIterator(); PointIndexIterator(const PointIndexIterator& rhs); PointIndexIterator& operator=(const PointIndexIterator& rhs); /// @brief Construct an iterator over the indices of the points contained in voxel (i, j, k). /// @param ijk the voxel containing the points over which to iterate /// @param acc an accessor for the grid or tree that holds the point indices PointIndexIterator(const Coord& ijk, ConstAccessor& acc); /// @brief Construct an iterator over the indices of the points contained in /// the given bounding box. /// @param bbox the bounding box of the voxels containing the points over which to iterate /// @param acc an accessor for the grid or tree that holds the point indices /// @note The range of the @a bbox is inclusive. Thus, a bounding box with /// min = max is not empty but rather encloses a single voxel. PointIndexIterator(const CoordBBox& bbox, ConstAccessor& acc); /// @brief Clear the iterator and update it with the result of the given voxel query. /// @param ijk the voxel containing the points over which to iterate /// @param acc an accessor for the grid or tree that holds the point indices void searchAndUpdate(const Coord& ijk, ConstAccessor& acc); /// @brief Clear the iterator and update it with the result of the given voxel region query. /// @param bbox the bounding box of the voxels containing the points over which to iterate /// @param acc an accessor for the grid or tree that holds the point indices /// @note The range of the @a bbox is inclusive. Thus, a bounding box with /// min = max is not empty but rather encloses a single voxel. void searchAndUpdate(const CoordBBox& bbox, ConstAccessor& acc); /// @brief Clear the iterator and update it with the result of the given /// index-space bounding box query. /// @param bbox index-space bounding box /// @param acc an accessor for the grid or tree that holds the point indices /// @param points world-space point array conforming to the PointArray interface /// @param xform linear, uniform-scale transform (i.e., cubical voxels) template void searchAndUpdate(const BBoxd& bbox, ConstAccessor& acc, const PointArray& points, const math::Transform& xform); /// @brief Clear the iterator and update it with the result of the given /// index-space radial query. /// @param center index-space center /// @param radius index-space radius /// @param acc an accessor for the grid or tree that holds the point indices /// @param points world-space point array conforming to the PointArray interface /// @param xform linear, uniform-scale transform (i.e., cubical voxels) /// @param subvoxelAccuracy if true, check individual points against the search region, /// otherwise return all points that reside in voxels that are inside /// or intersect the search region template void searchAndUpdate(const Vec3d& center, double radius, ConstAccessor& acc, const PointArray& points, const math::Transform& xform, bool subvoxelAccuracy = true); /// @brief Clear the iterator and update it with the result of the given /// world-space bounding box query. /// @param bbox world-space bounding box /// @param acc an accessor for the grid or tree that holds the point indices /// @param points world-space point array conforming to the PointArray interface /// @param xform linear, uniform-scale transform (i.e., cubical voxels) template void worldSpaceSearchAndUpdate(const BBoxd& bbox, ConstAccessor& acc, const PointArray& points, const math::Transform& xform); /// @brief Clear the iterator and update it with the result of the given /// world-space radial query. /// @param center world-space center /// @param radius world-space radius /// @param acc an accessor for the grid or tree that holds the point indices /// @param points world-space point array conforming to the PointArray interface /// @param xform linear, uniform-scale transform (i.e., cubical voxels) /// @param subvoxelAccuracy if true, check individual points against the search region, /// otherwise return all points that reside in voxels that are inside /// or intersect the search region template void worldSpaceSearchAndUpdate(const Vec3d& center, double radius, ConstAccessor& acc, const PointArray& points, const math::Transform& xform, bool subvoxelAccuracy = true); /// Reset the iterator to point to the first item. void reset(); /// Return a const reference to the item to which this iterator is pointing. const ValueType& operator*() const { return *mRange.first; } /// @{ /// @brief Return @c true if this iterator is not yet exhausted. bool test() const { return mRange.first < mRange.second || mIter != mRangeList.end(); } operator bool() const { return this->test(); } /// @} /// Advance iterator to next item. void increment(); /// Advance iterator to next item. void operator++() { this->increment(); } /// @brief Advance iterator to next item. /// @return @c true if this iterator is not yet exhausted. bool next(); /// Return the number of point indices in the iterator range. size_t size() const; /// Return @c true if both iterators point to the same element. bool operator==(const PointIndexIterator& p) const { return mRange.first == p.mRange.first; } bool operator!=(const PointIndexIterator& p) const { return !this->operator==(p); } private: typedef std::pair Range; typedef std::deque RangeDeque; typedef typename RangeDeque::const_iterator RangeDequeCIter; typedef boost::scoped_array IndexArray; void clear(); // Primary index collection Range mRange; RangeDeque mRangeList; RangeDequeCIter mIter; // Secondary index collection IndexArray mIndexArray; size_t mIndexArraySize; }; // struct PointIndexIterator /// @brief Selectively extract and filter point data using a custom filter operator. /// /// @par FilterType example: /// @interface FilterType /// @code /// template /// struct WeightedAverageAccumulator { /// typedef T ValueType; /// /// WeightedAverageAccumulator(T const * const array, const T radius) /// : mValues(array), mInvRadius(1.0/radius), mWeightSum(0.0), mValueSum(0.0) {} /// /// void reset() { mWeightSum = mValueSum = T(0.0); } /// /// // the following method is invoked by the PointIndexFilter /// void operator()(const T distSqr, const size_t pointIndex) { /// const T weight = T(1.0) - openvdb::math::Sqrt(distSqr) * mInvRadius; /// mWeightSum += weight; /// mValueSum += weight * mValues[pointIndex]; /// } /// /// T result() const { return mWeightSum > T(0.0) ? mValueSum / mWeightSum : T(0.0); } /// /// private: /// T const * const mValues; /// const T mInvRadius; /// T mWeightSum, mValueSum; /// }; // struct WeightedAverageAccumulator /// @endcode template struct PointIndexFilter { typedef typename PointArray::value_type PointType; typedef typename PointType::ValueType PointElementType; typedef tree::ValueAccessor ConstAccessor; /// @brief Constructor /// @param points world-space point array conforming to the PointArray interface /// @param tree a point index tree /// @param xform linear, uniform-scale transform (i.e., cubical voxels) PointIndexFilter(const PointArray& points, const TreeType& tree, const math::Transform& xform); /// Thread safe copy constructor PointIndexFilter(const PointIndexFilter& rhs); /// @brief Perform a radial search query and apply the given filter /// operator to the selected points. /// @param center world-space center /// @param radius world-space radius /// @param op custom filter operator (see the FilterType example for interface details) template void searchAndApply(const PointType& center, PointElementType radius, FilterType& op); private: PointArray const * const mPoints; ConstAccessor mAcc; const math::Transform mXform; const PointElementType mInvVoxelSize; PointIndexIterator mIter; }; // struct PointIndexFilter //////////////////////////////////////// // Internal operators and implementation details namespace point_index_grid_internal { template struct ValidPartitioningOp { ValidPartitioningOp(tbb::atomic& hasChanged, const PointArrayT& points, const math::Transform& xform) : mPoints(&points) , mTransform(&xform) , mHasChanged(&hasChanged) { } template void operator()(LeafT &leaf, size_t /*leafIndex*/) const { if ((*mHasChanged)) { tbb::task::self().cancel_group_execution(); return; } typedef typename LeafT::IndexArray IndexArrayT; typedef typename IndexArrayT::value_type IndexT; typedef typename PointArrayT::value_type PointT; typename LeafT::ValueOnCIter iter; Coord voxelCoord; PointT point; const IndexT *begin = static_cast(NULL), *end = static_cast(NULL); for (iter = leaf.cbeginValueOn(); iter; ++iter) { if ((*mHasChanged)) break; voxelCoord = iter.getCoord(); leaf.getIndices(iter.pos(), begin, end); while (begin < end) { mPoints->getPos(*begin, point); if (voxelCoord != mTransform->worldToIndexCellCentered(point)) { mHasChanged->fetch_and_store(true); break; } ++begin; } } } private: PointArrayT const * const mPoints; math::Transform const * const mTransform; tbb::atomic * const mHasChanged; }; template struct PopulateLeafNodesOp { typedef uint32_t IndexT; typedef PointPartitioner Partitioner; PopulateLeafNodesOp(boost::scoped_array& leafNodes, const Partitioner& partitioner) : mLeafNodes(leafNodes.get()) , mPartitioner(&partitioner) { } void operator()(const tbb::blocked_range& range) const { typedef typename Partitioner::VoxelOffsetType VoxelOffsetT; size_t maxPointCount = 0; for (size_t n = range.begin(), N = range.end(); n != N; ++n) { maxPointCount = std::max(maxPointCount, mPartitioner->indices(n).size()); } const IndexT voxelCount = LeafNodeT::SIZE; // allocate histogram buffers boost::scoped_array offsets(new VoxelOffsetT[maxPointCount]); boost::scoped_array histogram(new IndexT[voxelCount]); VoxelOffsetT const * const voxelOffsets = mPartitioner->voxelOffsets().get(); for (size_t n = range.begin(), N = range.end(); n != N; ++n) { LeafNodeT* node = new LeafNodeT(); node->setOrigin(mPartitioner->origin(n)); typename Partitioner::IndexIterator it = mPartitioner->indices(n); const size_t pointCount = it.size(); IndexT const * const indices = &*it; // local copy of voxel offsets. for (IndexT i = 0; i < pointCount; ++i) { offsets[i] = voxelOffsets[ indices[i] ]; } // compute voxel-offset histogram memset(&histogram[0], 0, voxelCount * sizeof(IndexT)); for (IndexT i = 0; i < pointCount; ++i) { ++histogram[ offsets[i] ]; } typename LeafNodeT::NodeMaskType& mask = node->getValueMask(); typename LeafNodeT::Buffer& buffer = node->buffer(); // scan histogram (all-prefix-sums) IndexT count = 0, startOffset; for (int i = 0; i < int(voxelCount); ++i) { if (histogram[i] > 0) { startOffset = count; count += histogram[i]; histogram[i] = startOffset; mask.setOn(i); } buffer.setValue(i, count); } // allocate point-index array node->indices().resize(pointCount); typename LeafNodeT::ValueType * const orderedIndices = node->indices().data(); // rank and permute for (IndexT i = 0; i < pointCount; ++i) { orderedIndices[ histogram[ offsets[i] ]++ ] = indices[i]; } mLeafNodes[n] = node; } } ////////// LeafNodeT* * const mLeafNodes; Partitioner const * const mPartitioner; }; /// Construct a @c PointIndexTree template inline void constructPointTree(TreeType& tree, const math::Transform& xform, const PointArray& points) { typedef typename TreeType::LeafNodeType LeafType; boost::scoped_array leafNodes; size_t leafNodeCount = 0; { PointPartitioner partitioner; partitioner.construct(points, xform, /*voxelOrder=*/false, /*recordVoxelOffsets=*/true); leafNodeCount = partitioner.size(); leafNodes.reset(new LeafType*[leafNodeCount]); const tbb::blocked_range range(0, leafNodeCount); tbb::parallel_for(range, PopulateLeafNodesOp(leafNodes, partitioner)); } tree::ValueAccessor acc(tree); for (size_t n = 0; n < leafNodeCount; ++n) { acc.addLeaf(leafNodes[n]); } } //////////////////////////////////////// template inline void dequeToArray(const std::deque& d, boost::scoped_array& a, size_t& size) { size = d.size(); a.reset(new T[size]); typename std::deque::const_iterator it = d.begin(), itEnd = d.end(); T* item = a.get(); for ( ; it != itEnd; ++it, ++item) *item = *it; } inline void constructExclusiveRegions(std::vector& regions, const CoordBBox& bbox, const CoordBBox& ibox) { regions.clear(); regions.reserve(6); Coord cmin = ibox.min(); Coord cmax = ibox.max(); // left-face bbox regions.push_back(bbox); regions.back().max().z() = cmin.z(); // right-face bbox regions.push_back(bbox); regions.back().min().z() = cmax.z(); --cmax.z(); // accounting for cell centered bucketing. ++cmin.z(); // front-face bbox regions.push_back(bbox); CoordBBox* lastRegion = ®ions.back(); lastRegion->min().z() = cmin.z(); lastRegion->max().z() = cmax.z(); lastRegion->max().x() = cmin.x(); // back-face bbox regions.push_back(*lastRegion); lastRegion = ®ions.back(); lastRegion->min().x() = cmax.x(); lastRegion->max().x() = bbox.max().x(); --cmax.x(); ++cmin.x(); // bottom-face bbox regions.push_back(*lastRegion); lastRegion = ®ions.back(); lastRegion->min().x() = cmin.x(); lastRegion->max().x() = cmax.x(); lastRegion->max().y() = cmin.y(); // top-face bbox regions.push_back(*lastRegion); lastRegion = ®ions.back(); lastRegion->min().y() = cmax.y(); lastRegion->max().y() = bbox.max().y(); } template struct BBoxFilter { typedef typename PointArray::value_type PointType; typedef typename PointType::ValueType PointElementType; typedef std::pair Range; typedef std::deque RangeDeque; typedef std::deque IndexDeque; BBoxFilter(RangeDeque& ranges, IndexDeque& indices, const BBoxd& bbox, const PointArray& points, const math::Transform& xform) : mRanges(ranges) , mIndices(indices) , mRegion(bbox) , mPoints(points) , mMap(*xform.baseMap()) { } template void filterLeafNode(const LeafNodeType& leaf) { typename LeafNodeType::ValueOnCIter iter; const IndexT *begin = static_cast(NULL), *end = static_cast(NULL); for (iter = leaf.cbeginValueOn(); iter; ++iter) { leaf.getIndices(iter.pos(), begin, end); filterVoxel(iter.getCoord(), begin, end); } } void filterVoxel(const Coord&, const IndexT* begin, const IndexT* end) { Vec3d xyz; PointType vec; for (; begin < end; ++begin) { mPoints.getPos(*begin, vec); // world to index cell centered, similar the PointPartitioner tool. xyz = mMap.applyInverseMap(vec); xyz[0] = math::Round(xyz[0]); xyz[1] = math::Round(xyz[1]); xyz[2] = math::Round(xyz[2]); if (mRegion.isInside(xyz)) { mIndices.push_back(*begin); } } } private: RangeDeque& mRanges; IndexDeque& mIndices; const BBoxd mRegion; const PointArray& mPoints; const math::MapBase& mMap; }; template struct RadialRangeFilter { typedef typename PointArray::value_type PointType; typedef typename PointType::ValueType PointElementType; typedef std::pair Range; typedef std::deque RangeDeque; typedef std::deque IndexDeque; RadialRangeFilter(RangeDeque& ranges, IndexDeque& indices, const Vec3d& xyz, double radius, const PointArray& points, const math::Transform& xform, const double leafNodeDim, const bool subvoxelAccuracy) : mRanges(ranges) , mIndices(indices) , mCenter(xyz) , mWSCenter(xform.indexToWorld(xyz)) , mVoxelDist1(PointElementType(0.0)) , mVoxelDist2(PointElementType(0.0)) , mLeafNodeDist1(PointElementType(0.0)) , mLeafNodeDist2(PointElementType(0.0)) , mWSRadiusSqr(PointElementType(radius * xform.voxelSize()[0])) , mPoints(points) , mSubvoxelAccuracy(subvoxelAccuracy) { const PointElementType voxelRadius = PointElementType(std::sqrt(3.0) * 0.5); mVoxelDist1 = voxelRadius + PointElementType(radius); mVoxelDist1 *= mVoxelDist1; if (radius > voxelRadius) { mVoxelDist2 = PointElementType(radius) - voxelRadius; mVoxelDist2 *= mVoxelDist2; } const PointElementType leafNodeRadius = PointElementType(leafNodeDim * std::sqrt(3.0) * 0.5); mLeafNodeDist1 = leafNodeRadius + PointElementType(radius); mLeafNodeDist1 *= mLeafNodeDist1; if (radius > leafNodeRadius) { mLeafNodeDist2 = PointElementType(radius) - leafNodeRadius; mLeafNodeDist2 *= mLeafNodeDist2; } mWSRadiusSqr *= mWSRadiusSqr; } template void filterLeafNode(const LeafNodeType& leaf) { { const Coord& ijk = leaf.origin(); PointType vec; vec[0] = PointElementType(ijk[0]); vec[1] = PointElementType(ijk[1]); vec[2] = PointElementType(ijk[2]); vec += PointElementType(LeafNodeType::DIM - 1) * 0.5; vec -= mCenter; const PointElementType dist = vec.lengthSqr(); if (dist > mLeafNodeDist1) return; if (mLeafNodeDist2 > 0.0 && dist < mLeafNodeDist2) { const IndexT* begin = &leaf.indices().front(); mRanges.push_back(Range(begin, begin + leaf.indices().size())); return; } } typename LeafNodeType::ValueOnCIter iter; const IndexT *begin = static_cast(NULL), *end = static_cast(NULL); for (iter = leaf.cbeginValueOn(); iter; ++iter) { leaf.getIndices(iter.pos(), begin, end); filterVoxel(iter.getCoord(), begin, end); } } void filterVoxel(const Coord& ijk, const IndexT* begin, const IndexT* end) { PointType vec; { vec[0] = mCenter[0] - PointElementType(ijk[0]); vec[1] = mCenter[1] - PointElementType(ijk[1]); vec[2] = mCenter[2] - PointElementType(ijk[2]); const PointElementType dist = vec.lengthSqr(); if (dist > mVoxelDist1) return; if (!mSubvoxelAccuracy || (mVoxelDist2 > 0.0 && dist < mVoxelDist2)) { if (!mRanges.empty() && mRanges.back().second == begin) { mRanges.back().second = end; } else { mRanges.push_back(Range(begin, end)); } return; } } while (begin < end) { mPoints.getPos(*begin, vec); vec = mWSCenter - vec; if (vec.lengthSqr() < mWSRadiusSqr) { mIndices.push_back(*begin); } ++begin; } } private: RangeDeque& mRanges; IndexDeque& mIndices; const PointType mCenter, mWSCenter; PointElementType mVoxelDist1, mVoxelDist2, mLeafNodeDist1, mLeafNodeDist2, mWSRadiusSqr; const PointArray& mPoints; const bool mSubvoxelAccuracy; }; // struct RadialRangeFilter //////////////////////////////////////// template inline void filteredPointIndexSearchVoxels(RangeFilterType& filter, const LeafNodeType& leaf, const Coord& min, const Coord& max) { typedef typename LeafNodeType::ValueType PointIndexT; Index xPos(0), yPos(0), pos(0); Coord ijk(0); const PointIndexT* dataPtr = &leaf.indices().front(); PointIndexT beginOffset, endOffset; for (ijk[0] = min[0]; ijk[0] <= max[0]; ++ijk[0]) { xPos = (ijk[0] & (LeafNodeType::DIM - 1u)) << (2 * LeafNodeType::LOG2DIM); for (ijk[1] = min[1]; ijk[1] <= max[1]; ++ijk[1]) { yPos = xPos + ((ijk[1] & (LeafNodeType::DIM - 1u)) << LeafNodeType::LOG2DIM); for (ijk[2] = min[2]; ijk[2] <= max[2]; ++ijk[2]) { pos = yPos + (ijk[2] & (LeafNodeType::DIM - 1u)); beginOffset = (pos == 0 ? PointIndexT(0) : leaf.getValue(pos - 1)); endOffset = leaf.getValue(pos); if (endOffset > beginOffset) { filter.filterVoxel(ijk, dataPtr + beginOffset, dataPtr + endOffset); } } } } } template inline void filteredPointIndexSearch(RangeFilterType& filter, ConstAccessor& acc, const CoordBBox& bbox) { typedef typename ConstAccessor::TreeType::LeafNodeType LeafNodeType; Coord ijk(0), ijkMax(0), ijkA(0), ijkB(0); const Coord leafMin = bbox.min() & ~(LeafNodeType::DIM - 1); const Coord leafMax = bbox.max() & ~(LeafNodeType::DIM - 1); for (ijk[0] = leafMin[0]; ijk[0] <= leafMax[0]; ijk[0] += LeafNodeType::DIM) { for (ijk[1] = leafMin[1]; ijk[1] <= leafMax[1]; ijk[1] += LeafNodeType::DIM) { for (ijk[2] = leafMin[2]; ijk[2] <= leafMax[2]; ijk[2] += LeafNodeType::DIM) { if (const LeafNodeType* leaf = acc.probeConstLeaf(ijk)) { ijkMax = ijk; ijkMax.offset(LeafNodeType::DIM - 1); // intersect leaf bbox with search region. ijkA = Coord::maxComponent(bbox.min(), ijk); ijkB = Coord::minComponent(bbox.max(), ijkMax); if (ijkA != ijk || ijkB != ijkMax) { filteredPointIndexSearchVoxels(filter, *leaf, ijkA, ijkB); } else { // leaf bbox is inside the search region filter.filterLeafNode(*leaf); } } } } } } //////////////////////////////////////// template inline void pointIndexSearchVoxels(RangeDeque& rangeList, const LeafNodeType& leaf, const Coord& min, const Coord& max) { typedef typename LeafNodeType::ValueType PointIndexT; typedef typename PointIndexT::IntType IntT; typedef typename RangeDeque::value_type Range; Index xPos(0), pos(0), zStride = Index(max[2] - min[2]); const PointIndexT* dataPtr = &leaf.indices().front(); PointIndexT beginOffset(0), endOffset(0), previousOffset(static_cast(leaf.indices().size() + 1u)); Coord ijk(0); for (ijk[0] = min[0]; ijk[0] <= max[0]; ++ijk[0]) { xPos = (ijk[0] & (LeafNodeType::DIM - 1u)) << (2 * LeafNodeType::LOG2DIM); for (ijk[1] = min[1]; ijk[1] <= max[1]; ++ijk[1]) { pos = xPos + ((ijk[1] & (LeafNodeType::DIM - 1u)) << LeafNodeType::LOG2DIM); pos += (min[2] & (LeafNodeType::DIM - 1u)); beginOffset = (pos == 0 ? PointIndexT(0) : leaf.getValue(pos - 1)); endOffset = leaf.getValue(pos+zStride); if (endOffset > beginOffset) { if (beginOffset == previousOffset) { rangeList.back().second = dataPtr + endOffset; } else { rangeList.push_back(Range(dataPtr + beginOffset, dataPtr + endOffset)); } previousOffset = endOffset; } } } } template inline void pointIndexSearch(RangeDeque& rangeList, ConstAccessor& acc, const CoordBBox& bbox) { typedef typename ConstAccessor::TreeType::LeafNodeType LeafNodeType; typedef typename LeafNodeType::ValueType PointIndexT; typedef typename RangeDeque::value_type Range; Coord ijk(0), ijkMax(0), ijkA(0), ijkB(0); const Coord leafMin = bbox.min() & ~(LeafNodeType::DIM - 1); const Coord leafMax = bbox.max() & ~(LeafNodeType::DIM - 1); for (ijk[0] = leafMin[0]; ijk[0] <= leafMax[0]; ijk[0] += LeafNodeType::DIM) { for (ijk[1] = leafMin[1]; ijk[1] <= leafMax[1]; ijk[1] += LeafNodeType::DIM) { for (ijk[2] = leafMin[2]; ijk[2] <= leafMax[2]; ijk[2] += LeafNodeType::DIM) { if (const LeafNodeType* leaf = acc.probeConstLeaf(ijk)) { ijkMax = ijk; ijkMax.offset(LeafNodeType::DIM - 1); // intersect leaf bbox with search region. ijkA = Coord::maxComponent(bbox.min(), ijk); ijkB = Coord::minComponent(bbox.max(), ijkMax); if (ijkA != ijk || ijkB != ijkMax) { pointIndexSearchVoxels(rangeList, *leaf, ijkA, ijkB); } else { // leaf bbox is inside the search region, add all indices. const PointIndexT* begin = &leaf->indices().front(); rangeList.push_back(Range(begin, (begin + leaf->indices().size()))); } } } } } } } // namespace point_index_grid_internal // PointIndexIterator implementation template inline PointIndexIterator::PointIndexIterator() : mRange(static_cast(NULL), static_cast(NULL)) , mRangeList() , mIter(mRangeList.begin()) , mIndexArray() , mIndexArraySize(0) { } template inline PointIndexIterator::PointIndexIterator(const PointIndexIterator& rhs) : mRange(rhs.mRange) , mRangeList(rhs.mRangeList) , mIter(mRangeList.begin()) , mIndexArray() , mIndexArraySize(rhs.mIndexArraySize) { if (rhs.mIndexArray) { mIndexArray.reset(new ValueType[mIndexArraySize]); memcpy(mIndexArray.get(), rhs.mIndexArray.get(), mIndexArraySize * sizeof(ValueType)); } } template inline PointIndexIterator& PointIndexIterator::operator=(const PointIndexIterator& rhs) { if (&rhs != this) { mRange = rhs.mRange; mRangeList = rhs.mRangeList; mIter = mRangeList.begin(); mIndexArray.reset(); mIndexArraySize = rhs.mIndexArraySize; if (rhs.mIndexArray) { mIndexArray.reset(new ValueType[mIndexArraySize]); memcpy(mIndexArray.get(), rhs.mIndexArray.get(), mIndexArraySize * sizeof(ValueType)); } } return *this; } template inline PointIndexIterator::PointIndexIterator(const Coord& ijk, ConstAccessor& acc) : mRange(static_cast(NULL), static_cast(NULL)) , mRangeList() , mIter(mRangeList.begin()) , mIndexArray() , mIndexArraySize(0) { const LeafNodeType* leaf = acc.probeConstLeaf(ijk); if (leaf && leaf->getIndices(ijk, mRange.first, mRange.second)) { mRangeList.push_back(mRange); mIter = mRangeList.begin(); } } template inline PointIndexIterator::PointIndexIterator(const CoordBBox& bbox, ConstAccessor& acc) : mRange(static_cast(NULL), static_cast(NULL)) , mRangeList() , mIter(mRangeList.begin()) , mIndexArray() , mIndexArraySize(0) { point_index_grid_internal::pointIndexSearch(mRangeList, acc, bbox); if (!mRangeList.empty()) { mIter = mRangeList.begin(); mRange = mRangeList.front(); } } template inline void PointIndexIterator::reset() { mIter = mRangeList.begin(); if (!mRangeList.empty()) { mRange = mRangeList.front(); } else if (mIndexArray) { mRange.first = mIndexArray.get(); mRange.second = mRange.first + mIndexArraySize; } else { mRange.first = static_cast(NULL); mRange.second = static_cast(NULL); } } template inline void PointIndexIterator::increment() { ++mRange.first; if (mRange.first >= mRange.second && mIter != mRangeList.end()) { ++mIter; if (mIter != mRangeList.end()) { mRange = *mIter; } else if (mIndexArray) { mRange.first = mIndexArray.get(); mRange.second = mRange.first + mIndexArraySize; } } } template inline bool PointIndexIterator::next() { if (!this->test()) return false; this->increment(); return this->test(); } template inline size_t PointIndexIterator::size() const { size_t count = 0; typename RangeDeque::const_iterator it = mRangeList.begin(); for ( ; it != mRangeList.end(); ++it) { count += it->second - it->first; } return count + mIndexArraySize; } template inline void PointIndexIterator::clear() { mRange.first = static_cast(NULL); mRange.second = static_cast(NULL); mRangeList.clear(); mIter = mRangeList.end(); mIndexArray.reset(); mIndexArraySize = 0; } template inline void PointIndexIterator::searchAndUpdate(const Coord& ijk, ConstAccessor& acc) { this->clear(); const LeafNodeType* leaf = acc.probeConstLeaf(ijk); if (leaf && leaf->getIndices(ijk, mRange.first, mRange.second)) { mRangeList.push_back(mRange); mIter = mRangeList.begin(); } } template inline void PointIndexIterator::searchAndUpdate(const CoordBBox& bbox, ConstAccessor& acc) { this->clear(); point_index_grid_internal::pointIndexSearch(mRangeList, acc, bbox); if (!mRangeList.empty()) { mIter = mRangeList.begin(); mRange = mRangeList.front(); } } template template inline void PointIndexIterator::searchAndUpdate(const BBoxd& bbox, ConstAccessor& acc, const PointArray& points, const math::Transform& xform) { this->clear(); std::vector searchRegions; CoordBBox region(Coord::round(bbox.min()), Coord::round(bbox.max())); const Coord dim = region.dim(); const int minExtent = std::min(dim[0], std::min(dim[1], dim[2])); if (minExtent > 2) { // collect indices that don't need to be tested CoordBBox ibox = region; ibox.expand(-1); point_index_grid_internal::pointIndexSearch(mRangeList, acc, ibox); // define regions for the filtered search ibox.expand(1); point_index_grid_internal::constructExclusiveRegions(searchRegions, region, ibox); } else { searchRegions.push_back(region); } // filtered search std::deque filteredIndices; point_index_grid_internal::BBoxFilter filter(mRangeList, filteredIndices, bbox, points, xform); for (size_t n = 0, N = searchRegions.size(); n < N; ++n) { point_index_grid_internal::filteredPointIndexSearch(filter, acc, searchRegions[n]); } point_index_grid_internal::dequeToArray(filteredIndices, mIndexArray, mIndexArraySize); this->reset(); } template template inline void PointIndexIterator::searchAndUpdate(const Vec3d& center, double radius, ConstAccessor& acc, const PointArray& points, const math::Transform& xform, bool subvoxelAccuracy) { this->clear(); std::vector searchRegions; // bounding box CoordBBox bbox( Coord::round(Vec3d(center[0] - radius, center[1] - radius, center[2] - radius)), Coord::round(Vec3d(center[0] + radius, center[1] + radius, center[2] + radius))); bbox.expand(1); const double iRadius = radius * double(1.0 / std::sqrt(3.0)); if (iRadius > 2.0) { // inscribed box CoordBBox ibox( Coord::round(Vec3d(center[0] - iRadius, center[1] - iRadius, center[2] - iRadius)), Coord::round(Vec3d(center[0] + iRadius, center[1] + iRadius, center[2] + iRadius))); ibox.expand(-1); // collect indices that don't need to be tested point_index_grid_internal::pointIndexSearch(mRangeList, acc, ibox); ibox.expand(1); point_index_grid_internal::constructExclusiveRegions(searchRegions, bbox, ibox); } else { searchRegions.push_back(bbox); } // filtered search std::deque filteredIndices; const double leafNodeDim = double(TreeType::LeafNodeType::DIM); typedef point_index_grid_internal::RadialRangeFilter FilterT; FilterT filter(mRangeList, filteredIndices, center, radius, points, xform, leafNodeDim, subvoxelAccuracy); for (size_t n = 0, N = searchRegions.size(); n < N; ++n) { point_index_grid_internal::filteredPointIndexSearch(filter, acc, searchRegions[n]); } point_index_grid_internal::dequeToArray(filteredIndices, mIndexArray, mIndexArraySize); this->reset(); } template template inline void PointIndexIterator::worldSpaceSearchAndUpdate(const BBoxd& bbox, ConstAccessor& acc, const PointArray& points, const math::Transform& xform) { this->searchAndUpdate( BBoxd(xform.worldToIndex(bbox.min()), xform.worldToIndex(bbox.max())), acc, points, xform); } template template inline void PointIndexIterator::worldSpaceSearchAndUpdate(const Vec3d& center, double radius, ConstAccessor& acc, const PointArray& points, const math::Transform& xform, bool subvoxelAccuracy) { this->searchAndUpdate(xform.worldToIndex(center), (radius / xform.voxelSize()[0]), acc, points, xform, subvoxelAccuracy); } //////////////////////////////////////// // PointIndexFilter implementation template inline PointIndexFilter::PointIndexFilter( const PointArray& points, const TreeType& tree, const math::Transform& xform) : mPoints(&points), mAcc(tree), mXform(xform), mInvVoxelSize(1.0/xform.voxelSize()[0]) { } template inline PointIndexFilter::PointIndexFilter(const PointIndexFilter& rhs) : mPoints(rhs.mPoints) , mAcc(rhs.mAcc.tree()) , mXform(rhs.mXform) , mInvVoxelSize(rhs.mInvVoxelSize) { } template template inline void PointIndexFilter::searchAndApply( const PointType& center, PointElementType radius, FilterType& op) { if (radius * mInvVoxelSize < PointElementType(8.0)) { mIter.searchAndUpdate(openvdb::CoordBBox( mXform.worldToIndexCellCentered(center - radius), mXform.worldToIndexCellCentered(center + radius)), mAcc); } else { mIter.worldSpaceSearchAndUpdate( center, radius, mAcc, *mPoints, mXform, /*subvoxelAccuracy=*/false); } const PointElementType radiusSqr = radius * radius; PointElementType distSqr = 0.0; PointType pos; for (; mIter; ++mIter) { mPoints->getPos(*mIter, pos); pos -= center; distSqr = pos.lengthSqr(); if (distSqr < radiusSqr) { op(distSqr, *mIter); } } } //////////////////////////////////////// template inline typename GridT::Ptr createPointIndexGrid(const PointArrayT& points, const math::Transform& xform) { typename GridT::Ptr grid = GridT::create(typename GridT::ValueType(0)); grid->setTransform(xform.copy()); if (points.size() > 0) { point_index_grid_internal::constructPointTree( grid->tree(), grid->transform(), points); } return grid; } template inline bool isValidPartition(const PointArrayT& points, const GridT& grid) { tree::LeafManager leafs(grid.tree()); size_t pointCount = 0; for (size_t n = 0, N = leafs.leafCount(); n < N; ++n) { pointCount += leafs.leaf(n).indices().size(); } if (points.size() != pointCount) { return false; } tbb::atomic changed; changed = false; point_index_grid_internal::ValidPartitioningOp op(changed, points, grid.transform()); leafs.foreach(op); return !bool(changed); } template inline typename GridT::ConstPtr getValidPointIndexGrid(const PointArrayT& points, const typename GridT::ConstPtr& grid) { if (isValidPartition(points, *grid)) { return grid; } return createPointIndexGrid(points, grid->transform()); } template inline typename GridT::Ptr getValidPointIndexGrid(const PointArrayT& points, const typename GridT::Ptr& grid) { if (isValidPartition(points, *grid)) { return grid; } return createPointIndexGrid(points, grid->transform()); } //////////////////////////////////////// template struct PointIndexLeafNode : public tree::LeafNode { typedef PointIndexLeafNode LeafNodeType; typedef boost::shared_ptr Ptr; typedef T ValueType; typedef std::vector IndexArray; IndexArray& indices() { return mIndices; } const IndexArray& indices() const { return mIndices; } bool getIndices(const Coord& ijk, const ValueType*& begin, const ValueType*& end) const; bool getIndices(Index offset, const ValueType*& begin, const ValueType*& end) const; void setOffsetOn(Index offset, const ValueType& val); void setOffsetOnly(Index offset, const ValueType& val); bool isEmpty(const CoordBBox& bbox) const; private: IndexArray mIndices; //////////////////////////////////////// // The following methods had to be copied from the LeafNode class // to make the derived PointIndexLeafNode class compatible with the tree structure. public: typedef tree::LeafNode BaseLeaf; typedef util::NodeMask NodeMaskType; using BaseLeaf::LOG2DIM; using BaseLeaf::TOTAL; using BaseLeaf::DIM; using BaseLeaf::NUM_VALUES; using BaseLeaf::NUM_VOXELS; using BaseLeaf::SIZE; using BaseLeaf::LEVEL; /// Default constructor PointIndexLeafNode() : BaseLeaf(), mIndices() {} explicit PointIndexLeafNode(const Coord& coords, const T& value = zeroVal(), bool active = false) : BaseLeaf(coords, value, active) , mIndices() { } #ifndef OPENVDB_2_ABI_COMPATIBLE PointIndexLeafNode(PartialCreate, const Coord& coords, const T& value = zeroVal(), bool active = false) : BaseLeaf(PartialCreate(), coords, value, active) , mIndices() { } #endif /// Deep copy constructor PointIndexLeafNode(const PointIndexLeafNode& rhs) : BaseLeaf(rhs), mIndices(rhs.mIndices) {} /// @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 PointIndexLeafNode* other) const { return BaseLeaf::hasSameTopology(other); } /// Check for buffer, state and origin equivalence. bool operator==(const PointIndexLeafNode& other) const { return BaseLeaf::operator==(other); } bool operator!=(const PointIndexLeafNode& other) const { return !(other == *this); } template void merge(const PointIndexLeafNode& rhs) { BaseLeaf::merge(rhs); } template void merge(const ValueType& tileValue, bool tileActive) { BaseLeaf::template merge(tileValue, tileActive); } template void merge(const PointIndexLeafNode& other, const ValueType& /*bg*/, const ValueType& /*otherBG*/) { BaseLeaf::template merge(other); } void addLeaf(PointIndexLeafNode*) {} template void addLeafAndCache(PointIndexLeafNode*, AccessorT&) {} //@{ /// @brief Return a pointer to this node. PointIndexLeafNode* touchLeaf(const Coord&) { return this; } template PointIndexLeafNode* 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 } PointIndexLeafNode* probeLeaf(const Coord&) { return this; } template PointIndexLeafNode* probeLeafAndCache(const Coord&, AccessorT&) { return this; } //@} //@{ /// @brief Return a @const pointer to this node. const PointIndexLeafNode* probeConstLeaf(const Coord&) const { return this; } template const PointIndexLeafNode* probeConstLeafAndCache(const Coord&, AccessorT&) const {return this;} template const PointIndexLeafNode* probeLeafAndCache(const Coord&, AccessorT&) const { return this; } const PointIndexLeafNode* 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 } //@} // I/O methods void readBuffers(std::istream& is, bool fromHalf = false); void readBuffers(std::istream& is, const CoordBBox&, bool fromHalf = false); void writeBuffers(std::ostream& os, bool toHalf = false) const; Index64 memUsage() const; //////////////////////////////////////// // Disable all write methods to avoid unintentional changes // to the point-array offsets. void assertNonmodifiable() { assert(false && "Cannot modify voxel values in a PointIndexTree."); } void setActiveState(const Coord&, bool) { assertNonmodifiable(); } void setActiveState(Index, bool) { assertNonmodifiable(); } void setValueOnly(const Coord&, const ValueType&) { assertNonmodifiable(); } void setValueOnly(Index, const ValueType&) { assertNonmodifiable(); } void setValueOff(const Coord&) { assertNonmodifiable(); } void setValueOff(Index) { assertNonmodifiable(); } void setValueOff(const Coord&, const ValueType&) { assertNonmodifiable(); } void setValueOff(Index, const ValueType&) { assertNonmodifiable(); } void setValueOn(const Coord&) { assertNonmodifiable(); } void setValueOn(Index) { assertNonmodifiable(); } void setValueOn(const Coord&, const ValueType&) { assertNonmodifiable(); } void setValueOn(Index, const ValueType&) { assertNonmodifiable(); } void setValue(const Coord&, const ValueType&) { assertNonmodifiable(); } void setValuesOn() { assertNonmodifiable(); } void setValuesOff() { assertNonmodifiable(); } template void modifyValue(Index, const ModifyOp&) { assertNonmodifiable(); } template void modifyValue(const Coord&, const ModifyOp&) { assertNonmodifiable(); } template void modifyValueAndActiveState(const Coord&, const ModifyOp&) { assertNonmodifiable(); } void clip(const CoordBBox&, const ValueType&) { assertNonmodifiable(); } void fill(const CoordBBox&, const ValueType&, bool) { assertNonmodifiable(); } void fill(const ValueType&) {} void fill(const ValueType&, bool) { assertNonmodifiable(); } template void setValueOnlyAndCache(const Coord&, const ValueType&, AccessorT&) {assertNonmodifiable();} template void modifyValueAndActiveStateAndCache(const Coord&, const ModifyOp&, AccessorT&) { assertNonmodifiable(); } template void setValueOffAndCache(const Coord&, const ValueType&, AccessorT&) { assertNonmodifiable(); } template void setActiveStateAndCache(const Coord&, bool, AccessorT&) { assertNonmodifiable(); } void resetBackground(const ValueType&, const ValueType&) { assertNonmodifiable(); } void signedFloodFill(const ValueType&) { assertNonmodifiable(); } void signedFloodFill(const ValueType&, const ValueType&) { assertNonmodifiable(); } void negate() { assertNonmodifiable(); } protected: typedef typename BaseLeaf::ValueOn ValueOn; typedef typename BaseLeaf::ValueOff ValueOff; typedef typename BaseLeaf::ValueAll ValueAll; typedef typename BaseLeaf::ChildOn ChildOn; typedef typename BaseLeaf::ChildOff ChildOff; typedef typename BaseLeaf::ChildAll ChildAll; typedef typename NodeMaskType::OnIterator MaskOnIterator; typedef typename NodeMaskType::OffIterator MaskOffIterator; typedef typename NodeMaskType::DenseIterator MaskDenseIterator; // During topology-only construction, access is needed // to protected/private members of other template instances. template friend struct PointIndexLeafNode; friend class tree::IteratorBase; friend class tree::IteratorBase; friend class tree::IteratorBase; public: typedef typename BaseLeaf::template ValueIter< MaskOnIterator, PointIndexLeafNode, const ValueType, ValueOn> ValueOnIter; typedef typename BaseLeaf::template ValueIter< MaskOnIterator, const PointIndexLeafNode, const ValueType, ValueOn> ValueOnCIter; typedef typename BaseLeaf::template ValueIter< MaskOffIterator, PointIndexLeafNode, const ValueType, ValueOff> ValueOffIter; typedef typename BaseLeaf::template ValueIter< MaskOffIterator,const PointIndexLeafNode,const ValueType,ValueOff> ValueOffCIter; typedef typename BaseLeaf::template ValueIter< MaskDenseIterator, PointIndexLeafNode, const ValueType, ValueAll> ValueAllIter; typedef typename BaseLeaf::template ValueIter< MaskDenseIterator,const PointIndexLeafNode,const ValueType,ValueAll> ValueAllCIter; typedef typename BaseLeaf::template ChildIter< MaskOnIterator, PointIndexLeafNode, ChildOn> ChildOnIter; typedef typename BaseLeaf::template ChildIter< MaskOnIterator, const PointIndexLeafNode, ChildOn> ChildOnCIter; typedef typename BaseLeaf::template ChildIter< MaskOffIterator, PointIndexLeafNode, ChildOff> ChildOffIter; typedef typename BaseLeaf::template ChildIter< MaskOffIterator, const PointIndexLeafNode, ChildOff> ChildOffCIter; typedef typename BaseLeaf::template DenseIter< PointIndexLeafNode, ValueType, ChildAll> ChildAllIter; typedef typename BaseLeaf::template DenseIter< const PointIndexLeafNode, const ValueType, ChildAll> ChildAllCIter; #define VMASK_ this->getValueMask() ValueOnCIter cbeginValueOn() const { return ValueOnCIter(VMASK_.beginOn(), this); } ValueOnCIter beginValueOn() const { return ValueOnCIter(VMASK_.beginOn(), this); } ValueOnIter beginValueOn() { return ValueOnIter(VMASK_.beginOn(), this); } ValueOffCIter cbeginValueOff() const { return ValueOffCIter(VMASK_.beginOff(), this); } ValueOffCIter beginValueOff() const { return ValueOffCIter(VMASK_.beginOff(), this); } ValueOffIter beginValueOff() { return ValueOffIter(VMASK_.beginOff(), this); } ValueAllCIter cbeginValueAll() const { return ValueAllCIter(VMASK_.beginDense(), this); } ValueAllCIter beginValueAll() const { return ValueAllCIter(VMASK_.beginDense(), this); } ValueAllIter beginValueAll() { return ValueAllIter(VMASK_.beginDense(), this); } ValueOnCIter cendValueOn() const { return ValueOnCIter(VMASK_.endOn(), this); } ValueOnCIter endValueOn() const { return ValueOnCIter(VMASK_.endOn(), this); } ValueOnIter endValueOn() { return ValueOnIter(VMASK_.endOn(), this); } ValueOffCIter cendValueOff() const { return ValueOffCIter(VMASK_.endOff(), this); } ValueOffCIter endValueOff() const { return ValueOffCIter(VMASK_.endOff(), this); } ValueOffIter endValueOff() { return ValueOffIter(VMASK_.endOff(), this); } ValueAllCIter cendValueAll() const { return ValueAllCIter(VMASK_.endDense(), this); } ValueAllCIter endValueAll() const { return ValueAllCIter(VMASK_.endDense(), this); } ValueAllIter endValueAll() { return ValueAllIter(VMASK_.endDense(), this); } ChildOnCIter cbeginChildOn() const { return ChildOnCIter(VMASK_.endOn(), this); } ChildOnCIter beginChildOn() const { return ChildOnCIter(VMASK_.endOn(), this); } ChildOnIter beginChildOn() { return ChildOnIter(VMASK_.endOn(), this); } ChildOffCIter cbeginChildOff() const { return ChildOffCIter(VMASK_.endOff(), this); } ChildOffCIter beginChildOff() const { return ChildOffCIter(VMASK_.endOff(), this); } ChildOffIter beginChildOff() { return ChildOffIter(VMASK_.endOff(), this); } ChildAllCIter cbeginChildAll() const { return ChildAllCIter(VMASK_.beginDense(), this); } ChildAllCIter beginChildAll() const { return ChildAllCIter(VMASK_.beginDense(), this); } ChildAllIter beginChildAll() { return ChildAllIter(VMASK_.beginDense(), this); } ChildOnCIter cendChildOn() const { return ChildOnCIter(VMASK_.endOn(), this); } ChildOnCIter endChildOn() const { return ChildOnCIter(VMASK_.endOn(), this); } ChildOnIter endChildOn() { return ChildOnIter(VMASK_.endOn(), this); } ChildOffCIter cendChildOff() const { return ChildOffCIter(VMASK_.endOff(), this); } ChildOffCIter endChildOff() const { return ChildOffCIter(VMASK_.endOff(), this); } ChildOffIter endChildOff() { return ChildOffIter(VMASK_.endOff(), this); } ChildAllCIter cendChildAll() const { return ChildAllCIter(VMASK_.endDense(), this); } ChildAllCIter endChildAll() const { return ChildAllCIter(VMASK_.endDense(), this); } ChildAllIter endChildAll() { return ChildAllIter(VMASK_.endDense(), this); } #undef VMASK_ }; // struct PointIndexLeafNode template inline bool PointIndexLeafNode::getIndices(const Coord& ijk, const ValueType*& begin, const ValueType*& end) const { return getIndices(LeafNodeType::coordToOffset(ijk), begin, end); } template inline bool PointIndexLeafNode::getIndices(Index offset, const ValueType*& begin, const ValueType*& end) const { if (this->isValueMaskOn(offset)) { const ValueType* dataPtr = &mIndices.front(); begin = dataPtr + (offset == 0 ? ValueType(0) : this->buffer()[offset - 1]); end = dataPtr + this->buffer()[offset]; return true; } return false; } template inline void PointIndexLeafNode::setOffsetOn(Index offset, const ValueType& val) { this->buffer().setValue(offset, val); this->setValueMaskOn(offset); } template inline void PointIndexLeafNode::setOffsetOnly(Index offset, const ValueType& val) { this->buffer().setValue(offset, val); } template inline bool PointIndexLeafNode::isEmpty(const CoordBBox& bbox) const { Index xPos, pos, zStride = Index(bbox.max()[2] - bbox.min()[2]); Coord ijk; for (ijk[0] = bbox.min()[0]; ijk[0] <= bbox.max()[0]; ++ijk[0]) { xPos = (ijk[0] & (DIM - 1u)) << (2 * LOG2DIM); for (ijk[1] = bbox.min()[1]; ijk[1] <= bbox.max()[1]; ++ijk[1]) { pos = xPos + ((ijk[1] & (DIM - 1u)) << LOG2DIM); pos += (bbox.min()[2] & (DIM - 1u)); if (this->buffer()[pos+zStride] > (pos == 0 ? T(0) : this->buffer()[pos - 1])) { return false; } } } return true; } template inline void PointIndexLeafNode::readBuffers(std::istream& is, bool fromHalf) { BaseLeaf::readBuffers(is, fromHalf); Index64 numIndices = Index64(0); is.read(reinterpret_cast(&numIndices), sizeof(Index64)); mIndices.resize(size_t(numIndices)); is.read(reinterpret_cast(mIndices.data()), numIndices * sizeof(T)); } template inline void PointIndexLeafNode::readBuffers(std::istream& is, const CoordBBox& bbox, bool fromHalf) { // Read and clip voxel values. BaseLeaf::readBuffers(is, bbox, fromHalf); Index64 numIndices = Index64(0); is.read(reinterpret_cast(&numIndices), sizeof(Index64)); const Index64 numBytes = numIndices * sizeof(T); if (bbox.hasOverlap(this->getNodeBoundingBox())) { mIndices.resize(size_t(numIndices)); is.read(reinterpret_cast(mIndices.data()), numBytes); /// @todo If any voxels were deactivated as a result of clipping in the call to /// BaseLeaf::readBuffers(), the point index list will need to be regenerated. } else { // Read and discard voxel values. boost::scoped_array buf(new char[numBytes]); is.read(buf.get(), numBytes); } // Reserved for future use Index64 auxDataBytes = Index64(0); is.read(reinterpret_cast(&auxDataBytes), sizeof(Index64)); if (auxDataBytes > 0) { // For now, read and discard any auxiliary data. boost::scoped_array auxData(new char[auxDataBytes]); is.read(auxData.get(), auxDataBytes); } } template inline void PointIndexLeafNode::writeBuffers(std::ostream& os, bool toHalf) const { BaseLeaf::writeBuffers(os, toHalf); Index64 numIndices = Index64(mIndices.size()); os.write(reinterpret_cast(&numIndices), sizeof(Index64)); os.write(reinterpret_cast(mIndices.data()), numIndices * sizeof(T)); // Reserved for future use const Index64 auxDataBytes = Index64(0); os.write(reinterpret_cast(&auxDataBytes), sizeof(Index64)); } template inline Index64 PointIndexLeafNode::memUsage() const { return BaseLeaf::memUsage() + Index64((sizeof(T)*mIndices.capacity()) + sizeof(mIndices)); } } // namespace tools //////////////////////////////////////// namespace tree { /// Helper metafunction used to implement LeafNode::SameConfiguration /// (which, as an inner class, can't be independently specialized) template struct SameLeafConfig > { static const bool value = true; }; } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_POINT_INDEX_GRID_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Clip.h0000644000000000000000000003301512603226506013415 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 Clip.h /// /// @brief Functions to clip a grid against a bounding box or against /// another grid's active voxel topology #ifndef OPENVDB_TOOLS_CLIP_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_CLIP_HAS_BEEN_INCLUDED #include #include // for isNegative #include #include "GridTransformer.h" // for resampleToMatch() #include #include #include #include #include #include "Prune.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Clip the given grid against a world-space bounding box /// and return a new grid containing the result. /// @warning Clipping a level set will likely produce a grid that is /// no longer a valid level set. template OPENVDB_STATIC_SPECIALIZATION inline typename GridType::Ptr clip(const GridType& grid, const BBoxd&); /// @brief Clip a grid against the active voxels of another grid /// and return a new grid containing the result. /// @param grid the grid to be clipped /// @param mask a grid whose active voxels form a boolean clipping mask /// @details The mask grid need not have the same transform as the source grid. /// Also, if the mask grid is a level set, consider using tools::sdfInteriorMask /// to construct a new mask comprising the interior (rather than the narrow band) /// of the level set. /// @warning Clipping a level set will likely produce a grid that is /// no longer a valid level set. template OPENVDB_STATIC_SPECIALIZATION inline typename GridType::Ptr clip(const GridType& grid, const Grid& mask); //////////////////////////////////////// namespace clip_internal { //////////////////////////////////////// template class MaskInteriorVoxels { public: typedef typename TreeT::ValueType ValueT; typedef typename TreeT::LeafNodeType LeafNodeT; MaskInteriorVoxels(const TreeT& tree): mAcc(tree) {} template void operator()(LeafNodeType &leaf, size_t /*leafIndex*/) const { const LeafNodeT *refLeaf = mAcc.probeConstLeaf(leaf.origin()); if (refLeaf) { typename LeafNodeType::ValueOffIter iter = leaf.beginValueOff(); for ( ; iter; ++iter) { const Index pos = iter.pos(); leaf.setActiveState(pos, math::isNegative(refLeaf->getValue(pos))); } } } private: tree::ValueAccessor mAcc; }; //////////////////////////////////////// template class CopyLeafNodes { public: typedef typename TreeT::template ValueConverter::Type BoolTreeT; typedef tree::LeafManager BoolLeafManagerT; CopyLeafNodes(const TreeT& tree, const BoolLeafManagerT& leafNodes); void run(bool threaded = true); typename TreeT::Ptr tree() const { return mNewTree; } CopyLeafNodes(CopyLeafNodes&, tbb::split); void operator()(const tbb::blocked_range&); void join(const CopyLeafNodes& rhs) { mNewTree->merge(*rhs.mNewTree); } private: const BoolTreeT* mClipMask; const TreeT* mTree; const BoolLeafManagerT* mLeafNodes; typename TreeT::Ptr mNewTree; }; template CopyLeafNodes::CopyLeafNodes(const TreeT& tree, const BoolLeafManagerT& leafNodes) : mTree(&tree) , mLeafNodes(&leafNodes) , mNewTree(new TreeT(mTree->background())) { } template CopyLeafNodes::CopyLeafNodes(CopyLeafNodes& rhs, tbb::split) : mTree(rhs.mTree) , mLeafNodes(rhs.mLeafNodes) , mNewTree(new TreeT(mTree->background())) { } template void CopyLeafNodes::run(bool threaded) { if (threaded) tbb::parallel_reduce(mLeafNodes->getRange(), *this); else (*this)(mLeafNodes->getRange()); } template void CopyLeafNodes::operator()(const tbb::blocked_range& range) { typedef typename TreeT::LeafNodeType LeafT; typedef typename BoolTree::LeafNodeType BoolLeafT; typename BoolLeafT::ValueOnCIter it; tree::ValueAccessor acc(*mNewTree); tree::ValueAccessor refAcc(*mTree); for (size_t n = range.begin(); n != range.end(); ++n) { const BoolLeafT& maskLeaf = mLeafNodes->leaf(n); const Coord& ijk = maskLeaf.origin(); const LeafT* refLeaf = refAcc.probeConstLeaf(ijk); LeafT* newLeaf = acc.touchLeaf(ijk); if (refLeaf) { for (it = maskLeaf.cbeginValueOn(); it; ++it) { const Index pos = it.pos(); newLeaf->setValueOnly(pos, refLeaf->getValue(pos)); newLeaf->setActiveState(pos, refLeaf->isValueOn(pos)); } } else { typename TreeT::ValueType value; bool isActive = refAcc.probeValue(ijk, value); for (it = maskLeaf.cbeginValueOn(); it; ++it) { const Index pos = it.pos(); newLeaf->setValueOnly(pos, value); newLeaf->setActiveState(pos, isActive); } } } } //////////////////////////////////////// struct BoolSampler { static const char* name() { return "bin"; } static int radius() { return 2; } static bool mipmap() { return false; } static bool consistent() { return true; } template static bool sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result) { Coord ijk; ijk[0] = int(std::floor(inCoord[0])); ijk[1] = int(std::floor(inCoord[1])); ijk[2] = int(std::floor(inCoord[2])); return inTree.probeValue(ijk, result); } }; //////////////////////////////////////// // Convert a grid of one type to a grid of another type template struct ConvertGrid { typedef typename FromGridT::Ptr FromGridPtrT; typedef typename ToGridT::Ptr ToGridPtrT; ToGridPtrT operator()(const FromGridPtrT& grid) { return ToGridPtrT(new ToGridT(*grid)); } }; // Partial specialization that avoids copying when // the input and output grid types are the same template struct ConvertGrid { typedef typename GridT::Ptr GridPtrT; GridPtrT operator()(const GridPtrT& grid) { return grid; } }; //////////////////////////////////////// // Convert a grid of arbitrary type to a boolean mask grid and return a pointer to the new grid. template inline typename boost::disable_if, typename GridT::template ValueConverter::Type::Ptr>::type convertToBoolMaskGrid(const GridT& grid) { typedef typename GridT::template ValueConverter::Type BoolGridT; typedef typename BoolGridT::Ptr BoolGridPtrT; // Convert the input grid to a boolean mask grid (with the same tree configuration). BoolGridPtrT mask = BoolGridT::create(/*background=*/false); mask->topologyUnion(grid); mask->setTransform(grid.constTransform().copy()); return mask; } // Overload that avoids any processing if the input grid is already a boolean grid template inline typename boost::enable_if, typename GridT::Ptr>::type convertToBoolMaskGrid(const GridT& grid) { return grid.copy(); // shallow copy } //////////////////////////////////////// template inline typename GridType::Ptr doClip(const GridType& grid, const typename GridType::template ValueConverter::Type& aMask) { typedef typename GridType::TreeType TreeT; typedef typename GridType::TreeType::template ValueConverter::Type BoolTreeT; const GridClass gridClass = grid.getGridClass(); const TreeT& tree = grid.tree(); BoolTreeT mask(false); mask.topologyUnion(tree); if (gridClass == GRID_LEVEL_SET) { tree::LeafManager leafNodes(mask); leafNodes.foreach(MaskInteriorVoxels(tree)); tree::ValueAccessor acc(tree); typename BoolTreeT::ValueAllIter iter(mask); iter.setMaxDepth(BoolTreeT::ValueAllIter::LEAF_DEPTH - 1); for ( ; iter; ++iter) { iter.setActiveState(math::isNegative(acc.getValue(iter.getCoord()))); } } mask.topologyIntersection(aMask.constTree()); typename GridType::Ptr outGrid; { // Copy voxel values and states. tree::LeafManager leafNodes(mask); CopyLeafNodes maskOp(tree, leafNodes); maskOp.run(); outGrid = GridType::create(maskOp.tree()); } { // Copy tile values and states. tree::ValueAccessor refAcc(tree); tree::ValueAccessor maskAcc(mask); typename TreeT::ValueAllIter it(outGrid->tree()); it.setMaxDepth(TreeT::ValueAllIter::LEAF_DEPTH - 1); for ( ; it; ++it) { Coord ijk = it.getCoord(); if (maskAcc.isValueOn(ijk)) { typename TreeT::ValueType value; bool isActive = refAcc.probeValue(ijk, value); it.setValue(value); if (!isActive) it.setValueOff(); } } } outGrid->setTransform(grid.transform().copy()); if (gridClass != GRID_LEVEL_SET) outGrid->setGridClass(gridClass); return outGrid; } } // namespace clip_internal //////////////////////////////////////// template OPENVDB_STATIC_SPECIALIZATION inline typename GridType::Ptr clip(const GridType& grid, const BBoxd& bbox) { typedef typename GridType::template ValueConverter::Type BoolGridT; // Transform the world-space bounding box into the source grid's index space. Vec3d idxMin, idxMax; math::calculateBounds(grid.constTransform(), bbox.min(), bbox.max(), idxMin, idxMax); CoordBBox region(Coord::floor(idxMin), Coord::floor(idxMax)); // Construct a boolean mask grid that is true inside the index-space bounding box // and false everywhere else. BoolGridT clipMask(/*background=*/false); clipMask.fill(region, /*value=*/true, /*active=*/true); return clip_internal::doClip(grid, clipMask); } template OPENVDB_STATIC_SPECIALIZATION inline typename GridType::Ptr clip(const GridType& grid, const Grid& maskGrid) { typedef typename GridType::template ValueConverter::Type BoolGridT; typedef typename BoolGridT::Ptr BoolGridPtrT; typedef Grid MaskGridType; typedef typename MaskGridType::template ValueConverter::Type BoolMaskGridT; typedef typename BoolMaskGridT::Ptr BoolMaskGridPtrT; // Convert the mask grid to a boolean grid with the same tree configuration. BoolMaskGridPtrT boolMaskGrid = clip_internal::convertToBoolMaskGrid(maskGrid); // Resample the boolean mask grid into the source grid's index space. if (grid.constTransform() != boolMaskGrid->constTransform()) { BoolMaskGridPtrT resampledMask = BoolMaskGridT::create(/*background=*/false); resampledMask->setTransform(grid.constTransform().copy()); tools::resampleToMatch(*boolMaskGrid, *resampledMask); tools::prune(resampledMask->tree()); boolMaskGrid = resampledMask; } // Convert the bool mask grid to a bool grid of the same configuration as the source grid. BoolGridPtrT clipMask = clip_internal::ConvertGrid()(boolMaskGrid); // Clip the source grid against the boolean mask grid. return clip_internal::doClip(grid, *clipMask); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_CLIP_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/VolumeAdvect.h0000644000000000000000000006225512603226506015134 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 VolumeAdvect.h /// /// @brief Sparse hyperbolic advection of volumes, e.g. a density or /// velocity (vs a level set interface). /// /// @todo Improve (i.e. reduce) padding by topology dilation. #ifndef OPENVDB_TOOLS_VOLUME_ADVECT_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_VOLUME_ADVECT_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include "Interpolation.h"// for Sampler #include "VelocityFields.h" // for VelocityIntegrator #include "Morphology.h"//for dilateVoxels #include "Prune.h"// for prune #include "Statistics.h" // for extrema namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { namespace Scheme { /// @brief Numerical advections schemes. enum SemiLagrangian { SEMI, MID, RK3, RK4, MAC, BFECC }; /// @brief Flux-limiters employed to stabalize the second-order /// advection schemes MacCormack and BFECC. enum Limiter { NO_LIMITER, CLAMP, REVERT }; } /// @brief Performs advections of an arbitrary type of volume in a /// static velocity field. The advections is performed by means /// of various derivatives of Semi-Lagrangian integration, i.e. /// backwards tracking along the hyperbolic characteristics /// followed by interpolation. /// /// @note Optionally a limiter can be combined with the higher-order /// integration schemes MacCormack and BFECC. There are two /// types of limiters (CLAMP and REVERT) that supress /// non-physical oscillations by means of either claminging or /// reverting to a first-order schemes when the function is not /// bounded by the cell values used for tri-linear interpolation. /// /// @details The supported integrations schemes: /// ================================================================ /// | Lable | Accuracy | Integration Scheme | Interpolations | /// | |Time/Space| | velocity/volume | /// ================================================================ /// | SEMI | 1/1 | Semi-Lagrangian | 1/1 | /// | MID | 2/1 | Mid-Point | 2/1 | /// | RK3 | 3/1 | 3rd Order Runge-Kutta | 3/1 | /// | RK4 | 4/1 | 4th Order Runge-Kutta | 4/1 | /// | MAC | 2/2 | MacCormack | 2/2 | /// | BFECC | 2/2 | BFECC | 3/2 | /// ================================================================ template class VolumeAdvection { public: /// @brief Constructor /// /// @param velGrid Velocity grid responsible for the (passive) advection. /// @param interrupter Optional interrupter used to prematurely end computations. /// /// @note The velocity field is assumed to be constant for the duration of the /// advection. VolumeAdvection(const VelocityGridT& velGrid, InterrupterType* interrupter = NULL) : mVelGrid(velGrid) , mInterrupter(interrupter) , mIntegrator( Scheme::SEMI ) , mLimiter( Scheme::CLAMP ) , mGrainSize( 128 ) , mSubSteps( 1 ) { math::Extrema e = extrema(velGrid.cbeginValueAll(), /*threading*/true); e.add(velGrid.background().length()); mMaxVelocity = e.max(); } virtual ~VolumeAdvection() { } /// @brief Return the spatial order of accuracy of the advection scheme /// /// @note This is the optimal order in smooth regions. In /// non-smooth regions the flux-limiter will drop the order of /// accuracy to add numerical dissipation. int spatialOrder() const { return (mIntegrator == Scheme::MAC || mIntegrator == Scheme::BFECC) ? 2 : 1; } /// @brief Return the temporal order of accuracy of the advection scheme /// /// @note This is the optimal order in smooth regions. In /// non-smooth regions the flux-limiter will drop the order of /// accuracy to add numerical dissipation. int temporalOrder() const { switch (mIntegrator) { case Scheme::SEMI: return 1; case Scheme::MID: return 2; case Scheme::RK3: return 3; case Scheme::RK4: return 4; case Scheme::BFECC:return 2; case Scheme::MAC: return 2; } return 0;//should never reach this point } /// @brief Set the integrator (see details in the table above) void setIntegrator(Scheme::SemiLagrangian integrator) { mIntegrator = integrator; } /// @brief Return the integrator (see details in the table above) Scheme::SemiLagrangian getIntegrator() const { return mIntegrator; } /// @brief Set the limiter (see details above) void setLimiter(Scheme::Limiter limiter) { mLimiter = limiter; } /// @brief Retrun the limiter (see details above) Scheme::Limiter getLimiter() const { return mLimiter; } /// @brief Return @c true if a limiter will be applied based on /// the current settings. bool isLimiterOn() const { return this->spatialOrder()>1 && mLimiter != Scheme::NO_LIMITER; } /// @return the grain-size used for multi-threading /// @note A grainsize of 0 implies serial execution size_t getGrainSize() const { return mGrainSize; } /// @brief Set the grain-size used for multi-threading /// @note A grainsize of 0 disables multi-threading /// @warning A small grainsize can degrade performance, /// both in terms of time and memory footprint! void setGrainSize(size_t grainsize) { mGrainSize = grainsize; } /// @return the number of sub-steps per integration (always larger /// than or equal to 1). int getSubSteps() const { return mSubSteps; } /// @brief Set the number of sub-steps per integration. /// @note The only reason to increase the sub-step above its /// default value of one is to reduce the memory footprint /// due to significant dilation. Values smaller than 1 will /// be clamped to 1! void setSubSteps(int substeps) { mSubSteps = math::Max(1, substeps); } /// @brief Return the maximum magnitude of the velocity in the /// advection velocity field defined during construction. double getMaxVelocity() const { return mMaxVelocity; } /// @return Returns the maximum distance in voxel units of @a inGrid /// that a particle can travel in the time-step @a dt when advected /// in the velocity field defined during construction. /// /// @details This method is useful when dilating sparse volume /// grids to pad boundary regions. Excessive dilation can be /// computationally expensive so use this method to prevent /// or warn against run-away computation. /// /// @throw RuntimeError if @a inGrid does not have uniform voxels. template int getMaxDistance(const VolumeGridT& inGrid, double dt) const { if (!inGrid.hasUniformVoxels()) { OPENVDB_THROW(RuntimeError, "Volume grid does not have uniform voxels!"); } const double d = mMaxVelocity*math::Abs(dt)/inGrid.voxelSize()[0]; return static_cast( math::RoundUp(d) ); } /// @return Returns a new grid that is the result of passive advection /// of all the active values the input grid by @a timeStep. /// /// @param inGrid The input grid to be advected (unmodified) /// @param timeStep Time-step of the Runge-Kutta integrator. /// /// @details This method will advect all of the active values in /// the input @a inGrid. To achieve this a /// deep-copy is dilated to account for the material /// transport. This dilation step can be slow for large /// time steps @a dt or a velocity field with large magnitudes. /// /// @warning If the VolumeSamplerT is of higher order than one /// (i.e. tri-linear interpolation) instabilities are /// known to occure. To suppress those monotonicity /// constrains or flux-limiters need to be applies. /// /// @throw RuntimeError if @a inGrid does not have uniform voxels. template//only C++11 allows for a default argument typename VolumeGridT::Ptr advect(const VolumeGridT& inGrid, double timeStep) { typename VolumeGridT::Ptr outGrid = inGrid.deepCopy(); const double dt = timeStep/mSubSteps; const int n = this->getMaxDistance(inGrid, dt); dilateVoxels( outGrid->tree(), n ); this->template cook(*outGrid, inGrid, dt); for (int step = 1; step < mSubSteps; ++step) { typename VolumeGridT::Ptr tmpGrid = outGrid->deepCopy(); dilateVoxels( tmpGrid->tree(), n ); this->template cook(*tmpGrid, *outGrid, dt); outGrid.swap( tmpGrid ); } return outGrid; } /// @return Returns a new grid that is the result of /// passive advection of the active values in @a inGrid /// that intersect the active values in @c mask. The time /// of the output grid is incremented by @a timeStep. /// /// @param inGrid The input grid to be advected (unmodified). /// @param mask The mask of active values defining the active voxels /// in @c inGrid on which to perform advection. Only /// if a value is active in both grids will it be modified. /// @param timeStep Time-step for a single Runge-Kutta integration step. /// /// /// @details This method will advect all of the active values in /// the input @a inGrid that intersects with the /// active values in @a mask. To achieve this a /// deep-copy is dilated to account for the material /// transport and finally cropped to the intersection /// with @a mask. The dilation step can be slow for large /// time steps @a dt or fast moving velocity fields. /// /// @warning If the VolumeSamplerT is of higher order the one /// (i.e. tri-linear interpolation) instabilities are /// known to occure. To suppress those monotonicity /// constrains or flux-limiters need to be applies. /// /// @throw RuntimeError if @a inGrid is not aligned with @a mask /// or if its voxels are not uniform. template//only C++11 allows for a default argument typename VolumeGridT::Ptr advect(const VolumeGridT& inGrid, const MaskGridT& mask, double timeStep) { if (inGrid.transform() != mask.transform()) { OPENVDB_THROW(RuntimeError, "Volume grid and mask grid are misaligned! Consider " "resampling either of the two grids into the index space of the other."); } typename VolumeGridT::Ptr outGrid = inGrid.deepCopy(); const double dt = timeStep/mSubSteps; const int n = this->getMaxDistance(inGrid, dt); dilateVoxels( outGrid->tree(), n ); outGrid->topologyIntersection( mask ); pruneInactive( outGrid->tree(), mGrainSize>0, mGrainSize ); this->template cook(*outGrid, inGrid, dt); outGrid->topologyUnion( inGrid ); for (int step = 1; step < mSubSteps; ++step) { typename VolumeGridT::Ptr tmpGrid = outGrid->deepCopy(); dilateVoxels( tmpGrid->tree(), n ); tmpGrid->topologyIntersection( mask ); pruneInactive( tmpGrid->tree(), mGrainSize>0, mGrainSize ); this->template cook(*tmpGrid, *outGrid, dt); tmpGrid->topologyUnion( inGrid ); outGrid.swap( tmpGrid ); } return outGrid; } private: // disallow copy construction and copy by assignment! VolumeAdvection(const VolumeAdvection&);// not implemented VolumeAdvection& operator=(const VolumeAdvection&);// not implemented void start(const char* str) const { if (mInterrupter) mInterrupter->start(str); } void stop() const { if (mInterrupter) mInterrupter->end(); } bool interrupt() const { if (mInterrupter && util::wasInterrupted(mInterrupter)) { tbb::task::self().cancel_group_execution(); return true; } return false; } template void cook(VolumeGridT& outGrid, const VolumeGridT& inGrid, double dt) { outGrid.tree().voxelizeActiveTiles(); switch (mIntegrator) { case Scheme::SEMI: { Advect adv(inGrid, *this); adv.cook(outGrid, dt); break; } case Scheme::MID: { Advect adv(inGrid, *this); adv.cook(outGrid, dt); break; } case Scheme::RK3: { Advect adv(inGrid, *this); adv.cook(outGrid, dt); break; } case Scheme::RK4: { Advect adv(inGrid, *this); adv.cook(outGrid, dt); break; } case Scheme::BFECC: { Advect adv(inGrid, *this); adv.cook(outGrid, dt); break; } case Scheme::MAC: { Advect adv(inGrid, *this); adv.cook(outGrid, dt); break; } default: OPENVDB_THROW(ValueError, "Spatial difference scheme not supported!"); } pruneInactive(outGrid.tree(), mGrainSize>0, mGrainSize); } // Private class that implements the multi-threaded advection template struct Advect; // Private member data of VolumeAdvection const VelocityGridT& mVelGrid; double mMaxVelocity; InterrupterType* mInterrupter; Scheme::SemiLagrangian mIntegrator; Scheme::Limiter mLimiter; size_t mGrainSize; int mSubSteps; };//end of VolumeAdvection class // Private class that implements the multi-threaded advection template template struct VolumeAdvection::Advect { typedef typename VolumeGridT::TreeType TreeT; typedef typename VolumeGridT::ConstAccessor AccT; typedef typename TreeT::ValueType ValueT; typedef typename tree::LeafManager LeafManagerT; typedef typename LeafManagerT::LeafNodeType LeafNodeT; typedef typename LeafManagerT::LeafRange LeafRangeT; typedef VelocityIntegrator VelocityIntegratorT; typedef typename VelocityIntegratorT::ElementType RealT; typedef typename TreeT::LeafNodeType::ValueOnIter VoxelIterT; Advect(const VolumeGridT& inGrid, const VolumeAdvection& parent) : mTask(0) , mInGrid(&inGrid) , mVelocityInt(parent.mVelGrid) , mParent(&parent) { } inline void cook(const LeafRangeT& range) { if (mParent->mGrainSize > 0) { tbb::parallel_for(range, *this); } else { (*this)(range); } } void operator()(const LeafRangeT& range) const { assert(mTask); mTask(const_cast(this), range); } void cook(VolumeGridT& outGrid, double time_step) { mParent->start("Advecting volume"); LeafManagerT manager(outGrid.tree(), mParent->spatialOrder()==2 ? 1 : 0); const LeafRangeT range = manager.leafRange(mParent->mGrainSize); const RealT dt = static_cast(-time_step);//method of characteristics backtracks if (mParent->mIntegrator == Scheme::MAC) { mTask = boost::bind(&Advect::rk, _1, _2, dt, 0, *mInGrid);//out[0]=forward this->cook(range); mTask = boost::bind(&Advect::rk, _1, _2,-dt, 1, outGrid);//out[1]=backward this->cook(range); mTask = boost::bind(&Advect::mac, _1, _2);//out[0] = out[0] + (in[0] - out[1])/2 this->cook(range); } else if (mParent->mIntegrator == Scheme::BFECC) { mTask = boost::bind(&Advect::rk, _1, _2, dt, 0, *mInGrid);//out[0]=forward this->cook(range); mTask = boost::bind(&Advect::rk, _1, _2,-dt, 1, outGrid);//out[1]=backward this->cook(range); mTask = boost::bind(&Advect::bfecc, _1, _2);//out[0] = (3*in[0] - out[1])/2 this->cook(range); mTask = boost::bind(&Advect::rk, _1, _2, dt, 1, outGrid);//out[1]=forward this->cook(range); manager.swapLeafBuffer(1);// out[0] = out[1] } else {// SEMI, MID, RK3 and RK4 mTask = boost::bind(&Advect::rk, _1, _2, dt, 0, *mInGrid);//forward this->cook(range); } if (mParent->spatialOrder()==2) manager.removeAuxBuffers(); mTask = boost::bind(&Advect::limiter, _1, _2, dt);// out[0] = limiter( out[0] ) this->cook(range); mParent->stop(); } // Last step of the MacCormack scheme: out[0] = out[0] + (in[0] - out[1])/2 void mac(const LeafRangeT& range) const { if (mParent->interrupt()) return; assert( mParent->mIntegrator == Scheme::MAC ); AccT acc = mInGrid->getAccessor(); for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) { ValueT* out0 = leafIter.buffer( 0 ).data();// forward const ValueT* out1 = leafIter.buffer( 1 ).data();// backward const LeafNodeT* leaf = acc.probeConstLeaf( leafIter->origin() ); if (leaf !=NULL) { const ValueT* in0 = leaf->buffer().data(); for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { const Index i = voxelIter.pos(); out0[i] += RealT(0.5) * ( in0[i] - out1[i] ); } } else { for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { const Index i = voxelIter.pos(); out0[i] += RealT(0.5) * ( acc.getValue(voxelIter.getCoord()) - out1[i] ); }//loop over active voxels } }//loop over leaf nodes } // Intermediate step in the BFECC scheme: out[0] = (3*in[0] - out[1])/2 void bfecc(const LeafRangeT& range) const { if (mParent->interrupt()) return; assert( mParent->mIntegrator == Scheme::BFECC ); AccT acc = mInGrid->getAccessor(); for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) { ValueT* out0 = leafIter.buffer( 0 ).data();// forward const ValueT* out1 = leafIter.buffer( 1 ).data();// backward const LeafNodeT* leaf = acc.probeConstLeaf(leafIter->origin()); if (leaf !=NULL) { const ValueT* in0 = leaf->buffer().data(); for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { const Index i = voxelIter.pos(); out0[i] = RealT(0.5)*( RealT(3)*in0[i] - out1[i] ); }//loop over active voxels } else { for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { const Index i = voxelIter.pos(); out0[i] = RealT(0.5)*( RealT(3)*acc.getValue(voxelIter.getCoord()) - out1[i] ); }//loop over active voxels } }//loop over leaf nodes } // Semi-Lagrangian integration with Runge-Kutta of various orders (1->4) void rk(const LeafRangeT& range, RealT dt, size_t n, const VolumeGridT& grid) const { if (mParent->interrupt()) return; const math::Transform& xform = mInGrid->transform(); AccT acc = grid.getAccessor(); for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) { ValueT* phi = leafIter.buffer( n ).data(); for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { ValueT& value = phi[voxelIter.pos()]; Vec3d wPos = xform.indexToWorld(voxelIter.getCoord()); mVelocityInt.template rungeKutta(dt, wPos); value = SamplerT::sample(acc, xform.worldToIndex(wPos)); }//loop over active voxels }//loop over leaf nodes } void limiter(const LeafRangeT& range, RealT dt) const { if (mParent->interrupt()) return; const bool doLimiter = mParent->isLimiterOn(); const bool doClamp = mParent->mLimiter == Scheme::CLAMP; ValueT data[2][2][2], vMin, vMax; const math::Transform& xform = mInGrid->transform(); AccT acc = mInGrid->getAccessor(); const ValueT backg = mInGrid->background(); for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) { ValueT* phi = leafIter.buffer( 0 ).data(); for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { ValueT& value = phi[voxelIter.pos()]; if ( doLimiter ) { assert(OrderRK == 1); Vec3d wPos = xform.indexToWorld(voxelIter.getCoord()); mVelocityInt.template rungeKutta<1, Vec3d>(dt, wPos);// Explicit Euler Vec3d iPos = xform.worldToIndex(wPos); Coord ijk = Coord::floor( iPos ); BoxSampler::getValues(data, acc, ijk); BoxSampler::extrema(data, vMin, vMax); if ( doClamp ) { value = math::Clamp( value, vMin, vMax); } else if (value < vMin || value > vMax ) { iPos -= Vec3R(ijk[0], ijk[1], ijk[2]);//unit coordinates value = BoxSampler::trilinearInterpolation( data, iPos ); } } if (math::isApproxEqual(value, backg, math::Delta::value())) { value = backg; leafIter->setValueOff( voxelIter.pos() ); } }//loop over active voxels }//loop over leaf nodes } // Public member data of the private Advect class typename boost::function mTask; const VolumeGridT* mInGrid; const VelocityIntegratorT mVelocityInt;// lightweight! const VolumeAdvection* mParent; };// end of private member class Advect } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_VOLUME_ADVECT_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetFilter.h0000644000000000000000000005016112603226506015420 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 proper 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; 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) , mMinMask(0) , mMaxMask(1) , mInvertMask(false) { } /// @brief Default destructor virtual ~LevelSetFilter() {} /// @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 than @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) { Filter f(this, mask); f.meanCurvature(); } /// @brief One iteration of Laplacian flow of the level set. /// @param mask Optional alpha mask. void laplacian(const MaskType* mask = NULL) { Filter f(this, mask); f.laplacian(); } /// @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) { Filter f(this, mask); f.gaussian(width); } /// @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) { Filter f(this, mask); f.offset(offset); } /// @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) { Filter f(this, mask); f.median(width); } /// @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) { Filter f(this, mask); f.mean(width); } private: // disallow copy construction and copy by assignment! LevelSetFilter(const LevelSetFilter&);// not implemented LevelSetFilter& operator=(const LevelSetFilter&);// not implemented // Private struct that implements all the filtering. struct Filter { typedef typename TreeType::LeafNodeType LeafT; typedef typename LeafT::ValueOnIter VoxelIterT; typedef typename LeafT::ValueOnCIter VoxelCIterT; typedef typename tree::LeafManager::BufferType BufferT; typedef typename tree::LeafManager::LeafRange LeafRange; typedef typename LeafRange::Iterator LeafIterT; typedef tools::AlphaMask AlphaMaskT; Filter(LevelSetFilter* parent, const MaskType* mask) : mParent(parent), mMask(mask) {} virtual ~Filter() {} void box(int width); void median(int width); void mean(int width); void gaussian(int width); void laplacian(); void meanCurvature(); void offset(ValueType value); 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"); } void cook(bool swap) { const int n = mParent->getGrainSize(); if (n>0) { tbb::parallel_for(mParent->leafs().leafRange(n), *this); } else { (*this)(mParent->leafs().leafRange()); } if (swap) mParent->leafs().swapLeafBuffer(1, n==0); } template struct Avg { Avg(const GridT& grid, Int32 w) : acc(grid.tree()), width(w), frac(1/ValueType(2*w+1)) {} inline 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; }; template void box( const LeafRange& r, Int32 w); void boxX(const LeafRange& r, Int32 w) { this->box >(r,w); } void boxZ(const LeafRange& r, Int32 w) { this->box >(r,w); } void boxY(const LeafRange& r, Int32 w) { this->box >(r,w); } void median(const LeafRange&, int); void meanCurvature(const LeafRange&); void laplacian(const LeafRange&); void offset(const LeafRange&, ValueType); LevelSetFilter* mParent; const MaskType* mMask; typename boost::function mTask; }; // end of private Filter struct AlphaType mMinMask, mMaxMask; bool mInvertMask; }; // end of LevelSetFilter class //////////////////////////////////////// template inline void LevelSetFilter:: Filter::median(int width) { mParent->startInterrupter("Median-value flow of level set"); mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0); mTask = boost::bind(&Filter::median, _1, _2, std::max(1, width)); this->cook(true); mParent->track(); mParent->endInterrupter(); } template inline void LevelSetFilter:: Filter::mean(int width) { mParent->startInterrupter("Mean-value flow of level set"); this->box(width); mParent->endInterrupter(); } template inline void LevelSetFilter:: Filter::gaussian(int width) { mParent->startInterrupter("Gaussian flow of level set"); for (int n=0; n<4; ++n) this->box(width); mParent->endInterrupter(); } template inline void LevelSetFilter:: Filter::box(int width) { mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0); width = std::max(1, width); mTask = boost::bind(&Filter::boxX, _1, _2, width); this->cook(true); mTask = boost::bind(&Filter::boxY, _1, _2, width); this->cook(true); mTask = boost::bind(&Filter::boxZ, _1, _2, width); this->cook(true); mParent->track(); } template inline void LevelSetFilter:: Filter::meanCurvature() { mParent->startInterrupter("Mean-curvature flow of level set"); mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0); mTask = boost::bind(&Filter::meanCurvature, _1, _2); this->cook(true); mParent->track(); mParent->endInterrupter(); } template inline void LevelSetFilter:: Filter::laplacian() { mParent->startInterrupter("Laplacian flow of level set"); mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0); mTask = boost::bind(&Filter::laplacian, _1, _2); this->cook(true); mParent->track(); mParent->endInterrupter(); } template inline void LevelSetFilter:: Filter::offset(ValueType value) { mParent->startInterrupter("Offsetting level set"); mParent->leafs().removeAuxBuffers();// no auxiliary buffers required const ValueType CFL = ValueType(0.5) * mParent->voxelSize(), offset = openvdb::math::Abs(value); ValueType dist = 0.0; while (offset-dist > ValueType(0.001)*CFL && mParent->checkInterrupter()) { const ValueType delta = openvdb::math::Min(offset-dist, CFL); dist += delta; mTask = boost::bind(&Filter::offset, _1, _2, copysign(delta, value)); this->cook(false); mParent->track(); } mParent->endInterrupter(); } ///////////////////////// PRIVATE METHODS ////////////////////// /// Performs parabolic mean-curvature diffusion template inline void LevelSetFilter:: Filter::meanCurvature(const LeafRange& range) { mParent->checkInterrupter(); //const float CFL = 0.9f, dt = CFL * mDx * mDx / 6.0f; const ValueType dx = mParent->voxelSize(), dt = math::Pow2(dx) / ValueType(3.0); math::CurvatureStencil stencil(mParent->grid(), dx); if (mMask) { typename AlphaMaskT::FloatType a, b; AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(), mParent->maxMask(), mParent->isMaskInverted()); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { ValueType* buffer = leafIter.buffer(1).data(); 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[iter.pos()] = b * phi0 + a * phi1; } } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { ValueType* buffer = leafIter.buffer(1).data(); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { stencil.moveTo(iter); buffer[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:: Filter::laplacian(const LeafRange& range) { mParent->checkInterrupter(); //const float CFL = 0.9f, half_dt = CFL * mDx * mDx / 12.0f; const ValueType dx = mParent->voxelSize(), dt = math::Pow2(dx) / ValueType(6.0); math::GradStencil stencil(mParent->grid(), dx); if (mMask) { typename AlphaMaskT::FloatType a, b; AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(), mParent->maxMask(), mParent->isMaskInverted()); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { ValueType* buffer = leafIter.buffer(1).data(); 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[iter.pos()] = b * phi0 + a * phi1; } } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { ValueType* buffer = leafIter.buffer(1).data(); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { stencil.moveTo(iter); buffer[iter.pos()] = *iter + dt*stencil.laplacian(); } } } } /// Offsets the values by a constant template inline void LevelSetFilter:: Filter::offset(const LeafRange& range, ValueType offset) { mParent->checkInterrupter(); if (mMask) { typename AlphaMaskT::FloatType a, b; AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(), mParent->maxMask(), mParent->isMaskInverted()); 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:: Filter::median(const LeafRange& range, int width) { mParent->checkInterrupter(); typename math::DenseStencil stencil(mParent->grid(), width);//creates local cache! if (mMask) { typename AlphaMaskT::FloatType a, b; AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(), mParent->maxMask(), mParent->isMaskInverted()); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { ValueType* buffer = leafIter.buffer(1).data(); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { if (alpha(iter.getCoord(), a, b)) { stencil.moveTo(iter); buffer[iter.pos()] = b * (*iter) + a * stencil.median(); } } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { ValueType* buffer = leafIter.buffer(1).data(); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { stencil.moveTo(iter); buffer[iter.pos()] = stencil.median(); } } } } /// One dimensional convolution of a separable box filter template template inline void LevelSetFilter:: Filter::box(const LeafRange& range, Int32 w) { mParent->checkInterrupter(); AvgT avg(mParent->grid(), w); if (mMask) { typename AlphaMaskT::FloatType a, b; AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(), mParent->maxMask(), mParent->isMaskInverted()); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { ValueType* buffer = leafIter.buffer(1).data(); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { const Coord xyz = iter.getCoord(); if (alpha(xyz, a, b)) buffer[iter.pos()] = b * (*iter)+ a * avg(xyz); } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { ValueType* buffer = leafIter.buffer(1).data(); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { buffer[iter.pos()] = avg(iter.getCoord()); } } } } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/DenseSparseTools.h0000644000000000000000000012741112603226506015767 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 resolution 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 candidate 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 candidate 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 bounding 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 pattern. // 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 supplied mask 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 candidate 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 bounding 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 iteration 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 iteration space is empty if (mBBox.empty()) return; const openvdb::math::Coord start = mBBox.getStart(); const openvdb::math::Coord end = mBBox.getEnd(); // The iteration 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 a 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); // Composite 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 regions that are represented 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*/) { // Optimal 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); // Iterate 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Prune.h0000644000000000000000000003535012603226506013623 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 Prune.h /// /// @brief Defined various multi-threaded utility functions for trees /// /// @author Ken Museth #ifndef OPENVDB_TOOLS_PRUNE_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_PRUNE_HAS_BEEN_INCLUDED #include #include #include #include // for isNegative and negative #include // for Index typedef #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Reduce the memory footprint of a @a tree by replacing with tiles /// any nodes whose values are all the same (optionally to within a tolerance) /// and have the same active state. /// /// @note For trees with floating-point values a child node with (approximately) /// constant values are replaced with a tile value corresponding to the average /// of the extrema values in said child node. Else the first value encountered /// in the child node is used. /// /// @param tree the tree to be pruned /// @param tolerance tolerance within which values are considered to be equal /// @param threaded enable or disable threading (threading is enabled by default) /// @param grainSize used to control the threading granularity (default is 1) template inline void prune(TreeT& tree, typename TreeT::ValueType tolerance = zeroVal(), bool threaded = true, size_t grainSize = 1); /// @brief Reduce the memory footprint of a @a 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. /// /// @param tree the tree to be pruned /// @param tolerance tolerance within which values are considered to be equal /// @param threaded enable or disable threading (threading is enabled by default) /// @param grainSize used to control the threading granularity (default is 1) template inline void pruneTiles(TreeT& tree, typename TreeT::ValueType tolerance = zeroVal(), bool threaded = true, size_t grainSize = 1); /// @brief Reduce the memory footprint of a @a tree by replacing with /// background tiles any nodes whose values are all inactive. /// /// @param tree the tree to be pruned /// @param threaded enable or disable threading (threading is enabled by default) /// @param grainSize used to control the threading granularity (default is 1) template inline void pruneInactive(TreeT& tree, bool threaded = true, size_t grainSize = 1); /// @brief Reduce the memory footprint of a @a tree by replacing any nodes /// whose values are all inactive with tiles of the given @a value. /// /// @param tree the tree to be pruned /// @param value value assigned to inactive tiles created during pruning /// @param threaded enable or disable threading (threading is enabled by default) /// @param grainSize used to control the threading granularity (default is 1) template inline void pruneInactiveWithValue( TreeT& tree, const typename TreeT::ValueType& value, bool threaded = true, size_t grainSize = 1); /// @brief Reduce the memory footprint of a @a 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. /// /// @param tree the tree to be pruned /// @param threaded enable or disable threading (threading is enabled by default) /// @param grainSize used to control the threading granularity (default is 1) /// /// @throw ValueError if the background of the @a tree is negative (as defined by math::isNegative) template inline void pruneLevelSet(TreeT& tree, bool threaded = true, size_t grainSize = 1); /// @brief Reduce the memory footprint of a @a tree by replacing nodes whose voxel values /// are all inactive with inactive tiles having the value -| @a insideWidth | /// if the voxel values are negative and | @a outsideWidth | otherwise. /// /// @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. /// /// @param tree the tree to be pruned /// @param outsideWidth the width of the outside of the narrow band /// @param insideWidth the width of the inside of the narrow band /// @param threaded enable or disable threading (threading is enabled by default) /// @param grainSize used to control the threading granularity (default is 1) /// /// @throw ValueError if @a outsideWidth is negative or @a insideWidth is /// not negative (as defined by math::isNegative). template inline void pruneLevelSet(TreeT& tree, const typename TreeT::ValueType& outsideWidth, const typename TreeT::ValueType& insideWidth, bool threaded = true, size_t grainSize = 1); //////////////////////////////////////////////// template class InactivePruneOp { public: typedef typename TreeT::ValueType ValueT; typedef typename TreeT::RootNodeType RootT; typedef typename TreeT::LeafNodeType LeafT; BOOST_STATIC_ASSERT(RootT::LEVEL > TerminationLevel); InactivePruneOp(TreeT& tree) : mValue(tree.background()) { tree.clearAllAccessors();//clear cache of nodes that could be pruned } InactivePruneOp(TreeT& tree, const ValueT& v) : mValue(v) { tree.clearAllAccessors();//clear cache of nodes that could be pruned } // Nothing to do at the leaf node level void operator()(LeafT&) const {} // Prune the child nodes of the internal nodes template void operator()(NodeT& node) const { if (NodeT::LEVEL > TerminationLevel) { for (typename NodeT::ChildOnIter it=node.beginChildOn(); it; ++it) { if (it->isInactive()) node.addTile(it.pos(), mValue, false); } } } // Prune the child nodes of the root node void operator()(RootT& root) const { for (typename RootT::ChildOnIter it = root.beginChildOn(); it; ++it) { if (it->isInactive()) root.addTile(it.getCoord(), mValue, false); } root.eraseBackgroundTiles(); } private: const ValueT mValue; };// InactivePruneOp template class TolerancePruneOp { public: typedef typename TreeT::ValueType ValueT; typedef typename TreeT::RootNodeType RootT; typedef typename TreeT::LeafNodeType LeafT; BOOST_STATIC_ASSERT(RootT::LEVEL > TerminationLevel); TolerancePruneOp(TreeT& tree, const ValueT& t) : mTolerance(t) { tree.clearAllAccessors();//clear cache of nodes that could be pruned } // Prune the child nodes of the root node inline void operator()(RootT& root) const { ValueT value; bool state; for (typename RootT::ChildOnIter it = root.beginChildOn(); it; ++it) { if (this->isConstant(*it, value, state)) root.addTile(it.getCoord(), value, state); } root.eraseBackgroundTiles(); } // Prune the child nodes of the internal nodes template inline void operator()(NodeT& node) const { if (NodeT::LEVEL > TerminationLevel) { ValueT value; bool state; for (typename NodeT::ChildOnIter it=node.beginChildOn(); it; ++it) { if (this->isConstant(*it, value, state)) node.addTile(it.pos(), value, state); } } } // Nothing to do at the leaf node level inline void operator()(LeafT&) const {} private: // For floating-point value types set tile values to // the mean of the extrema values of the constant node template inline typename boost::enable_if, bool>::type isConstant(const NodeT& node, ValueT& value, bool& state) const { ValueT tmp; const bool test = node.isConstant(value, tmp, state, mTolerance); if (test) value = ValueT(0.5f)*(value + tmp); return test; } // For non-floating-point value types set tile values to // the first value encountered in the constant node template inline typename boost::disable_if, bool>::type isConstant(const NodeT& node, ValueT& value, bool& state) const { return node.isConstant(value, state, mTolerance); } const ValueT mTolerance; };// TolerancePruneOp template class LevelSetPruneOp { public: typedef typename TreeT::ValueType ValueT; typedef typename TreeT::RootNodeType RootT; typedef typename TreeT::LeafNodeType LeafT; BOOST_STATIC_ASSERT(RootT::LEVEL > TerminationLevel); LevelSetPruneOp(TreeT& tree) : mOutside(tree.background()) , mInside(math::negative(mOutside)) { if (math::isNegative(mOutside)) { OPENVDB_THROW(ValueError, "LevelSetPruneOp: the background value cannot be negative!"); } tree.clearAllAccessors();//clear cache of nodes that could be pruned } LevelSetPruneOp(TreeT& tree, const ValueT& outside, const ValueT& inside) : mOutside(outside) , mInside(inside) { if (math::isNegative(mOutside)) { OPENVDB_THROW(ValueError, "LevelSetPruneOp: the outside value cannot be negative!"); } if (!math::isNegative(mInside)) { OPENVDB_THROW(ValueError, "LevelSetPruneOp: the inside value must be negative!"); } tree.clearAllAccessors();//clear cache of nodes that could be pruned } // Nothing to do at the leaf node level void operator()(LeafT&) const {} // Prune the child nodes of the internal nodes template void operator()(NodeT& node) const { if (NodeT::LEVEL > TerminationLevel) { for (typename NodeT::ChildOnIter it=node.beginChildOn(); it; ++it) { if (it->isInactive()) node.addTile(it.pos(), this->getTileValue(it), false); } } } // Prune the child nodes of the root node void operator()(RootT& root) const { for (typename RootT::ChildOnIter it = root.beginChildOn(); it; ++it) { if (it->isInactive()) root.addTile(it.getCoord(), this->getTileValue(it), false); } root.eraseBackgroundTiles(); } private: template inline ValueT getTileValue(const IterT& iter) const { return math::isNegative(iter->getFirstValue()) ? mInside : mOutside; } const ValueT mOutside, mInside; };// LevelSetPruneOp template inline void prune(TreeT& tree, typename TreeT::ValueType tol, bool threaded, size_t grainSize) { tree::NodeManager nodes(tree); TolerancePruneOp op(tree, tol); nodes.processBottomUp(op, threaded, grainSize); } template inline void pruneTiles(TreeT& tree, typename TreeT::ValueType tol, bool threaded, size_t grainSize) { tree::NodeManager nodes(tree); TolerancePruneOp op(tree, tol); nodes.processBottomUp(op, threaded, grainSize); } template inline void pruneInactive(TreeT& tree, bool threaded, size_t grainSize) { tree::NodeManager nodes(tree); InactivePruneOp op(tree); nodes.processBottomUp(op, threaded, grainSize); } template inline void pruneInactiveWithValue(TreeT& tree, const typename TreeT::ValueType& v, bool threaded, size_t grainSize) { tree::NodeManager nodes(tree); InactivePruneOp op(tree, v); nodes.processBottomUp(op, threaded, grainSize); } template inline void pruneLevelSet(TreeT& tree, const typename TreeT::ValueType& outside, const typename TreeT::ValueType& inside, bool threaded, size_t grainSize) { tree::NodeManager nodes(tree); LevelSetPruneOp op(tree, outside, inside); nodes.processBottomUp(op, threaded, grainSize); } template inline void pruneLevelSet(TreeT& tree, bool threaded, size_t grainSize) { tree::NodeManager nodes(tree); LevelSetPruneOp op(tree); nodes.processBottomUp(op, threaded, grainSize); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_PRUNE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetSphere.h0000644000000000000000000002217612603226506015426 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #include "SignedFloodFill.h" 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 than /// 1.5*voxelSize, i.e. the sphere is smaller than 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 a 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 consistent signed distances outside the narrow-band tools::signedFloodFill(mGrid->tree()); 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetMorph.h0000644000000000000000000007027512603226506015270 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 ValueType; /// Main constructor LevelSetMorphing(GridT& sourceGrid, const GridT& targetGrid, InterruptT* interrupt = NULL) : mTracker(sourceGrid, interrupt) , mTarget(&targetGrid) , mMask(NULL) , mSpatialScheme(math::HJWENO5_BIAS) , mTemporalScheme(math::TVD_RK2) , mMinMask(0) , mDeltaMask(1) , mInvertMask(false) { } virtual ~LevelSetMorphing() {} /// Redefine the target level set void setTarget(const GridT& targetGrid) { mTarget = &targetGrid; } /// Define the alpha mask void setAlphaMask(const GridT& maskGrid) { mMask = &maskGrid; } /// 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 Return the minimum value of the mask to be used for the /// derivation of a smooth alpha value. ValueType minMask() const { return mMinMask; } /// @brief Return the maximum value of the mask to be used for the /// derivation of a smooth alpha value. ValueType maxMask() const { return mDeltaMask + mMinMask; } /// @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 than @a max. void setMaskRange(ValueType min, ValueType max) { if (!(min < max)) OPENVDB_THROW(ValueError, "Invalid mask range (expects min < max)"); mMinMask = min; mDeltaMask = max-min; } /// @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 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(ValueType time0, ValueType time1); private: // disallow copy construction and copy by assignment! LevelSetMorphing(const LevelSetMorphing&);// not implemented LevelSetMorphing& operator=(const LevelSetMorphing&);// not implemented template size_t advect1(ValueType time0, ValueType time1); template size_t advect2(ValueType time0, ValueType time1); template size_t advect3(ValueType time0, ValueType time1); TrackerT mTracker; const GridT *mTarget, *mMask; math::BiasedGradientScheme mSpatialScheme; math::TemporalIntegrationScheme mTemporalScheme; ValueType mMinMask, mDeltaMask; bool mInvertMask; // This templated private class implements all the level set magic. template struct Morph { /// Main constructor Morph(LevelSetMorphing& parent); /// Shallow copy constructor called by tbb::parallel_for() threads Morph(const Morph& other); /// Shallow copy constructor called by tbb::parallel_reduce() threads Morph(Morph& other, tbb::split); /// destructor virtual ~Morph() {} /// Advect the level set from its current time, time0, to its final time, time1. /// @return number of CFL iterations size_t advect(ValueType time0, ValueType 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 Morph& other) { mMaxAbsS = math::Max(mMaxAbsS, other.mMaxAbsS); } /// 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(ValueType time0, ValueType time1, Index speedBuffer); void sampleXformedSpeed(const LeafRange& r, Index speedBuffer); void sampleAlignedSpeed(const LeafRange& r, 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)]|); template void euler(const LeafRange&, ValueType, Index, Index, Index); inline void euler01(const LeafRange& r, ValueType t, Index s) {this->euler<0,1>(r,t,0,1,s);} inline void euler12(const LeafRange& r, ValueType t) {this->euler<1,2>(r, t, 1, 1, 2);} inline void euler34(const LeafRange& r, ValueType t) {this->euler<3,4>(r, t, 1, 2, 3);} inline void euler13(const LeafRange& r, ValueType t) {this->euler<1,3>(r, t, 1, 2, 3);} typedef typename boost::function FuncType; LevelSetMorphing* mParent; ValueType mMinAbsS, mMaxAbsS; const MapT* mMap; FuncType mTask; }; // end of private Morph struct };//end of LevelSetMorphing template inline size_t LevelSetMorphing::advect(ValueType time0, ValueType 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(ValueType time0, ValueType 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(ValueType time0, ValueType 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(ValueType time0, ValueType time1) { Morph tmp(*this); return tmp.advect(time0, time1); } /////////////////////////////////////////////////////////////////////// template template inline LevelSetMorphing:: Morph:: Morph(LevelSetMorphing& parent) : mParent(&parent) , mMinAbsS(ValueType(1e-6)) , mMap(parent.mTracker.grid().transform().template constMap().get()) , mTask(0) { } template template inline LevelSetMorphing:: Morph:: Morph(const Morph& other) : mParent(other.mParent) , mMinAbsS(other.mMinAbsS) , mMaxAbsS(other.mMaxAbsS) , mMap(other.mMap) , mTask(other.mTask) { } template template inline LevelSetMorphing:: Morph:: Morph(Morph& other, tbb::split) : mParent(other.mParent) , mMinAbsS(other.mMinAbsS) , mMaxAbsS(other.mMaxAbsS) , mMap(other.mMap) , mTask(other.mTask) { } template template inline size_t LevelSetMorphing:: Morph:: advect(ValueType time0, ValueType 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 && mParent->mTracker.checkInterrupter()) { mParent->mTracker.leafs().rebuildAuxBuffers(auxBuffers); const ValueType 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(&Morph::euler01, _1, _2, dt, /*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(&Morph::euler01, _1, _2, dt, /*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(&Morph::euler12, _1, _2, dt); // 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(&Morph::euler01, _1, _2, dt, /*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(&Morph::euler34, _1, _2, dt); // 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(&Morph::euler13, _1, _2, dt); // 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; mParent->mTracker.leafs().removeAuxBuffers(); // 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 LevelSetMorphing:: Morph:: sampleSpeed(ValueType time0, ValueType time1, Index speedBuffer) { mMaxAbsS = mMinAbsS; const size_t leafCount = mParent->mTracker.leafs().leafCount(); if (leafCount==0 || time0 >= time1) return ValueType(0); const math::Transform& xform = mParent->mTracker.grid().transform(); if (mParent->mTarget->transform() == xform && (mParent->mMask == NULL || mParent->mMask->transform() == xform)) { mTask = boost::bind(&Morph::sampleAlignedSpeed, _1, _2, speedBuffer); } else { mTask = boost::bind(&Morph::sampleXformedSpeed, _1, _2, speedBuffer); } this->cook(PARALLEL_REDUCE); if (math::isApproxEqual(mMinAbsS, mMaxAbsS)) return ValueType(0);//speed is essentially zero static const ValueType CFL = (TemporalScheme == math::TVD_RK1 ? ValueType(0.3) : TemporalScheme == math::TVD_RK2 ? ValueType(0.9) : ValueType(1.0))/math::Sqrt(ValueType(3.0)); const ValueType dt = math::Abs(time1 - time0), dx = mParent->mTracker.voxelSize(); return math::Min(dt, ValueType(CFL*dx/mMaxAbsS)); } template template inline void LevelSetMorphing:: Morph:: sampleXformedSpeed(const LeafRange& range, Index speedBuffer) { typedef typename LeafType::ValueOnCIter VoxelIterT; typedef tools::GridSampler SamplerT; const MapT& map = *mMap; mParent->mTracker.checkInterrupter(); typename GridT::ConstAccessor targetAcc = mParent->mTarget->getAccessor(); SamplerT target(targetAcc, mParent->mTarget->transform()); if (mParent->mMask == NULL) { for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { ValueType* speed = leafIter.buffer(speedBuffer).data(); bool isZero = true; for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { ValueType& s = speed[voxelIter.pos()]; s -= target.wsSample(map.applyMap(voxelIter.getCoord().asVec3d())); if (!math::isApproxZero(s)) isZero = false; mMaxAbsS = math::Max(mMaxAbsS, math::Abs(s)); } if (isZero) speed[0] = std::numeric_limits::max();//tag first voxel } } else { const ValueType min = mParent->mMinMask, invNorm = 1.0f/(mParent->mDeltaMask); const bool invMask = mParent->isMaskInverted(); typename GridT::ConstAccessor maskAcc = mParent->mMask->getAccessor(); SamplerT mask(maskAcc, mParent->mMask->transform()); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { ValueType* speed = leafIter.buffer(speedBuffer).data(); bool isZero = true; for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { const Vec3R xyz = map.applyMap(voxelIter.getCoord().asVec3d());//world space const ValueType a = math::SmoothUnitStep((mask.wsSample(xyz)-min)*invNorm); ValueType& s = speed[voxelIter.pos()]; s -= target.wsSample(xyz); s *= invMask ? 1 - a : a; if (!math::isApproxZero(s)) isZero = false; mMaxAbsS = math::Max(mMaxAbsS, math::Abs(s)); } if (isZero) speed[0] = std::numeric_limits::max();//tag first voxel } } } template template inline void LevelSetMorphing:: Morph:: sampleAlignedSpeed(const LeafRange& range, Index speedBuffer) { typedef typename LeafType::ValueOnCIter VoxelIterT; mParent->mTracker.checkInterrupter(); typename GridT::ConstAccessor target = mParent->mTarget->getAccessor(); if (mParent->mMask == NULL) { for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { ValueType* speed = leafIter.buffer(speedBuffer).data(); bool isZero = true; for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { ValueType& s = speed[voxelIter.pos()]; s -= target.getValue(voxelIter.getCoord()); if (!math::isApproxZero(s)) isZero = false; mMaxAbsS = math::Max(mMaxAbsS, math::Abs(s)); } if (isZero) speed[0] = std::numeric_limits::max();//tag first voxel } } else { const ValueType min = mParent->mMinMask, invNorm = 1.0f/(mParent->mDeltaMask); const bool invMask = mParent->isMaskInverted(); typename GridT::ConstAccessor mask = mParent->mMask->getAccessor(); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { ValueType* speed = leafIter.buffer(speedBuffer).data(); bool isZero = true; for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { const Coord ijk = voxelIter.getCoord();//index space const ValueType a = math::SmoothUnitStep((mask.getValue(ijk)-min)*invNorm); ValueType& s = speed[voxelIter.pos()]; s -= target.getValue(ijk); s *= invMask ? 1 - a : a; if (!math::isApproxZero(s)) isZero = false; mMaxAbsS = math::Max(mMaxAbsS, math::Abs(s)); } if (isZero) speed[0] = std::numeric_limits::max();//tag first voxel } } } template template inline void LevelSetMorphing:: Morph:: cook(ThreadingMode mode, size_t swapBuffer) { mParent->mTracker.startInterrupter("Morphing level set"); const int grainSize = mParent->mTracker.getGrainSize(); const LeafRange range = mParent->mTracker.leafs().leafRange(grainSize); if (mParent->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"); } mParent->mTracker.leafs().swapLeafBuffer(swapBuffer, grainSize == 0); mParent->mTracker.endInterrupter(); } template template template inline void LevelSetMorphing:: Morph:: euler(const LeafRange& range, ValueType dt, Index phiBuffer, Index resultBuffer, Index speedBuffer) { typedef math::BIAS_SCHEME SchemeT; typedef typename SchemeT::template ISStencil::StencilType StencilT; typedef typename LeafType::ValueOnCIter VoxelIterT; typedef math::GradientNormSqrd NumGrad; static const ValueType Alpha = ValueType(Nominator)/ValueType(Denominator); static const ValueType Beta = ValueType(1) - Alpha; mParent->mTracker.checkInterrupter(); const MapT& map = *mMap; StencilT stencil(mParent->mTracker.grid()); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { const ValueType* speed = leafIter.buffer(speedBuffer).data(); if (math::isExactlyEqual(speed[0], std::numeric_limits::max())) continue; const ValueType* phi = leafIter.buffer(phiBuffer).data(); ValueType* result = leafIter.buffer(resultBuffer).data(); for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { const Index n = voxelIter.pos(); if (math::isApproxZero(speed[n])) continue; stencil.moveTo(voxelIter); const ValueType v = stencil.getValue() - dt * speed[n] * NumGrad::result(map, stencil); result[n] = Nominator ? Alpha * phi[n] + Beta * v : v; }//loop over active voxels in the leaf of the mask }//loop over leafs of the level set } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVEL_SET_MORPH_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/MeshToVolume.h0000644000000000000000000037611412603226506015127 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 MeshToVolume.h /// /// @brief Convert polygonal meshes that consist of quads and/or triangles /// into signed or unsigned distance field volumes. /// /// @note The signed distance field conversion 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 / polygon orientation. /// /// @author Mihai Alden #ifndef OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED #include #include // for GudonovsNormSqrd #include // for closestPointOnTriangleToPoint() #include #include #include "ChangeBackground.h" #include "Prune.h" // for pruneInactive and pruneLevelSet #include "SignedFloodFill.h" // for signedFloodFillWithValues #include #include #include #include #include #include #include #include #include // const_max #include // for isfinite() #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { //////////////////////////////////////// /// @brief Mesh to volume conversion flags enum MeshToVolumeFlags { /// Switch from the default signed distance field conversion that classifies /// regions as either inside or outside the mesh boundary to a unsigned distance /// field conversion that only computes distance values. This conversion type /// does not require a closed watertight mesh. UNSIGNED_DISTANCE_FIELD = 0x1, /// Disable the cleanup step that removes voxels created by self intersecting /// portions of the mesh. DISABLE_INTERSECTING_VOXEL_REMOVAL = 0x2, /// Disable the distance renormalization step that smooths out bumps caused /// by self intersecting or overlapping portions of the mesh DISABLE_RENORMALIZATION = 0x4, /// Disable the cleanup step that removes active voxels that exceed the /// narrow band limits. (Only relevant for small limits) DISABLE_NARROW_BAND_TRIMMING = 0x8 }; /// @brief Convert polygonal meshes that consist of quads and/or triangles into /// signed or unsigned distance field volumes. /// /// @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. /// /// @interface MeshDataAdapter /// Expected interface for the MeshDataAdapter class /// @code /// struct MeshDataAdapter { /// size_t polygonCount() const; // Total number of polygons /// size_t pointCount() const; // Total number of points /// size_t vertexCount(size_t n) const; // Vertex count for polygon n /// /// // Return position pos in local grid index space for polygon n and vertex v /// void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const; /// }; /// @endcode /// /// @param mesh mesh data access class that conforms to the MeshDataAdapter /// interface /// @param transform world-to-index-space transform /// @param exteriorBandWidth exterior narrow band width in voxel units /// @param interiorBandWidth interior narrow band width in voxel units /// (set to std::numeric_limits::max() to fill object /// interior with distance values) /// @param flags optional conversion flags defined in @c MeshToVolumeFlags /// @param polygonIndexGrid optional grid output that will contain the closest-polygon /// index for each voxel in the narrow band region template inline typename GridType::Ptr meshToVolume( const MeshDataAdapter& mesh, const math::Transform& transform, float exteriorBandWidth = 3.0f, float interiorBandWidth = 3.0f, int flags = 0, typename GridType::template ValueConverter::Type * polygonIndexGrid = NULL); /// @brief Convert polygonal meshes that consist of quads and/or triangles into /// signed or unsigned distance field volumes. /// /// @param interrupter a callback to interrupt the conversion process that conforms /// to the util::NullInterrupter interface /// @param mesh mesh data access class that conforms to the MeshDataAdapter /// interface /// @param transform world-to-index-space transform /// @param exteriorBandWidth exterior narrow band width in voxel units /// @param interiorBandWidth interior narrow band width in voxel units (set this value to /// std::numeric_limits::max() to fill interior regions /// with distance values) /// @param flags optional conversion flags defined in @c MeshToVolumeFlags /// @param polygonIndexGrid optional grid output that will contain the closest-polygon /// index for each voxel in the active narrow band region template inline typename GridType::Ptr meshToVolume( Interrupter& interrupter, const MeshDataAdapter& mesh, const math::Transform& transform, float exteriorBandWidth = 3.0f, float interiorBandWidth = 3.0f, int flags = 0, typename GridType::template ValueConverter::Type * polygonIndexGrid = NULL); //////////////////////////////////////// /// @brief Contiguous quad and triangle data adapter class /// /// @details PointType and PolygonType must provide element access /// through the square brackets operator. /// @details Points are assumed to be in local grid index space. /// @details The PolygonType tuple can have either three or four components /// this property must be specified in a static member variable /// named @c size, similar to the math::Tuple class. /// @details A four component tuple can represent a quads or a triangle /// if the fourth component set to @c util::INVALID_INDEX template struct QuadAndTriangleDataAdapter { QuadAndTriangleDataAdapter(const std::vector& points, const std::vector& polygons) : mPointArray(points.empty() ? NULL : &points[0]) , mPointArraySize(points.size()) , mPolygonArray(polygons.empty() ? NULL : &polygons[0]) , mPolygonArraySize(polygons.size()) { } QuadAndTriangleDataAdapter(const PointType * pointArray, size_t pointArraySize, const PolygonType* polygonArray, size_t polygonArraySize) : mPointArray(pointArray) , mPointArraySize(pointArraySize) , mPolygonArray(polygonArray) , mPolygonArraySize(polygonArraySize) { } size_t polygonCount() const { return mPolygonArraySize; } size_t pointCount() const { return mPointArraySize; } /// @brief Vertex count for polygon @a n size_t vertexCount(size_t n) const { return (PolygonType::size == 3 || mPolygonArray[n][3] == util::INVALID_IDX) ? 3 : 4; } /// @brief Returns position @a pos in local grid index space /// for polygon @a n and vertex @a v void getIndexSpacePoint(size_t n, size_t v, Vec3d& pos) const { const PointType& p = mPointArray[mPolygonArray[n][int(v)]]; pos[0] = double(p[0]); pos[1] = double(p[1]); pos[2] = double(p[2]); } private: PointType const * const mPointArray; size_t const mPointArraySize; PolygonType const * const mPolygonArray; size_t const mPolygonArraySize; }; // struct QuadAndTriangleDataAdapter //////////////////////////////////////// // Wrapper functions for the mesh to volume 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); //////////////////////////////////////// /// @brief Return a grid of type @c GridType containing a narrow-band level set /// representation of a box. /// /// @param bbox a bounding box in world units /// @param xform world-to-index-space transform /// @param halfWidth half the width of the narrow band, in voxel units template inline typename GridType::Ptr createLevelSetBox(const math::BBox& bbox, const openvdb::math::Transform& xform, typename VecType::ValueType halfWidth = LEVEL_SET_HALF_WIDTH); //////////////////////////////////////// /// @brief Traces the exterior voxel boundary of closed objects in the input /// volume @a tree. Exterior voxels are marked with a negative sign, /// voxels with a value below @c 0.75 are left unchanged and act as /// the boundary layer. /// /// @note Does not propagate sign information into tile regions. template inline void traceExteriorBoundaries(FloatTreeT& tree); //////////////////////////////////////// /// @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 mesh_to_volume_internal { template struct TransformPoints { TransformPoints(const PointType* pointsIn, PointType* pointsOut, const math::Transform& xform) : mPointsIn(pointsIn), mPointsOut(pointsOut), mXform(&xform) { } void operator()(const tbb::blocked_range& range) const { Vec3d pos; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { const PointType& wsP = mPointsIn[n]; pos[0] = double(wsP[0]); pos[1] = double(wsP[1]); pos[2] = double(wsP[2]); pos = mXform->worldToIndex(pos); PointType& isP = mPointsOut[n]; isP[0] = typename PointType::value_type(pos[0]); isP[1] = typename PointType::value_type(pos[1]); isP[2] = typename PointType::value_type(pos[2]); } } PointType const * const mPointsIn; PointType * const mPointsOut; math::Transform const * const mXform; }; // TransformPoints template struct Tolerance { static ValueType epsilon() { return ValueType(1e-7); } static ValueType minNarrowBandWidth() { return ValueType(1.0 + 1e-6); } }; //////////////////////////////////////// template class CombineLeafNodes { public: typedef typename TreeType::template ValueConverter::Type Int32TreeType; typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename Int32TreeType::LeafNodeType Int32LeafNodeType; CombineLeafNodes(TreeType& lhsDistTree, Int32TreeType& lhsIdxTree, LeafNodeType ** rhsDistNodes, Int32LeafNodeType ** rhsIdxNodes) : mDistTree(&lhsDistTree) , mIdxTree(&lhsIdxTree) , mRhsDistNodes(rhsDistNodes) , mRhsIdxNodes(rhsIdxNodes) { } void operator()(const tbb::blocked_range& range) const { tree::ValueAccessor distAcc(*mDistTree); tree::ValueAccessor idxAcc(*mIdxTree); typedef typename LeafNodeType::ValueType DistValueType; typedef typename Int32LeafNodeType::ValueType IndexValueType; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { const Coord& origin = mRhsDistNodes[n]->origin(); LeafNodeType* lhsDistNode = distAcc.probeLeaf(origin); Int32LeafNodeType* lhsIdxNode = idxAcc.probeLeaf(origin); DistValueType* lhsDistData = lhsDistNode->buffer().data(); IndexValueType* lhsIdxData = lhsIdxNode->buffer().data(); const DistValueType* rhsDistData = mRhsDistNodes[n]->buffer().data(); const IndexValueType* rhsIdxData = mRhsIdxNodes[n]->buffer().data(); for (Index32 offset = 0; offset < LeafNodeType::SIZE; ++offset) { if (rhsIdxData[offset] != Int32(util::INVALID_IDX)) { const DistValueType& lhsValue = lhsDistData[offset]; const DistValueType& rhsValue = rhsDistData[offset]; if (rhsValue < lhsValue) { lhsDistNode->setValueOn(offset, rhsValue); lhsIdxNode->setValueOn(offset, rhsIdxData[offset]); } else if (math::isExactlyEqual(rhsValue, lhsValue)) { lhsIdxNode->setValueOn(offset, std::min(lhsIdxData[offset], rhsIdxData[offset])); } } } delete mRhsDistNodes[n]; delete mRhsIdxNodes[n]; } } private: TreeType * const mDistTree; Int32TreeType * const mIdxTree; LeafNodeType ** const mRhsDistNodes; Int32LeafNodeType ** const mRhsIdxNodes; }; // class CombineLeafNodes //////////////////////////////////////// template struct StashOriginAndStoreOffset { typedef typename TreeType::LeafNodeType LeafNodeType; StashOriginAndStoreOffset(std::vector& nodes, Coord* coordinates) : mNodes(nodes.empty() ? NULL : &nodes[0]), mCoordinates(coordinates) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { Coord& origin = const_cast(mNodes[n]->origin()); mCoordinates[n] = origin; origin[0] = static_cast(n); } } LeafNodeType ** const mNodes; Coord * const mCoordinates; }; template struct RestoreOrigin { typedef typename TreeType::LeafNodeType LeafNodeType; RestoreOrigin(std::vector& nodes, const Coord* coordinates) : mNodes(nodes.empty() ? NULL : &nodes[0]), mCoordinates(coordinates) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { Coord& origin = const_cast(mNodes[n]->origin()); origin[0] = mCoordinates[n][0]; } } LeafNodeType ** const mNodes; Coord const * const mCoordinates; }; template class ComputeNodeConnectivity { public: typedef typename TreeType::LeafNodeType LeafNodeType; ComputeNodeConnectivity(const TreeType& tree, const Coord* coordinates, size_t* offsets, size_t numNodes, const CoordBBox& bbox) : mTree(&tree) , mCoordinates(coordinates) , mOffsets(offsets) , mNumNodes(numNodes) , mBBox(bbox) { } void operator()(const tbb::blocked_range& range) const { size_t* offsetsNextX = mOffsets; size_t* offsetsPrevX = mOffsets + mNumNodes; size_t* offsetsNextY = mOffsets + mNumNodes * 2; size_t* offsetsPrevY = mOffsets + mNumNodes * 3; size_t* offsetsNextZ = mOffsets + mNumNodes * 4; size_t* offsetsPrevZ = mOffsets + mNumNodes * 5; tree::ValueAccessor acc(*mTree); Coord ijk; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { const Coord& origin = mCoordinates[n]; offsetsNextX[n] = findNeighbourNode(acc, origin, Coord(LeafNodeType::DIM, 0, 0)); offsetsPrevX[n] = findNeighbourNode(acc, origin, Coord(-LeafNodeType::DIM, 0, 0)); offsetsNextY[n] = findNeighbourNode(acc, origin, Coord(0, LeafNodeType::DIM, 0)); offsetsPrevY[n] = findNeighbourNode(acc, origin, Coord(0, -LeafNodeType::DIM, 0)); offsetsNextZ[n] = findNeighbourNode(acc, origin, Coord(0, 0, LeafNodeType::DIM)); offsetsPrevZ[n] = findNeighbourNode(acc, origin, Coord(0, 0, -LeafNodeType::DIM)); } } size_t findNeighbourNode(tree::ValueAccessor& acc, const Coord& start, const Coord& step) const { Coord ijk = start + step; CoordBBox bbox(mBBox); while (bbox.isInside(ijk)) { const LeafNodeType* node = acc.probeConstLeaf(ijk); if (node) return static_cast(node->origin()[0]); ijk += step; } return boost::integer_traits::const_max; } private: // Disallow assignment ComputeNodeConnectivity& operator=(const ComputeNodeConnectivity&); TreeType const * const mTree; Coord const * const mCoordinates; size_t * const mOffsets; const size_t mNumNodes; const CoordBBox mBBox; }; // class ComputeNodeConnectivity template struct LeafNodeConnectivityTable { enum { INVALID_OFFSET = boost::integer_traits::const_max }; typedef typename TreeType::LeafNodeType LeafNodeType; LeafNodeConnectivityTable(TreeType& tree) : mLeafNodes() , mOffsets(NULL) { mLeafNodes.reserve(tree.leafCount()); tree.getNodes(mLeafNodes); if (mLeafNodes.empty()) return; CoordBBox bbox; tree.evalLeafBoundingBox(bbox); const tbb::blocked_range range(0, mLeafNodes.size()); // stash the leafnode origin coordinate and temporarily store the // linear offset in the origin.x variable. boost::scoped_array coordinates(new Coord[mLeafNodes.size()]); tbb::parallel_for(range, StashOriginAndStoreOffset(mLeafNodes, coordinates.get())); // build the leafnode offset table mOffsets.reset(new size_t[mLeafNodes.size() * 6]); tbb::parallel_for(range, ComputeNodeConnectivity(tree, coordinates.get(), mOffsets.get(), mLeafNodes.size(), bbox)); // restore the leafnode origin coordinate tbb::parallel_for(range, RestoreOrigin(mLeafNodes, coordinates.get())); } size_t size() const { return mLeafNodes.size(); } std::vector& nodes() { return mLeafNodes; } const std::vector& nodes() const { return mLeafNodes; } const size_t* offsetsNextX() const { return mOffsets.get(); } const size_t* offsetsPrevX() const { return mOffsets.get() + mLeafNodes.size(); } const size_t* offsetsNextY() const { return mOffsets.get() + mLeafNodes.size() * 2; } const size_t* offsetsPrevY() const { return mOffsets.get() + mLeafNodes.size() * 3; } const size_t* offsetsNextZ() const { return mOffsets.get() + mLeafNodes.size() * 4; } const size_t* offsetsPrevZ() const { return mOffsets.get() + mLeafNodes.size() * 5; } private: std::vector mLeafNodes; boost::scoped_array mOffsets; }; // struct LeafNodeConnectivityTable template class SweepExteriorSign { public: enum Axis { X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2 }; typedef typename TreeType::ValueType ValueType; typedef typename TreeType::LeafNodeType LeafNodeType; typedef LeafNodeConnectivityTable ConnectivityTable; SweepExteriorSign(Axis axis, const std::vector& startNodeIndices, ConnectivityTable& connectivity) : mStartNodeIndices(startNodeIndices.empty() ? NULL : &startNodeIndices[0]) , mConnectivity(&connectivity) , mAxis(axis) { } void operator()(const tbb::blocked_range& range) const { std::vector& nodes = mConnectivity->nodes(); // Z Axis size_t idxA = 0, idxB = 1; Index step = 1; const size_t* nextOffsets = mConnectivity->offsetsNextZ(); const size_t* prevOffsets = mConnectivity->offsetsPrevZ(); if (mAxis == Y_AXIS) { idxA = 0; idxB = 2; step = LeafNodeType::DIM; nextOffsets = mConnectivity->offsetsNextY(); prevOffsets = mConnectivity->offsetsPrevY(); } else if (mAxis == X_AXIS) { idxA = 1; idxB = 2; step = LeafNodeType::DIM * LeafNodeType::DIM; nextOffsets = mConnectivity->offsetsNextX(); prevOffsets = mConnectivity->offsetsPrevX(); } Coord ijk(0, 0, 0); int& a = ijk[idxA]; int& b = ijk[idxB]; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { size_t startOffset = mStartNodeIndices[n]; size_t lastOffset = startOffset; Index pos(0); for (a = 0; a < int(LeafNodeType::DIM); ++a) { for (b = 0; b < int(LeafNodeType::DIM); ++b) { pos = LeafNodeType::coordToOffset(ijk); size_t offset = startOffset; // sweep in +axis direction until a boundary voxel is hit. while ( offset != ConnectivityTable::INVALID_OFFSET && traceVoxelLine(*nodes[offset], pos, step) ) { lastOffset = offset; offset = nextOffsets[offset]; } // find last leafnode in +axis direction offset = lastOffset; while (offset != ConnectivityTable::INVALID_OFFSET) { lastOffset = offset; offset = nextOffsets[offset]; } // sweep in -axis direction until a boundary voxel is hit. offset = lastOffset; pos += step * (LeafNodeType::DIM - 1); while ( offset != ConnectivityTable::INVALID_OFFSET && traceVoxelLine(*nodes[offset], pos, -step)) { offset = prevOffsets[offset]; } } } } } bool traceVoxelLine(LeafNodeType& node, Index pos, Index step) const { ValueType* data = node.buffer().data(); bool isOutside = true; for (Index i = 0; i < LeafNodeType::DIM; ++i) { ValueType& dist = data[pos]; if (dist < ValueType(0.0)) { isOutside = true; } else { // Boundary voxel check. (Voxel that intersects the surface) if (!(dist > ValueType(0.75))) isOutside = false; if (isOutside) dist = ValueType(-dist); } pos += step; } return isOutside; } private: size_t const * const mStartNodeIndices; ConnectivityTable * const mConnectivity; const Axis mAxis; }; // class SweepExteriorSign template inline void seedFill(LeafNodeType& node) { typedef typename LeafNodeType::ValueType ValueType; typedef std::deque Queue; ValueType* data = node.buffer().data(); // find seed points Queue seedPoints; for (Index pos = 0; pos < LeafNodeType::SIZE; ++pos) { if (data[pos] < 0.0) seedPoints.push_back(pos); } if (seedPoints.empty()) return; // clear sign information for (Queue::iterator it = seedPoints.begin(); it != seedPoints.end(); ++it) { ValueType& dist = data[*it]; dist = -dist; } // flood fill Coord ijk(0, 0, 0); Index pos(0), nextPos(0); while (!seedPoints.empty()) { pos = seedPoints.back(); seedPoints.pop_back(); ValueType& dist = data[pos]; if (!(dist < ValueType(0.0))) { dist = -dist; // flip sign ijk = LeafNodeType::offsetToLocalCoord(pos); if (ijk[0] != 0) { // i - 1, j, k nextPos = pos - LeafNodeType::DIM * LeafNodeType::DIM; if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos); } if (ijk[0] != (LeafNodeType::DIM - 1)) { // i + 1, j, k nextPos = pos + LeafNodeType::DIM * LeafNodeType::DIM; if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos); } if (ijk[1] != 0) { // i, j - 1, k nextPos = pos - LeafNodeType::DIM; if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos); } if (ijk[1] != (LeafNodeType::DIM - 1)) { // i, j + 1, k nextPos = pos + LeafNodeType::DIM; if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos); } if (ijk[2] != 0) { // i, j, k - 1 nextPos = pos - 1; if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos); } if (ijk[2] != (LeafNodeType::DIM - 1)) { // i, j, k + 1 nextPos = pos + 1; if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos); } } } } // seedFill() template inline bool scanFill(LeafNodeType& node) { bool updatedNode = false; typedef typename LeafNodeType::ValueType ValueType; ValueType* data = node.buffer().data(); Coord ijk(0, 0, 0); bool updatedSign = true; while (updatedSign) { updatedSign = false; for (Index pos = 0; pos < LeafNodeType::SIZE; ++pos) { ValueType& dist = data[pos]; if (!(dist < ValueType(0.0)) && dist > ValueType(0.75)) { ijk = LeafNodeType::offsetToLocalCoord(pos); // i, j, k - 1 if (ijk[2] != 0 && data[pos - 1] < ValueType(0.0)) { updatedSign = true; dist = ValueType(-dist); // i, j, k + 1 } else if (ijk[2] != (LeafNodeType::DIM - 1) && data[pos + 1] < ValueType(0.0)) { updatedSign = true; dist = ValueType(-dist); // i, j - 1, k } else if (ijk[1] != 0 && data[pos - LeafNodeType::DIM] < ValueType(0.0)) { updatedSign = true; dist = ValueType(-dist); // i, j + 1, k } else if (ijk[1] != (LeafNodeType::DIM - 1) && data[pos + LeafNodeType::DIM] < ValueType(0.0)) { updatedSign = true; dist = ValueType(-dist); // i - 1, j, k } else if (ijk[0] != 0 && data[pos - LeafNodeType::DIM * LeafNodeType::DIM] < ValueType(0.0)) { updatedSign = true; dist = ValueType(-dist); // i + 1, j, k } else if (ijk[0] != (LeafNodeType::DIM - 1) && data[pos + LeafNodeType::DIM * LeafNodeType::DIM] < ValueType(0.0)) { updatedSign = true; dist = ValueType(-dist); } } } // end value loop updatedNode |= updatedSign; } // end update loop return updatedNode; } // scanFill() template class SeedFillExteriorSign { public: typedef typename TreeType::ValueType ValueType; typedef typename TreeType::LeafNodeType LeafNodeType; SeedFillExteriorSign(std::vector& nodes, bool* changedNodeMask) : mNodes(nodes.empty() ? NULL : &nodes[0]) , mChangedNodeMask(changedNodeMask) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { if (mChangedNodeMask[n]) { //seedFill(*mNodes[n]); mChangedNodeMask[n] = scanFill(*mNodes[n]); } } } LeafNodeType ** const mNodes; bool * const mChangedNodeMask; }; template struct FillArray { FillArray(ValueType* array, const ValueType v) : mArray(array), mValue(v) { } void operator()(const tbb::blocked_range& range) const { const ValueType v = mValue; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { mArray[n] = v; } } ValueType * const mArray; const ValueType mValue; }; template inline void fillArray(ValueType* array, const ValueType val, const size_t length) { const size_t grainSize = length / tbb::task_scheduler_init::default_num_threads(); const tbb::blocked_range range(0, length, grainSize); tbb::parallel_for(range, FillArray(array, val), tbb::simple_partitioner()); } template class SyncVoxelMask { public: typedef typename TreeType::ValueType ValueType; typedef typename TreeType::LeafNodeType LeafNodeType; SyncVoxelMask(std::vector& nodes, const bool* changedNodeMask, bool* changedVoxelMask) : mNodes(nodes.empty() ? NULL : &nodes[0]) , mChangedNodeMask(changedNodeMask) , mChangedVoxelMask(changedVoxelMask) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { if (mChangedNodeMask[n]) { bool* mask = &mChangedVoxelMask[n * LeafNodeType::SIZE]; ValueType* data = mNodes[n]->buffer().data(); for (Index pos = 0; pos < LeafNodeType::SIZE; ++pos) { if (mask[pos]) { data[pos] = ValueType(-data[pos]); mask[pos] = false; } } } } } LeafNodeType ** const mNodes; bool const * const mChangedNodeMask; bool * const mChangedVoxelMask; }; template class SeedPoints { public: typedef typename TreeType::ValueType ValueType; typedef typename TreeType::LeafNodeType LeafNodeType; typedef LeafNodeConnectivityTable ConnectivityTable; SeedPoints(ConnectivityTable& connectivity, bool* changedNodeMask, bool* nodeMask, bool* changedVoxelMask) : mConnectivity(&connectivity) , mChangedNodeMask(changedNodeMask) , mNodeMask(nodeMask) , mChangedVoxelMask(changedVoxelMask) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { if (!mChangedNodeMask[n]) { bool changedValue = false; changedValue |= processZ(n, /*firstFace=*/true); changedValue |= processZ(n, /*firstFace=*/false); changedValue |= processY(n, /*firstFace=*/true); changedValue |= processY(n, /*firstFace=*/false); changedValue |= processX(n, /*firstFace=*/true); changedValue |= processX(n, /*firstFace=*/false); mNodeMask[n] = changedValue; } } } bool processZ(const size_t n, bool firstFace) const { const size_t offset = firstFace ? mConnectivity->offsetsPrevZ()[n] : mConnectivity->offsetsNextZ()[n]; if (offset != ConnectivityTable::INVALID_OFFSET && mChangedNodeMask[offset]) { bool* mask = &mChangedVoxelMask[n * LeafNodeType::SIZE]; const ValueType* lhsData = mConnectivity->nodes()[n]->buffer().data(); const ValueType* rhsData = mConnectivity->nodes()[offset]->buffer().data(); const Index lastOffset = LeafNodeType::DIM - 1; const Index lhsOffset = firstFace ? 0 : lastOffset, rhsOffset = firstFace ? lastOffset : 0; Index tmpPos(0), pos(0); bool changedValue = false; for (Index x = 0; x < LeafNodeType::DIM; ++x) { tmpPos = x << (2 * LeafNodeType::LOG2DIM); for (Index y = 0; y < LeafNodeType::DIM; ++y) { pos = tmpPos + (y << LeafNodeType::LOG2DIM); if (lhsData[pos + lhsOffset] > ValueType(0.75)) { if (rhsData[pos + rhsOffset] < ValueType(0.0)) { changedValue = true; mask[pos + lhsOffset] = true; } } } } return changedValue; } return false; } bool processY(const size_t n, bool firstFace) const { const size_t offset = firstFace ? mConnectivity->offsetsPrevY()[n] : mConnectivity->offsetsNextY()[n]; if (offset != ConnectivityTable::INVALID_OFFSET && mChangedNodeMask[offset]) { bool* mask = &mChangedVoxelMask[n * LeafNodeType::SIZE]; const ValueType* lhsData = mConnectivity->nodes()[n]->buffer().data(); const ValueType* rhsData = mConnectivity->nodes()[offset]->buffer().data(); const Index lastOffset = LeafNodeType::DIM * (LeafNodeType::DIM - 1); const Index lhsOffset = firstFace ? 0 : lastOffset, rhsOffset = firstFace ? lastOffset : 0; Index tmpPos(0), pos(0); bool changedValue = false; for (Index x = 0; x < LeafNodeType::DIM; ++x) { tmpPos = x << (2 * LeafNodeType::LOG2DIM); for (Index z = 0; z < LeafNodeType::DIM; ++z) { pos = tmpPos + z; if (lhsData[pos + lhsOffset] > ValueType(0.75)) { if (rhsData[pos + rhsOffset] < ValueType(0.0)) { changedValue = true; mask[pos + lhsOffset] = true; } } } } return changedValue; } return false; } bool processX(const size_t n, bool firstFace) const { const size_t offset = firstFace ? mConnectivity->offsetsPrevX()[n] : mConnectivity->offsetsNextX()[n]; if (offset != ConnectivityTable::INVALID_OFFSET && mChangedNodeMask[offset]) { bool* mask = &mChangedVoxelMask[n * LeafNodeType::SIZE]; const ValueType* lhsData = mConnectivity->nodes()[n]->buffer().data(); const ValueType* rhsData = mConnectivity->nodes()[offset]->buffer().data(); const Index lastOffset = LeafNodeType::DIM * LeafNodeType::DIM * (LeafNodeType::DIM - 1); const Index lhsOffset = firstFace ? 0 : lastOffset, rhsOffset = firstFace ? lastOffset : 0; Index tmpPos(0), pos(0); bool changedValue = false; for (Index y = 0; y < LeafNodeType::DIM; ++y) { tmpPos = y << LeafNodeType::LOG2DIM; for (Index z = 0; z < LeafNodeType::DIM; ++z) { pos = tmpPos + z; if (lhsData[pos + lhsOffset] > ValueType(0.75)) { if (rhsData[pos + rhsOffset] < ValueType(0.0)) { changedValue = true; mask[pos + lhsOffset] = true; } } } } return changedValue; } return false; } ConnectivityTable * const mConnectivity; bool * const mChangedNodeMask; bool * const mNodeMask; bool * const mChangedVoxelMask; }; //////////////////////////////////////// template struct ComputeIntersectingVoxelSign { typedef typename TreeType::ValueType ValueType; typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::template ValueConverter::Type Int32TreeType; typedef typename Int32TreeType::LeafNodeType Int32LeafNodeType; typedef std::pair, boost::shared_array > LocalData; typedef tbb::enumerable_thread_specific LocalDataTable; ComputeIntersectingVoxelSign( std::vector& distNodes, const TreeType& distTree, const Int32TreeType& indexTree, const MeshDataAdapter& mesh) : mDistNodes(distNodes.empty() ? NULL : &distNodes[0]) , mDistTree(&distTree) , mIndexTree(&indexTree) , mMesh(&mesh) , mLocalDataTable(new LocalDataTable()) { } void operator()(const tbb::blocked_range& range) const { tree::ValueAccessor distAcc(*mDistTree); tree::ValueAccessor idxAcc(*mIndexTree); ValueType nval; CoordBBox bbox; Index xPos(0), yPos(0); Coord ijk, nijk, nodeMin, nodeMax; Vec3d cp, xyz, nxyz, dir1, dir2; LocalData& localData = mLocalDataTable->local(); boost::shared_array& points = localData.first; if (!points) points.reset(new Vec3d[LeafNodeType::SIZE * 2]); boost::shared_array& mask = localData.second; if (!mask) mask.reset(new bool[LeafNodeType::SIZE]); typename LeafNodeType::ValueOnCIter it; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { LeafNodeType& node = *mDistNodes[n]; ValueType* data = node.buffer().data(); const Int32LeafNodeType* idxNode = idxAcc.probeConstLeaf(node.origin()); const Int32* idxData = idxNode->buffer().data(); nodeMin = node.origin(); nodeMax = nodeMin.offsetBy(LeafNodeType::DIM - 1); // reset computed voxel mask. memset(mask.get(), 0, sizeof(bool) * LeafNodeType::SIZE); for (it = node.cbeginValueOn(); it; ++it) { Index pos = it.pos(); ValueType& dist = data[pos]; if (dist < 0.0 || dist > 0.75) continue; ijk = node.offsetToGlobalCoord(pos); xyz[0] = double(ijk[0]); xyz[1] = double(ijk[1]); xyz[2] = double(ijk[2]); bbox.min() = Coord::maxComponent(ijk.offsetBy(-1), nodeMin); bbox.max() = Coord::minComponent(ijk.offsetBy(1), nodeMax); bool flipSign = false; for (nijk[0] = bbox.min()[0]; nijk[0] <= bbox.max()[0] && !flipSign; ++nijk[0]) { xPos = (nijk[0] & (LeafNodeType::DIM - 1u)) << (2 * LeafNodeType::LOG2DIM); for (nijk[1] = bbox.min()[1]; nijk[1] <= bbox.max()[1] && !flipSign; ++nijk[1]) { yPos = xPos + ((nijk[1] & (LeafNodeType::DIM - 1u)) << LeafNodeType::LOG2DIM); for (nijk[2] = bbox.min()[2]; nijk[2] <= bbox.max()[2]; ++nijk[2]) { pos = yPos + (nijk[2] & (LeafNodeType::DIM - 1u)); const Int32& polyIdx = idxData[pos]; if (polyIdx == Int32(util::INVALID_IDX) || !(data[pos] < -0.75)) continue; const Index pointIndex = pos * 2; if (!mask[pos]) { mask[pos] = true; nxyz[0] = double(nijk[0]); nxyz[1] = double(nijk[1]); nxyz[2] = double(nijk[2]); Vec3d& point = points[pointIndex]; point = closestPoint(nxyz, polyIdx); Vec3d& direction = points[pointIndex + 1]; direction = nxyz - point; direction.normalize(); } dir1 = xyz - points[pointIndex]; dir1.normalize(); if (points[pointIndex + 1].dot(dir1) > 0.0) { flipSign = true; break; } } } } if (flipSign) { dist = -dist; } else { for (Int32 m = 0; m < 26; ++m) { nijk = ijk + util::COORD_OFFSETS[m]; if (!bbox.isInside(nijk) && distAcc.probeValue(nijk, nval) && nval < -0.75) { nxyz[0] = double(nijk[0]); nxyz[1] = double(nijk[1]); nxyz[2] = double(nijk[2]); cp = closestPoint(nxyz, idxAcc.getValue(nijk)); dir1 = xyz - cp; dir1.normalize(); dir2 = nxyz - cp; dir2.normalize(); if (dir2.dot(dir1) > 0.0) { dist = -dist; break; } } } } } // active voxel loop } // leaf node loop } private: Vec3d closestPoint(const Vec3d& center, Int32 polyIdx) const { Vec3d a, b, c, cp, uvw; const size_t polygon = size_t(polyIdx); mMesh->getIndexSpacePoint(polygon, 0, a); mMesh->getIndexSpacePoint(polygon, 1, b); mMesh->getIndexSpacePoint(polygon, 2, c); cp = closestPointOnTriangleToPoint(a, c, b, center, uvw); if (4 == mMesh->vertexCount(polygon)) { mMesh->getIndexSpacePoint(polygon, 3, b); c = closestPointOnTriangleToPoint(a, b, c, center, uvw); if ((center - c).lengthSqr() < (center - cp).lengthSqr()) { cp = c; } } return cp; } LeafNodeType ** const mDistNodes; TreeType const * const mDistTree; Int32TreeType const * const mIndexTree; MeshDataAdapter const * const mMesh; boost::shared_ptr mLocalDataTable; }; // ComputeIntersectingVoxelSign //////////////////////////////////////// template inline void maskNodeInternalNeighbours(const Index pos, bool (&mask)[26]) { typedef LeafNodeType NodeT; const Coord ijk = NodeT::offsetToLocalCoord(pos); // Face adjacent neighbours // i+1, j, k mask[0] = ijk[0] != (NodeT::DIM - 1); // i-1, j, k mask[1] = ijk[0] != 0; // i, j+1, k mask[2] = ijk[1] != (NodeT::DIM - 1); // i, j-1, k mask[3] = ijk[1] != 0; // i, j, k+1 mask[4] = ijk[2] != (NodeT::DIM - 1); // i, j, k-1 mask[5] = ijk[2] != 0; // Edge adjacent neighbour // i+1, j, k-1 mask[6] = mask[0] && mask[5]; // i-1, j, k-1 mask[7] = mask[1] && mask[5]; // i+1, j, k+1 mask[8] = mask[0] && mask[4]; // i-1, j, k+1 mask[9] = mask[1] && mask[4]; // i+1, j+1, k mask[10] = mask[0] && mask[2]; // i-1, j+1, k mask[11] = mask[1] && mask[2]; // i+1, j-1, k mask[12] = mask[0] && mask[3]; // i-1, j-1, k mask[13] = mask[1] && mask[3]; // i, j-1, k+1 mask[14] = mask[3] && mask[4]; // i, j-1, k-1 mask[15] = mask[3] && mask[5]; // i, j+1, k+1 mask[16] = mask[2] && mask[4]; // i, j+1, k-1 mask[17] = mask[2] && mask[5]; // Corner adjacent neighbours // i-1, j-1, k-1 mask[18] = mask[1] && mask[3] && mask[5]; // i-1, j-1, k+1 mask[19] = mask[1] && mask[3] && mask[4]; // i+1, j-1, k+1 mask[20] = mask[0] && mask[3] && mask[4]; // i+1, j-1, k-1 mask[21] = mask[0] && mask[3] && mask[5]; // i-1, j+1, k-1 mask[22] = mask[1] && mask[2] && mask[5]; // i-1, j+1, k+1 mask[23] = mask[1] && mask[2] && mask[4]; // i+1, j+1, k+1 mask[24] = mask[0] && mask[2] && mask[4]; // i+1, j+1, k-1 mask[25] = mask[0] && mask[2] && mask[5]; } template inline bool checkNeighbours(const Index pos, const typename LeafNodeType::ValueType * data, bool (&mask)[26]) { typedef LeafNodeType NodeT; // i, j, k - 1 if (mask[5] && Compare::check(data[pos - 1])) return true; // i, j, k + 1 if (mask[4] && Compare::check(data[pos + 1])) return true; // i, j - 1, k if (mask[3] && Compare::check(data[pos - NodeT::DIM])) return true; // i, j + 1, k if (mask[2] && Compare::check(data[pos + NodeT::DIM])) return true; // i - 1, j, k if (mask[1] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM])) return true; // i + 1, j, k if (mask[0] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM])) return true; // i+1, j, k-1 if (mask[6] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM])) return true; // i-1, j, k-1 if (mask[7] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM - 1])) return true; // i+1, j, k+1 if (mask[8] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM + 1])) return true; // i-1, j, k+1 if (mask[9] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM + 1])) return true; // i+1, j+1, k if (mask[10] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM + NodeT::DIM])) return true; // i-1, j+1, k if (mask[11] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM + NodeT::DIM])) return true; // i+1, j-1, k if (mask[12] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM - NodeT::DIM])) return true; // i-1, j-1, k if (mask[13] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM - NodeT::DIM])) return true; // i, j-1, k+1 if (mask[14] && Compare::check(data[pos - NodeT::DIM + 1])) return true; // i, j-1, k-1 if (mask[15] && Compare::check(data[pos - NodeT::DIM - 1])) return true; // i, j+1, k+1 if (mask[16] && Compare::check(data[pos + NodeT::DIM + 1])) return true; // i, j+1, k-1 if (mask[17] && Compare::check(data[pos + NodeT::DIM - 1])) return true; // i-1, j-1, k-1 if (mask[18] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM - NodeT::DIM - 1])) return true; // i-1, j-1, k+1 if (mask[19] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM - NodeT::DIM + 1])) return true; // i+1, j-1, k+1 if (mask[20] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM - NodeT::DIM + 1])) return true; // i+1, j-1, k-1 if (mask[21] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM - NodeT::DIM - 1])) return true; // i-1, j+1, k-1 if (mask[22] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM + NodeT::DIM - 1])) return true; // i-1, j+1, k+1 if (mask[23] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM + NodeT::DIM + 1])) return true; // i+1, j+1, k+1 if (mask[24] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM + NodeT::DIM + 1])) return true; // i+1, j+1, k-1 if (mask[25] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM + NodeT::DIM - 1])) return true; return false; } template inline bool checkNeighbours(const Coord& ijk, AccessorType& acc, bool (&mask)[26]) { for (Int32 m = 0; m < 26; ++m) { if (!mask[m] && Compare::check(acc.getValue(ijk + util::COORD_OFFSETS[m]))) { return true; } } return false; } template struct ValidateIntersectingVoxels { typedef typename TreeType::ValueType ValueType; typedef typename TreeType::LeafNodeType LeafNodeType; struct IsNegative { static bool check(const ValueType v) { return v < ValueType(0.0); } }; ValidateIntersectingVoxels(TreeType& tree, std::vector& nodes) : mTree(&tree) , mNodes(nodes.empty() ? NULL : &nodes[0]) { } void operator()(const tbb::blocked_range& range) const { tree::ValueAccessor acc(*mTree); bool neighbourMask[26]; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { LeafNodeType& node = *mNodes[n]; ValueType* data = node.buffer().data(); typename LeafNodeType::ValueOnCIter it; for (it = node.cbeginValueOn(); it; ++it) { const Index pos = it.pos(); ValueType& dist = data[pos]; if (dist < 0.0 || dist > 0.75) continue; // Mask node internal neighbours maskNodeInternalNeighbours(pos, neighbourMask); const bool hasNegativeNeighbour = checkNeighbours(pos, data, neighbourMask) || checkNeighbours(node.offsetToGlobalCoord(pos), acc, neighbourMask); if (!hasNegativeNeighbour) { // push over boundary voxel distance dist = ValueType(0.75) + Tolerance::epsilon(); } } } } TreeType * const mTree; LeafNodeType ** const mNodes; }; // ValidateIntersectingVoxels template struct RemoveSelfIntersectingSurface { typedef typename TreeType::ValueType ValueType; typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::template ValueConverter::Type Int32TreeType; struct Comp { static bool check(const ValueType v) { return !(v > ValueType(0.75)); } }; RemoveSelfIntersectingSurface(std::vector& nodes, TreeType& distTree, Int32TreeType& indexTree) : mNodes(nodes.empty() ? NULL : &nodes[0]) , mDistTree(&distTree) , mIndexTree(&indexTree) { } void operator()(const tbb::blocked_range& range) const { tree::ValueAccessor distAcc(*mDistTree); tree::ValueAccessor idxAcc(*mIndexTree); bool neighbourMask[26]; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { LeafNodeType& distNode = *mNodes[n]; ValueType* data = distNode.buffer().data(); typename Int32TreeType::LeafNodeType* idxNode = idxAcc.probeLeaf(distNode.origin()); typename LeafNodeType::ValueOnCIter it; for (it = distNode.cbeginValueOn(); it; ++it) { const Index pos = it.pos(); if (!(data[pos] > 0.75)) continue; // Mask node internal neighbours maskNodeInternalNeighbours(pos, neighbourMask); const bool hasBoundaryNeighbour = checkNeighbours(pos, data, neighbourMask) || checkNeighbours(distNode.offsetToGlobalCoord(pos), distAcc, neighbourMask); if (!hasBoundaryNeighbour) { distNode.setValueOff(pos); idxNode->setValueOff(pos); } } } } LeafNodeType * * const mNodes; TreeType * const mDistTree; Int32TreeType * const mIndexTree; }; // RemoveSelfIntersectingSurface //////////////////////////////////////// template struct ReleaseChildNodes { ReleaseChildNodes(NodeType ** nodes) : mNodes(nodes) {} void operator()(const tbb::blocked_range& range) const { typedef typename NodeType::NodeMaskType NodeMaskType; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { const_cast(mNodes[n]->getChildMask()).setOff(); } } NodeType ** const mNodes; }; template inline void releaseLeafNodes(TreeType& tree) { typedef typename TreeType::RootNodeType RootNodeType; typedef typename RootNodeType::NodeChainType NodeChainType; typedef typename boost::mpl::at >::type InternalNodeType; std::vector nodes; tree.getNodes(nodes); tbb::parallel_for(tbb::blocked_range(0, nodes.size()), ReleaseChildNodes(nodes.empty() ? NULL : &nodes[0])); } template struct StealUniqueLeafNodes { typedef typename TreeType::LeafNodeType LeafNodeType; StealUniqueLeafNodes(TreeType& lhsTree, TreeType& rhsTree, std::vector& overlappingNodes) : mLhsTree(&lhsTree) , mRhsTree(&rhsTree) , mNodes(&overlappingNodes) { } void operator()() const { std::vector rhsLeafNodes; rhsLeafNodes.reserve(mRhsTree->leafCount()); //mRhsTree->getNodes(rhsLeafNodes); //releaseLeafNodes(*mRhsTree); mRhsTree->stealNodes(rhsLeafNodes); tree::ValueAccessor acc(*mLhsTree); for (size_t n = 0, N = rhsLeafNodes.size(); n < N; ++n) { if (!acc.probeLeaf(rhsLeafNodes[n]->origin())) { acc.addLeaf(rhsLeafNodes[n]); } else { mNodes->push_back(rhsLeafNodes[n]); } } } private: TreeType * const mLhsTree; TreeType * const mRhsTree; std::vector * const mNodes; }; template inline void combineData(DistTreeType& lhsDist, IndexTreeType& lhsIdx, DistTreeType& rhsDist, IndexTreeType& rhsIdx) { typedef typename DistTreeType::LeafNodeType DistLeafNodeType; typedef typename IndexTreeType::LeafNodeType IndexLeafNodeType; std::vector overlappingDistNodes; std::vector overlappingIdxNodes; // Steal unique leafnodes tbb::task_group tasks; tasks.run(StealUniqueLeafNodes(lhsDist, rhsDist, overlappingDistNodes)); tasks.run(StealUniqueLeafNodes(lhsIdx, rhsIdx, overlappingIdxNodes)); tasks.wait(); // Combine overlapping leaf nodes if (!overlappingDistNodes.empty() && !overlappingIdxNodes.empty()) { tbb::parallel_for(tbb::blocked_range(0, overlappingDistNodes.size()), CombineLeafNodes(lhsDist, lhsIdx, &overlappingDistNodes[0], &overlappingIdxNodes[0])); } } /// @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 struct VoxelizationData { typedef boost::scoped_ptr Ptr; typedef typename TreeType::ValueType ValueType; typedef typename TreeType::template ValueConverter::Type Int32TreeType; typedef typename TreeType::template ValueConverter::Type UCharTreeType; typedef tree::ValueAccessor FloatTreeAcc; typedef tree::ValueAccessor Int32TreeAcc; typedef tree::ValueAccessor UCharTreeAcc; VoxelizationData() : distTree(std::numeric_limits::max()) , distAcc(distTree) , indexTree(Int32(util::INVALID_IDX)) , indexAcc(indexTree) , primIdTree(MaxPrimId) , primIdAcc(primIdTree) , mPrimCount(0) { } TreeType distTree; FloatTreeAcc distAcc; Int32TreeType indexTree; Int32TreeAcc indexAcc; UCharTreeType primIdTree; UCharTreeAcc primIdAcc; unsigned char getNewPrimId() { if (mPrimCount == MaxPrimId || primIdTree.leafCount() > 1000) { mPrimCount = 0; primIdTree.clear(); } return mPrimCount++; } private: enum { MaxPrimId = 100 }; unsigned char mPrimCount; }; template class VoxelizePolygons { public: typedef VoxelizationData VoxelizationDataType; typedef tbb::enumerable_thread_specific DataTable; VoxelizePolygons(DataTable& dataTable, const MeshDataAdapter& mesh, Interrupter* interrupter = NULL) : mDataTable(&dataTable) , mMesh(&mesh) , mInterrupter(interrupter) { } void operator()(const tbb::blocked_range& range) const { typename VoxelizationDataType::Ptr& dataPtr = mDataTable->local(); if (!dataPtr) dataPtr.reset(new VoxelizationDataType()); Triangle prim; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { if (this->wasInterrupted()) { tbb::task::self().cancel_group_execution(); break; } const size_t numVerts = mMesh->vertexCount(n); // rasterize triangles and quads. if (numVerts == 3 || numVerts == 4) { prim.index = Int32(n); mMesh->getIndexSpacePoint(n, 0, prim.a); mMesh->getIndexSpacePoint(n, 1, prim.b); mMesh->getIndexSpacePoint(n, 2, prim.c); evalTriangle(prim, *dataPtr); if (numVerts == 4) { mMesh->getIndexSpacePoint(n, 3, prim.b); evalTriangle(prim, *dataPtr); } } } } private: bool wasInterrupted() const { return mInterrupter && mInterrupter->wasInterrupted(); } struct Triangle { Vec3d a, b, c; Int32 index; }; struct SubTask { SubTask(const Triangle& prim, DataTable& dataTable, size_t polygonCount) : mLocalDataTable(&dataTable) , mPrim(prim) , mPolygonCount(polygonCount) { } void operator()() const { const size_t minNumTask = size_t(tbb::task_scheduler_init::default_num_threads() * 10); if (mPolygonCount > minNumTask) { typename VoxelizationDataType::Ptr& dataPtr = mLocalDataTable->local(); if (!dataPtr) dataPtr.reset(new VoxelizationDataType()); voxelizeTriangle(mPrim, *dataPtr); } else { spawnTasks(mPrim, *mLocalDataTable, mPolygonCount); } } DataTable * const mLocalDataTable; const Triangle mPrim; const size_t mPolygonCount; }; void evalTriangle(const Triangle& prim, VoxelizationDataType& data) const { const size_t minNumTask = size_t(tbb::task_scheduler_init::default_num_threads() * 10); if (mMesh->polygonCount() > minNumTask) { voxelizeTriangle(prim, data); } else { spawnTasks(prim, *mDataTable, mMesh->polygonCount()); } } static void spawnTasks(const Triangle& mainPrim, DataTable& dataTable, size_t primCount) { const size_t newPrimCount = primCount * 4; tbb::task_group tasks; const Vec3d ac = (mainPrim.a + mainPrim.c) * 0.5; const Vec3d bc = (mainPrim.b + mainPrim.c) * 0.5; const Vec3d ab = (mainPrim.a + mainPrim.b) * 0.5; Triangle prim; prim.index = mainPrim.index; prim.a = mainPrim.a; prim.b = ab; prim.c = ac; tasks.run(SubTask(prim, dataTable, newPrimCount)); prim.a = ab; prim.b = bc; prim.c = ac; tasks.run(SubTask(prim, dataTable, newPrimCount)); prim.a = ab; prim.b = mainPrim.b; prim.c = bc; tasks.run(SubTask(prim, dataTable, newPrimCount)); prim.a = ac; prim.b = bc; prim.c = mainPrim.c; tasks.run(SubTask(prim, dataTable, newPrimCount)); tasks.wait(); } static void voxelizeTriangle(const Triangle& prim, VoxelizationDataType& data) { std::deque coordList; Coord ijk, nijk; ijk = Coord::floor(prim.a); coordList.push_back(ijk); computeDistance(ijk, prim, data); unsigned char primId = data.getNewPrimId(); data.primIdAcc.setValueOnly(ijk, primId); while (!coordList.empty()) { ijk = coordList.back(); coordList.pop_back(); for (Int32 i = 0; i < 26; ++i) { nijk = ijk + util::COORD_OFFSETS[i]; if (primId != data.primIdAcc.getValue(nijk)) { data.primIdAcc.setValueOnly(nijk, primId); if(computeDistance(nijk, prim, data)) coordList.push_back(nijk); } } } } static bool computeDistance(const Coord& ijk, const Triangle& prim, VoxelizationDataType& data) { Vec3d uvw, voxelCenter(ijk[0], ijk[1], ijk[2]); typedef typename TreeType::ValueType ValueType; const ValueType dist = ValueType((voxelCenter - closestPointOnTriangleToPoint(prim.a, prim.c, prim.b, voxelCenter, uvw)).lengthSqr()); const ValueType oldDist = data.distAcc.getValue(ijk); if (dist < oldDist) { data.distAcc.setValue(ijk, dist); data.indexAcc.setValue(ijk, prim.index); } else if (math::isExactlyEqual(dist, oldDist)) { // makes reduction deterministic when different polygons // produce the same distance value. data.indexAcc.setValueOnly(ijk, std::min(prim.index, data.indexAcc.getValue(ijk))); } return !(dist > 0.75); // true if the primitive intersects the voxel. } DataTable * const mDataTable; MeshDataAdapter const * const mMesh; Interrupter * const mInterrupter; }; // VoxelizePolygons //////////////////////////////////////// template struct DiffLeafNodeMask { typedef typename tree::ValueAccessor AccessorType; typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::template ValueConverter::Type BoolTreeType; typedef typename BoolTreeType::LeafNodeType BoolLeafNodeType; DiffLeafNodeMask(const TreeType& rhsTree, std::vector& lhsNodes) : mRhsTree(&rhsTree), mLhsNodes(lhsNodes.empty() ? NULL : &lhsNodes[0]) { } void operator()(const tbb::blocked_range& range) const { tree::ValueAccessor acc(*mRhsTree); for (size_t n = range.begin(), N = range.end(); n < N; ++n) { BoolLeafNodeType* lhsNode = mLhsNodes[n]; const LeafNodeType* rhsNode = acc.probeConstLeaf(lhsNode->origin()); if (rhsNode) lhsNode->topologyDifference(*rhsNode, false); } } private: TreeType const * const mRhsTree; BoolLeafNodeType ** const mLhsNodes; }; template struct UnionValueMasks { UnionValueMasks(std::vector& nodesA, std::vector& nodesB) : mNodesA(nodesA.empty() ? NULL : &nodesA[0]) , mNodesB(nodesB.empty() ? NULL : &nodesB[0]) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { mNodesA[n]->topologyUnion(*mNodesB[n]); } } private: LeafNodeTypeA ** const mNodesA; LeafNodeTypeB ** const mNodesB; }; template struct ConstructVoxelMask { typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::template ValueConverter::Type BoolTreeType; typedef typename BoolTreeType::LeafNodeType BoolLeafNodeType; ConstructVoxelMask(BoolTreeType& maskTree, const TreeType& tree, std::vector& nodes) : mTree(&tree) , mNodes(nodes.empty() ? NULL : &nodes[0]) , mLocalMaskTree(false) , mMaskTree(&maskTree) { } ConstructVoxelMask(ConstructVoxelMask& rhs, tbb::split) : mTree(rhs.mTree) , mNodes(rhs.mNodes) , mLocalMaskTree(false) , mMaskTree(&mLocalMaskTree) { } void operator()(const tbb::blocked_range& range) { typedef typename LeafNodeType::ValueOnCIter Iterator; tree::ValueAccessor acc(*mTree); tree::ValueAccessor maskAcc(*mMaskTree); Coord ijk, nijk, localCorod; Index pos, npos; for (size_t n = range.begin(); n != range.end(); ++n) { LeafNodeType& node = *mNodes[n]; CoordBBox bbox = node.getNodeBoundingBox(); bbox.expand(-1); BoolLeafNodeType& maskNode = *maskAcc.touchLeaf(node.origin()); for (Iterator it = node.cbeginValueOn(); it; ++it) { ijk = it.getCoord(); pos = it.pos(); localCorod = LeafNodeType::offsetToLocalCoord(pos); if (localCorod[2] < int(LeafNodeType::DIM - 1)) { npos = pos + 1; if (!node.isValueOn(npos)) maskNode.setValueOn(npos); } else { nijk = ijk.offsetBy(0, 0, 1); if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk); } if (localCorod[2] > 0) { npos = pos - 1; if (!node.isValueOn(npos)) maskNode.setValueOn(npos); } else { nijk = ijk.offsetBy(0, 0, -1); if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk); } if (localCorod[1] < int(LeafNodeType::DIM - 1)) { npos = pos + LeafNodeType::DIM; if (!node.isValueOn(npos)) maskNode.setValueOn(npos); } else { nijk = ijk.offsetBy(0, 1, 0); if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk); } if (localCorod[1] > 0) { npos = pos - LeafNodeType::DIM; if (!node.isValueOn(npos)) maskNode.setValueOn(npos); } else { nijk = ijk.offsetBy(0, -1, 0); if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk); } if (localCorod[0] < int(LeafNodeType::DIM - 1)) { npos = pos + LeafNodeType::DIM * LeafNodeType::DIM; if (!node.isValueOn(npos)) maskNode.setValueOn(npos); } else { nijk = ijk.offsetBy(1, 0, 0); if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk); } if (localCorod[0] > 0) { npos = pos - LeafNodeType::DIM * LeafNodeType::DIM; if (!node.isValueOn(npos)) maskNode.setValueOn(npos); } else { nijk = ijk.offsetBy(-1, 0, 0); if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk); } } } } void join(ConstructVoxelMask& rhs) { mMaskTree->merge(*rhs.mMaskTree); } private: TreeType const * const mTree; LeafNodeType ** const mNodes; BoolTreeType mLocalMaskTree; BoolTreeType * const mMaskTree; }; /// @note The interior and exterior widths should be in world space units and squared. template struct ExpandNarrowband { typedef typename TreeType::ValueType ValueType; typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::template ValueConverter::Type Int32TreeType; typedef typename Int32TreeType::LeafNodeType Int32LeafNodeType; typedef typename TreeType::template ValueConverter::Type BoolTreeType; typedef typename BoolTreeType::LeafNodeType BoolLeafNodeType; ExpandNarrowband( std::vector& maskNodes, BoolTreeType& maskTree, TreeType& distTree, Int32TreeType& indexTree, const MeshDataAdapter& mesh, ValueType exteriorBandWidth, ValueType interiorBandWidth, ValueType voxelSize) : mMaskNodes(maskNodes.empty() ? NULL : &maskNodes[0]) , mMaskTree(&maskTree) , mDistTree(&distTree) , mIndexTree(&indexTree) , mMesh(&mesh) , mNewMaskTree(false) , mDistNodes() , mUpdatedDistNodes() , mIndexNodes() , mUpdatedIndexNodes() , mExteriorBandWidth(exteriorBandWidth) , mInteriorBandWidth(interiorBandWidth) , mVoxelSize(voxelSize) { } ExpandNarrowband(const ExpandNarrowband& rhs, tbb::split) : mMaskNodes(rhs.mMaskNodes) , mMaskTree(rhs.mMaskTree) , mDistTree(rhs.mDistTree) , mIndexTree(rhs.mIndexTree) , mMesh(rhs.mMesh) , mNewMaskTree(false) , mDistNodes() , mUpdatedDistNodes() , mIndexNodes() , mUpdatedIndexNodes() , mExteriorBandWidth(rhs.mExteriorBandWidth) , mInteriorBandWidth(rhs.mInteriorBandWidth) , mVoxelSize(rhs.mVoxelSize) { } void join(ExpandNarrowband& rhs) { mDistNodes.insert(mDistNodes.end(), rhs.mDistNodes.begin(), rhs.mDistNodes.end()); mIndexNodes.insert(mIndexNodes.end(), rhs.mIndexNodes.begin(), rhs.mIndexNodes.end()); mUpdatedDistNodes.insert(mUpdatedDistNodes.end(), rhs.mUpdatedDistNodes.begin(), rhs.mUpdatedDistNodes.end()); mUpdatedIndexNodes.insert(mUpdatedIndexNodes.end(), rhs.mUpdatedIndexNodes.begin(), rhs.mUpdatedIndexNodes.end()); mNewMaskTree.merge(rhs.mNewMaskTree); } void operator()(const tbb::blocked_range& range) { tree::ValueAccessor newMaskAcc(mNewMaskTree); tree::ValueAccessor distAcc(*mDistTree); tree::ValueAccessor indexAcc(*mIndexTree); std::vector primitives; primitives.reserve(26); LeafNodeType * newDistNodePt = NULL; Int32LeafNodeType * newIndexNodePt = NULL; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { BoolLeafNodeType& maskNode = *mMaskNodes[n]; if (maskNode.isEmpty()) continue; Coord ijk = maskNode.origin(); bool usingNewNodes = false; LeafNodeType * distNodePt = distAcc.probeLeaf(ijk); Int32LeafNodeType * indexNodePt = indexAcc.probeLeaf(ijk); assert(!distNodePt == !indexNodePt); if (!distNodePt && !indexNodePt) { const ValueType backgroundDist = distAcc.getValue(ijk); if (!newDistNodePt && !newIndexNodePt) { newDistNodePt = new LeafNodeType(ijk, backgroundDist); newIndexNodePt = new Int32LeafNodeType(ijk, indexAcc.getValue(ijk)); } else { if ((backgroundDist < ValueType(0.0)) != (newDistNodePt->getValue(0) < ValueType(0.0))) { newDistNodePt->buffer().fill(backgroundDist); } newDistNodePt->setOrigin(ijk); newIndexNodePt->setOrigin(ijk); } distNodePt = newDistNodePt; indexNodePt = newIndexNodePt; usingNewNodes = true; } bool updatedValues = false; for (typename BoolLeafNodeType::ValueOnIter it = maskNode.beginValueOn(); it; ++it) { ijk = it.getCoord(); const Index pos = it.pos(); Int32 closestPrimIdx = 0; const ValueType distance = computeDistance(ijk, distAcc, indexAcc, primitives, closestPrimIdx); const bool inside = distNodePt->getValue(pos) < ValueType(0.0); if (!inside && distance < mExteriorBandWidth) { distNodePt->setValueOnly(pos, distance); indexNodePt->setValueOn(pos, closestPrimIdx); } else if (inside && distance < mInteriorBandWidth) { distNodePt->setValueOnly(pos, -distance); indexNodePt->setValueOn(pos, closestPrimIdx); } else { continue; } for (Int32 i = 0; i < 6; ++i) { newMaskAcc.setValueOn(ijk + util::COORD_OFFSETS[i]); } updatedValues = true; } if (updatedValues && usingNewNodes) { distNodePt->topologyUnion(*indexNodePt); mDistNodes.push_back(distNodePt); mIndexNodes.push_back(indexNodePt); newDistNodePt = NULL; newIndexNodePt = NULL; } else if (updatedValues) { mUpdatedDistNodes.push_back(distNodePt); mUpdatedIndexNodes.push_back(indexNodePt); } } } ////////// BoolTreeType& newMaskTree() { return mNewMaskTree; } std::vector& newDistNodes() { return mDistNodes; } std::vector& updatedDistNodes() { return mUpdatedDistNodes; } std::vector& newIndexNodes() { return mIndexNodes; } std::vector& updatedIndexNodes() { return mUpdatedIndexNodes; } private: ValueType computeDistance(const Coord& ijk, tree::ValueAccessor& distAcc, tree::ValueAccessor& idxAcc, std::vector& primitives, Int32& closestPrimIdx) const { ValueType minDist = std::numeric_limits::max(); primitives.clear(); const Coord ijkMin = ijk.offsetBy(-1); const Coord ijkMax = ijk.offsetBy(1); const Coord nodeMin = ijkMin & ~(LeafNodeType::DIM - 1); const Coord nodeMax = ijkMax & ~(LeafNodeType::DIM - 1); CoordBBox bbox; Coord nijk; for (nijk[0] = nodeMin[0]; nijk[0] <= nodeMax[0]; nijk[0] += LeafNodeType::DIM) { for (nijk[1] = nodeMin[1]; nijk[1] <= nodeMax[1]; nijk[1] += LeafNodeType::DIM) { for (nijk[2] = nodeMin[2]; nijk[2] <= nodeMax[2]; nijk[2] += LeafNodeType::DIM) { if (LeafNodeType* distleaf = distAcc.probeLeaf(nijk)) { bbox.min() = Coord::maxComponent(ijkMin, nijk); bbox.max() = Coord::minComponent(ijkMax, nijk.offsetBy(LeafNodeType::DIM - 1)); evalLeafNode(bbox, *distleaf, *idxAcc.probeLeaf(nijk), primitives, minDist); } } } } const ValueType tmpDist = evalPrimitives(ijk, primitives, closestPrimIdx); return tmpDist > minDist ? tmpDist : minDist + mVoxelSize; } void evalLeafNode(const CoordBBox& bbox, LeafNodeType& distLeaf, Int32LeafNodeType& idxLeaf, std::vector& primitives, ValueType& minNeighbourDist) const { ValueType tmpDist; Index xPos(0), yPos(0), pos(0); for (int x = bbox.min()[0]; x <= bbox.max()[0]; ++x) { xPos = (x & (LeafNodeType::DIM - 1u)) << (2 * LeafNodeType::LOG2DIM); for (int y = bbox.min()[1]; y <= bbox.max()[1]; ++y) { yPos = xPos + ((y & (LeafNodeType::DIM - 1u)) << LeafNodeType::LOG2DIM); for (int z = bbox.min()[2]; z <= bbox.max()[2]; ++z) { pos = yPos + (z & (LeafNodeType::DIM - 1u)); if (distLeaf.probeValue(pos, tmpDist)) { primitives.push_back(idxLeaf.getValue(pos)); minNeighbourDist = std::min(std::abs(tmpDist), minNeighbourDist); } } } } } ValueType evalPrimitives(const Coord& ijk, std::vector& primitives, Int32& closestPrimIdx) const { std::sort(primitives.begin(), primitives.end()); Int32 lastPrim = -1; Vec3d a, b, c, uvw, voxelCenter(ijk[0], ijk[1], ijk[2]); double primDist, tmpDist, dist = std::numeric_limits::max(); for (size_t n = 0, N = primitives.size(); n < N; ++n) { if (primitives[n] == lastPrim) continue; lastPrim = primitives[n]; const size_t polygon = size_t(lastPrim); mMesh->getIndexSpacePoint(polygon, 0, a); mMesh->getIndexSpacePoint(polygon, 1, b); mMesh->getIndexSpacePoint(polygon, 2, c); primDist = (voxelCenter - closestPointOnTriangleToPoint(a, c, b, voxelCenter, uvw)).lengthSqr(); // Split-up quad into a second triangle if (4 == mMesh->vertexCount(polygon)) { mMesh->getIndexSpacePoint(polygon, 3, b); tmpDist = (voxelCenter - closestPointOnTriangleToPoint( a, b, c, voxelCenter, uvw)).lengthSqr(); if (tmpDist < primDist) primDist = tmpDist; } if (primDist < dist) { dist = primDist; closestPrimIdx = lastPrim; } } return ValueType(std::sqrt(dist)) * mVoxelSize; } ////////// BoolLeafNodeType ** const mMaskNodes; BoolTreeType * const mMaskTree; TreeType * const mDistTree; Int32TreeType * const mIndexTree; MeshDataAdapter const * const mMesh; BoolTreeType mNewMaskTree; std::vector mDistNodes, mUpdatedDistNodes; std::vector mIndexNodes, mUpdatedIndexNodes; const ValueType mExteriorBandWidth, mInteriorBandWidth, mVoxelSize; }; // ExpandNarrowband template inline void expandNarrowband( TreeType& distTree, Int32TreeType& indexTree, BoolTreeType& maskTree, std::vector& maskNodes, const MeshDataAdapter& mesh, typename TreeType::ValueType exteriorBandWidth, typename TreeType::ValueType interiorBandWidth, typename TreeType::ValueType voxelSize) { typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename Int32TreeType::LeafNodeType Int32LeafNodeType; ExpandNarrowband expandOp(maskNodes, maskTree, distTree, indexTree, mesh, exteriorBandWidth, interiorBandWidth, voxelSize); tbb::parallel_reduce(tbb::blocked_range(0, maskNodes.size()), expandOp); { tree::ValueAccessor acc(distTree); typedef typename std::vector LeafNodePtVec; LeafNodePtVec& nodes = expandOp.newDistNodes(); for (typename LeafNodePtVec::iterator it = nodes.begin(), end = nodes.end(); it != end; ++it) { acc.addLeaf(*it); } } { tree::ValueAccessor acc(indexTree); typedef typename std::vector LeafNodePtVec; LeafNodePtVec& nodes = expandOp.newIndexNodes(); for (typename LeafNodePtVec::iterator it = nodes.begin(), end = nodes.end(); it != end; ++it) { acc.addLeaf(*it); } } tbb::parallel_for(tbb::blocked_range(0, expandOp.updatedIndexNodes().size()), UnionValueMasks(expandOp.updatedDistNodes(), expandOp.updatedIndexNodes())); maskTree.clear(); maskTree.merge(expandOp.newMaskTree()); } //////////////////////////////////////// // Transform values (sqrt, world space scaling and sign flip if sdf) template struct TransformValues { typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::ValueType ValueType; TransformValues(std::vector& nodes, ValueType voxelSize, bool unsignedDist) : mNodes(&nodes[0]) , mVoxelSize(voxelSize) , mUnsigned(unsignedDist) { } void operator()(const tbb::blocked_range& range) const { typename LeafNodeType::ValueOnIter iter; const bool udf = mUnsigned; const ValueType w[2] = { -mVoxelSize, mVoxelSize }; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { for (iter = mNodes[n]->beginValueOn(); iter; ++iter) { ValueType& val = const_cast(iter.getValue()); val = w[udf || (val < ValueType(0.0))] * std::sqrt(std::abs(val)); } } } private: LeafNodeType * * const mNodes; const ValueType mVoxelSize; const bool mUnsigned; }; // Inactivate values outside the (exBandWidth, inBandWidth) range. template struct InactivateValues { typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::ValueType ValueType; InactivateValues(std::vector& nodes, ValueType exBandWidth, ValueType inBandWidth) : mNodes(nodes.empty() ? NULL : &nodes[0]) , mExBandWidth(exBandWidth) , mInBandWidth(inBandWidth) { } void operator()(const tbb::blocked_range& range) const { typename LeafNodeType::ValueOnIter iter; const ValueType exVal = mExBandWidth; const ValueType inVal = -mInBandWidth; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { for (iter = mNodes[n]->beginValueOn(); iter; ++iter) { ValueType& val = const_cast(iter.getValue()); const bool inside = val < ValueType(0.0); if (inside && !(val > inVal)) { val = inVal; iter.setValueOff(); } else if (!inside && !(val < exVal)) { val = exVal; iter.setValueOff(); } } } } private: LeafNodeType * * const mNodes; const ValueType mExBandWidth, mInBandWidth; }; template struct OffsetValues { typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::ValueType ValueType; OffsetValues(std::vector& nodes, ValueType offset) : mNodes(nodes.empty() ? NULL : &nodes[0]), mOffset(offset) { } void operator()(const tbb::blocked_range& range) const { const ValueType offset = mOffset; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { typename LeafNodeType::ValueOnIter iter = mNodes[n]->beginValueOn(); for (; iter; ++iter) { ValueType& val = const_cast(iter.getValue()); val += offset; } } } private: LeafNodeType * * const mNodes; const ValueType mOffset; }; template struct Renormalize { typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::ValueType ValueType; Renormalize(const TreeType& tree, const std::vector& nodes, ValueType* buffer, ValueType voxelSize) : mTree(&tree) , mNodes(nodes.empty() ? NULL : &nodes[0]) , mBuffer(buffer) , mVoxelSize(voxelSize) { } void operator()(const tbb::blocked_range& range) const { typedef math::Vec3 Vec3Type; tree::ValueAccessor acc(*mTree); Coord ijk; Vec3Type up, down; const ValueType dx = mVoxelSize, invDx = ValueType(1.0) / mVoxelSize; for (size_t n = range.begin(), N = range.end(); n < N; ++n) { ValueType* bufferData = &mBuffer[n * LeafNodeType::SIZE]; typename LeafNodeType::ValueOnCIter iter = mNodes[n]->cbeginValueOn(); for (; iter; ++iter) { const ValueType phi0 = *iter; ijk = iter.getCoord(); up[0] = acc.getValue(ijk.offsetBy(1, 0, 0)) - phi0; up[1] = acc.getValue(ijk.offsetBy(0, 1, 0)) - phi0; up[2] = acc.getValue(ijk.offsetBy(0, 0, 1)) - phi0; down[0] = phi0 - acc.getValue(ijk.offsetBy(-1, 0, 0)); down[1] = phi0 - acc.getValue(ijk.offsetBy(0, -1, 0)); down[2] = phi0 - acc.getValue(ijk.offsetBy(0, 0, -1)); const ValueType normSqGradPhi = math::GudonovsNormSqrd(phi0 > 0.0, down, up); const ValueType diff = math::Sqrt(normSqGradPhi) * invDx - ValueType(1.0); const ValueType S = phi0 / (math::Sqrt(math::Pow2(phi0) + normSqGradPhi)); bufferData[iter.pos()] = phi0 - dx * S * diff; } } } private: TreeType const * const mTree; LeafNodeType const * const * const mNodes; ValueType * const mBuffer; const ValueType mVoxelSize; }; template struct MinCombine { typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename TreeType::ValueType ValueType; MinCombine(std::vector& nodes, const ValueType* buffer) : mNodes(nodes.empty() ? NULL : &nodes[0]), mBuffer(buffer) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { const ValueType* bufferData = &mBuffer[n * LeafNodeType::SIZE]; typename LeafNodeType::ValueOnIter iter = mNodes[n]->beginValueOn(); for (; iter; ++iter) { ValueType& val = const_cast(iter.getValue()); val = std::min(val, bufferData[iter.pos()]); } } } private: LeafNodeType * * const mNodes; ValueType const * const mBuffer; }; } // mesh_to_volume_internal namespace //////////////////////////////////////// // Utility method implementation template inline void traceExteriorBoundaries(FloatTreeT& tree) { typedef mesh_to_volume_internal::LeafNodeConnectivityTable ConnectivityTable; ConnectivityTable nodeConnectivity(tree); std::vector zStartNodes, yStartNodes, xStartNodes; for (size_t n = 0; n < nodeConnectivity.size(); ++n) { if (ConnectivityTable::INVALID_OFFSET == nodeConnectivity.offsetsPrevX()[n]) { xStartNodes.push_back(n); } if (ConnectivityTable::INVALID_OFFSET == nodeConnectivity.offsetsPrevY()[n]) { yStartNodes.push_back(n); } if (ConnectivityTable::INVALID_OFFSET == nodeConnectivity.offsetsPrevZ()[n]) { zStartNodes.push_back(n); } } typedef mesh_to_volume_internal::SweepExteriorSign SweepingOp; tbb::parallel_for(tbb::blocked_range(0, zStartNodes.size()), SweepingOp(SweepingOp::Z_AXIS, zStartNodes, nodeConnectivity)); tbb::parallel_for(tbb::blocked_range(0, yStartNodes.size()), SweepingOp(SweepingOp::Y_AXIS, yStartNodes, nodeConnectivity)); tbb::parallel_for(tbb::blocked_range(0, xStartNodes.size()), SweepingOp(SweepingOp::X_AXIS, xStartNodes, nodeConnectivity)); const size_t numLeafNodes = nodeConnectivity.size(); const size_t numVoxels = numLeafNodes * FloatTreeT::LeafNodeType::SIZE; boost::scoped_array changedNodeMaskA(new bool[numLeafNodes]); boost::scoped_array changedNodeMaskB(new bool[numLeafNodes]); boost::scoped_array changedVoxelMask(new bool[numVoxels]); memset(changedNodeMaskA.get(), 1, sizeof(bool) * numLeafNodes); mesh_to_volume_internal::fillArray(changedVoxelMask.get(), false, numVoxels); const tbb::blocked_range nodeRange(0, numLeafNodes); bool nodesUpdated = false; do { tbb::parallel_for(nodeRange, mesh_to_volume_internal::SeedFillExteriorSign( nodeConnectivity.nodes(), changedNodeMaskA.get())); tbb::parallel_for(nodeRange, mesh_to_volume_internal::SeedPoints(nodeConnectivity, changedNodeMaskA.get(), changedNodeMaskB.get(), changedVoxelMask.get())); changedNodeMaskA.swap(changedNodeMaskB); nodesUpdated = false; for (size_t n = 0; n < numLeafNodes; ++n) { nodesUpdated |= changedNodeMaskA[n]; if (nodesUpdated) break; } if (nodesUpdated) { tbb::parallel_for(nodeRange, mesh_to_volume_internal::SyncVoxelMask( nodeConnectivity.nodes(), changedNodeMaskA.get(), changedVoxelMask.get())); } } while (nodesUpdated); } // void traceExteriorBoundaries() //////////////////////////////////////// template inline typename GridType::Ptr meshToVolume( Interrupter& interrupter, const MeshDataAdapter& mesh, const math::Transform& transform, float exteriorBandWidth, float interiorBandWidth, int flags, typename GridType::template ValueConverter::Type * polygonIndexGrid) { typedef typename GridType::Ptr GridTypePtr; typedef typename GridType::TreeType TreeType; typedef typename TreeType::LeafNodeType LeafNodeType; typedef typename GridType::ValueType ValueType; typedef typename GridType::template ValueConverter::Type Int32GridType; typedef typename Int32GridType::TreeType Int32TreeType; typedef typename TreeType::template ValueConverter::Type BoolTreeType; ////////// // Setup GridTypePtr distGrid(new GridType(std::numeric_limits::max())); distGrid->setTransform(transform.copy()); ValueType exteriorWidth = ValueType(exteriorBandWidth); ValueType interiorWidth = ValueType(interiorBandWidth); // inf interior width is all right, this value makes the converter fill // interior regions with distance values. if (!boost::math::isfinite(exteriorWidth) || boost::math::isnan(interiorWidth)) { std::stringstream msg; msg << "Illegal narrow band width: exterior = " << exteriorWidth << ", interior = " << interiorWidth; OPENVDB_LOG_DEBUG(msg.str()); return distGrid; } const ValueType voxelSize = ValueType(transform.voxelSize()[0]); if (!boost::math::isfinite(voxelSize) || math::isZero(voxelSize)) { std::stringstream msg; msg << "Illegal transform, voxel size = " << voxelSize; OPENVDB_LOG_DEBUG(msg.str()); return distGrid; } // convert narrow band width from voxel units to world space units. exteriorWidth *= voxelSize; // avoid the unit conversion if the interior band width is set to // inf or std::numeric_limits::max() if (interiorWidth < std::numeric_limits::max()) { interiorWidth *= voxelSize; } const bool computeSignedDistanceField = (flags & UNSIGNED_DISTANCE_FIELD) == 0; const bool removeIntersectingVoxels = (flags & DISABLE_INTERSECTING_VOXEL_REMOVAL) == 0; const bool renormalizeValues = (flags & DISABLE_RENORMALIZATION) == 0; const bool trimNarrowBand = (flags & DISABLE_NARROW_BAND_TRIMMING) == 0; Int32GridType* indexGrid = NULL; typename Int32GridType::Ptr temporaryIndexGrid; if (polygonIndexGrid) { indexGrid = polygonIndexGrid; } else { temporaryIndexGrid.reset(new Int32GridType(Int32(util::INVALID_IDX))); indexGrid = temporaryIndexGrid.get(); } indexGrid->newTree(); indexGrid->setTransform(transform.copy()); if (computeSignedDistanceField) { distGrid->setGridClass(GRID_LEVEL_SET); } else { distGrid->setGridClass(GRID_UNKNOWN); interiorWidth = ValueType(0.0); } TreeType& distTree = distGrid->tree(); Int32TreeType& indexTree = indexGrid->tree(); ////////// // Voxelize mesh { typedef mesh_to_volume_internal::VoxelizationData VoxelizationDataType; typedef tbb::enumerable_thread_specific DataTable; DataTable data; typedef mesh_to_volume_internal::VoxelizePolygons Voxelizer; const tbb::blocked_range polygonRange(0, mesh.polygonCount()); tbb::parallel_for(polygonRange, Voxelizer(data, mesh, &interrupter)); for (typename DataTable::iterator i = data.begin(); i != data.end(); ++i) { VoxelizationDataType& dataItem = **i; mesh_to_volume_internal::combineData(distTree, indexTree, dataItem.distTree, dataItem.indexTree); } } // the progress estimates are based on the observed average time for a few different // test cases and is only intended to provide some rough progression feedback to the user. if (interrupter.wasInterrupted(30)) return distGrid; ////////// // Classify interior and exterior regions if (computeSignedDistanceField) { // determines the inside/outside state for the narrow band of voxels. traceExteriorBoundaries(distTree); std::vector nodes; nodes.reserve(distTree.leafCount()); distTree.getNodes(nodes); const tbb::blocked_range nodeRange(0, nodes.size()); typedef mesh_to_volume_internal::ComputeIntersectingVoxelSign SignOp; tbb::parallel_for(nodeRange, SignOp(nodes, distTree, indexTree, mesh)); if (interrupter.wasInterrupted(45)) return distGrid; // remove voxels created by self intersecting portions of the mesh if (removeIntersectingVoxels) { tbb::parallel_for(nodeRange, mesh_to_volume_internal::ValidateIntersectingVoxels(distTree, nodes)); tbb::parallel_for(nodeRange, mesh_to_volume_internal::RemoveSelfIntersectingSurface(nodes, distTree, indexTree)); tools::pruneInactive(distTree, /*threading=*/true); tools::pruneInactive(indexTree, /*threading=*/true); } } if (interrupter.wasInterrupted(50)) return distGrid; if (distTree.activeVoxelCount() == 0) { distGrid.reset((new GridType(ValueType(0.0)))); return distGrid; } // transform values (world space scaling etc.) { std::vector nodes; nodes.reserve(distTree.leafCount()); distTree.getNodes(nodes); tbb::parallel_for(tbb::blocked_range(0, nodes.size()), mesh_to_volume_internal::TransformValues(nodes, voxelSize, !computeSignedDistanceField)); } // propagate sign information into tile regions if (computeSignedDistanceField) { distTree.root().setBackground(exteriorWidth, /*updateChildNodes=*/false); tools::signedFloodFillWithValues(distTree, exteriorWidth, -interiorWidth); } else { tools::changeBackground(distTree, exteriorWidth); } if (interrupter.wasInterrupted(54)) return distGrid; ////////// // Expand the narrow band region const ValueType minBandWidth = voxelSize * ValueType(2.0); if (interiorWidth > minBandWidth || exteriorWidth > minBandWidth) { // create the initial voxel mask. BoolTreeType maskTree(false); { std::vector nodes; nodes.reserve(distTree.leafCount()); distTree.getNodes(nodes); mesh_to_volume_internal::ConstructVoxelMask op(maskTree, distTree, nodes); tbb::parallel_reduce(tbb::blocked_range(0, nodes.size()), op); } // progress estimation unsigned maxIterations = std::numeric_limits::max(); float progress = 54.0f, step = 0.0f; double estimated = 2.0 * std::ceil((std::max(interiorWidth, exteriorWidth) - minBandWidth) / voxelSize); if (estimated < double(maxIterations)) { maxIterations = unsigned(estimated); step = 40.0f / float(maxIterations); } std::vector maskNodes; // expand unsigned count = 0; while (true) { if (interrupter.wasInterrupted(int(progress))) return distGrid; const size_t maskNodeCount = maskTree.leafCount(); if (maskNodeCount == 0) break; maskNodes.clear(); maskNodes.reserve(maskNodeCount); maskTree.getNodes(maskNodes); const tbb::blocked_range range(0, maskNodes.size()); tbb::parallel_for(range, mesh_to_volume_internal::DiffLeafNodeMask(distTree, maskNodes)); mesh_to_volume_internal::expandNarrowband(distTree, indexTree, maskTree, maskNodes, mesh, exteriorWidth, interiorWidth, voxelSize); if ((++count) >= maxIterations) break; progress += step; } } if (interrupter.wasInterrupted(94)) return distGrid; if (!polygonIndexGrid) indexGrid->clear(); ///////// // Renormalize distances to smooth out bumps caused by self intersecting // and overlapping portions of the mesh and renormalize the level set. if (computeSignedDistanceField && renormalizeValues) { std::vector nodes; nodes.reserve(distTree.leafCount()); distTree.getNodes(nodes); boost::scoped_array buffer(new ValueType[LeafNodeType::SIZE * nodes.size()]); const ValueType offset = ValueType(0.8 * voxelSize); tbb::parallel_for(tbb::blocked_range(0, nodes.size()), mesh_to_volume_internal::OffsetValues(nodes, -offset)); tbb::parallel_for(tbb::blocked_range(0, nodes.size()), mesh_to_volume_internal::Renormalize(distTree, nodes, buffer.get(), voxelSize)); tbb::parallel_for(tbb::blocked_range(0, nodes.size()), mesh_to_volume_internal::MinCombine(nodes, buffer.get())); tbb::parallel_for(tbb::blocked_range(0, nodes.size()), mesh_to_volume_internal::OffsetValues(nodes, offset - mesh_to_volume_internal::Tolerance::epsilon())); } if (interrupter.wasInterrupted(99)) return distGrid; ///////// // Remove active voxels that exceed the narrow band limits if (trimNarrowBand && std::min(interiorWidth, exteriorWidth) < voxelSize * ValueType(4.0)) { std::vector nodes; nodes.reserve(distTree.leafCount()); distTree.getNodes(nodes); tbb::parallel_for(tbb::blocked_range(0, nodes.size()), mesh_to_volume_internal::InactivateValues(nodes, exteriorWidth, computeSignedDistanceField ? interiorWidth : exteriorWidth)); tools::pruneLevelSet(distTree, exteriorWidth, computeSignedDistanceField ? -interiorWidth : -exteriorWidth); } return distGrid; } template inline typename GridType::Ptr meshToVolume( const MeshDataAdapter& mesh, const math::Transform& transform, float exteriorBandWidth, float interiorBandWidth, int flags, typename GridType::template ValueConverter::Type * polygonIndexGrid) { util::NullInterrupter nullInterrupter; return meshToVolume(nullInterrupter, mesh, transform, exteriorBandWidth, interiorBandWidth, flags, polygonIndexGrid); } //////////////////////////////////////// /// @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) { if (points.empty()) { return typename GridType::Ptr(new GridType(typename GridType::ValueType(exBandWidth))); } const size_t numPoints = points.size(); boost::scoped_array indexSpacePoints(new Vec3s[numPoints]); // transform points to local grid index space tbb::parallel_for(tbb::blocked_range(0, numPoints), mesh_to_volume_internal::TransformPoints( &points[0], indexSpacePoints.get(), xform)); const int conversionFlags = unsignedDistanceField ? UNSIGNED_DISTANCE_FIELD : 0; if (quads.empty()) { QuadAndTriangleDataAdapter mesh(indexSpacePoints.get(), numPoints, &triangles[0], triangles.size()); return meshToVolume(mesh, xform, exBandWidth, inBandWidth, conversionFlags); } else if (triangles.empty()) { QuadAndTriangleDataAdapter mesh(indexSpacePoints.get(), numPoints, &quads[0], quads.size()); return meshToVolume(mesh, xform, exBandWidth, inBandWidth, conversionFlags); } // pack primitives const size_t numPrimitives = triangles.size() + quads.size(); boost::scoped_array prims(new Vec4I[numPrimitives]); for (size_t n = 0, N = triangles.size(); n < N; ++n) { const Vec3I& triangle = triangles[n]; Vec4I& prim = prims[n]; prim[0] = triangle[0]; prim[1] = triangle[1]; prim[2] = triangle[2]; prim[3] = util::INVALID_IDX; } const size_t offset = triangles.size(); for (size_t n = 0, N = quads.size(); n < N; ++n) { prims[offset + n] = quads[n]; } QuadAndTriangleDataAdapter mesh(indexSpacePoints.get(), numPoints, prims.get(), numPrimitives); return meshToVolume(mesh, xform, exBandWidth, inBandWidth, conversionFlags); } /// @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 = Coord::floor(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 = float(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 = float(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 = float(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 = float(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 = float(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 = float(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); } } } template inline typename GridType::Ptr createLevelSetBox(const math::BBox& bbox, const openvdb::math::Transform& xform, typename VecType::ValueType halfWidth) { const Vec3s pmin = Vec3s(xform.worldToIndex(bbox.min())); const Vec3s pmax = Vec3s(xform.worldToIndex(bbox.max())); Vec3s points[8]; points[0] = Vec3s(pmin[0], pmin[1], pmin[2]); points[1] = Vec3s(pmin[0], pmin[1], pmax[2]); points[2] = Vec3s(pmax[0], pmin[1], pmax[2]); points[3] = Vec3s(pmax[0], pmin[1], pmin[2]); points[4] = Vec3s(pmin[0], pmax[1], pmin[2]); points[5] = Vec3s(pmin[0], pmax[1], pmax[2]); points[6] = Vec3s(pmax[0], pmax[1], pmax[2]); points[7] = Vec3s(pmax[0], pmax[1], pmin[2]); Vec4I faces[6]; faces[0] = Vec4I(0, 1, 2, 3); // bottom faces[1] = Vec4I(7, 6, 5, 4); // top faces[2] = Vec4I(4, 5, 1, 0); // front faces[3] = Vec4I(6, 7, 3, 2); // back faces[4] = Vec4I(0, 3, 7, 4); // left faces[5] = Vec4I(1, 5, 6, 2); // right QuadAndTriangleDataAdapter mesh(points, 8, faces, 6); return meshToVolume(mesh, xform, halfWidth, halfWidth); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/RayIntersector.h0000644000000000000000000007254412603226506015515 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 than 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 a 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, RealType &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, RealType &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, RealType &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, RealType &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, RealType &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 constructor. 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)//initialize new (vs 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 respect 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! /// @note Note that t0 and t1 are only resolved at the node level /// (e.g. a LeafNode with active voxels) as opposed to the individual /// active voxels. /// @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 respect to the ray represented in /// index space of the current grid, not world space! inline bool march(RealType& t0, RealType& t1) { const typename RayT::TimeSpan t = this->march(); t.get(t0, t1); return t.valid(); } /// @brief Generates a list of hits along the ray. /// /// @param list List of hits represented as time spans. /// /// @note ListType is a list of RayType::TimeSpan and is required to /// have the two methods: clear() and push_back(). Thus, it could /// be std::vector or /// std::deque. template inline void hits(ListType& 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(RealType time) const { return mRay(time); } /// @brief Return the floating-point world position along the /// current index ray at the specified time. inline Vec3R getWorldPos(RealType time) const { return mGrid->indexToWorld(mRay(time)); } inline RealType getWorldTime(RealType 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; RealType mTmax; math::CoordBBox mBBox; math::VolumeHDDA mHDDA; };// VolumeRayIntersector //////////////////////////////////////// LinearSearchImpl //////////////////////////////////////// /// @brief Implements linear iterative search for an iso-value of /// the level set 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 than 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; /// @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 - ValueT(2 * grid.voxelSize()[0])), mMaxValue(isoValue + ValueT(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 if 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 if 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] = static_cast(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 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 an 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 && V(this->interpValue(time)); if (math::ZeroCrossing(mV[0], mV[1])) { mTime = this->interpTime(); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN for (int n=0; Iterations>0 && n(this->interpValue(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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/PointPartitioner.h0000644000000000000000000010637612603226506016053 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 PointPartitioner.h /// /// @brief Spatially partitions points using a parallel radix-based /// sorting algorithm. /// /// @details Performs a stable deterministic sort; partitioning the same /// point sequence will produce the same result each time. /// @details The algorithm is unbounded meaning that points may be /// distributed anywhere in index space. /// @details The actual points are never stored in the tool, only /// offsets into an external array. /// /// @author Mihai Alden #ifndef OPENVDB_TOOLS_POINT_PARTITIONER_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_POINT_PARTITIONER_HAS_BEEN_INCLUDED #include #include #include #include #include #include // std::pair #include #include // boost::int_t::least #include #include #include // boost::math::isfinite #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { //////////////////////////////////////// /// @brief Partitions points into @c BucketLog2Dim aligned buckets /// using a parallel radix-based sorting algorithm. /// /// @interface PointArray /// Expected interface for the PointArray container /// @code /// template /// struct PointList { /// typedef VectorType value_type; /// size_t size() const; // total number of points /// void getPos(size_t n, VectorType& xyz) const; /// }; /// @endcode /// /// @details Performs a stable deterministic sort; partitioning the same /// point sequence will produce the same result each time. /// @details The algorithm is unbounded meaning that points may be /// distributed anywhere in index space. /// @details The actual points are never stored in the tool, only /// offsets into an external array. /// @details @c BucketLog2Dim defines the bucket coordinate dimensions, /// i.e. BucketLog2Dim = 3 corresponds to a bucket that spans /// a (2^3)^3 = 8^3 voxel region. template class PointPartitioner { public: enum { LOG2DIM = BucketLog2Dim }; typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; typedef PointIndexType IndexType; typedef typename boost::int_t<1 + (3 * BucketLog2Dim)>::least VoxelOffsetType; typedef boost::scoped_array VoxelOffsetArray; class IndexIterator; ////////// PointPartitioner(); /// @brief Partitions point indices into @c BucketLog2Dim aligned buckets. /// /// @param points list of world space points. /// @param xform world to index space transform. /// @param voxelOrder sort point indices by local voxel offsets. /// @param recordVoxelOffsets construct local voxel offsets template void construct(const PointArray& points, const math::Transform& xform, bool voxelOrder = false, bool recordVoxelOffsets = false); /// @brief Partitions point indices into @c BucketLog2Dim aligned buckets. /// /// @param points list of world space points. /// @param xform world to index space transform. /// @param voxelOrder sort point indices by local voxel offsets. /// @param recordVoxelOffsets construct local voxel offsets template static Ptr create(const PointArray& points, const math::Transform& xform, bool voxelOrder = false, bool recordVoxelOffsets = false); /// @brief Returns the number of buckets. size_t size() const { return mPageCount; } /// @brief true if the container size is 0, false otherwise. bool empty() const { return mPageCount == 0; } /// @brief Removes all data and frees up memory. void clear(); /// @brief Exchanges the content of the container by another. void swap(PointPartitioner&); /// @brief Returns the point indices for bucket @a n IndexIterator indices(size_t n) const; /// @brief Returns the coordinate-aligned bounding box for bucket @a n CoordBBox getBBox(size_t n) const { return CoordBBox::createCube(mPageCoordinates[n], (1u << BucketLog2Dim)); } /// @brief Returns the origin coordinate for bucket @a n const Coord& origin(size_t n) const { return mPageCoordinates[n]; } /// @brief Returns a list of @c LeafNode voxel offsets for the points. /// @note The list is optionally constructed. const VoxelOffsetArray& voxelOffsets() const { return mVoxelOffsets; } private: // Disallow copying PointPartitioner(const PointPartitioner&); PointPartitioner& operator=(const PointPartitioner&); boost::scoped_array mPointIndices; VoxelOffsetArray mVoxelOffsets; boost::scoped_array mPageOffsets; boost::scoped_array mPageCoordinates; IndexType mPageCount; }; // class PointPartitioner typedef PointPartitioner UInt32PointPartitioner; template class PointPartitioner::IndexIterator { public: typedef PointIndexType IndexType; IndexIterator(IndexType* begin = NULL, IndexType* end = NULL) : mBegin(begin), mEnd(end), mItem(begin) {} /// @brief Rewind to first item. void reset() { mItem = mBegin; } /// @brief Number of point indices in the iterator range. size_t size() const { return mEnd - mBegin; } /// @brief Returns the item to which this iterator is currently pointing. IndexType& operator*() { assert(mItem != NULL); return *mItem; } const IndexType& operator*() const { assert(mItem != NULL); return *mItem; } /// @brief Return @c true if this iterator is not yet exhausted. operator bool() const { return mItem < mEnd; } bool test() const { return mItem < mEnd; } /// @brief Advance to the next item. IndexIterator& operator++() { assert(this->test()); ++mItem; return *this; } /// @brief Advance to the next item. bool next() { this->operator++(); return this->test(); } bool increment() { this->next(); return this->test(); } /// @brief Equality operators bool operator==(const IndexIterator& other) const { return mItem == other.mItem; } bool operator!=(const IndexIterator& other) const { return !this->operator==(other); } private: IndexType * const mBegin, * const mEnd; IndexType * mItem; }; // class PointPartitioner::IndexIterator //////////////////////////////////////// //////////////////////////////////////// // Implementation details namespace point_partitioner_internal { template struct ComputePointOrderOp { ComputePointOrderOp(PointIndexType* pointOrder, const PointIndexType* bucketCounters, const PointIndexType* bucketOffsets) : mPointOrder(pointOrder) , mBucketCounters(bucketCounters) , mBucketOffsets(bucketOffsets) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n != N; ++n) { mPointOrder[n] += mBucketCounters[mBucketOffsets[n]]; } } PointIndexType * const mPointOrder; PointIndexType const * const mBucketCounters; PointIndexType const * const mBucketOffsets; }; // struct ComputePointOrderOp template struct CreateOrderedPointIndexArrayOp { CreateOrderedPointIndexArrayOp(PointIndexType* orderedIndexArray, const PointIndexType* pointOrder, const PointIndexType* indices) : mOrderedIndexArray(orderedIndexArray) , mPointOrder(pointOrder) , mIndices(indices) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n != N; ++n) { mOrderedIndexArray[mPointOrder[n]] = mIndices[n]; } } PointIndexType * const mOrderedIndexArray; PointIndexType const * const mPointOrder; PointIndexType const * const mIndices; }; // struct CreateOrderedPointIndexArrayOp template struct VoxelOrderOp { typedef typename boost::int_t<1 + (3 * BucketLog2Dim)>::least VoxelOffsetType; typedef boost::scoped_array VoxelOffsetArray; typedef boost::scoped_array IndexArray; VoxelOrderOp(IndexArray& indices, const IndexArray& pages,const VoxelOffsetArray& offsets) : mIndices(indices.get()) , mPages(pages.get()) , mVoxelOffsets(offsets.get()) { } void operator()(const tbb::blocked_range& range) const { PointIndexType pointCount = 0; for (size_t n(range.begin()), N(range.end()); n != N; ++n) { pointCount = std::max(pointCount, (mPages[n + 1] - mPages[n])); } const PointIndexType voxelCount = 1 << (3 * BucketLog2Dim); // allocate histogram buffers boost::scoped_array offsets(new VoxelOffsetType[pointCount]); boost::scoped_array sortedIndices(new PointIndexType[pointCount]); boost::scoped_array histogram(new PointIndexType[voxelCount]); for (size_t n(range.begin()), N(range.end()); n != N; ++n) { PointIndexType * const indices = mIndices + mPages[n]; pointCount = mPages[n + 1] - mPages[n]; // local copy of voxel offsets. for (PointIndexType i = 0; i < pointCount; ++i) { offsets[i] = mVoxelOffsets[ indices[i] ]; } // reset histogram memset(&histogram[0], 0, voxelCount * sizeof(PointIndexType)); // compute histogram for (PointIndexType i = 0; i < pointCount; ++i) { ++histogram[ offsets[i] ]; } PointIndexType count = 0, startOffset; for (int i = 0; i < int(voxelCount); ++i) { if (histogram[i] > 0) { startOffset = count; count += histogram[i]; histogram[i] = startOffset; } } // sort indices based on voxel offset for (PointIndexType i = 0; i < pointCount; ++i) { sortedIndices[ histogram[ offsets[i] ]++ ] = indices[i]; } memcpy(&indices[0], &sortedIndices[0], sizeof(PointIndexType) * pointCount); } } PointIndexType * const mIndices; PointIndexType const * const mPages; VoxelOffsetType const * const mVoxelOffsets; }; // struct VoxelOrderOp template struct LeafNodeOriginOp { typedef boost::scoped_array IndexArray; typedef boost::scoped_array CoordArray; LeafNodeOriginOp(CoordArray& coordinates, const IndexArray& indices, const IndexArray& pages, const PointArray& points, const math::Transform& m, int log2dim) : mCoordinates(coordinates.get()) , mIndices(indices.get()) , mPages(pages.get()) , mPoints(&points) , mXForm(m) , mLog2Dim(log2dim) { } void operator()(const tbb::blocked_range& range) const { typedef typename PointArray::value_type PointType; const int mask = ~((1 << mLog2Dim) - 1); Coord ijk; PointType pos; for (size_t n = range.begin(), N = range.end(); n != N; ++n) { mPoints->getPos(mIndices[mPages[n]], pos); if (boost::math::isfinite(pos[0]) && boost::math::isfinite(pos[1]) && boost::math::isfinite(pos[2])) { ijk = mXForm.worldToIndexCellCentered(pos); ijk[0] &= mask; ijk[1] &= mask; ijk[2] &= mask; mCoordinates[n] = ijk; } } } Coord * const mCoordinates; PointIndexType const * const mIndices; PointIndexType const * const mPages; PointArray const * const mPoints; math::Transform const mXForm; int const mLog2Dim; }; // struct LeafNodeOriginOp //////////////////////////////////////// template struct Array { typedef boost::shared_ptr Ptr; Array(size_t size) : mSize(size), mData(new T[size]) { } size_t size() const { return mSize; } T* data() { return mData.get(); } const T* data() const { return mData.get(); } void clear() { mSize = 0; mData.reset(); } private: size_t mSize; boost::scoped_array mData; }; // struct Array template struct MoveSegmentDataOp { typedef Array Segment; typedef typename Segment::Ptr SegmentPtr; MoveSegmentDataOp(std::vector& indexLists, SegmentPtr* segments) : mIndexLists(&indexLists[0]), mSegments(segments) { } void operator()(const tbb::blocked_range& range) const { for (size_t n(range.begin()), N(range.end()); n != N; ++n) { PointIndexType* indices = mIndexLists[n]; SegmentPtr& segment = mSegments[n]; tbb::parallel_for(tbb::blocked_range(0, segment->size()), CopyData(indices, segment->data())); segment.reset(); // clear data } } private: struct CopyData { CopyData(PointIndexType* lhs, const PointIndexType* rhs) : mLhs(lhs), mRhs(rhs) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n != N; ++n) { mLhs[n] = mRhs[n]; } } PointIndexType * const mLhs; PointIndexType const * const mRhs; }; PointIndexType * const * const mIndexLists; SegmentPtr * const mSegments; }; // struct MoveSegmentDataOp template struct MergeBinsOp { typedef Array Segment; typedef typename Segment::Ptr SegmentPtr; typedef std::pair IndexPair; typedef std::deque IndexPairList; typedef boost::shared_ptr IndexPairListPtr; typedef std::map IndexPairListMap; typedef boost::shared_ptr IndexPairListMapPtr; MergeBinsOp(IndexPairListMapPtr* bins, SegmentPtr* indexSegments, SegmentPtr* offsetSegments, Coord* coords, size_t numSegments) : mBins(bins) , mIndexSegments(indexSegments) , mOffsetSegments(offsetSegments) , mCoords(coords) , mNumSegments(numSegments) { } void operator()(const tbb::blocked_range& range) const { std::vector data; std::vector arrayOffsets; for (size_t n = range.begin(), N = range.end(); n != N; ++n) { const Coord& ijk = mCoords[n]; size_t numIndices = 0; data.clear(); for (size_t i = 0, I = mNumSegments; i < I; ++i) { IndexPairListMap& idxMap = *mBins[i]; typename IndexPairListMap::iterator iter = idxMap.find(ijk); if (iter != idxMap.end() && iter->second) { IndexPairListPtr& idxListPtr = iter->second; data.push_back(&idxListPtr); numIndices += idxListPtr->size(); } } if (data.empty() || numIndices == 0) continue; SegmentPtr& indexSegment = mIndexSegments[n]; SegmentPtr& offsetSegment = mOffsetSegments[n]; indexSegment.reset(new Segment(numIndices)); offsetSegment.reset(new Segment(numIndices)); arrayOffsets.clear(); arrayOffsets.reserve(data.size()); for (size_t i = 0, count = 0, I = data.size(); i < I; ++i) { arrayOffsets.push_back(PointIndexType(count)); count += (*data[i])->size(); } tbb::parallel_for(tbb::blocked_range(0, data.size()), CopyData(&data[0], &arrayOffsets[0], indexSegment->data(), offsetSegment->data())); } } private: struct CopyData { CopyData(IndexPairListPtr** indexLists, const PointIndexType* arrayOffsets, PointIndexType* indices, PointIndexType* offsets) : mIndexLists(indexLists) , mArrayOffsets(arrayOffsets) , mIndices(indices) , mOffsets(offsets) { } void operator()(const tbb::blocked_range& range) const { typedef typename IndexPairList::const_iterator CIter; for (size_t n = range.begin(), N = range.end(); n != N; ++n) { const PointIndexType arrayOffset = mArrayOffsets[n]; PointIndexType* indexPtr = &mIndices[arrayOffset]; PointIndexType* offsetPtr = &mOffsets[arrayOffset]; IndexPairListPtr& list = *mIndexLists[n]; for (CIter it = list->begin(), end = list->end(); it != end; ++it) { const IndexPair& data = *it; *indexPtr++ = data.first; *offsetPtr++ = data.second; } list.reset(); // clear data } } IndexPairListPtr * const * const mIndexLists; PointIndexType const * const mArrayOffsets; PointIndexType * const mIndices; PointIndexType * const mOffsets; }; // struct CopyData IndexPairListMapPtr * const mBins; SegmentPtr * const mIndexSegments; SegmentPtr * const mOffsetSegments; Coord const * const mCoords; size_t const mNumSegments; }; // struct MergeBinsOp template struct BinPointIndicesOp { typedef typename PointArray::value_type PointType; typedef typename PointType::value_type PointElementType; typedef std::pair IndexPair; typedef std::deque IndexPairList; typedef boost::shared_ptr IndexPairListPtr; typedef std::map IndexPairListMap; typedef boost::shared_ptr IndexPairListMapPtr; BinPointIndicesOp(IndexPairListMapPtr* data, const PointArray& points, VoxelOffsetType* voxelOffsets, const math::Transform& m, Index binLog2Dim, Index bucketLog2Dim, size_t numSegments) : mData(data) , mPoints(&points) , mVoxelOffsets(voxelOffsets) , mXForm(m) , mBinLog2Dim(binLog2Dim) , mBucketLog2Dim(bucketLog2Dim) , mNumSegments(numSegments) { } void operator()(const tbb::blocked_range& range) const { const Index log2dim = mBucketLog2Dim; const Index log2dim2 = 2 * log2dim; const Index bucketMask = (1u << log2dim) - 1u; const Index binLog2dim = mBinLog2Dim; const Index binLog2dim2 = 2 * binLog2dim; const Index binMask = (1u << (log2dim + binLog2dim)) - 1u; const Index invBinMask = ~binMask; IndexPairList * idxList = NULL; Coord ijk(0, 0, 0), loc(0, 0, 0), binCoord(0, 0, 0), lastBinCoord(1, 2, 3); PointType pos; PointIndexType bucketOffset = 0; VoxelOffsetType voxelOffset = 0; const size_t numPoints = mPoints->size(); const size_t segmentSize = numPoints / mNumSegments; for (size_t n = range.begin(), N = range.end(); n != N; ++n) { IndexPairListMapPtr& dataPtr = mData[n]; if (!dataPtr) dataPtr.reset(new IndexPairListMap()); IndexPairListMap& idxMap = *dataPtr; const bool isLastSegment = (n + 1) >= mNumSegments; const size_t start = n * segmentSize; const size_t end = isLastSegment ? numPoints : (start + segmentSize); for (size_t i = start; i != end; ++i) { mPoints->getPos(i, pos); if (boost::math::isfinite(pos[0]) && boost::math::isfinite(pos[1]) && boost::math::isfinite(pos[2])) { ijk = mXForm.worldToIndexCellCentered(pos); if (mVoxelOffsets) { loc[0] = ijk[0] & bucketMask; loc[1] = ijk[1] & bucketMask; loc[2] = ijk[2] & bucketMask; voxelOffset = VoxelOffsetType((loc[0] << log2dim2) + (loc[1] << log2dim) + loc[2]); } binCoord[0] = ijk[0] & invBinMask; binCoord[1] = ijk[1] & invBinMask; binCoord[2] = ijk[2] & invBinMask; ijk[0] &= binMask; ijk[1] &= binMask; ijk[2] &= binMask; ijk[0] >>= log2dim; ijk[1] >>= log2dim; ijk[2] >>= log2dim; bucketOffset = PointIndexType((ijk[0] << binLog2dim2) + (ijk[1] << binLog2dim) + ijk[2]); if (lastBinCoord != binCoord) { lastBinCoord = binCoord; IndexPairListPtr& idxListPtr = idxMap[lastBinCoord]; if (!idxListPtr) idxListPtr.reset(new IndexPairList()); idxList = idxListPtr.get(); } idxList->push_back(IndexPair(PointIndexType(i), bucketOffset)); if (mVoxelOffsets) mVoxelOffsets[i] = voxelOffset; } } } } IndexPairListMapPtr * const mData; PointArray const * const mPoints; VoxelOffsetType * const mVoxelOffsets; math::Transform const mXForm; Index const mBinLog2Dim; Index const mBucketLog2Dim; size_t const mNumSegments; }; // struct BinPointIndicesOp template struct OrderSegmentsOp { typedef boost::scoped_array IndexArray; typedef typename Array::Ptr SegmentPtr; OrderSegmentsOp(SegmentPtr* indexSegments, SegmentPtr* offestSegments, IndexArray* pageOffsetArrays, Index binVolume) : mIndexSegments(indexSegments) , mOffsetSegments(offestSegments) , mPageOffsetArrays(pageOffsetArrays) , mBinVolume(binVolume) { } void operator()(const tbb::blocked_range& range) const { const size_t bucketCountersSize = size_t(mBinVolume); IndexArray bucketCounters(new PointIndexType[bucketCountersSize]); size_t maxSegmentSize = 0; for (size_t n = range.begin(), N = range.end(); n != N; ++n) { maxSegmentSize = std::max(maxSegmentSize, mIndexSegments[n]->size()); } IndexArray bucketIndices(new PointIndexType[maxSegmentSize]); for (size_t n = range.begin(), N = range.end(); n != N; ++n) { memset(bucketCounters.get(), 0, sizeof(PointIndexType) * bucketCountersSize); const size_t segmentSize = mOffsetSegments[n]->size(); PointIndexType* offsets = mOffsetSegments[n]->data(); // Count the number of points per bucket and assign a local bucket index // to each point. for (size_t i = 0; i < segmentSize; ++i) { bucketIndices[i] = bucketCounters[offsets[i]]++; } PointIndexType nonemptyBucketCount = 0; for (size_t i = 0; i < bucketCountersSize; ++i) { nonemptyBucketCount += static_cast(bucketCounters[i] != 0); } IndexArray& pageOffsets = mPageOffsetArrays[n]; pageOffsets.reset(new PointIndexType[nonemptyBucketCount + 1]); pageOffsets[0] = nonemptyBucketCount + 1; // stores array size in first element // Compute bucket counter prefix sum PointIndexType count = 0, idx = 1; for (size_t i = 0; i < bucketCountersSize; ++i) { if (bucketCounters[i] != 0) { pageOffsets[idx] = bucketCounters[i]; bucketCounters[i] = count; count += pageOffsets[idx]; ++idx; } } PointIndexType* indices = mIndexSegments[n]->data(); const tbb::blocked_range segmentRange(0, segmentSize); // Compute final point order by incrementing the local bucket point index // with the prefix sum offset. tbb::parallel_for(segmentRange, ComputePointOrderOp( bucketIndices.get(), bucketCounters.get(), offsets)); tbb::parallel_for(segmentRange, CreateOrderedPointIndexArrayOp( offsets, bucketIndices.get(), indices)); mIndexSegments[n]->clear(); // clear data } } SegmentPtr * const mIndexSegments; SegmentPtr * const mOffsetSegments; IndexArray * const mPageOffsetArrays; Index const mBinVolume; }; // struct OrderSegmentsOp //////////////////////////////////////// /// @brief Segment points using one level of least significant digit radix bins. template inline void binAndSegment( const PointArray& points, const math::Transform& xform, boost::scoped_array::Ptr>& indexSegments, boost::scoped_array::Ptr>& offsetSegments, size_t& segmentCount, const Index binLog2Dim, const Index bucketLog2Dim, VoxelOffsetType* voxelOffsets = NULL) { typedef std::pair IndexPair; typedef std::deque IndexPairList; typedef boost::shared_ptr IndexPairListPtr; typedef std::map IndexPairListMap; typedef boost::shared_ptr IndexPairListMapPtr; size_t numTasks = 1, numThreads = size_t(tbb::task_scheduler_init::default_num_threads()); if (points.size() > (numThreads * 2)) numTasks = numThreads * 2; else if (points.size() > numThreads) numTasks = numThreads; boost::scoped_array bins(new IndexPairListMapPtr[numTasks]); typedef BinPointIndicesOp BinOp; tbb::parallel_for(tbb::blocked_range(0, numTasks), BinOp(bins.get(), points, voxelOffsets, xform, binLog2Dim, bucketLog2Dim, numTasks)); std::set uniqueCoords; for (size_t i = 0; i < numTasks; ++i) { IndexPairListMap& idxMap = *bins[i]; for (typename IndexPairListMap::iterator it = idxMap.begin(); it != idxMap.end(); ++it) { uniqueCoords.insert(it->first); } } std::vector coords(uniqueCoords.begin(), uniqueCoords.end()); uniqueCoords.clear(); segmentCount = coords.size(); typedef typename Array::Ptr SegmentPtr; indexSegments.reset(new SegmentPtr[segmentCount]); offsetSegments.reset(new SegmentPtr[segmentCount]); typedef MergeBinsOp MergeOp; tbb::parallel_for(tbb::blocked_range(0, segmentCount), MergeOp(bins.get(), indexSegments.get(), offsetSegments.get(), &coords[0], numTasks)); } template inline void partition( const PointArray& points, const math::Transform& xform, const Index bucketLog2Dim, boost::scoped_array& pointIndices, boost::scoped_array& pageOffsets, PointIndexType& pageCount, boost::scoped_array& voxelOffsets, bool recordVoxelOffsets) { if (recordVoxelOffsets) voxelOffsets.reset(new VoxelOffsetType[points.size()]); else voxelOffsets.reset(); const Index binLog2Dim = 5u; // note: Bins span a (2^(binLog2Dim + bucketLog2Dim))^3 voxel region, // i.e. bucketLog2Dim = 3 and binLog2Dim = 5 corresponds to a // (2^8)^3 = 256^3 voxel region. size_t numSegments = 0; boost::scoped_array::Ptr> indexSegments; boost::scoped_array::Ptr> offestSegments; binAndSegment(points, xform, indexSegments, offestSegments, numSegments, binLog2Dim, bucketLog2Dim, voxelOffsets.get()); const tbb::blocked_range segmentRange(0, numSegments); typedef boost::scoped_array IndexArray; boost::scoped_array pageOffsetArrays(new IndexArray[numSegments]); const Index binVolume = 1u << (3u * binLog2Dim); tbb::parallel_for(segmentRange, OrderSegmentsOp (indexSegments.get(), offestSegments.get(), pageOffsetArrays.get(), binVolume)); indexSegments.reset(); pageCount = 0; for (size_t n = 0; n < numSegments; ++n) { pageCount += pageOffsetArrays[n][0] - 1; } pageOffsets.reset(new PointIndexType[pageCount + 1]); PointIndexType count = 0; for (size_t n = 0, idx = 0; n < numSegments; ++n) { PointIndexType* offsets = pageOffsetArrays[n].get(); size_t size = size_t(offsets[0]); for (size_t i = 1; i < size; ++i) { pageOffsets[idx++] = count; count += offsets[i]; } } pageOffsets[pageCount] = count; pointIndices.reset(new PointIndexType[points.size()]); std::vector indexArray; indexArray.reserve(numSegments); PointIndexType* index = pointIndices.get(); for (size_t n = 0; n < numSegments; ++n) { indexArray.push_back(index); index += offestSegments[n]->size(); } tbb::parallel_for(segmentRange, MoveSegmentDataOp(indexArray, offestSegments.get())); } } // namespace point_partitioner_internal //////////////////////////////////////// template inline PointPartitioner::PointPartitioner() : mPointIndices(NULL) , mVoxelOffsets(NULL) , mPageOffsets(NULL) , mPageCoordinates(NULL) , mPageCount(0) { } template inline void PointPartitioner::clear() { mPageCount = 0; mPointIndices.reset(); mVoxelOffsets.reset(); mPageOffsets.reset(); mPageCoordinates.reset(); } template inline void PointPartitioner::swap(PointPartitioner& rhs) { const IndexType tmpLhsPageCount = mPageCount; mPageCount = rhs.mPageCount; rhs.mPageCount = tmpLhsPageCount; mPointIndices.swap(rhs.mPointIndices); mVoxelOffsets.swap(rhs.mVoxelOffsets); mPageOffsets.swap(rhs.mPageOffsets); mPageCoordinates.swap(rhs.mPageCoordinates); } template inline typename PointPartitioner::IndexIterator PointPartitioner::indices(size_t n) const { assert(bool(mPointIndices) && bool(mPageCount)); return IndexIterator( mPointIndices.get() + mPageOffsets[n], mPointIndices.get() + mPageOffsets[n + 1]); } template template inline void PointPartitioner::construct(const PointArray& points, const math::Transform& xform, bool voxelOrder, bool recordVoxelOffsets) { point_partitioner_internal::partition(points, xform, BucketLog2Dim, mPointIndices, mPageOffsets, mPageCount, mVoxelOffsets, (voxelOrder || recordVoxelOffsets)); const tbb::blocked_range pageRange(0, mPageCount); mPageCoordinates.reset(new Coord[mPageCount]); tbb::parallel_for(pageRange, point_partitioner_internal::LeafNodeOriginOp (mPageCoordinates, mPointIndices, mPageOffsets, points, xform, BucketLog2Dim)); if (mVoxelOffsets && voxelOrder) { tbb::parallel_for(pageRange, point_partitioner_internal::VoxelOrderOp< IndexType, BucketLog2Dim>(mPointIndices, mPageOffsets, mVoxelOffsets)); } if (mVoxelOffsets && !recordVoxelOffsets) { mVoxelOffsets.reset(); } } template template inline typename PointPartitioner::Ptr PointPartitioner::create(const PointArray& points, const math::Transform& xform, bool voxelOrder, bool recordVoxelOffsets) { Ptr ret(new PointPartitioner()); ret->construct(points, xform, voxelOrder, recordVoxelOffsets); return ret; } //////////////////////////////////////// } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_POINT_PARTITIONER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/ChangeBackground.h0000644000000000000000000002477212603226506015725 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 ChangeBackground.h /// /// @brief Efficient multi-threaded replacement of the background /// values in tree. /// /// @author Ken Museth #ifndef OPENVDB_TOOLS_ChangeBACKGROUND_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_ChangeBACKGROUND_HAS_BEEN_INCLUDED #include // for isNegative and negative #include // for Index typedef #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Replace the background value in all the nodes of a tree. /// @details The sign of the background value is preserved, and only /// inactive values equal to the old background value are replaced. /// /// @note If a LeafManager is used the cached leaf nodes are reused, /// resulting in slightly better overall performance. /// /// @param tree Tree (or LeafManager) that will have its background value changed /// @param background the new background value /// @param threaded enable or disable threading (threading is enabled by default) /// @param grainSize used to control the threading granularity (default is 32) template inline void changeBackground( TreeOrLeafManagerT& tree, const typename TreeOrLeafManagerT::ValueType& background, bool threaded = true, size_t grainSize = 32); /// @brief Replace the background value in all the nodes of a floating-point tree /// containing a symmetric narrow-band level set. /// @details All inactive values will be set to +| @a halfWidth | if outside /// and -| @a halfWidth | if inside, where @a halfWidth is half the width /// of the symmetric narrow band. /// /// @note This method is faster than changeBackground since it does not /// perform tests to see if inactive values are equal to the old background value. /// @note If a LeafManager is used the cached leaf nodes are reused, /// resulting in slightly better overall performance. /// /// @param tree Tree (or LeafManager) that will have its background value changed /// @param halfWidth half of the width of the symmetric narrow band /// @param threaded enable or disable threading (threading is enabled by default) /// @param grainSize used to control the threading granularity (default is 32) /// /// @throw ValueError if @a halfWidth is negative (as defined by math::isNegative) template inline void changeLevelSetBackground( TreeOrLeafManagerT& tree, const typename TreeOrLeafManagerT::ValueType& halfWidth, bool threaded = true, size_t grainSize = 32); /// @brief Replace the background values in all the nodes of a floating-point tree /// containing a possibly asymmetric narrow-band level set. /// @details All inactive values will be set to +| @a outsideWidth | if outside /// and -| @a insideWidth | if inside, where @a outsideWidth is the outside /// width of the narrow band and @a insideWidth is its inside width. /// /// @note This method is faster than changeBackground since it does not /// perform tests to see if inactive values are equal to the old background value. /// @note If a LeafManager is used the cached leaf nodes are reused, /// resulting in slightly better overall performance. /// /// @param tree Tree (or LeafManager) that will have its background value changed /// @param outsideWidth The width of the outside of the narrow band /// @param insideWidth The width of the inside of the narrow band /// @param threaded enable or disable threading (threading is enabled by default) /// @param grainSize used to control the threading granularity (default is 32) /// /// @throw ValueError if @a outsideWidth is negative or @a insideWidth is /// not negative (as defined by math::isNegative) template inline void changeAsymmetricLevelSetBackground( TreeOrLeafManagerT& tree, const typename TreeOrLeafManagerT::ValueType& outsideWidth, const typename TreeOrLeafManagerT::ValueType& insideWidth, bool threaded = true, size_t grainSize = 32); ////////////////////////////////////////////////////// // Replaces the background value in a Tree of any type. template class ChangeBackgroundOp { public: typedef typename TreeOrLeafManagerT::ValueType ValueT; typedef typename TreeOrLeafManagerT::RootNodeType RootT; typedef typename TreeOrLeafManagerT::LeafNodeType LeafT; ChangeBackgroundOp(const TreeOrLeafManagerT& tree, const ValueT& newValue) : mOldValue(tree.root().background()) , mNewValue(newValue) { } void operator()(RootT& root) const { for (typename RootT::ValueOffIter it = root.beginValueOff(); it; ++it) this->set(it); root.setBackground(mNewValue, false); } void operator()(LeafT& node) const { for (typename LeafT::ValueOffIter it = node.beginValueOff(); it; ++it) this->set(it); } template void operator()(NodeT& node) const { typename NodeT::NodeMaskType mask = node.getValueOffMask(); for (typename NodeT::ValueOnIter it(mask.beginOn(), &node); it; ++it) this->set(it); } private: template inline void set(IterT& iter) const { if (math::isApproxEqual(*iter, mOldValue)) { iter.setValue(mNewValue); } else if (math::isApproxEqual(*iter, math::negative(mOldValue))) { iter.setValue(math::negative(mNewValue)); } } const ValueT mOldValue, mNewValue; };// ChangeBackgroundOp // Replaces the background value in a Tree assumed to represent a // level set. It is generally faster than ChangeBackgroundOp. // Note that is follows the sign-convention that outside is positive // and inside is negative! template class ChangeLevelSetBackgroundOp { public: typedef typename TreeOrLeafManagerT::ValueType ValueT; typedef typename TreeOrLeafManagerT::RootNodeType RootT; typedef typename TreeOrLeafManagerT::LeafNodeType LeafT; /// @brief Constructor for asymmetric narrow-bands ChangeLevelSetBackgroundOp(const ValueT& outside, const ValueT& inside) : mOutside(outside) , mInside(inside) { if (math::isNegative(mOutside)) { OPENVDB_THROW(ValueError, "ChangeLevelSetBackgroundOp: the outside value cannot be negative!"); } if (!math::isNegative(mInside)) { OPENVDB_THROW(ValueError, "ChangeLevelSetBackgroundOp: the inside value must be negative!"); } } void operator()(RootT& root) const { for (typename RootT::ValueOffIter it = root.beginValueOff(); it; ++it) this->set(it); root.setBackground(mOutside, false); } void operator()(LeafT& node) const { for(typename LeafT::ValueOffIter it = node.beginValueOff(); it; ++it) this->set(it); } template void operator()(NodeT& node) const { typedef typename NodeT::ValueOffIter IterT; for (IterT it(node.getChildMask().beginOff(), &node); it; ++it) this->set(it); } private: template inline void set(IterT& iter) const { //this is safe since we know ValueType is_floating_point ValueT& v = const_cast(*iter); v = v < 0 ? mInside : mOutside; } const ValueT mOutside, mInside; };// ChangeLevelSetBackgroundOp template inline void changeBackground( TreeOrLeafManagerT& tree, const typename TreeOrLeafManagerT::ValueType& background, bool threaded, size_t grainSize) { tree::NodeManager linearTree(tree); ChangeBackgroundOp op(tree, background); linearTree.processTopDown(op, threaded, grainSize); } template inline void changeAsymmetricLevelSetBackground( TreeOrLeafManagerT& tree, const typename TreeOrLeafManagerT::ValueType& outsideValue, const typename TreeOrLeafManagerT::ValueType& insideValue, bool threaded, size_t grainSize) { tree::NodeManager linearTree(tree); ChangeLevelSetBackgroundOp op(outsideValue, insideValue); linearTree.processTopDown(op, threaded, grainSize); } // If the narrow-band is symmetric only one background value is required template inline void changeLevelSetBackground( TreeOrLeafManagerT& tree, const typename TreeOrLeafManagerT::ValueType& background, bool threaded, size_t grainSize) { changeAsymmetricLevelSetBackground( tree, background, math::negative(background), threaded, grainSize); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_CHANGEBACKGROUND_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/GridTransformer.h0000644000000000000000000010776312603226506015652 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 "ChangeBackground.h" #include "Interpolation.h" #include "LevelSetRebuild.h" // for doLevelSetRebuild() #include "SignedFloodFill.h" // for signedFloodFill #include "Prune.h" // for pruneLevelSet 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 class TileSampler: public PointSampler { public: TileSampler(const CoordBBox&, const typename TreeT::ValueType&, bool) {} }; /// @brief For point sampling, tree traversal is less expensive than testing /// bounding box membership. template class TileSampler: public StaggeredPointSampler { public: 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 { tools::changeBackground(outGrid.tree(), inGrid.background()); applyTransform(xform, inGrid, outGrid); } template void GridTransformer::transformGrid(const GridT& inGrid, GridT& outGrid) const { tools::changeBackground(outGrid.tree(), 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) { tools::pruneLevelSet(outTree); tools::signedFloodFill(outTree); } } //////////////////////////////////////// //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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Filter.h0000644000000000000000000004101712603226506013754 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 "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 grain size 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 than @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 Number 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 Number 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; typedef tools::AlphaMask AlphaMaskT; void cook(LeafManagerType& leafs); template struct Avg { Avg(const GridT* grid, Int32 w): acc(grid->tree()), width(w), frac(1.f/float(2*w+1)) {} inline ValueType operator()(Coord xyz); typename GridT::ConstAccessor acc; const Int32 width; const float 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 //////////////////////////////////////// namespace filter_internal { // Helper function for Filter::Avg::operator() template static inline void accum(T& sum, T addend) { sum += addend; } // Overload for bool ValueType inline void accum(bool& sum, bool addend) { sum = sum || addend; } } template template inline typename GridT::ValueType Filter::Avg::operator()(Coord xyz) { ValueType sum = zeroVal(); Int32 &i = xyz[Axis], j = i + width; for (i -= width; i <= j; ++i) filter_internal::accum(sum, acc.getValue(xyz)); return static_cast(sum * frac); } //////////////////////////////////////// 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) { typename AlphaMaskT::FloatType a, b; AlphaMaskT 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) { typename AlphaMaskT::FloatType a, b; AlphaMaskT 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) { typename AlphaMaskT::FloatType a, b; AlphaMaskT 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/SignedFloodFill.h0000644000000000000000000003120112603226506015525 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 SignedFloodFill.h /// /// @brief Propagates the sign of distance values from the active /// voxels in the narrow band to the inactive values outside the /// narrow band. /// /// @author Ken Museth #ifndef OPENVDB_TOOLS_SIGNEDFLOODFILL_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_SIGNEDFLOODFILL_HAS_BEEN_INCLUDED #include #include // for math::negative #include // for Index typedef #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @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, symmetric narrow-band level sets. /// /// @note If a LeafManager is used the cached leaf nodes are reused, /// resulting in slightly better overall performance. /// /// @param tree Tree or LeafManager that will be flood filled. /// @param threaded enable or disable threading (threading is enabled by default) /// @param grainSize used to control the threading granularity (default is 1) /// /// @throw TypeError if the ValueType of @a tree is not floating-point. template inline void signedFloodFill(TreeOrLeafManagerT& tree, bool threaded = true, size_t grainSize = 1); /// @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 outsideWidth and interior values to @a insideWidth. Set the background value /// of this tree to @a outsideWidth. /// /// @warning This method should only be used on closed, narrow-band level sets. /// /// @note If a LeafManager is used the cached leaf nodes are reused /// resulting in slightly better overall performance. /// /// @param tree Tree or LeafManager that will be flood filled /// @param outsideWidth the width of the outside of the narrow band /// @param insideWidth the width of the inside of the narrow band /// @param threaded enable or disable threading (threading is enabled by default) /// @param grainSize used to control the threading granularity (default is 1) /// /// @throw TypeError if the ValueType of @a tree is not floating-point. template inline void signedFloodFillWithValues( TreeOrLeafManagerT& tree, const typename TreeOrLeafManagerT::ValueType& outsideWidth, const typename TreeOrLeafManagerT::ValueType& insideWidth, bool threaded = true, size_t grainSize = 1); ////////////////////////// Implementation of SignedFloodFill //////////////////////////// template class SignedFloodFillOp { public: typedef typename TreeOrLeafManagerT::ValueType ValueT; typedef typename TreeOrLeafManagerT::RootNodeType RootT; typedef typename TreeOrLeafManagerT::LeafNodeType LeafT; BOOST_STATIC_ASSERT(boost::is_floating_point::value || boost::is_signed::value); SignedFloodFillOp(const TreeOrLeafManagerT& tree) : mOutside(ValueT(math::Abs(tree.background()))) , mInside(ValueT(math::negative(mOutside))) { } SignedFloodFillOp(ValueT outsideValue, ValueT insideValue) : mOutside(ValueT(math::Abs(outsideValue))) , mInside(ValueT(math::negative(math::Abs(insideValue)))) { } // Nothing to do at the leaf node level void operator()(LeafT& leaf) const { #ifndef OPENVDB_2_ABI_COMPATIBLE if (!leaf.allocate()) return;//this assures that the buffer is allocated and in-memory #endif const typename LeafT::NodeMaskType& valueMask = leaf.getValueMask(); // WARNING: "Never do what you're about to see at home, we're what you call experts!" typename LeafT::ValueType* buffer = const_cast(&(leaf.getFirstValue())); const Index first = valueMask.findFirstOn(); if (first < LeafT::SIZE) { bool xInside = buffer[first]<0, yInside = xInside, zInside = xInside; for (Index x = 0; x != (1 << LeafT::LOG2DIM); ++x) { const Index x00 = x << (2 * LeafT::LOG2DIM); if (valueMask.isOn(x00)) xInside = buffer[x00] < 0; // element(x, 0, 0) yInside = xInside; for (Index y = 0; y != (1 << LeafT::LOG2DIM); ++y) { const Index xy0 = x00 + (y << LeafT::LOG2DIM); if (valueMask.isOn(xy0)) yInside = buffer[xy0] < 0; // element(x, y, 0) zInside = yInside; for (Index z = 0; z != (1 << LeafT::LOG2DIM); ++z) { const Index xyz = xy0 + z; // element(x, y, z) if (valueMask.isOn(xyz)) { zInside = buffer[xyz] < 0; } else { buffer[xyz] = zInside ? mInside : mOutside; } } } } } else {// if no active voxels exist simply use the sign of the first value leaf.fill(buffer[0] < 0 ? mInside : mOutside); } } // Prune the child nodes of the internal nodes template void operator()(NodeT& node) const { // We assume the child nodes have already been flood filled! const typename NodeT::NodeMaskType& childMask = node.getChildMask(); // WARNING: "Never do what you're about to see at home, we're what you call experts!" typename NodeT::UnionType* table = const_cast(node.getTable()); const Index first = childMask.findFirstOn(); if (first < NodeT::NUM_VALUES) { bool xInside = table[first].getChild()->getFirstValue()<0; bool yInside = xInside, zInside = xInside; for (Index x = 0; x != (1 << NodeT::LOG2DIM); ++x) { const int x00 = x << (2 * NodeT::LOG2DIM); // offset for block(x, 0, 0) if (childMask.isOn(x00)) xInside = table[x00].getChild()->getLastValue()<0; yInside = xInside; for (Index y = 0; y != (1 << NodeT::LOG2DIM); ++y) { const Index xy0 = x00 + (y << NodeT::LOG2DIM); // offset for block(x, y, 0) if (childMask.isOn(xy0)) yInside = table[xy0].getChild()->getLastValue()<0; zInside = yInside; for (Index z = 0; z != (1 << NodeT::LOG2DIM); ++z) { const Index xyz = xy0 + z; // offset for block(x, y, z) if (childMask.isOn(xyz)) { zInside = table[xyz].getChild()->getLastValue()<0; } else { table[xyz].setValue(zInside ? mInside : mOutside); } } } } } else {//no child nodes exist simply use the sign of the first tile value. const ValueT v = table[0].getValue()<0 ? mInside : mOutside; for (Index i = 0; i < NodeT::NUM_VALUES; ++i) table[i].setValue(v); } } // Prune the child nodes of the root node void operator()(RootT& root) const { typedef typename RootT::ChildNodeType ChildT; // Insert the child nodes into a map sorted according to their origin std::map nodeKeys; typename RootT::ChildOnIter it = root.beginChildOn(); for (; it; ++it) nodeKeys.insert(std::pair(it.getCoord(), &(*it))); static const Index DIM = RootT::ChildNodeType::DIM; // We employ a simple z-scanline algorithm that inserts inactive tiles with // the inside value if they are sandwiched between inside child nodes only! typename std::map::const_iterator b = nodeKeys.begin(), e = nodeKeys.end(); if ( b == e ) return; for (typename std::map::const_iterator a = b++; b != e; ++a, ++b) { Coord d = b->first - a->first; // delta of neighboring coordinates if (d[0]!=0 || d[1]!=0 || d[2]==Int32(DIM)) continue;// not same z-scanline or neighbors const ValueT fill[] = { a->second->getLastValue(), b->second->getFirstValue() }; if (!(fill[0] < 0) || !(fill[1] < 0)) continue; // scanline isn't inside Coord c = a->first + Coord(0u, 0u, DIM); for (; c[2] != b->first[2]; c[2] += DIM) root.addTile(c, mInside, false); } root.setBackground(mOutside, /*updateChildNodes=*/false); } private: const ValueT mOutside, mInside; };// SignedFloodFillOp template inline typename boost::enable_if_c< boost::is_floating_point::value || boost::is_signed::value, void>::type doSignedFloodFill(TreeOrLeafManagerT& tree, typename TreeOrLeafManagerT::ValueType outsideValue, typename TreeOrLeafManagerT::ValueType insideValue, bool threaded, size_t grainSize) { tree::NodeManager nodes(tree); SignedFloodFillOp op(outsideValue, insideValue); nodes.processBottomUp(op, threaded, grainSize); } // Dummy (no-op) implementation for non-float types template inline typename boost::disable_if_c< boost::is_floating_point::value || boost::is_signed::value, void>::type doSignedFloodFill(TreeOrLeafManagerT&, const typename TreeOrLeafManagerT::ValueType&, const typename TreeOrLeafManagerT::ValueType&, bool, size_t) { OPENVDB_THROW(TypeError, "signedFloodFill is supported only for signed value grids"); } // If the narrow-band is symmetric and unchanged template inline void signedFloodFillWithValues( TreeOrLeafManagerT& tree, const typename TreeOrLeafManagerT::ValueType& outsideValue, const typename TreeOrLeafManagerT::ValueType& insideValue, bool threaded, size_t grainSize) { doSignedFloodFill(tree, outsideValue, insideValue, threaded, grainSize); } template inline void signedFloodFill(TreeOrLeafManagerT& tree, bool threaded, size_t grainSize) { const typename TreeOrLeafManagerT::ValueType v = tree.root().background(); doSignedFloodFill(tree, v, math::negative(v), threaded, grainSize); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_RESETBACKGROUND_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Statistics.h0000644000000000000000000004205512603226506014664 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Statistics.h /// /// @brief Functions to efficiently compute histograms, extremas /// (min/max) and statistics (mean, variance, etc.) of grid values #ifndef OPENVDB_TOOLS_STATISTICS_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_STATISTICS_HAS_BEEN_INCLUDED #include #include #include #include "ValueTransformer.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Iterate over a scalar grid and compute a histogram of the values /// of the voxels that are visited, or iterate over a vector-valued grid /// and compute a histogram of the magnitudes of the vectors. /// @param iter an iterator over the values of a grid or its tree /// (@c Grid::ValueOnCIter, @c Tree::ValueOffIter, etc.) /// @param minVal the smallest value that can be added to the histogram /// @param maxVal the largest value that can be added to the histogram /// @param numBins the number of histogram bins /// @param threaded if true, iterate over the grid in parallel template inline math::Histogram histogram(const IterT& iter, double minVal, double maxVal, size_t numBins = 10, bool threaded = true); /// @brief Iterate over a scalar grid and compute extrema (min/max) of the /// values of the voxels that are visited, or iterate over a vector-valued grid /// and compute extrema 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::Extrema extrema(const IterT& iter, 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 extrema (min/max) 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::Extrema& ex) /// { /// const float f = *iter; /// if (f > 0.0) { /// if (iter.isVoxelValue()) ex.add(f); /// else ex.add(f, iter.getVoxelCount()); /// } /// } /// }; /// FloatGrid grid = ...; /// math::Extrema stats = /// tools::extrema(grid.cbeginValueOn(), Local::addIfPositive, /*threaded=*/true); /// @endcode template inline math::Extrema extrema(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 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); /// @brief Same as opStatistics except it returns a math::Extrema vs a math::Stats template inline math::Extrema opExtrema(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); } StatsT 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; StatsT 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::Extrema extrema(const IterT& iter, bool threaded) { stats_internal::GetVal valOp; return extrema(iter, valOp, threaded); } template inline math::Stats statistics(const IterT& iter, bool threaded) { stats_internal::GetVal valOp; return statistics(iter, valOp, threaded); } template inline math::Extrema extrema(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 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::Extrema opExtrema(const IterT& iter, const OperatorT& op, bool threaded) { stats_internal::MathOp func(iter, op); tools::accumulate(iter, func, threaded); return func.mStats; } 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/VolumeToSpheres.h0000644000000000000000000007371512603226506015645 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 world-space 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); inline void run(bool threaded = true); inline 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 { Vec3d 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 = int(mLeafRanges[n].second) - int(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)); double 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]; double 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 = float((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 = float((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; } inline void run(bool threaded = true); UpdatePoints(UpdatePoints&, tbb::split); inline 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 = int(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 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, Index64(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() < size_t(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() == size_t(instances)) break; } if (instancePoints.size() == size_t(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 = int(n); } } Vec3s pos; Vec4s sphere; minRadius = float(minRadius * transform.voxelSize()[0]); maxRadius = float(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.51f; 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetMeasure.h0000644000000000000000000004705112603226506015600 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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; 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 virtual ~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 grain size 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-curvature 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); private: // disallow copy construction and copy by assignment! LevelSetMeasure(const LevelSetMeasure&);// not implemented LevelSetMeasure& operator=(const LevelSetMeasure&);// not implemented const TreeType* mTree; ManagerType* mLeafs; InterruptT* mInterrupter; double mDx; double* mArray; int mGrainSize; // @brief Return false if the process was interrupted bool checkInterrupter(); typedef typename TreeType::LeafNodeType LeafT; typedef typename LeafT::ValueOnCIter VoxelCIterT; typedef typename ManagerType::LeafRange LeafRange; typedef typename LeafRange::Iterator LeafIterT; struct Measure2 { Measure2(LevelSetMeasure* parent) : mParent(parent), mAcc(*mParent->mTree) { if (parent->mGrainSize>0) { tbb::parallel_for(parent->mLeafs->leafRange(parent->mGrainSize), *this); } else { (*this)(parent->mLeafs->leafRange()); } } Measure2(const Measure2& other) : mParent(other.mParent), mAcc(*mParent->mTree) {} void operator()(const LeafRange& range) const; LevelSetMeasure* mParent; typename GridT::ConstAccessor mAcc; }; struct Measure3 { Measure3(LevelSetMeasure* parent) : mParent(parent), mAcc(*mParent->mTree) { if (parent->mGrainSize>0) { tbb::parallel_for(parent->mLeafs->leafRange(parent->mGrainSize), *this); } else { (*this)(parent->mLeafs->leafRange()); } } Measure3(const Measure3& other) : mParent(other.mParent), mAcc(*mParent->mTree) {} void operator()(const LeafRange& range) const; LevelSetMeasure* mParent; typename GridT::ConstAccessor mAcc; }; 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) : mTree(&(grid.tree())) , mLeafs(NULL) , mInterrupter(interrupt) , mDx(grid.voxelSize()[0]) , mArray(NULL) , 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) : mTree(&(leafs.tree())) , mLeafs(&leafs) , mInterrupter(interrupt) , mDx(dx) , mArray(NULL) , 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\""); } mTree = &(grid.tree()); mLeafs = NULL; mDx = grid.voxelSize()[0]; } template inline void LevelSetMeasure::reinit(ManagerType& leafs, Real dx) { mLeafs = &leafs; mTree = &(leafs.tree()); mDx = dx; } //////////////////////////////////////// template inline void LevelSetMeasure::measure(Real& area, Real& volume, bool useWorldUnits) { if (mInterrupter) mInterrupter->start("Measuring level set"); const bool newLeafs = mLeafs == NULL; if (newLeafs) mLeafs = new ManagerType(*mTree); const size_t leafCount = mLeafs->leafCount(); if (leafCount == 0) { area = volume = 0; return; } mArray = new double[2*leafCount]; Measure2 m(this); 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"); const bool newLeafs = mLeafs == NULL; if (newLeafs) mLeafs = new ManagerType(*mTree); const size_t leafCount = mLeafs->leafCount(); if (leafCount == 0) { area = volume = avgMeanCurvature = 0; return; } mArray = new double[3*leafCount]; Measure3 m(this); 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::operator()(const LeafRange& range) const { typedef math::Vec3 Vec3T; typedef math::ISGradient Grad; mParent->checkInterrupter(); const Real invDx = 1.0/mParent->mDx; const DiracDelta DD(1.5); const size_t leafCount = mParent->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 = mParent->mArray + leafIter.pos(); *v = sumA; v += leafCount; *v = sumV; } } template inline void LevelSetMeasure:: Measure3::operator()(const LeafRange& range) const { typedef math::Vec3 Vec3T; typedef math::ISGradient Grad; typedef math::ISMeanCurvature Curv; mParent->checkInterrupter(); const Real invDx = 1.0/mParent->mDx; const DiracDelta DD(1.5); ValueType alpha, beta; const size_t leafCount = mParent->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 = mParent->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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetRebuild.h0000644000000000000000000003146112603226506015563 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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] = Vec3s(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(); } QuadAndTriangleDataAdapter mesh(points, primitives); if (interrupter) { return meshToVolume(*interrupter, mesh, *transform, exBandWidth, inBandWidth, DISABLE_RENORMALIZATION, NULL); } return meshToVolume(mesh, *transform, exBandWidth, inBandWidth, DISABLE_RENORMALIZATION, NULL); } /// @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() + ValueT(iso)), exBandWidth(zeroVal() + ValueT(exWidth)), inBandWidth(zeroVal() + ValueT(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() + ValueT(iso)), exBandWidth(zeroVal() + ValueT(exWidth)), inBandWidth(zeroVal() + ValueT(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() + ValueT(iso)), halfWidth(zeroVal() + ValueT(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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/VectorTransformer.h0000644000000000000000000001202012603226506016204 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/PointScatter.h0000644000000000000000000004161112603226506015146 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 PointScatter.h /// /// @brief We offer three different algorithms (each in its own class) /// for scattering of point in active voxels: /// /// 1) UniformPointScatter. Has two modes: Either randomly distributes /// a fixed number of points in the active voxels, or the user can /// specify a fixed probability of having a points per unit of volume. /// /// 2) DenseUniformPointScatter. Randomly distributes points in active /// voxels using a fixed number of points per voxel. /// /// 3) NonIniformPointScatter. Define the local probability of having /// a point in a voxel as the product of a global density and the /// value of the voxel itself. #ifndef OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// Forward declaration of base class template class BasePointScatter; /// @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 BasePointScatter { public: typedef BasePointScatter BaseT; UniformPointScatter(PointAccessorType& points, Index64 pointCount, RandomGenerator& randGen, InterruptType* interrupt = NULL) : BaseT(points, randGen, interrupt) , mTargetPointCount(pointCount) , mPointsPerVolume(0.0f) { } UniformPointScatter(PointAccessorType& points, float pointsPerVolume, RandomGenerator& randGen, InterruptType* interrupt = NULL) : BaseT(points, randGen, interrupt) , mTargetPointCount(0) , mPointsPerVolume(pointsPerVolume) { } /// @brief This is the main functor method implementing the actual /// scattering of points. template bool operator()(const GridT& grid) { mVoxelCount = grid.activeVoxelCount(); if (mVoxelCount == 0) return false; const Vec3d dim = grid.voxelSize(); if (mPointsPerVolume>0) { BaseT::start("Uniform scattering with fixed point density"); mTargetPointCount = Index64(mPointsPerVolume*dim[0]*dim[1]*dim[2])*mVoxelCount; } else if (mTargetPointCount>0) { BaseT::start("Uniform scattering with fixed point count"); mPointsPerVolume = mTargetPointCount/float(dim[0]*dim[1]*dim[2] * mVoxelCount); } else { return false; } boost::scoped_array list(new Index64[mTargetPointCount]); math::RandInt rand(BaseT::mRand01.engine(), 0, mVoxelCount-1); for (Index64 i=0; i class DenseUniformPointScatter : public BasePointScatter { public: typedef BasePointScatter BaseT; DenseUniformPointScatter(PointAccessorType& points, float pointsPerVoxel, RandomGenerator& randGen, InterruptType* interrupt = NULL) : BaseT(points, randGen, interrupt) , mPointsPerVoxel(pointsPerVoxel) { } /// This is the main functor method implementing the actual scattering of points. template bool operator()(const GridT& grid) { typedef typename GridT::ValueOnCIter ValueIter; if (mPointsPerVoxel < 1.0e-6) return false; mVoxelCount = grid.activeVoxelCount(); if (mVoxelCount == 0) return false; BaseT::start("Dense uniform scattering with fixed point count"); CoordBBox bbox; const Vec3R offset(0.5, 0.5, 0.5); const int ppv = math::Floor(mPointsPerVoxel); const double delta = mPointsPerVoxel - ppv; const bool fractional = !math::isApproxZero(delta, 1.0e-6); for (ValueIter iter = grid.cbeginValueOn(); iter; ++iter) { if (BaseT::interrupt()) return false; if (iter.isVoxelValue()) {// a majority is expected to be voxels const Vec3R dmin = iter.getCoord() - offset; for (int n = 0; n != ppv; ++n) BaseT::addPoint(grid, dmin); if (fractional && BaseT::getRand() < delta) BaseT::addPoint(grid, dmin); } else {// tiles contain multiple (virtual) voxels iter.getBoundingBox(bbox); const Coord size(bbox.extents()); const Vec3R dmin = bbox.min() - offset; const double d = mPointsPerVoxel * iter.getVoxelCount(); const int m = math::Floor(d); for (int n = 0; n != m; ++n) BaseT::addPoint(grid, dmin, size); if (BaseT::getRand() < d - m) BaseT::addPoint(grid, dmin, size); } }//loop over all the active voxels and tiles BaseT::end(); return true; } // 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 << "Dense uniformly scattered " << mPointCount << " points into " << mVoxelCount << " active voxels in \"" << name << "\" corresponding to " << mPointsPerVoxel << " points per voxel." << std::endl; } float getPointsPerVoxel() const { return mPointsPerVoxel; } private: using BaseT::mPointCount; using BaseT::mVoxelCount; float mPointsPerVoxel; }; // class DenseUniformPointScatter /// @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 (called pointsPerVolume) 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 BasePointScatter { public: typedef BasePointScatter BaseT; NonUniformPointScatter(PointAccessorType& points, float pointsPerVolume, RandomGenerator& randGen, InterruptType* interrupt = NULL) : BaseT(points, randGen, interrupt) , mPointsPerVolume(pointsPerVolume)//note this is merely a //multiplier for the local point density { } /// This is the main functor method implementing the actual scattering of points. template bool operator()(const GridT& grid) { if (mPointsPerVolume <= 0.0f) return false; mVoxelCount = grid.activeVoxelCount(); if (mVoxelCount == 0) return false; BaseT::start("Non-uniform scattering with local point density"); const Vec3d dim = grid.voxelSize(); const double volumePerVoxel = dim[0]*dim[1]*dim[2], pointsPerVoxel = mPointsPerVolume * volumePerVoxel; CoordBBox bbox; const Vec3R offset(0.5, 0.5, 0.5); for (typename GridT::ValueOnCIter iter = grid.cbeginValueOn(); iter; ++iter) { if (BaseT::interrupt()) return false; const double d = (*iter) * pointsPerVoxel * iter.getVoxelCount(); const int n = int(d); if (iter.isVoxelValue()) { // a majority is expected to be voxels const Vec3R dmin =iter.getCoord() - offset; for (int i = 0; i < n; ++i) BaseT::addPoint(grid, dmin); if (BaseT::getRand() < (d - n)) BaseT::addPoint(grid, dmin); } else { // tiles contain multiple (virtual) voxels iter.getBoundingBox(bbox); const Coord size(bbox.extents()); const Vec3R dmin = bbox.min() - offset; for (int i = 0; i < n; ++i) BaseT::addPoint(grid, dmin, size); if (BaseT::getRand() < (d - n)) BaseT::addPoint(grid, dmin, size); } }//loop over all the active voxels and tiles BaseT::end(); return true; } // 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-uniformly scattered " << mPointCount << " points into " << mVoxelCount << " active voxels in \"" << name << "\"." << std::endl; } float getPointPerVolume() const { return mPointsPerVolume; } private: using BaseT::mPointCount; using BaseT::mVoxelCount; float mPointsPerVolume; }; // class NonUniformPointScatter /// Base class of all the point scattering classes defined above template class BasePointScatter { public: Index64 getPointCount() const { return mPointCount; } Index64 getVoxelCount() const { return mVoxelCount; } protected: /// This is a base class so the constructor is protected BasePointScatter(PointAccessorType& points, RandomGenerator& randGen, InterruptType* interrupt = NULL) : mPoints(points) , mInterrupter(interrupt) , mPointCount(0) , mVoxelCount(0) , mInterruptCount(0) , mRand01(randGen) { } PointAccessorType& mPoints; InterruptType* mInterrupter; Index64 mPointCount; Index64 mVoxelCount; Index64 mInterruptCount; math::Rand01 mRand01; inline void start(const char* name) { if (mInterrupter) mInterrupter->start(name); } inline void end() { if (mInterrupter) mInterrupter->end(); } inline bool interrupt() { //only check interrupter for every 32'th call return !(mInterruptCount++ & ((1<<5)-1)) && util::wasInterrupted(mInterrupter); } inline double getRand() { return mRand01(); } template inline void addPoint(const GridT &grid, const Vec3R &dmin) { const Vec3R pos(dmin[0] + this->getRand(), dmin[1] + this->getRand(), dmin[2] + this->getRand()); mPoints.add(grid.indexToWorld(pos)); ++mPointCount; } template inline void addPoint(const GridT &grid, const Vec3R &dmin, const Coord &size) { const Vec3R pos(dmin[0] + size[0]*this->getRand(), dmin[1] + size[1]*this->getRand(), dmin[2] + size[2]*this->getRand()); mPoints.add(grid.indexToWorld(pos)); ++mPointCount; } };// class BasePointScatter } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/PoissonSolver.h0000644000000000000000000007637612603226506015374 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 PoissonSolver.h /// /// @authors D.J. Hill, Peter Cucka /// /// @brief Solve Poisson's equation ∇2x = b /// for x, where @e b is a vector comprising the values of all of the active voxels /// in a grid. /// /// @par Example: /// Solve for the pressure in a cubic tank of liquid, assuming uniform boundary conditions: /// @code /// FloatTree source(/*background=*/0.0f); /// // Activate voxels to indicate that they contain liquid. /// source.fill(CoordBBox(Coord(0, -10, 0), Coord(10, 0, 10)), /*value=*/0.0f); /// /// math::pcg::State state = math::pcg::terminationDefaults(); /// FloatTree::Ptr solution = tools::poisson::solve(source, state); /// @endcode /// /// @par Example: /// Solve for the pressure, P, in a cubic tank of liquid that is open at the top. /// Boundary conditions are P = 0 at the top, /// ∂P/∂y = −1 at the bottom /// and ∂P/∂x = 0 at the sides: ///
///                P = 0
///             +--------+ (N,0,N)
///            /|       /|
///   (0,0,0) +--------+ |
///           | |      | | dP/dx = 0
/// dP/dx = 0 | +------|-+
///           |/       |/
///  (0,-N,0) +--------+ (N,-N,N)
///           dP/dy = -1
/// 
/// @code /// const int N = 10; /// DoubleTree source(/*background=*/0.0); /// // Activate voxels to indicate that they contain liquid. /// source.fill(CoordBBox(Coord(0, -N, 0), Coord(N, 0, N)), /*value=*/0.0); /// /// // C++11 /// auto boundary = [](const openvdb::Coord& ijk, const openvdb::Coord& neighbor, /// double& source, double& diagonal) /// { /// if (neighbor.x() == ijk.x() && neighbor.z() == ijk.z()) { /// if (neighbor.y() < ijk.y()) source -= 1.0; /// else diagonal -= 1.0; /// } /// }; /// /// math::pcg::State state = math::pcg::terminationDefaults(); /// util::NullInterrupter interrupter; /// /// DoubleTree::Ptr solution = tools::poisson::solveWithBoundaryConditions( /// source, boundary, state, interrupter); /// @endcode #ifndef OPENVDB_TOOLS_POISSONSOLVER_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_POISSONSOLVER_HAS_BEEN_INCLUDED #include #include #include #include #include #include "Morphology.h" // for erodeVoxels #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { namespace poisson { // This type should be at least as wide as math::pcg::SizeType. typedef Int32 VIndex; /// The type of a matrix used to represent a three-dimensional Laplacian operator typedef math::pcg::SparseStencilMatrix LaplacianMatrix; //@{ /// @brief Solve ∇2x = b for x, /// where @e b is a vector comprising the values of all of the active voxels /// in the input tree. /// @return a new tree, with the same active voxel topology as the input tree, /// whose voxel values are the elements of the solution vector x. /// @details On input, the State object should specify convergence criteria /// (minimum error and maximum number of iterations); on output, it gives /// the actual termination conditions. /// @details The solution is computed using the conjugate gradient method /// with (where possible) incomplete Cholesky preconditioning, falling back /// to Jacobi preconditioning. /// @sa solveWithBoundaryConditions template inline typename TreeType::Ptr solve(const TreeType&, math::pcg::State&); template inline typename TreeType::Ptr solve(const TreeType&, math::pcg::State&, Interrupter&); //@} //@{ /// @brief Solve ∇2x = b for x /// with user-specified boundary conditions, where @e b is a vector comprising /// the values of all of the active voxels in the input tree or domain mask if provided /// @return a new tree, with the same active voxel topology as the input tree, /// whose voxel values are the elements of the solution vector x. /// @details On input, the State object should specify convergence criteria /// (minimum error and maximum number of iterations); on output, it gives /// the actual termination conditions. /// @details The solution is computed using the conjugate gradient method with /// the specified type of preconditioner (default: incomplete Cholesky), /// falling back to Jacobi preconditioning if necessary. /// @details Each thread gets its own copy of the BoundaryOp, which should be /// a functor of the form /// @code /// struct BoundaryOp { /// typedef LaplacianMatrix::ValueType ValueType; /// void operator()( /// const Coord& ijk, // coordinates of a boundary voxel /// const Coord& ijkNeighbor, // coordinates of an exterior neighbor of ijk /// ValueType& source, // element of b corresponding to ijk /// ValueType& diagonal // element of Laplacian matrix corresponding to ijk /// ) const; /// }; /// @endcode /// The functor is called for each of the exterior neighbors of each boundary voxel @ijk, /// and it must specify a boundary condition for @ijk by modifying one or both of two /// provided values: the entry in the source vector @e b corresponding to @ijk and /// the weighting coefficient for @ijk in the Laplacian operator matrix. /// /// @sa solve template inline typename TreeType::Ptr solveWithBoundaryConditions(const TreeType&, const BoundaryOp&, math::pcg::State&, Interrupter&); template inline typename TreeType::Ptr solveWithBoundaryConditionsAndPreconditioner(const TreeType&, const BoundaryOp&, math::pcg::State&, Interrupter&); template inline typename TreeType::Ptr solveWithBoundaryConditionsAndPreconditioner(const TreeType&, const DomainTreeType&, const BoundaryOp&, math::pcg::State&, Interrupter&); //@} /// @name Low-level functions //@{ // The following are low-level routines that can be used to assemble custom solvers. /// @brief Overwrite each active voxel in the given scalar tree /// with a sequential index, starting from zero. template inline void populateIndexTree(VIndexTreeType&); /// @brief Iterate over the active voxels of the input tree and for each one /// assign its index in the iteration sequence to the corresponding voxel /// of an integer-valued output tree. template inline typename TreeType::template ValueConverter::Type::Ptr createIndexTree(const TreeType&); /// @brief Return a vector of the active voxel values of the scalar-valued @a source tree. /// @details The nth element of the vector corresponds to the voxel whose value /// in the @a index tree is @e n. /// @param source a tree with a scalar value type /// @param index a tree of the same configuration as @a source but with /// value type VIndex that maps voxels to elements of the output vector template inline typename math::pcg::Vector::Ptr createVectorFromTree( const SourceTreeType& source, const typename SourceTreeType::template ValueConverter::Type& index); /// @brief Return a tree with the same active voxel topology as the @a index tree /// but whose voxel values are taken from the the given vector. /// @details The voxel whose value in the @a index tree is @e n gets assigned /// the nth element of the vector. /// @param index a tree with value type VIndex that maps voxels to elements of @a values /// @param values a vector of values with which to populate the active voxels of the output tree /// @param background the value for the inactive voxels of the output tree template inline typename VIndexTreeType::template ValueConverter::Type::Ptr createTreeFromVector( const math::pcg::Vector& values, const VIndexTreeType& index, const TreeValueType& background); /// @brief Generate a sparse matrix of the index-space (Δx = 1) Laplacian operator /// using second-order finite differences. /// @details This construction assumes homogeneous Dirichlet boundary conditions /// (exterior grid points are zero). template inline LaplacianMatrix::Ptr createISLaplacian( const typename BoolTreeType::template ValueConverter::Type& vectorIndexTree, const BoolTreeType& interiorMask); /// @brief Generate a sparse matrix of the index-space (Δx = 1) Laplacian operator /// with user-specified boundary conditions using second-order finite differences. /// @details Each thread gets its own copy of @a boundaryOp, which should be /// a functor of the form /// @code /// struct BoundaryOp { /// typedef LaplacianMatrix::ValueType ValueType; /// void operator()( /// const Coord& ijk, // coordinates of a boundary voxel /// const Coord& ijkNeighbor, // coordinates of an exterior neighbor of ijk /// ValueType& source, // element of source vector corresponding to ijk /// ValueType& diagonal // element of Laplacian matrix corresponding to ijk /// ) const; /// }; /// @endcode /// The functor is called for each of the exterior neighbors of each boundary voxel @ijk, /// and it must specify a boundary condition for @ijk by modifying one or both of two /// provided values: an entry in the given @a source vector corresponding to @ijk and /// the weighting coefficient for @ijk in the Laplacian matrix. template inline LaplacianMatrix::Ptr createISLaplacianWithBoundaryConditions( const typename BoolTreeType::template ValueConverter::Type& vectorIndexTree, const BoolTreeType& interiorMask, const BoundaryOp& boundaryOp, typename math::pcg::Vector& source); //@} //////////////////////////////////////// namespace internal { /// @brief Functor for use with LeafManager::foreach() to populate an array /// with per-leaf active voxel counts template struct LeafCountOp { VIndex* count; LeafCountOp(VIndex* count_): count(count_) {} void operator()(const LeafType& leaf, size_t leafIdx) const { count[leafIdx] = static_cast(leaf.onVoxelCount()); } }; /// @brief Functor for use with LeafManager::foreach() to populate /// active leaf voxels with sequential indices template struct LeafIndexOp { const VIndex* count; LeafIndexOp(const VIndex* count_): count(count_) {} void operator()(LeafType& leaf, size_t leafIdx) const { VIndex idx = (leafIdx == 0) ? 0 : count[leafIdx - 1]; for (typename LeafType::ValueOnIter it = leaf.beginValueOn(); it; ++it) { it.setValue(idx++); } } }; } // namespace internal template inline void populateIndexTree(VIndexTreeType& result) { typedef typename VIndexTreeType::LeafNodeType LeafT; typedef typename tree::LeafManager LeafMgrT; // Linearize the tree. LeafMgrT leafManager(result); const size_t leafCount = leafManager.leafCount(); // Count the number of active voxels in each leaf node. boost::scoped_array perLeafCount(new VIndex[leafCount]); VIndex* perLeafCountPtr = perLeafCount.get(); leafManager.foreach(internal::LeafCountOp(perLeafCountPtr)); // The starting index for each leaf node is the total number // of active voxels in all preceding leaf nodes. for (size_t i = 1; i < leafCount; ++i) { perLeafCount[i] += perLeafCount[i - 1]; } // The last accumulated value should be the total of all active voxels. assert(Index64(perLeafCount[leafCount-1]) == result.activeVoxelCount()); // Parallelize over the leaf nodes of the tree, storing a unique index // in each active voxel. leafManager.foreach(internal::LeafIndexOp(perLeafCountPtr)); } template inline typename TreeType::template ValueConverter::Type::Ptr createIndexTree(const TreeType& tree) { typedef typename TreeType::template ValueConverter::Type VIdxTreeT; // Construct an output tree with the same active voxel topology as the input tree. const VIndex invalidIdx = -1; typename VIdxTreeT::Ptr result( new VIdxTreeT(tree, /*background=*/invalidIdx, TopologyCopy())); // All active voxels are degrees of freedom, including voxels contained in active tiles. result->voxelizeActiveTiles(); populateIndexTree(*result); return result; } //////////////////////////////////////// namespace internal { /// @brief Functor for use with LeafManager::foreach() to populate a vector /// with the values of a tree's active voxels template struct CopyToVecOp { typedef typename SourceTreeType::template ValueConverter::Type VIdxTreeT; typedef typename VIdxTreeT::LeafNodeType VIdxLeafT; typedef typename SourceTreeType::LeafNodeType LeafT; typedef typename SourceTreeType::ValueType TreeValueT; typedef typename math::pcg::Vector VectorT; const SourceTreeType* tree; VectorT* vector; CopyToVecOp(const SourceTreeType& t, VectorT& v): tree(&t), vector(&v) {} void operator()(const VIdxLeafT& idxLeaf, size_t /*leafIdx*/) const { VectorT& vec = *vector; if (const LeafT* leaf = tree->probeLeaf(idxLeaf.origin())) { // If a corresponding leaf node exists in the source tree, // copy voxel values from the source node to the output vector. for (typename VIdxLeafT::ValueOnCIter it = idxLeaf.cbeginValueOn(); it; ++it) { vec[*it] = leaf->getValue(it.pos()); } } else { // If no corresponding leaf exists in the source tree, // fill the vector with a uniform value. const TreeValueT& value = tree->getValue(idxLeaf.origin()); for (typename VIdxLeafT::ValueOnCIter it = idxLeaf.cbeginValueOn(); it; ++it) { vec[*it] = value; } } } }; } // namespace internal template inline typename math::pcg::Vector::Ptr createVectorFromTree(const SourceTreeType& tree, const typename SourceTreeType::template ValueConverter::Type& idxTree) { typedef typename SourceTreeType::template ValueConverter::Type VIdxTreeT; typedef tree::LeafManager VIdxLeafMgrT; typedef typename math::pcg::Vector VectorT; // Allocate the vector. const size_t numVoxels = idxTree.activeVoxelCount(); typename VectorT::Ptr result(new VectorT(static_cast(numVoxels))); // Parallelize over the leaf nodes of the index tree, filling the output vector // with values from corresponding voxels of the source tree. VIdxLeafMgrT leafManager(idxTree); leafManager.foreach(internal::CopyToVecOp(tree, *result)); return result; } //////////////////////////////////////// namespace internal { /// @brief Functor for use with LeafManager::foreach() to populate a tree /// with values from a vector template struct CopyFromVecOp { typedef typename VIndexTreeType::template ValueConverter::Type OutTreeT; typedef typename OutTreeT::LeafNodeType OutLeafT; typedef typename VIndexTreeType::LeafNodeType VIdxLeafT; typedef typename math::pcg::Vector VectorT; const VectorT* vector; OutTreeT* tree; CopyFromVecOp(const VectorT& v, OutTreeT& t): vector(&v), tree(&t) {} void operator()(const VIdxLeafT& idxLeaf, size_t /*leafIdx*/) const { const VectorT& vec = *vector; OutLeafT* leaf = tree->probeLeaf(idxLeaf.origin()); assert(leaf != NULL); for (typename VIdxLeafT::ValueOnCIter it = idxLeaf.cbeginValueOn(); it; ++it) { leaf->setValueOnly(it.pos(), static_cast(vec[*it])); } } }; } // namespace internal template inline typename VIndexTreeType::template ValueConverter::Type::Ptr createTreeFromVector( const math::pcg::Vector& vector, const VIndexTreeType& idxTree, const TreeValueType& background) { typedef typename VIndexTreeType::template ValueConverter::Type OutTreeT; typedef typename tree::LeafManager VIdxLeafMgrT; // Construct an output tree with the same active voxel topology as the index tree. typename OutTreeT::Ptr result(new OutTreeT(idxTree, background, TopologyCopy())); OutTreeT& tree = *result; // Parallelize over the leaf nodes of the index tree, populating voxels // of the output tree with values from the input vector. VIdxLeafMgrT leafManager(idxTree); leafManager.foreach( internal::CopyFromVecOp(vector, tree)); return result; } //////////////////////////////////////// namespace internal { /// Constant boundary condition functor template struct DirichletOp { inline void operator()( const Coord&, const Coord&, ValueType&, ValueType& diag) const { diag -= 1; } }; /// Functor for use with LeafManager::foreach() to populate a sparse Laplacian matrix template struct ISLaplacianOp { typedef typename BoolTreeType::template ValueConverter::Type VIdxTreeT; typedef typename VIdxTreeT::LeafNodeType VIdxLeafT; typedef LaplacianMatrix::ValueType ValueT; typedef typename math::pcg::Vector VectorT; LaplacianMatrix* laplacian; const VIdxTreeT* idxTree; const BoolTreeType* interiorMask; const BoundaryOp boundaryOp; VectorT* source; ISLaplacianOp(LaplacianMatrix& m, const VIdxTreeT& idx, const BoolTreeType& mask, const BoundaryOp& op, VectorT& src): laplacian(&m), idxTree(&idx), interiorMask(&mask), boundaryOp(op), source(&src) {} void operator()(const VIdxLeafT& idxLeaf, size_t /*leafIdx*/) const { // Local accessors typename tree::ValueAccessor interior(*interiorMask); typename tree::ValueAccessor vectorIdx(*idxTree); Coord ijk; VIndex column; const ValueT diagonal = -6.f, offDiagonal = 1.f; // Loop over active voxels in this leaf. for (typename VIdxLeafT::ValueOnCIter it = idxLeaf.cbeginValueOn(); it; ++it) { assert(it.getValue() > -1); const math::pcg::SizeType rowNum = static_cast(it.getValue()); LaplacianMatrix::RowEditor row = laplacian->getRowEditor(rowNum); ijk = it.getCoord(); if (interior.isValueOn(ijk)) { // The current voxel is an interior voxel. // All of its neighbors are in the solution domain. // -x direction row.setValue(vectorIdx.getValue(ijk.offsetBy(-1, 0, 0)), offDiagonal); // -y direction row.setValue(vectorIdx.getValue(ijk.offsetBy(0, -1, 0)), offDiagonal); // -z direction row.setValue(vectorIdx.getValue(ijk.offsetBy(0, 0, -1)), offDiagonal); // diagonal row.setValue(rowNum, diagonal); // +z direction row.setValue(vectorIdx.getValue(ijk.offsetBy(0, 0, 1)), offDiagonal); // +y direction row.setValue(vectorIdx.getValue(ijk.offsetBy(0, 1, 0)), offDiagonal); // +x direction row.setValue(vectorIdx.getValue(ijk.offsetBy(1, 0, 0)), offDiagonal); } else { // The current voxel is a boundary voxel. // At least one of its neighbors is outside the solution domain. ValueT modifiedDiagonal = 0.f; // -x direction if (vectorIdx.probeValue(ijk.offsetBy(-1, 0, 0), column)) { row.setValue(column, offDiagonal); modifiedDiagonal -= 1; } else { boundaryOp(ijk, ijk.offsetBy(-1, 0, 0), source->at(rowNum), modifiedDiagonal); } // -y direction if (vectorIdx.probeValue(ijk.offsetBy(0, -1, 0), column)) { row.setValue(column, offDiagonal); modifiedDiagonal -= 1; } else { boundaryOp(ijk, ijk.offsetBy(0, -1, 0), source->at(rowNum), modifiedDiagonal); } // -z direction if (vectorIdx.probeValue(ijk.offsetBy(0, 0, -1), column)) { row.setValue(column, offDiagonal); modifiedDiagonal -= 1; } else { boundaryOp(ijk, ijk.offsetBy(0, 0, -1), source->at(rowNum), modifiedDiagonal); } // +z direction if (vectorIdx.probeValue(ijk.offsetBy(0, 0, 1), column)) { row.setValue(column, offDiagonal); modifiedDiagonal -= 1; } else { boundaryOp(ijk, ijk.offsetBy(0, 0, 1), source->at(rowNum), modifiedDiagonal); } // +y direction if (vectorIdx.probeValue(ijk.offsetBy(0, 1, 0), column)) { row.setValue(column, offDiagonal); modifiedDiagonal -= 1; } else { boundaryOp(ijk, ijk.offsetBy(0, 1, 0), source->at(rowNum), modifiedDiagonal); } // +x direction if (vectorIdx.probeValue(ijk.offsetBy(1, 0, 0), column)) { row.setValue(column, offDiagonal); modifiedDiagonal -= 1; } else { boundaryOp(ijk, ijk.offsetBy(1, 0, 0), source->at(rowNum), modifiedDiagonal); } // diagonal row.setValue(rowNum, modifiedDiagonal); } } // end loop over voxels } }; } // namespace internal template inline LaplacianMatrix::Ptr createISLaplacian(const typename BoolTreeType::template ValueConverter::Type& idxTree, const BoolTreeType& interiorMask) { typedef LaplacianMatrix::ValueType ValueT; math::pcg::Vector unused( static_cast(idxTree.activeVoxelCount())); internal::DirichletOp op; return createISLaplacianWithBoundaryConditions(idxTree, interiorMask, op, unused); } template inline LaplacianMatrix::Ptr createISLaplacianWithBoundaryConditions( const typename BoolTreeType::template ValueConverter::Type& idxTree, const BoolTreeType& interiorMask, const BoundaryOp& boundaryOp, typename math::pcg::Vector& source) { typedef typename BoolTreeType::template ValueConverter::Type VIdxTreeT; typedef typename tree::LeafManager VIdxLeafMgrT; // The number of active voxels is the number of degrees of freedom. const Index64 numDoF = idxTree.activeVoxelCount(); // Construct the matrix. LaplacianMatrix::Ptr laplacianPtr( new LaplacianMatrix(static_cast(numDoF))); LaplacianMatrix& laplacian = *laplacianPtr; // Populate the matrix using a second-order, 7-point CD stencil. VIdxLeafMgrT idxLeafManager(idxTree); idxLeafManager.foreach(internal::ISLaplacianOp( laplacian, idxTree, interiorMask, boundaryOp, source)); return laplacianPtr; } //////////////////////////////////////// template inline typename TreeType::Ptr solve(const TreeType& inTree, math::pcg::State& state) { util::NullInterrupter interrupter; return solve(inTree, state, interrupter); } template inline typename TreeType::Ptr solve(const TreeType& inTree, math::pcg::State& state, Interrupter& interrupter) { internal::DirichletOp boundaryOp; return solveWithBoundaryConditions(inTree, boundaryOp, state, interrupter); } template inline typename TreeType::Ptr solveWithBoundaryConditions(const TreeType& inTree, const BoundaryOp& boundaryOp, math::pcg::State& state, Interrupter& interrupter) { typedef math::pcg::IncompleteCholeskyPreconditioner DefaultPrecondT; return solveWithBoundaryConditionsAndPreconditioner( inTree, boundaryOp, state, interrupter); } template inline typename TreeType::Ptr solveWithBoundaryConditionsAndPreconditioner(const TreeType& inTree, const BoundaryOp& boundaryOp, math::pcg::State& state, Interrupter& interrupter) { return solveWithBoundaryConditionsAndPreconditioner(inTree /*source*/, inTree /*domain mask*/, boundaryOp, state, interrupter); } template inline typename TreeType::Ptr solveWithBoundaryConditionsAndPreconditioner(const TreeType& inTree, const DomainTreeType& domainMask, const BoundaryOp& boundaryOp, math::pcg::State& state, Interrupter& interrupter) { typedef typename TreeType::ValueType TreeValueT; typedef LaplacianMatrix::ValueType VecValueT; typedef typename math::pcg::Vector VectorT; typedef typename TreeType::template ValueConverter::Type VIdxTreeT; typedef typename TreeType::template ValueConverter::Type MaskTreeT; // 1. Create a mapping from active voxels of the input tree to elements of a vector. typename VIdxTreeT::ConstPtr idxTree = createIndexTree(domainMask); // 2. Populate a vector with values from the input tree. typename VectorT::Ptr b = createVectorFromTree(inTree, *idxTree); // 3. Create a mask of the interior voxels of the input tree (from the densified index tree). typename MaskTreeT::Ptr interiorMask( new MaskTreeT(*idxTree, /*background=*/false, TopologyCopy())); tools::erodeVoxels(*interiorMask, /*iterations=*/1, tools::NN_FACE); // 4. Create the Laplacian matrix. LaplacianMatrix::Ptr laplacian = createISLaplacianWithBoundaryConditions( *idxTree, *interiorMask, boundaryOp, *b); // 5. Solve the Poisson equation. laplacian->scale(-1.0); // matrix is negative-definite; solve -M x = -b b->scale(-1.0); typename VectorT::Ptr x(new VectorT(b->size(), zeroVal())); typename math::pcg::Preconditioner::Ptr precond( new PreconditionerType(*laplacian)); if (!precond->isValid()) { precond.reset(new math::pcg::JacobiPreconditioner(*laplacian)); } state = math::pcg::solve(*laplacian, *b, *x, *precond, interrupter, state); // 6. Populate the output tree with values from the solution vector. /// @todo if (state.success) ... ? return createTreeFromVector(*x, *idxTree, /*background=*/zeroVal()); } } // namespace poisson } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_POISSONSOLVER_HAS_BEEN_INCLUDED /////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// openvdb/tools/Composite.h0000644000000000000000000005124012603226506014470 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 "Prune.h"// for prune #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))); } template inline typename boost::disable_if, T>::type // = T if T is not an integer type divide(const T& a, const T& b) { return a / b; } template inline typename boost::enable_if, T>::type // = T if T is an integer type divide(const T& a, const T& b) { const T zero(0); if (b != zero) return a / b; if (a == zero) return 0; return (a > 0 ? std::numeric_limits::max() : -std::numeric_limits::max()); } // If b is true, return a / 1 = a. // If b is false and a is true, return 1 / 0 = inf = MAX_BOOL = 1 = a. // If b is false and a is false, return 0 / 0 = NaN = 0 = a. inline bool divide(bool a, bool /*b*/) { return a; } } // 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(composite::divide(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) tools::pruneLevelSet(aTree); } 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) tools::pruneLevelSet(aTree); } 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) tools::pruneLevelSet(aTree); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_COMPOSITE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/GridOperators.h0000644000000000000000000011556712603226506015327 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Morphology.h0000644000000000000000000010246312603226506014671 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 Morphology.h /// /// @brief Implementation of morphological dilation and erosion. /// /// @note By design the morphological operations only change the /// state of voxels, not their values. If one desires to /// change the values of voxels that change state an efficient /// technique is to construct a boolean mask by performing a /// topology difference between the original and final grids. /// /// @todo Extend erosion with 18 and 26 neighbors (coming soon!) /// /// @author Ken Museth /// #ifndef OPENVDB_TOOLS_MORPHOLOGY_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_MORPHOLOGY_HAS_BEEN_INCLUDED #include #include #include // for isApproxEqual() #include #include #include #include #include #include "Prune.h"// for pruneLevelSet #include "ValueTransformer.h" // for foreach() namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Voxel topology of nearest neighbors /// @details ///
///
NN_FACE ///
face adjacency (6 nearest neighbors, defined as all neighbor /// voxels connected along one of the primary axes) /// ///
NN_FACE_EDGE ///
face and edge adjacency (18 nearest neighbors, defined as all /// neighbor voxels connected along either one or two of the primary axes) /// ///
NN_FACE_EDGE_VERTEX ///
face, edge and vertex adjacency (26 nearest neighbors, defined /// as all neighbor voxels connected along either one, two or all /// three of the primary axes) ///
enum NearestNeighbors { NN_FACE = 6, NN_FACE_EDGE = 18, NN_FACE_EDGE_VERTEX = 26 }; /// @brief Topologically dilate all leaf-level active voxels in a tree /// using one of three nearest neighbor connectivity patterns. /// /// @param tree tree to be dilated /// @param iterations number of iterations to apply the dilation /// @param nn connectivity pattern of the dilation: either /// face-adjacent (6 nearest neighbors), face- and edge-adjacent /// (18 nearest neighbors) or face-, edge- and vertex-adjacent (26 /// nearest neighbors). /// /// @note The values of any voxels are unchanged. /// @todo Currently operates only on leaf voxels; need to extend to tiles. template OPENVDB_STATIC_SPECIALIZATION inline void dilateVoxels(TreeType& tree, int iterations = 1, NearestNeighbors nn = NN_FACE); /// @brief Topologically dilate all leaf-level active voxels in a tree /// using one of three nearest neighbor connectivity patterns. /// /// @param manager LeafManager containing the tree to be dilated. /// @param iterations number of iterations to apply the dilation /// @param nn connectivity pattern of the dilation: either /// face-adjacent (6 nearest neighbors), face- and edge-adjacent /// (18 nearest neighbors) or face-, edge- and vertex-adjacent (26 /// nearest neighbors). /// /// @note The values of any voxels are unchanged. /// @todo Currently operates only on leaf voxels; need to extend to tiles. template OPENVDB_STATIC_SPECIALIZATION inline void dilateVoxels(tree::LeafManager& manager, int iterations = 1, NearestNeighbors nn = NN_FACE); //@{ /// @brief Topologically erode all leaf-level active voxels in the given tree. /// @details That is, shrink the set of active voxels by @a iterations 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 iterations=1, NearestNeighbors nn = NN_FACE); template OPENVDB_STATIC_SPECIALIZATION inline void erodeVoxels(tree::LeafManager& manager, int iterations = 1, NearestNeighbors nn = NN_FACE); //@} /// @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; } /// @brief Face-adjacent dilation pattern void dilateVoxels6(); /// @brief Face- and edge-adjacent dilation pattern. void dilateVoxels18(); /// @brief Face-, edge- and vertex-adjacent dilation pattern. void dilateVoxels26(); void dilateVoxels(int iterations = 1, NearestNeighbors nn = NN_FACE); /// @brief Face-adjacent erosion pattern. void erodeVoxels6() { mSteps = 1; this->doErosion(NN_FACE); } /// @brief Face- and edge-adjacent erosion pattern. void erodeVoxels18() { mSteps = 1; this->doErosion(NN_FACE_EDGE); } /// @brief Face-, edge- and vertex-adjacent erosion pattern. void erodeVoxels26() { mSteps = 1; this->doErosion(NN_FACE_EDGE_VERTEX); } void erodeVoxels(int iterations = 1, NearestNeighbors nn = NN_FACE) { mSteps = iterations; this->doErosion(nn); } protected: void doErosion(NearestNeighbors nn); 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() { leaf = NULL; init = true; } template void scatter(AccessorType& acc, const Coord &xyz, int indx, Word mask) { 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); } #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif const int N = (LEAF_DIM - 1)*(DY + DX*LEAF_DIM); if (leaf) leaf->getValueMask().template getWord(indx-N) |= mask; } 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); } #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif 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 LeafCache { LeafCache(size_t n, TreeType& tree) : size(n), leafs(new LeafType*[n]), acc(tree) { onTile.setValuesOn(); this->clear(); } ~LeafCache() { delete [] leafs; } LeafType*& operator[](int offset) { return leafs[offset]; } inline void clear() { for (size_t i=0; igetValueMask().template getWord(indx) |= mask; } template inline void scatter(int n, int indx) { if (!leafs[n]) { const Coord xyz = origin->offsetBy(DX*LEAF_DIM, DY*LEAF_DIM, DZ*LEAF_DIM); leafs[n] = acc.probeLeaf(xyz); if (!leafs[n]) leafs[n] = acc.isValueOn(xyz) ? &onTile : acc.touchLeaf(xyz); } this->scatter(n, indx - (LEAF_DIM - 1)*(DY + DX*LEAF_DIM)); } inline Word gather(int n, int indx) { assert(leafs[n]); return leafs[n]->getValueMask().template getWord(indx); } template inline Word gather(int n, int indx) { if (!leafs[n]) { const Coord xyz = origin->offsetBy(DX*LEAF_DIM, DY*LEAF_DIM, DZ*LEAF_DIM); leafs[n] = acc.probeLeaf(xyz); if (!leafs[n]) leafs[n] = acc.isValueOn(xyz) ? &onTile : &offTile; } return this->gather(n, indx - (LEAF_DIM -1 )*(DY + DX*LEAF_DIM)); } // Scatters in the xy face-directions relative to leaf i1 void scatterFacesXY(int x, int y, int i1, int n, int i2); // Scatters in the xy edge-directions relative to leaf i1 void scatterEdgesXY(int x, int y, int i1, int n, int i2); Word gatherFacesXY(int x, int y, int i1, int n, int i2); Word gatherEdgesXY(int x, int y, int i1, int n, int i2); const Coord* origin; size_t size; LeafType** leafs; LeafType onTile, offTile; AccessorType acc; Word mask; };// LeafCache struct ErodeVoxelsOp { typedef tbb::blocked_range RangeT; ErodeVoxelsOp(std::vector& masks, ManagerType& manager) : mTask(0), mSavedMasks(masks) , mManager(manager) {} void runParallel(NearestNeighbors nn); void operator()(const RangeT& r) const {mTask(const_cast(this), r);} void erode6( const RangeT&) const; void erode18(const RangeT&) const; void erode26(const RangeT&) const; private: typedef typename boost::function FuncT; FuncT mTask; 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 struct UpdateMasks { UpdateMasks(const std::vector& masks, ManagerType& manager) : mMasks(masks), mManager(manager) {} void update() { tbb::parallel_for(mManager.getRange(), *this); } void operator()(const tbb::blocked_range& r) const { for (size_t i=r.begin(); i& mMasks; ManagerType& mManager; }; struct CopyMasks { CopyMasks(std::vector& masks, const ManagerType& manager) : mMasks(masks), mManager(manager) {} void copy() { tbb::parallel_for(mManager.getRange(), *this); } void operator()(const tbb::blocked_range& r) const { for (size_t i=r.begin(); i& mMasks; const ManagerType& mManager; }; void copyMasks(std::vector& a, const ManagerType& b) {CopyMasks c(a, b); c.copy();} };// Morphology template inline void Morphology::dilateVoxels(int iterations, NearestNeighbors nn) { for (int i=0; idilateVoxels18(); break; case NN_FACE_EDGE_VERTEX: this->dilateVoxels26(); break; default: this->dilateVoxels6(); } } } template inline void Morphology::dilateVoxels6() { /// @todo Currently operates only on leaf voxels; need to extend to tiles. const int leafCount = static_cast(mManager->leafCount()); // Save the value masks of all leaf nodes. std::vector savedMasks(leafCount); this->copyMasks(savedMasks, *mManager); LeafCache cache(7, mManager->tree()); for (int leafIdx = 0; leafIdx < leafCount; ++leafIdx) { const MaskType& oldMask = savedMasks[leafIdx];//original bit-mask of current leaf node cache[0] = &mManager->leaf(leafIdx); cache.setOrigin(cache[0]->origin()); 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. if (const Word w = oldMask.template getWord(n)) { // Dilate the current leaf in the +z and -z direction cache.mask = Word(w | (w>>1) | (w<<1)); cache.scatter(0, n); // Dilate into neighbor leaf in the -z direction if ( (cache.mask = Word(w<<(LEAF_DIM-1))) ) { cache.template scatter< 0, 0,-1>(1, n); } // Dilate into neighbor leaf in the +z direction if ( (cache.mask = Word(w>>(LEAF_DIM-1))) ) { cache.template scatter< 0, 0, 1>(2, n); } // Dilate in the xy-face directions relative to the center leaf cache.mask = w; cache.scatterFacesXY(x, y, 0, n, 3); } }// loop over y }//loop over x cache.clear(); }//loop over leafs mManager->rebuildLeafArray(); }//dilateVoxels6 template inline void Morphology::dilateVoxels18() { /// @todo Currently operates only on leaf voxels; need to extend to tiles. const int leafCount = static_cast(mManager->leafCount()); // Save the value masks of all leaf nodes. std::vector savedMasks(leafCount); this->copyMasks(savedMasks, *mManager); LeafCache cache(19, mManager->tree()); Coord orig_mz, orig_pz;//origins of neighbor leaf nodes in the -z and +z directions for (int leafIdx = 0; leafIdx < leafCount; ++leafIdx) { const MaskType& oldMask = savedMasks[leafIdx];//original bit-mask of current leaf node cache[0] = &mManager->leaf(leafIdx); orig_mz = cache[0]->origin().offsetBy(0, 0, -LEAF_DIM); orig_pz = cache[0]->origin().offsetBy(0, 0, LEAF_DIM); for (int x = 0; x < LEAF_DIM; ++x ) { for (int y = 0, n = (x << LEAF_LOG2DIM); y < LEAF_DIM; ++y, ++n) { if (const Word w = oldMask.template getWord(n)) { { cache.mask = Word(w | (w>>1) | (w<<1)); cache.setOrigin(cache[0]->origin()); cache.scatter(0, n); cache.scatterFacesXY(x, y, 0, n, 3); cache.mask = w; cache.scatterEdgesXY(x, y, 0, n, 3); } if ( (cache.mask = Word(w<<(LEAF_DIM-1))) ) { cache.setOrigin(cache[0]->origin()); cache.template scatter< 0, 0,-1>(1, n); cache.setOrigin(orig_mz); cache.scatterFacesXY(x, y, 1, n, 11); } if ( (cache.mask = Word(w>>(LEAF_DIM-1))) ) { cache.setOrigin(cache[0]->origin()); cache.template scatter< 0, 0, 1>(2, n); cache.setOrigin(orig_pz); cache.scatterFacesXY(x, y, 2, n, 15); } } }// loop over y }//loop over x cache.clear(); }//loop over leafs mManager->rebuildLeafArray(); }// dilateVoxels18 template inline void Morphology::dilateVoxels26() { /// @todo Currently operates only on leaf voxels; need to extend to tiles. const int leafCount = static_cast(mManager->leafCount()); // Save the value masks of all leaf nodes. std::vector savedMasks(leafCount); this->copyMasks(savedMasks, *mManager); LeafCache cache(27, mManager->tree()); Coord orig_mz, orig_pz;//origins of neighbor leaf nodes in the -z and +z directions for (int leafIdx = 0; leafIdx < leafCount; ++leafIdx) { const MaskType& oldMask = savedMasks[leafIdx];//original bit-mask of current leaf node cache[0] = &mManager->leaf(leafIdx); orig_mz = cache[0]->origin().offsetBy(0, 0, -LEAF_DIM); orig_pz = cache[0]->origin().offsetBy(0, 0, LEAF_DIM); for (int x = 0; x < LEAF_DIM; ++x ) { for (int y = 0, n = (x << LEAF_LOG2DIM); y < LEAF_DIM; ++y, ++n) { if (const Word w = oldMask.template getWord(n)) { { cache.mask = Word(w | (w>>1) | (w<<1)); cache.setOrigin(cache[0]->origin()); cache.scatter(0, n); cache.scatterFacesXY(x, y, 0, n, 3); cache.scatterEdgesXY(x, y, 0, n, 3); } if ( (cache.mask = Word(w<<(LEAF_DIM-1))) ) { cache.setOrigin(cache[0]->origin()); cache.template scatter< 0, 0,-1>(1, n); cache.setOrigin(orig_mz); cache.scatterFacesXY(x, y, 1, n, 11); cache.scatterEdgesXY(x, y, 1, n, 11); } if ( (cache.mask = Word(w>>(LEAF_DIM-1))) ) { cache.setOrigin(cache[0]->origin()); cache.template scatter< 0, 0, 1>(2, n); cache.setOrigin(orig_pz); cache.scatterFacesXY(x, y, 2, n, 19); cache.scatterEdgesXY(x, y, 2, n, 19); } } }// loop over y }//loop over x cache.clear(); }//loop over leafs mManager->rebuildLeafArray(); }// dilateVoxels26 template inline void Morphology::LeafCache::scatterFacesXY(int x, int y, int i1, int n, int i2) { // dilate current leaf or neighbor in the -x direction if (x > 0) { this->scatter(i1, n-LEAF_DIM); } else { this->template scatter<-1, 0, 0>(i2, n); } // dilate current leaf or neighbor in the +x direction if (x < LEAF_DIM-1) { this->scatter(i1, n+LEAF_DIM); } else { this->template scatter< 1, 0, 0>(i2+1, n); } // dilate current leaf or neighbor in the -y direction if (y > 0) { this->scatter(i1, n-1); } else { this->template scatter< 0,-1, 0>(i2+2, n); } // dilate current leaf or neighbor in the +y direction if (y < LEAF_DIM-1) { this->scatter(i1, n+1); } else { this->template scatter< 0, 1, 0>(i2+3, n); } } template inline void Morphology::LeafCache::scatterEdgesXY(int x, int y, int i1, int n, int i2) { if (x > 0) { if (y > 0) { this->scatter(i1, n-LEAF_DIM-1); } else { this->template scatter< 0,-1, 0>(i2+2, n-LEAF_DIM); } if (y < LEAF_DIM-1) { this->scatter(i1, n-LEAF_DIM+1); } else { this->template scatter< 0, 1, 0>(i2+3, n-LEAF_DIM); } } else { if (y < LEAF_DIM-1) { this->template scatter<-1, 0, 0>(i2 , n+1); } else { this->template scatter<-1, 1, 0>(i2+7, n ); } if (y > 0) { this->template scatter<-1, 0, 0>(i2 , n-1); } else { this->template scatter<-1,-1, 0>(i2+4, n ); } } if (x < LEAF_DIM-1) { if (y > 0) { this->scatter(i1, n+LEAF_DIM-1); } else { this->template scatter< 0,-1, 0>(i2+2, n+LEAF_DIM); } if (y < LEAF_DIM-1) { this->scatter(i1, n+LEAF_DIM+1); } else { this->template scatter< 0, 1, 0>(i2+3, n+LEAF_DIM); } } else { if (y > 0) { this->template scatter< 1, 0, 0>(i2+1, n-1); } else { this->template scatter< 1,-1, 0>(i2+6, n ); } if (y < LEAF_DIM-1) { this->template scatter< 1, 0, 0>(i2+1, n+1); } else { this->template scatter< 1, 1, 0>(i2+5, n ); } } } template inline void Morphology::ErodeVoxelsOp::runParallel(NearestNeighbors nn) { switch (nn) { case NN_FACE_EDGE: mTask = boost::bind(&ErodeVoxelsOp::erode18, _1, _2); break; case NN_FACE_EDGE_VERTEX: mTask = boost::bind(&ErodeVoxelsOp::erode26, _1, _2); break; default: mTask = boost::bind(&ErodeVoxelsOp::erode6, _1, _2); } tbb::parallel_for(mManager.getRange(), *this); } template inline typename Morphology::Word Morphology::LeafCache::gatherFacesXY(int x, int y, int i1, int n, int i2) { // erode current leaf or neighbor in negative x-direction Word w = x>0 ? this->gather(i1,n-LEAF_DIM) : this->template gather<-1,0,0>(i2, n); // erode current leaf or neighbor in positive x-direction w = Word(w & (xgather(i1,n+LEAF_DIM):this->template gather<1,0,0>(i2+1,n))); // erode current leaf or neighbor in negative y-direction w = Word(w & (y>0 ? this->gather(i1, n-1) : this->template gather<0,-1,0>(i2+2, n))); // erode current leaf or neighbor in positive y-direction w = Word(w & (ygather(i1, n+1) : this->template gather<0,1,0>(i2+3, n))); return w; } template inline typename Morphology::Word Morphology::LeafCache::gatherEdgesXY(int x, int y, int i1, int n, int i2) { Word w = ~Word(0); if (x > 0) { w &= y > 0 ? this->gather(i1, n-LEAF_DIM-1) : this->template gather< 0,-1, 0>(i2+2, n-LEAF_DIM); w &= y < LEAF_DIM-1 ? this->gather(i1, n-LEAF_DIM+1) : this->template gather< 0, 1, 0>(i2+3, n-LEAF_DIM); } else { w &= y < LEAF_DIM-1 ? this->template gather<-1, 0, 0>(i2 , n+1): this->template gather<-1, 1, 0>(i2+7, n ); w &= y > 0 ? this->template gather<-1, 0, 0>(i2 , n-1): this->template gather<-1,-1, 0>(i2+4, n ); } if (x < LEAF_DIM-1) { w &= y > 0 ? this->gather(i1, n+LEAF_DIM-1) : this->template gather< 0,-1, 0>(i2+2, n+LEAF_DIM); w &= y < LEAF_DIM-1 ? this->gather(i1, n+LEAF_DIM+1) : this->template gather< 0, 1, 0>(i2+3, n+LEAF_DIM); } else { w &= y > 0 ? this->template gather< 1, 0, 0>(i2+1, n-1): this->template gather< 1,-1, 0>(i2+6, n ); w &= y < LEAF_DIM-1 ? this->template gather< 1, 0, 0>(i2+1, n+1): this->template gather< 1, 1, 0>(i2+5, n ); } return w; } template inline void Morphology::ErodeVoxelsOp::erode6(const RangeT& range) const { LeafCache cache(7, mManager.tree()); for (size_t leafIdx = range.begin(); leafIdx < range.end(); ++leafIdx) { cache[0] = &mManager.leaf(leafIdx); if (cache[0]->isEmpty()) continue; cache.setOrigin(cache[0]->origin()); MaskType& newMask = mSavedMasks[leafIdx];//original bit-mask of 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. if (Word& w = newMask.template getWord(n)) { // erode in two z directions (this is first since it uses the original w) w = Word(w & (Word(w<<1 | (cache.template gather<0,0,-1>(1, n)>>(LEAF_DIM-1))) & Word(w>>1 | (cache.template gather<0,0, 1>(2, n)<<(LEAF_DIM-1))))); w = Word(w & cache.gatherFacesXY(x, y, 0, n, 3)); } }// loop over y }//loop over x cache.clear(); }//loop over leafs } template inline void Morphology::ErodeVoxelsOp::erode18(const RangeT&) const { OPENVDB_THROW(NotImplementedError, "tools::erode18 is not implemented yet!"); } template inline void Morphology::ErodeVoxelsOp::erode26(const RangeT&) const { OPENVDB_THROW(NotImplementedError, "tools::erode26 is not implemented yet!"); } template inline void Morphology::doErosion(NearestNeighbors nn) { /// @todo Currently operates only on leaf voxels; need to extend to tiles. const size_t leafCount = mManager->leafCount(); // Save the value masks of all leaf nodes. std::vector savedMasks(leafCount); this->copyMasks(savedMasks, *mManager); UpdateMasks a(savedMasks, *mManager); ErodeVoxelsOp erode(savedMasks, *mManager); for (int i = 0; i < mSteps; ++i) { erode.runParallel(nn); a.update(); } tools::pruneLevelSet(mManager->tree()); } //////////////////////////////////////// template OPENVDB_STATIC_SPECIALIZATION inline void dilateVoxels(tree::LeafManager& manager, int iterations, NearestNeighbors nn) { if (iterations > 0 ) { Morphology m(&manager); m.dilateVoxels(iterations, nn); } } template OPENVDB_STATIC_SPECIALIZATION inline void dilateVoxels(TreeType& tree, int iterations, NearestNeighbors nn) { if (iterations > 0 ) { Morphology m(tree); m.dilateVoxels(iterations, nn); } } template OPENVDB_STATIC_SPECIALIZATION inline void erodeVoxels(tree::LeafManager& manager, int iterations, NearestNeighbors nn) { if (iterations > 0 ) { Morphology m(&manager); m.erodeVoxels(iterations, nn); } } template OPENVDB_STATIC_SPECIALIZATION inline void erodeVoxels(TreeType& tree, int iterations, NearestNeighbors nn) { if (iterations > 0 ) { Morphology m(tree); m.erodeVoxels(iterations, nn); } } //////////////////////////////////////// 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetTracker.h0000644000000000000000000006070712603226506015575 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 "ChangeBackground.h"// for changeLevelSetBackground #include "Morphology.h"//for dilateVoxels #include "Prune.h"// for pruneLevelSet 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::LeafRange LeafRange; typedef typename LeafManagerType::BufferType BufferType; typedef typename TreeType::template ValueConverter::Type BoolMaskType; BOOST_STATIC_ASSERT(boost::is_floating_point::value); /// Lightweight struct that stores the state of the LevelSetTracker struct State { State(math::BiasedGradientScheme s = math::HJWENO5_BIAS, math::TemporalIntegrationScheme t = math::TVD_RK1, int n = static_cast(LEVEL_SET_HALF_WIDTH), int g = 1) : spatialScheme(s), temporalScheme(t), normCount(n), grainSize(g) {} math::BiasedGradientScheme spatialScheme; math::TemporalIntegrationScheme temporalScheme; int normCount;// Number of iterations of normalization int grainSize; }; /// @brief Main constructor /// @throw RuntimeError if the grid is not a level set LevelSetTracker(GridT& grid, InterruptT* interrupt = NULL); virtual ~LevelSetTracker() { delete mLeafs; } /// @brief Iterative normalization, i.e. solving the Eikonal equation /// @note The mask it optional and by default it is ignored. template void normalize(const MaskType* mask); /// @brief Iterative normalization, i.e. solving the Eikonal equation void normalize() { this->normalize(NULL); } /// @brief Track the level set interface, i.e. rebuild and normalize the /// narrow band of the level set. void track(); /// @brief Remove voxels that are outside the narrow band. (substep of track) void prune(); /// @brief Fast but approximate dilation of the narrow band - one /// layer at a time. Normally we recommend using the resize method below /// which internally calls dilate (or erode) with the correct /// number of @a iterations to achieve the desired half voxel width /// of the narrow band (3 is recomended for most level set applications). /// /// @note Since many level set applications perform /// interface-tracking, which in turn rebuilds the narrow-band /// accurately, this dilate method can often be used with a /// single iterations of low-order re-normalization. This /// effectively allows very narrow bands to be created from points /// or polygons (e.g. with a half voxel width of 1), followed by a /// fast but approximate dilation (typically with a half voxel /// width of 3). This can be significantly faster than generating /// the final width of the narrow band from points or polygons. void dilate(int iterations = 1); /// @brief Erodes the width of the narrow-band and update the background values /// @throw ValueError if @a iterations is larger than the current half-width. void erode(int iterations = 1); /// @brief Resize the width of the narrow band, i.e. perform /// dilation and renormalization or erosion as required. bool resize(Index halfWidth = static_cast(LEVEL_SET_HALF_WIDTH)); /// @brief Return the half width of the narrow band in floating-point voxel units. ValueType getHalfWidth() const { return mGrid->background()/mDx; } /// @brief Return the state of the tracker (see struct defined above) State getState() const { return mState; } /// @brief Set the state of the tracker (see struct defined above) void setState(const State& s) { mState =s; } /// @return the spatial finite difference scheme math::BiasedGradientScheme getSpatialScheme() const { return mState.spatialScheme; } /// @brief Set the spatial finite difference scheme void setSpatialScheme(math::BiasedGradientScheme scheme) { mState.spatialScheme = scheme; } /// @return the temporal integration scheme math::TemporalIntegrationScheme getTemporalScheme() const { return mState.temporalScheme; } /// @brief Set the spatial finite difference scheme void setTemporalScheme(math::TemporalIntegrationScheme scheme) { mState.temporalScheme = scheme;} /// @return The number of normalizations performed per track or /// normalize call. int getNormCount() const { return mState.normCount; } /// @brief Set the number of normalizations performed per track or /// normalize call. void setNormCount(int n) { mState.normCount = n; } /// @return the grain-size used for multi-threading int getGrainSize() const { return mState.grainSize; } /// @brief Set the grain-size used for multi-threading. /// @note A grainsize of 0 or less disables multi-threading! void setGrainSize(int grainsize) { mState.grainSize = 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; } private: // disallow copy construction and copy by assignment! LevelSetTracker(const LevelSetTracker&);// not implemented LevelSetTracker& operator=(const LevelSetTracker&);// not implemented // Private class to perform multi-threaded trimming of // voxels that are too far away from the zero-crossing. struct Trim { Trim(LevelSetTracker& tracker) : mTracker(tracker) {} void trim(); void operator()(const LeafRange& r) const; LevelSetTracker& mTracker; };// Trim // Private struct to perform multi-threaded normalization template struct Normalizer { typedef math::BIAS_SCHEME SchemeT; typedef typename SchemeT::template ISStencil::StencilType StencilT; typedef typename MaskT::LeafNodeType MaskLeafT; typedef typename MaskLeafT::ValueOnCIter MaskIterT; typedef typename LeafType::ValueOnCIter VoxelIterT; Normalizer(LevelSetTracker& tracker, const MaskT* mask); void normalize(); void operator()(const LeafRange& r) const {mTask(const_cast(this), r);} void cook(int swapBuffer=0); template void euler(const LeafRange& range, Index phiBuffer, Index resultBuffer); inline void euler01(const LeafRange& r) {this->euler<0,1>(r, 0, 1);} inline void euler12(const LeafRange& r) {this->euler<1,2>(r, 1, 1);} inline void euler34(const LeafRange& r) {this->euler<3,4>(r, 1, 2);} inline void euler13(const LeafRange& r) {this->euler<1,3>(r, 1, 2);} template void eval(StencilT& stencil, const ValueType* phi, ValueType* result, Index n) const; LevelSetTracker& mTracker; const MaskT* mMask; const ValueType mDt, mInvDx; typename boost::function mTask; }; // Normalizer struct template void normalize1(const MaskT* mask); template void normalize2(const MaskT* mask); // 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; State mState; }; // end of LevelSetTracker class template LevelSetTracker:: LevelSetTracker(GridT& grid, InterruptT* interrupt): mGrid(&grid), mLeafs(new LeafManagerType(grid.tree())), mInterrupter(interrupt), mDx(static_cast(grid.voxelSize()[0])), mState() { 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 expected a level set, got a grid of class \"" + grid.gridClassToString(grid.getGridClass()) + "\" [hint: Grid::setGridClass(openvdb::GRID_LEVEL_SET)]"); } } template inline void LevelSetTracker:: prune() { this->startInterrupter("Pruning Level Set"); // Prune voxels that are too far away from the zero-crossing Trim t(*this); t.trim(); // Remove inactive nodes from tree tools::pruneLevelSet(mGrid->tree()); // 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:: dilate(int iterations) { if (this->getNormCount() == 0) { for (int i=0; i < iterations; ++i) { tools::dilateVoxels(*mLeafs); mLeafs->rebuildLeafArray(); tools::changeLevelSetBackground(this->leafs(), mDx + mGrid->background()); } } else { for (int i=0; i < iterations; ++i) { BoolMaskType mask0(mGrid->tree(), false, TopologyCopy()); tools::dilateVoxels(*mLeafs); mLeafs->rebuildLeafArray(); tools::changeLevelSetBackground(this->leafs(), mDx + mGrid->background()); BoolMaskType mask(mGrid->tree(), false, TopologyCopy()); mask.topologyDifference(mask0); this->normalize(&mask); } } } template inline void LevelSetTracker:: erode(int iterations) { tools::erodeVoxels(*mLeafs, iterations); mLeafs->rebuildLeafArray(); const ValueType background = mGrid->background() - iterations*mDx; tools::changeLevelSetBackground(this->leafs(), background); } template inline bool LevelSetTracker:: resize(Index halfWidth) { const int wOld = static_cast(math::RoundDown(this->getHalfWidth())); const int wNew = static_cast(halfWidth); if (wOld < wNew) { this->dilate(wNew - wOld); } else if (wOld > wNew) { this->erode(wOld - wNew); } return wOld != wNew; } 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; } template template inline void LevelSetTracker:: normalize(const MaskT* mask) { switch (this->getSpatialScheme()) { case math::FIRST_BIAS: this->normalize1(mask); break; case math::SECOND_BIAS: this->normalize1(mask); break; case math::THIRD_BIAS: this->normalize1(mask); break; case math::WENO5_BIAS: this->normalize1(mask); break; case math::HJWENO5_BIAS: this->normalize1(mask); break; default: OPENVDB_THROW(ValueError, "Spatial difference scheme not supported!"); } } template template inline void LevelSetTracker:: normalize1(const MaskT* mask) { switch (this->getTemporalScheme()) { case math::TVD_RK1: this->normalize2(mask); break; case math::TVD_RK2: this->normalize2(mask); break; case math::TVD_RK3: this->normalize2(mask); break; default: OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); } } template template inline void LevelSetTracker:: normalize2(const MaskT* mask) { Normalizer tmp(*this, mask); tmp.normalize(); } //////////////////////////////////////////////////////////////////////////// template inline void LevelSetTracker:: Trim::trim() { const int grainSize = mTracker.getGrainSize(); const LeafRange range = mTracker.leafs().leafRange(grainSize); if (grainSize>0) { tbb::parallel_for(range, *this); } else { (*this)(range); } } /// Prunes away voxels that have moved outside the narrow band template inline void LevelSetTracker:: Trim::operator()(const LeafRange& range) const { typedef typename LeafType::ValueOnIter VoxelIterT; mTracker.checkInterrupter(); const ValueType gamma = mTracker.mGrid->background(); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { LeafType &leaf = *leafIter; 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 template inline LevelSetTracker:: Normalizer:: Normalizer(LevelSetTracker& tracker, const MaskT* mask) : mTracker(tracker) , mMask(mask) , mDt(tracker.voxelSize()*(TemporalScheme == math::TVD_RK1 ? 0.3f : TemporalScheme == math::TVD_RK2 ? 0.9f : 1.0f)) , mInvDx(1.0f/tracker.voxelSize()) , mTask(0) { } template template inline void LevelSetTracker:: Normalizer:: normalize() { /// Make sure we have enough temporal auxiliary buffers mTracker.mLeafs->rebuildAuxBuffers(TemporalScheme == math::TVD_RK3 ? 2 : 1); 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: // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(0) = Phi_t0(0) - dt * VdotG_t0(1) mTask = boost::bind(&Normalizer::euler01, _1, _2); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(1); break; case math::TVD_RK2: // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(1) mTask = boost::bind(&Normalizer::euler01, _1, _2); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(1); // Convex combine explicit 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::euler12, _1, _2); // Cook and swap buffer 0 and 1 such that Phi_t2(0) and Phi_t1(1) this->cook(1); break; case math::TVD_RK3: // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(1) mTask = boost::bind(&Normalizer::euler01, _1, _2); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(1); // Convex combine explicit 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::euler34, _1, _2); // Cook and swap buffer 0 and 2 such that Phi_t2(0) and Phi_t1(2) this->cook(2); // Convex combine explicit 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::euler13, _1, _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"); const int grainSize = mTracker.getGrainSize(); const LeafRange range = mTracker.leafs().leafRange(grainSize); if (grainSize>0) { tbb::parallel_for(range, *this); } else { (*this)(range); } mTracker.leafs().swapLeafBuffer(swapBuffer, grainSize==0); mTracker.endInterrupter(); } template template template inline void LevelSetTracker:: Normalizer:: eval(StencilT& stencil, const ValueType* phi, ValueType* result, Index n) const { typedef typename math::ISGradientNormSqrd GradientT; static const ValueType alpha = ValueType(Nominator)/ValueType(Denominator); static const ValueType beta = ValueType(1) - alpha; const ValueType normSqGradPhi = GradientT::result(stencil); const ValueType phi0 = stencil.getValue(); ValueType v = phi0 / ( math::Sqrt(math::Pow2(phi0) + normSqGradPhi) + math::Tolerance::value() ); v = phi0 - mDt * v * (math::Sqrt(normSqGradPhi) * mInvDx - 1.0f); result[n] = Nominator ? alpha * phi[n] + beta * v : v; } template template template inline void LevelSetTracker:: Normalizer:: euler(const LeafRange& range, Index phiBuffer, Index resultBuffer) { typedef typename LeafType::ValueOnCIter VoxelIterT; mTracker.checkInterrupter(); StencilT stencil(mTracker.grid()); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { const ValueType* phi = leafIter.buffer(phiBuffer).data(); ValueType* result = leafIter.buffer(resultBuffer).data(); if (mMask == NULL) { for (VoxelIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { stencil.moveTo(iter); this->eval(stencil, phi, result, iter.pos()); }//loop over active voxels in the leaf of the level set } else if (const MaskLeafT* mask = mMask->probeLeaf(leafIter->origin())) { const ValueType* phi0 = leafIter->buffer().data(); for (MaskIterT iter = mask->cbeginValueOn(); iter; ++iter) { const Index i = iter.pos(); stencil.moveTo(iter.getCoord(), phi0[i]); this->eval(stencil, phi, result, i); }//loop over active voxels in the leaf of the mask } }//loop over leafs of the level set } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVEL_SET_TRACKER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetFracture.h0000644000000000000000000003431012603226506015744 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 #include #include "Composite.h" // for csgIntersection() and csgDifference() #include "GridTransformer.h" // for resampleToMatch() #include #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); } } } tools::pruneInactive(grid.tree()); tools::signedFloodFill(segment->tree()); segments.push_back(segment); } return segments; } 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; }; 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(std::numeric_limits::max()) , mMax(-mMin) { } 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); } } // 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()); internal::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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Exceptions.h0000644000000000000000000000715112603226506013511 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/INSTALL0000644000000000000000000002733412603226302012247 0ustar rootrootInstalling OpenVDB ================== Requirements ------------ - GNU GCC (gcc.gnu.org), version 4.1 or later or Intel ICC (software.intel.com), version 11.1 or later - GNU gmake (www.gnu.org/software/make/), version 3.81 or later - Boost (www.boost.org), version 1.42.0 or later (Linux: yum install boost-devel; OS X: port install boost +python26) - libz (zlib.net) (Linux: yum install zlib-devel) - OpenEXR (www.openexr.com), for the 16-bit float Half class in half.h and for .exr file output in vdb_render - Intel Threading Building Blocks (threadingbuildingblocks.org), version 3.0 or later Other compilers or versions might work but have not been tested. Optional: - Doxygen 1.8.7 (www.stack.nl/~dimitri/doxygen/), for documentation - CppUnit (www.freedesktop.org/wiki/Software/cppunit), version 1.10 or later (Linux: yum install cppunit-devel) - Houdini HDK (ftp://ftp.sidefx.com/public/Houdini12.0), version 12.0.628 or later - Blosc compression library (www.blosc.org), version 1.5.0 or later (included in the Houdini HDK as of version 14.0) - Ghostscript (www.ghostscript.com), version 8.70 or later, for documentation in PDF format - pdfLaTeX (www.pdftex.org), version 1.21 or later, for documentation in PDF format - log4cplus (log4cplus.sourceforge.net), version 1.0 or later, for error logging - GLFW 2.7 (www.glfw.org), for the OpenVDB viewer - OpenGL 3.2 or later, for the OpenVDB viewer - Python 2.5, 2.6 or 2.7, for the Python module - NumPy (www.numpy.org), for the Python module - Epydoc (http://epydoc.sourceforge.net/), version 3.0 or later, for Python module documentation Other versions might work but have not been tested. Installation ------------ 1. Set values appropriate to your environment for the following variables at the top of the Makefile: INSTALL_DIR the directory into which to install libraries, executables and header files (e.g., /usr/local) BOOST_INCL_DIR the parent directory of the boost/ header directory (e.g., /usr/include) BOOST_LIB_DIR the directory containing libboost_iostreams, etc. BOOST_LIB linker flags for libboost_iostreams and libboost_system BOOST_THREAD_LIB linker flags for libboost_thread ILMBASE_INCL_DIR the parent directory of the OpenEXR/ header directory (which contains half.h) ILMBASE_LIB_DIR the directory containing libHalf.so and/or libHalf.a ILMBASE_LIB linker flags for libIlmThread, libIex and libImath HALF_LIB linker flag(s) for the Half library (e.g., -lHalf) EXR_INCL_DIR the parent directory of the OpenEXR/ header directory (Note: some OpenEXR headers incorrectly include other OpenEXR headers with, e.g., '#include ' instead of '#include "ImfName.h"'. When compiling with Clang, set EXR_INCL_DIR to the parent directory of the OpenEXR/ directory and ILMBASE_INCL_DIR to the OpenEXR/ directory itself to avoid errors.) EXR_LIB_DIR the directory containing libIlmImf EXR_LIB linker flags for libIlmImf TBB_INCL_DIR the parent directory of the tbb/ header directory TBB_LIB_DIR the directory containing libtbb TBB_LIB linker flag(s) for the TBB library (e.g., -ltbb) BLOSC_INCL_DIR the parent directory of the blosc.h header BLOSC_LIB_DIR the directory containing libblosc BLOSC_LIB linker flags for libblosc CONCURRENT_MALLOC_LIB_DIR a directory containing a scalable, concurrent malloc replacement library such as jemalloc or TBB malloc (leave blank if no such library is available, but be aware that using standard malloc in concurrent code incurs a significant performance penalty) CONCURRENT_MALLOC_LIB linker flag(s) for the malloc replacement library (e.g., -ltbbmalloc_proxy -ltbbmalloc) CPPUNIT_INCL_DIR the parent directory of the cppunit/ header directory (leave blank if CppUnit is not available) CPPUNIT_LIB_DIR the directory containing libcppunit.so or libcppunit.a CPPUNIT_LIB linker flag(s) for the cppunit library (e.g., -lcppunit) GLFW_INCL_DIR the directory containing glfw.h (leave blank if GLFW is not available; GLFW is needed only for the command-line viewer tool) GLFW_LIB_DIR the directory containing libglfw GLFW_LIB linker flags for the GLFW library (e.g., -lglfw) GLFW_MAJOR_VERSION the major version number of the GLFW library (header filenames changed between GLFW 2 and 3, so this must be specified explicitly) LOG4CPLUS_INCL_DIR the parent directory of the log4cplus/ header directory (leave blank if log4cplus is not available) LOG4CPLUS_LIB_DIR directory containing liblog4cplus.so or liblog4cplus.a LOG4CPLUS_LIB linker flags for the log4cplus library (e.g., -llog4cplus) PYTHON_VERSION the version of Python for which to build the OpenVDB module (e.g., 2.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. Also note that certain new features in OpenVDB 3 (see the CHANGES file for details) necessitated changes to the ABI of the Grid class, rendering it incompatible with earlier versions of the library, such as the ones in Houdini 12.5 and 13. Passing grids between native VDB nodes in a scene graph and nodes built against the new ABI will lead to crashes, so if you must use OpenVDB 3 with those versions of Houdini, set abi=2 to compile with the incompatible features disabled. To build the OpenVDB Python module, you will need local installations of Python, Boost.Python, and optionally NumPy. 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.3.1.0 the OpenVDB library openvdb/libopenvdb.so symlink to libopenvdb.so.3.1.0 openvdb/pyopenvdb.so the OpenVDB Python module (if Python and Boost.Python are available) openvdb/vdb_print command-line tool that prints info about OpenVDB .vdb files openvdb/vdb_render command-line tool that ray-traces OpenVDB volumes openvdb/vdb_test unit test runner for libopenvdb (if CppUnit is available) From the openvdb/ directory, type "make test" to run the unit tests and verify that the library is working correctly. (Alternatively, once the library has been installed (Step 5), run the unit test executable directly with "./vdb_test", or "./vdb_test -v" for more verbose output.) Type "make pytest" to run the Python module unit tests. 3. From the openvdb/ directory, type "make doc" (or "make -s doc") to generate HTML library documentation, then open the file openvdb/doc/html/index.html in a browser. Type "make pydoc" (or "make -s pydoc") to generate HTML Python module documentation, then open openvdb/doc/html/python/index.html in a browser. 4. Optionally (if OpenGL and GLFW are available), from the top-level openvdb/ directory, type "make vdb_view" (or "make -s vdb_view") to locally build the OpenVDB viewer tool. Then type "./vdb_view" for usage information. 5. From the openvdb/ directory, type "make install" (or "make -s install") to copy generated files into the directory tree rooted at $(INSTALL_DIR). This creates the following distribution: $(INSTALL_DIR)/ bin/ vdb_print vdb_render vdb_view include/ openvdb/ Exceptions.h ... openvdb.h tools/ tree/ ... version.h lib/ libopenvdb.so libopenvdb.so.3.1 libopenvdb.so.3.1.0 python/ include/ python$(PYTHON_VERSION)/ pyopenvdb.h lib/ python$(PYTHON_VERSION)/ pyopenvdb.so pyopenvdb.so.3.1 share/ doc/ openvdb/ html/ index.html ... python/ index.html ... EOF openvdb/PlatformConfig.h0000644000000000000000000000474412603226506014307 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Platform.h0000644000000000000000000001643312603226506013157 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// /// /// @file Platform.h #ifndef OPENVDB_PLATFORM_HAS_BEEN_INCLUDED #define OPENVDB_PLATFORM_HAS_BEEN_INCLUDED #include "PlatformConfig.h" #define PRAGMA(x) _Pragma(#x) /// Use OPENVDB_DEPRECATED to mark functions as deprecated. /// It should be placed right before the signature of the function, /// e.g., "OPENVDB_DEPRECATED void functionName();". #ifdef OPENVDB_DEPRECATED #undef OPENVDB_DEPRECATED #endif #ifdef _MSC_VER #define OPENVDB_DEPRECATED __declspec(deprecated) #else #define OPENVDB_DEPRECATED __attribute__ ((deprecated)) #endif /// Macro for determining if GCC version is >= than X.Y #if defined(__GNUC__) #define OPENVDB_CHECK_GCC(MAJOR, MINOR) \ (__GNUC__ > MAJOR || (__GNUC__ == MAJOR && __GNUC_MINOR__ >= MINOR)) #else #define OPENVDB_CHECK_GCC(MAJOR, MINOR) 0 #endif /// Macro for determining if there are sufficient C++0x/C++11 features #ifdef __INTEL_COMPILER #ifdef __INTEL_CXX11_MODE__ #define OPENVDB_HAS_CXX11 1 #endif #elif defined(__clang__) #ifndef _LIBCPP_VERSION #include #endif #ifdef _LIBCPP_VERSION #define OPENVDB_HAS_CXX11 1 #endif #elif defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus > 199711L) #define OPENVDB_HAS_CXX11 1 #elif defined(_MSC_VER) #if (_MSC_VER >= 1700) #define OPENVDB_HAS_CXX11 1 #endif #endif #if defined(__GNUC__) && !OPENVDB_CHECK_GCC(4, 4) // ICC uses GCC's standard library headers, so even if the ICC version // is recent enough for C++11, the GCC version might not be. #undef OPENVDB_HAS_CXX11 #endif /// For compilers that need templated function specializations to have /// storage qualifiers, we need to declare the specializations as static inline. /// Otherwise, we'll get linker errors about multiply defined symbols. #if defined(__GNUC__) && OPENVDB_CHECK_GCC(4, 4) #define OPENVDB_STATIC_SPECIALIZATION #else #define OPENVDB_STATIC_SPECIALIZATION static #endif /// Bracket code with OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN/_END, /// as in the following example, to inhibit ICC remarks about unreachable code: /// @code /// template /// void processNode(NodeType& node) /// { /// OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN /// if (NodeType::LEVEL == 0) return; // ignore leaf nodes /// int i = 0; /// ... /// OPENVDB_NO_UNREACHABLE_CODE_WARNING_END /// } /// @endcode /// In the above, NodeType::LEVEL == 0 is a compile-time constant expression, /// so for some template instantiations, the line below it is unreachable. #if defined(__INTEL_COMPILER) // Disable ICC remarks 111 ("statement is unreachable"), 128 ("loop is not reachable"), // 185 ("dynamic initialization in unreachable code"), and 280 ("selector expression // is constant"). #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN \ _Pragma("warning (push)") \ _Pragma("warning (disable:111)") \ _Pragma("warning (disable:128)") \ _Pragma("warning (disable:185)") \ _Pragma("warning (disable:280)") #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_END \ _Pragma("warning (pop)") #elif defined(__clang__) #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN \ PRAGMA(clang diagnostic push) \ PRAGMA(clang diagnostic ignored "-Wunreachable-code") #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_END \ PRAGMA(clang diagnostic pop) #else #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_END #endif /// 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/LICENSE0000644000000000000000000004052612603226302012221 0ustar rootrootMozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. openvdb/math/0000755000000000000000000000000012603226506012144 5ustar rootrootopenvdb/math/Mat4.h0000644000000000000000000012515612603226506013134 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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] = T(a); MyBase::mm[ 1] = T(b); MyBase::mm[ 2] = T(c); MyBase::mm[ 3] = T(d); MyBase::mm[ 4] = T(e); MyBase::mm[ 5] = T(f); MyBase::mm[ 6] = T(g); MyBase::mm[ 7] = T(h); MyBase::mm[ 8] = T(i); MyBase::mm[ 9] = T(j); MyBase::mm[10] = T(k); MyBase::mm[11] = T(l); MyBase::mm[12] = T(m); MyBase::mm[13] = T(n); MyBase::mm[14] = T(o); MyBase::mm[15] = T(p); } /// Construct matrix 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 = static_cast(p[0] * MyBase::mm[ 3] + p[1] * MyBase::mm[ 7] + p[2] * MyBase::mm[11] + MyBase::mm[15]); if ( !isExactlyEqual(w , 0.0) ) { return Vec3(static_cast((p[0] * MyBase::mm[ 0] + p[1] * MyBase::mm[ 4] + p[2] * MyBase::mm[ 8] + MyBase::mm[12]) / w), static_cast((p[0] * MyBase::mm[ 1] + p[1] * MyBase::mm[ 5] + p[2] * MyBase::mm[ 9] + MyBase::mm[13]) / w), static_cast((p[0] * MyBase::mm[ 2] + p[1] * MyBase::mm[ 6] + p[2] * MyBase::mm[10] + MyBase::mm[14]) / w)); } return Vec3(0, 0, 0); } /// Transform a Vec3 by pre-multiplication, doing homogenous division. template Vec3 pretransformH(const Vec3 &p) const { T0 w; // w = p * (*this).col(3); w = p[0] * MyBase::mm[12] + p[1] * MyBase::mm[13] + p[2] * MyBase::mm[14] + MyBase::mm[15]; if ( !isExactlyEqual(w , 0.0) ) { return Vec3(static_cast((p[0] * MyBase::mm[ 0] + p[1] * MyBase::mm[ 1] + p[2] * MyBase::mm[ 2] + MyBase::mm[ 3]) / w), static_cast((p[0] * MyBase::mm[ 4] + p[1] * MyBase::mm[ 5] + p[2] * MyBase::mm[ 6] + MyBase::mm[ 7]) / w), static_cast((p[0] * MyBase::mm[ 8] + p[1] * MyBase::mm[ 9] + p[2] * MyBase::mm[10] + MyBase::mm[11]) / w)); } return Vec3(0, 0, 0); } /// Transform a Vec3 by post-multiplication, without translation. template Vec3 transform3x3(const Vec3 &v) const { return Vec3( static_cast(v[0] * MyBase::mm[ 0] + v[1] * MyBase::mm[ 4] + v[2] * MyBase::mm[ 8]), static_cast(v[0] * MyBase::mm[ 1] + v[1] * MyBase::mm[ 5] + v[2] * MyBase::mm[ 9]), static_cast(v[0] * MyBase::mm[ 2] + v[1] * MyBase::mm[ 6] + v[2] * MyBase::mm[10])); } private: bool invert(Mat4 &inverse, T tolerance) const; T det2(const Mat4 &a, int i0, int i1, int j0, int j1) const { int i0row = i0 * 4; int i1row = i1 * 4; return a.mm[i0row+j0]*a.mm[i1row+j1] - a.mm[i0row+j1]*a.mm[i1row+j0]; } T det3(const Mat4 &a, int i0, int i1, int i2, int j0, int j1, int j2) const { int i0row = i0 * 4; return a.mm[i0row+j0]*det2(a, i1,i2, j1,j2) + a.mm[i0row+j1]*det2(a, i1,i2, j2,j0) + a.mm[i0row+j2]*det2(a, i1,i2, j0,j1); } 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Coord.h0000644000000000000000000004171312603226506013371 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 Coord min() { return Coord(Limits::min()); } /// @brief Return the largest possible coordinate static Coord max() { return Coord(Limits::max()); } /// @brief Return @a xyz rounded to the closest integer coordinates /// (cell centered conversion). template static Coord round(const Vec3& xyz) { return Coord(Int32(Round(xyz[0])), Int32(Round(xyz[1])), Int32(Round(xyz[2]))); } /// @brief Return the largest integer coordinates that are not greater /// than @a xyz (node centered conversion). template static Coord floor(const Vec3& xyz) { return Coord(Int32(Floor(xyz[0])), Int32(Floor(xyz[1])), Int32(Floor(xyz[2]))); } /// @brief Return the largest integer coordinates that are not greater /// than @a xyz+1 (node centered conversion). template static Coord ceil(const Vec3& xyz) { return Coord(Int32(Ceil(xyz[0])), Int32(Ceil(xyz[1])), Int32(Ceil(xyz[2]))); } Coord& reset(Int32 x, Int32 y, Int32 z) { mVec[0] = x; mVec[1] = y; mVec[2] = z; return *this; } Coord& reset(Int32 xyz) { return this->reset(xyz, xyz, xyz); } Coord& setX(Int32 x) { mVec[0] = x; return *this; } Coord& setY(Int32 y) { mVec[1] = y; return *this; } Coord& setZ(Int32 z) { mVec[2] = z; return *this; } Coord& offset(Int32 dx, Int32 dy, Int32 dz) { mVec[0]+=dx; mVec[1]+=dy; mVec[2]+=dz; return *this; } Coord& offset(Int32 n) { return this->offset(n, n, n); } Coord offsetBy(Int32 dx, Int32 dy, Int32 dz) const { return Coord(mVec[0] + dx, mVec[1] + dy, mVec[2] + dz); } Coord offsetBy(Int32 n) const { return offsetBy(n, n, n); } Coord& operator+=(const Coord& rhs) { mVec[0] += rhs[0]; mVec[1] += rhs[1]; mVec[2] += rhs[2]; return *this; } Coord& operator-=(const Coord& rhs) { mVec[0] -= rhs[0]; mVec[1] -= rhs[1]; mVec[2] -= rhs[2]; return *this; } Coord operator+(const Coord& rhs) const { return Coord(mVec[0] + rhs[0], mVec[1] + rhs[1], mVec[2] + rhs[2]); } Coord operator-(const Coord& rhs) const { return Coord(mVec[0] - rhs[0], mVec[1] - rhs[1], mVec[2] - rhs[2]); } Coord operator-() const { return Coord(-mVec[0], -mVec[1], -mVec[2]); } Coord operator>> (size_t n) const { return Coord(mVec[0]>>n, mVec[1]>>n, mVec[2]>>n); } Coord operator<< (size_t n) const { return Coord(mVec[0]<>=(size_t n) { mVec[0]>>=n; mVec[1]>>=n; mVec[2]>>=n; return *this; } Coord operator& (Int32 n) const { return Coord(mVec[0] & n, mVec[1] & n, mVec[2] & n); } Coord operator| (Int32 n) const { return Coord(mVec[0] | n, mVec[1] | n, mVec[2] | n); } Coord& operator&= (Int32 n) { mVec[0]&=n; mVec[1]&=n; mVec[2]&=n; return *this; } Coord& operator|= (Int32 n) { mVec[0]|=n; mVec[1]|=n; mVec[2]|=n; return *this; } Int32 x() const { return mVec[0]; } Int32 y() const { return mVec[1]; } Int32 z() const { return mVec[2]; } Int32 operator[](size_t i) const { assert(i < 3); return mVec[i]; } Int32& x() { return mVec[0]; } Int32& y() { return mVec[1]; } Int32& z() { return mVec[2]; } Int32& operator[](size_t i) { assert(i < 3); return mVec[i]; } const Int32* asPointer() const { return mVec; } Int32* asPointer() { 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); } /// Perform a component-wise minimum with the other Coord. void minComponent(const Coord& other) { mVec[0] = std::min(mVec[0], other.mVec[0]); mVec[1] = std::min(mVec[1], other.mVec[1]); mVec[2] = std::min(mVec[2], other.mVec[2]); } /// Perform a component-wise maximum with the other Coord. void maxComponent(const Coord& other) { mVec[0] = std::max(mVec[0], other.mVec[0]); mVec[1] = std::max(mVec[1], other.mVec[1]); mVec[2] = std::max(mVec[2], other.mVec[2]); } /// Return the component-wise minimum of the two Coords. static inline Coord minComponent(const Coord& lhs, const Coord& rhs) { return Coord(std::min(lhs.x(), rhs.x()), std::min(lhs.y(), rhs.y()), std::min(lhs.z(), rhs.z())); } /// Return the component-wise maximum of the two Coords. static inline Coord maxComponent(const Coord& lhs, const Coord& rhs) { return Coord(std::max(lhs.x(), rhs.x()), std::max(lhs.y(), rhs.y()), std::max(lhs.z(), rhs.z())); } /// Return true if any of the components of @a a are smaller than the /// corresponding components of @a b. static inline bool lessThan(const Coord& a, const Coord& b) { return (a[0] < b[0] || a[1] < b[1] || a[2] < b[2]); } /// @brief Return the index (0, 1 or 2) with the smallest value. size_t minIndex() const { return MinIndex(mVec); } /// @brief Return the index (0, 1 or 2) with the largest value. size_t maxIndex() const { return MaxIndex(mVec); } void read(std::istream& is) { is.read(reinterpret_cast(mVec), sizeof(mVec)); } void write(std::ostream& os) const { os.write(reinterpret_cast(mVec), sizeof(mVec)); } private: Int32 mVec[3]; }; // 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Tuple.h0000644000000000000000000001450412603226506013412 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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) { enum { COPY_END = (SIZE < src_size ? SIZE : src_size) }; for (int i = 0; i < COPY_END; ++i) { mm[i] = src[i]; } for (int i = COPY_END; i < SIZE; ++i) { mm[i] = 0; } } T operator[](int i) const { // we'd prefer to use size_t, but can't because gcc3.2 doesn't like // it - it conflicts with child class conversion operators to // pointer types. // assert(i >= 0 && i < SIZE); return mm[i]; } T& operator[](int i) { // see above for size_t vs int // assert(i >= 0 && i < SIZE); return mm[i]; } /// @name Compatibility /// These are mostly for backwards compability with functions that take /// old-style Vs (which are just arrays). //@{ /// Copies this tuple into an array of a compatible type template void toV(S *v) const { for (int i = 0; i < SIZE; ++i) { v[i] = mm[i]; } } /// Exposes the internal array. Be careful when using this function. value_type *asV() { return mm; } /// Exposes the internal array. Be careful when using this function. value_type const *asV() const { return mm; } //@} Compatibility /// @return string representation of Classname std::string str() const { std::ostringstream buffer; buffer << "["; // For each column for (unsigned j(0); j < SIZE; j++) { if (j) buffer << ", "; buffer << 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 (int i = 0; i < SIZE-1; ++i) { if (!isExactlyEqual(t0[i], t1[i])) return t0[i] < t1[i]; } return t0[SIZE-1] < t1[SIZE-1]; } /// @return true if t0 > t1, comparing components in order of significance. template bool operator>(const Tuple& t0, const Tuple& t1) { for (int i = 0; i < SIZE-1; ++i) { if (!isExactlyEqual(t0[i], t1[i])) return t0[i] > t1[i]; } return t0[SIZE-1] > t1[SIZE-1]; } //////////////////////////////////////// /// @return the absolute value of the given Tuple. template Tuple Abs(const Tuple& t) { Tuple result; for (int i = 0; i < SIZE; ++i) result[i] = math::Abs(t[i]); return result; } //////////////////////////////////////// /// 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Maps.cc0000644000000000000000000002141012603226506013351 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 { return mat4d.inverse(); } catch (ArithmeticError& ) { // Mat4 code couldn't invert. } } const Mat3d mat3 = mat4d.getMat3(); const Mat3d mat3T = mat3.transpose(); const Vec3d trans = mat4d.getTranslation(); // absolute tolerance used for the symmetric test. const double tol = 1.e-6; // only create the pseudoInverse for symmetric bool symmetric = true; for (int i = 0; i < 3; ++i ) { for (int j = 0; j < 3; ++j ) { if (!isApproxEqual(mat3[i][j], mat3T[i][j], tol)) { symmetric = false; } } } if (!symmetric) { // not symmetric, so just zero out the mat3 inverse and reverse the translation Mat4d result = Mat4d::zero(); result.setTranslation(-trans); result[3][3] = 1.f; return result; } else { // compute the pseudo inverse Mat3d eigenVectors; Vec3d eigenValues; diagonalizeSymmetricMatrix(mat3, eigenVectors, eigenValues); Mat3d d = Mat3d::identity(); for (int i = 0; i < 3; ++i ) { if (std::abs(eigenValues[i]) < 10.0 * math::Tolerance::value()) { d[i][i] = 0.f; } else { d[i][i] = 1.f/eigenValues[i]; } } // assemble the pseudo inverse Mat3d pseudoInv = eigenVectors * d * eigenVectors.transpose(); Vec3d invTrans = -trans * pseudoInv; Mat4d result = Mat4d::identity(); result.setMat3(pseudoInv); result.setTranslation(invTrans); return result; } } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Proximity.h0000644000000000000000000000677512603226506014340 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Transform.cc0000644000000000000000000004173612603226506014441 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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); } //////////////////////////////////////// BBoxd Transform::indexToWorld(const CoordBBox& indexBBox) const { return this->indexToWorld(BBoxd(indexBBox.min().asVec3d(), indexBBox.max().asVec3d())); } BBoxd Transform::indexToWorld(const BBoxd& indexBBox) const { const Vec3d &imin = indexBBox.min(), &imax = indexBBox.max(); Vec3d corners[8]; corners[0] = imin; corners[1] = Vec3d(imax(0), imin(1), imin(2)); corners[2] = Vec3d(imax(0), imax(1), imin(2)); corners[3] = Vec3d(imin(0), imax(1), imin(2)); corners[4] = Vec3d(imin(0), imin(1), imax(2)); corners[5] = Vec3d(imax(0), imin(1), imax(2)); corners[6] = imax; corners[7] = Vec3d(imin(0), imax(1), imax(2)); BBoxd worldBBox; Vec3d &wmin = worldBBox.min(), &wmax = worldBBox.max(); wmin = wmax = this->indexToWorld(corners[0]); for (int i = 1; i < 8; ++i) { Vec3d image = this->indexToWorld(corners[i]); wmin = minComponent(wmin, image); wmax = maxComponent(wmax, image); } return worldBBox; } BBoxd Transform::worldToIndex(const BBoxd& worldBBox) const { Vec3d indexMin, indexMax; calculateBounds(*this, worldBBox.min(), worldBBox.max(), indexMin, indexMax); return BBoxd(indexMin, indexMax); } CoordBBox Transform::worldToIndexCellCentered(const BBoxd& worldBBox) const { Vec3d indexMin, indexMax; calculateBounds(*this, worldBBox.min(), worldBBox.max(), indexMin, indexMax); return CoordBBox(Coord::round(indexMin), Coord::round(indexMax)); } CoordBBox Transform::worldToIndexNodeCentered(const BBoxd& worldBBox) const { Vec3d indexMin, indexMax; calculateBounds(*this, worldBBox.min(), worldBBox.max(), indexMin, indexMax); return CoordBBox(Coord::floor(indexMin), Coord::floor(indexMax)); } //////////////////////////////////////// // Utility methods void calculateBounds(const Transform& t, const Vec3d& minWS, const Vec3d& maxWS, Vec3d& minIS, Vec3d& maxIS) { /// the pre-image of the 8 corners of the box Vec3d corners[8]; corners[0] = minWS; corners[1] = Vec3d(maxWS(0), minWS(1), minWS(2)); corners[2] = Vec3d(maxWS(0), maxWS(1), minWS(2)); corners[3] = Vec3d(minWS(0), maxWS(1), minWS(2)); corners[4] = Vec3d(minWS(0), minWS(1), maxWS(2)); corners[5] = Vec3d(maxWS(0), minWS(1), maxWS(2)); corners[6] = maxWS; corners[7] = Vec3d(minWS(0), maxWS(1), maxWS(2)); Vec3d pre_image; minIS = t.worldToIndex(corners[0]); maxIS = minIS; for (int i = 1; i < 8; ++i) { pre_image = t.worldToIndex(corners[i]); for (int j = 0; j < 3; ++j) { minIS(j) = std::min(minIS(j), pre_image(j)); maxIS(j) = std::max(maxIS(j), pre_image(j)); } } } //////////////////////////////////////// bool Transform::operator==(const Transform& other) const { if (!this->voxelSize().eq(other.voxelSize())) return false; if (this->mapType() == other.mapType()) { return this->baseMap()->isEqual(*other.baseMap()); } if (this->isLinear() && other.isLinear()) { // promote both maps to mat4 form and compare return ( *(this->baseMap()->getAffineMap()) == *(other.baseMap()->getAffineMap()) ); } return this->baseMap()->isEqual(*other.baseMap()); } //////////////////////////////////////// void Transform::print(std::ostream& os, const std::string& indent) const { struct Local { // Print a Vec4d more compactly than Vec4d::str() does. static std::string rowAsString(const Vec4d& row) { std::ostringstream ostr; ostr << "[" << std::setprecision(3) << row[0] << ", " << row[1] << ", " << row[2] << ", " << row[3] << "] "; return ostr.str(); } }; // Write to a string stream so that I/O manipulators don't affect the output stream. std::ostringstream ostr; { Vec3d dim = this->voxelSize(); if (dim.eq(Vec3d(dim[0]))) { ostr << indent << std::left << "voxel size: " << std::setprecision(3) << dim[0]; } else { ostr << indent << std::left << "voxel dimensions: [" << std::setprecision(3) << dim[0] << ", " << dim[1] << ", " << dim[2] << "]"; } ostr << "\n"; } if (this->isLinear()) { openvdb::Mat4R v2w = this->baseMap()->getAffineMap()->getMat4(); ostr << indent << std::left << "index to world:\n"; for (int row = 0; row < 4; ++row) { ostr << indent << " " << std::left << Local::rowAsString(v2w[row]) << "\n"; } } else if (this->mapType() == NonlinearFrustumMap::mapType()) { const NonlinearFrustumMap& frustum = static_cast(*this->baseMap()); const openvdb::Mat4R linear = this->baseMap()->getAffineMap()->getMat4(); std::vector linearRow; size_t w = 0; for (int row = 0; row < 4; ++row) { std::string str = Local::rowAsString(linear[row]); w = std::max(w, str.size()); linearRow.push_back(str); } w = std::max(w, 30); const int iw = int(w); // Print rows of the linear component matrix side-by-side with frustum parameters. ostr << indent << std::left << std::setw(iw) << "linear:" << " frustum:\n"; ostr << indent << " " << std::left << std::setw(iw) << linearRow[0] << " taper: " << frustum.getTaper() << "\n"; ostr << indent << " " << std::left << std::setw(iw) << linearRow[1] << " depth: " << frustum.getDepth() << "\n"; std::ostringstream ostmp; ostmp << indent << " " << std::left << std::setw(iw) << linearRow[2] << " bounds: " << frustum.getBBox(); if (ostmp.str().size() < 79) { ostr << ostmp.str() << "\n"; ostr << indent << " " << std::left << std::setw(iw) << linearRow[3] << "\n"; } else { // If the frustum bounding box doesn't fit on one line, split it into two lines. ostr << indent << " " << std::left << std::setw(iw) << linearRow[2] << " bounds: " << frustum.getBBox().min() << " ->\n"; ostr << indent << " " << std::left << std::setw(iw) << linearRow[3] << " " << frustum.getBBox().max() << "\n"; } } else { /// @todo Handle other map types. } os << ostr.str(); } //////////////////////////////////////// std::ostream& operator<<(std::ostream& os, const Transform& t) { os << "Transform type: " << t.baseMap()->type() << std::endl; os << t.baseMap()->str() << std::endl; return os; } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Transform.h0000644000000000000000000002541112603226506014273 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_TRANSFORM_HAS_BEEN_INCLUDED #define OPENVDB_MATH_TRANSFORM_HAS_BEEN_INCLUDED #include "Maps.h" #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { // Forward declaration class Transform; // Utility methods /// @brief Calculate an axis-aligned bounding box in index space from an /// axis-aligned bounding box in world space. /// @see Transform::worldToIndex(const BBoxd&) const OPENVDB_API void calculateBounds(const Transform& t, const Vec3d& minWS, const Vec3d& maxWS, Vec3d& minIS, Vec3d& maxIS); /// @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));} //@} //@{ /// @brief Apply this transformation to the given index-space bounding box. /// @return an axis-aligned world-space bounding box BBoxd indexToWorld(const CoordBBox&) const; BBoxd indexToWorld(const BBoxd&) const; //@} //@{ /// @brief Apply the inverse of this transformation to the given world-space bounding box. /// @return an axis-aligned index-space bounding box BBoxd worldToIndex(const BBoxd&) const; CoordBBox worldToIndexCellCentered(const BBoxd&) const; CoordBBox worldToIndexNodeCentered(const BBoxd&) const; //@} //@{ /// Return a base pointer to the transformation map. MapBase::ConstPtr baseMap() const { return mMap; } MapBase::Ptr baseMap() { return mMap; } //@} //@{ /// @brief Return the result of downcasting the base map pointer to a /// @c MapType pointer, or return a null pointer if the types are incompatible. template typename MapType::Ptr map(); template typename MapType::ConstPtr map() const; template typename MapType::ConstPtr constMap() const; //@} /// Unserialize this transform from the given stream. void read(std::istream&); /// Serialize this transform to the given stream. void write(std::ostream&) const; /// @brief Print a description of this transform. /// @param os a stream to which to write textual information /// @param indent a string with which to prefix each line of text void print(std::ostream& os = std::cout, const std::string& indent = "") const; bool operator==(const Transform& other) const; inline bool operator!=(const Transform& other) const { return !(*this == other); } private: MapBase::Ptr mMap; }; // class Transform OPENVDB_API std::ostream& operator<<(std::ostream&, const Transform&); //////////////////////////////////////// template inline typename MapType::Ptr Transform::map() { if (mMap->type() == MapType::mapType()) { return 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Maps.h0000644000000000000000000027643612603226506013237 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 // for io::getFormatVersion() #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. /// First it will take a box of size (Lx X Ly X Lz) defined by a member data bounding box /// and map it into a frustum with near plane (1 X Ly/Lx) and prescribed depth /// Then this frustum is transformed by an internal second map: most often a uniform scale, /// but other effects can be achieved by accumulating translation, shear and rotation: these /// are all applied to the second map class OPENVDB_API NonlinearFrustumMap: public MapBase { public: 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Mat.h0000644000000000000000000007155712603226506013055 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 = static_cast(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 = T(2.0 / qdot); } T x = s*q.x(); T y = s*q.y(); T z = s*q.z(); T wx = x*q.w(); T wy = y*q.w(); T wz = z*q.w(); T xx = x*q.x(); T xy = y*q.x(); T xz = z*q.x(); T yy = y*q.y(); T yz = z*q.y(); T zz = z*q.z(); MatType r; r[0][0]=T(1) - (yy+zz); r[0][1]=xy + wz; r[0][2]=xz - wy; r[1][0]=xy - wz; r[1][1]=T(1) - (xx+zz); r[1][2]=yz + wx; r[2][0]=xz + wy; r[2][1]=yz - wx; r[2][2]=T(1) - (xx+yy); if(MatType::numColumns() == 4) padMat4(r); return r; } /// @brief Return a matrix for rotation by @a angle radians about the given @a axis. /// @param axis The axis (one of X, Y, Z) to rotate about. /// @param angle The rotation angle, in radians. template MatType rotation(Axis axis, typename MatType::value_type angle) { 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 = static_cast(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 = ValueType(M_PI_2); phi = ValueType(0.5 * atan2(mat[1][2], mat[1][1])); psi = phi; } else if (isApproxEqual(mat[2][0], ValueType(-1.0), eps)) { theta = ValueType(-M_PI_2); phi = ValueType(0.5 * atan2(mat[1][2], mat[1][1])); psi = -phi; } else { psi = ValueType(atan2(-mat[1][0],mat[0][0])); phi = ValueType(atan2(-mat[2][1],mat[2][2])); theta = ValueType(atan2(mat[2][0], sqrt( mat[2][1]*mat[2][1] + mat[2][2]*mat[2][2]))); } return V(phi, theta, psi); case ZXY_ROTATION: if (isApproxEqual(mat[1][2], ValueType(1.0), eps)) { theta = ValueType(M_PI_2); phi = ValueType(0.5 * atan2(mat[0][1], mat[0][0])); psi = phi; } else if (isApproxEqual(mat[1][2], ValueType(-1.0), eps)) { theta = ValueType(-M_PI/2); phi = ValueType(0.5 * atan2(mat[0][1],mat[2][1])); psi = -phi; } else { psi = ValueType(atan2(-mat[0][2], mat[2][2])); phi = ValueType(atan2(-mat[1][0], mat[1][1])); theta = ValueType(atan2(mat[1][2], sqrt(mat[0][2] * mat[0][2] + mat[2][2] * mat[2][2]))); } return V(theta, psi, phi); case YZX_ROTATION: if (isApproxEqual(mat[0][1], ValueType(1.0), eps)) { theta = ValueType(M_PI_2); phi = ValueType(0.5 * atan2(mat[2][0], mat[2][2])); psi = phi; } else if (isApproxEqual(mat[0][1], ValueType(-1.0), eps)) { theta = ValueType(-M_PI/2); phi = ValueType(0.5 * atan2(mat[2][0], mat[1][0])); psi = -phi; } else { psi = ValueType(atan2(-mat[2][1], mat[1][1])); phi = ValueType(atan2(-mat[0][2], mat[0][0])); theta = ValueType(atan2(mat[0][1], sqrt(mat[0][0] * mat[0][0] + mat[0][2] * mat[0][2]))); } return V(psi, phi, theta); case XZX_ROTATION: if (isApproxEqual(mat[0][0], ValueType(1.0), eps)) { theta = ValueType(0.0); phi = ValueType(0.5 * atan2(mat[1][2], mat[1][1])); psi = phi; } else if (isApproxEqual(mat[0][0], ValueType(-1.0), eps)) { theta = ValueType(M_PI); psi = ValueType(0.5 * atan2(mat[2][1], -mat[1][1])); phi = - psi; } else { psi = ValueType(atan2(mat[2][0], -mat[1][0])); phi = ValueType(atan2(mat[0][2], mat[0][1])); theta = ValueType(atan2(sqrt(mat[0][1] * mat[0][1] + mat[0][2] * mat[0][2]), mat[0][0])); } return V(phi, psi, theta); case ZXZ_ROTATION: if (isApproxEqual(mat[2][2], ValueType(1.0), eps)) { theta = ValueType(0.0); phi = ValueType(0.5 * atan2(mat[0][1], mat[0][0])); psi = phi; } else if (isApproxEqual(mat[2][2], ValueType(-1.0), eps)) { theta = ValueType(M_PI); phi = ValueType(0.5 * atan2(mat[0][1], mat[0][0])); psi = -phi; } else { psi = ValueType(atan2(mat[0][2], mat[1][2])); phi = ValueType(atan2(mat[2][0], -mat[2][1])); theta = ValueType(atan2(sqrt(mat[0][2] * mat[0][2] + mat[1][2] * mat[1][2]), mat[2][2])); } return V(theta, psi, phi); case YXZ_ROTATION: if (isApproxEqual(mat[2][1], ValueType(1.0), eps)) { theta = ValueType(-M_PI_2); phi = ValueType(0.5 * atan2(-mat[1][0], mat[0][0])); psi = phi; } else if (isApproxEqual(mat[2][1], ValueType(-1.0), eps)) { theta = ValueType(M_PI_2); phi = ValueType(0.5 * atan2(mat[1][0], mat[0][0])); psi = -phi; } else { psi = ValueType(atan2(mat[0][1], mat[1][1])); phi = ValueType(atan2(mat[2][0], mat[2][2])); theta = ValueType(atan2(-mat[2][1], sqrt(mat[0][1] * mat[0][1] + mat[1][1] * mat[1][1]))); } return V(theta, phi, psi); case ZYX_ROTATION: if (isApproxEqual(mat[0][2], ValueType(1.0), eps)) { theta = ValueType(-M_PI_2); phi = ValueType(0.5 * atan2(-mat[1][0], mat[1][1])); psi = phi; } else if (isApproxEqual(mat[0][2], ValueType(-1.0), eps)) { theta = ValueType(M_PI_2); phi = ValueType(0.5 * atan2(mat[2][1], mat[2][0])); psi = -phi; } else { psi = ValueType(atan2(mat[1][2], mat[2][2])); phi = ValueType(atan2(mat[0][1], mat[0][0])); theta = ValueType(atan2(-mat[0][2], sqrt(mat[0][1] * mat[0][1] + mat[0][0] * mat[0][0]))); } return V(psi, theta, phi); case XZY_ROTATION: if (isApproxEqual(mat[1][0], ValueType(-1.0), eps)) { theta = ValueType(M_PI_2); psi = ValueType(0.5 * atan2(mat[2][1], mat[2][2])); phi = -psi; } else if (isApproxEqual(mat[1][0], ValueType(1.0), eps)) { theta = ValueType(-M_PI_2); psi = ValueType(0.5 * atan2(- mat[2][1], mat[2][2])); phi = psi; } else { psi = ValueType(atan2(mat[2][0], mat[0][0])); phi = ValueType(atan2(mat[1][2], mat[1][1])); theta = ValueType(atan2(- mat[1][0], sqrt(mat[1][1] * mat[1][1] + mat[1][2] * mat[1][2]))); } return V(phi, psi, theta); } OPENVDB_THROW(NotImplementedError, "Euler extraction sequence not implemented"); } /// @brief Return a rotation matrix that maps @a v1 onto @a v2 /// about the cross product of @a v1 and @a v2. template MatType rotation( const Vec3& _v1, const Vec3& _v2, typename MatType::value_type eps=1.0e-8) { 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Vec3.h0000644000000000000000000004747512603226506013136 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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]; } /// @brief Construct a Vec3 from a 3-Tuple with a possibly different value type. /// @details Type conversion warnings are suppressed. template explicit Vec3(const Tuple<3, Source> &v) { this->mm[0] = static_cast(v[0]); this->mm[1] = static_cast(v[1]); this->mm[2] = static_cast(v[2]); } /// @brief Construct a Vec3 from another Vec3 with a possibly different value type. /// @details Type conversion warnings are suppressed. template Vec3(const Vec3& v) { this->mm[0] = static_cast(v[0]); this->mm[1] = static_cast(v[1]); this->mm[2] = static_cast(v[2]); } /// Reference to the component, e.g. v.x() = 4.5f; T& x() { return this->mm[0]; } T& y() { return this->mm[1]; } T& z() { return this->mm[2]; } /// Get the component, e.g. float f = v.y(); T x() const { return this->mm[0]; } T y() const { return this->mm[1]; } T z() const { return this->mm[2]; } T* asPointer() { return this->mm; } const T* asPointer() const { return this->mm; } /// Alternative indexed reference to the elements T& operator()(int i) { return this->mm[i]; } /// Alternative indexed constant reference to the elements, T operator()(int i) const { return this->mm[i]; } /// "this" vector gets initialized to [x, y, z], /// calling v.init(); has same effect as calling v = Vec3::zero(); const Vec3& init(T x=0, T y=0, T z=0) { this->mm[0] = x; this->mm[1] = y; this->mm[2] = z; return *this; } /// Set "this" vector to zero const Vec3& setZero() { this->mm[0] = 0; this->mm[1] = 0; this->mm[2] = 0; return *this; } /// @brief Assignment operator /// @details Type conversion warnings are not suppressed. template const Vec3& operator=(const Vec3 &v) { // note: don't static_cast because that suppresses warnings this->mm[0] = v[0]; this->mm[1] = v[1]; this->mm[2] = v[2]; return *this; } /// Test if "this" vector is equivalent to vector v with tolerance of eps bool eq(const Vec3 &v, T eps = static_cast(1.0e-7)) const { return isRelOrApproxEqual(this->mm[0], v.mm[0], eps, eps) && isRelOrApproxEqual(this->mm[1], v.mm[1], eps, eps) && isRelOrApproxEqual(this->mm[2], v.mm[2], eps, eps); } /// Negation operator, for e.g. v1 = -v2; Vec3 operator-() const { return Vec3(-this->mm[0], -this->mm[1], -this->mm[2]); } /// this = v1 + v2 /// "this", v1 and v2 need not be distinct objects, e.g. v.add(v1,v); template const Vec3& add(const Vec3 &v1, const Vec3 &v2) { this->mm[0] = v1[0] + v2[0]; this->mm[1] = v1[1] + v2[1]; this->mm[2] = v1[2] + v2[2]; return *this; } /// this = v1 - v2 /// "this", v1 and v2 need not be distinct objects, e.g. v.sub(v1,v); template const Vec3& sub(const Vec3 &v1, const Vec3 &v2) { this->mm[0] = v1[0] - v2[0]; this->mm[1] = v1[1] - v2[1]; this->mm[2] = v1[2] - v2[2]; return *this; } /// this = scalar*v, v need not be a distinct object from "this", /// e.g. v.scale(1.5,v1); template const Vec3& scale(T0 scale, const Vec3 &v) { this->mm[0] = scale * v[0]; this->mm[1] = scale * v[1]; this->mm[2] = scale * v[2]; return *this; } template const Vec3 &div(T0 scale, const Vec3 &v) { this->mm[0] = v[0] / scale; this->mm[1] = v[1] / scale; this->mm[2] = v[2] / scale; return *this; } /// Dot product T dot(const Vec3 &v) const { return this->mm[0]*v.mm[0] + this->mm[1]*v.mm[1] + this->mm[2]*v.mm[2]; } /// Length of the vector T length() const { return static_cast(sqrt(double( this->mm[0]*this->mm[0] + this->mm[1]*this->mm[1] + this->mm[2]*this->mm[2]))); } /// Squared length of the vector, much faster than length() as it /// does not involve square root T lengthSqr() const { return this->mm[0]*this->mm[0] + this->mm[1]*this->mm[1] + this->mm[2]*this->mm[2]; } /// Return the cross product of "this" vector and v; Vec3 cross(const Vec3 &v) const { return Vec3(this->mm[1]*v.mm[2] - this->mm[2]*v.mm[1], this->mm[2]*v.mm[0] - this->mm[0]*v.mm[2], this->mm[0]*v.mm[1] - this->mm[1]*v.mm[0]); } /// this = v1 cross v2, v1 and v2 must be distinct objects than "this" const Vec3& cross(const Vec3 &v1, const Vec3 &v2) { // assert(this!=&v1); // assert(this!=&v2); this->mm[0] = v1.mm[1]*v2.mm[2] - v1.mm[2]*v2.mm[1]; this->mm[1] = v1.mm[2]*v2.mm[0] - v1.mm[0]*v2.mm[2]; this->mm[2] = v1.mm[0]*v2.mm[1] - v1.mm[1]*v2.mm[0]; return *this; } /// Returns v, where \f$v_i *= scalar\f$ for \f$i \in [0, 2]\f$ template const Vec3 &operator*=(S scalar) { this->mm[0] = static_cast(this->mm[0] * scalar); this->mm[1] = static_cast(this->mm[1] * scalar); this->mm[2] = static_cast(this->mm[2] * scalar); return *this; } /// 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] = static_cast(this->mm[0] + scalar); this->mm[1] = static_cast(this->mm[1] + scalar); this->mm[2] = static_cast(this->mm[2] + scalar); return *this; } /// 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 = static_cast(1.0e-7)) const { T l = onto.length(); if (isApproxEqual(l, T(0), eps)) return 0; return dot(onto)*(T(1)/l); } /// Return the projection of v onto the vector, onto need not be unit /// e.g. Vec3d a = vprojection(n); Vec3 projection(const Vec3 &onto, T eps = static_cast(1.0e-7)) const { T l = onto.lengthSqr(); if (isApproxEqual(l, T(0), eps)) return Vec3::zero(); return onto*(dot(onto)*(T(1)/l)); } /// Return an arbitrary unit vector perpendicular to v /// Vector this must be a unit vector /// e.g. v = v.normalize(); Vec3d n = v.getArbPerpendicular(); Vec3 getArbPerpendicular() const { Vec3 u; T l; if ( fabs(this->mm[0]) >= fabs(this->mm[1]) ) { // v.x or v.z is the largest magnitude component, swap them l = this->mm[0]*this->mm[0] + this->mm[2]*this->mm[2]; l = static_cast(T(1)/sqrt(double(l))); u.mm[0] = -this->mm[2]*l; u.mm[1] = (T)0.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()); } template inline bool isFinite(const Vec3& v) { return isFinite(v[0]) && isFinite(v[1]) && isFinite(v[2]); } template inline Vec3 Abs(const Vec3& v) { return Vec3(Abs(v[0]), Abs(v[1]), Abs(v[2])); } /// Orthonormalize vectors v1, v2 and v3 and store back the resulting /// basis e.g. Vec3d::orthonormalize(v1,v2,v3); template inline void orthonormalize(Vec3 &v1, Vec3 &v2, Vec3 &v3) { // If the input vectors are v0, v1, and v2, then the Gram-Schmidt // orthonormalization produces vectors u0, u1, and u2 as follows, // // u0 = v0/|v0| // u1 = (v1-(u0*v1)u0)/|v1-(u0*v1)u0| // u2 = (v2-(u0*v2)u0-(u1*v2)u1)/|v2-(u0*v2)u0-(u1*v2)u1| // // where |A| indicates length of vector A and A*B indicates dot // product of vectors A and B. // compute u0 v1.normalize(); // compute u1 T d0 = v1.dot(v2); v2 -= v1*d0; v2.normalize(); // compute u2 T d1 = v2.dot(v3); d0 = v1.dot(v3); v3 -= v1*d0 + v2*d1; v3.normalize(); } /// @remark We are switching to a more explicit name because the semantics /// are different from std::min/max. In that case, the function returns a /// reference to one of the objects based on a comparator. Here, we must /// fabricate a new object which might not match either of the inputs. /// Return component-wise minimum of the two vectors. template inline Vec3 minComponent(const Vec3 &v1, const Vec3 &v2) { return Vec3( std::min(v1.x(), v2.x()), std::min(v1.y(), v2.y()), std::min(v1.z(), v2.z())); } /// Return component-wise maximum of the two vectors. template inline Vec3 maxComponent(const Vec3 &v1, const Vec3 &v2) { return Vec3( std::max(v1.x(), v2.x()), std::max(v1.y(), v2.y()), std::max(v1.z(), v2.z())); } /// @brief Return a vector with the exponent applied to each of /// the components of the input vector. template inline Vec3 Exp(Vec3 v) { return v.exp(); } 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/QuantizedUnitVec.cc0000644000000000000000000000616212603226506015722 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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]; namespace { // 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 = uint16_t((b & MASK_XSLOT) >> 7); ybits = b & MASK_YSLOT; if ((xbits + ybits) > 126) { xbits = uint16_t(127 - xbits); ybits = uint16_t(127 - ybits); } x = double(xbits); y = double(ybits); z = double(126 - xbits - ybits); w = 1.0 / std::sqrt(x*x + y*y + z*z); sNormalizationWeights[b] = float(w); } OPENVDB_FINISH_THREADSAFE_STATIC_WRITE } } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/BBox.h0000644000000000000000000003421012603226506013147 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 (int i = 0; i < 3; ++i) { mMin[i] = std::min(tMin[i], tMax[i]); mMax[i] = std::max(tMin[i], tMax[i]); } } template inline bool BBox::isSorted() const { if (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 (int i = 0; i < 3; ++i) { mMin[i] -= dx; mMax[i] += dx; } } template inline void BBox::expand(const Vec3T& xyz) { for (int i = 0; i < 3; ++i) { mMin[i] = std::min(mMin[i], xyz[i]); mMax[i] = std::max(mMax[i], xyz[i]); } } template inline void BBox::expand(const BBox& b) { for (int i = 0; i < 3; ++i) { mMin[i] = std::min(mMin[i], b.min()[i]); mMax[i] = std::max(mMax[i], b.max()[i]); } } template inline void BBox::expand(const Vec3T& xyzMin, const ElementType& length) { const ElementType size = boost::is_integral::value ? length-1 : length; for (int i = 0; i < 3; ++i) { mMin[i] = std::min(mMin[i], xyzMin[i]); mMax[i] = std::max(mMax[i], xyzMin[i] + size); } } template inline void BBox::translate(const Vec3T& dx) { mMin += dx; mMax += dx; } template template inline BBox BBox::applyMap(const MapType& map) const { 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Operators.h0000644000000000000000000024031212603226506014275 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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) { struct GetValue { const Accessor& acc; GetValue(const Accessor& acc_): acc(acc_) {} // Return the grid value at ijk converted to simd::Float4::value_type (= float). inline simd::Float4::value_type operator()(const Coord& ijk_) { return static_cast(acc.getValue(ijk_)); } } valueAt(grid); // SSE optimized const simd::Float4 v1(valueAt(ijk.offsetBy(-2, 0, 0)) - valueAt(ijk.offsetBy(-3, 0, 0)), valueAt(ijk.offsetBy( 0,-2, 0)) - valueAt(ijk.offsetBy( 0,-3, 0)), valueAt(ijk.offsetBy( 0, 0,-2)) - valueAt(ijk.offsetBy( 0, 0,-3)), 0), v2(valueAt(ijk.offsetBy(-1, 0, 0)) - valueAt(ijk.offsetBy(-2, 0, 0)), valueAt(ijk.offsetBy( 0,-1, 0)) - valueAt(ijk.offsetBy( 0,-2, 0)), valueAt(ijk.offsetBy( 0, 0,-1)) - valueAt(ijk.offsetBy( 0, 0,-2)), 0), v3(valueAt(ijk ) - valueAt(ijk.offsetBy(-1, 0, 0)), valueAt(ijk ) - valueAt(ijk.offsetBy( 0,-1, 0)), valueAt(ijk ) - valueAt(ijk.offsetBy( 0, 0,-1)), 0), v4(valueAt(ijk.offsetBy( 1, 0, 0)) - valueAt(ijk ), valueAt(ijk.offsetBy( 0, 1, 0)) - valueAt(ijk ), valueAt(ijk.offsetBy( 0, 0, 1)) - valueAt(ijk ), 0), v5(valueAt(ijk.offsetBy( 2, 0, 0)) - valueAt(ijk.offsetBy( 1, 0, 0)), valueAt(ijk.offsetBy( 0, 2, 0)) - valueAt(ijk.offsetBy( 0, 1, 0)), valueAt(ijk.offsetBy( 0, 0, 2)) - valueAt(ijk.offsetBy( 0, 0, 1)), 0), v6(valueAt(ijk.offsetBy( 3, 0, 0)) - valueAt(ijk.offsetBy( 2, 0, 0)), valueAt(ijk.offsetBy( 0, 3, 0)) - valueAt(ijk.offsetBy( 0, 2, 0)), valueAt(ijk.offsetBy( 0, 0, 3)) - valueAt(ijk.offsetBy( 0, 0, 2)), 0), down = math::WENO5(v1, v2, v3, v4, v5), up = math::WENO5(v6, v5, v4, v3, v2); return math::GudonovsNormSqrd(grid.getValue(ijk)>0, down, up); } // stencil access version template static typename StencilT::ValueType result(const StencilT& s) { typedef simd::Float4::value_type F4Val; // SSE optimized const simd::Float4 v1(F4Val(s.template getValue<-2, 0, 0>()) - F4Val(s.template getValue<-3, 0, 0>()), F4Val(s.template getValue< 0,-2, 0>()) - F4Val(s.template getValue< 0,-3, 0>()), F4Val(s.template getValue< 0, 0,-2>()) - F4Val(s.template getValue< 0, 0,-3>()), 0), v2(F4Val(s.template getValue<-1, 0, 0>()) - F4Val(s.template getValue<-2, 0, 0>()), F4Val(s.template getValue< 0,-1, 0>()) - F4Val(s.template getValue< 0,-2, 0>()), F4Val(s.template getValue< 0, 0,-1>()) - F4Val(s.template getValue< 0, 0,-2>()), 0), v3(F4Val(s.template getValue< 0, 0, 0>()) - F4Val(s.template getValue<-1, 0, 0>()), F4Val(s.template getValue< 0, 0, 0>()) - F4Val(s.template getValue< 0,-1, 0>()), F4Val(s.template getValue< 0, 0, 0>()) - F4Val(s.template getValue< 0, 0,-1>()), 0), v4(F4Val(s.template getValue< 1, 0, 0>()) - F4Val(s.template getValue< 0, 0, 0>()), F4Val(s.template getValue< 0, 1, 0>()) - F4Val(s.template getValue< 0, 0, 0>()), F4Val(s.template getValue< 0, 0, 1>()) - F4Val(s.template getValue< 0, 0, 0>()), 0), v5(F4Val(s.template getValue< 2, 0, 0>()) - F4Val(s.template getValue< 1, 0, 0>()), F4Val(s.template getValue< 0, 2, 0>()) - F4Val(s.template getValue< 0, 1, 0>()), F4Val(s.template getValue< 0, 0, 2>()) - F4Val(s.template getValue< 0, 0, 1>()), 0), v6(F4Val(s.template getValue< 3, 0, 0>()) - F4Val(s.template getValue< 2, 0, 0>()), F4Val(s.template getValue< 0, 3, 0>()) - F4Val(s.template getValue< 0, 2, 0>()), F4Val(s.template getValue< 0, 0, 3>()) - F4Val(s.template getValue< 0, 0, 2>()), 0), down = math::WENO5(v1, v2, v3, v4, v5), up = math::WENO5(v6, v5, v4, v3, v2); return math::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) { typedef typename Accessor::ValueType ValueT; return static_cast( (-1./12.)*( grid.getValue(ijk.offsetBy(2,0,0)) + grid.getValue(ijk.offsetBy(-2, 0, 0)) + grid.getValue(ijk.offsetBy(0,2,0)) + grid.getValue(ijk.offsetBy( 0,-2, 0)) + grid.getValue(ijk.offsetBy(0,0,2)) + grid.getValue(ijk.offsetBy( 0, 0,-2)) ) + (4./3.)*( grid.getValue(ijk.offsetBy(1,0,0)) + grid.getValue(ijk.offsetBy(-1, 0, 0)) + grid.getValue(ijk.offsetBy(0,1,0)) + grid.getValue(ijk.offsetBy( 0,-1, 0)) + grid.getValue(ijk.offsetBy(0,0,1)) + grid.getValue(ijk.offsetBy( 0, 0,-1)) ) - 7.5*grid.getValue(ijk)); } // stencil access version template static typename StencilT::ValueType result(const StencilT& stencil) { typedef typename StencilT::ValueType ValueT; return static_cast( (-1./12.)*( stencil.template getValue< 2, 0, 0>() + stencil.template getValue<-2, 0, 0>() + stencil.template getValue< 0, 2, 0>() + stencil.template getValue< 0,-2, 0>() + stencil.template getValue< 0, 0, 2>() + stencil.template getValue< 0, 0,-2>() ) + (4./3.)*( stencil.template getValue< 1, 0, 0>() + stencil.template getValue<-1, 0, 0>() + stencil.template getValue< 0, 1, 0>() + stencil.template getValue< 0,-1, 0>() + stencil.template getValue< 0, 0, 1>() + stencil.template getValue< 0, 0,-1>() ) - 7.5*stencil.template getValue< 0, 0, 0>()); } }; template<> struct ISLaplacian { // random access version template static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueT; return static_cast( (1./90.)*( grid.getValue(ijk.offsetBy(3,0,0)) + grid.getValue(ijk.offsetBy(-3, 0, 0)) + grid.getValue(ijk.offsetBy(0,3,0)) + grid.getValue(ijk.offsetBy( 0,-3, 0)) + grid.getValue(ijk.offsetBy(0,0,3)) + grid.getValue(ijk.offsetBy( 0, 0,-3)) ) - (3./20.)*( grid.getValue(ijk.offsetBy(2,0,0)) + grid.getValue(ijk.offsetBy(-2, 0, 0)) + grid.getValue(ijk.offsetBy(0,2,0)) + grid.getValue(ijk.offsetBy( 0,-2, 0)) + grid.getValue(ijk.offsetBy(0,0,2)) + grid.getValue(ijk.offsetBy( 0, 0,-2)) ) + 1.5 *( grid.getValue(ijk.offsetBy(1,0,0)) + grid.getValue(ijk.offsetBy(-1, 0, 0)) + grid.getValue(ijk.offsetBy(0,1,0)) + grid.getValue(ijk.offsetBy( 0,-1, 0)) + grid.getValue(ijk.offsetBy(0,0,1)) + grid.getValue(ijk.offsetBy( 0, 0,-1)) ) - (3*49/18.)*grid.getValue(ijk)); } // stencil access version template static typename StencilT::ValueType result(const StencilT& stencil) { typedef typename StencilT::ValueType ValueT; return static_cast( (1./90.)*( stencil.template getValue< 3, 0, 0>() + stencil.template getValue<-3, 0, 0>() + stencil.template getValue< 0, 3, 0>() + stencil.template getValue< 0,-3, 0>() + stencil.template getValue< 0, 0, 3>() + stencil.template getValue< 0, 0,-3>() ) - (3./20.)*( stencil.template getValue< 2, 0, 0>() + stencil.template getValue<-2, 0, 0>() + stencil.template getValue< 0, 2, 0>() + stencil.template getValue< 0,-2, 0>() + stencil.template getValue< 0, 0, 2>() + stencil.template getValue< 0, 0,-2>() ) + 1.5 *( stencil.template getValue< 1, 0, 0>() + stencil.template getValue<-1, 0, 0>() + stencil.template getValue< 0, 1, 0>() + stencil.template getValue< 0,-1, 0>() + stencil.template getValue< 0, 0, 1>() + stencil.template getValue< 0, 0,-1>() ) - (3*49/18.)*stencil.template getValue< 0, 0, 0>()); } }; //@} //@{ /// Divergence operator defined in index space using various first derivative schemes template struct ISDivergence { // random access version template static typename Accessor::ValueType::value_type result(const Accessor& grid, const Coord& ijk) { return D1Vec::inX(grid, ijk, 0) + D1Vec::inY(grid, ijk, 1) + D1Vec::inZ(grid, ijk, 2); } // stencil access version template static typename StencilT::ValueType::value_type result(const StencilT& stencil) { return D1Vec::inX(stencil, 0) + D1Vec::inY(stencil, 1) + D1Vec::inZ(stencil, 2); } }; //@} //@{ /// Curl operator defined in index space using various first derivative schemes template struct ISCurl { // random access version template static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) { 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(static_cast(D1::inX(grid, ijk)), static_cast(D1::inY(grid, ijk)), static_cast(D1::inZ(grid, ijk))); d2_rs = map.applyIJC(d2_is, d1_is, ijk.asVec3d()); } // the trace of the second derivative (range space) matrix is laplacian return ValueType(d2_rs(0,0) + d2_rs(1,1) + d2_rs(2,2)); } // stencil access version template static typename StencilT::ValueType result(const MapType& map, const StencilT& stencil) { 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(static_cast(D1::inX(grid, ijk)), static_cast(D1::inY(grid, ijk)), static_cast(D1::inZ(grid, ijk))), d1_ws; if (is_linear::value) {//resolved at compiletime d1_ws = map.applyIJT(d1_is); } else { d1_ws = map.applyIJT(d1_is, ijk.asVec3d()); } const double Dx2 = d1_ws(0)*d1_ws(0); const double Dy2 = d1_ws(1)*d1_ws(1); const double Dz2 = d1_ws(2)*d1_ws(2); const double normGrad = Dx2 + Dy2 + Dz2; if (normGrad <= math::Tolerance::value()) { alpha = beta = 0; return false; } // all the second derivatives in index space ValueType iddx = D2::inX(grid, ijk); ValueType iddy = D2::inY(grid, ijk); ValueType iddz = D2::inZ(grid, ijk); ValueType iddxy = D2::inXandY(grid, ijk); ValueType iddyz = D2::inYandZ(grid, ijk); ValueType iddxz = D2::inXandZ(grid, ijk); // second derivatives in index space Mat3d d2_is(iddx, iddxy, iddxz, iddxy, iddy, iddyz, iddxz, iddyz, iddz); // convert second derivatives to world space Mat3d d2_ws; if (is_linear::value) {//resolved at compiletime d2_ws = map.applyIJC(d2_is); } else { d2_ws = map.applyIJC(d2_is, d1_is, ijk.asVec3d()); } // assemble the nominator and denominator for mean curvature alpha = (Dx2*(d2_ws(1,1)+d2_ws(2,2))+Dy2*(d2_ws(0,0)+d2_ws(2,2)) +Dz2*(d2_ws(0,0)+d2_ws(1,1)) -2*(d1_ws(0)*(d1_ws(1)*d2_ws(0,1)+d1_ws(2)*d2_ws(0,2)) +d1_ws(1)*d1_ws(2)*d2_ws(1,2))); beta = std::sqrt(normGrad); // * 1/dx return true; } template static typename Accessor::ValueType result(const MapType& map, const Accessor& grid, const Coord& ijk) { 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Proximity.cc0000644000000000000000000001225012603226506014457 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/ConjGradient.h0000644000000000000000000016576712603226506014712 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// /// @file ConjGradient.h /// @authors D.J. Hill, Peter Cucka /// @brief Preconditioned conjugate gradient solver (solves @e Ax = @e b using /// the conjugate gradient method with one of a selection of preconditioners) #ifndef OPENVDB_MATH_CONJGRADIENT_HAS_BEEN_INCLUDED #define OPENVDB_MATH_CONJGRADIENT_HAS_BEEN_INCLUDED #include #include #include #include #include "Math.h" // for Abs(), isZero(), Max(), Sqrt() #include #include #include #include #include // for std::lower_bound() #include #include // for std::isfinite() #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { namespace pcg { typedef Index32 SizeType; typedef tbb::blocked_range SizeRange; template class Vector; template class SparseStencilMatrix; template class Preconditioner; template class JacobiPreconditioner; template class IncompleteCholeskyPreconditioner; /// Information about the state of a conjugate gradient solution struct State { bool success; int iterations; double relativeError; double absoluteError; }; /// Return default termination conditions for a conjugate gradient solver. template inline State terminationDefaults() { State s; s.success = false; s.iterations = 50; s.relativeError = 1.0e-6; s.absoluteError = std::numeric_limits::epsilon() * 100.0; return s; } //////////////////////////////////////// /// @brief Solve @e Ax = @e b via the preconditioned conjugate gradient method. /// /// @param A a symmetric, positive-definite, @e N x @e N matrix /// @param b a vector of size @e N /// @param x a vector of size @e N /// @param preconditioner a Preconditioner matrix /// @param termination termination conditions given as a State object with the following fields: ///
///
success ///
ignored ///
iterations ///
the maximum number of iterations, with or without convergence ///
relativeError ///
the relative error ||bA@f$\hat{x}@f$|| / ||b|| /// that denotes convergence ///
absoluteError ///
the absolute error ||bA@f$\hat{x}@f$|| that denotes convergence /// /// @throw ArithmeticError if either @a x or @a b is not of the appropriate size. template inline State solve( const PositiveDefMatrix& A, const Vector& b, Vector& x, Preconditioner& preconditioner, const State& termination = terminationDefaults()); /// @brief Solve @e Ax = @e b via the preconditioned conjugate gradient method. /// /// @param A a symmetric, positive-definite, @e N x @e N matrix /// @param b a vector of size @e N /// @param x a vector of size @e N /// @param preconditioner a Preconditioner matrix /// @param termination termination conditions given as a State object with the following fields: ///
///
success ///
ignored ///
iterations ///
the maximum number of iterations, with or without convergence ///
relativeError ///
the relative error ||bA@f$\hat{x}@f$|| / ||b|| /// that denotes convergence ///
absoluteError ///
the absolute error ||bA@f$\hat{x}@f$|| that denotes convergence /// @param interrupter an object adhering to the util::NullInterrupter interface /// with which computation can be interrupted /// /// @throw ArithmeticError if either @a x or @a b is not of the appropriate size. /// @throw RuntimeError if the computation is interrupted. template inline State solve( const PositiveDefMatrix& A, const Vector& b, Vector& x, Preconditioner& preconditioner, Interrupter& interrupter, const State& termination = terminationDefaults()); //////////////////////////////////////// /// Lightweight, variable-length vector template class Vector { public: typedef T ValueType; typedef boost::shared_ptr Ptr; /// Construct an empty vector. Vector(): mData(NULL), mSize(0) {} /// Construct a vector of @a n elements, with uninitialized values. Vector(SizeType n): mData(new T[n]), mSize(n) {} /// Construct a vector of @a n elements and initialize each element to the given value. Vector(SizeType n, const ValueType& val): mData(new T[n]), mSize(n) { this->fill(val); } ~Vector() { mSize = 0; delete[] mData; mData = NULL; } /// Deep copy the given vector. Vector(const Vector&); /// Deep copy the given vector. Vector& operator=(const Vector&); /// Return the number of elements in this vector. SizeType size() const { return mSize; } /// Return @c true if this vector has no elements. bool empty() const { return (mSize == 0); } /// @brief Reset this vector to have @a n elements, with uninitialized values. /// @warning All of this vector's existing values will be lost. void resize(SizeType n); /// Swap internal storage with another vector, which need not be the same size. void swap(Vector& other) { std::swap(mData, other.mData); std::swap(mSize, other.mSize); } /// Set all elements of this vector to @a value. void fill(const ValueType& value); //@{ /// @brief Multiply each element of this vector by @a s. template void scale(const Scalar& s); template Vector& operator*=(const Scalar& s) { this->scale(s); return *this; } //@} /// Return the dot product of this vector with the given vector, which must be the same size. ValueType dot(const Vector&) const; /// Return the infinity norm of this vector. ValueType infNorm() const; /// Return the L2 norm of this vector. ValueType l2Norm() const { return Sqrt(this->dot(*this)); } /// Return @c true if every element of this vector has a finite value. bool isFinite() const; /// @brief Return @c true if this vector is equivalent to the given vector /// to within the specified tolerance. template bool eq(const Vector& other, ValueType eps = Tolerance::value()) const; /// Return a string representation of this vector. std::string str() const; //@{ /// @brief Return the value of this vector's ith element. inline T& at(SizeType i) { return mData[i]; } inline const T& at(SizeType i) const { return mData[i]; } inline T& operator[](SizeType i) { return this->at(i); } inline const T& operator[](SizeType i) const { return this->at(i); } //@} //@{ /// @brief Return a pointer to this vector's elements. inline T* data() { return mData; } inline const T* data() const { return mData; } inline const T* constData() const { return mData; } //@} private: // Functor for use with tbb::parallel_for() template struct ScaleOp; struct DeterministicDotProductOp; // Functors for use with tbb::parallel_reduce() template struct EqOp; struct InfNormOp; struct IsFiniteOp; T* mData; SizeType mSize; }; typedef Vector VectorS; typedef Vector VectorD; //typedef Vector LinearVector; //////////////////////////////////////// /// @brief Sparse, square matrix representing a 3D stencil operator of size @a STENCIL_SIZE /// @details The implementation is a variation on compressed row storage (CRS). template class SparseStencilMatrix { public: typedef ValueType_ ValueType; typedef Vector VectorType; typedef boost::shared_ptr Ptr; class ConstValueIter; class ConstRow; class RowEditor; static const ValueType sZeroValue; /// Construct an @a n x @a n matrix with at most @a STENCIL_SIZE nonzero elements per row. SparseStencilMatrix(SizeType n); /// Deep copy the given matrix. SparseStencilMatrix(const SparseStencilMatrix&); //@{ /// Return the number of rows in this matrix. SizeType numRows() const { return mNumRows; } SizeType size() const { return mNumRows; } //@} /// @brief Set the value at the given coordinates. /// @warning It is not safe to set values in the same row simultaneously /// from multiple threads. void setValue(SizeType row, SizeType col, const ValueType&); //@{ /// @brief Return the value at the given coordinates. /// @warning It is not safe to get values from a row while another thread /// is setting values in that row. const ValueType& getValue(SizeType row, SizeType col) const; const ValueType& operator()(SizeType row, SizeType col) const; //@} /// Return a read-only view onto the given row of this matrix. ConstRow getConstRow(SizeType row) const; /// Return a read/write view onto the given row of this matrix. RowEditor getRowEditor(SizeType row); //@{ /// @brief Multiply all elements in the matrix by @a s; template void scale(const Scalar& s); template SparseStencilMatrix& operator*=(const Scalar& s) { this->scale(s); return *this; } //@} /// @brief Multiply this matrix by @a inVec and return the result in @a resultVec. /// @throw ArithmeticError if either @a inVec or @a resultVec is not of size @e N, /// where @e N x @e N is the size of this matrix. template void vectorMultiply(const Vector& inVec, Vector& resultVec) const; /// @brief Multiply this matrix by the vector represented by the array @a inVec /// and return the result in @a resultVec. /// @warning Both @a inVec and @a resultVec must have at least @e N elements, /// where @e N x @e N is the size of this matrix. template void vectorMultiply(const VecValueType* inVec, VecValueType* resultVec) const; /// @brief Return @c true if this matrix is equivalent to the given matrix /// to within the specified tolerance. template bool eq(const SparseStencilMatrix& other, ValueType eps = Tolerance::value()) const; /// Return @c true if every element of this matrix has a finite value. bool isFinite() const; /// Return a string representation of this matrix. std::string str() const; private: struct RowData { RowData(ValueType* v, SizeType* c, SizeType& s): mVals(v), mCols(c), mSize(s) {} ValueType* mVals; SizeType* mCols; SizeType& mSize; }; struct ConstRowData { ConstRowData(const ValueType* v, const SizeType* c, const SizeType& s): mVals(v), mCols(c), mSize(s) {} const ValueType* mVals; const SizeType* mCols; const SizeType& mSize; }; /// Base class for row accessors template class RowBase { public: typedef DataType_ DataType; static SizeType capacity() { return STENCIL_SIZE; } RowBase(const DataType& data): mData(data) {} bool empty() const { return (mData.mSize == 0); } const SizeType& size() const { return mData.mSize; } const ValueType& getValue(SizeType columnIdx, bool& active) const; const ValueType& getValue(SizeType columnIdx) const; /// Return an iterator over the stored values in this row. ConstValueIter cbegin() const; /// @brief Return @c true if this row is equivalent to the given row /// to within the specified tolerance. template bool eq(const RowBase& other, ValueType eps = Tolerance::value()) const; /// @brief Return the dot product of this row with the first /// @a vecSize elements of @a inVec. /// @warning @a inVec must have at least @a vecSize elements. template VecValueType dot(const VecValueType* inVec, SizeType vecSize) const; /// Return the dot product of this row with the given vector. template VecValueType dot(const Vector& inVec) const; /// Return a string representation of this row. std::string str() const; protected: friend class ConstValueIter; const ValueType& value(SizeType i) const { return mData.mVals[i]; } SizeType column(SizeType i) const { return mData.mCols[i]; } /// @brief Return the array index of the first column index that is /// equal to or greater than the given column index. /// @note If @a columnIdx is larger than any existing column index, /// the return value will point beyond the end of the array. SizeType find(SizeType columnIdx) const; DataType mData; }; typedef RowBase ConstRowBase; public: /// Iterator over the stored values in a row of this matrix class ConstValueIter { public: const ValueType& operator*() const { if (mData.mSize == 0) return SparseStencilMatrix::sZeroValue; return mData.mVals[mCursor]; } SizeType column() const { return mData.mCols[mCursor]; } void increment() { mCursor++; } ConstValueIter& operator++() { increment(); return *this; } operator bool() const { return (mCursor < mData.mSize); } void reset() { mCursor = 0; } private: friend class SparseStencilMatrix; ConstValueIter(const RowData& d): mData(d.mVals, d.mCols, d.mSize), mCursor(0) {} ConstValueIter(const ConstRowData& d): mData(d), mCursor(0) {} const ConstRowData mData; SizeType mCursor; }; /// Read-only accessor to a row of this matrix class ConstRow: public ConstRowBase { public: ConstRow(const ValueType* valueHead, const SizeType* columnHead, const SizeType& rowSize); }; // class ConstRow /// Read/write accessor to a row of this matrix class RowEditor: public RowBase<> { public: RowEditor(ValueType* valueHead, SizeType* columnHead, SizeType& rowSize, SizeType colSize); /// Set the number of entries in this row to zero. void clear(); /// @brief Set the value of the entry in the specified column. /// @return the current number of entries stored in this row. SizeType setValue(SizeType column, const ValueType& value); //@{ /// @brief Scale all of the entries in this row. template void scale(const Scalar&); template RowEditor& operator*=(const Scalar& s) { this->scale(s); return *this; } //@} private: const SizeType mNumColumns; // used only for bounds checking }; // class RowEditor private: // Functors for use with tbb::parallel_for() struct MatrixCopyOp; template struct VecMultOp; template struct RowScaleOp; // Functors for use with tbb::parallel_reduce() struct IsFiniteOp; template struct EqOp; const SizeType mNumRows; boost::scoped_array mValueArray; boost::scoped_array mColumnIdxArray; boost::scoped_array mRowSizeArray; }; // class SparseStencilMatrix //////////////////////////////////////// /// Base class for conjugate gradient preconditioners template class Preconditioner { public: typedef T ValueType; typedef boost::shared_ptr Ptr; template Preconditioner(const SparseStencilMatrix&) {} virtual ~Preconditioner() {} virtual bool isValid() const { return true; } /// @brief Apply this preconditioner to a residue vector: /// @e z = M−1r /// @param r residue vector /// @param[out] z preconditioned residue vector virtual void apply(const Vector& r, Vector& z) = 0; }; //////////////////////////////////////// namespace internal { // Functor for use with tbb::parallel_for() to copy data from one array to another template struct CopyOp { CopyOp(const T* from_, T* to_): from(from_), to(to_) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) to[n] = from[n]; } const T* from; T* to; }; // Functor for use with tbb::parallel_for() to fill an array with a constant value template struct FillOp { FillOp(T* data_, const T& val_): data(data_), val(val_) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) data[n] = val; } T* data; const T val; }; // Functor for use with tbb::parallel_for() that computes a * x + y template struct LinearOp { LinearOp(const T& a_, const T* x_, const T* y_, T* out_): a(a_), x(x_), y(y_), out(out_) {} void operator()(const SizeRange& range) const { if (isExactlyEqual(a, T(1))) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) out[n] = x[n] + y[n]; } else if (isExactlyEqual(a, T(-1))) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) out[n] = -x[n] + y[n]; } else { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) out[n] = a * x[n] + y[n]; } } const T a, *x, *y; T* out; }; } // namespace internal //////////////////////////////////////// inline std::ostream& operator<<(std::ostream& os, const State& state) { os << (state.success ? "succeeded with " : "") << "rel. err. " << state.relativeError << ", abs. err. " << state.absoluteError << " after " << state.iterations << " iteration" << (state.iterations == 1 ? "" : "s"); return os; } //////////////////////////////////////// template inline Vector::Vector(const Vector& other): mData(new T[other.mSize]), mSize(other.mSize) { tbb::parallel_for(SizeRange(0, mSize), internal::CopyOp(/*from=*/other.mData, /*to=*/mData)); } template inline Vector& Vector::operator=(const Vector& other) { // Update the internal storage to the correct size if (mSize != other.mSize) { mSize = other.mSize; delete[] mData; mData = new T[mSize]; } // Deep copy the data tbb::parallel_for(SizeRange(0, mSize), internal::CopyOp(/*from=*/other.mData, /*to=*/mData)); return *this; } template inline void Vector::resize(SizeType n) { if (n != mSize) { if (mData) delete[] mData; mData = new T[n]; mSize = n; } } template inline void Vector::fill(const ValueType& value) { tbb::parallel_for(SizeRange(0, mSize), internal::FillOp(mData, value)); } template template struct Vector::ScaleOp { ScaleOp(T* data_, const Scalar& s_): data(data_), s(s_) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) data[n] *= s; } T* data; const Scalar s; }; template template inline void Vector::scale(const Scalar& s) { tbb::parallel_for(SizeRange(0, mSize), ScaleOp(mData, s)); } template struct Vector::DeterministicDotProductOp { DeterministicDotProductOp(const T* a_, const T* b_, const SizeType binCount_, const SizeType arraySize_, T* reducetmp_): a(a_), b(b_), binCount(binCount_), arraySize(arraySize_), reducetmp(reducetmp_) {} void operator()(const SizeRange& range) const { const SizeType binSize = arraySize / binCount; // Iterate over bins (array segments) for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { const SizeType begin = n * binSize; const SizeType end = (n == binCount-1) ? arraySize : begin + binSize; // Compute the partial sum for this array segment T sum = zeroVal(); for (SizeType i = begin; i < end; ++i) { sum += a[i] * b[i]; } // Store the partial sum reducetmp[n] = sum; } } const T* a; const T* b; const SizeType binCount; const SizeType arraySize; T* reducetmp; }; template inline T Vector::dot(const Vector& other) const { assert(this->size() == other.size()); const T* aData = this->data(); const T* bData = other.data(); SizeType arraySize = this->size(); T result = zeroVal(); if (arraySize < 1024) { // Compute the dot product in serial for small arrays for (SizeType n = 0; n < arraySize; ++n) { result += aData[n] * bData[n]; } } else { // Compute the dot product by segmenting the arrays into // a predetermined number of sub arrays in parallel and // accumulate the finial result in series. const SizeType binCount = 100; T partialSums[100]; tbb::parallel_for(SizeRange(0, binCount), DeterministicDotProductOp(aData, bData, binCount, arraySize, partialSums)); for (SizeType n = 0; n < binCount; ++n) { result += partialSums[n]; } } return result; } template struct Vector::InfNormOp { InfNormOp(const T* data_): data(data_) {} T operator()(const SizeRange& range, T maxValue) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { maxValue = Max(maxValue, Abs(data[n])); } return maxValue; } static T join(T max1, T max2) { return Max(max1, max2); } const T* data; }; template inline T Vector::infNorm() const { // Parallelize over the elements of this vector. T result = tbb::parallel_reduce(SizeRange(0, this->size()), /*seed=*/zeroVal(), InfNormOp(this->data()), InfNormOp::join); return result; } template struct Vector::IsFiniteOp { IsFiniteOp(const T* data_): data(data_) {} bool operator()(const SizeRange& range, bool finite) const { if (finite) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { if (!std::isfinite(data[n])) return false; } } return finite; } static bool join(bool finite1, bool finite2) { return (finite1 && finite2); } const T* data; }; template inline bool Vector::isFinite() const { // Parallelize over the elements of this vector. bool finite = tbb::parallel_reduce(SizeRange(0, this->size()), /*seed=*/true, IsFiniteOp(this->data()), IsFiniteOp::join); return finite; } template template struct Vector::EqOp { EqOp(const T* a_, const OtherValueType* b_, T e): a(a_), b(b_), eps(e) {} bool operator()(const SizeRange& range, bool equal) const { if (equal) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { if (!isApproxEqual(a[n], b[n], eps)) return false; } } return equal; } static bool join(bool eq1, bool eq2) { return (eq1 && eq2); } const T* a; const OtherValueType* b; const T eps; }; template template inline bool Vector::eq(const Vector& other, ValueType eps) const { if (this->size() != other.size()) return false; bool equal = tbb::parallel_reduce(SizeRange(0, this->size()), /*seed=*/true, EqOp(this->data(), other.data(), eps), EqOp::join); return equal; } template inline std::string Vector::str() const { std::ostringstream ostr; ostr << "["; std::string sep; for (SizeType n = 0, N = this->size(); n < N; ++n) { ostr << sep << (*this)[n]; sep = ", "; } ostr << "]"; return ostr.str(); } //////////////////////////////////////// template const ValueType SparseStencilMatrix::sZeroValue = zeroVal(); template inline SparseStencilMatrix::SparseStencilMatrix(SizeType numRows) : mNumRows(numRows) , mValueArray(new ValueType[mNumRows * STENCIL_SIZE]) , mColumnIdxArray(new SizeType[mNumRows * STENCIL_SIZE]) , mRowSizeArray(new SizeType[mNumRows]) { // Initialize the matrix to a null state by setting the size of each row to zero. tbb::parallel_for(SizeRange(0, mNumRows), internal::FillOp(mRowSizeArray.get(), /*value=*/0)); } template struct SparseStencilMatrix::MatrixCopyOp { MatrixCopyOp(const SparseStencilMatrix& from_, SparseStencilMatrix& to_): from(&from_), to(&to_) {} void operator()(const SizeRange& range) const { const ValueType* fromVal = from->mValueArray.get(); const SizeType* fromCol = from->mColumnIdxArray.get(); ValueType* toVal = to->mValueArray.get(); SizeType* toCol = to->mColumnIdxArray.get(); for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { toVal[n] = fromVal[n]; toCol[n] = fromCol[n]; } } const SparseStencilMatrix* from; SparseStencilMatrix* to; }; template inline SparseStencilMatrix::SparseStencilMatrix(const SparseStencilMatrix& other) : mNumRows(other.mNumRows) , mValueArray(new ValueType[mNumRows * STENCIL_SIZE]) , mColumnIdxArray(new SizeType[mNumRows * STENCIL_SIZE]) , mRowSizeArray(new SizeType[mNumRows]) { SizeType size = mNumRows * STENCIL_SIZE; // Copy the value and column index arrays from the other matrix to this matrix. tbb::parallel_for(SizeRange(0, size), MatrixCopyOp(/*from=*/other, /*to=*/*this)); // Copy the row size array from the other matrix to this matrix. tbb::parallel_for(SizeRange(0, mNumRows), internal::CopyOp(/*from=*/other.mRowSizeArray.get(), /*to=*/mRowSizeArray.get())); } template inline void SparseStencilMatrix::setValue(SizeType row, SizeType col, const ValueType& val) { assert(row < mNumRows); this->getRowEditor(row).setValue(col, val); } template inline const ValueType& SparseStencilMatrix::getValue(SizeType row, SizeType col) const { assert(row < mNumRows); return this->getConstRow(row).getValue(col); } template inline const ValueType& SparseStencilMatrix::operator()(SizeType row, SizeType col) const { return this->getValue(row,col); } template template struct SparseStencilMatrix::RowScaleOp { RowScaleOp(SparseStencilMatrix& m, const Scalar& s_): mat(&m), s(s_) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { RowEditor row = mat->getRowEditor(n); row.scale(s); } } SparseStencilMatrix* mat; const Scalar s; }; template template inline void SparseStencilMatrix::scale(const Scalar& s) { // Parallelize over the rows in the matrix. tbb::parallel_for(SizeRange(0, mNumRows), RowScaleOp(*this, s)); } template template struct SparseStencilMatrix::VecMultOp { VecMultOp(const SparseStencilMatrix& m, const VecValueType* i, VecValueType* o): mat(&m), in(i), out(o) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { ConstRow row = mat->getConstRow(n); out[n] = row.dot(in, mat->numRows()); } } const SparseStencilMatrix* mat; const VecValueType* in; VecValueType* out; }; template template inline void SparseStencilMatrix::vectorMultiply( const Vector& inVec, Vector& resultVec) const { if (inVec.size() != mNumRows) { OPENVDB_THROW(ArithmeticError, "matrix and input vector have incompatible sizes (" << mNumRows << "x" << mNumRows << " vs. " << inVec.size() << ")"); } if (resultVec.size() != mNumRows) { OPENVDB_THROW(ArithmeticError, "matrix and result vector have incompatible sizes (" << mNumRows << "x" << mNumRows << " vs. " << resultVec.size() << ")"); } vectorMultiply(inVec.data(), resultVec.data()); } template template inline void SparseStencilMatrix::vectorMultiply( const VecValueType* inVec, VecValueType* resultVec) const { // Parallelize over the rows in the matrix. tbb::parallel_for(SizeRange(0, mNumRows), VecMultOp(*this, inVec, resultVec)); } template template struct SparseStencilMatrix::EqOp { EqOp(const SparseStencilMatrix& a_, const SparseStencilMatrix& b_, ValueType e): a(&a_), b(&b_), eps(e) {} bool operator()(const SizeRange& range, bool equal) const { if (equal) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { if (!a->getConstRow(n).eq(b->getConstRow(n), eps)) return false; } } return equal; } static bool join(bool eq1, bool eq2) { return (eq1 && eq2); } const SparseStencilMatrix* a; const SparseStencilMatrix* b; const ValueType eps; }; template template inline bool SparseStencilMatrix::eq( const SparseStencilMatrix& other, ValueType eps) const { if (this->numRows() != other.numRows()) return false; bool equal = tbb::parallel_reduce(SizeRange(0, this->numRows()), /*seed=*/true, EqOp(*this, other, eps), EqOp::join); return equal; } template struct SparseStencilMatrix::IsFiniteOp { IsFiniteOp(const SparseStencilMatrix& m): mat(&m) {} bool operator()(const SizeRange& range, bool finite) const { if (finite) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { const ConstRow row = mat->getConstRow(n); for (ConstValueIter it = row.cbegin(); it; ++it) { if (!std::isfinite(*it)) return false; } } } return finite; } static bool join(bool finite1, bool finite2) { return (finite1 && finite2); } const SparseStencilMatrix* mat; }; template inline bool SparseStencilMatrix::isFinite() const { // Parallelize over the rows of this matrix. bool finite = tbb::parallel_reduce(SizeRange(0, this->numRows()), /*seed=*/true, IsFiniteOp(*this), IsFiniteOp::join); return finite; } template inline std::string SparseStencilMatrix::str() const { std::ostringstream ostr; for (SizeType n = 0, N = this->size(); n < N; ++n) { ostr << n << ": " << this->getConstRow(n).str() << "\n"; } return ostr.str(); } template inline typename SparseStencilMatrix::RowEditor SparseStencilMatrix::getRowEditor(SizeType i) { assert(i < mNumRows); const SizeType head = i * STENCIL_SIZE; return RowEditor(&mValueArray[head], &mColumnIdxArray[head], mRowSizeArray[i], mNumRows); } template inline typename SparseStencilMatrix::ConstRow SparseStencilMatrix::getConstRow(SizeType i) const { assert(i < mNumRows); const SizeType head = i * STENCIL_SIZE; // index for this row into main storage return ConstRow(&mValueArray[head], &mColumnIdxArray[head], mRowSizeArray[i]); } template template inline SizeType SparseStencilMatrix::RowBase::find(SizeType columnIdx) const { if (this->empty()) return mData.mSize; // Get a pointer to the first column index that is equal to or greater than the given index. // (This assumes that the data is sorted by column.) const SizeType* colPtr = std::lower_bound(mData.mCols, mData.mCols + mData.mSize, columnIdx); // Return the offset of the pointer from the beginning of the array. return static_cast(colPtr - mData.mCols); } template template inline const ValueType& SparseStencilMatrix::RowBase::getValue( SizeType columnIdx, bool& active) const { active = false; SizeType idx = this->find(columnIdx); if (idx < this->size() && this->column(idx) == columnIdx) { active = true; return this->value(idx); } return SparseStencilMatrix::sZeroValue; } template template inline const ValueType& SparseStencilMatrix::RowBase::getValue(SizeType columnIdx) const { SizeType idx = this->find(columnIdx); if (idx < this->size() && this->column(idx) == columnIdx) { return this->value(idx); } return SparseStencilMatrix::sZeroValue; } template template inline typename SparseStencilMatrix::ConstValueIter SparseStencilMatrix::RowBase::cbegin() const { return ConstValueIter(mData); } template template template inline bool SparseStencilMatrix::RowBase::eq( const RowBase& other, ValueType eps) const { if (this->size() != other.size()) return false; for (ConstValueIter it = cbegin(), oit = other.cbegin(); it || oit; ++it, ++oit) { if (it.column() != oit.column()) return false; if (!isApproxEqual(*it, *oit, eps)) return false; } return true; } template template template inline VecValueType SparseStencilMatrix::RowBase::dot( const VecValueType* inVec, SizeType vecSize) const { VecValueType result = zeroVal(); for (SizeType idx = 0, N = std::min(vecSize, this->size()); idx < N; ++idx) { result += static_cast(this->value(idx) * inVec[this->column(idx)]); } return result; } template template template inline VecValueType SparseStencilMatrix::RowBase::dot( const Vector& inVec) const { return dot(inVec.data(), inVec.size()); } template template inline std::string SparseStencilMatrix::RowBase::str() const { std::ostringstream ostr; std::string sep; for (SizeType n = 0, N = this->size(); n < N; ++n) { ostr << sep << "(" << this->column(n) << ", " << this->value(n) << ")"; sep = ", "; } return ostr.str(); } template inline SparseStencilMatrix::ConstRow::ConstRow( const ValueType* valueHead, const SizeType* columnHead, const SizeType& rowSize): ConstRowBase(ConstRowData(const_cast(valueHead), const_cast(columnHead), const_cast(rowSize))) { } template inline SparseStencilMatrix::RowEditor::RowEditor( ValueType* valueHead, SizeType* columnHead, SizeType& rowSize, SizeType colSize): RowBase<>(RowData(valueHead, columnHead, rowSize)), mNumColumns(colSize) { } template inline void SparseStencilMatrix::RowEditor::clear() { // Note: since mSize is a reference, this modifies the underlying matrix. RowBase<>::mData.mSize = 0; } template inline SizeType SparseStencilMatrix::RowEditor::setValue( SizeType column, const ValueType& value) { assert(column < mNumColumns); RowData& data = RowBase<>::mData; // Get the offset of the first column index that is equal to or greater than // the column to be modified. SizeType offset = this->find(column); if (offset < data.mSize && data.mCols[offset] == column) { // If the column already exists, just update its value. data.mVals[offset] = value; return data.mSize; } // Check that it is safe to add a new column. assert(data.mSize < this->capacity()); if (offset >= data.mSize) { // The new column's index is larger than any existing index. Append the new column. data.mVals[data.mSize] = value; data.mCols[data.mSize] = column; } else { // Insert the new column at the computed offset after shifting subsequent columns. for (SizeType i = data.mSize; i > offset; --i) { data.mVals[i] = data.mVals[i - 1]; data.mCols[i] = data.mCols[i - 1]; } data.mVals[offset] = value; data.mCols[offset] = column; } ++data.mSize; return data.mSize; } template template inline void SparseStencilMatrix::RowEditor::scale(const Scalar& s) { for (int idx = 0, N = this->size(); idx < N; ++idx) { RowBase<>::mData.mVals[idx] *= s; } } //////////////////////////////////////// /// Diagonal preconditioner template class JacobiPreconditioner: public Preconditioner { private: struct InitOp; struct ApplyOp; public: typedef typename MatrixType::ValueType ValueType; typedef Preconditioner BaseType; typedef Vector VectorType; typedef boost::shared_ptr Ptr; JacobiPreconditioner(const MatrixType& A): BaseType(A), mDiag(A.numRows()) { // Initialize vector mDiag with the values from the matrix diagonal. tbb::parallel_for(SizeRange(0, A.numRows()), InitOp(A, mDiag.data())); } virtual ~JacobiPreconditioner() {} virtual void apply(const Vector& r, Vector& z) { const SizeType size = mDiag.size(); assert(r.size() == z.size()); assert(r.size() == size); tbb::parallel_for(SizeRange(0, size), ApplyOp(mDiag.data(), r.data(), z.data())); } /// Return @c true if all values along the diagonal are finite. bool isFinite() const { return mDiag.isFinite(); } private: // Functor for use with tbb::parallel_for() struct InitOp { InitOp(const MatrixType& m, ValueType* v): mat(&m), vec(v) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { const ValueType val = mat->getValue(n, n); assert(!isApproxZero(val, ValueType(0.0001))); vec[n] = static_cast(1.0 / val); } } const MatrixType* mat; ValueType* vec; }; // Functor for use with tbb::parallel_reduce() struct ApplyOp { ApplyOp(const ValueType* x_, const ValueType* y_, ValueType* out_): x(x_), y(y_), out(out_) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) out[n] = x[n] * y[n]; } const ValueType *x, *y; ValueType* out; }; // The Jacobi preconditioner is a diagonal matrix VectorType mDiag; }; // class JacobiPreconditioner /// Preconditioner using incomplete Cholesky factorization template class IncompleteCholeskyPreconditioner: public Preconditioner { private: struct CopyToLowerOp; struct TransposeOp; public: typedef typename MatrixType::ValueType ValueType; typedef Preconditioner BaseType; typedef Vector VectorType; typedef boost::shared_ptr Ptr; typedef SparseStencilMatrix TriangularMatrix; typedef typename TriangularMatrix::ConstRow TriangleConstRow; typedef typename TriangularMatrix::RowEditor TriangleRowEditor; IncompleteCholeskyPreconditioner(const MatrixType& matrix) : BaseType(matrix) , mLowerTriangular(matrix.numRows()) , mUpperTriangular(matrix.numRows()) , mTempVec(matrix.numRows()) { // Size of matrix const SizeType numRows = mLowerTriangular.numRows(); // Copy the upper triangular part to the lower triangular part. tbb::parallel_for(SizeRange(0, numRows), CopyToLowerOp(matrix, mLowerTriangular)); // Build the Incomplete Cholesky Matrix // // Algorithm: // // for (k = 0; k < size; ++k) { // A(k,k) = sqrt(A(k,k)); // for (i = k +1, i < size; ++i) { // if (A(i,k) == 0) continue; // A(i,k) = A(i,k) / A(k,k); // } // for (j = k+1; j < size; ++j) { // for (i = j; i < size; ++i) { // if (A(i,j) == 0) continue; // A(i,j) -= A(i,k)*A(j,k); // } // } // } mPassedCompatibilityCondition = true; for (SizeType k = 0; k < numRows; ++k) { TriangleConstRow crow_k = mLowerTriangular.getConstRow(k); ValueType diagonalValue = crow_k.getValue(k); // Test if the matrix build has failed. if (diagonalValue < 1.e-5) { mPassedCompatibilityCondition = false; break; } diagonalValue = Sqrt(diagonalValue); TriangleRowEditor row_k = mLowerTriangular.getRowEditor(k); row_k.setValue(k, diagonalValue); // Exploit the fact that the matrix is symmetric. typename MatrixType::ConstRow srcRow = matrix.getConstRow(k); typename MatrixType::ConstValueIter citer = srcRow.cbegin(); for ( ; citer; ++citer) { SizeType ii = citer.column(); if (ii < k+1) continue; // look above diagonal TriangleRowEditor row_ii = mLowerTriangular.getRowEditor(ii); row_ii.setValue(k, *citer / diagonalValue); } // for (j = k+1; j < size; ++j) replaced by row iter below citer.reset(); // k,j entries for ( ; citer; ++citer) { SizeType j = citer.column(); if (j < k+1) continue; TriangleConstRow row_j = mLowerTriangular.getConstRow(j); ValueType a_jk = row_j.getValue(k); // a_jk is non zero if a_kj is non zero // Entry (i,j) is non-zero if matrix(j,i) is nonzero typename MatrixType::ConstRow mask = matrix.getConstRow(j); typename MatrixType::ConstValueIter maskIter = mask.cbegin(); for ( ; maskIter; ++maskIter) { SizeType i = maskIter.column(); if (i < j) continue; TriangleConstRow crow_i = mLowerTriangular.getConstRow(i); ValueType a_ij = crow_i.getValue(j); ValueType a_ik = crow_i.getValue(k); TriangleRowEditor row_i = mLowerTriangular.getRowEditor(i); a_ij -= a_ik * a_jk; row_i.setValue(j, a_ij); } } } // Build the transpose of the IC matrix: mUpperTriangular tbb::parallel_for(SizeRange(0, numRows), TransposeOp(matrix, mLowerTriangular, mUpperTriangular)); } virtual ~IncompleteCholeskyPreconditioner() {} virtual bool isValid() const { return mPassedCompatibilityCondition; } virtual void apply(const Vector& rVec, Vector& zVec) { if (!mPassedCompatibilityCondition) { OPENVDB_THROW(ArithmeticError, "invalid Cholesky decomposition"); } // Solve mUpperTriangular * mLowerTriangular * rVec = zVec; SizeType size = mLowerTriangular.numRows(); zVec.fill(zeroVal()); ValueType* zData = zVec.data(); if (size == 0) return; assert(rVec.size() == size); assert(zVec.size() == size); // Allocate a temp vector mTempVec.fill(zeroVal()); ValueType* tmpData = mTempVec.data(); const ValueType* rData = rVec.data(); // Solve mLowerTriangular * tmp = rVec; for (SizeType i = 0; i < size; ++i) { typename TriangularMatrix::ConstRow row = mLowerTriangular.getConstRow(i); ValueType diagonal = row.getValue(i); ValueType dot = row.dot(mTempVec); tmpData[i] = (rData[i] - dot) / diagonal; if (!std::isfinite(tmpData[i])) { OPENVDB_LOG_DEBUG_RUNTIME("1 diagonal was " << diagonal); OPENVDB_LOG_DEBUG_RUNTIME("1a diagonal " << row.getValue(i)); } } // Solve mUpperTriangular * zVec = tmp; for (SizeType ii = 0; ii < size; ++ii) { SizeType i = size - 1 - ii; typename TriangularMatrix::ConstRow row = mUpperTriangular.getConstRow(i); ValueType diagonal = row.getValue(i); ValueType dot = row.dot(zVec); zData[i] = (tmpData[i] - dot) / diagonal; if (!std::isfinite(zData[i])) { OPENVDB_LOG_DEBUG_RUNTIME("2 diagonal was " << diagonal); } } } const TriangularMatrix& lowerMatrix() const { return mLowerTriangular; } const TriangularMatrix& upperMatrix() const { return mUpperTriangular; } private: // Functor for use with tbb::parallel_for() struct CopyToLowerOp { CopyToLowerOp(const MatrixType& m, TriangularMatrix& l): mat(&m), lower(&l) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { typename TriangularMatrix::RowEditor outRow = lower->getRowEditor(n); outRow.clear(); typename MatrixType::ConstRow inRow = mat->getConstRow(n); for (typename MatrixType::ConstValueIter it = inRow.cbegin(); it; ++it) { if (it.column() > n) continue; // skip above diagonal outRow.setValue(it.column(), *it); } } } const MatrixType* mat; TriangularMatrix* lower; }; // Functor for use with tbb::parallel_for() struct TransposeOp { TransposeOp(const MatrixType& m, const TriangularMatrix& l, TriangularMatrix& u): mat(&m), lower(&l), upper(&u) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { typename TriangularMatrix::RowEditor outRow = upper->getRowEditor(n); outRow.clear(); // Use the fact that matrix is symmetric. typename MatrixType::ConstRow inRow = mat->getConstRow(n); for (typename MatrixType::ConstValueIter it = inRow.cbegin(); it; ++it) { const SizeType column = it.column(); if (column < n) continue; // only set upper triangle outRow.setValue(column, lower->getValue(column, n)); } } } const MatrixType* mat; const TriangularMatrix* lower; TriangularMatrix* upper; }; TriangularMatrix mLowerTriangular; TriangularMatrix mUpperTriangular; Vector mTempVec; bool mPassedCompatibilityCondition; }; // class IncompleteCholeskyPreconditioner //////////////////////////////////////// namespace internal { /// Compute @e ax + @e y. template inline void axpy(const T& a, const T* xVec, const T* yVec, T* resultVec, SizeType size) { tbb::parallel_for(SizeRange(0, size), LinearOp(a, xVec, yVec, resultVec)); } /// Compute @e ax + @e y. template inline void axpy(const T& a, const Vector& xVec, const Vector& yVec, Vector& result) { assert(xVec.size() == yVec.size()); assert(xVec.size() == result.size()); axpy(a, xVec.data(), yVec.data(), result.data(), xVec.size()); } /// Compute @e r = @e b − @e Ax. template inline void computeResidual(const MatrixOperator& A, const VecValueType* x, const VecValueType* b, VecValueType* r) { // Compute r = A * x. A.vectorMultiply(x, r); // Compute r = b - r. tbb::parallel_for(SizeRange(0, A.numRows()), LinearOp(-1.0, r, b, r)); } /// Compute @e r = @e b − @e Ax. template inline void computeResidual(const MatrixOperator& A, const Vector& x, const Vector& b, Vector& r) { assert(x.size() == b.size()); assert(x.size() == r.size()); assert(x.size() == A.numRows()); computeResidual(A, x.data(), b.data(), r.data()); } } // namespace internal //////////////////////////////////////// template inline State solve( const PositiveDefMatrix& Amat, const Vector& bVec, Vector& xVec, Preconditioner& precond, const State& termination) { util::NullInterrupter interrupter; return solve(Amat, bVec, xVec, precond, interrupter, termination); } template inline State solve( const PositiveDefMatrix& Amat, const Vector& bVec, Vector& xVec, Preconditioner& precond, Interrupter& interrupter, const State& termination) { typedef typename PositiveDefMatrix::ValueType ValueType; typedef Vector VectorType; State result; result.success = false; result.iterations = 0; result.relativeError = 0.0; result.absoluteError = 0.0; const SizeType size = Amat.numRows(); if (size == 0) { OPENVDB_LOG_WARN("pcg::solve(): matrix has dimension zero"); return result; } if (size != bVec.size()) { OPENVDB_THROW(ArithmeticError, "A and b have incompatible sizes" << size << "x" << size << " vs. " << bVec.size() << ")"); } if (size != xVec.size()) { OPENVDB_THROW(ArithmeticError, "A and x have incompatible sizes" << size << "x" << size << " vs. " << xVec.size() << ")"); } // Temp vectors VectorType zVec(size); // transformed residual (M^-1 r) VectorType pVec(size); // search direction VectorType qVec(size); // A * p // Compute norm of B (the source) const ValueType tmp = bVec.infNorm(); const ValueType infNormOfB = isZero(tmp) ? 1.f : tmp; // Compute rVec: residual = b - Ax. VectorType rVec(size); // vector of residuals internal::computeResidual(Amat, xVec, bVec, rVec); assert(rVec.isFinite()); // Normalize the residual norm with the source norm and look for early out. result.absoluteError = static_cast(rVec.infNorm()); result.relativeError = static_cast(result.absoluteError / infNormOfB); if (result.relativeError <= termination.relativeError) { result.success = true; return result; } // Iterations of the CG solve ValueType rDotZPrev(1); // inner product of // Keep track of the minimum error to monitor convergence. ValueType minL2Error = std::numeric_limits::max(); ValueType l2Error; int iteration = 0; for ( ; iteration < termination.iterations; ++iteration) { if (interrupter.wasInterrupted()) { OPENVDB_THROW(RuntimeError, "conjugate gradient solver was interrupted"); } OPENVDB_LOG_DEBUG_RUNTIME("pcg::solve() " << result); result.iterations = iteration + 1; // Apply preconditioner to residual // z_{k} = M^-1 r_{k} precond.apply(rVec, zVec); // const ValueType rDotZ = rVec.dot(zVec); assert(std::isfinite(rDotZ)); if (0 == iteration) { // Initialize pVec = zVec; } else { const ValueType beta = rDotZ / rDotZPrev; // p = beta * p + z internal::axpy(beta, pVec, zVec, /*result */pVec); } // q_{k} = A p_{k} Amat.vectorMultiply(pVec, qVec); // alpha = / const ValueType pAp = pVec.dot(qVec); assert(std::isfinite(pAp)); const ValueType alpha = rDotZ / pAp; rDotZPrev = rDotZ; // x_{k} = x_{k-1} + alpha * p_{k} internal::axpy(alpha, pVec, xVec, /*result=*/xVec); // r_{k} = r_{k-1} - alpha_{k-1} A p_{k} internal::axpy(-alpha, qVec, rVec, /*result=*/rVec); // update tolerances l2Error = rVec.l2Norm(); minL2Error = Min(l2Error, minL2Error); result.absoluteError = static_cast(rVec.infNorm()); result.relativeError = static_cast(result.absoluteError / infNormOfB); if (l2Error > 2 * minL2Error) { // The solution started to diverge. result.success = false; break; } if (!std::isfinite(result.absoluteError)) { // Total divergence of solution result.success = false; break; } if (result.absoluteError <= termination.absoluteError) { // Convergence result.success = true; break; } if (result.relativeError <= termination.relativeError) { // Convergence result.success = true; break; } } OPENVDB_LOG_DEBUG_RUNTIME("pcg::solve() " << result); return result; } } // namespace pcg } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_CONJGRADIENT_HAS_BEEN_INCLUDED /////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// openvdb/math/DDA.h0000644000000000000000000003412612603226506012713 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file DDA.h /// /// @author Ken Museth /// /// @brief Digital Differential Analyzers specialized for VDB. #ifndef OPENVDB_MATH_DDA_HAS_BEEN_INCLUDED #define OPENVDB_MATH_DDA_HAS_BEEN_INCLUDED #include "Coord.h" #include "Math.h" #include "Vec3.h" #include // for std::ostream #include // for std::numeric_limits::max() namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @brief A Digital Differential Analyzer specialized for OpenVDB grids /// @note Conceptually similar to Bresenham's line algorithm applied /// to a 3D Ray intersecting OpenVDB nodes or voxels. Log2Dim = 0 /// corresponds to a voxel and Log2Dim a tree node of size 2^Log2Dim. /// /// @note The Ray template class is expected to have the following /// methods: test(time), t0(), t1(), invDir(), and operator()(time). /// See the example Ray class above for their definition. template class DDA { public: typedef typename RayT::RealType RealType; typedef RealType RealT; typedef typename RayT::Vec3Type Vec3Type; typedef Vec3Type Vec3T; /// @brief uninitialized constructor DDA() {} DDA(const RayT& ray) { this->init(ray); } DDA(const RayT& ray, RealT startTime) { this->init(ray, startTime); } DDA(const RayT& ray, RealT startTime, RealT maxTime) { this->init(ray, startTime, maxTime); } inline void init(const RayT& ray, RealT startTime, RealT maxTime) { assert(startTime <= maxTime); static const int DIM = 1 << Log2Dim; mT0 = startTime; mT1 = maxTime; const Vec3T &pos = ray(mT0), &dir = ray.dir(), &inv = ray.invDir(); mVoxel = Coord::floor(pos) & (~(DIM-1)); for (int axis = 0; axis < 3; ++axis) { if (math::isZero(dir[axis])) {//handles dir = +/- 0 mStep[axis] = 0;//dummy value mNext[axis] = std::numeric_limits::max();//i.e. disabled! mDelta[axis] = std::numeric_limits::max();//dummy value } else if (inv[axis] > 0) { mStep[axis] = DIM; mNext[axis] = mT0 + (mVoxel[axis] + DIM - pos[axis]) * inv[axis]; mDelta[axis] = mStep[axis] * inv[axis]; } else { mStep[axis] = -DIM; mNext[axis] = mT0 + (mVoxel[axis] - pos[axis]) * inv[axis]; mDelta[axis] = mStep[axis] * inv[axis]; } } } inline void init(const RayT& ray) { this->init(ray, ray.t0(), ray.t1()); } inline void init(const RayT& ray, RealT startTime) { this->init(ray, startTime, ray.t1()); } /// @brief Increment the voxel index to next intersected voxel or node /// and returns true if the step in time does not exceed maxTime. inline bool step() { const int stepAxis = static_cast(math::MinIndex(mNext)); mT0 = mNext[stepAxis]; mNext[stepAxis] += mDelta[stepAxis]; mVoxel[stepAxis] += mStep[stepAxis]; return mT0 <= mT1; } /// @brief Return the index coordinates of the next node or voxel /// intersected by the ray. If Log2Dim = 0 the return value is the /// actual signed coordinate of the voxel, else it is the origin /// of the corresponding VDB tree node or tile. /// @note Incurs no computational overhead. inline const Coord& voxel() const { return mVoxel; } /// @brief Return the time (parameterized along the Ray) of the /// first hit of a tree node of size 2^Log2Dim. /// @details This value is initialized to startTime or ray.t0() /// depending on the constructor used. /// @note Incurs no computational overhead. inline RealType time() const { return mT0; } /// @brief Return the maximum time (parameterized along the Ray). inline RealType maxTime() const { return mT1; } /// @brief Return the time (parameterized along the Ray) of the /// second (i.e. next) hit of a tree node of size 2^Log2Dim. /// @note Incurs a (small) computational overhead. inline RealType next() const { return math::Min(mT1, mNext[0], mNext[1], mNext[2]); } /// @brief Print information about this DDA for debugging. /// @param os a stream to which to write textual information. void print(std::ostream& os = std::cout) const { os << "Dim=" << (1< #include #include #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] = static_cast(a); MyBase::mm[1] = static_cast(b); MyBase::mm[2] = static_cast(c); MyBase::mm[3] = static_cast(d); MyBase::mm[4] = static_cast(e); MyBase::mm[5] = static_cast(f); MyBase::mm[6] = static_cast(g); MyBase::mm[7] = static_cast(h); MyBase::mm[8] = static_cast(i); } // constructor1Test /// Construct matrix 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Math.h0000644000000000000000000006601512603226506013216 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 // boost::math::isfinite #include // for boost::random::mt19937 #include #include #include // for BOOST_VERSION #include #include // Compile pragmas #define PRAGMA(x) _Pragma(#x) // Intel(r) compiler fires remark #1572: floating-point equality and inequality // comparisons are unrealiable when == or != is used with floating point operands. #if defined(__INTEL_COMPILER) #define OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN \ _Pragma("warning (push)") \ _Pragma("warning (disable:1572)") #define OPENVDB_NO_FP_EQUALITY_WARNING_END \ _Pragma("warning (pop)") #elif defined(__clang__) #define OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN \ PRAGMA(clang diagnostic push) \ PRAGMA(clang diagnostic ignored "-Wfloat-equal") #define OPENVDB_NO_FP_EQUALITY_WARNING_END \ PRAGMA(clang diagnostic pop) #else // For GCC, #pragma GCC diagnostic ignored "-Wfloat-equal" // isn't working until gcc 4.2+, // Trying // #pragma GCC system_header // creates other problems, most notably "warning: will never be executed" // in from templates, unsure of how to work around. // If necessary, could use integer based comparisons for equality #define OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN #define OPENVDB_NO_FP_EQUALITY_WARNING_END #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { /// @brief Return the value of type T that corresponds to zero. /// @note A zeroVal() specialization must be defined for each @c ValueType T /// that cannot be constructed using the form @c T(0). For example, @c std::string(0) /// treats 0 as @c 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 engine random number generator Rand01(const EngineType& engine): mEngine(engine) {} /// @brief Initialize the generator. /// @param seed seed value for the random number generator Rand01(unsigned int seed): mEngine(static_cast(seed)) {} /// Set the seed value for the random number generator void setSeed(unsigned int seed) { mEngine.seed(static_cast(seed)); } /// Return a const reference to the random number generator. const EngineType& engine() const { return mEngine; } /// Return a uniformly distributed random number in the range [0, 1). FloatType operator()() { return mRand(mEngine); } }; 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 engine random number generator /// @param imin,imax generate integers that are uniformly distributed over [imin, imax] RandInt(const EngineType& engine, IntType imin, IntType imax): mEngine(engine), mRand(std::min(imin, imax), std::max(imin, imax)) {} /// @brief Initialize the generator. /// @param seed seed value for the random number generator /// @param imin,imax generate integers that are uniformly distributed over [imin, imax] RandInt(unsigned int seed, IntType imin, IntType imax): mEngine(static_cast(seed)), mRand(std::min(imin, imax), std::max(imin, imax)) {} /// Change the range over which integers are distributed to [imin, imax]. void setRange(IntType imin, IntType imax) { mRand = Distr(std::min(imin, imax), std::max(imin, imax)); } /// Set the seed value for the random number generator void setSeed(unsigned int seed) { mEngine.seed(static_cast(seed)); } /// Return a const reference to the random number generator. const EngineType& engine() const { return mEngine; } /// Return a randomly-generated integer in the current range. IntType operator()() { return mRand(mEngine); } /// @brief Return a randomly-generated integer in the new range [imin, imax], /// without changing the current range. IntType operator()(IntType imin, IntType imax) { const IntType lo = std::min(imin, imax), hi = std::max(imin, imax); #if BOOST_VERSION >= 104700 return mRand(mEngine, typename 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>max) ); return x > min ? x < max ? x : max : min; } /// Return @a x clamped to [0, 1] template inline Type Clamp01(Type x) { return x > Type(0) ? x < Type(1) ? x : Type(1) : Type(0); } /// Return @c true if @a x is outside [0,1] template inline bool ClampTest01(Type &x) { if (x >= Type(0) && x <= Type(1)) return false; x = x < Type(0) ? Type(0) : Type(1); return true; } /// @brief Return 0 if @a x < @a 0, 1 if @a x > 1 or else @f$(3-2x)x^2@f$. template inline Type SmoothUnitStep(Type x) { return x > 0 ? x < 1 ? (3-2*x)*x*x : Type(1) : Type(0); } /// @brief Return 0 if @a x < @a min, 1 if @a x > @a max or else @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); return SmoothUnitStep((x-min)/(max-min)); } // ==========> Absolute Value <================== //@{ /// Return the absolute value of the given quantity. inline int32_t Abs(int32_t i) { return abs(i); } inline int64_t Abs(int64_t i) { #ifdef _MSC_VER return (i < int64_t(0) ? -i : i); #else return labs(i); #endif } inline float Abs(float x) { return fabsf(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; } inline bool Abs(bool b) { return b; } // On OSX size_t and uint64_t are different types #if defined(__APPLE__) || defined(MACOSX) inline size_t Abs(size_t i) { return i; } #endif //@} //////////////////////////////////////// // ==========> Value Comparison <================== /// Return @c true if @a x is exactly equal to zero. template inline bool isZero(const Type& x) { OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN return x == zeroVal(); OPENVDB_NO_FP_EQUALITY_WARNING_END } /// @brief Return @c true if @a x is equal to zero to within /// the default floating-point comparison tolerance. template inline bool isApproxZero(const Type& x) { const Type tolerance = Type(zeroVal() + Tolerance::value()); return x < tolerance && x > -tolerance; } /// Return @c true if @a x is equal to zero to within the given tolerance. template inline bool isApproxZero(const Type& x, const Type& tolerance) { return x < tolerance && x > -tolerance; } /// Return @c true if @a x is less than zero. template inline bool isNegative(const Type& x) { return x < zeroVal(); } /// Return @c false, since @c bool values are never less than zero. template<> inline bool isNegative(const bool&) { return false; } /// Return @c true if @a x is finite. template inline bool isFinite(const Type& x) { return boost::math::isfinite(x); } /// @brief Return @c true if @a a is equal to @a b to within /// the default floating-point comparison tolerance. template inline bool isApproxEqual(const Type& a, const Type& b) { const Type tolerance = Type(zeroVal() + Tolerance::value()); return !(Abs(a - b) > tolerance); } /// Return @c true if @a a is equal to @a b to within the given tolerance. template inline bool isApproxEqual(const Type& a, const Type& b, const Type& tolerance) { return !(Abs(a - b) > tolerance); } #define OPENVDB_EXACT_IS_APPROX_EQUAL(T) \ template<> inline bool isApproxEqual(const T& a, const T& b) { return a == b; } \ template<> inline bool isApproxEqual(const T& a, const T& b, const T&) { return a == b; } \ /**/ OPENVDB_EXACT_IS_APPROX_EQUAL(bool) OPENVDB_EXACT_IS_APPROX_EQUAL(std::string) /// @brief Return @c true if @a a is larger than @a b to within /// the given tolerance, i.e., if @a b - @a a < @a tolerance. template inline bool isApproxLarger(const Type& a, const Type& b, const Type& tolerance) { return (b - a < tolerance); } /// @brief Return @c true if @a a is exactly equal to @a b. template inline bool isExactlyEqual(const T0& a, const T1& b) { OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN return a == b; OPENVDB_NO_FP_EQUALITY_WARNING_END } template inline bool isRelOrApproxEqual(const Type& a, const Type& b, const Type& absTol, const Type& relTol) { // First check to see if we are inside the absolute tolerance // Necessary for numbers close to 0 if (!(Abs(a - b) > absTol)) return true; // Next check to see if we are inside the relative tolerance // to handle large numbers that aren't within the abs tolerance // but could be the closest floating point representation double relError; if (Abs(b) > Abs(a)) { relError = Abs((a - b) / b); } else { relError = Abs((a - b) / a); } return (relError <= relTol); } template<> inline bool isRelOrApproxEqual(const bool& a, const bool& b, const bool&, const bool&) { return (a == b); } // Avoid strict aliasing issues by using type punning // http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html // Using "casting through a union(2)" inline int32_t floatToInt32(const float aFloatValue) { union FloatOrInt32 { float floatValue; int32_t int32Value; }; const FloatOrInt32* foi = reinterpret_cast(&aFloatValue); return foi->int32Value; } inline int64_t doubleToInt64(const double aDoubleValue) { union DoubleOrInt64 { double doubleValue; int64_t int64Value; }; const DoubleOrInt64* dol = reinterpret_cast(&aDoubleValue); return dol->int64Value; } // aUnitsInLastPlace is the allowed difference between the least significant digits // of the numbers' floating point representation // Please read the reference paper before trying to use isUlpsEqual // http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm inline bool isUlpsEqual(const double aLeft, const double aRight, const int64_t aUnitsInLastPlace) { int64_t longLeft = doubleToInt64(aLeft); // Because of 2's complement, must restore lexicographical order if (longLeft < 0) { longLeft = INT64_C(0x8000000000000000) - longLeft; } int64_t longRight = doubleToInt64(aRight); // Because of 2's complement, must restore lexicographical order if (longRight < 0) { longRight = INT64_C(0x8000000000000000) - longRight; } int64_t difference = labs(longLeft - longRight); return (difference <= aUnitsInLastPlace); } inline bool isUlpsEqual(const float aLeft, const float aRight, const int32_t aUnitsInLastPlace) { int32_t intLeft = floatToInt32(aLeft); // Because of 2's complement, must restore lexicographical order if (intLeft < 0) { intLeft = 0x80000000 - intLeft; } int32_t intRight = floatToInt32(aRight); // Because of 2's complement, must restore lexicographical order if (intRight < 0) { intRight = 0x80000000 - intRight; } int32_t difference = abs(intLeft - intRight); return (difference <= aUnitsInLastPlace); } //////////////////////////////////////// // ==========> Pow <================== /// Return @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); } //@} /// Return @a x rounded down to the nearest multiple of @a base. template inline Type RoundDown(Type x, Type base) { Type remainder = Remainder(x, base); return remainder ? x-remainder : x; } //@{ /// Return @a x rounded to the nearest integer. inline float Round(float x) { return RoundDown(x + 0.5f); } inline double Round(double x) { return RoundDown(x + 0.5); } inline long double Round(long double x) { return RoundDown(x + 0.5l); } //@} /// Return the euclidean remainder of @a x. /// Note unlike % operator this will always return a positive result template inline Type EuclideanRemainder(Type x) { return x - RoundDown(x); } /// Return the integer part of @a x. template inline Type IntegerPart(Type x) { return (x > 0 ? RoundDown(x) : RoundUp(x)); } /// Return the fractional part of @a x. template inline Type FractionalPart(Type x) { return Mod(x,Type(1)); } //@{ /// Return the floor of @a x. inline int Floor(float x) { return int(RoundDown(x)); } inline int Floor(double x) { return int(RoundDown(x)); } inline int Floor(long double x) { return int(RoundDown(x)); } //@} //@{ /// Return the ceiling of @a x. inline int Ceil(float x) { return int(RoundUp(x)); } inline int Ceil(double x) { return int(RoundUp(x)); } inline int Ceil(long double x) { return int(RoundUp(x)); } //@} /// Return @a x if it is greater 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 than the /// third component, the largest index of the two is always returned. /// If all three vector components are equal the largest index, i.e. 2, is /// returned. In other words the return value corresponds to the largest index /// of the of the smallest vector components. template size_t MinIndex(const Vec3T& v) { #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif const size_t hashTable[8] = { 2, 1, 9, 1, 2, 9, 0, 0 };//9 is a dummy value const size_t hashKey = ((v[0] < v[1]) << 2) + ((v[0] < v[2]) << 1) + (v[1] < v[2]);// ?*4+?*2+?*1 return hashTable[hashKey]; } /// @brief Return the index [0,1,2] of the largest value in a 3D vector. /// @note This methods assumes operator[] exists and avoids branching. /// @details If two components of the input vector are equal and larger than the /// third component, the largest index of the two is always returned. /// If all three vector components are equal the largest index, i.e. 2, is /// returned. In other words the return value corresponds to the largest index /// of the largest vector components. template size_t MaxIndex(const Vec3T& v) { #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif const size_t hashTable[8] = { 2, 1, 9, 1, 2, 9, 0, 0 };//9 is a dummy value const size_t hashKey = ((v[0] > v[1]) << 2) + ((v[0] > v[2]) << 1) + (v[1] > v[2]);// ?*4+?*2+?*1 return hashTable[hashKey]; } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_MATH_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/LegacyFrustum.h0000644000000000000000000001675212603226506015122 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Ray.h0000644000000000000000000003251612603226506013057 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 than t0 by at least eps. inline bool valid(RealT eps=math::Delta::value()) const { return (t1-t0)>eps; } /// @brief Return the midpoint of the ray. inline RealT mid() const { return 0.5*(t0 + t1); } /// @brief Multiplies both times inline void scale(RealT s) {assert(s>0); t0*=s; t1*=s; } /// @brief Return @c true if time is inclusive inline bool test(RealT t) const { return (t>=t0 && t<=t1); } }; Ray(const Vec3Type& eye = Vec3Type(0,0,0), const Vec3Type& direction = Vec3Type(1,0,0), RealT t0 = math::Delta::value(), RealT t1 = std::numeric_limits::max()) : mEye(eye), mDir(direction), mInvDir(1/mDir), mTimeSpan(t0, t1) { } inline void setEye(const Vec3Type& eye) { mEye = eye; } inline void setDir(const Vec3Type& dir) { mDir = dir; mInvDir = 1/mDir; } inline void setMinTime(RealT t0) { assert(t0>0); mTimeSpan.t0 = t0; } inline void setMaxTime(RealT t1) { assert(t1>0); mTimeSpan.t1 = t1; } inline void setTimes(RealT t0 = math::Delta::value(), RealT t1 = std::numeric_limits::max()) { assert(t0>0 && t1>0); mTimeSpan.set(t0, t1); } inline void scaleTimes(RealT scale) { mTimeSpan.scale(scale); } inline void reset(const Vec3Type& eye, const Vec3Type& direction, RealT t0 = math::Delta::value(), RealT t1 = std::numeric_limits::max()) { this->setEye(eye); this->setDir(direction); this->setTimes(t0, t1); } inline const Vec3T& eye() const {return mEye;} inline const Vec3T& dir() const {return mDir;} inline const Vec3T& invDir() const {return mInvDir;} inline RealT t0() const {return mTimeSpan.t0;} inline RealT t1() const {return mTimeSpan.t1;} /// @brief Return the position along the ray at the specified time. inline Vec3R operator()(RealT time) const { return mEye + mDir * time; } /// @brief Return the starting point of the ray. inline Vec3R start() const { return (*this)(mTimeSpan.t0); } /// @brief Return the endpoint of the ray. inline Vec3R end() const { return (*this)(mTimeSpan.t1); } /// @brief Return the midpoint of the ray. inline Vec3R mid() const { return (*this)(mTimeSpan.mid()); } /// @brief Return @c true if t0 is strictly less than t1. OPENVDB_DEPRECATED inline bool test() const { return mTimeSpan.valid(RealT(0)); } /// @brief Return @c true if t1 is larger than t0 by at least eps. inline bool valid(RealT eps=math::Delta::value()) const { return mTimeSpan.valid(eps); } /// @brief Return @c true if @a time is within t0 and t1, both inclusive. inline bool test(RealT time) const { return mTimeSpan.test(time); } /// @brief Return a new Ray that is transformed with the specified map. /// @param map the map from which to construct the new Ray. /// @warning Assumes a linear map and a normalized direction. /// @details The requirement that the direction is normalized /// follows from the transformation of t0 and t1 - and that fact that /// we want applyMap and applyInverseMap to be inverse operations. template inline Ray applyMap(const MapType& map) const { assert(map.isLinear()); assert(math::isRelOrApproxEqual(mDir.length(), RealT(1), Tolerance::value(), Delta::value())); const Vec3T eye = map.applyMap(mEye); const Vec3T dir = map.applyJacobian(mDir); const RealT length = dir.length(); return Ray(eye, dir/length, length*mTimeSpan.t0, length*mTimeSpan.t1); } /// @brief Return a new Ray that is transformed with the inverse of the specified map. /// @param map the map from which to construct the new Ray by inverse mapping. /// @warning Assumes a linear map and a normalized direction. /// @details The requirement that the direction is normalized /// follows from the transformation of t0 and t1 - and that fact that /// we want applyMap and applyInverseMap to be inverse operations. template inline Ray applyInverseMap(const MapType& map) const { assert(map.isLinear()); assert(math::isRelOrApproxEqual(mDir.length(), RealT(1), Tolerance::value(), Delta::value())); const Vec3T eye = map.applyInverseMap(mEye); const Vec3T dir = map.applyInverseJacobian(mDir); const RealT length = dir.length(); return Ray(eye, dir/length, length*mTimeSpan.t0, length*mTimeSpan.t1); } /// @brief Return a new ray in world space, assuming the existing /// ray is represented in the index space of the specified grid. template inline Ray indexToWorld(const GridType& grid) const { return this->applyMap(*(grid.transform().baseMap())); } /// @brief Return a new ray in the index space of the specified /// grid, assuming the existing ray is represented in world space. template inline Ray worldToIndex(const GridType& grid) const { return this->applyInverseMap(*(grid.transform().baseMap())); } /// @brief Return true if this ray intersects the specified sphere. /// @param center The center of the sphere in the same space as this ray. /// @param radius The radius of the sphere in the same units as this ray. /// @param t0 The first intersection point if an intersection exists. /// @param t1 The second intersection point if an intersection exists. /// @note If the return value is true, i.e. a hit, and t0 = /// this->t0() or t1 == this->t1() only one true intersection exist. inline bool intersects(const Vec3T& center, RealT radius, RealT& t0, RealT& t1) const { const Vec3T origin = mEye - center; const RealT A = mDir.lengthSqr(); const RealT B = 2 * mDir.dot(origin); const RealT C = origin.lengthSqr() - radius * radius; const RealT D = B * B - 4 * A * C; if (D < 0) return false; const RealT Q = RealT(-0.5)*(B<0 ? (B + Sqrt(D)) : (B - Sqrt(D))); t0 = Q / A; t1 = C / Q; if (t0 > t1) std::swap(t0, t1); if (t0 < mTimeSpan.t0) t0 = mTimeSpan.t0; if (t1 > mTimeSpan.t1) t1 = mTimeSpan.t1; return t0 <= t1; } /// @brief Return true if this ray intersects the specified sphere. /// @param center The center of the sphere in the same space as this ray. /// @param radius The radius of the sphere in the same units as this ray. inline bool intersects(const Vec3T& center, RealT radius) const { RealT t0, t1; return this->intersects(center, radius, t0, t1)>0; } /// @brief Return true if this ray intersects the specified sphere. /// @note For intersection this ray is clipped to the two intersection points. /// @param center The center of the sphere in the same space as this ray. /// @param radius The radius of the sphere in the same units as this ray. inline bool clip(const Vec3T& center, RealT radius) { RealT t0, t1; const bool hit = this->intersects(center, radius, t0, t1); if (hit) mTimeSpan.set(t0, t1); return hit; } /// @brief Return true if the Ray intersects the specified /// axisaligned bounding box. /// @param bbox Axis-aligned bounding box in the same space as the Ray. /// @param t0 If an intersection is detected this is assigned /// the time for the first intersection point. /// @param t1 If an intersection is detected this is assigned /// the time for the second intersection point. template inline bool intersects(const BBoxT& bbox, RealT& t0, RealT& t1) const { mTimeSpan.get(t0, t1); for (int i = 0; i < 3; ++i) { RealT a = (bbox.min()[i] - mEye[i]) * mInvDir[i]; RealT b = (bbox.max()[i] - mEye[i]) * mInvDir[i]; if (a > b) std::swap(a, b); if (a > t0) t0 = a; if (b < t1) t1 = b; if (t0 > t1) return false; } return true; } /// @brief Return true if this ray intersects the specified bounding box. /// @param bbox Axis-aligned bounding box in the same space as this ray. template inline bool intersects(const BBoxT& bbox) const { RealT t0, t1; return this->intersects(bbox, t0, t1); } /// @brief Return true if this ray intersects the specified bounding box. /// @note For intersection this ray is clipped to the two intersection points. /// @param bbox Axis-aligned bounding box in the same space as this ray. template inline bool clip(const BBoxT& bbox) { RealT t0, t1; const bool hit = this->intersects(bbox, t0, t1); if (hit) mTimeSpan.set(t0, t1); return hit; } /// @brief Return true if the Ray intersects the plane specified /// by a normal and distance from the origin. /// @param normal Normal of the plane. /// @param distance Distance of the plane to the origin. /// @param t Time of intersection, if one exists. inline bool intersects(const Vec3T& normal, RealT distance, RealT& t) const { const RealT cosAngle = mDir.dot(normal); if (math::isApproxZero(cosAngle)) return false;//parallel t = (distance - mEye.dot(normal))/cosAngle; return this->test(t); } /// @brief Return true if the Ray intersects the plane specified /// by a normal and point. /// @param normal Normal of the plane. /// @param point Point in the plane. /// @param t Time of intersection, if one exists. inline bool intersects(const Vec3T& normal, const Vec3T& point, RealT& t) const { return this->intersects(normal, point.dot(normal), t); } private: Vec3T mEye, mDir, mInvDir; TimeSpan mTimeSpan; }; // end of Ray class /// @brief Output streaming of the Ray class. /// @note Primarily intended for debugging. template inline std::ostream& operator<<(std::ostream& os, const Ray& r) { os << "eye=" << r.eye() << " dir=" << r.dir() << " 1/dir="< #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 = T(sin(angle*T(0.5))); mm[0] = axis.x() * s; mm[1] = axis.y() * s; mm[2] = axis.z() * s; mm[3] = T(cos(angle*T(0.5))); } /// Constructor given rotation as axis and angle Quat(math::Axis axis, T angle) { T s = T(sin(angle*T(0.5))); mm[0] = (axis==math::X_AXIS) * s; mm[1] = (axis==math::Y_AXIS) * s; mm[2] = (axis==math::Z_AXIS) * s; mm[3] = T(cos(angle*T(0.5))); } /// Constructor given a rotation matrix template Quat(const Mat3 &rot) { // verify that the matrix is really a rotation if(!isUnitary(rot)) { // unitary is reflection or rotation OPENVDB_THROW(ArithmeticError, "A non-rotation matrix can not be used to construct a quaternion"); } if (!isApproxEqual(rot.det(), (T1)1)) { // rule out reflection OPENVDB_THROW(ArithmeticError, "A reflection matrix can not be used to construct a quaternion"); } T trace = (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(T(2.0) * acos(mm[3])); } else { return T(0.0); } } /// Return axis of rotation Vec3 axis() const { T sqrLength = mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2]; if ( sqrLength > 1.0e-8 ) { T invLength = T(T(1)/sqrt(sqrLength)); return Vec3( mm[0]*invLength, mm[1]*invLength, mm[2]*invLength ); } else { return Vec3(1,0,0); } } /// "this" quaternion gets initialized to [x, y, z, w] Quat& init(T x, T y, T z, T w) { mm[0] = x; mm[1] = y; mm[2] = z; mm[3] = w; return *this; } /// "this" quaternion gets initialized to identity, same as setIdentity() Quat& init() { return setIdentity(); } /// Set "this" quaternion to rotation specified by axis and angle, /// the axis must be unit vector Quat& setAxisAngle(const Vec3& axis, T angle) { T s = T(sin(angle*T(0.5))); mm[0] = axis.x() * s; mm[1] = axis.y() * s; mm[2] = axis.z() * s; mm[3] = T(cos(angle*T(0.5))); return *this; } // axisAngleTest /// Set "this" vector to zero Quat& setZero() { mm[0] = mm[1] = mm[2] = mm[3] = 0; return *this; } /// Set "this" vector to identity Quat& setIdentity() { mm[0] = mm[1] = mm[2] = 0; mm[3] = 1; return *this; } /// Returns vector of x,y,z rotational components Vec3 eulerAngles(RotationOrder rotationOrder) const { return math::eulerAngles(Mat3(*this), rotationOrder); } /// Assignment operator Quat& operator=(const Quat &q) { mm[0] = q.mm[0]; mm[1] = q.mm[1]; mm[2] = q.mm[2]; mm[3] = q.mm[3]; return *this; } /// Equality operator, does exact floating point comparisons bool operator==(const Quat &q) const { return (isExactlyEqual(mm[0],q.mm[0]) && isExactlyEqual(mm[1],q.mm[1]) && isExactlyEqual(mm[2],q.mm[2]) && isExactlyEqual(mm[3],q.mm[3]) ); } /// Test if "this" is equivalent to q with tolerance of eps value bool eq(const Quat &q, T eps=1.0e-7) const { return isApproxEqual(mm[0],q.mm[0],eps) && isApproxEqual(mm[1],q.mm[1],eps) && isApproxEqual(mm[2],q.mm[2],eps) && isApproxEqual(mm[3],q.mm[3],eps) ; } // trivial /// Add quaternion q to "this" quaternion, e.g. q += q1; Quat& operator+=(const Quat &q) { mm[0] += q.mm[0]; mm[1] += q.mm[1]; mm[2] += q.mm[2]; mm[3] += q.mm[3]; return *this; } /// Subtract quaternion q from "this" quaternion, e.g. q -= q1; Quat& operator-=(const Quat &q) { mm[0] -= q.mm[0]; mm[1] -= q.mm[1]; mm[2] -= q.mm[2]; mm[3] -= q.mm[3]; return *this; } /// Scale "this" quaternion by scalar, e.g. q *= scalar; Quat& operator*=(T scalar) { mm[0] *= scalar; mm[1] *= scalar; mm[2] *= scalar; mm[3] *= scalar; return *this; } /// Return (this+q), e.g. q = q1 + q2; Quat operator+(const Quat &q) const { return Quat(mm[0]+q.mm[0], mm[1]+q.mm[1], mm[2]+q.mm[2], mm[3]+q.mm[3]); } /// Return (this-q), e.g. q = q1 - q2; Quat operator-(const Quat &q) const { return Quat(mm[0]-q.mm[0], mm[1]-q.mm[1], mm[2]-q.mm[2], mm[3]-q.mm[3]); } /// Return (this*q), e.g. q = q1 * q2; Quat operator*(const Quat &q) const { Quat prod; prod.mm[0] = mm[3]*q.mm[0] + mm[0]*q.mm[3] + mm[1]*q.mm[2] - mm[2]*q.mm[1]; prod.mm[1] = mm[3]*q.mm[1] + mm[1]*q.mm[3] + mm[2]*q.mm[0] - mm[0]*q.mm[2]; prod.mm[2] = mm[3]*q.mm[2] + mm[2]*q.mm[3] + mm[0]*q.mm[1] - mm[1]*q.mm[0]; prod.mm[3] = mm[3]*q.mm[3] - mm[0]*q.mm[0] - mm[1]*q.mm[1] - mm[2]*q.mm[2]; return prod; } /// Assigns this to (this*q), e.g. q *= q1; Quat operator*=(const Quat &q) { *this = *this * q; return *this; } /// Return (this*scalar), e.g. q = q1 * scalar; Quat operator*(T scalar) const { return Quat(mm[0]*scalar, mm[1]*scalar, mm[2]*scalar, mm[3]*scalar); } /// Return (this/scalar), e.g. q = q1 / scalar; Quat operator/(T scalar) const { return Quat(mm[0]/scalar, mm[1]/scalar, mm[2]/scalar, mm[3]/scalar); } /// Negation operator, e.g. q = -q; Quat operator-() const { return Quat(-mm[0], -mm[1], -mm[2], -mm[3]); } /// this = q1 + q2 /// "this", q1 and q2 need not be distinct objects, e.g. q.add(q1,q); Quat& add(const Quat &q1, const Quat &q2) { mm[0] = q1.mm[0] + q2.mm[0]; mm[1] = q1.mm[1] + q2.mm[1]; mm[2] = q1.mm[2] + q2.mm[2]; mm[3] = q1.mm[3] + q2.mm[3]; return *this; } /// this = q1 - q2 /// "this", q1 and q2 need not be distinct objects, e.g. q.sub(q1,q); Quat& sub(const Quat &q1, const Quat &q2) { mm[0] = q1.mm[0] - q2.mm[0]; mm[1] = q1.mm[1] - q2.mm[1]; mm[2] = q1.mm[2] - q2.mm[2]; mm[3] = q1.mm[3] - q2.mm[3]; return *this; } /// this = q1 * q2 /// q1 and q2 must be distinct objects than "this", e.g. q.mult(q1,q2); Quat& mult(const Quat &q1, const Quat &q2) { mm[0] = q1.mm[3]*q2.mm[0] + q1.mm[0]*q2.mm[3] + q1.mm[1]*q2.mm[2] - q1.mm[2]*q2.mm[1]; mm[1] = q1.mm[3]*q2.mm[1] + q1.mm[1]*q2.mm[3] + q1.mm[2]*q2.mm[0] - q1.mm[0]*q2.mm[2]; mm[2] = q1.mm[3]*q2.mm[2] + q1.mm[2]*q2.mm[3] + q1.mm[0]*q2.mm[1] - q1.mm[1]*q2.mm[0]; mm[3] = q1.mm[3]*q2.mm[3] - q1.mm[0]*q2.mm[0] - q1.mm[1]*q2.mm[1] - q1.mm[2]*q2.mm[2]; return *this; } /// this = scalar*q, q need not be distinct object than "this", /// e.g. q.scale(1.5,q1); Quat& scale(T scale, const Quat &q) { mm[0] = scale * q.mm[0]; mm[1] = scale * q.mm[1]; mm[2] = scale * q.mm[2]; mm[3] = scale * q.mm[3]; return *this; } /// Dot product T dot(const Quat &q) const { return (mm[0]*q.mm[0] + mm[1]*q.mm[1] + mm[2]*q.mm[2] + mm[3]*q.mm[3]); } /// Return the quaternion rate corrsponding to the angular velocity omega /// and "this" current rotation Quat derivative(const Vec3& omega) const { return Quat( +w()*omega.x() -z()*omega.y() +y()*omega.z() , +z()*omega.x() +w()*omega.y() -x()*omega.z() , -y()*omega.x() +x()*omega.y() +w()*omega.z() , -x()*omega.x() -y()*omega.y() -z()*omega.z() ); } /// this = normalized this bool normalize(T eps = T(1.0e-8)) { T d = T(sqrt(mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2] + mm[3]*mm[3])); if( isApproxEqual(d, T(0.0), eps) ) return false; *this *= ( T(1)/d ); return true; } /// this = normalized this Quat unit() const { T d = sqrt(mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2] + mm[3]*mm[3]); if( isExactlyEqual(d , T(0.0) ) ) OPENVDB_THROW(ArithmeticError, "Normalizing degenerate quaternion"); return *this / d; } /// returns inverse of this Quat inverse(T tolerance = T(0)) { T d = mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2] + mm[3]*mm[3]; if( isApproxEqual(d, T(0.0), tolerance) ) OPENVDB_THROW(ArithmeticError, "Cannot invert degenerate quaternion"); Quat result = *this/-d; result.mm[3] = -result.mm[3]; return result; } /// Return the conjugate of "this", same as invert without /// unit quaternion test Quat conjugate() const { return Quat(-mm[0], -mm[1], -mm[2], mm[3]); } /// Return rotated vector by "this" quaternion Vec3 rotateVector(const Vec3 &v) const { Mat3 m(*this); return m.transform(v); } /// Predefined constants, e.g. Quat q = Quat::identity(); static Quat zero() { return Quat(0,0,0,0); } static Quat identity() { return Quat(0,0,0,1); } /// @return string representation of Classname std::string str() const { std::ostringstream buffer; buffer << "["; // For each column for (unsigned j(0); j < 4; j++) { if (j) buffer << ", "; buffer << mm[j]; } buffer << "]"; return buffer.str(); } /// Output to the stream, e.g. std::cout << q << std::endl; friend std::ostream& operator<<(std::ostream &stream, const Quat &q) { stream << q.str(); return stream; } friend Quat slerp<>(const Quat &q1, const Quat &q2, T t, T tolerance); void write(std::ostream& os) const { os.write((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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/FiniteDifference.h0000644000000000000000000025735112603226506015523 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file FiniteDifference.h #ifndef OPENVDB_MATH_FINITEDIFFERENCE_HAS_BEEN_INCLUDED #define OPENVDB_MATH_FINITEDIFFERENCE_HAS_BEEN_INCLUDED #include #include "Math.h" #include "Coord.h" #include "Vec3.h" #include #include #ifdef DWA_OPENVDB #include #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { //////////////////////////////////////// /// @brief Different discrete schemes used in the first derivatives. // Add new items to the *end* of this list, and update NUM_DS_SCHEMES. enum DScheme { UNKNOWN_DS = -1, CD_2NDT = 0, // center difference, 2nd order, but the result must be divided by 2 CD_2ND, // center difference, 2nd order CD_4TH, // center difference, 4th order CD_6TH, // center difference, 6th order FD_1ST, // forward difference, 1st order FD_2ND, // forward difference, 2nd order FD_3RD, // forward difference, 3rd order BD_1ST, // backward difference, 1st order BD_2ND, // backward difference, 2nd order BD_3RD, // backward difference, 3rd order FD_WENO5, // forward difference, weno5 BD_WENO5, // backward difference, weno5 FD_HJWENO5, // forward differene, HJ-weno5 BD_HJWENO5 // backward difference, HJ-weno5 }; enum { NUM_DS_SCHEMES = BD_HJWENO5 + 1 }; inline std::string dsSchemeToString(DScheme dss) { std::string ret; switch (dss) { case UNKNOWN_DS: ret = "unknown_ds"; break; case CD_2NDT: ret = "cd_2ndt"; break; case CD_2ND: ret = "cd_2nd"; break; case CD_4TH: ret = "cd_4th"; break; case CD_6TH: ret = "cd_6th"; break; case FD_1ST: ret = "fd_1st"; break; case FD_2ND: ret = "fd_2nd"; break; case FD_3RD: ret = "fd_3rd"; break; case BD_1ST: ret = "bd_1st"; break; case BD_2ND: ret = "bd_2nd"; break; case BD_3RD: ret = "bd_3rd"; break; case FD_WENO5: ret = "fd_weno5"; break; case BD_WENO5: ret = "bd_weno5"; break; case FD_HJWENO5: ret = "fd_hjweno5"; break; case BD_HJWENO5: ret = "bd_hjweno5"; break; } return ret; } inline DScheme stringToDScheme(const std::string& s) { DScheme ret = UNKNOWN_DS; std::string str = s; boost::trim(str); boost::to_lower(str); if (str == dsSchemeToString(CD_2NDT)) { ret = CD_2NDT; } else if (str == dsSchemeToString(CD_2ND)) { ret = CD_2ND; } else if (str == dsSchemeToString(CD_4TH)) { ret = CD_4TH; } else if (str == dsSchemeToString(CD_6TH)) { ret = CD_6TH; } else if (str == dsSchemeToString(FD_1ST)) { ret = FD_1ST; } else if (str == dsSchemeToString(FD_2ND)) { ret = FD_2ND; } else if (str == dsSchemeToString(FD_3RD)) { ret = FD_3RD; } else if (str == dsSchemeToString(BD_1ST)) { ret = BD_1ST; } else if (str == dsSchemeToString(BD_2ND)) { ret = BD_2ND; } else if (str == dsSchemeToString(BD_3RD)) { ret = BD_3RD; } else if (str == dsSchemeToString(FD_WENO5)) { ret = FD_WENO5; } else if (str == dsSchemeToString(BD_WENO5)) { ret = BD_WENO5; } else if (str == dsSchemeToString(FD_HJWENO5)) { ret = FD_HJWENO5; } else if (str == dsSchemeToString(BD_HJWENO5)) { ret = BD_HJWENO5; } return ret; } inline std::string dsSchemeToMenuName(DScheme dss) { std::string ret; switch (dss) { case UNKNOWN_DS: ret = "Unknown DS scheme"; break; case CD_2NDT: ret = "Twice 2nd-order center difference"; break; case CD_2ND: ret = "2nd-order center difference"; break; case CD_4TH: ret = "4th-order center difference"; break; case CD_6TH: ret = "6th-order center difference"; break; case FD_1ST: ret = "1st-order forward difference"; break; case FD_2ND: ret = "2nd-order forward difference"; break; case FD_3RD: ret = "3rd-order forward difference"; break; case BD_1ST: ret = "1st-order backward difference"; break; case BD_2ND: ret = "2nd-order backward difference"; break; case BD_3RD: ret = "3rd-order backward difference"; break; case FD_WENO5: ret = "5th-order WENO forward difference"; break; case BD_WENO5: ret = "5th-order WENO backward difference"; break; case FD_HJWENO5: ret = "5th-order HJ-WENO forward difference"; break; case BD_HJWENO5: ret = "5th-order HJ-WENO backward difference"; break; } return ret; } //////////////////////////////////////// /// @brief Different discrete schemes used in the second derivatives. // Add new items to the *end* of this list, and update NUM_DD_SCHEMES. enum DDScheme { UNKNOWN_DD = -1, CD_SECOND = 0, // center difference, 2nd order CD_FOURTH, // center difference, 4th order CD_SIXTH // center difference, 6th order }; enum { NUM_DD_SCHEMES = CD_SIXTH + 1 }; //////////////////////////////////////// /// @brief Biased Gradients are limited to non-centered differences // Add new items to the *end* of this list, and update NUM_BIAS_SCHEMES. enum BiasedGradientScheme { UNKNOWN_BIAS = -1, FIRST_BIAS = 0, // uses FD_1ST & BD_1ST SECOND_BIAS, // uses FD_2ND & BD_2ND THIRD_BIAS, // uses FD_3RD & BD_3RD WENO5_BIAS, // uses WENO5 HJWENO5_BIAS // uses HJWENO5 }; enum { NUM_BIAS_SCHEMES = HJWENO5_BIAS + 1 }; inline std::string biasedGradientSchemeToString(BiasedGradientScheme bgs) { std::string ret; switch (bgs) { case UNKNOWN_BIAS: ret = "unknown_bias"; break; case FIRST_BIAS: ret = "first_bias"; break; case SECOND_BIAS: ret = "second_bias"; break; case THIRD_BIAS: ret = "third_bias"; break; case WENO5_BIAS: ret = "weno5_bias"; break; case HJWENO5_BIAS: ret = "hjweno5_bias"; break; } return ret; } inline BiasedGradientScheme stringToBiasedGradientScheme(const std::string& s) { BiasedGradientScheme ret = UNKNOWN_BIAS; std::string str = s; boost::trim(str); boost::to_lower(str); if (str == biasedGradientSchemeToString(FIRST_BIAS)) { ret = FIRST_BIAS; } else if (str == biasedGradientSchemeToString(SECOND_BIAS)) { ret = SECOND_BIAS; } else if (str == biasedGradientSchemeToString(THIRD_BIAS)) { ret = THIRD_BIAS; } else if (str == biasedGradientSchemeToString(WENO5_BIAS)) { ret = WENO5_BIAS; } else if (str == biasedGradientSchemeToString(HJWENO5_BIAS)) { ret = HJWENO5_BIAS; } return ret; } inline std::string biasedGradientSchemeToMenuName(BiasedGradientScheme bgs) { std::string ret; switch (bgs) { case UNKNOWN_BIAS: ret = "Unknown biased gradient"; break; case FIRST_BIAS: ret = "1st-order biased gradient"; break; case SECOND_BIAS: ret = "2nd-order biased gradient"; break; case THIRD_BIAS: ret = "3rd-order biased gradient"; break; case WENO5_BIAS: ret = "5th-order WENO biased gradient"; break; case HJWENO5_BIAS: ret = "5th-order HJ-WENO biased gradient"; break; } return ret; } //////////////////////////////////////// /// @brief Temporal integration schemes // Add new items to the *end* of this list, and update NUM_TEMPORAL_SCHEMES. enum TemporalIntegrationScheme { UNKNOWN_TIS = -1, TVD_RK1,//same as explicit Euler integration TVD_RK2, TVD_RK3 }; enum { NUM_TEMPORAL_SCHEMES = TVD_RK3 + 1 }; inline std::string temporalIntegrationSchemeToString(TemporalIntegrationScheme tis) { std::string ret; switch (tis) { case UNKNOWN_TIS: ret = "unknown_tis"; break; case TVD_RK1: ret = "tvd_rk1"; break; case TVD_RK2: ret = "tvd_rk2"; break; case TVD_RK3: ret = "tvd_rk3"; break; } return ret; } inline TemporalIntegrationScheme stringToTemporalIntegrationScheme(const std::string& s) { TemporalIntegrationScheme ret = UNKNOWN_TIS; std::string str = s; boost::trim(str); boost::to_lower(str); if (str == temporalIntegrationSchemeToString(TVD_RK1)) { ret = TVD_RK1; } else if (str == temporalIntegrationSchemeToString(TVD_RK2)) { ret = TVD_RK2; } else if (str == temporalIntegrationSchemeToString(TVD_RK3)) { ret = TVD_RK3; } return ret; } inline std::string temporalIntegrationSchemeToMenuName(TemporalIntegrationScheme tis) { std::string ret; switch (tis) { case UNKNOWN_TIS: ret = "Unknown temporal integration"; break; case TVD_RK1: ret = "Forward Euler"; break; case TVD_RK2: ret = "2nd-order Runge-Kutta"; break; case TVD_RK3: ret = "3rd-order Runge-Kutta"; break; } return ret; } //@} /// @brief Implementation of nominally fifth-order finite-difference WENO /// @details This function returns the numerical flux. See "High Order Finite Difference and /// Finite Volume WENO Schemes and Discontinuous Galerkin Methods for CFD" - Chi-Wang Shu /// ICASE Report No 2001-11 (page 6). Also see ICASE No 97-65 for a more complete reference /// (Shu, 1997). /// Given v1 = f(x-2dx), v2 = f(x-dx), v3 = f(x), v4 = f(x+dx) and v5 = f(x+2dx), /// return an interpolated value f(x+dx/2) with the special property that /// ( f(x+dx/2) - f(x-dx/2) ) / dx = df/dx (x) + error, /// where the error is fifth-order in smooth regions: O(dx) <= error <=O(dx^5) template inline ValueType WENO5(const ValueType& v1, const ValueType& v2, const ValueType& v3, const ValueType& v4, const ValueType& v5, float scale2 = 0.01f) { const double C = 13.0 / 12.0; // WENO is formulated for non-dimensional equations, here the optional scale2 // is a reference value (squared) for the function being interpolated. For // example if 'v' is of order 1000, then scale2 = 10^6 is ok. But in practice // leave scale2 = 1. const double eps = 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 static_cast(static_cast( A1*(2.0*v1 - 7.0*v2 + 11.0*v3) + A2*(5.0*v3 - v2 + 2.0*v4) + A3*(2.0*v3 + 5.0*v4 - v5))/(6.0*(A1+A2+A3))); } template inline Real 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.f / 12.f), eps(1.0e-6f * scale2), two(2.0), three(3.0), four(4.0), five(5.0), fourth(0.25), A1 = F4(0.1f) / Pow2(C*Pow2(v1-two*v2+v3) + fourth*Pow2(v1-four*v2+three*v3) + eps), A2 = F4(0.6f) / Pow2(C*Pow2(v2-two*v3+v4) + fourth*Pow2(v2-v4) + eps), A3 = F4(0.3f) / Pow2(C*Pow2(v3-two*v4+v5) + fourth*Pow2(three*v3-four*v4+v5) + eps); return (A1 * (two * v1 - F4(7.0) * v2 + F4(11.0) * v3) + A2 * (five * v3 - v2 + two * v4) + A3 * (two * v3 + five * v4 - v5)) / (F4(6.0) * (A1 + A2 + A3)); } inline float simdSum(const simd::Float4& v) { // temp = { v3+v3, v2+v2, v1+v3, v0+v2 } __m128 temp = _mm_add_ps(v.base(), _mm_movehl_ps(v.base(), v.base())); // temp = { v3+v3, v2+v2, v1+v3, (v0+v2)+(v1+v3) } temp = _mm_add_ss(temp, _mm_shuffle_ps(temp, temp, 1)); return _mm_cvtss_f32(temp); } inline float 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 static_cast(xp3/3.0 - 1.5*xp2 + 3.0*xp1 - 11.0*xp0/6.0); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(3,0,0)), grid.getValue(ijk.offsetBy(2,0,0)), grid.getValue(ijk.offsetBy(1,0,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(0,3,0)), grid.getValue(ijk.offsetBy(0,2,0)), grid.getValue(ijk.offsetBy(0,1,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(0,0,3)), grid.getValue(ijk.offsetBy(0,0,2)), grid.getValue(ijk.offsetBy(0,0,1)), grid.getValue(ijk) ); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference(S.template getValue< 3, 0, 0>(), S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference(S.template getValue< 0, 3, 0>(), S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0, 3>(), S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>() ); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xm1, const ValueType& xm0) { return -D1::difference(xm1, xm0); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(-1,0,0)), grid.getValue(ijk)); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0,-1,0)), grid.getValue(ijk)); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0, 0,-1)), grid.getValue(ijk)); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference(S.template getValue<-1, 0, 0>(), S.template getValue< 0, 0, 0>()); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference(S.template getValue< 0,-1, 0>(), S.template getValue< 0, 0, 0>()); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference(S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0, 0>()); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xm2, const ValueType& xm1, const ValueType& xm0) { return -D1::difference(xm2, xm1, xm0); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(-2,0,0)), grid.getValue(ijk.offsetBy(-1,0,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(0,-2,0)), grid.getValue(ijk.offsetBy(0,-1,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(0,0,-2)), grid.getValue(ijk.offsetBy(0,0,-1)), grid.getValue(ijk) ); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue<-2, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0,-2, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0,-2>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0, 0>() ); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xm3, const ValueType& xm2, const ValueType& xm1, const ValueType& xm0) { return -D1::difference(xm3, xm2, xm1, xm0); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(-3,0,0)), grid.getValue(ijk.offsetBy(-2,0,0)), grid.getValue(ijk.offsetBy(-1,0,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 0,-3,0)), grid.getValue(ijk.offsetBy( 0,-2,0)), grid.getValue(ijk.offsetBy( 0,-1,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 0, 0,-3)), grid.getValue(ijk.offsetBy( 0, 0,-2)), grid.getValue(ijk.offsetBy( 0, 0,-1)), grid.getValue(ijk) ); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue<-3, 0, 0>(), S.template getValue<-2, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0,-3, 0>(), S.template getValue< 0,-2, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0,-3>(), S.template getValue< 0, 0,-2>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0, 0>() ); } }; template<> struct D1 { // the difference operator template static ValueType difference(const ValueType& xp3, const ValueType& xp2, const ValueType& xp1, const ValueType& xp0, const ValueType& xm1, const ValueType& xm2) { return WENO5(xp3, xp2, xp1, xp0, xm1) - WENO5(xp2, xp1, xp0, xm1, xm2); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { 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 static_cast(difference( S.template getValue< 3, 0, 0>(), S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue<-2, 0, 0>() )); } template static typename Stencil::ValueType inY(const Stencil& S) { return static_cast(difference( S.template getValue< 0, 3, 0>(), S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0,-2, 0>() )); } template static typename Stencil::ValueType inZ(const Stencil& S) { return static_cast(difference( S.template getValue< 0, 0, 3>(), S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0,-2>() )); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xp3, const ValueType& xp2, const ValueType& xp1, const ValueType& xp0, const ValueType& xm1, const ValueType& xm2) { return WENO5(xp3 - xp2, xp2 - xp1, xp1 - xp0, xp0-xm1, xm1-xm2); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { 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) { typedef typename Accessor::ValueType ValueT; ValueT tmp1 = D1::inX(grid, ijk.offsetBy(0, 1, 0)) - D1::inX(grid, ijk.offsetBy(0,-1, 0)); ValueT tmp2 = D1::inX(grid, ijk.offsetBy(0, 2, 0)) - D1::inX(grid, ijk.offsetBy(0,-2, 0)); ValueT tmp3 = D1::inX(grid, ijk.offsetBy(0, 3, 0)) - D1::inX(grid, ijk.offsetBy(0,-3, 0)); return ValueT(0.75*tmp1 - 0.15*tmp2 + 1./60*tmp3); } template static typename Accessor::ValueType inXandZ(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueT; ValueT tmp1 = D1::inX(grid, ijk.offsetBy(0, 0, 1)) - D1::inX(grid, ijk.offsetBy(0, 0,-1)); ValueT tmp2 = D1::inX(grid, ijk.offsetBy(0, 0, 2)) - D1::inX(grid, ijk.offsetBy(0, 0,-2)); ValueT tmp3 = D1::inX(grid, ijk.offsetBy(0, 0, 3)) - D1::inX(grid, ijk.offsetBy(0, 0,-3)); return ValueT(0.75*tmp1 - 0.15*tmp2 + 1./60*tmp3); } template static typename Accessor::ValueType inYandZ(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueT; ValueT tmp1 = D1::inY(grid, ijk.offsetBy(0, 0, 1)) - D1::inY(grid, ijk.offsetBy(0, 0,-1)); ValueT tmp2 = D1::inY(grid, ijk.offsetBy(0, 0, 2)) - D1::inY(grid, ijk.offsetBy(0, 0,-2)); ValueT tmp3 = D1::inY(grid, ijk.offsetBy(0, 0, 3)) - D1::inY(grid, ijk.offsetBy(0, 0,-3)); return ValueT(0.75*tmp1 - 0.15*tmp2 + 1./60*tmp3); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue< 3, 0, 0>(), S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue<-2, 0, 0>(), S.template getValue<-3, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0, 3, 0>(), S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0,-2, 0>(), S.template getValue< 0,-3, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0, 3>(), S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0,-2>(), S.template getValue< 0, 0,-3>() ); } template static typename Stencil::ValueType inXandY(const Stencil& S) { return crossdifference( S.template getValue< 1, 1, 0>(), S.template getValue<-1, 1, 0>(), S.template getValue< 1,-1, 0>(), S.template getValue<-1,-1, 0>(), S.template getValue< 2, 1, 0>(), S.template getValue<-2, 1, 0>(), S.template getValue< 2,-1, 0>(), S.template getValue<-2,-1, 0>(), S.template getValue< 3, 1, 0>(), S.template getValue<-3, 1, 0>(), S.template getValue< 3,-1, 0>(), S.template getValue<-3,-1, 0>(), S.template getValue< 1, 2, 0>(), S.template getValue<-1, 2, 0>(), S.template getValue< 1,-2, 0>(), S.template getValue<-1,-2, 0>(), S.template getValue< 2, 2, 0>(), S.template getValue<-2, 2, 0>(), S.template getValue< 2,-2, 0>(), S.template getValue<-2,-2, 0>(), S.template getValue< 3, 2, 0>(), S.template getValue<-3, 2, 0>(), S.template getValue< 3,-2, 0>(), S.template getValue<-3,-2, 0>(), S.template getValue< 1, 3, 0>(), S.template getValue<-1, 3, 0>(), S.template getValue< 1,-3, 0>(), S.template getValue<-1,-3, 0>(), S.template getValue< 2, 3, 0>(), S.template getValue<-2, 3, 0>(), S.template getValue< 2,-3, 0>(), S.template getValue<-2,-3, 0>(), S.template getValue< 3, 3, 0>(), S.template getValue<-3, 3, 0>(), S.template getValue< 3,-3, 0>(), S.template getValue<-3,-3, 0>() ); } template static typename Stencil::ValueType inXandZ(const Stencil& S) { return crossdifference( S.template getValue< 1, 0, 1>(), S.template getValue<-1, 0, 1>(), S.template getValue< 1, 0,-1>(), S.template getValue<-1, 0,-1>(), S.template getValue< 2, 0, 1>(), S.template getValue<-2, 0, 1>(), S.template getValue< 2, 0,-1>(), S.template getValue<-2, 0,-1>(), S.template getValue< 3, 0, 1>(), S.template getValue<-3, 0, 1>(), S.template getValue< 3, 0,-1>(), S.template getValue<-3, 0,-1>(), S.template getValue< 1, 0, 2>(), S.template getValue<-1, 0, 2>(), S.template getValue< 1, 0,-2>(), S.template getValue<-1, 0,-2>(), S.template getValue< 2, 0, 2>(), S.template getValue<-2, 0, 2>(), S.template getValue< 2, 0,-2>(), S.template getValue<-2, 0,-2>(), S.template getValue< 3, 0, 2>(), S.template getValue<-3, 0, 2>(), S.template getValue< 3, 0,-2>(), S.template getValue<-3, 0,-2>(), S.template getValue< 1, 0, 3>(), S.template getValue<-1, 0, 3>(), S.template getValue< 1, 0,-3>(), S.template getValue<-1, 0,-3>(), S.template getValue< 2, 0, 3>(), S.template getValue<-2, 0, 3>(), S.template getValue< 2, 0,-3>(), S.template getValue<-2, 0,-3>(), S.template getValue< 3, 0, 3>(), S.template getValue<-3, 0, 3>(), S.template getValue< 3, 0,-3>(), S.template getValue<-3, 0,-3>() ); } template static typename Stencil::ValueType inYandZ(const Stencil& S) { return crossdifference( S.template getValue< 0, 1, 1>(), S.template getValue< 0,-1, 1>(), S.template getValue< 0, 1,-1>(), S.template getValue< 0,-1,-1>(), S.template getValue< 0, 2, 1>(), S.template getValue< 0,-2, 1>(), S.template getValue< 0, 2,-1>(), S.template getValue< 0,-2,-1>(), S.template getValue< 0, 3, 1>(), S.template getValue< 0,-3, 1>(), S.template getValue< 0, 3,-1>(), S.template getValue< 0,-3,-1>(), S.template getValue< 0, 1, 2>(), S.template getValue< 0,-1, 2>(), S.template getValue< 0, 1,-2>(), S.template getValue< 0,-1,-2>(), S.template getValue< 0, 2, 2>(), S.template getValue< 0,-2, 2>(), S.template getValue< 0, 2,-2>(), S.template getValue< 0,-2,-2>(), S.template getValue< 0, 3, 2>(), S.template getValue< 0,-3, 2>(), S.template getValue< 0, 3,-2>(), S.template getValue< 0,-3,-2>(), S.template getValue< 0, 1, 3>(), S.template getValue< 0,-1, 3>(), S.template getValue< 0, 1,-3>(), S.template getValue< 0,-1,-3>(), S.template getValue< 0, 2, 3>(), S.template getValue< 0,-2, 3>(), S.template getValue< 0, 2,-3>(), S.template getValue< 0,-2,-3>(), S.template getValue< 0, 3, 3>(), S.template getValue< 0,-3, 3>(), S.template getValue< 0, 3,-3>(), S.template getValue< 0,-3,-3>() ); } }; } // end math namespace } // namespace OPENVDB_VERSION_NAME } // end openvdb namespace #endif // OPENVDB_MATH_FINITEDIFFERENCE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Stats.h0000644000000000000000000002771212603226506013424 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Stats.h /// /// @author Ken Museth /// /// @brief Classes to compute statistics and histograms #ifndef OPENVDB_MATH_STATS_HAS_BEEN_INCLUDED #define OPENVDB_MATH_STATS_HAS_BEEN_INCLUDED #include // for ostringstream #include #include #include #include #include #include "Math.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @brief This class computes the minimum and maximum values of a population /// of floating-point values. class Extrema { public: /// @brief Constructor /// @warning The min/max values are initiated to extreme values Extrema() : mSize(0) , mMin(std::numeric_limits::max()) , mMax(-mMin) { } /// Add a single sample. void add(double val) { ++mSize; mMin = std::min(val, mMin); mMax = std::max(val, mMax); } /// Add @a n samples with constant value @a val. void add(double val, uint64_t n) { mSize += n; mMin = std::min(val, mMin); mMax = std::max(val, mMax); } /// Return the size of the population, i.e., the total number of samples. inline uint64_t size() const { return mSize; } /// Return the minimum value. inline double min() const { return mMin; } /// Return the maximum value. inline double max() const { return mMax; } /// Return the range defined as the maximum value minus the minimum value. inline double range() const { return mMax - mMin; } /// Add the samples from the other Stats instance. void add(const Extrema& other) { if (other.mSize > 0) this->join(other); } /// @brief Print extrema to the specified output stream. void print(const std::string &name= "", std::ostream &strm=std::cout, int precision=3) const { // Write to a temporary string stream so as not to affect the state // (precision, field width, etc.) of the output stream. std::ostringstream os; os << std::setprecision(precision) << std::setiosflags(std::ios::fixed); os << "Extrema "; if (!name.empty()) os << "for \"" << name << "\" "; if (mSize>0) { os << "with " << mSize << " samples:\n" << " Min=" << mMin << ", Max=" << mMax << ", Range="<< this->range() << std::endl; } else { os << ": no samples were added." << std::endl; } strm << os.str(); } protected: inline void join(const Extrema& other) { assert(other.mSize > 0); mSize += other.mSize; mMin = std::min(mMin, other.mMin); mMax = std::max(mMax, other.mMax); } uint64_t mSize; double mMin, mMax; };//end Extrema /// @brief This class computes statistics (minimum value, maximum /// value, mean, variance and standard deviation) of a population /// of floating-point values. /// /// @details variance = Mean[ (X-Mean[X])^2 ] = Mean[X^2] - Mean[X]^2, /// standard deviation = sqrt(variance) /// /// @note This class employs incremental computation and double precision. class Stats : public Extrema { public: Stats() : Extrema() , mAvg(0.0) , mAux(0.0) { } /// Add a single sample. void add(double val) { Extrema::add(val); const double delta = val - mAvg; mAvg += delta/double(mSize); mAux += delta*(val - mAvg); } /// Add @a n samples with constant value @a val. void add(double val, uint64_t n) { const double denom = 1.0/double(mSize + n); const double delta = val - mAvg; mAvg += denom * delta * double(n); mAux += denom * delta * delta * double(mSize) * double(n); Extrema::add(val, n); } /// Add the samples from the other Stats instance. void add(const Stats& other) { if (other.mSize > 0) { const double denom = 1.0/double(mSize + other.mSize); const double delta = other.mAvg - mAvg; mAvg += denom * delta * double(other.mSize); mAux += other.mAux + denom * delta * delta * double(mSize) * double(other.mSize); Extrema::join(other); } } //@{ /// Return the arithmetic mean, i.e. average, value. inline double avg() const { return mAvg; } inline double mean() const { return mAvg; } //@} //@{ /// @brief Return the population variance. /// @note The unbiased sample variance = population variance * //num/(num-1) inline double var() const { return mSize<2 ? 0.0 : mAux/double(mSize); } inline double variance() const { return this->var(); } //@} //@{ /// @brief Return the standard deviation (=Sqrt(variance)) as /// defined from the (biased) population variance. inline double std() const { return sqrt(this->var()); } inline double stdDev() const { return this->std(); } //@} /// @brief Print statistics to the specified output stream. void print(const std::string &name= "", std::ostream &strm=std::cout, int precision=3) const { // Write to a temporary string stream so as not to affect the state // (precision, field width, etc.) of the output stream. std::ostringstream os; os << std::setprecision(precision) << std::setiosflags(std::ios::fixed); os << "Statistics "; if (!name.empty()) os << "for \"" << name << "\" "; if (mSize>0) { os << "with " << mSize << " samples:\n" << " Min=" << mMin << ", Max=" << mMax << ", Ave=" << mAvg << ", Std=" << this->stdDev() << ", Var=" << this->variance() << std::endl; } else { os << ": no samples were added." << std::endl; } strm << os.str(); } protected: using Extrema::mSize; using Extrema::mMin; using Extrema::mMax; double mAvg, mAux; }; // end Stats //////////////////////////////////////// /// @brief This class computes a histogram, with a fixed interval width, /// of a population of floating-point values. class Histogram { public: /// Construct with given minimum and maximum values and the given bin count. Histogram(double min, double max, size_t numBins = 10) : mSize(0), mMin(min), mMax(max+1e-10), mDelta(double(numBins)/(max-min)), mBins(numBins) { 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 (int i = 0, e = int(mBins.size()); i != e; ++i) { os << "|| " << std::setw(4) << i << " | " << std::setw(14) << this->min(i) << " | " << std::setw(14) << this->max(i) << " | " << std::setw(9) << mBins[i] << " | " << std::setw(3) << (100*mBins[i]/mSize) << " ||\n"; } os << "==============================================================\n"; } else { os << ": no samples were added." << std::endl; } strm << os.str(); } private: uint64_t mSize; double mMin, mMax, mDelta; std::vector mBins; }; } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_STATS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Vec2.h0000644000000000000000000004012112603226506013112 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 two arguments, e.g. Vec2f v(1,2,3); Vec2(T x, T y) { this->mm[0] = x; this->mm[1] = y; } /// Constructor with array argument, e.g. float a[2]; Vec2f v(a); template Vec2(Source *a) { this->mm[0] = a[0]; this->mm[1] = a[1]; } // trivial /// Conversion constructor template explicit Vec2(const Tuple<2, Source> &t) { this->mm[0] = static_cast(t[0]); this->mm[1] = static_cast(t[1]); } /// 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()); } template inline bool isFinite(const Vec2& v) { return isFinite(v[0]) && isFinite(v[1]); } template inline Vec2 Abs(const Vec2& v) { return Vec2(Abs(v[0]), Abs(v[1])); } /// Orthonormalize vectors v1 and v2 and store back the resulting basis /// e.g. Vec2f::orthonormalize(v1,v2); template inline void orthonormalize(Vec2 &v1, Vec2 &v2) { // If the input vectors are v0, v1, and v2, then the Gram-Schmidt // orthonormalization produces vectors u0, u1, and u2 as follows, // // u0 = v0/|v0| // u1 = (v1-(u0*v1)u0)/|v1-(u0*v1)u0| // // where |A| indicates length of vector A and A*B indicates dot // product of vectors A and B. // compute u0 v1.normalize(); // compute u1 T d0 = v1.dot(v2); v2 -= v1*d0; v2.normalize(); } /// \remark We are switching to a more explicit name because the semantics /// are different from std::min/max. In that case, the function returns a /// reference to one of the objects based on a comparator. Here, we must /// fabricate a new object which might not match either of the inputs. /// Return component-wise minimum of the two vectors. template inline Vec2 minComponent(const Vec2 &v1, const Vec2 &v2) { return Vec2( std::min(v1.x(), v2.x()), std::min(v1.y(), v2.y())); } /// Return component-wise maximum of the two vectors. template inline Vec2 maxComponent(const Vec2 &v1, const Vec2 &v2) { return Vec2( std::max(v1.x(), v2.x()), std::max(v1.y(), v2.y())); } /// @brief Return a vector with the exponent applied to each of /// the components of the input vector. template inline Vec2 Exp(Vec2 v) { return v.exp(); } 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Stencils.h0000644000000000000000000023644212603226506014114 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth /// /// @file Stencils.h /// /// @brief Defines various finite difference stencils by means of the /// "curiously recurring template pattern" on a BaseStencil /// that caches stencil values and stores a ValueAccessor for /// fast lookup. #ifndef OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED #define OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED #include #include #include // for Pow2, needed by WENO and Gudonov #include // for Real #include // for Coord #include // for WENO5 and GudonovsNormSqrd #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { //////////////////////////////////////// template class BaseStencil { public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridT::ValueType ValueType; typedef tree::ValueAccessor AccessorType; typedef std::vector BufferType; typedef typename BufferType::iterator IterType; /// @brief Initialize the stencil buffer with the values of voxel (i, j, k) /// and its neighbors. /// @param ijk Index coordinates of stencil center inline void moveTo(const Coord& ijk) { mCenter = ijk; mStencil[0] = mCache.getValue(ijk); static_cast(*this).init(mCenter); } /// @brief Initialize the stencil buffer with the values of voxel (i, j, k) /// and its neighbors. The method also takes a value of the center /// element of the stencil, assuming it is already known. /// @param ijk Index coordinates of stnecil center /// @param centerValue Value of the center element of the stencil inline void moveTo(const Coord& ijk, const ValueType& centerValue) { mCenter = ijk; mStencil[0] = centerValue; static_cast(*this).init(mCenter); } /// @brief Initialize the stencil buffer with the values of voxel /// (x, y, z) and its neighbors. /// /// @note This version is slightly faster than the one above, since /// the center voxel's value is read directly from the iterator. template inline void moveTo(const IterType& iter) { mCenter = iter.getCoord(); mStencil[0] = *iter; static_cast(*this).init(mCenter); } /// @brief Initialize the stencil buffer with the values of voxel (x, y, z) /// and its neighbors. /// @param xyz Floating point voxel coordinates of stencil center /// @details This method will check to see if it is necessary to /// update the stencil based on the cached index coordinates of /// the center point. 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 { BufferType tmp(mStencil);//local copy assert(!tmp.empty()); size_t midpoint = (tmp.size() - 1) >> 1; // Partially sort the vector until the median value is at the midpoint. std::nth_element(tmp.begin(), tmp.begin() + midpoint, tmp.end()); return tmp[midpoint]; } /// @brief Return the mean value of the current stencil. inline ValueType mean() const { ValueType sum = 0.0; for (int n = 0, s = int(mStencil.size()); n < s; ++n) sum += mStencil[n]; return sum / mStencil.size(); } /// @brief Return the smallest value in the stencil buffer. inline ValueType min() const { IterType iter = std::min_element(mStencil.begin(), mStencil.end()); return *iter; } /// @brief Return the largest value in the stencil buffer. inline ValueType max() const { IterType iter = std::max_element(mStencil.begin(), mStencil.end()); return *iter; } /// @brief Return the coordinates of the center point of the stencil. inline const Coord& getCenterCoord() const { return mCenter; } /// @brief Return the value at the center of the stencil inline const ValueType& getCenterValue() const { return mStencil[0]; } /// @brief Return true if the center of the stencil intersects the /// iso-contour specified by the isoValue inline bool intersects(const ValueType &isoValue = zeroVal()) const { const bool less = this->getValue< 0, 0, 0>() < isoValue; return (less ^ (this->getValue<-1, 0, 0>() < isoValue)) || (less ^ (this->getValue< 1, 0, 0>() < isoValue)) || (less ^ (this->getValue< 0,-1, 0>() < isoValue)) || (less ^ (this->getValue< 0, 1, 0>() < isoValue)) || (less ^ (this->getValue< 0, 0,-1>() < isoValue)) || (less ^ (this->getValue< 0, 0, 1>() < isoValue)) ; } /// @brief Return a const reference to the grid from which this /// stencil was constructed. inline const GridType& grid() const { return *mGrid; } /// @brief Return a const reference to the ValueAccessor /// associated with this Stencil. inline const AccessorType& accessor() const { return mCache; } protected: // Constructor is protected to prevent direct instantiation. BaseStencil(const GridType& grid, int size) : mGrid(&grid) , mCache(grid.tree()) , mStencil(size) , mCenter(Coord::max()) { } const GridType* mGrid; AccessorType mCache; BufferType mStencil; Coord mCenter; }; // BaseStencil class //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the seven point stencil template struct SevenPt {}; template<> struct SevenPt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct SevenPt< 1, 0, 0> { enum { idx = 1 }; }; template<> struct SevenPt< 0, 1, 0> { enum { idx = 2 }; }; template<> struct SevenPt< 0, 0, 1> { enum { idx = 3 }; }; template<> struct SevenPt<-1, 0, 0> { enum { idx = 4 }; }; template<> struct SevenPt< 0,-1, 0> { enum { idx = 5 }; }; template<> struct SevenPt< 0, 0,-1> { enum { idx = 6 }; }; } template class SevenPointStencil: public BaseStencil, GridT, IsSafe> { typedef SevenPointStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridT::ValueType ValueType; static const int SIZE = 7; SevenPointStencil(const GridT& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return SevenPt::idx; } private: inline void init(const Coord& ijk) { BaseType::template setValue<-1, 0, 0>(mCache.getValue(ijk.offsetBy(-1, 0, 0))); BaseType::template setValue< 1, 0, 0>(mCache.getValue(ijk.offsetBy( 1, 0, 0))); BaseType::template setValue< 0,-1, 0>(mCache.getValue(ijk.offsetBy( 0,-1, 0))); BaseType::template setValue< 0, 1, 0>(mCache.getValue(ijk.offsetBy( 0, 1, 0))); BaseType::template setValue< 0, 0,-1>(mCache.getValue(ijk.offsetBy( 0, 0,-1))); BaseType::template setValue< 0, 0, 1>(mCache.getValue(ijk.offsetBy( 0, 0, 1))); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; };// SevenPointStencil class //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the eight point box stencil template struct BoxPt {}; template<> struct BoxPt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct BoxPt< 0, 0, 1> { enum { idx = 1 }; }; template<> struct BoxPt< 0, 1, 1> { enum { idx = 2 }; }; template<> struct BoxPt< 0, 1, 0> { enum { idx = 3 }; }; template<> struct BoxPt< 1, 0, 0> { enum { idx = 4 }; }; template<> struct BoxPt< 1, 0, 1> { enum { idx = 5 }; }; template<> struct BoxPt< 1, 1, 1> { enum { idx = 6 }; }; template<> struct BoxPt< 1, 1, 0> { enum { idx = 7 }; }; } template class BoxStencil: public BaseStencil, GridT, IsSafe> { typedef BoxStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridT::ValueType ValueType; static const int SIZE = 8; BoxStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return BoxPt::idx; } /// @brief Return true if the center of the stencil intersects the /// iso-contour specified by the isoValue inline bool intersects(const ValueType &isoValue = zeroVal()) const { const bool less = mStencil[0] < isoValue; return (less ^ (mStencil[1] < isoValue)) || (less ^ (mStencil[2] < isoValue)) || (less ^ (mStencil[3] < isoValue)) || (less ^ (mStencil[4] < isoValue)) || (less ^ (mStencil[5] < isoValue)) || (less ^ (mStencil[6] < isoValue)) || (less ^ (mStencil[7] < isoValue)) ; } /// @brief Return the trilinear interpolation at the normalized position. /// @param xyz Floating point coordinate position. /// @warning It is assumed that the stencil has already been moved /// to the relevant voxel position, e.g. using moveTo(xyz). /// @note Trilinear interpolation kernal reads as: /// v000 (1-u)(1-v)(1-w) + v001 (1-u)(1-v)w + v010 (1-u)v(1-w) + v011 (1-u)vw /// + v100 u(1-v)(1-w) + v101 u(1-v)w + v110 uv(1-w) + v111 uvw inline ValueType interpolation(const math::Vec3& xyz) const { const 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 = static_cast(V + (BaseType::template getValue<0,0,1>() - V) * w); V = BaseType::template getValue< 0, 1, 0>(); ValueType B = static_cast(V + (BaseType::template getValue<0,1,1>() - V) * w); ValueType C = static_cast(A + (B - A) * v); V = BaseType::template getValue<1,0,0>(); A = static_cast(V + (BaseType::template getValue<1,0,1>() - V) * w); V = BaseType::template getValue<1,1,0>(); B = static_cast(V + (BaseType::template getValue<1,1,1>() - V) * w); ValueType D = static_cast(A + (B - A) * v); return static_cast(C + (D - C) * u); } /// @brief Return the gradient in world space of the trilinear interpolation kernel. /// @param xyz Floating point coordinate position. /// @warning It is assumed that the stencil has already been moved /// to the relevant voxel position, e.g. using moveTo(xyz). /// @note Computed as partial derivatives of the trilinear interpolation kernel: /// v000 (1-u)(1-v)(1-w) + v001 (1-u)(1-v)w + v010 (1-u)v(1-w) + v011 (1-u)vw /// + v100 u(1-v)(1-w) + v101 u(1-v)w + v110 uv(1-w) + v111 uvw inline math::Vec3 gradient(const math::Vec3& xyz) const { const 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 = static_cast(D[0] + (D[1]- D[0]) * v); ValueType B = static_cast(D[2] + (D[3]- D[2]) * v); math::Vec3 grad(zeroVal(), zeroVal(), static_cast(A + (B - A) * u)); D[0] = static_cast(BaseType::template getValue<0,0,0>() + D[0] * w); D[1] = static_cast(BaseType::template getValue<0,1,0>() + D[1] * w); D[2] = static_cast(BaseType::template getValue<1,0,0>() + D[2] * w); D[3] = static_cast(BaseType::template getValue<1,1,0>() + D[3] * w); // X component A = static_cast(D[0] + (D[1] - D[0]) * v); B = static_cast(D[2] + (D[3] - D[2]) * v); grad[0] = B - A; // Y component A = D[1] - D[0]; B = D[3] - D[2]; grad[1] = static_cast(A + (B - A) * u); return BaseType::mGrid->transform().baseMap()->applyIJT(grad, xyz); } private: inline void init(const Coord& ijk) { BaseType::template setValue< 0, 0, 1>(mCache.getValue(ijk.offsetBy( 0, 0, 1))); BaseType::template setValue< 0, 1, 1>(mCache.getValue(ijk.offsetBy( 0, 1, 1))); BaseType::template setValue< 0, 1, 0>(mCache.getValue(ijk.offsetBy( 0, 1, 0))); BaseType::template setValue< 1, 0, 0>(mCache.getValue(ijk.offsetBy( 1, 0, 0))); BaseType::template setValue< 1, 0, 1>(mCache.getValue(ijk.offsetBy( 1, 0, 1))); BaseType::template setValue< 1, 1, 1>(mCache.getValue(ijk.offsetBy( 1, 1, 1))); BaseType::template setValue< 1, 1, 0>(mCache.getValue(ijk.offsetBy( 1, 1, 0))); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; };// BoxStencil class //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the dense point stencil template struct DensePt {}; template<> struct DensePt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct DensePt< 1, 0, 0> { enum { idx = 1 }; }; template<> struct DensePt< 0, 1, 0> { enum { idx = 2 }; }; template<> struct DensePt< 0, 0, 1> { enum { idx = 3 }; }; template<> struct DensePt<-1, 0, 0> { enum { idx = 4 }; }; template<> struct DensePt< 0,-1, 0> { enum { idx = 5 }; }; template<> struct DensePt< 0, 0,-1> { enum { idx = 6 }; }; template<> struct DensePt<-1,-1, 0> { enum { idx = 7 }; }; template<> struct DensePt< 0,-1,-1> { enum { idx = 8 }; }; template<> struct DensePt<-1, 0,-1> { enum { idx = 9 }; }; template<> struct DensePt< 1,-1, 0> { enum { idx = 10 }; }; template<> struct DensePt< 0, 1,-1> { enum { idx = 11 }; }; template<> struct DensePt<-1, 0, 1> { enum { idx = 12 }; }; template<> struct DensePt<-1, 1, 0> { enum { idx = 13 }; }; template<> struct DensePt< 0,-1, 1> { enum { idx = 14 }; }; template<> struct DensePt< 1, 0,-1> { enum { idx = 15 }; }; template<> struct DensePt< 1, 1, 0> { enum { idx = 16 }; }; template<> struct DensePt< 0, 1, 1> { enum { idx = 17 }; }; template<> struct DensePt< 1, 0, 1> { enum { idx = 18 }; }; } template class SecondOrderDenseStencil : public BaseStencil, GridT, IsSafe > { typedef SecondOrderDenseStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; static const int SIZE = 19; SecondOrderDenseStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return DensePt::idx; } private: inline void init(const Coord& ijk) { mStencil[DensePt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[DensePt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[DensePt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[DensePt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[DensePt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); mStencil[DensePt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); mStencil[DensePt<-1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, -1, 0)); mStencil[DensePt< 1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, -1, 0)); mStencil[DensePt<-1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 1, 0)); mStencil[DensePt< 1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 1, 0)); mStencil[DensePt<-1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, -1)); mStencil[DensePt< 1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, -1)); mStencil[DensePt<-1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 1)); mStencil[DensePt< 1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 1)); mStencil[DensePt< 0,-1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, -1)); mStencil[DensePt< 0, 1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, -1)); mStencil[DensePt< 0,-1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 1)); mStencil[DensePt< 0, 1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 1)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; };// SecondOrderDenseStencil class //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the dense point stencil template struct ThirteenPt {}; template<> struct ThirteenPt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct ThirteenPt< 1, 0, 0> { enum { idx = 1 }; }; template<> struct ThirteenPt< 0, 1, 0> { enum { idx = 2 }; }; template<> struct ThirteenPt< 0, 0, 1> { enum { idx = 3 }; }; template<> struct ThirteenPt<-1, 0, 0> { enum { idx = 4 }; }; template<> struct ThirteenPt< 0,-1, 0> { enum { idx = 5 }; }; template<> struct ThirteenPt< 0, 0,-1> { enum { idx = 6 }; }; template<> struct ThirteenPt< 2, 0, 0> { enum { idx = 7 }; }; template<> struct ThirteenPt< 0, 2, 0> { enum { idx = 8 }; }; template<> struct ThirteenPt< 0, 0, 2> { enum { idx = 9 }; }; template<> struct ThirteenPt<-2, 0, 0> { enum { idx = 10 }; }; template<> struct ThirteenPt< 0,-2, 0> { enum { idx = 11 }; }; template<> struct ThirteenPt< 0, 0,-2> { enum { idx = 12 }; }; } template class ThirteenPointStencil : public BaseStencil, GridT, IsSafe> { typedef ThirteenPointStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; static const int SIZE = 13; ThirteenPointStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return ThirteenPt::idx; } private: inline void init(const Coord& ijk) { mStencil[ThirteenPt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); mStencil[ThirteenPt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[ThirteenPt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[ThirteenPt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); mStencil[ThirteenPt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); mStencil[ThirteenPt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[ThirteenPt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); mStencil[ThirteenPt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -2, 0)); mStencil[ThirteenPt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); mStencil[ThirteenPt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[ThirteenPt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); mStencil[ThirteenPt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -2)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; };// ThirteenPointStencil class //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the 4th-order dense point stencil template struct FourthDensePt {}; template<> struct FourthDensePt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct FourthDensePt<-2, 2, 0> { enum { idx = 1 }; }; template<> struct FourthDensePt<-1, 2, 0> { enum { idx = 2 }; }; template<> struct FourthDensePt< 0, 2, 0> { enum { idx = 3 }; }; template<> struct FourthDensePt< 1, 2, 0> { enum { idx = 4 }; }; template<> struct FourthDensePt< 2, 2, 0> { enum { idx = 5 }; }; template<> struct FourthDensePt<-2, 1, 0> { enum { idx = 6 }; }; template<> struct FourthDensePt<-1, 1, 0> { enum { idx = 7 }; }; template<> struct FourthDensePt< 0, 1, 0> { enum { idx = 8 }; }; template<> struct FourthDensePt< 1, 1, 0> { enum { idx = 9 }; }; template<> struct FourthDensePt< 2, 1, 0> { enum { idx = 10 }; }; template<> struct FourthDensePt<-2, 0, 0> { enum { idx = 11 }; }; template<> struct FourthDensePt<-1, 0, 0> { enum { idx = 12 }; }; template<> struct FourthDensePt< 1, 0, 0> { enum { idx = 13 }; }; template<> struct FourthDensePt< 2, 0, 0> { enum { idx = 14 }; }; template<> struct FourthDensePt<-2,-1, 0> { enum { idx = 15 }; }; template<> struct FourthDensePt<-1,-1, 0> { enum { idx = 16 }; }; template<> struct FourthDensePt< 0,-1, 0> { enum { idx = 17 }; }; template<> struct FourthDensePt< 1,-1, 0> { enum { idx = 18 }; }; template<> struct FourthDensePt< 2,-1, 0> { enum { idx = 19 }; }; template<> struct FourthDensePt<-2,-2, 0> { enum { idx = 20 }; }; template<> struct FourthDensePt<-1,-2, 0> { enum { idx = 21 }; }; template<> struct FourthDensePt< 0,-2, 0> { enum { idx = 22 }; }; template<> struct FourthDensePt< 1,-2, 0> { enum { idx = 23 }; }; template<> struct FourthDensePt< 2,-2, 0> { enum { idx = 24 }; }; template<> struct FourthDensePt<-2, 0, 2> { enum { idx = 25 }; }; template<> struct FourthDensePt<-1, 0, 2> { enum { idx = 26 }; }; template<> struct FourthDensePt< 0, 0, 2> { enum { idx = 27 }; }; template<> struct FourthDensePt< 1, 0, 2> { enum { idx = 28 }; }; template<> struct FourthDensePt< 2, 0, 2> { enum { idx = 29 }; }; template<> struct FourthDensePt<-2, 0, 1> { enum { idx = 30 }; }; template<> struct FourthDensePt<-1, 0, 1> { enum { idx = 31 }; }; template<> struct FourthDensePt< 0, 0, 1> { enum { idx = 32 }; }; template<> struct FourthDensePt< 1, 0, 1> { enum { idx = 33 }; }; template<> struct FourthDensePt< 2, 0, 1> { enum { idx = 34 }; }; template<> struct FourthDensePt<-2, 0,-1> { enum { idx = 35 }; }; template<> struct FourthDensePt<-1, 0,-1> { enum { idx = 36 }; }; template<> struct FourthDensePt< 0, 0,-1> { enum { idx = 37 }; }; template<> struct FourthDensePt< 1, 0,-1> { enum { idx = 38 }; }; template<> struct FourthDensePt< 2, 0,-1> { enum { idx = 39 }; }; template<> struct FourthDensePt<-2, 0,-2> { enum { idx = 40 }; }; template<> struct FourthDensePt<-1, 0,-2> { enum { idx = 41 }; }; template<> struct FourthDensePt< 0, 0,-2> { enum { idx = 42 }; }; template<> struct FourthDensePt< 1, 0,-2> { enum { idx = 43 }; }; template<> struct FourthDensePt< 2, 0,-2> { enum { idx = 44 }; }; template<> struct FourthDensePt< 0,-2, 2> { enum { idx = 45 }; }; template<> struct FourthDensePt< 0,-1, 2> { enum { idx = 46 }; }; template<> struct FourthDensePt< 0, 1, 2> { enum { idx = 47 }; }; template<> struct FourthDensePt< 0, 2, 2> { enum { idx = 48 }; }; template<> struct FourthDensePt< 0,-2, 1> { enum { idx = 49 }; }; template<> struct FourthDensePt< 0,-1, 1> { enum { idx = 50 }; }; template<> struct FourthDensePt< 0, 1, 1> { enum { idx = 51 }; }; template<> struct FourthDensePt< 0, 2, 1> { enum { idx = 52 }; }; template<> struct FourthDensePt< 0,-2,-1> { enum { idx = 53 }; }; template<> struct FourthDensePt< 0,-1,-1> { enum { idx = 54 }; }; template<> struct FourthDensePt< 0, 1,-1> { enum { idx = 55 }; }; template<> struct FourthDensePt< 0, 2,-1> { enum { idx = 56 }; }; template<> struct FourthDensePt< 0,-2,-2> { enum { idx = 57 }; }; template<> struct FourthDensePt< 0,-1,-2> { enum { idx = 58 }; }; template<> struct FourthDensePt< 0, 1,-2> { enum { idx = 59 }; }; template<> struct FourthDensePt< 0, 2,-2> { enum { idx = 60 }; }; } template class FourthOrderDenseStencil : public BaseStencil, GridT, IsSafe> { typedef FourthOrderDenseStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; static const int SIZE = 61; FourthOrderDenseStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return FourthDensePt::idx; } private: inline void init(const Coord& ijk) { mStencil[FourthDensePt<-2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 2, 0)); mStencil[FourthDensePt<-1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 2, 0)); mStencil[FourthDensePt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); mStencil[FourthDensePt< 1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 2, 0)); mStencil[FourthDensePt< 2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 2, 0)); mStencil[FourthDensePt<-2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 1, 0)); mStencil[FourthDensePt<-1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 1, 0)); mStencil[FourthDensePt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[FourthDensePt< 1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 1, 0)); mStencil[FourthDensePt< 2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 1, 0)); mStencil[FourthDensePt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); mStencil[FourthDensePt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[FourthDensePt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[FourthDensePt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); mStencil[FourthDensePt<-2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-1, 0)); mStencil[FourthDensePt<-1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-1, 0)); mStencil[FourthDensePt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 0)); mStencil[FourthDensePt< 1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-1, 0)); mStencil[FourthDensePt< 2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-1, 0)); mStencil[FourthDensePt<-2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-2, 0)); mStencil[FourthDensePt<-1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-2, 0)); mStencil[FourthDensePt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 0)); mStencil[FourthDensePt< 1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-2, 0)); mStencil[FourthDensePt< 2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-2, 0)); mStencil[FourthDensePt<-2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 2)); mStencil[FourthDensePt<-1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 2)); mStencil[FourthDensePt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); mStencil[FourthDensePt< 1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 2)); mStencil[FourthDensePt< 2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 2)); mStencil[FourthDensePt<-2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 1)); mStencil[FourthDensePt<-1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 1)); mStencil[FourthDensePt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[FourthDensePt< 1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 1)); mStencil[FourthDensePt< 2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 1)); mStencil[FourthDensePt<-2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-1)); mStencil[FourthDensePt<-1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-1)); mStencil[FourthDensePt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-1)); mStencil[FourthDensePt< 1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-1)); mStencil[FourthDensePt< 2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-1)); mStencil[FourthDensePt<-2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-2)); mStencil[FourthDensePt<-1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-2)); mStencil[FourthDensePt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-2)); mStencil[FourthDensePt< 1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-2)); mStencil[FourthDensePt< 2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-2)); mStencil[FourthDensePt< 0,-2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 2)); mStencil[FourthDensePt< 0,-1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 2)); mStencil[FourthDensePt< 0, 1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 2)); mStencil[FourthDensePt< 0, 2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 2)); mStencil[FourthDensePt< 0,-2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 1)); mStencil[FourthDensePt< 0,-1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 1)); mStencil[FourthDensePt< 0, 1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 1)); mStencil[FourthDensePt< 0, 2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 1)); mStencil[FourthDensePt< 0,-2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-1)); mStencil[FourthDensePt< 0,-1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-1)); mStencil[FourthDensePt< 0, 1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-1)); mStencil[FourthDensePt< 0, 2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-1)); mStencil[FourthDensePt< 0,-2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-2)); mStencil[FourthDensePt< 0,-1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-2)); mStencil[FourthDensePt< 0, 1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-2)); mStencil[FourthDensePt< 0, 2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-2)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; };// FourthOrderDenseStencil class //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the dense point stencil template struct NineteenPt {}; template<> struct NineteenPt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct NineteenPt< 1, 0, 0> { enum { idx = 1 }; }; template<> struct NineteenPt< 0, 1, 0> { enum { idx = 2 }; }; template<> struct NineteenPt< 0, 0, 1> { enum { idx = 3 }; }; template<> struct NineteenPt<-1, 0, 0> { enum { idx = 4 }; }; template<> struct NineteenPt< 0,-1, 0> { enum { idx = 5 }; }; template<> struct NineteenPt< 0, 0,-1> { enum { idx = 6 }; }; template<> struct NineteenPt< 2, 0, 0> { enum { idx = 7 }; }; template<> struct NineteenPt< 0, 2, 0> { enum { idx = 8 }; }; template<> struct NineteenPt< 0, 0, 2> { enum { idx = 9 }; }; template<> struct NineteenPt<-2, 0, 0> { enum { idx = 10 }; }; template<> struct NineteenPt< 0,-2, 0> { enum { idx = 11 }; }; template<> struct NineteenPt< 0, 0,-2> { enum { idx = 12 }; }; template<> struct NineteenPt< 3, 0, 0> { enum { idx = 13 }; }; template<> struct NineteenPt< 0, 3, 0> { enum { idx = 14 }; }; template<> struct NineteenPt< 0, 0, 3> { enum { idx = 15 }; }; template<> struct NineteenPt<-3, 0, 0> { enum { idx = 16 }; }; template<> struct NineteenPt< 0,-3, 0> { enum { idx = 17 }; }; template<> struct NineteenPt< 0, 0,-3> { enum { idx = 18 }; }; } template class NineteenPointStencil : public BaseStencil, GridT, IsSafe> { typedef NineteenPointStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; static const int SIZE = 19; NineteenPointStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return NineteenPt::idx; } private: inline void init(const Coord& ijk) { mStencil[NineteenPt< 3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 0)); mStencil[NineteenPt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); mStencil[NineteenPt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[NineteenPt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[NineteenPt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); mStencil[NineteenPt<-3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 0)); mStencil[NineteenPt< 0, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 0)); mStencil[NineteenPt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); mStencil[NineteenPt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[NineteenPt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); mStencil[NineteenPt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -2, 0)); mStencil[NineteenPt< 0,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -3, 0)); mStencil[NineteenPt< 0, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 3)); mStencil[NineteenPt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); mStencil[NineteenPt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[NineteenPt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); mStencil[NineteenPt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -2)); mStencil[NineteenPt< 0, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -3)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; };// NineteenPointStencil class //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the 4th-order dense point stencil template struct SixthDensePt { }; template<> struct SixthDensePt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct SixthDensePt<-3, 3, 0> { enum { idx = 1 }; }; template<> struct SixthDensePt<-2, 3, 0> { enum { idx = 2 }; }; template<> struct SixthDensePt<-1, 3, 0> { enum { idx = 3 }; }; template<> struct SixthDensePt< 0, 3, 0> { enum { idx = 4 }; }; template<> struct SixthDensePt< 1, 3, 0> { enum { idx = 5 }; }; template<> struct SixthDensePt< 2, 3, 0> { enum { idx = 6 }; }; template<> struct SixthDensePt< 3, 3, 0> { enum { idx = 7 }; }; template<> struct SixthDensePt<-3, 2, 0> { enum { idx = 8 }; }; template<> struct SixthDensePt<-2, 2, 0> { enum { idx = 9 }; }; template<> struct SixthDensePt<-1, 2, 0> { enum { idx = 10 }; }; template<> struct SixthDensePt< 0, 2, 0> { enum { idx = 11 }; }; template<> struct SixthDensePt< 1, 2, 0> { enum { idx = 12 }; }; template<> struct SixthDensePt< 2, 2, 0> { enum { idx = 13 }; }; template<> struct SixthDensePt< 3, 2, 0> { enum { idx = 14 }; }; template<> struct SixthDensePt<-3, 1, 0> { enum { idx = 15 }; }; template<> struct SixthDensePt<-2, 1, 0> { enum { idx = 16 }; }; template<> struct SixthDensePt<-1, 1, 0> { enum { idx = 17 }; }; template<> struct SixthDensePt< 0, 1, 0> { enum { idx = 18 }; }; template<> struct SixthDensePt< 1, 1, 0> { enum { idx = 19 }; }; template<> struct SixthDensePt< 2, 1, 0> { enum { idx = 20 }; }; template<> struct SixthDensePt< 3, 1, 0> { enum { idx = 21 }; }; template<> struct SixthDensePt<-3, 0, 0> { enum { idx = 22 }; }; template<> struct SixthDensePt<-2, 0, 0> { enum { idx = 23 }; }; template<> struct SixthDensePt<-1, 0, 0> { enum { idx = 24 }; }; template<> struct SixthDensePt< 1, 0, 0> { enum { idx = 25 }; }; template<> struct SixthDensePt< 2, 0, 0> { enum { idx = 26 }; }; template<> struct SixthDensePt< 3, 0, 0> { enum { idx = 27 }; }; template<> struct SixthDensePt<-3,-1, 0> { enum { idx = 28 }; }; template<> struct SixthDensePt<-2,-1, 0> { enum { idx = 29 }; }; template<> struct SixthDensePt<-1,-1, 0> { enum { idx = 30 }; }; template<> struct SixthDensePt< 0,-1, 0> { enum { idx = 31 }; }; template<> struct SixthDensePt< 1,-1, 0> { enum { idx = 32 }; }; template<> struct SixthDensePt< 2,-1, 0> { enum { idx = 33 }; }; template<> struct SixthDensePt< 3,-1, 0> { enum { idx = 34 }; }; template<> struct SixthDensePt<-3,-2, 0> { enum { idx = 35 }; }; template<> struct SixthDensePt<-2,-2, 0> { enum { idx = 36 }; }; template<> struct SixthDensePt<-1,-2, 0> { enum { idx = 37 }; }; template<> struct SixthDensePt< 0,-2, 0> { enum { idx = 38 }; }; template<> struct SixthDensePt< 1,-2, 0> { enum { idx = 39 }; }; template<> struct SixthDensePt< 2,-2, 0> { enum { idx = 40 }; }; template<> struct SixthDensePt< 3,-2, 0> { enum { idx = 41 }; }; template<> struct SixthDensePt<-3,-3, 0> { enum { idx = 42 }; }; template<> struct SixthDensePt<-2,-3, 0> { enum { idx = 43 }; }; template<> struct SixthDensePt<-1,-3, 0> { enum { idx = 44 }; }; template<> struct SixthDensePt< 0,-3, 0> { enum { idx = 45 }; }; template<> struct SixthDensePt< 1,-3, 0> { enum { idx = 46 }; }; template<> struct SixthDensePt< 2,-3, 0> { enum { idx = 47 }; }; template<> struct SixthDensePt< 3,-3, 0> { enum { idx = 48 }; }; template<> struct SixthDensePt<-3, 0, 3> { enum { idx = 49 }; }; template<> struct SixthDensePt<-2, 0, 3> { enum { idx = 50 }; }; template<> struct SixthDensePt<-1, 0, 3> { enum { idx = 51 }; }; template<> struct SixthDensePt< 0, 0, 3> { enum { idx = 52 }; }; template<> struct SixthDensePt< 1, 0, 3> { enum { idx = 53 }; }; template<> struct SixthDensePt< 2, 0, 3> { enum { idx = 54 }; }; template<> struct SixthDensePt< 3, 0, 3> { enum { idx = 55 }; }; template<> struct SixthDensePt<-3, 0, 2> { enum { idx = 56 }; }; template<> struct SixthDensePt<-2, 0, 2> { enum { idx = 57 }; }; template<> struct SixthDensePt<-1, 0, 2> { enum { idx = 58 }; }; template<> struct SixthDensePt< 0, 0, 2> { enum { idx = 59 }; }; template<> struct SixthDensePt< 1, 0, 2> { enum { idx = 60 }; }; template<> struct SixthDensePt< 2, 0, 2> { enum { idx = 61 }; }; template<> struct SixthDensePt< 3, 0, 2> { enum { idx = 62 }; }; template<> struct SixthDensePt<-3, 0, 1> { enum { idx = 63 }; }; template<> struct SixthDensePt<-2, 0, 1> { enum { idx = 64 }; }; template<> struct SixthDensePt<-1, 0, 1> { enum { idx = 65 }; }; template<> struct SixthDensePt< 0, 0, 1> { enum { idx = 66 }; }; template<> struct SixthDensePt< 1, 0, 1> { enum { idx = 67 }; }; template<> struct SixthDensePt< 2, 0, 1> { enum { idx = 68 }; }; template<> struct SixthDensePt< 3, 0, 1> { enum { idx = 69 }; }; template<> struct SixthDensePt<-3, 0,-1> { enum { idx = 70 }; }; template<> struct SixthDensePt<-2, 0,-1> { enum { idx = 71 }; }; template<> struct SixthDensePt<-1, 0,-1> { enum { idx = 72 }; }; template<> struct SixthDensePt< 0, 0,-1> { enum { idx = 73 }; }; template<> struct SixthDensePt< 1, 0,-1> { enum { idx = 74 }; }; template<> struct SixthDensePt< 2, 0,-1> { enum { idx = 75 }; }; template<> struct SixthDensePt< 3, 0,-1> { enum { idx = 76 }; }; template<> struct SixthDensePt<-3, 0,-2> { enum { idx = 77 }; }; template<> struct SixthDensePt<-2, 0,-2> { enum { idx = 78 }; }; template<> struct SixthDensePt<-1, 0,-2> { enum { idx = 79 }; }; template<> struct SixthDensePt< 0, 0,-2> { enum { idx = 80 }; }; template<> struct SixthDensePt< 1, 0,-2> { enum { idx = 81 }; }; template<> struct SixthDensePt< 2, 0,-2> { enum { idx = 82 }; }; template<> struct SixthDensePt< 3, 0,-2> { enum { idx = 83 }; }; template<> struct SixthDensePt<-3, 0,-3> { enum { idx = 84 }; }; template<> struct SixthDensePt<-2, 0,-3> { enum { idx = 85 }; }; template<> struct SixthDensePt<-1, 0,-3> { enum { idx = 86 }; }; template<> struct SixthDensePt< 0, 0,-3> { enum { idx = 87 }; }; template<> struct SixthDensePt< 1, 0,-3> { enum { idx = 88 }; }; template<> struct SixthDensePt< 2, 0,-3> { enum { idx = 89 }; }; template<> struct SixthDensePt< 3, 0,-3> { enum { idx = 90 }; }; template<> struct SixthDensePt< 0,-3, 3> { enum { idx = 91 }; }; template<> struct SixthDensePt< 0,-2, 3> { enum { idx = 92 }; }; template<> struct SixthDensePt< 0,-1, 3> { enum { idx = 93 }; }; template<> struct SixthDensePt< 0, 1, 3> { enum { idx = 94 }; }; template<> struct SixthDensePt< 0, 2, 3> { enum { idx = 95 }; }; template<> struct SixthDensePt< 0, 3, 3> { enum { idx = 96 }; }; template<> struct SixthDensePt< 0,-3, 2> { enum { idx = 97 }; }; template<> struct SixthDensePt< 0,-2, 2> { enum { idx = 98 }; }; template<> struct SixthDensePt< 0,-1, 2> { enum { idx = 99 }; }; template<> struct SixthDensePt< 0, 1, 2> { enum { idx = 100 }; }; template<> struct SixthDensePt< 0, 2, 2> { enum { idx = 101 }; }; template<> struct SixthDensePt< 0, 3, 2> { enum { idx = 102 }; }; template<> struct SixthDensePt< 0,-3, 1> { enum { idx = 103 }; }; template<> struct SixthDensePt< 0,-2, 1> { enum { idx = 104 }; }; template<> struct SixthDensePt< 0,-1, 1> { enum { idx = 105 }; }; template<> struct SixthDensePt< 0, 1, 1> { enum { idx = 106 }; }; template<> struct SixthDensePt< 0, 2, 1> { enum { idx = 107 }; }; template<> struct SixthDensePt< 0, 3, 1> { enum { idx = 108 }; }; template<> struct SixthDensePt< 0,-3,-1> { enum { idx = 109 }; }; template<> struct SixthDensePt< 0,-2,-1> { enum { idx = 110 }; }; template<> struct SixthDensePt< 0,-1,-1> { enum { idx = 111 }; }; template<> struct SixthDensePt< 0, 1,-1> { enum { idx = 112 }; }; template<> struct SixthDensePt< 0, 2,-1> { enum { idx = 113 }; }; template<> struct SixthDensePt< 0, 3,-1> { enum { idx = 114 }; }; template<> struct SixthDensePt< 0,-3,-2> { enum { idx = 115 }; }; template<> struct SixthDensePt< 0,-2,-2> { enum { idx = 116 }; }; template<> struct SixthDensePt< 0,-1,-2> { enum { idx = 117 }; }; template<> struct SixthDensePt< 0, 1,-2> { enum { idx = 118 }; }; template<> struct SixthDensePt< 0, 2,-2> { enum { idx = 119 }; }; template<> struct SixthDensePt< 0, 3,-2> { enum { idx = 120 }; }; template<> struct SixthDensePt< 0,-3,-3> { enum { idx = 121 }; }; template<> struct SixthDensePt< 0,-2,-3> { enum { idx = 122 }; }; template<> struct SixthDensePt< 0,-1,-3> { enum { idx = 123 }; }; template<> struct SixthDensePt< 0, 1,-3> { enum { idx = 124 }; }; template<> struct SixthDensePt< 0, 2,-3> { enum { idx = 125 }; }; template<> struct SixthDensePt< 0, 3,-3> { enum { idx = 126 }; }; } template class SixthOrderDenseStencil : public BaseStencil, GridT, IsSafe> { typedef SixthOrderDenseStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; static const int SIZE = 127; SixthOrderDenseStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return SixthDensePt::idx; } private: inline void init(const Coord& ijk) { mStencil[SixthDensePt<-3, 3, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 3, 0)); mStencil[SixthDensePt<-2, 3, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 3, 0)); mStencil[SixthDensePt<-1, 3, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 3, 0)); mStencil[SixthDensePt< 0, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 0)); mStencil[SixthDensePt< 1, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 3, 0)); mStencil[SixthDensePt< 2, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 3, 0)); mStencil[SixthDensePt< 3, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 3, 0)); mStencil[SixthDensePt<-3, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 2, 0)); mStencil[SixthDensePt<-2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 2, 0)); mStencil[SixthDensePt<-1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 2, 0)); mStencil[SixthDensePt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); mStencil[SixthDensePt< 1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 2, 0)); mStencil[SixthDensePt< 2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 2, 0)); mStencil[SixthDensePt< 3, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 2, 0)); mStencil[SixthDensePt<-3, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 1, 0)); mStencil[SixthDensePt<-2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 1, 0)); mStencil[SixthDensePt<-1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 1, 0)); mStencil[SixthDensePt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[SixthDensePt< 1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 1, 0)); mStencil[SixthDensePt< 2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 1, 0)); mStencil[SixthDensePt< 3, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 1, 0)); mStencil[SixthDensePt<-3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 0)); mStencil[SixthDensePt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); mStencil[SixthDensePt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[SixthDensePt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[SixthDensePt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); mStencil[SixthDensePt< 3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 0)); mStencil[SixthDensePt<-3,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-3,-1, 0)); mStencil[SixthDensePt<-2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-1, 0)); mStencil[SixthDensePt<-1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-1, 0)); mStencil[SixthDensePt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 0)); mStencil[SixthDensePt< 1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-1, 0)); mStencil[SixthDensePt< 2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-1, 0)); mStencil[SixthDensePt< 3,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 3,-1, 0)); mStencil[SixthDensePt<-3,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-3,-2, 0)); mStencil[SixthDensePt<-2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-2, 0)); mStencil[SixthDensePt<-1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-2, 0)); mStencil[SixthDensePt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 0)); mStencil[SixthDensePt< 1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-2, 0)); mStencil[SixthDensePt< 2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-2, 0)); mStencil[SixthDensePt< 3,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 3,-2, 0)); mStencil[SixthDensePt<-3,-3, 0>::idx] = mCache.getValue(ijk.offsetBy(-3,-3, 0)); mStencil[SixthDensePt<-2,-3, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-3, 0)); mStencil[SixthDensePt<-1,-3, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-3, 0)); mStencil[SixthDensePt< 0,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 0)); mStencil[SixthDensePt< 1,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-3, 0)); mStencil[SixthDensePt< 2,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-3, 0)); mStencil[SixthDensePt< 3,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 3,-3, 0)); mStencil[SixthDensePt<-3, 0, 3>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 3)); mStencil[SixthDensePt<-2, 0, 3>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 3)); mStencil[SixthDensePt<-1, 0, 3>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 3)); mStencil[SixthDensePt< 0, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 3)); mStencil[SixthDensePt< 1, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 3)); mStencil[SixthDensePt< 2, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 3)); mStencil[SixthDensePt< 3, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 3)); mStencil[SixthDensePt<-3, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 2)); mStencil[SixthDensePt<-2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 2)); mStencil[SixthDensePt<-1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 2)); mStencil[SixthDensePt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); mStencil[SixthDensePt< 1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 2)); mStencil[SixthDensePt< 2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 2)); mStencil[SixthDensePt< 3, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 2)); mStencil[SixthDensePt<-3, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 1)); mStencil[SixthDensePt<-2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 1)); mStencil[SixthDensePt<-1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 1)); mStencil[SixthDensePt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[SixthDensePt< 1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 1)); mStencil[SixthDensePt< 2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 1)); mStencil[SixthDensePt< 3, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 1)); mStencil[SixthDensePt<-3, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-3, 0,-1)); mStencil[SixthDensePt<-2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-1)); mStencil[SixthDensePt<-1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-1)); mStencil[SixthDensePt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-1)); mStencil[SixthDensePt< 1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-1)); mStencil[SixthDensePt< 2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-1)); mStencil[SixthDensePt< 3, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 3, 0,-1)); mStencil[SixthDensePt<-3, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-3, 0,-2)); mStencil[SixthDensePt<-2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-2)); mStencil[SixthDensePt<-1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-2)); mStencil[SixthDensePt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-2)); mStencil[SixthDensePt< 1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-2)); mStencil[SixthDensePt< 2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-2)); mStencil[SixthDensePt< 3, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 3, 0,-2)); mStencil[SixthDensePt<-3, 0,-3>::idx] = mCache.getValue(ijk.offsetBy(-3, 0,-3)); mStencil[SixthDensePt<-2, 0,-3>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-3)); mStencil[SixthDensePt<-1, 0,-3>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-3)); mStencil[SixthDensePt< 0, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-3)); mStencil[SixthDensePt< 1, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-3)); mStencil[SixthDensePt< 2, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-3)); mStencil[SixthDensePt< 3, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 3, 0,-3)); mStencil[SixthDensePt< 0,-3, 3>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 3)); mStencil[SixthDensePt< 0,-2, 3>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 3)); mStencil[SixthDensePt< 0,-1, 3>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 3)); mStencil[SixthDensePt< 0, 1, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 3)); mStencil[SixthDensePt< 0, 2, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 3)); mStencil[SixthDensePt< 0, 3, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 3)); mStencil[SixthDensePt< 0,-3, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 2)); mStencil[SixthDensePt< 0,-2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 2)); mStencil[SixthDensePt< 0,-1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 2)); mStencil[SixthDensePt< 0, 1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 2)); mStencil[SixthDensePt< 0, 2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 2)); mStencil[SixthDensePt< 0, 3, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 2)); mStencil[SixthDensePt< 0,-3, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 1)); mStencil[SixthDensePt< 0,-2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 1)); mStencil[SixthDensePt< 0,-1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 1)); mStencil[SixthDensePt< 0, 1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 1)); mStencil[SixthDensePt< 0, 2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 1)); mStencil[SixthDensePt< 0, 3, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 1)); mStencil[SixthDensePt< 0,-3,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-3,-1)); mStencil[SixthDensePt< 0,-2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-1)); mStencil[SixthDensePt< 0,-1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-1)); mStencil[SixthDensePt< 0, 1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-1)); mStencil[SixthDensePt< 0, 2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-1)); mStencil[SixthDensePt< 0, 3,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 3,-1)); mStencil[SixthDensePt< 0,-3,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-3,-2)); mStencil[SixthDensePt< 0,-2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-2)); mStencil[SixthDensePt< 0,-1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-2)); mStencil[SixthDensePt< 0, 1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-2)); mStencil[SixthDensePt< 0, 2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-2)); mStencil[SixthDensePt< 0, 3,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 3,-2)); mStencil[SixthDensePt< 0,-3,-3>::idx] = mCache.getValue(ijk.offsetBy( 0,-3,-3)); mStencil[SixthDensePt< 0,-2,-3>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-3)); mStencil[SixthDensePt< 0,-1,-3>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-3)); mStencil[SixthDensePt< 0, 1,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-3)); mStencil[SixthDensePt< 0, 2,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-3)); mStencil[SixthDensePt< 0, 3,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 3,-3)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; };// SixthOrderDenseStencil class ////////////////////////////////////////////////////////////////////// /// This is a simple 7-point nearest neighbor stencil that supports /// gradient by second-order central differencing, first-order upwinding, /// Laplacian, closest-point transform and zero-crossing test. /// /// @note For optimal random access performance this class /// includes its own grid accessor. template class GradStencil : public BaseStencil, GridT, IsSafe> { typedef GradStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; static const int SIZE = 7; GradStencil(const GridType& grid) : BaseType(grid, SIZE) , mInv2Dx(ValueType(0.5 / grid.voxelSize()[0])) , mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) { } GradStencil(const GridType& grid, Real dx) : BaseType(grid, SIZE) , mInv2Dx(ValueType(0.5 / dx)) , mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) { } /// @brief Return the norm square of the single-sided upwind gradient /// (computed via 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 math::Vec3 gradient() const { return math::Vec3(mStencil[2] - mStencil[1], mStencil[4] - mStencil[3], mStencil[6] - mStencil[5])*mInv2Dx; } /// @brief Return the first-order upwind gradient corresponding to the direction V. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline math::Vec3 gradient(const math::Vec3& V) const { return math::Vec3( V[0]>0 ? mStencil[0] - mStencil[1] : mStencil[2] - mStencil[0], V[1]>0 ? mStencil[0] - mStencil[3] : mStencil[4] - mStencil[0], V[2]>0 ? mStencil[0] - mStencil[5] : mStencil[6] - mStencil[0])*2*mInv2Dx; } /// Return the Laplacian computed at the previously buffered /// location by second-order central differencing. inline ValueType laplacian() const { return mInvDx2 * (mStencil[1] + mStencil[2] + mStencil[3] + mStencil[4] + mStencil[5] + mStencil[6] - 6*mStencil[0]); } /// Return @c true if the sign of the value at the center point of the stencil /// is different from the signs of any of its six nearest neighbors. inline bool zeroCrossing() const { const typename BaseType::BufferType& v = mStencil; return (v[0]>0 ? (v[1]<0 || v[2]<0 || v[3]<0 || v[4]<0 || v[5]<0 || v[6]<0) : (v[1]>0 || v[2]>0 || v[3]>0 || v[4]>0 || v[5]>0 || v[6]>0)); } /// @brief Compute the closest-point transform to a level set. /// @return the closest point in index space to the surface /// from which the level set was derived. /// /// @note This method assumes that the grid represents a level set /// with distances in world units and a simple affine transfrom /// with uniform scaling. inline math::Vec3 cpt() { const Coord& ijk = BaseType::getCenterCoord(); const ValueType d = ValueType(mStencil[0] * 0.5 * mInvDx2); // distance in voxels / (2dx^2) return math::Vec3(ijk[0] - d*(mStencil[2] - mStencil[1]), ijk[1] - d*(mStencil[4] - mStencil[3]), ijk[2] - d*(mStencil[6] - mStencil[5])); } 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; }; // GradStencil class //////////////////////////////////////// /// @brief This is a special 19-point stencil that supports optimal fifth-order WENO /// upwinding, second-order central differencing, Laplacian, and zero-crossing test. /// /// @note For optimal random access performance this class /// includes its own grid accessor. template class WenoStencil: public BaseStencil, GridT, IsSafe> { typedef WenoStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; static const int SIZE = 19; WenoStencil(const GridType& grid) : BaseType(grid, SIZE) , mDx2(ValueType(math::Pow2(grid.voxelSize()[0]))) , mInv2Dx(ValueType(0.5 / grid.voxelSize()[0])) , mInvDx2(ValueType(1.0 / mDx2)) { } WenoStencil(const GridType& grid, Real dx) : BaseType(grid, SIZE) , mDx2(ValueType(dx * dx)) , mInv2Dx(ValueType(0.5 / dx)) , mInvDx2(ValueType(1.0 / mDx2)) { } /// @brief Return the norm-square of the WENO upwind gradient (computed via /// WENO upwinding and 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 typename BaseType::BufferType& v = mStencil; #ifdef DWA_OPENVDB // SSE optimized const simd::Float4 v1(v[2]-v[1], v[ 8]-v[ 7], v[14]-v[13], 0), v2(v[3]-v[2], v[ 9]-v[ 8], v[15]-v[14], 0), v3(v[0]-v[3], v[ 0]-v[ 9], v[ 0]-v[15], 0), v4(v[4]-v[0], v[10]-v[ 0], v[16]-v[ 0], 0), v5(v[5]-v[4], v[11]-v[10], v[17]-v[16], 0), v6(v[6]-v[5], v[12]-v[11], v[18]-v[17], 0), dP_m = math::WENO5(v1, v2, v3, v4, v5, mDx2), dP_p = math::WENO5(v6, v5, v4, v3, v2, mDx2); return mInvDx2 * math::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 static_cast( 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 math::Vec3 gradient(const math::Vec3& V) const { const typename BaseType::BufferType& v = mStencil; return 2*mInv2Dx * math::Vec3( V[0]>0 ? math::WENO5(v[ 2]-v[ 1],v[ 3]-v[ 2],v[ 0]-v[ 3], v[ 4]-v[ 0],v[ 5]-v[ 4],mDx2) : math::WENO5(v[ 6]-v[ 5],v[ 5]-v[ 4],v[ 4]-v[ 0], v[ 0]-v[ 3],v[ 3]-v[ 2],mDx2), V[1]>0 ? math::WENO5(v[ 8]-v[ 7],v[ 9]-v[ 8],v[ 0]-v[ 9], v[10]-v[ 0],v[11]-v[10],mDx2) : math::WENO5(v[12]-v[11],v[11]-v[10],v[10]-v[ 0], v[ 0]-v[ 9],v[ 9]-v[ 8],mDx2), V[2]>0 ? math::WENO5(v[14]-v[13],v[15]-v[14],v[ 0]-v[15], v[16]-v[ 0],v[17]-v[16],mDx2) : math::WENO5(v[18]-v[17],v[17]-v[16],v[16]-v[ 0], v[ 0]-v[15],v[15]-v[14],mDx2)); } /// Return the gradient computed at the previously buffered /// location by second-order central differencing. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline math::Vec3 gradient() const { return mInv2Dx * math::Vec3(mStencil[ 4] - mStencil[ 3], mStencil[10] - mStencil[ 9], mStencil[16] - mStencil[15]); } /// Return the Laplacian computed at the previously buffered /// location by second-order central differencing. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline ValueType laplacian() const { return mInvDx2 * ( mStencil[ 3] + mStencil[ 4] + mStencil[ 9] + mStencil[10] + mStencil[15] + mStencil[16] - 6*mStencil[0]); } /// Return @c true if the sign of the value at the center point of the stencil /// differs from the sign of any of its six nearest neighbors inline bool zeroCrossing() const { const typename BaseType::BufferType& v = mStencil; return (v[ 0]>0 ? (v[ 3]<0 || v[ 4]<0 || v[ 9]<0 || v[10]<0 || v[15]<0 || v[16]<0) : (v[ 3]>0 || v[ 4]>0 || v[ 9]>0 || v[10]>0 || v[15]>0 || v[16]>0)); } private: inline void init(const Coord& ijk) { mStencil[ 1] = mCache.getValue(ijk.offsetBy(-3, 0, 0)); mStencil[ 2] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); mStencil[ 3] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[ 4] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[ 5] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); mStencil[ 6] = mCache.getValue(ijk.offsetBy( 3, 0, 0)); mStencil[ 7] = mCache.getValue(ijk.offsetBy( 0, -3, 0)); mStencil[ 8] = mCache.getValue(ijk.offsetBy( 0, -2, 0)); mStencil[ 9] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); mStencil[10] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[11] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); mStencil[12] = mCache.getValue(ijk.offsetBy( 0, 3, 0)); mStencil[13] = mCache.getValue(ijk.offsetBy( 0, 0, -3)); mStencil[14] = mCache.getValue(ijk.offsetBy( 0, 0, -2)); mStencil[15] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); mStencil[16] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[17] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); mStencil[18] = mCache.getValue(ijk.offsetBy( 0, 0, 3)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; const ValueType mDx2, mInv2Dx, mInvDx2; }; // WenoStencil class ////////////////////////////////////////////////////////////////////// template class CurvatureStencil: public BaseStencil, GridT, IsSafe> { typedef CurvatureStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridT::ValueType ValueType; static const int SIZE = 19; CurvatureStencil(const GridType& grid) : BaseType(grid, SIZE) , mInv2Dx(ValueType(0.5 / grid.voxelSize()[0])) , mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) { } CurvatureStencil(const GridType& grid, Real dx) : BaseType(grid, SIZE) , mInv2Dx(ValueType(0.5 / dx)) , mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) { } /// @brief Return the mean curvature at the previously buffered location. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline ValueType meanCurvature() { Real alpha, beta; return this->meanCurvature(alpha, beta) ? ValueType(alpha*mInv2Dx/math::Pow3(beta)) : 0; } /// Return the mean curvature multiplied by the norm of the /// central-difference gradient. This method is very useful for /// mean-curvature flow of level sets! /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline ValueType meanCurvatureNormGrad() { Real alpha, beta; return this->meanCurvature(alpha, beta) ? ValueType(alpha*mInvDx2/(2*math::Pow2(beta))) : 0; } /// Return the Laplacian computed at the previously buffered /// location by second-order central differencing. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline ValueType laplacian() const { return mInvDx2 * ( mStencil[1] + mStencil[2] + mStencil[3] + mStencil[4] + mStencil[5] + mStencil[6] - 6*mStencil[0]); } /// Return the gradient computed at the previously buffered /// location by second-order central differencing. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline math::Vec3 gradient() { return math::Vec3( mStencil[2] - mStencil[1], mStencil[4] - mStencil[3], mStencil[6] - mStencil[5])*mInv2Dx; } private: inline void init(const Coord &ijk) { mStencil[ 1] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[ 2] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[ 3] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); mStencil[ 4] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[ 5] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); mStencil[ 6] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[ 7] = mCache.getValue(ijk.offsetBy(-1, -1, 0)); mStencil[ 8] = mCache.getValue(ijk.offsetBy( 1, -1, 0)); mStencil[ 9] = mCache.getValue(ijk.offsetBy(-1, 1, 0)); mStencil[10] = mCache.getValue(ijk.offsetBy( 1, 1, 0)); mStencil[11] = mCache.getValue(ijk.offsetBy(-1, 0, -1)); mStencil[12] = mCache.getValue(ijk.offsetBy( 1, 0, -1)); mStencil[13] = mCache.getValue(ijk.offsetBy(-1, 0, 1)); mStencil[14] = mCache.getValue(ijk.offsetBy( 1, 0, 1)); mStencil[15] = mCache.getValue(ijk.offsetBy( 0, -1, -1)); mStencil[16] = mCache.getValue(ijk.offsetBy( 0, 1, -1)); mStencil[17] = mCache.getValue(ijk.offsetBy( 0, -1, 1)); mStencil[18] = mCache.getValue(ijk.offsetBy( 0, 1, 1)); } inline bool meanCurvature(Real& alpha, Real& beta) const { // For performance all finite differences are unscaled wrt dx const Real Half(0.5), Quarter(0.25), Dx = Half * (mStencil[2] - mStencil[1]), Dx2 = Dx * Dx, // * 1/dx Dy = Half * (mStencil[4] - mStencil[3]), Dy2 = Dy * Dy, // * 1/dx Dz = Half * (mStencil[6] - mStencil[5]), Dz2 = Dz * Dz, // * 1/dx normGrad = Dx2 + Dy2 + Dz2; if (normGrad <= math::Tolerance::value()) { alpha = beta = 0; return false; } const Real Dxx = mStencil[2] - 2 * mStencil[0] + mStencil[1], // * 1/dx2 Dyy = mStencil[4] - 2 * mStencil[0] + mStencil[3], // * 1/dx2 Dzz = mStencil[6] - 2 * mStencil[0] + mStencil[5], // * 1/dx2 Dxy = Quarter * (mStencil[10] - mStencil[ 8] + mStencil[7] - mStencil[ 9]), // * 1/dx2 Dxz = Quarter * (mStencil[14] - mStencil[12] + mStencil[11] - mStencil[13]), // * 1/dx2 Dyz = Quarter * (mStencil[18] - mStencil[16] + mStencil[15] - mStencil[17]); // * 1/dx2 alpha = (Dx2*(Dyy+Dzz)+Dy2*(Dxx+Dzz)+Dz2*(Dxx+Dyy)-2*(Dx*(Dy*Dxy+Dz*Dxz)+Dy*Dz*Dyz)); beta = std::sqrt(normGrad); // * 1/dx return true; } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; const ValueType mInv2Dx, mInvDx2; }; // CurvatureStencil class ////////////////////////////////////////////////////////////////////// /// @brief Dense stencil of a given width template class DenseStencil: public BaseStencil, GridT, IsSafe> { typedef DenseStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; DenseStencil(const GridType& grid, int halfWidth) : BaseType(grid, /*size=*/math::Pow3(2 * halfWidth + 1)) , mHalfWidth(halfWidth) { assert(halfWidth>0); } inline const ValueType& getCenterValue() const { return mStencil[(mStencil.size()-1)>>1]; } /// @brief Initialize the stencil buffer with the values of voxel (x, y, z) /// and its neighbors. inline void moveTo(const Coord& ijk) { BaseType::mCenter = ijk; this->init(ijk); } /// @brief Initialize the stencil buffer with the values of voxel /// (x, y, z) and its neighbors. template inline void moveTo(const IterType& iter) { BaseType::mCenter = iter.getCoord(); this->init(BaseType::mCenter); } private: /// Initialize the stencil buffer centered at (i, j, k). /// @warning The center point is NOT at mStencil[0] for this DenseStencil! inline void init(const Coord& ijk) { int n = 0; for (Coord p=ijk.offsetBy(-mHalfWidth), q=ijk.offsetBy(mHalfWidth); p[0] <= q[0]; ++p[0]) { for (p[1] = ijk[1]-mHalfWidth; p[1] <= q[1]; ++p[1]) { for (p[2] = ijk[2]-mHalfWidth; p[2] <= q[2]; ++p[2]) { mStencil[n++] = mCache.getValue(p); } } } } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; const int mHalfWidth; };// DenseStencil class } // end math namespace } // namespace OPENVDB_VERSION_NAME } // end openvdb namespace #endif // OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/QuantizedUnitVec.h0000644000000000000000000001307012603226506015560 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 = static_cast((x * w)); uint16_t ybits = static_cast((y * w)); // The remaining 13 bits in our 16 bit word are dividied into a // 6-bit x-slot and a 7-bit y-slot. Both the xbits and the ybits // can still be represented using (2^7 - 1) quantization levels. // If the xbits requre more than 6-bits, store the complement. // (xbits + ybits < 127, thus if xbits > 63 => ybits <= 63) if(xbits > 63) { xbits = static_cast(127 - xbits); ybits = static_cast(127 - ybits); } // Pack components into their respective slots. data = static_cast(data | (xbits << 7)); data = static_cast(data | ybits); return data; } inline Vec3s QuantizedUnitVec::unpack(const uint16_t data) { if (!sInitialized) init(); const float w = sNormalizationWeights[data & MASK_SLOTS]; uint16_t xbits = static_cast((data & MASK_XSLOT) >> 7); uint16_t ybits = static_cast(data & MASK_YSLOT); // Check if the complement components where stored and revert. if ((xbits + ybits) > 126) { xbits = static_cast(127 - xbits); ybits = static_cast(127 - ybits); } Vec3s vec(float(xbits) * w, float(ybits) * w, float(126 - xbits - ybits) * w); if (data & MASK_XSIGN) vec[0] = -vec[0]; if (data & MASK_YSIGN) vec[1] = -vec[1]; if (data & MASK_ZSIGN) vec[2] = -vec[2]; return vec; } //////////////////////////////////////// inline void QuantizedUnitVec::flipSignBits(uint16_t& v) { v = static_cast((v & MASK_SLOTS) | (~v & ~MASK_SLOTS)); } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_QUANTIZED_UNIT_VEC_HAS_BEEN_INCLUDED // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Vec4.h0000644000000000000000000004273612603226506013132 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 four arguments, e.g. Vec4f v(1,2,3,4); Vec4(T x, T y, T z, T w) { this->mm[0] = x; this->mm[1] = y; this->mm[2] = z; this->mm[3] = w; } /// Constructor with array argument, e.g. float a[4]; Vec4f v(a); template Vec4(Source *a) { this->mm[0] = a[0]; this->mm[1] = a[1]; this->mm[2] = a[2]; this->mm[3] = a[3]; } /// Conversion constructor template explicit Vec4(const Tuple<4, Source> &v) { this->mm[0] = static_cast(v[0]); this->mm[1] = static_cast(v[1]); this->mm[2] = static_cast(v[2]); this->mm[3] = static_cast(v[3]); } /// 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; } template inline bool isApproxEqual(const Vec4& a, const Vec4& b) { return a.eq(b); } template inline bool isApproxEqual(const Vec4& a, const Vec4& b, const Vec4& eps) { return isApproxEqual(a[0], b[0], eps[0]) && isApproxEqual(a[1], b[1], eps[1]) && isApproxEqual(a[2], b[2], eps[2]) && isApproxEqual(a[3], b[3], eps[3]); } template inline bool isFinite(const Vec4& v) { return isFinite(v[0]) && isFinite(v[1]) && isFinite(v[2]) && isFinite(v[3]); } template inline Vec4 Abs(const Vec4& v) { return Vec4(Abs(v[0]), Abs(v[1]), Abs(v[2]), Abs(v[3])); } /// @remark We are switching to a more explicit name because the semantics /// are different from std::min/max. In that case, the function returns a /// reference to one of the objects based on a comparator. Here, we must /// fabricate a new object which might not match either of the inputs. /// Return component-wise minimum of the two vectors. template inline Vec4 minComponent(const Vec4 &v1, const Vec4 &v2) { return Vec4( std::min(v1.x(), v2.x()), std::min(v1.y(), v2.y()), std::min(v1.z(), v2.z()), std::min(v1.w(), v2.w())); } /// Return component-wise maximum of the two vectors. template inline Vec4 maxComponent(const Vec4 &v1, const Vec4 &v2) { return Vec4( std::max(v1.x(), v2.x()), std::max(v1.y(), v2.y()), std::max(v1.z(), v2.z()), std::max(v1.w(), v2.w())); } /// @brief Return a vector with the exponent applied to each of /// the components of the input vector. template inline Vec4 Exp(Vec4 v) { return v.exp(); } 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/metadata/0000755000000000000000000000000012603226506012773 5ustar rootrootopenvdb/metadata/Metadata.h0000644000000000000000000002541312603226506014671 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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; Metadata() {} 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 the given metadata into this metadata. virtual void copy(const Metadata& other) = 0; /// Return a textual representation of this 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 @c true if the given metadata is equivalent to this metadata. bool operator==(const Metadata& other) const; /// Return @c true if the given metadata is different from this metadata. bool operator!=(const Metadata& other) const { return !(*this == other); } /// Return the size of this metadata in bytes. virtual Index32 size() const = 0; /// Unserialize this metadata from a stream. void read(std::istream&); /// Serialize this metadata to a stream. void write(std::ostream&) const; /// Create new metadata of the given type. static Metadata::Ptr createMetadata(const Name& typeName); /// Return @c true if the given type is known by the metadata type registry. static bool isRegisteredType(const Name& typeName); /// Clear out the metadata registry. static void clearRegistry(); /// 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); protected: /// Read the size of the metadata from a stream. static Index32 readSize(std::istream&); /// Write the size of the metadata to a stream. void writeSize(std::ostream&) const; /// Read the metadata from a stream. virtual void readValue(std::istream&, Index32 numBytes) = 0; /// Write the metadata to a stream. virtual void writeValue(std::ostream&) const = 0; 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; TypedMetadata(); TypedMetadata(const T& value); TypedMetadata(const TypedMetadata& other); virtual ~TypedMetadata(); virtual Name typeName() const; virtual Metadata::Ptr copy() const; virtual void copy(const Metadata& other); virtual std::string str() const; virtual bool asBool() const; 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(); } /// Create new metadata of this type. static Metadata::Ptr createMetadata(); static void registerType(); static void unregisterType(); static bool isRegisteredType(); protected: virtual void readValue(std::istream&, Index32 numBytes); 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/metadata/MetaMap.cc0000644000000000000000000001577112603226506014641 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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) { this->insertMeta(other); } 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 the 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::insertMeta(const MetaMap& other) { for (ConstMetaIterator it = other.beginMeta(), end = other.endMeta(); it != end; ++it) { if (it->second) this->insertMeta(it->first, *it->second); } } void MetaMap::removeMeta(const Name &name) { MetaIterator iter = mMeta.find(name); if (iter != mMeta.end()) { mMeta.erase(iter); } } bool MetaMap::operator==(const MetaMap& other) const { // Check if the two maps have the same number of elements. if (this->mMeta.size() != other.mMeta.size()) return false; // Iterate over the two maps in sorted order. for (ConstMetaIterator it = beginMeta(), otherIt = other.beginMeta(), end = endMeta(); it != end; ++it, ++otherIt) { // Check if the two keys match. if (it->first != otherIt->first) return false; // Check if the two values are either both null or both non-null pointers. if (bool(it->second) != bool(otherIt->second)) return false; // If the two values are both non-null, compare their contents. if (it->second && otherIt->second && *it->second != *otherIt->second) return false; } return true; } std::string MetaMap::str(const std::string& indent) const { std::ostringstream ostr; char sep[2] = { 0, 0 }; for (ConstMetaIterator iter = beginMeta(); iter != endMeta(); ++iter) { ostr << sep << indent << iter->first; if (iter->second) { const std::string value = iter->second->str(); if (!value.empty()) ostr << ": " << value; } sep[0] = '\n'; } return ostr.str(); } std::ostream& operator<<(std::ostream& ostr, const MetaMap& metamap) { ostr << metamap.str(); return ostr; } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/metadata/Metadata.cc0000644000000000000000000001301412603226506015021 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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(); } //////////////////////////////////////// bool Metadata::operator==(const Metadata& other) const { if (other.size() != this->size()) return false; if (other.typeName() != this->typeName()) return false; std::ostringstream bytes(std::ios_base::binary), otherBytes(std::ios_base::binary); try { this->writeValue(bytes); other.writeValue(otherBytes); return (bytes.str() == otherBytes.str()); } catch (Exception&) {} return false; } //////////////////////////////////////// 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/metadata/StringMetadata.h0000644000000000000000000000510012603226506016047 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 Index32(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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/metadata/MetaMap.h0000644000000000000000000002231712603226506014475 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2015 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 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 { /// Container that maps names (strings) to values of arbitrary types 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 MetaMap() {} MetaMap(const MetaMap& other); 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 a deep copy of another map to this map. MetaMap& operator=(const MetaMap&); /// Unserialize metadata from the given stream. void readMeta(std::istream&); /// Serialize metadata to the given stream. void writeMeta(std::ostream&) const; /// @brief Insert a new metadata field or overwrite the value of an existing field. /// @details If a field with the given name doesn't already exist, add a new field. /// Otherwise, if the new value's type is the same as the existing field's value type, /// overwrite the existing value with new value. /// @throw TypeError if a field with the given name already exists, but its value type /// is not the same as the new value's /// @throw ValueError if the given field name is empty. void insertMeta(const Name&, const Metadata& value); /// @brief Deep copy all of the metadata fields from the given map into this map. /// @throw TypeError if any field in the given map has the same name as /// but a different value type than one of this map's fields. void insertMeta(const MetaMap&); /// Remove the given metadata field if it exists. void removeMeta(const Name&); //@{ /// @brief Return a pointer to the metadata with the given name. /// If no such field exists, return a null pointer. Metadata::Ptr operator[](const Name&); Metadata::ConstPtr operator[](const Name&) const; //@} //@{ /// @brief Return a pointer to a TypedMetadata object of type @c T and with the given name. /// If no such field exists or if there is a type mismatch, return a null pointer. template typename T::Ptr getMetadata(const Name&); template typename T::ConstPtr getMetadata(const Name&) const; //@} /// @brief Return a reference to the value of type @c T stored in the given metadata field. /// @throw LookupError if no field with the given name exists. /// @throw TypeError if the given field is not of type @c T. template T& metaValue(const Name&); template const T& metaValue(const 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(); } /// Return a string describing this metadata map. Prefix each line with @a indent. std::string str(const std::string& indent = "") const; /// Return @c true if the given map is equivalent to this map. bool operator==(const MetaMap& other) const; /// Return @c true if the given map is different from this map. bool operator!=(const MetaMap& other) const { return !(*this == other); } private: /// @brief 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-2015 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ )