From ec2a10fbdeec44ad3c7ef53adceacb0240e7c6fd Mon Sep 17 00:00:00 2001 From: Mathieu Malaterre Date: Tue, 29 Nov 2022 08:14:07 +0100 Subject: [PATCH 01/17] Use fixed-width integers to remove warnings (32bits) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes: /<>/nanovdb/nanovdb/unittest/TestNanoVDB.cc:1729:16: warning: unsigned conversion from ‘long long int’ to ‘size_t’ {aka ‘unsigned int’} changes value from ‘45634923663’ to ‘2685250703’ [-Woverflow] 1729 | offset = 45634923663; | ^~~~~~~~~~~ /<>/nanovdb/nanovdb/unittest/TestNanoVDB.cc:1757:16: warning: unsigned conversion from ‘long long int’ to ‘size_t’ {aka ‘unsigned int’} changes value from ‘45634923663’ to ‘2685250703’ [-Woverflow] 1757 | offset = 45634923663; | ^~~~~~~~~~~ /<>/nanovdb/nanovdb/unittest/TestNanoVDB.cc: In member function ‘virtual void TestNanoVDB_BasicGrid_Test::TestBody()’: /<>/nanovdb/nanovdb/unittest/TestNanoVDB.cc:2286:39: warning: narrowing conversion of ‘nanovdb::Grid, 4>, 5> > > >::memUsage()’ from ‘uint64_t’ {aka ‘long long unsigned int’} to ‘size_t’ {aka ‘unsigned int’} [-Wnarrowing] 2286 | size_t bytes[6] = {GridT::memUsage(), TreeT::memUsage(), RootT::memUsage(1), NodeT2::memUsage(), NodeT1::memUsage(), LeafT::DataType::memUsage()}; | ~~~~~~~~~~~~~~~^~ /<>/nanovdb/nanovdb/unittest/TestNanoVDB.cc:2286:58: warning: narrowing conversion of ‘nanovdb::Tree, 4>, 5> > >::memUsage()’ from ‘uint64_t’ {aka ‘long long unsigned int’} to ‘size_t’ {aka ‘unsigned int’} [-Wnarrowing] 2286 | size_t bytes[6] = {GridT::memUsage(), TreeT::memUsage(), RootT::memUsage(1), NodeT2::memUsage(), NodeT1::memUsage(), LeafT::DataType::memUsage()}; | ~~~~~~~~~~~~~~~^~ /<>/nanovdb/nanovdb/unittest/TestNanoVDB.cc:2286:77: warning: narrowing conversion of ‘nanovdb::RootNode, 4>, 5> >::memUsage(1)’ from ‘uint64_t’ {aka ‘long long unsigned int’} to ‘size_t’ {aka ‘unsigned int’} [-Wnarrowing] 2286 | size_t bytes[6] = {GridT::memUsage(), TreeT::memUsage(), RootT::memUsage(1), NodeT2::memUsage(), NodeT1::memUsage(), LeafT::DataType::memUsage()}; | ~~~~~~~~~~~~~~~^~~ /<>/nanovdb/nanovdb/unittest/TestNanoVDB.cc:2286:147: warning: narrowing conversion of ‘nanovdb::LeafData::memUsage()’ from ‘uint64_t’ {aka ‘long long unsigned int’} to ‘size_t’ {aka ‘unsigned int’} [-Wnarrowing] 2286 | size_t bytes[6] = {GridT::memUsage(), TreeT::memUsage(), RootT::memUsage(1), NodeT2::memUsage(), NodeT1::memUsage(), LeafT::DataType::memUsage()}; | ~~~~~~~~~~~~~~~~~~~~~~~~~^~ Signed-off-by: Mathieu Malaterre --- nanovdb/nanovdb/unittest/TestNanoVDB.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nanovdb/nanovdb/unittest/TestNanoVDB.cc b/nanovdb/nanovdb/unittest/TestNanoVDB.cc index d4cc3bf6ee..22558626c5 100644 --- a/nanovdb/nanovdb/unittest/TestNanoVDB.cc +++ b/nanovdb/nanovdb/unittest/TestNanoVDB.cc @@ -1723,7 +1723,7 @@ TEST_F(TestNanoVDB, InternalNodeValueMask) EXPECT_EQ(end-start, 28);// padding is 28 bytes // use padding for an offset and check that it doesn't interfere with other data - size_t &offset = *reinterpret_cast(&(data->mStdDevi)+1); + uint64_t &offset = *reinterpret_cast(&(data->mStdDevi)+1); data->mStdDevi = true; data->mTable[0].child = 123434214; offset = 45634923663; @@ -1751,7 +1751,7 @@ TEST_F(TestNanoVDB, InternalNodeValueMask) EXPECT_EQ(end-start, 28);// padding is 28 bytes // use padding for an offset and check that it doesn't interfere with other data - size_t &offset = *reinterpret_cast(&(data->mStdDevi)+1); + uint64_t &offset = *reinterpret_cast(&(data->mStdDevi)+1); data->mStdDevi = true; data->mTable[0].child = 123434214; offset = 45634923663; @@ -2283,7 +2283,7 @@ TEST_F(TestNanoVDB, BasicGrid) EXPECT_EQ(sizeof(TreeT), nanovdb::AlignUp(4*8 + 3*4 + 3*4 + 8)); EXPECT_EQ(sizeof(TreeT), size_t(4*8 + 3*4 + 3*4 + 8));// should already be 32 byte aligned - size_t bytes[6] = {GridT::memUsage(), TreeT::memUsage(), RootT::memUsage(1), NodeT2::memUsage(), NodeT1::memUsage(), LeafT::DataType::memUsage()}; + uint64_t bytes[6] = {GridT::memUsage(), TreeT::memUsage(), RootT::memUsage(1), NodeT2::memUsage(), NodeT1::memUsage(), LeafT::DataType::memUsage()}; for (int i = 1; i < 6; ++i) bytes[i] += bytes[i - 1]; // Byte offsets to: tree, root, internal nodes, leafs, total std::unique_ptr pool(new uint8_t[bytes[5] + NANOVDB_DATA_ALIGNMENT]); From c0bb9fe950a8d85327a9a2814db7b2d34065c802 Mon Sep 17 00:00:00 2001 From: jlait Date: Wed, 25 Jan 2023 12:01:42 -0500 Subject: [PATCH 02/17] Replace boost::uid with a simple string. Unlike the comments, however, we were NOT storing UUID as an ascii string, we were instead storing a fixed byte array of the ascii version of the UUID string. This means we have to continue to save a uuid-like string to ensure old readers will not fail (the boost::uuid will just fail if it doesn't like where hyphens are) Do not attempt to format the UUID as a random UUID, instead just randomize the entire string; this is not meant to interoperate with any other UUID system. Generate the random string purely from std::random_device so we get maximum entropy. This can throw system specific exceptions, so catch those and produce an always-fail state of an empty string. These files may not load in older versions as the nil string I think will be rejected by the boost uuid reader; but I also don't think this can occur in modern systems with /dev/urandom. Signed-off-by: jlait --- openvdb/openvdb/io/Archive.cc | 93 +++++++++++++++++++++++++++++------ openvdb/openvdb/io/Archive.h | 5 +- 2 files changed, 80 insertions(+), 18 deletions(-) diff --git a/openvdb/openvdb/io/Archive.cc b/openvdb/openvdb/io/Archive.cc index 0da3530a8e..6c763c9d2b 100644 --- a/openvdb/openvdb/io/Archive.cc +++ b/openvdb/openvdb/io/Archive.cc @@ -42,9 +42,6 @@ namespace boost { namespace interprocess { namespace detail {} namespace ipcdeta #endif #endif // OPENVDB_USE_DELAYED_LOADING -#include -#include - #include #include // for std::find_if() @@ -608,7 +605,7 @@ getErrorString() Archive::Archive() : mFileVersion(OPENVDB_FILE_VERSION) , mLibraryVersion(OPENVDB_LIBRARY_MAJOR_VERSION, OPENVDB_LIBRARY_MINOR_VERSION) - , mUuid(boost::uuids::nil_uuid()) + , mUuid() , mInputHasGridOffsets(false) , mEnableInstancing(true) , mCompression(DEFAULT_COMPRESSION_FLAGS) @@ -635,13 +632,17 @@ Archive::copy() const std::string Archive::getUniqueTag() const { - return boost::uuids::to_string(mUuid); + return mUuid; } bool Archive::isIdentical(const std::string& uuidStr) const { + // If either uuids are blank, they will always fail to match + // as something went wrong generating them. + if (uuidStr.empty()) return false; + if (getUniqueTag().empty()) return false; return uuidStr == getUniqueTag(); } @@ -1013,16 +1014,37 @@ Archive::readHeader(std::istream& is) } // 6) Read the 16-byte (128-bit) uuid. - boost::uuids::uuid oldUuid = mUuid; + std::string oldUuid = mUuid; if (mFileVersion >= OPENVDB_FILE_VERSION_BOOST_UUID) { - // UUID is stored as an ASCII string. - is >> mUuid; + // UUID is stored as fixed-length ASCII string + // The extra 4 bytes are for the hyphens. + char uuidValues[16*2+4+1]; + is.read(uuidValues, 16*2+4); + uuidValues[16*2+4] = 0; + mUuid = uuidValues; } 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())); + char uuidStr[33]; + auto to_hex = [](char c) -> char + { + c &= 0xf; + if (c < 10) return '0' + c; + return c - 10 + 'A'; + }; + for (int i = 0; i < 16; i++) + { + uuidStr[i*2] = to_hex(uuidBytes[i] >> 4); + uuidStr[i*2+1] = to_hex(uuidBytes[i]); + } + uuidStr[33] = 0; + mUuid = uuidStr; } + + // CHeck if new and old uuid differ. If either are blank, they + // differ because an error occurred. + if (oldUuid.empty() || mUuid.empty()) return true; return oldUuid != mUuid; // true if UUID in input stream differs from old UUID } @@ -1051,12 +1073,53 @@ Archive::writeHeader(std::ostream& os, bool seekable) const // 5) Write a flag indicating that this stream contains compressed leaf data. // (Omitted as of version 222) - // 6) Generate a new random 16-byte (128-bit) uuid and write it to the stream. - std::mt19937 ran; - ran.seed(std::mt19937::result_type(std::random_device()() + std::time(nullptr))); - boost::uuids::basic_random_generator gen(&ran); - mUuid = gen(); // mUuid is mutable - os << mUuid; + // 6) Generate a new random 16-byte (128-bit) sequence and write it to the stream. + + char uuidStr[16*2+4+1]; + auto to_hex = [](unsigned int c) -> char + { + c &= 0xf; + if (c < 10) return '0' + c; + return c - 10 + 'A'; + }; + + try + { + std::random_device seed; + for (int i = 0; i < 4; i++) + { + unsigned int v = seed(); + // This writes out in reverse direction of bit order, but + // as source is random we don't mind. + for (int j = 0; j < 8; j++) + { + uuidStr[i*8+j] = to_hex(v); + v >>= 4; + } + } + } + catch (std::exception&) + { + // We could have failed due to running out of entropy, but hopefully + // most platforms use /dev/urandom equivalent... + // Create a blank UUID that means it should always fail comparisons. + uuidStr[0] = 0; + } + // Insert our hyphens. + for (int i = 0; i < 4; i++) + uuidStr[16*2+i] = '-'; + std::swap(uuidStr[16*2+0], uuidStr[8+0]); + std::swap(uuidStr[16*2+1], uuidStr[12+1]); + std::swap(uuidStr[16*2+2], uuidStr[16+2]); + std::swap(uuidStr[16*2+3], uuidStr[20+3]); + uuidStr[16*2+4] = 0; + mUuid = uuidStr; // mUuid is mutable + // We don't write a string; but instead a fixed length buffer. + // To match the old UUID, we need an extra 4 bytes for hyphens. + for (int i = 0; i < 16*2+4; i++) + { + os << uuidStr[i]; + } } diff --git a/openvdb/openvdb/io/Archive.h b/openvdb/openvdb/io/Archive.h index ac0c300a3f..8b4e483860 100644 --- a/openvdb/openvdb/io/Archive.h +++ b/openvdb/openvdb/io/Archive.h @@ -10,7 +10,6 @@ #include #include #include // for VersionId -#include #include #include #include @@ -179,8 +178,8 @@ class OPENVDB_API Archive 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! + /// Unique tag, a random 16-byte (128-bit) value, stored as a string format. + mutable std::string 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; From ffdf6971f08fe5d298a5b53abe7ca328fbd9bdb0 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Thu, 16 Feb 2023 10:14:59 -0800 Subject: [PATCH 03/17] Fixed precision bug in tree::RangeIterator Signed-off-by: Ken Museth --- openvdb/openvdb/tree/TreeIterator.h | 16 +++++++++++----- pendingchanges/fix_RangeIterator.txt | 2 ++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 pendingchanges/fix_RangeIterator.txt diff --git a/openvdb/openvdb/tree/TreeIterator.h b/openvdb/openvdb/tree/TreeIterator.h index 328d170a44..0f81f7399a 100644 --- a/openvdb/openvdb/tree/TreeIterator.h +++ b/openvdb/openvdb/tree/TreeIterator.h @@ -1302,6 +1302,9 @@ template class IteratorRange { public: + /// @brief Constructor from iterator and grain size + /// @param iter Iterator from which the range is constructed + /// @param grainSize Grain size which controls the granularity of range splitting IteratorRange(const IterT& iter, size_t grainSize = 8): mIter(iter), mGrainSize(grainSize), @@ -1309,6 +1312,10 @@ class IteratorRange { mSize = this->size(); } + + /// @brief Split constructor used by tbb (should rarely be called directly) + /// @param other IteratorRange to be split + /// @param tbb::split Dummy class used to create a unique signature for this constructor IteratorRange(IteratorRange& other, tbb::split): mIter(other.mIter), mGrainSize(other.mGrainSize), @@ -1331,7 +1338,7 @@ class IteratorRange 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) {} } + void increment(size_t 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. @@ -1339,16 +1346,15 @@ class IteratorRange bool next() { this->increment(); return this->test(); } private: - Index size() const { Index n = 0; for (IterT it(mIter); it.test(); ++n, ++it) {} return n; } + size_t size() const { size_t n = 0; for (IterT it(mIter); it.test(); ++n, ++it) {} return n; } IterT mIter; - size_t mGrainSize; + size_t mGrainSize, mSize; /// @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; -}; +};// class IteratorRange //////////////////////////////////////// diff --git a/pendingchanges/fix_RangeIterator.txt b/pendingchanges/fix_RangeIterator.txt new file mode 100644 index 0000000000..4d621d8dfb --- /dev/null +++ b/pendingchanges/fix_RangeIterator.txt @@ -0,0 +1,2 @@ +Bug-fix: +- Internal counters in tree::RangeIterator were limited to 32bit precision. They are now extended to size_t. (reported by SpaceX) From ddaff1f0f02df5d9ffdab678f33851536c294511 Mon Sep 17 00:00:00 2001 From: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> Date: Sat, 25 Feb 2023 16:07:44 +0000 Subject: [PATCH 04/17] Fixed a crash with GCC11 in Tree::treeType() Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> --- openvdb/openvdb/tree/Tree.h | 15 +++++---------- pendingchanges/init_fix.txt | 4 ++++ 2 files changed, 9 insertions(+), 10 deletions(-) create mode 100644 pendingchanges/init_fix.txt diff --git a/openvdb/openvdb/tree/Tree.h b/openvdb/openvdb/tree/Tree.h index 609be1e41b..ecb85768c5 100644 --- a/openvdb/openvdb/tree/Tree.h +++ b/openvdb/openvdb/tree/Tree.h @@ -1038,13 +1038,8 @@ class Tree: public TreeBase RootNodeType mRoot; // root node of the tree mutable AccessorRegistry mAccessorRegistry; mutable ConstAccessorRegistry mConstAccessorRegistry; - - static std::unique_ptr sTreeTypeName; }; // end of Tree class -template -std::unique_ptr Tree<_RootNodeType>::sTreeTypeName; - /// @brief Tree3::Type is the type of a three-level tree /// (Root, Internal, Leaf) with value type T and @@ -1805,9 +1800,9 @@ template inline const Name& Tree::treeType() { - static std::once_flag once; - std::call_once(once, []() + static std::string sTreeTypeName = []() { + // @todo use RootNode::NodeChain::foreach() instead std::vector dims; Tree::getNodeLog2Dims(dims); std::ostringstream ostr; @@ -1815,9 +1810,9 @@ Tree::treeType() for (size_t i = 1, N = dims.size(); i < N; ++i) { // start from 1 to skip the RootNode ostr << "_" << dims[i]; } - sTreeTypeName.reset(new Name(ostr.str())); - }); - return *sTreeTypeName; + return ostr.str(); + }(); + return sTreeTypeName; } diff --git a/pendingchanges/init_fix.txt b/pendingchanges/init_fix.txt new file mode 100644 index 0000000000..641d30d93d --- /dev/null +++ b/pendingchanges/init_fix.txt @@ -0,0 +1,4 @@ +OpenVDB: + - Bug Fixes: + Fixed a bug when compiling with -fvisibility=hidden and GCC 11 which + would cause a crash in openvdb::initialize() From 040893418c24da08c1c3334661f60507eb6ee0b0 Mon Sep 17 00:00:00 2001 From: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> Date: Sat, 4 Mar 2023 21:16:32 +0000 Subject: [PATCH 05/17] Moved most internal point tool implementations into points/impl Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> --- openvdb/openvdb/CMakeLists.txt | 11 + openvdb/openvdb/points/PointAttribute.h | 389 +------- openvdb/openvdb/points/PointConversion.h | 886 +----------------- openvdb/openvdb/points/PointCount.h | 132 +-- openvdb/openvdb/points/PointDelete.h | 194 +--- openvdb/openvdb/points/PointGroup.h | 600 +----------- openvdb/openvdb/points/PointMask.h | 343 +------ openvdb/openvdb/points/PointMove.h | 750 +-------------- openvdb/openvdb/points/PointReplicate.h | 273 +----- openvdb/openvdb/points/PointSample.h | 446 +-------- openvdb/openvdb/points/PointScatter.h | 406 +------- openvdb/openvdb/points/PointStatistics.h | 524 +---------- .../openvdb/points/impl/PointAttributeImpl.h | 406 ++++++++ .../openvdb/points/impl/PointConversionImpl.h | 849 +++++++++++++++++ openvdb/openvdb/points/impl/PointCountImpl.h | 142 +++ openvdb/openvdb/points/impl/PointDeleteImpl.h | 209 +++++ openvdb/openvdb/points/impl/PointGroupImpl.h | 614 ++++++++++++ openvdb/openvdb/points/impl/PointMaskImpl.h | 358 +++++++ openvdb/openvdb/points/impl/PointMoveImpl.h | 766 +++++++++++++++ .../openvdb/points/impl/PointReplicateImpl.h | 287 ++++++ openvdb/openvdb/points/impl/PointSampleImpl.h | 457 +++++++++ .../openvdb/points/impl/PointScatterImpl.h | 424 +++++++++ .../openvdb/points/impl/PointStatisticsImpl.h | 544 +++++++++++ 23 files changed, 5114 insertions(+), 4896 deletions(-) create mode 100644 openvdb/openvdb/points/impl/PointAttributeImpl.h create mode 100644 openvdb/openvdb/points/impl/PointConversionImpl.h create mode 100644 openvdb/openvdb/points/impl/PointCountImpl.h create mode 100644 openvdb/openvdb/points/impl/PointDeleteImpl.h create mode 100644 openvdb/openvdb/points/impl/PointGroupImpl.h create mode 100644 openvdb/openvdb/points/impl/PointMaskImpl.h create mode 100644 openvdb/openvdb/points/impl/PointMoveImpl.h create mode 100644 openvdb/openvdb/points/impl/PointReplicateImpl.h create mode 100644 openvdb/openvdb/points/impl/PointSampleImpl.h create mode 100644 openvdb/openvdb/points/impl/PointScatterImpl.h create mode 100644 openvdb/openvdb/points/impl/PointStatisticsImpl.h diff --git a/openvdb/openvdb/CMakeLists.txt b/openvdb/openvdb/CMakeLists.txt index 923157debb..79ffebc83d 100644 --- a/openvdb/openvdb/CMakeLists.txt +++ b/openvdb/openvdb/CMakeLists.txt @@ -468,9 +468,20 @@ set(OPENVDB_LIBRARY_POINTS_INCLUDE_FILES ) set(OPENVDB_LIBRARY_POINTS_IMPL_INCLUDE_FILES + points/impl/PointAttributeImpl.h + points/impl/PointConversionImpl.h + points/impl/PointCountImpl.h + points/impl/PointDeleteImpl.h + points/impl/PointGroupImpl.h + points/impl/PointMaskImpl.h + points/impl/PointMoveImpl.h points/impl/PointRasterizeFrustumImpl.h points/impl/PointRasterizeSDFImpl.h points/impl/PointRasterizeTrilinearImpl.h + points/impl/PointReplicateImpl.h + points/impl/PointSampleImpl.h + points/impl/PointScatterImpl.h + points/impl/PointStatisticsImpl.h ) set(OPENVDB_LIBRARY_TOOLS_INCLUDE_FILES diff --git a/openvdb/openvdb/points/PointAttribute.h b/openvdb/openvdb/points/PointAttribute.h index c0d76467f4..8b54930c47 100644 --- a/openvdb/openvdb/points/PointAttribute.h +++ b/openvdb/openvdb/points/PointAttribute.h @@ -154,395 +154,10 @@ template inline void compactAttributes(PointDataTreeT& tree); -//////////////////////////////////////// - -/// @cond OPENVDB_DOCS_INTERNAL - -namespace point_attribute_internal { - - -template -inline void collapseAttribute(AttributeArray& array, - const AttributeSet::Descriptor&, const ValueType& uniformValue) -{ - AttributeWriteHandle handle(array); - handle.collapse(uniformValue); -} - - -inline void collapseAttribute(AttributeArray& array, - const AttributeSet::Descriptor& descriptor, const Name& uniformValue) -{ - StringAttributeWriteHandle handle(array, descriptor.getMetadata()); - handle.collapse(uniformValue); -} - - -//////////////////////////////////////// - - -template -struct AttributeTypeConversion -{ - static const NamePair& type() { - return TypedAttributeArray::attributeType(); - } -}; - - -template -struct AttributeTypeConversion -{ - static const NamePair& type() { return StringAttributeArray::attributeType(); } -}; - - -//////////////////////////////////////// - - -template -struct MetadataStorage -{ - static void add(PointDataTreeT&, const ValueType&) {} - - template - static void add(PointDataTreeT&, const AttributeListType&) {} -}; - - -template -struct MetadataStorage -{ - static void add(PointDataTreeT& tree, const Name& uniformValue) { - MetaMap& metadata = makeDescriptorUnique(tree)->getMetadata(); - StringMetaInserter inserter(metadata); - inserter.insert(uniformValue); - } - - template - static void add(PointDataTreeT& tree, const AttributeListType& data) { - MetaMap& metadata = makeDescriptorUnique(tree)->getMetadata(); - StringMetaInserter inserter(metadata); - Name value; - - for (size_t i = 0; i < data.size(); i++) { - data.get(value, i); - inserter.insert(value); - } - } -}; - - -} // namespace point_attribute_internal - -/// @endcond - - -//////////////////////////////////////// - - -template -inline void appendAttribute(PointDataTreeT& tree, - const Name& name, - const NamePair& type, - const Index strideOrTotalSize, - const bool constantStride, - const Metadata* defaultValue, - const bool hidden, - const bool transient) -{ - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - // do not append a non-unique attribute - - const auto& descriptor = iter->attributeSet().descriptor(); - const size_t index = descriptor.find(name); - - if (index != AttributeSet::INVALID_POS) { - OPENVDB_THROW(KeyError, - "Cannot append an attribute with a non-unique name - " << name << "."); - } - - // create a new attribute descriptor - - auto newDescriptor = descriptor.duplicateAppend(name, type); - - // store the attribute default value in the descriptor metadata - - if (defaultValue) { - newDescriptor->setDefaultValue(name, *defaultValue); - } - - // extract new pos - - const size_t pos = newDescriptor->find(name); - - // acquire registry lock to avoid locking when appending attributes in parallel - - AttributeArray::ScopedRegistryLock lock; - - // insert attributes using the new descriptor - - tree::LeafManager leafManager(tree); - leafManager.foreach( - [&](typename PointDataTreeT::LeafNodeType& leaf, size_t /*idx*/) { - auto expected = leaf.attributeSet().descriptorPtr(); - - auto attribute = leaf.appendAttribute(*expected, newDescriptor, - pos, strideOrTotalSize, constantStride, defaultValue, - &lock); - - if (hidden) attribute->setHidden(true); - if (transient) attribute->setTransient(true); - }, /*threaded=*/ true - ); -} - - -//////////////////////////////////////// - - -template -inline void appendAttribute(PointDataTreeT& tree, - const std::string& name, - const ValueType& uniformValue, - const Index strideOrTotalSize, - const bool constantStride, - const TypedMetadata* defaultValue, - const bool hidden, - const bool transient) -{ - static_assert(!std::is_base_of::value, - "ValueType must not be derived from AttributeArray"); - - using point_attribute_internal::AttributeTypeConversion; - using point_attribute_internal::Default; - using point_attribute_internal::MetadataStorage; - - appendAttribute(tree, name, AttributeTypeConversion::type(), - strideOrTotalSize, constantStride, defaultValue, hidden, transient); - - // if the uniform value is equal to either the default value provided - // through the metadata argument or the default value for this value type, - // it is not necessary to perform the collapse - - const bool uniformIsDefault = math::isExactlyEqual(uniformValue, - bool(defaultValue) ? defaultValue->value() : Default::value()); - if (!uniformIsDefault) { - MetadataStorage::add(tree, uniformValue); - collapseAttribute(tree, name, uniformValue); - } -} - - -//////////////////////////////////////// - - -template -inline void collapseAttribute( PointDataTreeT& tree, - const Name& name, - const ValueType& uniformValue) -{ - static_assert(!std::is_base_of::value, - "ValueType must not be derived from AttributeArray"); - - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - const auto& descriptor = iter->attributeSet().descriptor(); - - // throw if attribute name does not exist - - const size_t index = descriptor.find(name); - if (index == AttributeSet::INVALID_POS) { - OPENVDB_THROW(KeyError, "Cannot find attribute name in PointDataTree."); - } - - tree::LeafManager leafManager(tree); - leafManager.foreach( - [&](typename PointDataTreeT::LeafNodeType& leaf, size_t /*idx*/) { - assert(leaf.hasAttribute(index)); - AttributeArray& array = leaf.attributeArray(index); - point_attribute_internal::collapseAttribute( - array, descriptor, uniformValue); - }, /*threaded=*/true - ); -} - - -//////////////////////////////////////// - - -template -inline void dropAttributes( PointDataTreeT& tree, - const std::vector& indices) -{ - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - const auto& descriptor = iter->attributeSet().descriptor(); - - // throw if position index present in the indices as this attribute is mandatory - - const size_t positionIndex = descriptor.find("P"); - if (positionIndex!= AttributeSet::INVALID_POS && - std::find(indices.begin(), indices.end(), positionIndex) != indices.end()) { - OPENVDB_THROW(KeyError, "Cannot drop mandatory position attribute."); - } - - // insert attributes using the new descriptor - - auto newDescriptor = descriptor.duplicateDrop(indices); - - tree::LeafManager leafManager(tree); - leafManager.foreach( - [&](typename PointDataTreeT::LeafNodeType& leaf, size_t /*idx*/) { - auto expected = leaf.attributeSet().descriptorPtr(); - leaf.dropAttributes(indices, *expected, newDescriptor); - }, /*threaded=*/true - ); -} - - -//////////////////////////////////////// - - -template -inline void dropAttributes( PointDataTreeT& tree, - const std::vector& names) -{ - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - const AttributeSet& attributeSet = iter->attributeSet(); - const AttributeSet::Descriptor& descriptor = attributeSet.descriptor(); - - std::vector indices; - - for (const Name& name : names) { - const size_t index = descriptor.find(name); - - // do not attempt to drop an attribute that does not exist - if (index == AttributeSet::INVALID_POS) { - OPENVDB_THROW(KeyError, - "Cannot drop an attribute that does not exist - " << name << "."); - } - - indices.push_back(index); - } - - dropAttributes(tree, indices); -} - - -//////////////////////////////////////// - - -template -inline void dropAttribute( PointDataTreeT& tree, - const size_t& index) -{ - std::vector indices{index}; - dropAttributes(tree, indices); -} - - -template -inline void dropAttribute( PointDataTreeT& tree, - const Name& name) -{ - std::vector names{name}; - dropAttributes(tree, names); -} - - -//////////////////////////////////////// - - -template -inline void renameAttributes( PointDataTreeT& tree, - const std::vector& oldNames, - const std::vector& newNames) -{ - if (oldNames.size() != newNames.size()) { - OPENVDB_THROW(ValueError, "Mis-matching sizes of name vectors, cannot rename attributes."); - } - - using Descriptor = AttributeSet::Descriptor; - - auto iter = tree.beginLeaf(); - - if (!iter) return; - - const AttributeSet& attributeSet = iter->attributeSet(); - const Descriptor::Ptr descriptor = attributeSet.descriptorPtr(); - auto newDescriptor = std::make_shared(*descriptor); - - for (size_t i = 0; i < oldNames.size(); i++) { - const Name& oldName = oldNames[i]; - if (descriptor->find(oldName) == AttributeSet::INVALID_POS) { - OPENVDB_THROW(KeyError, "Cannot find requested attribute - " << oldName << "."); - } - - const Name& newName = newNames[i]; - if (descriptor->find(newName) != AttributeSet::INVALID_POS) { - OPENVDB_THROW(KeyError, - "Cannot rename attribute as new name already exists - " << newName << "."); - } - - const AttributeArray* array = attributeSet.getConst(oldName); - assert(array); - - if (isGroup(*array)) { - OPENVDB_THROW(KeyError, "Cannot rename group attribute - " << oldName << "."); - } - - newDescriptor->rename(oldName, newName); - } - - for (; iter; ++iter) { - iter->renameAttributes(*descriptor, newDescriptor); - } -} - - -template -inline void renameAttribute(PointDataTreeT& tree, - const Name& oldName, - const Name& newName) -{ - renameAttributes(tree, {oldName}, {newName}); -} - - -//////////////////////////////////////// - - -template -inline void compactAttributes(PointDataTreeT& tree) -{ - auto iter = tree.beginLeaf(); - if (!iter) return; - - tree::LeafManager leafManager(tree); - leafManager.foreach( - [&](typename PointDataTreeT::LeafNodeType& leaf, size_t /*idx*/) { - leaf.compactAttributes(); - }, /*threaded=*/ true - ); -} - - -//////////////////////////////////////// - - } // namespace points } // namespace OPENVDB_VERSION_NAME } // namespace openvdb +#include "impl/PointAttributeImpl.h" + #endif // OPENVDB_POINTS_POINT_ATTRIBUTE_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/PointConversion.h b/openvdb/openvdb/points/PointConversion.h index fbc685c957..f73ddc0554 100644 --- a/openvdb/openvdb/points/PointConversion.h +++ b/openvdb/openvdb/points/PointConversion.h @@ -32,6 +32,33 @@ OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace points { +//////////////////////////////////////// + + +/// @brief Point-partitioner compatible STL vector attribute wrapper for convenience +template +class PointAttributeVector { +public: + using PosType = ValueType; + using value_type= ValueType; + + PointAttributeVector(const std::vector& data, + const Index stride = 1) + : mData(data) + , mStride(stride) { } + + size_t size() const { return mData.size(); } + void getPos(size_t n, ValueType& xyz) const { xyz = mData[n]; } + void get(ValueType& value, size_t n) const { value = mData[n]; } + void get(ValueType& value, size_t n, openvdb::Index m) const { value = mData[n * mStride + m]; } + +private: + const std::vector& mData; + const Index mStride; +}; // PointAttributeVector + + +//////////////////////////////////////// /// @brief Localises points with position into a @c PointDataGrid into two stages: /// allocation of the leaf attribute data and population of the positions. @@ -196,865 +223,10 @@ computeVoxelSize( const PositionWrapper& positions, const Index decimalPlaces = 5, InterrupterT* const interrupter = nullptr); - -//////////////////////////////////////// - - -/// @brief Point-partitioner compatible STL vector attribute wrapper for convenience -template -class PointAttributeVector { -public: - using PosType = ValueType; - using value_type= ValueType; - - PointAttributeVector(const std::vector& data, - const Index stride = 1) - : mData(data) - , mStride(stride) { } - - size_t size() const { return mData.size(); } - void getPos(size_t n, ValueType& xyz) const { xyz = mData[n]; } - void get(ValueType& value, size_t n) const { value = mData[n]; } - void get(ValueType& value, size_t n, openvdb::Index m) const { value = mData[n * mStride + m]; } - -private: - const std::vector& mData; - const Index mStride; -}; // PointAttributeVector - - -//////////////////////////////////////// - -/// @cond OPENVDB_DOCS_INTERNAL - -namespace point_conversion_internal { - - -// ConversionTraits to create the relevant Attribute Handles from a LeafNode -template struct ConversionTraits -{ - using Handle = AttributeHandle; - using WriteHandle = AttributeWriteHandle; - static T zero() { return zeroVal(); } - template - static std::unique_ptr handleFromLeaf(const LeafT& leaf, const Index index) { - const AttributeArray& array = leaf.constAttributeArray(index); - return std::make_unique(array); - } - template - static std::unique_ptr writeHandleFromLeaf(LeafT& leaf, const Index index) { - AttributeArray& array = leaf.attributeArray(index); - return std::make_unique(array); - } -}; // ConversionTraits -template <> struct ConversionTraits -{ - using Handle = StringAttributeHandle; - using WriteHandle = StringAttributeWriteHandle; - static openvdb::Name zero() { return ""; } - template - static std::unique_ptr handleFromLeaf(const LeafT& leaf, const Index index) { - const AttributeArray& array = leaf.constAttributeArray(index); - const AttributeSet::Descriptor& descriptor = leaf.attributeSet().descriptor(); - return std::make_unique(array, descriptor.getMetadata()); - } - template - static std::unique_ptr writeHandleFromLeaf(LeafT& leaf, const Index index) { - AttributeArray& array = leaf.attributeArray(index); - const AttributeSet::Descriptor& descriptor = leaf.attributeSet().descriptor(); - return std::make_unique(array, descriptor.getMetadata()); - } -}; // ConversionTraits - -template< typename PointDataTreeType, - typename PointIndexTreeType, - typename AttributeListType> -struct PopulateAttributeOp { - - using LeafManagerT = typename tree::LeafManager; - using LeafRangeT = typename LeafManagerT::LeafRange; - using PointIndexLeafNode = typename PointIndexTreeType::LeafNodeType; - using IndexArray = typename PointIndexLeafNode::IndexArray; - using ValueType = typename AttributeListType::value_type; - using HandleT = typename ConversionTraits::WriteHandle; - - PopulateAttributeOp(const PointIndexTreeType& pointIndexTree, - const AttributeListType& data, - const size_t index, - const Index stride = 1) - : mPointIndexTree(pointIndexTree) - , mData(data) - , mIndex(index) - , mStride(stride) { } - - void operator()(const typename LeafManagerT::LeafRange& range) const { - - for (auto leaf = range.begin(); leaf; ++leaf) { - - // obtain the PointIndexLeafNode (using the origin of the current leaf) - - const PointIndexLeafNode* pointIndexLeaf = - mPointIndexTree.probeConstLeaf(leaf->origin()); - - if (!pointIndexLeaf) continue; - - auto attributeWriteHandle = - ConversionTraits::writeHandleFromLeaf(*leaf, static_cast(mIndex)); - - Index64 index = 0; - - const IndexArray& indices = pointIndexLeaf->indices(); - - for (const Index64 leafIndex: indices) - { - ValueType value; - for (Index i = 0; i < mStride; i++) { - mData.get(value, leafIndex, i); - attributeWriteHandle->set(static_cast(index), i, value); - } - index++; - } - - // attempt to compact the array - - attributeWriteHandle->compact(); - } - } - - ////////// - - const PointIndexTreeType& mPointIndexTree; - const AttributeListType& mData; - const size_t mIndex; - const Index mStride; -}; - -template -struct ConvertPointDataGridPositionOp { - - using LeafNode = typename PointDataTreeType::LeafNodeType; - using ValueType = typename Attribute::ValueType; - using HandleT = typename Attribute::Handle; - using SourceHandleT = AttributeHandle; - using LeafManagerT = typename tree::LeafManager; - using LeafRangeT = typename LeafManagerT::LeafRange; - - ConvertPointDataGridPositionOp( Attribute& attribute, - const std::vector& pointOffsets, - const Index64 startOffset, - const math::Transform& transform, - const size_t index, - const FilterT& filter, - const bool inCoreOnly) - : mAttribute(attribute) - , mPointOffsets(pointOffsets) - , mStartOffset(startOffset) - , mTransform(transform) - , mIndex(index) - , mFilter(filter) - , mInCoreOnly(inCoreOnly) - { - // only accept Vec3f as ValueType - static_assert(VecTraits::Size == 3 && - std::is_floating_point::value, - "ValueType is not Vec3f"); - } - - template - void convert(IterT& iter, HandleT& targetHandle, - SourceHandleT& sourceHandle, Index64& offset) const - { - for (; iter; ++iter) { - const Vec3d xyz = iter.getCoord().asVec3d(); - const Vec3d pos = sourceHandle.get(*iter); - targetHandle.set(static_cast(offset++), /*stride=*/0, - mTransform.indexToWorld(pos + xyz)); - } - } - - void operator()(const LeafRangeT& range) const - { - HandleT pHandle(mAttribute); - - for (auto leaf = range.begin(); leaf; ++leaf) { - - assert(leaf.pos() < mPointOffsets.size()); - - if (mInCoreOnly && leaf->buffer().isOutOfCore()) continue; - - Index64 offset = mStartOffset; - - if (leaf.pos() > 0) offset += mPointOffsets[leaf.pos() - 1]; - - auto handle = SourceHandleT::create(leaf->constAttributeArray(mIndex)); - - if (mFilter.state() == index::ALL) { - auto iter = leaf->beginIndexOn(); - convert(iter, pHandle, *handle, offset); - } - else { - auto iter = leaf->beginIndexOn(mFilter); - convert(iter, pHandle, *handle, offset); - } - } - } - - ////////// - - Attribute& mAttribute; - const std::vector& mPointOffsets; - const Index64 mStartOffset; - const math::Transform& mTransform; - const size_t mIndex; - const FilterT& mFilter; - const bool mInCoreOnly; -}; // ConvertPointDataGridPositionOp - - -template -struct ConvertPointDataGridAttributeOp { - - using LeafNode = typename PointDataTreeType::LeafNodeType; - using ValueType = typename Attribute::ValueType; - using HandleT = typename Attribute::Handle; - using SourceHandleT = typename ConversionTraits::Handle; - using LeafManagerT = typename tree::LeafManager; - using LeafRangeT = typename LeafManagerT::LeafRange; - - ConvertPointDataGridAttributeOp(Attribute& attribute, - const std::vector& pointOffsets, - const Index64 startOffset, - const size_t index, - const Index stride, - const FilterT& filter, - const bool inCoreOnly) - : mAttribute(attribute) - , mPointOffsets(pointOffsets) - , mStartOffset(startOffset) - , mIndex(index) - , mStride(stride) - , mFilter(filter) - , mInCoreOnly(inCoreOnly) { } - - template - void convert(IterT& iter, HandleT& targetHandle, - SourceHandleT& sourceHandle, Index64& offset) const - { - if (sourceHandle.isUniform()) { - const ValueType uniformValue(sourceHandle.get(0)); - for (; iter; ++iter) { - for (Index i = 0; i < mStride; i++) { - targetHandle.set(static_cast(offset), i, uniformValue); - } - offset++; - } - } - else { - for (; iter; ++iter) { - for (Index i = 0; i < mStride; i++) { - targetHandle.set(static_cast(offset), i, - sourceHandle.get(*iter, /*stride=*/i)); - } - offset++; - } - } - } - - void operator()(const LeafRangeT& range) const - { - HandleT pHandle(mAttribute); - - for (auto leaf = range.begin(); leaf; ++leaf) { - - assert(leaf.pos() < mPointOffsets.size()); - - if (mInCoreOnly && leaf->buffer().isOutOfCore()) continue; - - Index64 offset = mStartOffset; - - if (leaf.pos() > 0) offset += mPointOffsets[leaf.pos() - 1]; - - auto handle = ConversionTraits::handleFromLeaf( - *leaf, static_cast(mIndex)); - - if (mFilter.state() == index::ALL) { - auto iter = leaf->beginIndexOn(); - convert(iter, pHandle, *handle, offset); - } else { - auto iter = leaf->beginIndexOn(mFilter); - convert(iter, pHandle, *handle, offset); - } - } - } - - ////////// - - Attribute& mAttribute; - const std::vector& mPointOffsets; - const Index64 mStartOffset; - const size_t mIndex; - const Index mStride; - const FilterT& mFilter; - const bool mInCoreOnly; -}; // ConvertPointDataGridAttributeOp - -template -struct ConvertPointDataGridGroupOp { - - using LeafNode = typename PointDataTreeType::LeafNodeType; - using GroupIndex = AttributeSet::Descriptor::GroupIndex; - using LeafManagerT = typename tree::LeafManager; - using LeafRangeT = typename LeafManagerT::LeafRange; - - ConvertPointDataGridGroupOp(Group& group, - const std::vector& pointOffsets, - const Index64 startOffset, - const AttributeSet::Descriptor::GroupIndex index, - const FilterT& filter, - const bool inCoreOnly) - : mGroup(group) - , mPointOffsets(pointOffsets) - , mStartOffset(startOffset) - , mIndex(index) - , mFilter(filter) - , mInCoreOnly(inCoreOnly) { } - - template - void convert(IterT& iter, const GroupAttributeArray& groupArray, Index64& offset) const - { - const auto bitmask = static_cast(1 << mIndex.second); - - if (groupArray.isUniform()) { - if (groupArray.get(0) & bitmask) { - for (; iter; ++iter) { - mGroup.setOffsetOn(static_cast(offset)); - offset++; - } - } - } - else { - for (; iter; ++iter) { - if (groupArray.get(*iter) & bitmask) { - mGroup.setOffsetOn(static_cast(offset)); - } - offset++; - } - } - } - - void operator()(const LeafRangeT& range) const - { - for (auto leaf = range.begin(); leaf; ++leaf) { - - assert(leaf.pos() < mPointOffsets.size()); - - if (mInCoreOnly && leaf->buffer().isOutOfCore()) continue; - - Index64 offset = mStartOffset; - - if (leaf.pos() > 0) offset += mPointOffsets[leaf.pos() - 1]; - - const AttributeArray& array = leaf->constAttributeArray(mIndex.first); - assert(isGroup(array)); - const GroupAttributeArray& groupArray = GroupAttributeArray::cast(array); - - if (mFilter.state() == index::ALL) { - auto iter = leaf->beginIndexOn(); - convert(iter, groupArray, offset); - } - else { - auto iter = leaf->beginIndexOn(mFilter); - convert(iter, groupArray, offset); - } - } - } - - ////////// - - Group& mGroup; - const std::vector& mPointOffsets; - const Index64 mStartOffset; - const GroupIndex mIndex; - const FilterT& mFilter; - const bool mInCoreOnly; -}; // ConvertPointDataGridGroupOp - -template -struct CalculatePositionBounds -{ - CalculatePositionBounds(const PositionArrayT& positions, - const math::Mat4d& inverse) - : mPositions(positions) - , mInverseMat(inverse) - , mMin(std::numeric_limits::max()) - , mMax(-std::numeric_limits::max()) {} - - CalculatePositionBounds(const CalculatePositionBounds& other, tbb::split) - : mPositions(other.mPositions) - , mInverseMat(other.mInverseMat) - , mMin(std::numeric_limits::max()) - , mMax(-std::numeric_limits::max()) {} - - void operator()(const tbb::blocked_range& range) { - VecT pos; - for (size_t n = range.begin(), N = range.end(); n != N; ++n) { - mPositions.getPos(n, pos); - pos = mInverseMat.transform(pos); - mMin = math::minComponent(mMin, pos); - mMax = math::maxComponent(mMax, pos); - } - } - - void join(const CalculatePositionBounds& other) { - mMin = math::minComponent(mMin, other.mMin); - mMax = math::maxComponent(mMax, other.mMax); - } - - BBoxd getBoundingBox() const { - return BBoxd(mMin, mMax); - } - -private: - const PositionArrayT& mPositions; - const math::Mat4d& mInverseMat; - VecT mMin, mMax; -}; - -} // namespace point_conversion_internal - -/// @endcond - -//////////////////////////////////////// - - -template -inline typename PointDataGridT::Ptr -createPointDataGrid(const PointIndexGridT& pointIndexGrid, - const PositionArrayT& positions, - const math::Transform& xform, - const Metadata* positionDefaultValue) -{ - using PointDataTreeT = typename PointDataGridT::TreeType; - using LeafT = typename PointDataTreeT::LeafNodeType; - using PointIndexLeafT = typename PointIndexGridT::TreeType::LeafNodeType; - using PointIndexT = typename PointIndexLeafT::ValueType; - using LeafManagerT = typename tree::LeafManager; - using PositionAttributeT = TypedAttributeArray; - - const NamePair positionType = PositionAttributeT::attributeType(); - - // construct the Tree using a topology copy of the PointIndexGrid - - const auto& pointIndexTree = pointIndexGrid.tree(); - typename PointDataTreeT::Ptr treePtr(new PointDataTreeT(pointIndexTree)); - - // create attribute descriptor from position type - - auto descriptor = AttributeSet::Descriptor::create(positionType); - - // add default value for position if provided - - if (positionDefaultValue) descriptor->setDefaultValue("P", *positionDefaultValue); - - // retrieve position index - - const size_t positionIndex = descriptor->find("P"); - assert(positionIndex != AttributeSet::INVALID_POS); - - // acquire registry lock to avoid locking when appending attributes in parallel - - AttributeArray::ScopedRegistryLock lock; - - // populate position attribute - - LeafManagerT leafManager(*treePtr); - leafManager.foreach( - [&](LeafT& leaf, size_t /*idx*/) { - - // obtain the PointIndexLeafNode (using the origin of the current leaf) - - const auto* pointIndexLeaf = pointIndexTree.probeConstLeaf(leaf.origin()); - assert(pointIndexLeaf); - - // initialise the attribute storage - - Index pointCount(static_cast(pointIndexLeaf->indices().size())); - leaf.initializeAttributes(descriptor, pointCount, &lock); - - // create write handle for position - - auto attributeWriteHandle = AttributeWriteHandle::create( - leaf.attributeArray(positionIndex)); - - Index index = 0; - - const PointIndexT - *begin = static_cast(nullptr), - *end = static_cast(nullptr); - - // iterator over every active voxel in the point index leaf - - for (auto iter = pointIndexLeaf->cbeginValueOn(); iter; ++iter) { - - // find the voxel center - - const Coord& ijk = iter.getCoord(); - const Vec3d& positionCellCenter(ijk.asVec3d()); - - // obtain pointers for this voxel from begin to end in the indices array - - pointIndexLeaf->getIndices(ijk, begin, end); - - while (begin < end) { - - typename PositionArrayT::value_type positionWorldSpace; - positions.getPos(*begin, positionWorldSpace); - - // compute the index-space position and then subtract the voxel center - - const Vec3d positionIndexSpace = xform.worldToIndex(positionWorldSpace); - const Vec3f positionVoxelSpace(positionIndexSpace - positionCellCenter); - - attributeWriteHandle->set(index++, positionVoxelSpace); - - ++begin; - } - } - }, - /*threaded=*/true); - - auto grid = PointDataGridT::create(treePtr); - grid->setTransform(xform.copy()); - return grid; -} - - -//////////////////////////////////////// - - -template -inline typename PointDataGridT::Ptr -createPointDataGrid(const std::vector& positions, - const math::Transform& xform, - const Metadata* positionDefaultValue) -{ - const PointAttributeVector pointList(positions); - - tools::PointIndexGrid::Ptr pointIndexGrid = - tools::createPointIndexGrid(pointList, xform); - return createPointDataGrid( - *pointIndexGrid, pointList, xform, positionDefaultValue); -} - - -//////////////////////////////////////// - - -template -inline void -populateAttribute(PointDataTreeT& tree, const PointIndexTreeT& pointIndexTree, - const openvdb::Name& attributeName, const PointArrayT& data, const Index stride, - const bool insertMetadata) -{ - using point_conversion_internal::PopulateAttributeOp; - using ValueType = typename PointArrayT::value_type; - - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - const size_t index = iter->attributeSet().find(attributeName); - - if (index == AttributeSet::INVALID_POS) { - OPENVDB_THROW(KeyError, "Attribute not found to populate - " << attributeName << "."); - } - - if (insertMetadata) { - point_attribute_internal::MetadataStorage::add(tree, data); - } - - // populate attribute - - typename tree::LeafManager leafManager(tree); - - PopulateAttributeOp populate(pointIndexTree, data, index, stride); - tbb::parallel_for(leafManager.leafRange(), populate); -} - - -//////////////////////////////////////// - - -template -inline void -convertPointDataGridPosition( PositionAttribute& positionAttribute, - const PointDataGridT& grid, - const std::vector& pointOffsets, - const Index64 startOffset, - const FilterT& filter, - const bool inCoreOnly) -{ - using TreeType = typename PointDataGridT::TreeType; - using LeafManagerT = typename tree::LeafManager; - - using point_conversion_internal::ConvertPointDataGridPositionOp; - - const TreeType& tree = grid.tree(); - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - const size_t positionIndex = iter->attributeSet().find("P"); - - positionAttribute.expand(); - LeafManagerT leafManager(tree); - ConvertPointDataGridPositionOp convert( - positionAttribute, pointOffsets, startOffset, grid.transform(), positionIndex, - filter, inCoreOnly); - tbb::parallel_for(leafManager.leafRange(), convert); - positionAttribute.compact(); -} - - -//////////////////////////////////////// - - -template -inline void -convertPointDataGridAttribute( TypedAttribute& attribute, - const PointDataTreeT& tree, - const std::vector& pointOffsets, - const Index64 startOffset, - const unsigned arrayIndex, - const Index stride, - const FilterT& filter, - const bool inCoreOnly) -{ - using LeafManagerT = typename tree::LeafManager; - - using point_conversion_internal::ConvertPointDataGridAttributeOp; - - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - attribute.expand(); - LeafManagerT leafManager(tree); - ConvertPointDataGridAttributeOp convert( - attribute, pointOffsets, startOffset, arrayIndex, stride, - filter, inCoreOnly); - tbb::parallel_for(leafManager.leafRange(), convert); - attribute.compact(); -} - - -//////////////////////////////////////// - - -template -inline void -convertPointDataGridGroup( Group& group, - const PointDataTreeT& tree, - const std::vector& pointOffsets, - const Index64 startOffset, - const AttributeSet::Descriptor::GroupIndex index, - const FilterT& filter, - const bool inCoreOnly) -{ - using LeafManagerT= typename tree::LeafManager; - - using point_conversion_internal::ConvertPointDataGridGroupOp; - - auto iter = tree.cbeginLeaf(); - if (!iter) return; - - LeafManagerT leafManager(tree); - ConvertPointDataGridGroupOp convert( - group, pointOffsets, startOffset, index, - filter, inCoreOnly); - tbb::parallel_for(leafManager.leafRange(), convert); - - // must call this after modifying point groups in parallel - - group.finalize(); -} - -template -inline float -computeVoxelSize( const PositionWrapper& positions, - const uint32_t pointsPerVoxel, - const math::Mat4d transform, - const Index decimalPlaces, - InterrupterT* const interrupter) -{ - using namespace point_conversion_internal; - - struct Local { - - static bool voxelSizeFromVolume(const double volume, - const size_t estimatedVoxelCount, - float& voxelSize) - { - // dictated by the math::ScaleMap limit - static const double minimumVoxelVolume(3e-15); - static const double maximumVoxelVolume(std::numeric_limits::max()); - - double voxelVolume = volume / static_cast(estimatedVoxelCount); - bool valid = true; - - if (voxelVolume < minimumVoxelVolume) { - voxelVolume = minimumVoxelVolume; - valid = false; - } - else if (voxelVolume > maximumVoxelVolume) { - voxelVolume = maximumVoxelVolume; - valid = false; - } - - voxelSize = static_cast(math::Pow(voxelVolume, 1.0/3.0)); - return valid; - } - - static float truncate(const float voxelSize, Index decPlaces) - { - float truncatedVoxelSize = voxelSize; - - // attempt to truncate from decPlaces -> 11 - for (int i = decPlaces; i < 11; i++) { - truncatedVoxelSize = static_cast(math::Truncate(double(voxelSize), i)); - if (truncatedVoxelSize != 0.0f) break; - } - - return truncatedVoxelSize; - } - }; - - if (pointsPerVoxel == 0) OPENVDB_THROW(ValueError, "Points per voxel cannot be zero."); - - // constructed with the default voxel size as specified by openvdb interface values - - float voxelSize(0.1f); - - const size_t numPoints = positions.size(); - - // return the default voxel size if we have zero or only 1 point - - if (numPoints <= 1) return voxelSize; - - size_t targetVoxelCount(numPoints / size_t(pointsPerVoxel)); - if (targetVoxelCount == 0) targetVoxelCount++; - - // calculate the world space, transform-oriented bounding box - - math::Mat4d inverseTransform = transform.inverse(); - inverseTransform = math::unit(inverseTransform); - - tbb::blocked_range range(0, numPoints); - CalculatePositionBounds calculateBounds(positions, inverseTransform); - tbb::parallel_reduce(range, calculateBounds); - - BBoxd bbox = calculateBounds.getBoundingBox(); - - // return default size if points are coincident - - if (bbox.min() == bbox.max()) return voxelSize; - - double volume = bbox.volume(); - - // handle points that are collinear or coplanar by expanding the volume - - if (math::isApproxZero(volume)) { - Vec3d extents = bbox.extents().sorted().reversed(); - if (math::isApproxZero(extents[1])) { - // colinear (maxExtent^3) - volume = extents[0]*extents[0]*extents[0]; - } - else { - // coplanar (maxExtent*nextMaxExtent^2) - volume = extents[0]*extents[1]*extents[1]; - } - } - - double previousVolume = volume; - - if (!Local::voxelSizeFromVolume(volume, targetVoxelCount, voxelSize)) { - OPENVDB_LOG_DEBUG("Out of range, clamping voxel size."); - return voxelSize; - } - - size_t previousVoxelCount(0); - size_t voxelCount(1); - - if (interrupter) interrupter->start("Computing voxel size"); - - while (voxelCount > previousVoxelCount) - { - math::Transform::Ptr newTransform; - - if (!math::isIdentity(transform)) - { - // if using a custom transform, pre-scale by coefficients - // which define the new voxel size - - math::Mat4d matrix(transform); - matrix.preScale(Vec3d(voxelSize) / math::getScale(matrix)); - newTransform = math::Transform::createLinearTransform(matrix); - } - else - { - newTransform = math::Transform::createLinearTransform(voxelSize); - } - - // create a mask grid of the points from the calculated voxel size - // this is the same function call as tools::createPointMask() which has - // been duplicated to provide an interrupter - - MaskGrid::Ptr mask = createGrid(false); - mask->setTransform(newTransform); - tools::PointsToMask pointMaskOp(*mask, interrupter); - pointMaskOp.template addPoints(positions); - - if (interrupter && util::wasInterrupted(interrupter)) break; - - previousVoxelCount = voxelCount; - voxelCount = mask->activeVoxelCount(); - volume = math::Pow3(voxelSize) * static_cast(voxelCount); - - // stop if no change in the volume or the volume has increased - - if (volume >= previousVolume) break; - previousVolume = volume; - - const float previousVoxelSize = voxelSize; - - // compute the new voxel size and if invalid return the previous value - - if (!Local::voxelSizeFromVolume(volume, targetVoxelCount, voxelSize)) { - voxelSize = previousVoxelSize; - break; - } - - // halt convergence if the voxel size has decreased by less - // than 10% in this iteration - - if (voxelSize / previousVoxelSize > 0.9f) break; - } - - if (interrupter) interrupter->end(); - - // truncate the voxel size for readability and return the value - - return Local::truncate(voxelSize, decimalPlaces); -} - - -//////////////////////////////////////// - - } // namespace points } // namespace OPENVDB_VERSION_NAME } // namespace openvdb +#include "impl/PointConversionImpl.h" + #endif // OPENVDB_POINTS_POINT_CONVERSION_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/PointCount.h b/openvdb/openvdb/points/PointCount.h index f1905b7a10..3a77b7e110 100644 --- a/openvdb/openvdb/points/PointCount.h +++ b/openvdb/openvdb/points/PointCount.h @@ -20,13 +20,11 @@ #include - namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace points { - /// @brief Count the total number of points in a PointDataTree /// @param tree the PointDataTree in which to count the points /// @param filter an optional index filter @@ -38,7 +36,6 @@ inline Index64 pointCount( const PointDataTreeT& tree, const bool inCoreOnly = false, const bool threaded = true); - /// @brief Populate an array of cumulative point offsets per leaf node. /// @param pointOffsets array of offsets to be populated /// @param tree the PointDataTree from which to populate the offsets @@ -53,7 +50,6 @@ inline Index64 pointOffsets(std::vector& pointOffsets, const bool inCoreOnly = false, const bool threaded = true); - /// @brief Generate a new grid with voxel values to store the number of points per voxel /// @param grid the PointDataGrid to use to compute the count grid /// @param filter an optional index filter @@ -65,7 +61,6 @@ inline typename GridT::Ptr pointCountGrid( const PointDataGridT& grid, const FilterT& filter = NullFilter()); - /// @brief Generate a new grid that uses the supplied transform with voxel values to store the /// number of points per voxel. /// @param grid the PointDataGrid to use to compute the count grid @@ -80,133 +75,10 @@ pointCountGrid( const PointDataGridT& grid, const openvdb::math::Transform& transform, const FilterT& filter = NullFilter()); - -//////////////////////////////////////// - - -template -Index64 pointCount(const PointDataTreeT& tree, - const FilterT& filter, - const bool inCoreOnly, - const bool threaded) -{ - using LeafManagerT = tree::LeafManager; - using LeafRangeT = typename LeafManagerT::LeafRange; - - auto countLambda = - [&filter, &inCoreOnly] (const LeafRangeT& range, Index64 sum) -> Index64 { - for (const auto& leaf : range) { - if (inCoreOnly && leaf.buffer().isOutOfCore()) continue; - auto state = filter.state(leaf); - if (state == index::ALL) { - sum += leaf.pointCount(); - } else if (state != index::NONE) { - sum += iterCount(leaf.beginIndexAll(filter)); - } - } - return sum; - }; - - LeafManagerT leafManager(tree); - if (threaded) { - return tbb::parallel_reduce(leafManager.leafRange(), Index64(0), countLambda, - [] (Index64 n, Index64 m) -> Index64 { return n + m; }); - } - else { - return countLambda(leafManager.leafRange(), Index64(0)); - } -} - - -template -Index64 pointOffsets( std::vector& pointOffsets, - const PointDataTreeT& tree, - const FilterT& filter, - const bool inCoreOnly, - const bool threaded) -{ - using LeafT = typename PointDataTreeT::LeafNodeType; - using LeafManagerT = typename tree::LeafManager; - - // allocate and zero values in point offsets array - - pointOffsets.assign(tree.leafCount(), Index64(0)); - if (pointOffsets.empty()) return 0; - - // compute total points per-leaf - - LeafManagerT leafManager(tree); - leafManager.foreach( - [&pointOffsets, &filter, &inCoreOnly](const LeafT& leaf, size_t pos) { - if (inCoreOnly && leaf.buffer().isOutOfCore()) return; - auto state = filter.state(leaf); - if (state == index::ALL) { - pointOffsets[pos] = leaf.pointCount(); - } else if (state != index::NONE) { - pointOffsets[pos] = iterCount(leaf.beginIndexAll(filter)); - } - }, - threaded); - - // turn per-leaf totals into cumulative leaf totals - - Index64 pointOffset(pointOffsets[0]); - for (size_t n = 1; n < pointOffsets.size(); n++) { - pointOffset += pointOffsets[n]; - pointOffsets[n] = pointOffset; - } - - return pointOffset; -} - - -template -typename GridT::Ptr -pointCountGrid( const PointDataGridT& points, - const FilterT& filter) -{ - static_assert(std::is_integral::value || - std::is_floating_point::value, - "openvdb::points::pointCountGrid must return an integer or floating-point scalar grid"); - - using PointDataTreeT = typename PointDataGridT::TreeType; - using TreeT = typename GridT::TreeType; - - typename TreeT::Ptr tree = - point_mask_internal::convertPointsToScalar - (points.tree(), filter); - - typename GridT::Ptr grid(new GridT(tree)); - grid->setTransform(points.transform().copy()); - return grid; -} - - -template -typename GridT::Ptr -pointCountGrid( const PointDataGridT& points, - const openvdb::math::Transform& transform, - const FilterT& filter) -{ - static_assert( std::is_integral::value || - std::is_floating_point::value, - "openvdb::points::pointCountGrid must return an integer or floating-point scalar grid"); - - // This is safe because the PointDataGrid can only be modified by the deformer - using AdapterT = TreeAdapter; - auto& nonConstPoints = const_cast(points); - - NullDeformer deformer; - return point_mask_internal::convertPointsToScalar( - nonConstPoints, transform, filter, deformer); -} - - -//////////////////////////////////////// - - } // namespace points } // namespace OPENVDB_VERSION_NAME } // namespace openvdb +#include "impl/PointCountImpl.h" + #endif // OPENVDB_POINTS_POINT_COUNT_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/PointDelete.h b/openvdb/openvdb/points/PointDelete.h index abab4b5d35..c7504efb9e 100644 --- a/openvdb/openvdb/points/PointDelete.h +++ b/openvdb/openvdb/points/PointDelete.h @@ -22,13 +22,11 @@ #include #include - namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace points { - /// @brief Delete points that are members of specific groups /// /// @details This method will delete points which are members of any of the supplied groups and @@ -69,198 +67,10 @@ inline void deleteFromGroup(PointDataTreeT& pointTree, bool invert = false, bool drop = true); - -//////////////////////////////////////// - -/// @cond OPENVDB_DOCS_INTERNAL - -namespace point_delete_internal { - - -struct VectorWrapper -{ - using T = std::vector>; - - VectorWrapper(const T& _data) : data(_data) { } - operator bool() const { return index < data.size(); } - VectorWrapper& operator++() { index++; return *this; } - Index sourceIndex() const { assert(*this); return data[index].first; } - Index targetIndex() const { assert(*this); return data[index].second; } - -private: - const T& data; - T::size_type index = 0; -}; // struct VectorWrapper - - -template -struct DeleteByFilterOp -{ - using LeafManagerT = tree::LeafManager; - using LeafRangeT = typename LeafManagerT::LeafRange; - using LeafNodeT = typename PointDataTreeT::LeafNodeType; - using ValueType = typename LeafNodeT::ValueType; - - DeleteByFilterOp(const FilterT& filter, - const AttributeArray::ScopedRegistryLock* lock) - : mFilter(filter) - , mLock(lock) { } - - void operator()(const LeafRangeT& range) const - { - for (auto leaf = range.begin(); leaf != range.end(); ++leaf) { - - const size_t newSize = - iterCount(leaf->template beginIndexAll(mFilter)); - - // if all points are being deleted, clear the leaf attributes - if (newSize == 0) { - leaf->clearAttributes(/*updateValueMask=*/true, mLock); - continue; - } - - // early exit if no points are being deleted - - const size_t currentSize = leaf->getLastValue(); - if (newSize == currentSize) continue; - - const AttributeSet& existingAttributeSet = leaf->attributeSet(); - AttributeSet* newAttributeSet = new AttributeSet( - existingAttributeSet, static_cast(newSize), mLock); - const size_t attributeSetSize = existingAttributeSet.size(); - - // cache the attribute arrays for efficiency - - std::vector newAttributeArrays; - std::vector existingAttributeArrays; - - for (size_t i = 0; i < attributeSetSize; i++) { - AttributeArray* newArray = newAttributeSet->get(i); - const AttributeArray* existingArray = existingAttributeSet.getConst(i); - - if (!newArray->hasConstantStride() || !existingArray->hasConstantStride()) { - OPENVDB_THROW(openvdb::NotImplementedError, - "Transfer of attribute values for dynamic arrays not currently supported."); - } - - if (newArray->stride() != existingArray->stride()) { - OPENVDB_THROW(openvdb::LookupError, - "Cannot transfer attribute values with mis-matching strides."); - } - - newAttributeArrays.push_back(newArray); - existingAttributeArrays.push_back(existingArray); - } - - Index attributeIndex = 0; - std::vector endOffsets; - - endOffsets.reserve(LeafNodeT::NUM_VALUES); - - // now construct new attribute arrays which exclude data from deleted points - - std::vector> indexMapping; - indexMapping.reserve(newSize); - - for (auto voxel = leaf->cbeginValueAll(); voxel; ++voxel) { - for (auto iter = leaf->beginIndexVoxel(voxel.getCoord(), mFilter); - iter; ++iter) { - indexMapping.emplace_back(*iter, attributeIndex++); - } - endOffsets.push_back(static_cast(attributeIndex)); - } - - for (size_t i = 0; i < attributeSetSize; i++) { - VectorWrapper indexMappingWrapper(indexMapping); - newAttributeArrays[i]->copyValues(*(existingAttributeArrays[i]), indexMappingWrapper); - } - - leaf->replaceAttributeSet(newAttributeSet); - leaf->setOffsets(endOffsets); - } - } - -private: - const FilterT& mFilter; - const AttributeArray::ScopedRegistryLock* mLock; -}; // struct DeleteByFilterOp - -} // namespace point_delete_internal - -/// @endcond - -//////////////////////////////////////// - - -template -inline void deleteFromGroups(PointDataTreeT& pointTree, - const std::vector& groups, - bool invert, - bool drop) -{ - const typename PointDataTreeT::LeafCIter leafIter = pointTree.cbeginLeaf(); - - if (!leafIter) return; - - const openvdb::points::AttributeSet& attributeSet = leafIter->attributeSet(); - const AttributeSet::Descriptor& descriptor = attributeSet.descriptor(); - std::vector availableGroups; - - // determine which of the requested groups exist, and early exit - // if none are present in the tree - - for (const auto& groupName : groups) { - if (descriptor.hasGroup(groupName)) { - availableGroups.push_back(groupName); - } - } - - if (availableGroups.empty()) return; - - std::vector empty; - std::unique_ptr filter; - if (invert) { - filter.reset(new MultiGroupFilter(groups, empty, leafIter->attributeSet())); - } - else { - filter.reset(new MultiGroupFilter(empty, groups, leafIter->attributeSet())); - } - - { // acquire registry lock to avoid locking when appending attributes in parallel - - AttributeArray::ScopedRegistryLock lock; - - tree::LeafManager leafManager(pointTree); - point_delete_internal::DeleteByFilterOp deleteOp( - *filter, &lock); - tbb::parallel_for(leafManager.leafRange(), deleteOp); - } - - // remove empty leaf nodes - - tools::pruneInactive(pointTree); - - // drop the now-empty groups if requested (unless invert = true) - - if (drop && !invert) { - dropGroups(pointTree, availableGroups); - } -} - -template -inline void deleteFromGroup(PointDataTreeT& pointTree, - const std::string& group, - bool invert, - bool drop) -{ - std::vector groups(1, group); - - deleteFromGroups(pointTree, groups, invert, drop); -} - - } // namespace points } // namespace OPENVDB_VERSION_NAME } // namespace openvdb +#include "impl/PointDeleteImpl.h" + #endif // OPENVDB_POINTS_POINT_DELETE_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/PointGroup.h b/openvdb/openvdb/points/PointGroup.h index 76be8f5a39..9ad58b82d6 100644 --- a/openvdb/openvdb/points/PointGroup.h +++ b/openvdb/openvdb/points/PointGroup.h @@ -121,608 +121,10 @@ inline void setGroupByFilter( PointDataTreeT& tree, const Name& group, const FilterT& filter); - -//////////////////////////////////////// - -/// @cond OPENVDB_DOCS_INTERNAL - -namespace point_group_internal { - - -/// Copy a group attribute value from one group offset to another -template -struct CopyGroupOp { - - using LeafManagerT = typename tree::LeafManager; - using LeafRangeT = typename LeafManagerT::LeafRange; - using GroupIndex = AttributeSet::Descriptor::GroupIndex; - - CopyGroupOp(const GroupIndex& targetIndex, - const GroupIndex& sourceIndex) - : mTargetIndex(targetIndex) - , mSourceIndex(sourceIndex) { } - - void operator()(const typename LeafManagerT::LeafRange& range) const { - - for (auto leaf = range.begin(); leaf; ++leaf) { - - GroupHandle sourceGroup = leaf->groupHandle(mSourceIndex); - GroupWriteHandle targetGroup = leaf->groupWriteHandle(mTargetIndex); - - for (auto iter = leaf->beginIndexAll(); iter; ++iter) { - const bool groupOn = sourceGroup.get(*iter); - targetGroup.set(*iter, groupOn); - } - } - } - - ////////// - - const GroupIndex mTargetIndex; - const GroupIndex mSourceIndex; -}; - - -/// Set membership on or off for the specified group -template -struct SetGroupOp -{ - using LeafManagerT = typename tree::LeafManager; - using GroupIndex = AttributeSet::Descriptor::GroupIndex; - - SetGroupOp(const AttributeSet::Descriptor::GroupIndex& index) - : mIndex(index) { } - - void operator()(const typename LeafManagerT::LeafRange& range) const - { - for (auto leaf = range.begin(); leaf; ++leaf) { - - // obtain the group attribute array - - GroupWriteHandle group(leaf->groupWriteHandle(mIndex)); - - // set the group value - - group.collapse(Member); - } - } - - ////////// - - const GroupIndex& mIndex; -}; // struct SetGroupOp - - -template -struct SetGroupFromIndexOp -{ - using LeafManagerT = typename tree::LeafManager; - using LeafRangeT = typename LeafManagerT::LeafRange; - using PointIndexLeafNode = typename PointIndexTreeT::LeafNodeType; - using IndexArray = typename PointIndexLeafNode::IndexArray; - using GroupIndex = AttributeSet::Descriptor::GroupIndex; - using MembershipArray = std::vector; - - SetGroupFromIndexOp(const PointIndexTreeT& indexTree, - const MembershipArray& membership, - const GroupIndex& index) - : mIndexTree(indexTree) - , mMembership(membership) - , mIndex(index) { } - - void operator()(const typename LeafManagerT::LeafRange& range) const - { - for (auto leaf = range.begin(); leaf; ++leaf) { - - // obtain the PointIndexLeafNode (using the origin of the current leaf) - - const PointIndexLeafNode* pointIndexLeaf = mIndexTree.probeConstLeaf(leaf->origin()); - - if (!pointIndexLeaf) continue; - - // obtain the group attribute array - - GroupWriteHandle group(leaf->groupWriteHandle(mIndex)); - - // initialise the attribute storage - - Index64 index = 0; - - const IndexArray& indices = pointIndexLeaf->indices(); - - for (const Index64 i: indices) { - if (Remove) { - group.set(static_cast(index), mMembership[i]); - } else if (mMembership[i] == short(1)) { - group.set(static_cast(index), short(1)); - } - index++; - } - - // attempt to compact the array - - group.compact(); - } - } - - ////////// - - const PointIndexTreeT& mIndexTree; - const MembershipArray& mMembership; - const GroupIndex& mIndex; -}; // struct SetGroupFromIndexOp - - -template -struct SetGroupByFilterOp -{ - using LeafManagerT = typename tree::LeafManager; - using LeafRangeT = typename LeafManagerT::LeafRange; - using LeafNodeT = typename PointDataTreeT::LeafNodeType; - using GroupIndex = AttributeSet::Descriptor::GroupIndex; - - SetGroupByFilterOp( const GroupIndex& index, const FilterT& filter) - : mIndex(index) - , mFilter(filter) { } - - void operator()(const typename LeafManagerT::LeafRange& range) const - { - for (auto leaf = range.begin(); leaf; ++leaf) { - - // obtain the group attribute array - - GroupWriteHandle group(leaf->groupWriteHandle(mIndex)); - - auto iter = leaf->template beginIndex(mFilter); - - for (; iter; ++iter) { - group.set(*iter, true); - } - - // attempt to compact the array - - group.compact(); - } - } - - ////////// - - const GroupIndex& mIndex; - const FilterT& mFilter; // beginIndex takes a copy of mFilter -}; // struct SetGroupByFilterOp - - -//////////////////////////////////////// - - -} // namespace point_group_internal - -/// @endcond - -//////////////////////////////////////// - - -inline void deleteMissingPointGroups( std::vector& groups, - const AttributeSet::Descriptor& descriptor) -{ - for (auto it = groups.begin(); it != groups.end();) { - if (!descriptor.hasGroup(*it)) it = groups.erase(it); - else ++it; - } -} - - -//////////////////////////////////////// - - -template -inline void appendGroup(PointDataTreeT& tree, const Name& group) -{ - if (group.empty()) { - OPENVDB_THROW(KeyError, "Cannot use an empty group name as a key."); - } - - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - const AttributeSet& attributeSet = iter->attributeSet(); - auto descriptor = attributeSet.descriptorPtr(); - - // don't add if group already exists - - if (descriptor->hasGroup(group)) return; - - const bool hasUnusedGroup = descriptor->unusedGroups() > 0; - - // add a new group attribute if there are no unused groups - - if (!hasUnusedGroup) { - - // find a new internal group name - - const Name groupName = descriptor->uniqueName("__group"); - - descriptor = descriptor->duplicateAppend(groupName, GroupAttributeArray::attributeType()); - const size_t pos = descriptor->find(groupName); - - // insert new group attribute - - tree::LeafManager leafManager(tree); - leafManager.foreach( - [&](typename PointDataTreeT::LeafNodeType& leaf, size_t /*idx*/) { - auto expected = leaf.attributeSet().descriptorPtr(); - leaf.appendAttribute(*expected, descriptor, pos); - }, /*threaded=*/true - ); - } - else { - // make the descriptor unique before we modify the group map - - makeDescriptorUnique(tree); - descriptor = attributeSet.descriptorPtr(); - } - - // ensure that there are now available groups - - assert(descriptor->unusedGroups() > 0); - - // find next unused offset - - const size_t offset = descriptor->unusedGroupOffset(); - - // add the group mapping to the descriptor - - descriptor->setGroup(group, offset); - - // if there was an unused group then we did not need to append a new attribute, so - // we must manually clear membership in the new group as its bits may have been - // previously set - - if (hasUnusedGroup) setGroup(tree, group, false); -} - - -//////////////////////////////////////// - - -template -inline void appendGroups(PointDataTreeT& tree, - const std::vector& groups) -{ - // TODO: could be more efficient by appending multiple groups at once - // instead of one-by-one, however this is likely not that common a use case - - for (const Name& name : groups) { - appendGroup(tree, name); - } -} - - -//////////////////////////////////////// - - -template -inline void dropGroup(PointDataTreeT& tree, const Name& group, const bool compact) -{ - using Descriptor = AttributeSet::Descriptor; - - if (group.empty()) { - OPENVDB_THROW(KeyError, "Cannot use an empty group name as a key."); - } - - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - const AttributeSet& attributeSet = iter->attributeSet(); - - // make the descriptor unique before we modify the group map - - makeDescriptorUnique(tree); - Descriptor::Ptr descriptor = attributeSet.descriptorPtr(); - - // now drop the group - - descriptor->dropGroup(group); - - if (compact) { - compactGroups(tree); - } -} - - -//////////////////////////////////////// - - -template -inline void dropGroups( PointDataTreeT& tree, - const std::vector& groups) -{ - for (const Name& name : groups) { - dropGroup(tree, name, /*compact=*/false); - } - - // compaction done once for efficiency - - compactGroups(tree); -} - - -//////////////////////////////////////// - - -template -inline void dropGroups( PointDataTreeT& tree) -{ - using Descriptor = AttributeSet::Descriptor; - - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - const AttributeSet& attributeSet = iter->attributeSet(); - - // make the descriptor unique before we modify the group map - - makeDescriptorUnique(tree); - Descriptor::Ptr descriptor = attributeSet.descriptorPtr(); - - descriptor->clearGroups(); - - // find all indices for group attribute arrays - - std::vector indices = attributeSet.groupAttributeIndices(); - - // drop these attributes arrays - - dropAttributes(tree, indices); -} - - -//////////////////////////////////////// - - -template -inline void compactGroups(PointDataTreeT& tree) -{ - using Descriptor = AttributeSet::Descriptor; - using GroupIndex = Descriptor::GroupIndex; - using LeafManagerT = typename tree::template LeafManager; - - using point_group_internal::CopyGroupOp; - - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - const AttributeSet& attributeSet = iter->attributeSet(); - - // early exit if not possible to compact - - if (!attributeSet.descriptor().canCompactGroups()) return; - - // make the descriptor unique before we modify the group map - - makeDescriptorUnique(tree); - Descriptor::Ptr descriptor = attributeSet.descriptorPtr(); - - // generate a list of group offsets and move them (one-by-one) - // TODO: improve this algorithm to move multiple groups per array at once - // though this is likely not that common a use case - - Name sourceName; - size_t sourceOffset, targetOffset; - - while (descriptor->requiresGroupMove(sourceName, sourceOffset, targetOffset)) { - - const GroupIndex sourceIndex = attributeSet.groupIndex(sourceOffset); - const GroupIndex targetIndex = attributeSet.groupIndex(targetOffset); - - CopyGroupOp copy(targetIndex, sourceIndex); - LeafManagerT leafManager(tree); - tbb::parallel_for(leafManager.leafRange(), copy); - - descriptor->setGroup(sourceName, targetOffset); - } - - // drop unused attribute arrays - - const std::vector indices = attributeSet.groupAttributeIndices(); - - const size_t totalAttributesToDrop = descriptor->unusedGroups() / descriptor->groupBits(); - - assert(totalAttributesToDrop <= indices.size()); - - const std::vector indicesToDrop(indices.end() - totalAttributesToDrop, - indices.end()); - - dropAttributes(tree, indicesToDrop); -} - - -//////////////////////////////////////// - - -template -inline void setGroup( PointDataTreeT& tree, - const PointIndexTreeT& indexTree, - const std::vector& membership, - const Name& group, - const bool remove) -{ - using Descriptor = AttributeSet::Descriptor; - using LeafManagerT = typename tree::LeafManager; - using point_group_internal::SetGroupFromIndexOp; - - auto iter = tree.cbeginLeaf(); - if (!iter) return; - - const AttributeSet& attributeSet = iter->attributeSet(); - const Descriptor& descriptor = attributeSet.descriptor(); - - if (!descriptor.hasGroup(group)) { - OPENVDB_THROW(LookupError, "Group must exist on Tree before defining membership."); - } - - { - // Check that that the largest index in the PointIndexTree is smaller than the size - // of the membership vector. The index tree will be used to lookup membership - // values. If the index tree was constructed with nan positions, this index will - // differ from the PointDataTree count - - using IndexTreeManager = tree::LeafManager; - IndexTreeManager leafManager(indexTree); - - const int64_t max = tbb::parallel_reduce(leafManager.leafRange(), -1, - [](const typename IndexTreeManager::LeafRange& range, int64_t value) -> int64_t { - for (auto leaf = range.begin(); leaf; ++leaf) { - auto it = std::max_element(leaf->indices().begin(), leaf->indices().end()); - value = std::max(value, static_cast(*it)); - } - return value; - }, - [](const int64_t a, const int64_t b) { - return std::max(a, b); - } - ); - - if (max != -1 && membership.size() <= static_cast(max)) { - OPENVDB_THROW(IndexError, "Group membership vector size must be larger than " - " the maximum index within the provided index tree."); - } - } - - const Descriptor::GroupIndex index = attributeSet.groupIndex(group); - LeafManagerT leafManager(tree); - - // set membership - - if (remove) { - SetGroupFromIndexOp - set(indexTree, membership, index); - tbb::parallel_for(leafManager.leafRange(), set); - } - else { - SetGroupFromIndexOp - set(indexTree, membership, index); - tbb::parallel_for(leafManager.leafRange(), set); - } -} - - -//////////////////////////////////////// - - -template -inline void setGroup( PointDataTreeT& tree, - const Name& group, - const bool member) -{ - using Descriptor = AttributeSet::Descriptor; - using LeafManagerT = typename tree::LeafManager; - - using point_group_internal::SetGroupOp; - - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - const AttributeSet& attributeSet = iter->attributeSet(); - const Descriptor& descriptor = attributeSet.descriptor(); - - if (!descriptor.hasGroup(group)) { - OPENVDB_THROW(LookupError, "Group must exist on Tree before defining membership."); - } - - const Descriptor::GroupIndex index = attributeSet.groupIndex(group); - LeafManagerT leafManager(tree); - - // set membership based on member variable - - if (member) tbb::parallel_for(leafManager.leafRange(), SetGroupOp(index)); - else tbb::parallel_for(leafManager.leafRange(), SetGroupOp(index)); -} - - -//////////////////////////////////////// - - -template -inline void setGroupByFilter( PointDataTreeT& tree, - const Name& group, - const FilterT& filter) -{ - using Descriptor = AttributeSet::Descriptor; - using LeafManagerT = typename tree::LeafManager; - - using point_group_internal::SetGroupByFilterOp; - - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - const AttributeSet& attributeSet = iter->attributeSet(); - const Descriptor& descriptor = attributeSet.descriptor(); - - if (!descriptor.hasGroup(group)) { - OPENVDB_THROW(LookupError, "Group must exist on Tree before defining membership."); - } - - const Descriptor::GroupIndex index = attributeSet.groupIndex(group); - - // set membership using filter - - SetGroupByFilterOp set(index, filter); - LeafManagerT leafManager(tree); - - tbb::parallel_for(leafManager.leafRange(), set); -} - - -//////////////////////////////////////// - - -template -inline void setGroupByRandomTarget( PointDataTreeT& tree, - const Name& group, - const Index64 targetPoints, - const unsigned int seed = 0) -{ - using RandomFilter = RandomLeafFilter; - - RandomFilter filter(tree, targetPoints, seed); - - setGroupByFilter(tree, group, filter); -} - - -//////////////////////////////////////// - - -template -inline void setGroupByRandomPercentage( PointDataTreeT& tree, - const Name& group, - const float percentage = 10.0f, - const unsigned int seed = 0) -{ - using RandomFilter = RandomLeafFilter; - - const int currentPoints = static_cast(pointCount(tree)); - const int targetPoints = int(math::Round((percentage * float(currentPoints))/100.0f)); - - RandomFilter filter(tree, targetPoints, seed); - - setGroupByFilter(tree, group, filter); -} - - -//////////////////////////////////////// - - } // namespace points } // namespace OPENVDB_VERSION_NAME } // namespace openvdb +#include "impl/PointGroupImpl.h" #endif // OPENVDB_POINTS_POINT_GROUP_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/PointMask.h b/openvdb/openvdb/points/PointMask.h index bd5b4ff44a..cb01992cfc 100644 --- a/openvdb/openvdb/points/PointMask.h +++ b/openvdb/openvdb/points/PointMask.h @@ -21,7 +21,6 @@ #include #include - namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { @@ -88,348 +87,10 @@ struct DeformerTraits static const bool IndexSpace = false; }; - -//////////////////////////////////////// - -/// @cond OPENVDB_DOCS_INTERNAL - -namespace point_mask_internal { - -template -void voxelSum(LeafT& leaf, const Index offset, const typename LeafT::ValueType& value) -{ - leaf.modifyValue(offset, tools::valxform::SumOp(value)); -} - -// overload PointDataLeaf access to use setOffsetOn(), as modifyValue() -// is intentionally disabled to avoid accidental usage - -template -void voxelSum(PointDataLeafNode& leaf, const Index offset, - const typename PointDataLeafNode::ValueType& value) -{ - leaf.setOffsetOn(offset, leaf.getValue(offset) + value); -} - - -/// @brief Combines multiple grids into one by stealing leaf nodes and summing voxel values -/// This class is designed to work with thread local storage containers such as tbb::combinable -template -struct GridCombinerOp -{ - using CombinableT = typename tbb::combinable; - - using TreeT = typename GridT::TreeType; - using LeafT = typename TreeT::LeafNodeType; - using ValueType = typename TreeT::ValueType; - using SumOp = tools::valxform::SumOp; - - GridCombinerOp(GridT& grid) - : mTree(grid.tree()) {} - - void operator()(const GridT& grid) - { - for (auto leaf = grid.tree().beginLeaf(); leaf; ++leaf) { - auto* newLeaf = mTree.probeLeaf(leaf->origin()); - if (!newLeaf) { - // if the leaf doesn't yet exist in the new tree, steal it - auto& tree = const_cast(grid).tree(); - mTree.addLeaf(tree.template stealNode(leaf->origin(), - zeroVal(), false)); - } - else { - // otherwise increment existing values - for (auto iter = leaf->cbeginValueOn(); iter; ++iter) { - voxelSum(*newLeaf, iter.offset(), ValueType(*iter)); - } - } - } - } - -private: - TreeT& mTree; -}; // struct GridCombinerOp - - -/// @brief Compute scalar grid from PointDataGrid while evaluating the point filter -template -struct PointsToScalarOp -{ - using LeafT = typename TreeT::LeafNodeType; - using ValueT = typename LeafT::ValueType; - // This method is also used by PointCount so ValueT may not be bool - static constexpr bool IsBool = - std::is_same::value; - - PointsToScalarOp(const PointDataTreeT& tree, - const FilterT& filter) - : mPointDataAccessor(tree) - , mFilter(filter) {} - - void operator()(LeafT& leaf, size_t /*idx*/) const - { - // assumes matching topology - const auto* const pointLeaf = - mPointDataAccessor.probeConstLeaf(leaf.origin()); - assert(pointLeaf); - - for (auto value = leaf.beginValueOn(); value; ++value) { - const auto iter = pointLeaf->beginIndexVoxel(value.getCoord(), mFilter); - if (IsBool) { - if (!iter) value.setValueOn(false); - } - else { - const Index64 count = points::iterCount(iter); - if (count > Index64(0)) value.setValue(ValueT(count)); - else value.setValueOn(false); - } - } - } - -private: - const tree::ValueAccessor mPointDataAccessor; - const FilterT& mFilter; -}; // struct PointsToScalarOp - - -/// @brief Compute scalar grid from PointDataGrid using a different transform -/// and while evaluating the point filter -template -struct PointsToTransformedScalarOp -{ - using PointDataLeafT = typename PointDataGridT::TreeType::LeafNodeType; - using ValueT = typename GridT::TreeType::ValueType; - using HandleT = AttributeHandle; - using CombinableT = typename GridCombinerOp::CombinableT; - - PointsToTransformedScalarOp(const math::Transform& targetTransform, - const math::Transform& sourceTransform, - const FilterT& filter, - const DeformerT& deformer, - CombinableT& combinable) - : mTargetTransform(targetTransform) - , mSourceTransform(sourceTransform) - , mFilter(filter) - , mDeformer(deformer) - , mCombinable(combinable) { } - - void operator()(const PointDataLeafT& leaf, size_t idx) const - { - DeformerT deformer(mDeformer); - - auto& grid = mCombinable.local(); - auto& countTree = grid.tree(); - tree::ValueAccessor accessor(countTree); - - deformer.reset(leaf, idx); - - auto handle = HandleT::create(leaf.constAttributeArray("P")); - - for (auto iter = leaf.beginIndexOn(mFilter); iter; iter++) { - - // extract index-space position - - Vec3d position = handle->get(*iter) + iter.getCoord().asVec3d(); - - // if deformer is designed to be used in index-space, perform deformation prior - // to transforming position to world-space, otherwise perform deformation afterwards - - if (DeformerTraits::IndexSpace) { - deformer.template apply(position, iter); - position = mSourceTransform.indexToWorld(position); - } - else { - position = mSourceTransform.indexToWorld(position); - deformer.template apply(position, iter); - } - - // determine coord of target grid - - const Coord ijk = mTargetTransform.worldToIndexCellCentered(position); - - // increment count in target voxel - - auto* newLeaf = accessor.touchLeaf(ijk); - assert(newLeaf); - voxelSum(*newLeaf, newLeaf->coordToOffset(ijk), ValueT(1)); - } - } - -private: - const openvdb::math::Transform& mTargetTransform; - const openvdb::math::Transform& mSourceTransform; - const FilterT& mFilter; - const DeformerT& mDeformer; - CombinableT& mCombinable; -}; // struct PointsToTransformedScalarOp - - -template -inline typename TreeT::Ptr convertPointsToScalar( - const PointDataTreeT& points, - const FilterT& filter, - bool threaded = true) -{ - using point_mask_internal::PointsToScalarOp; - - using ValueT = typename TreeT::ValueType; - - // copy the topology from the points tree - - typename TreeT::Ptr tree(new TreeT(/*background=*/false)); - tree->topologyUnion(points); - - // early exit if no leaves - - if (points.leafCount() == 0) return tree; - - // early exit if mask and no group logic - - if (std::is_same::value && filter.state() == index::ALL) return tree; - - // evaluate point group filters to produce a subset of the generated mask - - tree::LeafManager leafManager(*tree); - - if (filter.state() == index::ALL) { - NullFilter nullFilter; - PointsToScalarOp pointsToScalarOp( - points, nullFilter); - leafManager.foreach(pointsToScalarOp, threaded); - } else { - // build mask from points in parallel only where filter evaluates to true - PointsToScalarOp pointsToScalarOp( - points, filter); - leafManager.foreach(pointsToScalarOp, threaded); - } - - return tree; -} - - -template -inline typename GridT::Ptr convertPointsToScalar( - PointDataGridT& points, - const math::Transform& transform, - const FilterT& filter, - const DeformerT& deformer, - bool threaded = true) -{ - using point_mask_internal::PointsToTransformedScalarOp; - using point_mask_internal::GridCombinerOp; - - using CombinerOpT = GridCombinerOp; - using CombinableT = typename GridCombinerOp::CombinableT; - - typename GridT::Ptr grid = GridT::create(); - grid->setTransform(transform.copy()); - - // use the simpler method if the requested transform matches the existing one - - const math::Transform& pointsTransform = points.constTransform(); - - if (transform == pointsTransform && std::is_same()) { - using TreeT = typename GridT::TreeType; - typename TreeT::Ptr tree = - convertPointsToScalar(points.tree(), filter, threaded); - grid->setTree(tree); - return grid; - } - - // early exit if no leaves - - if (points.constTree().leafCount() == 0) return grid; - - // compute mask grids in parallel using new transform - - CombinableT combiner; - - tree::LeafManager leafManager(points.tree()); - - if (filter.state() == index::ALL) { - NullFilter nullFilter; - PointsToTransformedScalarOp pointsToScalarOp( - transform, pointsTransform, nullFilter, deformer, combiner); - leafManager.foreach(pointsToScalarOp, threaded); - } else { - PointsToTransformedScalarOp pointsToScalarOp( - transform, pointsTransform, filter, deformer, combiner); - leafManager.foreach(pointsToScalarOp, threaded); - } - - // combine the mask grids into one - - CombinerOpT combineOp(*grid); - combiner.combine_each(combineOp); - - return grid; -} - - -} // namespace point_mask_internal - -/// @endcond - -//////////////////////////////////////// - - -template -inline typename std::enable_if::value && - std::is_same::value, typename MaskTreeT::Ptr>::type -convertPointsToMask(const PointDataTreeT& tree, - const FilterT& filter, - bool threaded) -{ - return point_mask_internal::convertPointsToScalar( - tree, filter, threaded); -} - - -template -inline typename std::enable_if::value && - std::is_same::value, typename MaskGridT::Ptr>::type -convertPointsToMask( - const PointDataGridT& points, - const FilterT& filter, - bool threaded) -{ - using PointDataTreeT = typename PointDataGridT::TreeType; - using MaskTreeT = typename MaskGridT::TreeType; - - typename MaskTreeT::Ptr tree = - convertPointsToMask - (points.tree(), filter, threaded); - - typename MaskGridT::Ptr grid(new MaskGridT(tree)); - grid->setTransform(points.transform().copy()); - return grid; -} - - -template -inline typename std::enable_if::value, - typename MaskT::Ptr>::type -convertPointsToMask( - const PointDataGridT& points, - const openvdb::math::Transform& transform, - const FilterT& filter, - bool threaded) -{ - // This is safe because the PointDataGrid can only be modified by the deformer - using AdapterT = TreeAdapter; - auto& nonConstPoints = const_cast(points); - - NullDeformer deformer; - return point_mask_internal::convertPointsToScalar( - nonConstPoints, transform, filter, deformer, threaded); -} - - -//////////////////////////////////////// - - } // namespace points } // namespace OPENVDB_VERSION_NAME } // namespace openvdb +#include "impl/PointMaskImpl.h" + #endif // OPENVDB_POINTS_POINT_MASK_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/PointMove.h b/openvdb/openvdb/points/PointMove.h index f34e4031f8..ce8ebf20bd 100644 --- a/openvdb/openvdb/points/PointMove.h +++ b/openvdb/openvdb/points/PointMove.h @@ -47,9 +47,6 @@ #include #include -class TestPointMove; - - namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { @@ -58,7 +55,6 @@ namespace points { // dummy object for future use namespace future { struct Advect { }; } - /// @brief Move points in a PointDataGrid using a custom deformer /// @param points the PointDataGrid containing the points to be moved. /// @param deformer a custom deformer that defines how to move the points. @@ -88,752 +84,10 @@ inline void movePoints(PointDataGridT& points, future::Advect* objectNotInUse = nullptr, bool threaded = true); - -// define leaf index in use as 32-bit -namespace point_move_internal { using LeafIndex = Index32; } - - -/// @brief A Deformer that caches the resulting positions from evaluating another Deformer -template -class CachedDeformer -{ -public: - using LeafIndex = point_move_internal::LeafIndex; - using Vec3T = typename math::Vec3; - using LeafVecT = std::vector; - using LeafMapT = std::unordered_map; - - // Internal data cache to allow the deformer to offer light-weight copying - struct Cache - { - struct Leaf - { - /// @brief clear data buffers and reset counter - void clear() { - vecData.clear(); - mapData.clear(); - totalSize = 0; - } - - LeafVecT vecData; - LeafMapT mapData; - Index totalSize = 0; - }; // struct Leaf - - std::vector leafs; - }; // struct Cache - - /// Cache is expected to be persistent for the lifetime of the CachedDeformer - explicit CachedDeformer(Cache& cache); - - /// Caches the result of evaluating the supplied point grid using the deformer and filter - /// @param grid the points to be moved - /// @param deformer the deformer to apply to the points - /// @param filter the point filter to use when evaluating the points - /// @param threaded enable or disable threading (threading is enabled by default) - template - void evaluate(PointDataGridT& grid, DeformerT& deformer, const FilterT& filter, - bool threaded = true); - - /// Stores pointers to the vector or map and optionally expands the map into a vector - /// @throw IndexError if idx is out-of-range of the leafs in the cache - template - void reset(const LeafT& leaf, size_t idx); - - /// Retrieve the new position from the cache - template - void apply(Vec3d& position, const IndexIterT& iter) const; - -private: - friend class ::TestPointMove; - - Cache& mCache; - const LeafVecT* mLeafVec = nullptr; - const LeafMapT* mLeafMap = nullptr; -}; // class CachedDeformer - - -//////////////////////////////////////// - - -namespace point_move_internal { - -using IndexArray = std::vector; - -using IndexTriple = std::tuple; -using IndexTripleArray = tbb::concurrent_vector; -using GlobalPointIndexMap = std::vector; -using GlobalPointIndexIndices = std::vector; - -using IndexPair = std::pair; -using IndexPairArray = std::vector; -using LocalPointIndexMap = std::vector; - -using LeafIndexArray = std::vector; -using LeafOffsetArray = std::vector; -using LeafMap = std::unordered_map; - - -template -struct BuildMoveMapsOp -{ - using LeafT = typename TreeT::LeafNodeType; - using LeafArrayT = std::vector; - using LeafManagerT = typename tree::LeafManager; - - BuildMoveMapsOp(const DeformerT& deformer, - GlobalPointIndexMap& globalMoveLeafMap, - LocalPointIndexMap& localMoveLeafMap, - const LeafMap& targetLeafMap, - const math::Transform& targetTransform, - const math::Transform& sourceTransform, - const FilterT& filter) - : mDeformer(deformer) - , mGlobalMoveLeafMap(globalMoveLeafMap) - , mLocalMoveLeafMap(localMoveLeafMap) - , mTargetLeafMap(targetLeafMap) - , mTargetTransform(targetTransform) - , mSourceTransform(sourceTransform) - , mFilter(filter) { } - - void operator()(LeafT& leaf, size_t idx) const - { - DeformerT deformer(mDeformer); - deformer.reset(leaf, idx); - - // determine source leaf node origin and offset in the source leaf vector - - Coord sourceLeafOrigin = leaf.origin(); - - auto sourceHandle = AttributeWriteHandle::create(leaf.attributeArray("P")); - - for (auto iter = leaf.beginIndexOn(mFilter); iter; iter++) { - - const bool useIndexSpace = DeformerTraits::IndexSpace; - - // extract index-space position and apply index-space deformation (if applicable) - - Vec3d positionIS = sourceHandle->get(*iter) + iter.getCoord().asVec3d(); - if (useIndexSpace) { - deformer.apply(positionIS, iter); - } - - // transform to world-space position and apply world-space deformation (if applicable) - - Vec3d positionWS = mSourceTransform.indexToWorld(positionIS); - if (!useIndexSpace) { - deformer.apply(positionWS, iter); - } - - // transform to index-space position of target grid - - positionIS = mTargetTransform.worldToIndex(positionWS); - - // determine target voxel and offset - - Coord targetVoxel = Coord::round(positionIS); - Index targetOffset = LeafT::coordToOffset(targetVoxel); - - // set new local position in source transform space (if point has been deformed) - - Vec3d voxelPosition(positionIS - targetVoxel.asVec3d()); - sourceHandle->set(*iter, voxelPosition); - - // determine target leaf node origin and offset in the target leaf vector - - Coord targetLeafOrigin = targetVoxel & ~(LeafT::DIM - 1); - assert(mTargetLeafMap.find(targetLeafOrigin) != mTargetLeafMap.end()); - const LeafIndex targetLeafOffset(mTargetLeafMap.at(targetLeafOrigin)); - - // insert into move map based on whether point ends up in a new leaf node or not - - if (targetLeafOrigin == sourceLeafOrigin) { - mLocalMoveLeafMap[targetLeafOffset].emplace_back(targetOffset, *iter); - } - else { - mGlobalMoveLeafMap[targetLeafOffset].push_back(IndexTriple( - LeafIndex(static_cast(idx)), targetOffset, *iter)); - } - } - } - -private: - const DeformerT& mDeformer; - GlobalPointIndexMap& mGlobalMoveLeafMap; - LocalPointIndexMap& mLocalMoveLeafMap; - const LeafMap& mTargetLeafMap; - const math::Transform& mTargetTransform; - const math::Transform& mSourceTransform; - const FilterT& mFilter; -}; // struct BuildMoveMapsOp - -template -inline Index -indexOffsetFromVoxel(const Index voxelOffset, const LeafT& leaf, IndexArray& offsets) -{ - // compute the target point index by summing the point index of the previous - // voxel with the current number of points added to this voxel, tracked by the - // offsets array - - Index targetOffset = offsets[voxelOffset]++; - if (voxelOffset > 0) { - targetOffset += static_cast(leaf.getValue(voxelOffset - 1)); - } - return targetOffset; -} - - -template -struct GlobalMovePointsOp -{ - using LeafT = typename TreeT::LeafNodeType; - using LeafArrayT = std::vector; - using LeafManagerT = typename tree::LeafManager; - using AttributeArrays = std::vector; - - GlobalMovePointsOp(LeafOffsetArray& offsetMap, - LeafManagerT& sourceLeafManager, - const Index attributeIndex, - const GlobalPointIndexMap& moveLeafMap, - const GlobalPointIndexIndices& moveLeafIndices) - : mOffsetMap(offsetMap) - , mSourceLeafManager(sourceLeafManager) - , mAttributeIndex(attributeIndex) - , mMoveLeafMap(moveLeafMap) - , mMoveLeafIndices(moveLeafIndices) { } - - // A CopyIterator is designed to use the indices in a GlobalPointIndexMap for this leaf - // and match the interface required for AttributeArray::copyValues() - struct CopyIterator - { - CopyIterator(const LeafT& leaf, const IndexArray& sortedIndices, - const IndexTripleArray& moveIndices, IndexArray& offsets) - : mLeaf(leaf) - , mSortedIndices(sortedIndices) - , mMoveIndices(moveIndices) - , mOffsets(offsets) { } - - operator bool() const { return bool(mIt); } - - void reset(Index startIndex, Index endIndex) - { - mIndex = startIndex; - mEndIndex = endIndex; - this->advance(); - } - - CopyIterator& operator++() - { - this->advance(); - return *this; - } - - Index leafIndex(Index i) const - { - if (i < mSortedIndices.size()) { - return std::get<0>(this->leafIndexTriple(i)); - } - return std::numeric_limits::max(); - } - - Index sourceIndex() const - { - assert(mIt); - return std::get<2>(*mIt); - } - - Index targetIndex() const - { - assert(mIt); - return indexOffsetFromVoxel(std::get<1>(*mIt), mLeaf, mOffsets); - } - - private: - void advance() - { - if (mIndex >= mEndIndex || mIndex >= mSortedIndices.size()) { - mIt = nullptr; - } - else { - mIt = &this->leafIndexTriple(mIndex); - } - ++mIndex; - } - - const IndexTriple& leafIndexTriple(Index i) const - { - return mMoveIndices[mSortedIndices[i]]; - } - - private: - const LeafT& mLeaf; - Index mIndex; - Index mEndIndex; - const IndexArray& mSortedIndices; - const IndexTripleArray& mMoveIndices; - IndexArray& mOffsets; - const IndexTriple* mIt = nullptr; - }; // struct CopyIterator - - void operator()(LeafT& leaf, size_t idx) const - { - const IndexTripleArray& moveIndices = mMoveLeafMap[idx]; - if (moveIndices.empty()) return; - const IndexArray& sortedIndices = mMoveLeafIndices[idx]; - - // extract per-voxel offsets for this leaf - - LeafIndexArray& offsets = mOffsetMap[idx]; - - // extract target array and ensure data is out-of-core and non-uniform - - auto& targetArray = leaf.attributeArray(mAttributeIndex); - targetArray.loadData(); - targetArray.expand(); - - // perform the copy - - CopyIterator copyIterator(leaf, sortedIndices, moveIndices, offsets); - - // use the sorted indices to track the index of the source leaf - - Index sourceLeafIndex = copyIterator.leafIndex(0); - Index startIndex = 0; - - for (size_t i = 1; i <= sortedIndices.size(); i++) { - Index endIndex = static_cast(i); - Index newSourceLeafIndex = copyIterator.leafIndex(endIndex); - - // when it changes, do a batch-copy of all the indices that lie within this range - // TODO: this step could use nested parallelization for cases where there are a - // large number of points being moved per attribute - - if (newSourceLeafIndex > sourceLeafIndex) { - copyIterator.reset(startIndex, endIndex); - - const LeafT& sourceLeaf = mSourceLeafManager.leaf(sourceLeafIndex); - const auto& sourceArray = sourceLeaf.constAttributeArray(mAttributeIndex); - sourceArray.loadData(); - - targetArray.copyValuesUnsafe(sourceArray, copyIterator); - - sourceLeafIndex = newSourceLeafIndex; - startIndex = endIndex; - } - } - } - -private: - LeafOffsetArray& mOffsetMap; - LeafManagerT& mSourceLeafManager; - const Index mAttributeIndex; - const GlobalPointIndexMap& mMoveLeafMap; - const GlobalPointIndexIndices& mMoveLeafIndices; -}; // struct GlobalMovePointsOp - - -template -struct LocalMovePointsOp -{ - using LeafT = typename TreeT::LeafNodeType; - using LeafArrayT = std::vector; - using LeafManagerT = typename tree::LeafManager; - using AttributeArrays = std::vector; - - LocalMovePointsOp( LeafOffsetArray& offsetMap, - const LeafIndexArray& sourceIndices, - LeafManagerT& sourceLeafManager, - const Index attributeIndex, - const LocalPointIndexMap& moveLeafMap) - : mOffsetMap(offsetMap) - , mSourceIndices(sourceIndices) - , mSourceLeafManager(sourceLeafManager) - , mAttributeIndex(attributeIndex) - , mMoveLeafMap(moveLeafMap) { } - - // A CopyIterator is designed to use the indices in a LocalPointIndexMap for this leaf - // and match the interface required for AttributeArray::copyValues() - struct CopyIterator - { - CopyIterator(const LeafT& leaf, const IndexPairArray& indices, IndexArray& offsets) - : mLeaf(leaf) - , mIndices(indices) - , mOffsets(offsets) { } - - operator bool() const { return mIndex < static_cast(mIndices.size()); } - - CopyIterator& operator++() { ++mIndex; return *this; } - - Index sourceIndex() const - { - return mIndices[mIndex].second; - } - - Index targetIndex() const - { - return indexOffsetFromVoxel(mIndices[mIndex].first, mLeaf, mOffsets); - } - - private: - const LeafT& mLeaf; - const IndexPairArray& mIndices; - IndexArray& mOffsets; - int mIndex = 0; - }; // struct CopyIterator - - void operator()(LeafT& leaf, size_t idx) const - { - const IndexPairArray& moveIndices = mMoveLeafMap[idx]; - if (moveIndices.empty()) return; - - // extract per-voxel offsets for this leaf - - LeafIndexArray& offsets = mOffsetMap[idx]; - - // extract source array that has the same origin as the target leaf - - assert(idx < mSourceIndices.size()); - const Index sourceLeafOffset(mSourceIndices[idx]); - LeafT& sourceLeaf = mSourceLeafManager.leaf(sourceLeafOffset); - const auto& sourceArray = sourceLeaf.constAttributeArray(mAttributeIndex); - sourceArray.loadData(); - - // extract target array and ensure data is out-of-core and non-uniform - - auto& targetArray = leaf.attributeArray(mAttributeIndex); - targetArray.loadData(); - targetArray.expand(); - - // perform the copy - - CopyIterator copyIterator(leaf, moveIndices, offsets); - targetArray.copyValuesUnsafe(sourceArray, copyIterator); - } - -private: - LeafOffsetArray& mOffsetMap; - const LeafIndexArray& mSourceIndices; - LeafManagerT& mSourceLeafManager; - const Index mAttributeIndex; - const LocalPointIndexMap& mMoveLeafMap; -}; // struct LocalMovePointsOp - - -} // namespace point_move_internal - - -//////////////////////////////////////// - - -template -inline void movePoints( PointDataGridT& points, - const math::Transform& transform, - DeformerT& deformer, - const FilterT& filter, - future::Advect* objectNotInUse, - bool threaded) -{ - using LeafIndex = point_move_internal::LeafIndex; - using PointDataTreeT = typename PointDataGridT::TreeType; - using LeafT = typename PointDataTreeT::LeafNodeType; - using LeafManagerT = typename tree::LeafManager; - - using namespace point_move_internal; - - // this object is for future use only - assert(!objectNotInUse); - (void)objectNotInUse; - - PointDataTreeT& tree = points.tree(); - - // early exit if no LeafNodes - - auto iter = tree.cbeginLeaf(); - - if (!iter) return; - - // build voxel topology taking into account any point group deletion - - auto newPoints = point_mask_internal::convertPointsToScalar( - points, transform, filter, deformer, threaded); - auto& newTree = newPoints->tree(); - - // create leaf managers for both trees - - LeafManagerT sourceLeafManager(tree); - LeafManagerT targetLeafManager(newTree); - - // extract the existing attribute set - const auto& existingAttributeSet = points.tree().cbeginLeaf()->attributeSet(); - - // build a coord -> index map for looking up target leafs by origin and a faster - // unordered map for finding the source index from a target index - - LeafMap targetLeafMap; - LeafIndexArray sourceIndices(targetLeafManager.leafCount(), - std::numeric_limits::max()); - - LeafOffsetArray offsetMap(targetLeafManager.leafCount()); - - { - LeafMap sourceLeafMap; - auto sourceRange = sourceLeafManager.leafRange(); - for (auto leaf = sourceRange.begin(); leaf; ++leaf) { - sourceLeafMap.insert({leaf->origin(), LeafIndex(static_cast(leaf.pos()))}); - } - auto targetRange = targetLeafManager.leafRange(); - for (auto leaf = targetRange.begin(); leaf; ++leaf) { - targetLeafMap.insert({leaf->origin(), LeafIndex(static_cast(leaf.pos()))}); - } - - // acquire registry lock to avoid locking when appending attributes in parallel - - AttributeArray::ScopedRegistryLock lock; - - // perform four independent per-leaf operations in parallel - targetLeafManager.foreach( - [&](LeafT& leaf, size_t idx) { - // map frequency => cumulative histogram - auto* buffer = leaf.buffer().data(); - for (Index i = 1; i < leaf.buffer().size(); i++) { - buffer[i] = buffer[i-1] + buffer[i]; - } - // replace attribute set with a copy of the existing one - leaf.replaceAttributeSet( - new AttributeSet(existingAttributeSet, leaf.getLastValue(), &lock), - /*allowMismatchingDescriptors=*/true); - // store the index of the source leaf in a corresponding target leaf array - const auto it = sourceLeafMap.find(leaf.origin()); - if (it != sourceLeafMap.end()) { - sourceIndices[idx] = it->second; - } - // allocate offset maps - offsetMap[idx].resize(LeafT::SIZE); - }, - threaded); - } - - // moving leaf - - GlobalPointIndexMap globalMoveLeafMap(targetLeafManager.leafCount()); - LocalPointIndexMap localMoveLeafMap(targetLeafManager.leafCount()); - - // build global and local move leaf maps and update local positions - - if (filter.state() == index::ALL) { - NullFilter nullFilter; - BuildMoveMapsOp op(deformer, - globalMoveLeafMap, localMoveLeafMap, targetLeafMap, - transform, points.transform(), nullFilter); - sourceLeafManager.foreach(op, threaded); - } else { - BuildMoveMapsOp op(deformer, - globalMoveLeafMap, localMoveLeafMap, targetLeafMap, - transform, points.transform(), filter); - sourceLeafManager.foreach(op, threaded); - } - - // build a sorted index vector for each leaf that references the global move map - // indices in order of their source leafs and voxels to ensure determinism in the - // resulting point orders - - GlobalPointIndexIndices globalMoveLeafIndices(globalMoveLeafMap.size()); - - targetLeafManager.foreach( - [&](LeafT& /*leaf*/, size_t idx) { - const IndexTripleArray& moveIndices = globalMoveLeafMap[idx]; - if (moveIndices.empty()) return; - - IndexArray& sortedIndices = globalMoveLeafIndices[idx]; - sortedIndices.resize(moveIndices.size()); - std::iota(std::begin(sortedIndices), std::end(sortedIndices), 0); - std::sort(std::begin(sortedIndices), std::end(sortedIndices), - [&](int i, int j) - { - const Index& indexI0(std::get<0>(moveIndices[i])); - const Index& indexJ0(std::get<0>(moveIndices[j])); - if (indexI0 < indexJ0) return true; - if (indexI0 > indexJ0) return false; - return std::get<2>(moveIndices[i]) < std::get<2>(moveIndices[j]); - } - ); - }, - threaded); - - for (const auto& it : existingAttributeSet.descriptor().map()) { - - const Index attributeIndex = static_cast(it.second); - - // zero offsets - targetLeafManager.foreach( - [&offsetMap](const LeafT& /*leaf*/, size_t idx) { - std::fill(offsetMap[idx].begin(), offsetMap[idx].end(), 0); - }, - threaded); - - // move points between leaf nodes - - GlobalMovePointsOp globalMoveOp(offsetMap, - sourceLeafManager, attributeIndex, globalMoveLeafMap, globalMoveLeafIndices); - targetLeafManager.foreach(globalMoveOp, threaded); - - // move points within leaf nodes - - LocalMovePointsOp localMoveOp(offsetMap, - sourceIndices, sourceLeafManager, attributeIndex, localMoveLeafMap); - targetLeafManager.foreach(localMoveOp, threaded); - } - - points.setTree(newPoints->treePtr()); -} - - -template -inline void movePoints( PointDataGridT& points, - DeformerT& deformer, - const FilterT& filter, - future::Advect* objectNotInUse, - bool threaded) -{ - movePoints(points, points.transform(), deformer, filter, objectNotInUse, threaded); -} - - -//////////////////////////////////////// - - -template -CachedDeformer::CachedDeformer(Cache& cache) - : mCache(cache) { } - - -template -template -void CachedDeformer::evaluate(PointDataGridT& grid, DeformerT& deformer, const FilterT& filter, - bool threaded) -{ - using TreeT = typename PointDataGridT::TreeType; - using LeafT = typename TreeT::LeafNodeType; - using LeafManagerT = typename tree::LeafManager; - LeafManagerT leafManager(grid.tree()); - - // initialize cache - auto& leafs = mCache.leafs; - leafs.resize(leafManager.leafCount()); - - const auto& transform = grid.transform(); - - // insert deformed positions into the cache - - auto cachePositionsOp = [&](const LeafT& leaf, size_t idx) { - - const Index64 totalPointCount = leaf.pointCount(); - if (totalPointCount == 0) return; - - // deformer is copied to ensure that it is unique per-thread - - DeformerT newDeformer(deformer); - - newDeformer.reset(leaf, idx); - - auto handle = AttributeHandle::create(leaf.constAttributeArray("P")); - - auto& cache = leafs[idx]; - cache.clear(); - - // only insert into a vector directly if the filter evaluates all points - // and all points are stored in active voxels - const bool useVector = filter.state() == index::ALL && - (leaf.isDense() || (leaf.onPointCount() == leaf.pointCount())); - if (useVector) { - cache.vecData.resize(totalPointCount); - } - - for (auto iter = leaf.beginIndexOn(filter); iter; iter++) { - - // extract index-space position and apply index-space deformation (if defined) - - Vec3d position = handle->get(*iter) + iter.getCoord().asVec3d(); - - // if deformer is designed to be used in index-space, perform deformation prior - // to transforming position to world-space, otherwise perform deformation afterwards - - if (DeformerTraits::IndexSpace) { - newDeformer.apply(position, iter); - position = transform.indexToWorld(position); - } - else { - position = transform.indexToWorld(position); - newDeformer.apply(position, iter); - } - - // insert new position into the cache - - if (useVector) { - cache.vecData[*iter] = static_cast(position); - } - else { - cache.mapData.insert({*iter, static_cast(position)}); - } - } - - // store the total number of points to allow use of an expanded vector on access - - if (!cache.mapData.empty()) { - cache.totalSize = static_cast(totalPointCount); - } - }; - - leafManager.foreach(cachePositionsOp, threaded); -} - - -template -template -void CachedDeformer::reset(const LeafT& /*leaf*/, size_t idx) -{ - if (idx >= mCache.leafs.size()) { - if (mCache.leafs.empty()) { - throw IndexError("No leafs in cache, perhaps CachedDeformer has not been evaluated?"); - } else { - throw IndexError("Leaf index is out-of-range of cache leafs."); - } - } - auto& cache = mCache.leafs[idx]; - if (!cache.mapData.empty()) { - mLeafMap = &cache.mapData; - mLeafVec = nullptr; - } - else { - mLeafVec = &cache.vecData; - mLeafMap = nullptr; - } -} - - -template -template -void CachedDeformer::apply(Vec3d& position, const IndexIterT& iter) const -{ - assert(*iter >= 0); - - if (mLeafMap) { - auto it = mLeafMap->find(*iter); - if (it == mLeafMap->end()) return; - position = static_cast(it->second); - } - else { - assert(mLeafVec); - - if (mLeafVec->empty()) return; - assert(*iter < mLeafVec->size()); - position = static_cast((*mLeafVec)[*iter]); - } -} - - } // namespace points } // namespace OPENVDB_VERSION_NAME } // namespace openvdb +#include "impl/PointMoveImpl.h" + #endif // OPENVDB_POINTS_POINT_MOVE_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/PointReplicate.h b/openvdb/openvdb/points/PointReplicate.h index b300d170c5..f16810ad3d 100644 --- a/openvdb/openvdb/points/PointReplicate.h +++ b/openvdb/openvdb/points/PointReplicate.h @@ -18,7 +18,6 @@ OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace points { - /// @brief Replicates points provided in a source grid into a new grid, /// transfering and creating attributes found in a provided /// attribute vector. If an attribute doesn't exist, it is ignored. @@ -62,278 +61,10 @@ replicate(const PointDataGridT& source, const std::string& scaleAttribute = "", const std::string& replicationIndex = ""); - -//////////////////////////////////////// - - -template -typename PointDataGridT::Ptr -replicate(const PointDataGridT& source, - const Index multiplier, - const std::vector& attributes, - const std::string& scaleAttribute, - const std::string& replicationIndex) -{ - // The copy iterator, used to transfer array values from the source grid - // to the target (replicated grid). - struct CopyIter - { -#ifdef __clang__ - // Silence incorrect clang warning - _Pragma("clang diagnostic push") - _Pragma("clang diagnostic ignored \"-Wunused-local-typedef\"") - using GetIncrementCB = std::function; - _Pragma("clang diagnostic pop") -#else - using GetIncrementCB = std::function; -#endif - - CopyIter(const Index end, const GetIncrementCB& cb) - : mIt(0), mEnd(0), mSource(0), mSourceEnd(end), mCallback(cb) { - mEnd = mCallback(mSource); - } - - operator bool() const { return mSource < mSourceEnd; } - - CopyIter& operator++() - { - ++mIt; - // If we have hit the end for current source idx, increment the source idx - // moving end to the new position for the next source with a non zero - // number of new values - while (mIt >= mEnd) { - ++mSource; - if (*this) mEnd += mCallback(mSource); - else return *this; - } - - return *this; - } - - Index sourceIndex() const { assert(*this); return mSource; } - Index targetIndex() const { assert(*this); return mIt; } - - private: - Index mIt, mEnd, mSource; - const Index mSourceEnd; - const GetIncrementCB mCallback; - }; // struct CopyIter - - - // We want the topology and index values of the leaf nodes, but we - // DON'T want to copy the arrays. This should only shallow copy the - // descriptor and arrays - PointDataGrid::Ptr points = source.deepCopy(); - - auto iter = source.tree().cbeginLeaf(); - if (!iter) return points; - - const AttributeSet::Descriptor& sourceDescriptor = - iter->attributeSet().descriptor(); - - // verify position - - const size_t ppos = sourceDescriptor.find("P"); - assert(ppos != AttributeSet::INVALID_POS); - - // build new dummy attribute set - - AttributeSet::Ptr set; - std::vector attribsToDrop; - if (!attributes.empty()) { - for (const auto& nameIdxPair : sourceDescriptor.map()) { - if (std::find(attributes.begin(), attributes.end(), nameIdxPair.first) != attributes.end()) continue; - if (nameIdxPair.first == "P") continue; - attribsToDrop.emplace_back(nameIdxPair.second); - } - set.reset(new AttributeSet(iter->attributeSet(), 1)); - } - else { - set.reset(new AttributeSet(AttributeSet::Descriptor::create(sourceDescriptor.type(ppos)))); - } - - if (!replicationIndex.empty()) { - // don't copy replicationIndex attribute if it exists - // as it'll be overwritten and we create it after - const size_t replIdxIdx = sourceDescriptor.find(replicationIndex); - if (replIdxIdx != AttributeSet::INVALID_POS) attribsToDrop.emplace_back(replIdxIdx); - } - set->dropAttributes(attribsToDrop); - - // validate replication attributes - - size_t replicationIdx = AttributeSet::INVALID_POS; - if (!replicationIndex.empty()) { - set->appendAttribute(replicationIndex, TypedAttributeArray::attributeType()); - replicationIdx = set->descriptor().find(replicationIndex); - } - - AttributeSet::DescriptorPtr descriptor = set->descriptorPtr(); - - const size_t scaleIdx = !scaleAttribute.empty() ? - sourceDescriptor.find(scaleAttribute) : AttributeSet::INVALID_POS; - - openvdb::tree::LeafManager sourceManager(source.tree()); - openvdb::tree::LeafManager manager(points->tree()); - - manager.foreach( - [&](PointDataTree::LeafNodeType& leaf, size_t pos) - { - using ValueType = PointDataTree::ValueType; - - const auto& sourceLeaf = sourceManager.leaf(pos); - // @note This really shoudn't return uint64_t as AttributeArray's size is - // limited to the max of a uint32_t... - assert(sourceLeaf.pointCount() < Index64(std::numeric_limits::max())); - const Index sourceCount = static_cast(sourceLeaf.pointCount()); - - Index uniformMultiplier = multiplier; - AttributeHandle::UniquePtr scaleHandle(nullptr); - bool useScale = scaleIdx != AttributeSet::INVALID_POS; - if (useScale) { - scaleHandle = std::make_unique> - (sourceLeaf.constAttributeArray(scaleIdx)); - } - // small lambda that returns the amount of points to generate - // based on a scale. Should only be called if useScale is true, - // otherwise the scaleHandle will be reset or null - auto getPointsToGenerate = [&](const Index index) -> Index { - const float scale = std::max(0.0f, scaleHandle->get(index)); - return static_cast - (math::Round(static_cast(multiplier) * scale)); - }; - - // if uniform, update the multiplier and don't bother using the scale attribute - - if (useScale && scaleHandle->isUniform()) { - uniformMultiplier = getPointsToGenerate(0); - scaleHandle.reset(); - useScale = false; - } - - // get the new count and build the new offsets - do this in this loop so we - // don't have to cache the offset vector. Note that the leaf offsets become - // invalid until leaf.replaceAttributeSet is called and should not be used - - Index total = 0; - - if (useScale) { - for (auto iter = sourceLeaf.cbeginValueAll(); iter; ++iter) { - for (auto piter = sourceLeaf.beginIndexVoxel(iter.getCoord()); - piter; ++piter) { total += getPointsToGenerate(*piter); } - leaf.setOffsetOnly(iter.pos(), total); - } - } - else { - total = uniformMultiplier * sourceCount; - - // with a uniform multiplier, just multiply each voxel value - auto* data = leaf.buffer().data(); - for (size_t i = 0; i < leaf.buffer().size(); ++i) { - const ValueType::IntType value = data[i]; - data[i] = value * uniformMultiplier; - } - } - - // turn voxels off if no points - leaf.updateValueMask(); - const AttributeSet& sourceSet = sourceLeaf.attributeSet(); - - std::unique_ptr newSet - (new AttributeSet(*set, total)); - - auto copy = [&](const std::string& name) - { - const auto* sourceArray = sourceSet.getConst(name); - assert(sourceArray); - - // manually expand so that copyValues() doesn't expand and fill the array - - // we don't want to unnecessarily zero initialize the target values as we know - // we're going to write to all of them. - auto* array = newSet->get(name); - assert(array); - array->expand(/*fill*/false); - - if (useScale) { - const CopyIter iter(sourceCount, [&](const Index i) { return getPointsToGenerate(i); }); - array->copyValues(*sourceArray, iter); - } - else { - const CopyIter iter(sourceCount, [&](const Index) { return uniformMultiplier; }); - array->copyValues(*sourceArray, iter); - } - }; - - copy("P"); - for (const auto& iter : descriptor->map()) { - if (iter.first == "P") continue; - if (iter.first == replicationIndex) continue; - copy(iter.first); - } - - // assign the replication idx if requested - - if (replicationIdx != AttributeSet::INVALID_POS && total > 0) { - AttributeWriteHandle - idxHandle(*newSet->get(replicationIdx), /*expand*/false); - idxHandle.expand(/*fill*/false); - assert(idxHandle.size() == total); - - - Index offset = 0; - - if (useScale) { - for (Index i = 0; i < sourceCount; ++i) { - const Index pointRepCount = getPointsToGenerate(i); - for (Index j = 0; j < pointRepCount; ++j) { - idxHandle.set(offset++, j); - } - } - } - else { - while (offset < total) { - for (Index j = 0; j < uniformMultiplier; ++j) { - idxHandle.set(offset++, j); - } - } - } - } - - leaf.replaceAttributeSet(newSet.release(), /*mismatch*/true); - }); - - if (!scaleAttribute.empty()) { - tools::pruneInactive(points->tree()); - } - - return points; -} - -template -typename PointDataGridT::Ptr -replicate(const PointDataGridT& source, - const Index multiplier, - const std::string& scaleAttribute, - const std::string& replicationIndex) -{ - auto iter = source.tree().cbeginLeaf(); - if (!iter) return source.deepCopy(); - - const openvdb::points::AttributeSet::Descriptor& sourceDescriptor = - iter->attributeSet().descriptor(); - - std::vector attribs; - attribs.reserve(sourceDescriptor.map().size()); - for (const auto& namepos : sourceDescriptor.map()) { - attribs.emplace_back(namepos.first); - } - - return replicate(source, multiplier, attribs, scaleAttribute, replicationIndex); -} - - } // namespace points } // namespace OPENVDB_VERSION_NAME } // namespace openvdb +#include "impl/PointReplicateImpl.h" + #endif // OPENVDB_POINTS_POINT_REPLICATE_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/PointSample.h b/openvdb/openvdb/points/PointSample.h index 334dcfc147..1ff49b611f 100644 --- a/openvdb/openvdb/points/PointSample.h +++ b/openvdb/openvdb/points/PointSample.h @@ -25,7 +25,6 @@ OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace points { - /// @brief Performs closest point sampling from a VDB grid onto a VDB Points attribute /// @param points the PointDataGrid whose points will be sampled on to /// @param sourceGrid VDB grid which will be sampled @@ -43,7 +42,6 @@ inline void pointSample(PointDataGridT& points, const FilterT& filter = NullFilter(), InterrupterT* const interrupter = nullptr); - /// @brief Performs tri-linear sampling from a VDB grid onto a VDB Points attribute /// @param points the PointDataGrid whose points will be sampled on to /// @param sourceGrid VDB grid which will be sampled @@ -61,7 +59,6 @@ inline void boxSample( PointDataGridT& points, const FilterT& filter = NullFilter(), InterrupterT* const interrupter = nullptr); - /// @brief Performs tri-quadratic sampling from a VDB grid onto a VDB Points attribute /// @param points the PointDataGrid whose points will be sampled on to /// @param sourceGrid VDB grid which will be sampled @@ -90,12 +87,10 @@ struct SampleWithRounding inline ValueT sample(const AccessorT& accessor, const Vec3d& position) const; }; - // A dummy struct that is used to mean that the sampled attribute should either match the type // of the existing attribute or the type of the source grid (if the attribute doesn't exist yet) struct DummySampleType { }; - /// @brief Performs sampling and conversion from a VDB grid onto a VDB Points attribute /// @param order the sampling order - 0 = closest-point, 1 = trilinear, 2 = triquadratic /// @param points the PointDataGrid whose points will be sampled on to @@ -121,447 +116,10 @@ inline void sampleGrid( size_t order, InterrupterT* const interrupter = nullptr, const bool threaded = true); - -/////////////////////////////////////////////////// - -/// @cond OPENVDB_DOCS_INTERNAL - -namespace point_sample_internal { - - -template -struct CompatibleTypes { enum { value = std::is_constructible::value }; }; - -// Specializations for types that can be converted from source grid to target attribute -template struct CompatibleTypes< - T, T> { enum { value = true }; }; -template struct CompatibleTypes< - T, math::Vec2> { enum { value = true }; }; -template struct CompatibleTypes< - T, math::Vec3> { enum { value = true }; }; -template struct CompatibleTypes< - T, math::Vec4> { enum { value = true }; }; -template struct CompatibleTypes< - math::Vec2, math::Vec2> { enum { value = true }; }; -template struct CompatibleTypes< - math::Vec3, math::Vec3> { enum { value = true }; }; -template struct CompatibleTypes< - math::Vec4, math::Vec4> { enum { value = true }; }; -template struct CompatibleTypes< - math::Vec2, math::Vec2> { enum { value = CompatibleTypes::value }; }; -template struct CompatibleTypes< - math::Vec3, math::Vec3> { enum { value = CompatibleTypes::value }; }; -template struct CompatibleTypes< - math::Vec4, math::Vec4> { enum { value = CompatibleTypes::value }; }; -template struct CompatibleTypes< - ValueMask, T> { enum { value = CompatibleTypes::value }; }; - - -// Ability to access the Order and Staggered template parameter from tools::Sampler -template struct SamplerTraits { - static const size_t Order = 0; - static const bool Staggered = false; -}; -template struct SamplerTraits> { - static const size_t Order = T0; - static const bool Staggered = T1; -}; - - -// default sampling is incompatible, so throw an error -template -struct SampleWithRoundingOp -{ - static inline void sample(ValueT&, const AccessorT&, const Vec3d&) - { - std::ostringstream ostr; - ostr << "Cannot sample a " << typeNameAsString() - << " grid on to a " << typeNameAsString() << " attribute"; - OPENVDB_THROW(TypeError, ostr.str()); - } -}; -// partial specialization to handle sampling and rounding of compatible conversion -template -struct SampleWithRoundingOp -{ - static inline void sample(ValueT& value, const AccessorT& accessor, const Vec3d& position) - { - value = ValueT(math::Round(SamplerT::sample(accessor, position))); - } -}; -// partial specialization to handle sampling and simple casting of compatible conversion -template -struct SampleWithRoundingOp -{ - static inline void sample(ValueT& value, const AccessorT& accessor, const Vec3d& position) - { - value = ValueT(SamplerT::sample(accessor, position)); - } -}; - - -template -class PointDataSampler -{ -public: - PointDataSampler(size_t order, - PointDataGridT& points, - const SamplerT& sampler, - const FilterT& filter, - InterrupterT* const interrupter, - const bool threaded) - : mOrder(order) - , mPoints(points) - , mSampler(sampler) - , mFilter(filter) - , mInterrupter(interrupter) - , mThreaded(threaded) { } - -private: - // No-op transformation - struct AlignedTransform - { - inline Vec3d transform(const Vec3d& position) const { return position; } - }; // struct AlignedTransform - - // Re-sample world-space position from source to target transforms - struct NonAlignedTransform - { - NonAlignedTransform(const math::Transform& source, const math::Transform& target) - : mSource(source) - , mTarget(target) { } - - inline Vec3d transform(const Vec3d& position) const - { - return mSource.worldToIndex(mTarget.indexToWorld(position)); - } - - private: - const math::Transform& mSource; - const math::Transform& mTarget; - }; // struct NonAlignedTransform - - // A simple convenience wrapper that contains the source grid accessor and the sampler - template - struct SamplerWrapper - { - using ValueType = ValueT; - using SourceValueType = typename SourceGridT::ValueType; - using SourceAccessorT = typename SourceGridT::ConstAccessor; - - // can only sample from a bool or mask grid using a PointSampler - static const bool SourceIsBool = std::is_same::value || - std::is_same::value; - static const bool OrderIsZero = SamplerTraits::Order == 0; - static const bool IsValid = !SourceIsBool || OrderIsZero; - - SamplerWrapper(const SourceGridT& sourceGrid, const SamplerT& sampler) - : mAccessor(sourceGrid.getConstAccessor()) - , mSampler(sampler) { } - - // note that creating a new accessor from the underlying tree is faster than - // copying an existing accessor - SamplerWrapper(const SamplerWrapper& other) - : mAccessor(other.mAccessor.tree()) - , mSampler(other.mSampler) { } - - template - inline typename std::enable_if::type - sample(const Vec3d& position) const { - return mSampler.template sample( - mAccessor, position); - } - - template - inline typename std::enable_if::type - sample(const Vec3d& /*position*/) const { - OPENVDB_THROW(RuntimeError, "Cannot sample bool grid with BoxSampler or QuadraticSampler."); - } - - private: - SourceAccessorT mAccessor; - const SamplerT& mSampler; - }; // struct SamplerWrapper - - template - inline void doSample(const SamplerWrapperT& sampleWrapper, const Index targetIndex, - const TransformerT& transformer) - { - using PointDataTreeT = typename PointDataGridT::TreeType; - using LeafT = typename PointDataTreeT::LeafNodeType; - using LeafManagerT = typename tree::LeafManager; - - const auto& filter(mFilter); - const auto& interrupter(mInterrupter); - - auto sampleLambda = [targetIndex, &sampleWrapper, &transformer, &filter, &interrupter]( - LeafT& leaf, size_t /*idx*/) - { - using TargetHandleT = AttributeWriteHandle; - - if (util::wasInterrupted(interrupter)) { - thread::cancelGroupExecution(); - return; - } - - SamplerWrapperT newSampleWrapper(sampleWrapper); - auto positionHandle = AttributeHandle::create(leaf.constAttributeArray("P")); - auto targetHandle = TargetHandleT::create(leaf.attributeArray(targetIndex)); - for (auto iter = leaf.beginIndexOn(filter); iter; ++iter) { - const Vec3d position = transformer.transform( - positionHandle->get(*iter) + iter.getCoord().asVec3d()); - targetHandle->set(*iter, newSampleWrapper.sample(position)); - } - }; - - LeafManagerT leafManager(mPoints.tree()); - - if (mInterrupter) mInterrupter->start(); - - leafManager.foreach(sampleLambda, mThreaded); - - if (mInterrupter) mInterrupter->end(); - } - - template - inline void resolveTransform(const SourceGridT& sourceGrid, const SamplerWrapperT& sampleWrapper, - const Index targetIndex) - { - const auto& sourceTransform = sourceGrid.constTransform(); - const auto& pointsTransform = mPoints.constTransform(); - - if (sourceTransform == pointsTransform) { - AlignedTransform transformer; - doSample(sampleWrapper, targetIndex, transformer); - } else { - NonAlignedTransform transformer(sourceTransform, pointsTransform); - doSample(sampleWrapper, targetIndex, transformer); - } - } - - template - inline void resolveStaggered(const SourceGridT& sourceGrid, const Index targetIndex) - { - using SamplerWrapperT = SamplerWrapper>; - using StaggeredSamplerWrapperT = SamplerWrapper>; - - using SourceValueType = typename SourceGridT::ValueType; - if (VecTraits::Size == 3 && sourceGrid.getGridClass() == GRID_STAGGERED) { - StaggeredSamplerWrapperT sampleWrapper(sourceGrid, mSampler); - resolveTransform(sourceGrid, sampleWrapper, targetIndex); - } else { - SamplerWrapperT sampleWrapper(sourceGrid, mSampler); - resolveTransform(sourceGrid, sampleWrapper, targetIndex); - } - } - -public: - template - inline void sample(const SourceGridT& sourceGrid, Index targetIndex) - { - using SourceValueType = typename SourceGridT::ValueType; - static const bool SourceIsMask = std::is_same::value || - std::is_same::value; - - if (SourceIsMask || mOrder == 0) { - resolveStaggered(sourceGrid, targetIndex); - } else if (mOrder == 1) { - resolveStaggered(sourceGrid, targetIndex); - } else if (mOrder == 2) { - resolveStaggered(sourceGrid, targetIndex); - } - } - -private: - size_t mOrder; - PointDataGridT& mPoints; - const SamplerT& mSampler; - const FilterT& mFilter; - InterrupterT* const mInterrupter; - const bool mThreaded; -}; // class PointDataSampler - - -template -struct AppendAttributeOp -{ - static void append(PointDataGridT& points, const Name& attribute) - { - appendAttribute(points.tree(), attribute); - } -}; -// partial specialization to disable attempts to append attribute type of DummySampleType -template -struct AppendAttributeOp -{ - static void append(PointDataGridT&, const Name&) { } -}; - -} // namespace point_sample_internal - -/// @endcond - -//////////////////////////////////////// - - -template -ValueT SampleWithRounding::sample(const AccessorT& accessor, const Vec3d& position) const -{ - using namespace point_sample_internal; - using SourceValueT = typename AccessorT::ValueType; - static const bool staggered = SamplerTraits::Staggered; - static const bool compatible = CompatibleTypes::value && - (!staggered || (staggered && VecTraits::Size == 3)); - static const bool round = std::is_floating_point::value && - std::is_integral::value; - ValueT value; - SampleWithRoundingOp::sample( - value, accessor, position); - return value; -} - - -//////////////////////////////////////// - - -template -inline void sampleGrid( size_t order, - PointDataGridT& points, - const SourceGridT& sourceGrid, - const Name& targetAttribute, - const FilterT& filter, - const SamplerT& sampler, - InterrupterT* const interrupter, - const bool threaded) -{ - using point_sample_internal::AppendAttributeOp; - using point_sample_internal::PointDataSampler; - - // use the name of the grid if no target attribute name supplied - Name attribute(targetAttribute); - if (targetAttribute.empty()) { - attribute = sourceGrid.getName(); - } - - // we do not allow sampling onto the "P" attribute - if (attribute == "P") { - OPENVDB_THROW(RuntimeError, "Cannot sample onto the \"P\" attribute"); - } - - auto leaf = points.tree().cbeginLeaf(); - if (!leaf) return; - - PointDataSampler pointDataSampler( - order, points, sampler, filter, interrupter, threaded); - - const auto& descriptor = leaf->attributeSet().descriptor(); - size_t targetIndex = descriptor.find(attribute); - const bool attributeExists = targetIndex != AttributeSet::INVALID_POS; - - if (std::is_same::value) { - if (!attributeExists) { - // append attribute of source grid value type - appendAttribute(points.tree(), attribute); - targetIndex = leaf->attributeSet().descriptor().find(attribute); - assert(targetIndex != AttributeSet::INVALID_POS); - - // sample using same type as source grid - pointDataSampler.template sample(sourceGrid, Index(targetIndex)); - } else { - auto targetIdx = static_cast(targetIndex); - // attempt to explicitly sample using type of existing attribute - const Name& targetType = descriptor.valueType(targetIndex); - if (targetType == typeNameAsString()) { - pointDataSampler.template sample(sourceGrid, targetIdx); - } else if (targetType == typeNameAsString()) { - pointDataSampler.template sample(sourceGrid, targetIdx); - } else if (targetType == typeNameAsString()) { - pointDataSampler.template sample(sourceGrid, targetIdx); - } else if (targetType == typeNameAsString()) { - pointDataSampler.template sample(sourceGrid, targetIdx); - } else if (targetType == typeNameAsString()) { - pointDataSampler.template sample(sourceGrid, targetIdx); - } else if (targetType == typeNameAsString()) { - pointDataSampler.template sample(sourceGrid, targetIdx); - } else if (targetType == typeNameAsString()) { - pointDataSampler.template sample(sourceGrid, targetIdx); - } else if (targetType == typeNameAsString()) { - pointDataSampler.template sample(sourceGrid, targetIdx); - } else if (targetType == typeNameAsString()) { - pointDataSampler.template sample(sourceGrid, targetIdx); - } else if (targetType == typeNameAsString()) { - pointDataSampler.template sample(sourceGrid, targetIdx); - } else { - std::ostringstream ostr; - ostr << "Cannot sample attribute of type - " << targetType; - OPENVDB_THROW(TypeError, ostr.str()); - } - } - } else { - if (!attributeExists) { - // append attribute of target value type - // (point_sample_internal wrapper disables the ability to use DummySampleType) - AppendAttributeOp::append(points, attribute); - targetIndex = leaf->attributeSet().descriptor().find(attribute); - assert(targetIndex != AttributeSet::INVALID_POS); - } - else { - const Name targetType = typeNameAsString(); - const Name attributeType = descriptor.valueType(targetIndex); - if (targetType != attributeType) { - std::ostringstream ostr; - ostr << "Requested attribute type " << targetType << " for sampling " - << " does not match existing attribute type " << attributeType; - OPENVDB_THROW(TypeError, ostr.str()); - } - } - - // sample using target value type - pointDataSampler.template sample( - sourceGrid, static_cast(targetIndex)); - } -} - -template -inline void pointSample(PointDataGridT& points, - const SourceGridT& sourceGrid, - const Name& targetAttribute, - const FilterT& filter, - InterrupterT* const interrupter) -{ - SampleWithRounding sampler; - sampleGrid(/*order=*/0, points, sourceGrid, targetAttribute, filter, sampler, interrupter); -} - -template -inline void boxSample( PointDataGridT& points, - const SourceGridT& sourceGrid, - const Name& targetAttribute, - const FilterT& filter, - InterrupterT* const interrupter) -{ - SampleWithRounding sampler; - sampleGrid(/*order=*/1, points, sourceGrid, targetAttribute, filter, sampler, interrupter); -} - -template -inline void quadraticSample(PointDataGridT& points, - const SourceGridT& sourceGrid, - const Name& targetAttribute, - const FilterT& filter, - InterrupterT* const interrupter) -{ - SampleWithRounding sampler; - sampleGrid(/*order=*/2, points, sourceGrid, targetAttribute, filter, sampler, interrupter); -} - - -//////////////////////////////////////// - - } // namespace points } // namespace OPENVDB_VERSION_NAME } // namespace openvdb +#include "impl/PointSampleImpl.h" + #endif // OPENVDB_POINTS_POINT_SAMPLE_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/PointScatter.h b/openvdb/openvdb/points/PointScatter.h index cc031d901e..e089da513c 100644 --- a/openvdb/openvdb/points/PointScatter.h +++ b/openvdb/openvdb/points/PointScatter.h @@ -143,414 +143,10 @@ nonUniformPointScatter(const GridT& grid, const float spread = 1.0f, InterrupterT* interrupter = nullptr); - -//////////////////////////////////////// - -/// @cond OPENVDB_DOCS_INTERNAL - -namespace point_scatter_internal -{ - -/// @brief initialise the topology of a PointDataGrid and ensure -/// everything is voxelized -/// @param grid The source grid from which to base the topology generation -template -inline typename PointDataGridT::Ptr -initialisePointTopology(const GridT& grid) -{ - typename PointDataGridT::Ptr points(new PointDataGridT); - points->setTransform(grid.transform().copy()); - points->topologyUnion(grid); - if (points->tree().hasActiveTiles()) { - points->tree().voxelizeActiveTiles(); - } - - return points; -} - -/// @brief Generate random point positions for a leaf node -/// @param leaf The leaf node to initialize -/// @param descriptor The descriptor containing the position type -/// @param count The number of points to generate -/// @param spread The spread of points from the voxel center -/// @param rand01 The random number generator, expected to produce floating point -/// values between 0 and 1. -template -inline void -generatePositions(LeafNodeT& leaf, - const AttributeSet::Descriptor::Ptr& descriptor, - const Index64& count, - const float spread, - RandGenT& rand01) -{ - using PositionTraits = VecTraits; - using ValueType = typename PositionTraits::ElementType; - using PositionWriteHandle = AttributeWriteHandle; - - leaf.initializeAttributes(descriptor, static_cast(count)); - - // directly expand to avoid needlessly setting uniform values in the - // write handle - auto& array = leaf.attributeArray(0); - array.expand(/*fill*/false); - - PositionWriteHandle pHandle(array, /*expand*/false); - PositionType P; - for (Index64 index = 0; index < count; ++index) { - P[0] = (spread * (rand01() - ValueType(0.5))); - P[1] = (spread * (rand01() - ValueType(0.5))); - P[2] = (spread * (rand01() - ValueType(0.5))); - pHandle.set(static_cast(index), P); - } -} - -} // namespace point_scatter_internal - -/// @endcond - -//////////////////////////////////////// - - -template< - typename GridT, - typename RandGenT, - typename PositionArrayT, - typename PointDataGridT, - typename InterrupterT> -inline typename PointDataGridT::Ptr -uniformPointScatter(const GridT& grid, - const Index64 count, - const unsigned int seed, - const float spread, - InterrupterT* interrupter) -{ - using PositionType = typename PositionArrayT::ValueType; - using PositionTraits = VecTraits; - using ValueType = typename PositionTraits::ElementType; - using CodecType = typename PositionArrayT::Codec; - - using RandomGenerator = math::Rand01; - - using TreeType = typename PointDataGridT::TreeType; - using LeafNodeType = typename TreeType::LeafNodeType; - using LeafManagerT = tree::LeafManager; - - struct Local - { - /// @brief Get the prefixed voxel counts for each leaf node with an - /// additional value to represent the end voxel count. - /// See also LeafManager::getPrefixSum() - static void getPrefixSum(LeafManagerT& leafManager, - std::vector& offsets) - { - Index64 offset = 0; - offsets.reserve(leafManager.leafCount() + 1); - offsets.push_back(0); - const auto leafRange = leafManager.leafRange(); - for (auto leaf = leafRange.begin(); leaf; ++leaf) { - offset += leaf->onVoxelCount(); - offsets.push_back(offset); - } - } - }; - - static_assert(PositionTraits::IsVec && PositionTraits::Size == 3, - "Invalid Position Array type."); - - if (spread < 0.0f || spread > 1.0f) { - OPENVDB_THROW(ValueError, "Spread must be between 0 and 1."); - } - - if (interrupter) interrupter->start("Uniform scattering with fixed point count"); - - typename PointDataGridT::Ptr points = - point_scatter_internal::initialisePointTopology(grid); - TreeType& tree = points->tree(); - if (!tree.cbeginLeaf()) return points; - - LeafManagerT leafManager(tree); - const Index64 voxelCount = leafManager.activeLeafVoxelCount(); - assert(voxelCount != 0); - - const double pointsPerVolume = double(count) / double(voxelCount); - const Index32 pointsPerVoxel = static_cast(math::RoundDown(pointsPerVolume)); - const Index64 remainder = count - (pointsPerVoxel * voxelCount); - - if (remainder == 0) { - return denseUniformPointScatter< - GridT, RandGenT, PositionArrayT, PointDataGridT, InterrupterT>( - grid, float(pointsPerVoxel), seed, spread, interrupter); - } - - std::vector voxelOffsets, values; - std::thread worker(&Local::getPrefixSum, std::ref(leafManager), std::ref(voxelOffsets)); - - { - math::RandInt gen(seed, 0, voxelCount-1); - values.reserve(remainder); - for (Index64 i = 0; i < remainder; ++i) values.emplace_back(gen()); - } - - worker.join(); - - if (util::wasInterrupted(interrupter)) { - tree.clear(); - return points; - } - - tbb::parallel_sort(values.begin(), values.end()); - const bool fractionalOnly(pointsPerVoxel == 0); - - leafManager.foreach([&voxelOffsets, &values, fractionalOnly] - (LeafNodeType& leaf, const size_t idx) - { - const Index64 lowerOffset = voxelOffsets[idx]; // inclusive - const Index64 upperOffset = voxelOffsets[idx + 1]; // exclusive - assert(upperOffset > lowerOffset); - - const auto valuesEnd = values.end(); - auto lower = std::lower_bound(values.begin(), valuesEnd, lowerOffset); - - auto* const data = leaf.buffer().data(); - auto iter = leaf.beginValueOn(); - - Index32 currentOffset(0); - bool addedPoints(!fractionalOnly); - while (lower != valuesEnd) { - const Index64 vId = *lower; - if (vId >= upperOffset) break; - - const Index32 nextOffset = Index32(vId - lowerOffset); - iter.increment(nextOffset - currentOffset); - currentOffset = nextOffset; - assert(iter); - - auto& value = data[iter.pos()]; - value = value + 1; // no += operator support - addedPoints = true; - ++lower; - } - - // deactivate this leaf if no points were added. This will speed up - // the unthreaded rng - if (!addedPoints) leaf.setValuesOff(); - }); - - voxelOffsets.clear(); - values.clear(); - - if (fractionalOnly) { - tools::pruneInactive(tree); - leafManager.rebuild(); - } - - const AttributeSet::Descriptor::Ptr descriptor = - AttributeSet::Descriptor::create(PositionArrayT::attributeType()); - RandomGenerator rand01(seed); - - const auto leafRange = leafManager.leafRange(); - auto leaf = leafRange.begin(); - for (; leaf; ++leaf) { - if (util::wasInterrupted(interrupter)) break; - Index32 offset(0); - for (auto iter = leaf->beginValueAll(); iter; ++iter) { - if (iter.isValueOn()) { - const Index32 value = Index32(pointsPerVolume + Index32(*iter)); - if (value == 0) leaf->setValueOff(iter.pos()); - else offset += value; - } - // @note can't use iter.setValue(offset) on point grids - leaf->setOffsetOnly(iter.pos(), offset); - } - - // offset should always be non zero - assert(offset != 0); - point_scatter_internal::generatePositions - (*leaf, descriptor, offset, spread, rand01); - } - - // if interrupted, remove remaining leaf nodes - if (leaf) { - for (; leaf; ++leaf) leaf->setValuesOff(); - tools::pruneInactive(tree); - } - - if (interrupter) interrupter->end(); - return points; -} - - -//////////////////////////////////////// - - -template< - typename GridT, - typename RandGenT, - typename PositionArrayT, - typename PointDataGridT, - typename InterrupterT> -inline typename PointDataGridT::Ptr -denseUniformPointScatter(const GridT& grid, - const float pointsPerVoxel, - const unsigned int seed, - const float spread, - InterrupterT* interrupter) -{ - using PositionType = typename PositionArrayT::ValueType; - using PositionTraits = VecTraits; - using ValueType = typename PositionTraits::ElementType; - using CodecType = typename PositionArrayT::Codec; - - using RandomGenerator = math::Rand01; - - using TreeType = typename PointDataGridT::TreeType; - - static_assert(PositionTraits::IsVec && PositionTraits::Size == 3, - "Invalid Position Array type."); - - if (pointsPerVoxel < 0.0f) { - OPENVDB_THROW(ValueError, "Points per voxel must not be less than zero."); - } - - if (spread < 0.0f || spread > 1.0f) { - OPENVDB_THROW(ValueError, "Spread must be between 0 and 1."); - } - - if (interrupter) interrupter->start("Dense uniform scattering with fixed point count"); - - typename PointDataGridT::Ptr points = - point_scatter_internal::initialisePointTopology(grid); - TreeType& tree = points->tree(); - auto leafIter = tree.beginLeaf(); - if (!leafIter) return points; - - const Index32 pointsPerVoxelInt = math::Floor(pointsPerVoxel); - const double delta = pointsPerVoxel - float(pointsPerVoxelInt); - const bool fractional = !math::isApproxZero(delta, 1.0e-6); - const bool fractionalOnly = pointsPerVoxelInt == 0; - - const AttributeSet::Descriptor::Ptr descriptor = - AttributeSet::Descriptor::create(PositionArrayT::attributeType()); - RandomGenerator rand01(seed); - - for (; leafIter; ++leafIter) { - if (util::wasInterrupted(interrupter)) break; - Index32 offset(0); - for (auto iter = leafIter->beginValueAll(); iter; ++iter) { - if (iter.isValueOn()) { - offset += pointsPerVoxelInt; - if (fractional && rand01() < delta) ++offset; - else if (fractionalOnly) leafIter->setValueOff(iter.pos()); - } - // @note can't use iter.setValue(offset) on point grids - leafIter->setOffsetOnly(iter.pos(), offset); - } - - if (offset != 0) { - point_scatter_internal::generatePositions - (*leafIter, descriptor, offset, spread, rand01); - } - } - - // if interrupted, remove remaining leaf nodes - const bool prune(leafIter || fractionalOnly); - for (; leafIter; ++leafIter) leafIter->setValuesOff(); - - if (prune) tools::pruneInactive(tree); - if (interrupter) interrupter->end(); - return points; -} - - -//////////////////////////////////////// - - -template< - typename GridT, - typename RandGenT, - typename PositionArrayT, - typename PointDataGridT, - typename InterrupterT> -inline typename PointDataGridT::Ptr -nonUniformPointScatter(const GridT& grid, - const float pointsPerVoxel, - const unsigned int seed, - const float spread, - InterrupterT* interrupter) -{ - using PositionType = typename PositionArrayT::ValueType; - using PositionTraits = VecTraits; - using ValueType = typename PositionTraits::ElementType; - using CodecType = typename PositionArrayT::Codec; - - using RandomGenerator = math::Rand01; - - using TreeType = typename PointDataGridT::TreeType; - - static_assert(PositionTraits::IsVec && PositionTraits::Size == 3, - "Invalid Position Array type."); - static_assert(std::is_arithmetic::value, - "Scalar grid type required for weighted voxel scattering."); - - if (pointsPerVoxel < 0.0f) { - OPENVDB_THROW(ValueError, "Points per voxel must not be less than zero."); - } - - if (spread < 0.0f || spread > 1.0f) { - OPENVDB_THROW(ValueError, "Spread must be between 0 and 1."); - } - - if (interrupter) interrupter->start("Non-uniform scattering with local point density"); - - typename PointDataGridT::Ptr points = - point_scatter_internal::initialisePointTopology(grid); - TreeType& tree = points->tree(); - auto leafIter = tree.beginLeaf(); - if (!leafIter) return points; - - const AttributeSet::Descriptor::Ptr descriptor = - AttributeSet::Descriptor::create(PositionArrayT::attributeType()); - RandomGenerator rand01(seed); - const auto accessor = grid.getConstAccessor(); - - for (; leafIter; ++leafIter) { - if (util::wasInterrupted(interrupter)) break; - Index32 offset(0); - for (auto iter = leafIter->beginValueAll(); iter; ++iter) { - if (iter.isValueOn()) { - double fractional = - double(accessor.getValue(iter.getCoord())) * pointsPerVoxel; - fractional = std::max(0.0, fractional); - int count = int(fractional); - if (rand01() < (fractional - double(count))) ++count; - else if (count == 0) leafIter->setValueOff(iter.pos()); - offset += count; - } - // @note can't use iter.setValue(offset) on point grids - leafIter->setOffsetOnly(iter.pos(), offset); - } - - if (offset != 0) { - point_scatter_internal::generatePositions - (*leafIter, descriptor, offset, spread, rand01); - } - } - - // if interrupted, remove remaining leaf nodes - for (; leafIter; ++leafIter) leafIter->setValuesOff(); - - tools::pruneInactive(points->tree()); - if (interrupter) interrupter->end(); - return points; -} - - } // namespace points } // namespace OPENVDB_VERSION_NAME } // namespace openvdb +#include "impl/PointScatterImpl.h" #endif // OPENVDB_POINTS_POINT_SCATTER_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/PointStatistics.h b/openvdb/openvdb/points/PointStatistics.h index df9555fbbb..74e310576d 100644 --- a/openvdb/openvdb/points/PointStatistics.h +++ b/openvdb/openvdb/points/PointStatistics.h @@ -214,530 +214,10 @@ bool accumulate(const PointDataTreeT& points, const FilterT& filter = NullFilter(), typename PointDataTreeT::template ValueConverter::Type* totalTree = nullptr); -/////////////////////////////////////////////////// -/////////////////////////////////////////////////// - -namespace statistics_internal -{ - -/// @brief Scalar extent op to evaluate the min/max values of a -/// single integral or floating point attribute type -template -struct ScalarMinMax -{ - using ExtentT = std::pair; - ScalarMinMax(const ValueT& init) : mMinMax(init, init) {} - ScalarMinMax(const ExtentT& init) : mMinMax(init) {} - inline void operator()(const ValueT& b) - { - mMinMax.first = std::min(mMinMax.first, b); - mMinMax.second = std::max(mMinMax.second, b); - } - inline void operator()(const ExtentT& b) - { - mMinMax.first = std::min(mMinMax.first, b.first); - mMinMax.second = std::max(mMinMax.second, b.second); - } - inline const ExtentT& get() const { return mMinMax; } - ExtentT mMinMax; -}; - -/// @brief Vector squared magnitude op to evaluate the min/max of a -/// vector attribute and return the result as a scalar of the -/// appropriate precision -template -struct MagnitudeExtent - : public ScalarMinMax::ElementType> -{ - using ElementT = typename ValueTraits::ElementType; - using ExtentT = typename ScalarMinMax::ExtentT; - using BaseT = ScalarMinMax; - MagnitudeExtent(const VecT& init) : BaseT(init.lengthSqr()) {} - MagnitudeExtent(const ExtentT& init) : BaseT(init) {} - inline void operator()(const VecT& b) { this->BaseT::operator()(b.lengthSqr()); } - inline void operator()(const ExtentT& b) { this->BaseT::operator()(b); } -}; - -/// @brief Vector squared magnitude op to evaluate the min/max of a -/// vector attribute and return the result as the original vector -template -struct MagnitudeExtent -{ - using ElementT = typename ValueTraits::ElementType; - using ExtentT = std::pair; - MagnitudeExtent(const VecT& init) - : mLengths(), mMinMax(init, init) { - mLengths.first = init.lengthSqr(); - mLengths.second = mLengths.first; - } - MagnitudeExtent(const ExtentT& init) - : mLengths(), mMinMax(init) { - mLengths.first = init.first.lengthSqr(); - mLengths.second = init.second.lengthSqr(); - } - inline const ExtentT& get() const { return mMinMax; } - inline void operator()(const VecT& b) - { - const ElementT l = b.lengthSqr(); - if (l < mLengths.first) { - mLengths.first = l; - mMinMax.first = b; - } - else if (l > mLengths.second) { - mLengths.second = l; - mMinMax.second = b; - } - } - inline void operator()(const ExtentT& b) - { - ElementT l = b.first.lengthSqr(); - if (l < mLengths.first) { - mLengths.first = l; - mMinMax.first = b.first; - } - l = b.second.lengthSqr(); - if (l > mLengths.second) { - mLengths.second = l; - mMinMax.second = b.second; - } - } - - std::pair mLengths; - ExtentT mMinMax; -}; - -/// @brief Vector component-wise op to evaluate the min/max of -/// vector components and return the result as a vector of -/// equal size and precision -template -struct ComponentExtent -{ - using ExtentT = std::pair; - ComponentExtent(const VecT& init) : mMinMax(init, init) {} - ComponentExtent(const ExtentT& init) : mMinMax(init) {} - inline const ExtentT& get() const { return mMinMax; } - inline void operator()(const VecT& b) - { - mMinMax.first = math::minComponent(mMinMax.first, b); - mMinMax.second = math::maxComponent(mMinMax.second, b); - } - inline void operator()(const ExtentT& b) - { - mMinMax.first = math::minComponent(mMinMax.first, b.first); - mMinMax.second = math::maxComponent(mMinMax.second, b.second); - } - - ExtentT mMinMax; -}; - -template -bool evalExtents(const PointDataTreeT& points, - const std::string& attribute, - typename ExtentOp::ExtentT& ext, - const FilterT& filter, - typename PointDataTreeT::template ValueConverter - ::Type* const minTree = nullptr, - typename PointDataTreeT::template ValueConverter - ::Type* const maxTree = nullptr) -{ - static_assert(std::is_base_of::value, - "PointDataTreeT in instantiation of evalExtents is not an openvdb Tree type"); - - struct ResultType { - typename ExtentOp::ExtentT ext; - bool data = false; - }; - - tree::LeafManager manager(points); - if (manager.leafCount() == 0) return false; - const size_t idx = manager.leaf(0).attributeSet().find(attribute); - if (idx == AttributeSet::INVALID_POS) return false; - - // track results per leaf for min/max trees - std::vector> values; - if (minTree || maxTree) values.resize(manager.leafCount()); - - const ResultType result = tbb::parallel_reduce(manager.leafRange(), - ResultType(), - [idx, &filter, &values] - (const auto& range, ResultType in) -> ResultType - { - for (auto leaf = range.begin(); leaf; ++leaf) { - AttributeHandle handle(leaf->constAttributeArray(idx)); - if (handle.size() == 0) continue; - if (filter.state() == index::ALL) { - const size_t size = handle.isUniform() ? 1 : handle.size(); - ExtentOp op(handle.get(0)); - for (size_t i = 1; i < size; ++i) { - assert(i < size_t(std::numeric_limits::max())); - op(handle.get(Index(i))); - } - if (!values.empty()) { - values[leaf.pos()].reset(new typename ExtentOp::ExtentT(op.get())); - } - if (in.data) op(in.ext); - in.data = true; - in.ext = op.get(); - } - else { - auto iter = leaf->beginIndexOn(filter); - if (!iter) continue; - ExtentOp op(handle.get(*iter)); - ++iter; - for (; iter; ++iter) op(handle.get(*iter)); - if (!values.empty()) { - values[leaf.pos()].reset(new typename ExtentOp::ExtentT(op.get())); - } - if (in.data) op(in.ext); - in.data = true; - in.ext = op.get(); - } - } - - return in; - }, - [](const ResultType& a, const ResultType& b) -> ResultType { - if (!b.data) return a; - if (!a.data) return b; - ExtentOp op(a.ext); op(b.ext); - ResultType t; - t.ext = op.get(); - t.data = true; - return t; - }); - - // set minmax trees only if a new value was set - if the value - // hasn't changed, leave it as inactive background (this is - // only possible if a point leaf exists with no points or if a - // filter is provided but is not hit for a given leaf) - if (minTree || maxTree) { - manager.foreach([minTree, maxTree, &values] - (const auto& leaf, const size_t idx) { - const auto& v = values[idx]; - if (v == nullptr) return; - const Coord& origin = leaf.origin(); - if (minTree) minTree->addTile(1, origin, v->first, true); - if (maxTree) maxTree->addTile(1, origin, v->second, true); - }, false); - } - - if (result.data) ext = result.ext; - return result.data; -} - -template ::IsVec, int>::type = 0> -bool evalExtents(const PointDataTreeT& points, - const std::string& attribute, - ValueT& min, - ValueT& max, - const FilterT& filter, - typename PointDataTreeT::template ValueConverter::Type* minTree, - typename PointDataTreeT::template ValueConverter::Type* maxTree) -{ - typename ComponentExtent::ExtentT ext; - const bool s = evalExtents, PointDataTreeT> - (points, attribute, ext, filter, minTree, maxTree); - if (s) min = ext.first, max = ext.second; - return s; -} - -template ::IsVec, int>::type = 0> -bool evalExtents(const PointDataTreeT& points, - const std::string& attribute, - ValueT& min, - ValueT& max, - const FilterT& filter, - typename PointDataTreeT::template ValueConverter::Type* minTree, - typename PointDataTreeT::template ValueConverter::Type* maxTree) -{ - typename ScalarMinMax::ExtentT ext; - const bool s = evalExtents, PointDataTreeT> - (points, attribute, ext, filter, minTree, maxTree); - if (s) min = ext.first, max = ext.second; - return s; -} - -} // namespace statistics_internal - -template -bool evalMinMax(const PointDataTreeT& points, - const std::string& attribute, - ValueT& min, - ValueT& max, - const FilterT& filter, - typename PointDataTreeT::template ValueConverter::Type* minTree, - typename PointDataTreeT::template ValueConverter::Type* maxTree) -{ - return statistics_internal::evalExtents - (points, attribute, min, max, filter, minTree, maxTree); -} - -template -bool evalAverage(const PointDataTreeT& points, - const std::string& attribute, - typename ConvertElementType::Type& average, - const FilterT& filter, - typename PointDataTreeT::template ValueConverter::Type* averageTree) -{ - using ResultT = typename ConvertElementType::Type; - - struct Sample - { - Sample(const ResultT& _avg, size_t _size) : avg(_avg), size(_size) {} - - void add(const ResultT& val) - { - ++size; - const ResultT delta = val - avg; - avg = avg + (delta / static_cast(size)); - } - - void add(const Sample& other) - { - assert(other.size > 0); - const double denom = 1.0 / static_cast(size + other.size); - const ResultT delta = other.avg - avg; - avg = avg + (denom * delta * static_cast(other.size)); - size += other.size; - } - - ResultT avg; size_t size; - }; - - static_assert(std::is_base_of::value, - "PointDataTreeT in instantiation of evalAverage is not an openvdb Tree type"); - static_assert(std::is_constructible::value, - "Target value in points::evalAverage is not constructible from the source value type."); - - tree::LeafManager manager(points); - if (manager.leafCount() == 0) return false; - const size_t idx = manager.leaf(0).attributeSet().find(attribute); - if (idx == AttributeSet::INVALID_POS) return false; - - std::vector> values; - values.resize(manager.leafCount()); - tbb::parallel_for(manager.leafRange(), - [idx, &filter, &values] (const auto& range) { - for (auto leaf = range.begin(); leaf; ++leaf) { - AttributeHandle handle(leaf->constAttributeArray(idx)); - size_t size = handle.size(); - if (size == 0) continue; - if (filter.state() == index::ALL) { - std::unique_ptr S(new Sample(ResultT(handle.get(0)), 1)); - if (handle.isUniform()) { - S->avg = S->avg / static_cast(size); - S->size = size; - } - else { - for (size_t i = 1; i < size; ++i) { - assert(i < size_t(std::numeric_limits::max())); - S->add(ResultT(handle.get(Index(i)))); - } - } - values[leaf.pos()] = std::move(S); - } - else { - auto iter = leaf->beginIndexOn(filter); - if (!iter) continue; - std::unique_ptr S(new Sample(ResultT(handle.get(*iter)), 1)); - ++iter; - for (; iter; ++iter, ++size) { - S->add(ResultT(handle.get(*iter))); - } - values[leaf.pos()] = std::move(S); - } - } - }); - - auto iter = values.cbegin(); - while (iter != values.cend() && !(*iter)) ++iter; - if (iter == values.cend()) return false; - assert(*iter); - - // serial deterministic reduction of floating point samples - Sample S = **iter; - ++iter; - for (; iter != values.cend(); ++iter) { - if (*iter) S.add(**iter); - } - average = S.avg; - - // set average tree only if a new value was set - if the value - // hasn't changed, leave it as inactive background (this is - // only possible if a point leaf exists with no points or if a - // filter is provided but is not hit for a given leaf) - if (averageTree) { - manager.foreach([averageTree, &values] - (const auto& leaf, const size_t idx) { - const auto& S = values[idx]; - if (S == nullptr) return; - const Coord& origin = leaf.origin(); - averageTree->addTile(1, origin, S->avg, true); - }, false); - } - - return true; -} - -template -bool accumulate(const PointDataTreeT& points, - const std::string& attribute, - typename PromoteType::Highest& total, - const FilterT& filter, - typename PointDataTreeT::template ValueConverter::Type* totalTree) -{ - using ResultT = typename PromoteType::Highest; - using ElementT = typename ValueTraits::ElementType; - - static_assert(std::is_base_of::value, - "PointDataTreeT in instantiation of accumulate is not an openvdb Tree type"); - static_assert(std::is_constructible::value, - "Target value in points::accumulate is not constructible from the source value type."); - - tree::LeafManager manager(points); - if (manager.leafCount() == 0) return false; - const size_t idx = manager.leaf(0).attributeSet().find(attribute); - if (idx == AttributeSet::INVALID_POS) return false; - - std::vector> values; - values.resize(manager.leafCount()); - tbb::parallel_for(manager.leafRange(), - [idx, &filter, &values](const auto& range) { - for (auto leaf = range.begin(); leaf; ++leaf) { - AttributeHandle handle(leaf->constAttributeArray(idx)); - if (handle.size() == 0) continue; - if (filter.state() == index::ALL) { - const size_t size = handle.isUniform() ? 1 : handle.size(); - auto total = ResultT(handle.get(0)); - for (size_t i = 1; i < size; ++i) { - assert(i < size_t(std::numeric_limits::max())); - total += ResultT(handle.get(Index(i))); - } - values[leaf.pos()].reset(new ResultT(total)); - } - else { - auto iter = leaf->beginIndexOn(filter); - if (!iter) continue; - auto total = ResultT(handle.get(*iter)); - ++iter; - for (; iter; ++iter) total += ResultT(handle.get(*iter)); - values[leaf.pos()].reset(new ResultT(total)); - } - } - }); - - auto iter = values.cbegin(); - while (iter != values.cend() && !(*iter)) ++iter; - if (iter == values.cend()) return false; - assert(*iter); - total = **iter; ++iter; - - if (std::is_integral::value) { - using RangeT = tbb::blocked_range*>; - // reasonable grain size for accumulation of single to matrix types - total = tbb::parallel_reduce(RangeT(&(*iter), (&values.back())+1, 32), total, - [](const RangeT& range, ResultT p) -> ResultT { - for (const auto& r : range) if (r) p += *r; - return p; - }, std::plus()); - } - else { - for (; iter != values.cend(); ++iter) { - if (*iter) total += (**iter); - } - } - - // set total tree only if a new value was set - if the value - // hasn't changed, leave it as inactive background (this is - // only possible if a point leaf exists with no points or if a - // filter is provided but is not hit for a given leaf) - if (totalTree) { - manager.foreach([totalTree, &values] - (const auto& leaf, const size_t idx) { - const auto& v = values[idx]; - if (v == nullptr) return; - const Coord& origin = leaf.origin(); - totalTree->addTile(1, origin, *v, true); - }, false); - } - - return true; -} - -template -std::pair -evalMinMax(const PointDataTreeT& points, - const std::string& attribute, - const FilterT& filter) -{ - std::pair results { - zeroVal(), zeroVal() - }; - evalMinMax - (points, attribute, results.first, results.second, filter); - return results; -} - -template -typename ConvertElementType::Type -evalAverage(const PointDataTreeT& points, - const std::string& attribute, - const FilterT& filter) -{ - using ConvertedT = typename ConvertElementType::Type; - ConvertedT result = zeroVal(); - evalAverage(points, attribute, result, filter); - return result; -} - -template -typename PromoteType::Highest -accumulate(const PointDataTreeT& points, - const std::string& attribute, - const FilterT& filter) -{ - using PromotedT = typename PromoteType::Highest; - PromotedT result = zeroVal(); - accumulate(points, attribute, result, filter); - return result; -} - } // namespace points } // namespace OPENVDB_VERSION_NAME } // namespace openvdb +#include "impl/PointStatisticsImpl.h" + #endif // OPENVDB_POINTS_STATISTICS_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/impl/PointAttributeImpl.h b/openvdb/openvdb/points/impl/PointAttributeImpl.h new file mode 100644 index 0000000000..6100bbf9d1 --- /dev/null +++ b/openvdb/openvdb/points/impl/PointAttributeImpl.h @@ -0,0 +1,406 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 + +/// @author Dan Bailey, Khang Ngo +/// +/// @file PointAttributeImpl.h +/// + +#ifndef OPENVDB_POINTS_POINT_ATTRIBUTE_IMPL_HAS_BEEN_INCLUDED +#define OPENVDB_POINTS_POINT_ATTRIBUTE_IMPL_HAS_BEEN_INCLUDED + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace points { + +/// @cond OPENVDB_DOCS_INTERNAL + +namespace point_attribute_internal { + + +template +inline void collapseAttribute(AttributeArray& array, + const AttributeSet::Descriptor&, const ValueType& uniformValue) +{ + AttributeWriteHandle handle(array); + handle.collapse(uniformValue); +} + + +inline void collapseAttribute(AttributeArray& array, + const AttributeSet::Descriptor& descriptor, const Name& uniformValue) +{ + StringAttributeWriteHandle handle(array, descriptor.getMetadata()); + handle.collapse(uniformValue); +} + + +//////////////////////////////////////// + + +template +struct AttributeTypeConversion +{ + static const NamePair& type() { + return TypedAttributeArray::attributeType(); + } +}; + + +template +struct AttributeTypeConversion +{ + static const NamePair& type() { return StringAttributeArray::attributeType(); } +}; + + +//////////////////////////////////////// + + +template +struct MetadataStorage +{ + static void add(PointDataTreeT&, const ValueType&) {} + + template + static void add(PointDataTreeT&, const AttributeListType&) {} +}; + + +template +struct MetadataStorage +{ + static void add(PointDataTreeT& tree, const Name& uniformValue) { + MetaMap& metadata = makeDescriptorUnique(tree)->getMetadata(); + StringMetaInserter inserter(metadata); + inserter.insert(uniformValue); + } + + template + static void add(PointDataTreeT& tree, const AttributeListType& data) { + MetaMap& metadata = makeDescriptorUnique(tree)->getMetadata(); + StringMetaInserter inserter(metadata); + Name value; + + for (size_t i = 0; i < data.size(); i++) { + data.get(value, i); + inserter.insert(value); + } + } +}; + + +} // namespace point_attribute_internal + +/// @endcond + + +//////////////////////////////////////// + + +template +inline void appendAttribute(PointDataTreeT& tree, + const Name& name, + const NamePair& type, + const Index strideOrTotalSize, + const bool constantStride, + const Metadata* defaultValue, + const bool hidden, + const bool transient) +{ + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + // do not append a non-unique attribute + + const auto& descriptor = iter->attributeSet().descriptor(); + const size_t index = descriptor.find(name); + + if (index != AttributeSet::INVALID_POS) { + OPENVDB_THROW(KeyError, + "Cannot append an attribute with a non-unique name - " << name << "."); + } + + // create a new attribute descriptor + + auto newDescriptor = descriptor.duplicateAppend(name, type); + + // store the attribute default value in the descriptor metadata + + if (defaultValue) { + newDescriptor->setDefaultValue(name, *defaultValue); + } + + // extract new pos + + const size_t pos = newDescriptor->find(name); + + // acquire registry lock to avoid locking when appending attributes in parallel + + AttributeArray::ScopedRegistryLock lock; + + // insert attributes using the new descriptor + + tree::LeafManager leafManager(tree); + leafManager.foreach( + [&](typename PointDataTreeT::LeafNodeType& leaf, size_t /*idx*/) { + auto expected = leaf.attributeSet().descriptorPtr(); + + auto attribute = leaf.appendAttribute(*expected, newDescriptor, + pos, strideOrTotalSize, constantStride, defaultValue, + &lock); + + if (hidden) attribute->setHidden(true); + if (transient) attribute->setTransient(true); + }, /*threaded=*/ true + ); +} + + +//////////////////////////////////////// + + +template +inline void appendAttribute(PointDataTreeT& tree, + const std::string& name, + const ValueType& uniformValue, + const Index strideOrTotalSize, + const bool constantStride, + const TypedMetadata* defaultValue, + const bool hidden, + const bool transient) +{ + static_assert(!std::is_base_of::value, + "ValueType must not be derived from AttributeArray"); + + using point_attribute_internal::AttributeTypeConversion; + using point_attribute_internal::Default; + using point_attribute_internal::MetadataStorage; + + appendAttribute(tree, name, AttributeTypeConversion::type(), + strideOrTotalSize, constantStride, defaultValue, hidden, transient); + + // if the uniform value is equal to either the default value provided + // through the metadata argument or the default value for this value type, + // it is not necessary to perform the collapse + + const bool uniformIsDefault = math::isExactlyEqual(uniformValue, + bool(defaultValue) ? defaultValue->value() : Default::value()); + if (!uniformIsDefault) { + MetadataStorage::add(tree, uniformValue); + collapseAttribute(tree, name, uniformValue); + } +} + + +//////////////////////////////////////// + + +template +inline void collapseAttribute( PointDataTreeT& tree, + const Name& name, + const ValueType& uniformValue) +{ + static_assert(!std::is_base_of::value, + "ValueType must not be derived from AttributeArray"); + + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + const auto& descriptor = iter->attributeSet().descriptor(); + + // throw if attribute name does not exist + + const size_t index = descriptor.find(name); + if (index == AttributeSet::INVALID_POS) { + OPENVDB_THROW(KeyError, "Cannot find attribute name in PointDataTree."); + } + + tree::LeafManager leafManager(tree); + leafManager.foreach( + [&](typename PointDataTreeT::LeafNodeType& leaf, size_t /*idx*/) { + assert(leaf.hasAttribute(index)); + AttributeArray& array = leaf.attributeArray(index); + point_attribute_internal::collapseAttribute( + array, descriptor, uniformValue); + }, /*threaded=*/true + ); +} + + +//////////////////////////////////////// + + +template +inline void dropAttributes( PointDataTreeT& tree, + const std::vector& indices) +{ + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + const auto& descriptor = iter->attributeSet().descriptor(); + + // throw if position index present in the indices as this attribute is mandatory + + const size_t positionIndex = descriptor.find("P"); + if (positionIndex!= AttributeSet::INVALID_POS && + std::find(indices.begin(), indices.end(), positionIndex) != indices.end()) { + OPENVDB_THROW(KeyError, "Cannot drop mandatory position attribute."); + } + + // insert attributes using the new descriptor + + auto newDescriptor = descriptor.duplicateDrop(indices); + + tree::LeafManager leafManager(tree); + leafManager.foreach( + [&](typename PointDataTreeT::LeafNodeType& leaf, size_t /*idx*/) { + auto expected = leaf.attributeSet().descriptorPtr(); + leaf.dropAttributes(indices, *expected, newDescriptor); + }, /*threaded=*/true + ); +} + + +//////////////////////////////////////// + + +template +inline void dropAttributes( PointDataTreeT& tree, + const std::vector& names) +{ + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + const AttributeSet& attributeSet = iter->attributeSet(); + const AttributeSet::Descriptor& descriptor = attributeSet.descriptor(); + + std::vector indices; + + for (const Name& name : names) { + const size_t index = descriptor.find(name); + + // do not attempt to drop an attribute that does not exist + if (index == AttributeSet::INVALID_POS) { + OPENVDB_THROW(KeyError, + "Cannot drop an attribute that does not exist - " << name << "."); + } + + indices.push_back(index); + } + + dropAttributes(tree, indices); +} + + +//////////////////////////////////////// + + +template +inline void dropAttribute( PointDataTreeT& tree, + const size_t& index) +{ + std::vector indices{index}; + dropAttributes(tree, indices); +} + + +template +inline void dropAttribute( PointDataTreeT& tree, + const Name& name) +{ + std::vector names{name}; + dropAttributes(tree, names); +} + + +//////////////////////////////////////// + + +template +inline void renameAttributes( PointDataTreeT& tree, + const std::vector& oldNames, + const std::vector& newNames) +{ + if (oldNames.size() != newNames.size()) { + OPENVDB_THROW(ValueError, "Mis-matching sizes of name vectors, cannot rename attributes."); + } + + using Descriptor = AttributeSet::Descriptor; + + auto iter = tree.beginLeaf(); + + if (!iter) return; + + const AttributeSet& attributeSet = iter->attributeSet(); + const Descriptor::Ptr descriptor = attributeSet.descriptorPtr(); + auto newDescriptor = std::make_shared(*descriptor); + + for (size_t i = 0; i < oldNames.size(); i++) { + const Name& oldName = oldNames[i]; + if (descriptor->find(oldName) == AttributeSet::INVALID_POS) { + OPENVDB_THROW(KeyError, "Cannot find requested attribute - " << oldName << "."); + } + + const Name& newName = newNames[i]; + if (descriptor->find(newName) != AttributeSet::INVALID_POS) { + OPENVDB_THROW(KeyError, + "Cannot rename attribute as new name already exists - " << newName << "."); + } + + const AttributeArray* array = attributeSet.getConst(oldName); + assert(array); + + if (isGroup(*array)) { + OPENVDB_THROW(KeyError, "Cannot rename group attribute - " << oldName << "."); + } + + newDescriptor->rename(oldName, newName); + } + + for (; iter; ++iter) { + iter->renameAttributes(*descriptor, newDescriptor); + } +} + + +template +inline void renameAttribute(PointDataTreeT& tree, + const Name& oldName, + const Name& newName) +{ + renameAttributes(tree, {oldName}, {newName}); +} + + +//////////////////////////////////////// + + +template +inline void compactAttributes(PointDataTreeT& tree) +{ + auto iter = tree.beginLeaf(); + if (!iter) return; + + tree::LeafManager leafManager(tree); + leafManager.foreach( + [&](typename PointDataTreeT::LeafNodeType& leaf, size_t /*idx*/) { + leaf.compactAttributes(); + }, /*threaded=*/ true + ); +} + + +//////////////////////////////////////// + + +} // namespace points +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_POINTS_POINT_ATTRIBUTE_IMPL_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/impl/PointConversionImpl.h b/openvdb/openvdb/points/impl/PointConversionImpl.h new file mode 100644 index 0000000000..f812c03257 --- /dev/null +++ b/openvdb/openvdb/points/impl/PointConversionImpl.h @@ -0,0 +1,849 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 + +/// @author Dan Bailey, Nick Avramoussis +/// +/// @file PointConversionImpl.h +/// + +#ifndef OPENVDB_POINTS_POINT_CONVERSION_IMPL_HAS_BEEN_INCLUDED +#define OPENVDB_POINTS_POINT_CONVERSION_IMPL_HAS_BEEN_INCLUDED + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace points { + +/// @cond OPENVDB_DOCS_INTERNAL + +namespace point_conversion_internal { + + +// ConversionTraits to create the relevant Attribute Handles from a LeafNode +template struct ConversionTraits +{ + using Handle = AttributeHandle; + using WriteHandle = AttributeWriteHandle; + static T zero() { return zeroVal(); } + template + static std::unique_ptr handleFromLeaf(const LeafT& leaf, const Index index) { + const AttributeArray& array = leaf.constAttributeArray(index); + return std::make_unique(array); + } + template + static std::unique_ptr writeHandleFromLeaf(LeafT& leaf, const Index index) { + AttributeArray& array = leaf.attributeArray(index); + return std::make_unique(array); + } +}; // ConversionTraits +template <> struct ConversionTraits +{ + using Handle = StringAttributeHandle; + using WriteHandle = StringAttributeWriteHandle; + static openvdb::Name zero() { return ""; } + template + static std::unique_ptr handleFromLeaf(const LeafT& leaf, const Index index) { + const AttributeArray& array = leaf.constAttributeArray(index); + const AttributeSet::Descriptor& descriptor = leaf.attributeSet().descriptor(); + return std::make_unique(array, descriptor.getMetadata()); + } + template + static std::unique_ptr writeHandleFromLeaf(LeafT& leaf, const Index index) { + AttributeArray& array = leaf.attributeArray(index); + const AttributeSet::Descriptor& descriptor = leaf.attributeSet().descriptor(); + return std::make_unique(array, descriptor.getMetadata()); + } +}; // ConversionTraits + +template< typename PointDataTreeType, + typename PointIndexTreeType, + typename AttributeListType> +struct PopulateAttributeOp { + + using LeafManagerT = typename tree::LeafManager; + using LeafRangeT = typename LeafManagerT::LeafRange; + using PointIndexLeafNode = typename PointIndexTreeType::LeafNodeType; + using IndexArray = typename PointIndexLeafNode::IndexArray; + using ValueType = typename AttributeListType::value_type; + using HandleT = typename ConversionTraits::WriteHandle; + + PopulateAttributeOp(const PointIndexTreeType& pointIndexTree, + const AttributeListType& data, + const size_t index, + const Index stride = 1) + : mPointIndexTree(pointIndexTree) + , mData(data) + , mIndex(index) + , mStride(stride) { } + + void operator()(const typename LeafManagerT::LeafRange& range) const { + + for (auto leaf = range.begin(); leaf; ++leaf) { + + // obtain the PointIndexLeafNode (using the origin of the current leaf) + + const PointIndexLeafNode* pointIndexLeaf = + mPointIndexTree.probeConstLeaf(leaf->origin()); + + if (!pointIndexLeaf) continue; + + auto attributeWriteHandle = + ConversionTraits::writeHandleFromLeaf(*leaf, static_cast(mIndex)); + + Index64 index = 0; + + const IndexArray& indices = pointIndexLeaf->indices(); + + for (const Index64 leafIndex: indices) + { + ValueType value; + for (Index i = 0; i < mStride; i++) { + mData.get(value, leafIndex, i); + attributeWriteHandle->set(static_cast(index), i, value); + } + index++; + } + + // attempt to compact the array + + attributeWriteHandle->compact(); + } + } + + ////////// + + const PointIndexTreeType& mPointIndexTree; + const AttributeListType& mData; + const size_t mIndex; + const Index mStride; +}; + +template +struct ConvertPointDataGridPositionOp { + + using LeafNode = typename PointDataTreeType::LeafNodeType; + using ValueType = typename Attribute::ValueType; + using HandleT = typename Attribute::Handle; + using SourceHandleT = AttributeHandle; + using LeafManagerT = typename tree::LeafManager; + using LeafRangeT = typename LeafManagerT::LeafRange; + + ConvertPointDataGridPositionOp( Attribute& attribute, + const std::vector& pointOffsets, + const Index64 startOffset, + const math::Transform& transform, + const size_t index, + const FilterT& filter, + const bool inCoreOnly) + : mAttribute(attribute) + , mPointOffsets(pointOffsets) + , mStartOffset(startOffset) + , mTransform(transform) + , mIndex(index) + , mFilter(filter) + , mInCoreOnly(inCoreOnly) + { + // only accept Vec3f as ValueType + static_assert(VecTraits::Size == 3 && + std::is_floating_point::value, + "ValueType is not Vec3f"); + } + + template + void convert(IterT& iter, HandleT& targetHandle, + SourceHandleT& sourceHandle, Index64& offset) const + { + for (; iter; ++iter) { + const Vec3d xyz = iter.getCoord().asVec3d(); + const Vec3d pos = sourceHandle.get(*iter); + targetHandle.set(static_cast(offset++), /*stride=*/0, + mTransform.indexToWorld(pos + xyz)); + } + } + + void operator()(const LeafRangeT& range) const + { + HandleT pHandle(mAttribute); + + for (auto leaf = range.begin(); leaf; ++leaf) { + + assert(leaf.pos() < mPointOffsets.size()); + + if (mInCoreOnly && leaf->buffer().isOutOfCore()) continue; + + Index64 offset = mStartOffset; + + if (leaf.pos() > 0) offset += mPointOffsets[leaf.pos() - 1]; + + auto handle = SourceHandleT::create(leaf->constAttributeArray(mIndex)); + + if (mFilter.state() == index::ALL) { + auto iter = leaf->beginIndexOn(); + convert(iter, pHandle, *handle, offset); + } + else { + auto iter = leaf->beginIndexOn(mFilter); + convert(iter, pHandle, *handle, offset); + } + } + } + + ////////// + + Attribute& mAttribute; + const std::vector& mPointOffsets; + const Index64 mStartOffset; + const math::Transform& mTransform; + const size_t mIndex; + const FilterT& mFilter; + const bool mInCoreOnly; +}; // ConvertPointDataGridPositionOp + + +template +struct ConvertPointDataGridAttributeOp { + + using LeafNode = typename PointDataTreeType::LeafNodeType; + using ValueType = typename Attribute::ValueType; + using HandleT = typename Attribute::Handle; + using SourceHandleT = typename ConversionTraits::Handle; + using LeafManagerT = typename tree::LeafManager; + using LeafRangeT = typename LeafManagerT::LeafRange; + + ConvertPointDataGridAttributeOp(Attribute& attribute, + const std::vector& pointOffsets, + const Index64 startOffset, + const size_t index, + const Index stride, + const FilterT& filter, + const bool inCoreOnly) + : mAttribute(attribute) + , mPointOffsets(pointOffsets) + , mStartOffset(startOffset) + , mIndex(index) + , mStride(stride) + , mFilter(filter) + , mInCoreOnly(inCoreOnly) { } + + template + void convert(IterT& iter, HandleT& targetHandle, + SourceHandleT& sourceHandle, Index64& offset) const + { + if (sourceHandle.isUniform()) { + const ValueType uniformValue(sourceHandle.get(0)); + for (; iter; ++iter) { + for (Index i = 0; i < mStride; i++) { + targetHandle.set(static_cast(offset), i, uniformValue); + } + offset++; + } + } + else { + for (; iter; ++iter) { + for (Index i = 0; i < mStride; i++) { + targetHandle.set(static_cast(offset), i, + sourceHandle.get(*iter, /*stride=*/i)); + } + offset++; + } + } + } + + void operator()(const LeafRangeT& range) const + { + HandleT pHandle(mAttribute); + + for (auto leaf = range.begin(); leaf; ++leaf) { + + assert(leaf.pos() < mPointOffsets.size()); + + if (mInCoreOnly && leaf->buffer().isOutOfCore()) continue; + + Index64 offset = mStartOffset; + + if (leaf.pos() > 0) offset += mPointOffsets[leaf.pos() - 1]; + + auto handle = ConversionTraits::handleFromLeaf( + *leaf, static_cast(mIndex)); + + if (mFilter.state() == index::ALL) { + auto iter = leaf->beginIndexOn(); + convert(iter, pHandle, *handle, offset); + } else { + auto iter = leaf->beginIndexOn(mFilter); + convert(iter, pHandle, *handle, offset); + } + } + } + + ////////// + + Attribute& mAttribute; + const std::vector& mPointOffsets; + const Index64 mStartOffset; + const size_t mIndex; + const Index mStride; + const FilterT& mFilter; + const bool mInCoreOnly; +}; // ConvertPointDataGridAttributeOp + +template +struct ConvertPointDataGridGroupOp { + + using LeafNode = typename PointDataTreeType::LeafNodeType; + using GroupIndex = AttributeSet::Descriptor::GroupIndex; + using LeafManagerT = typename tree::LeafManager; + using LeafRangeT = typename LeafManagerT::LeafRange; + + ConvertPointDataGridGroupOp(Group& group, + const std::vector& pointOffsets, + const Index64 startOffset, + const AttributeSet::Descriptor::GroupIndex index, + const FilterT& filter, + const bool inCoreOnly) + : mGroup(group) + , mPointOffsets(pointOffsets) + , mStartOffset(startOffset) + , mIndex(index) + , mFilter(filter) + , mInCoreOnly(inCoreOnly) { } + + template + void convert(IterT& iter, const GroupAttributeArray& groupArray, Index64& offset) const + { + const auto bitmask = static_cast(1 << mIndex.second); + + if (groupArray.isUniform()) { + if (groupArray.get(0) & bitmask) { + for (; iter; ++iter) { + mGroup.setOffsetOn(static_cast(offset)); + offset++; + } + } + } + else { + for (; iter; ++iter) { + if (groupArray.get(*iter) & bitmask) { + mGroup.setOffsetOn(static_cast(offset)); + } + offset++; + } + } + } + + void operator()(const LeafRangeT& range) const + { + for (auto leaf = range.begin(); leaf; ++leaf) { + + assert(leaf.pos() < mPointOffsets.size()); + + if (mInCoreOnly && leaf->buffer().isOutOfCore()) continue; + + Index64 offset = mStartOffset; + + if (leaf.pos() > 0) offset += mPointOffsets[leaf.pos() - 1]; + + const AttributeArray& array = leaf->constAttributeArray(mIndex.first); + assert(isGroup(array)); + const GroupAttributeArray& groupArray = GroupAttributeArray::cast(array); + + if (mFilter.state() == index::ALL) { + auto iter = leaf->beginIndexOn(); + convert(iter, groupArray, offset); + } + else { + auto iter = leaf->beginIndexOn(mFilter); + convert(iter, groupArray, offset); + } + } + } + + ////////// + + Group& mGroup; + const std::vector& mPointOffsets; + const Index64 mStartOffset; + const GroupIndex mIndex; + const FilterT& mFilter; + const bool mInCoreOnly; +}; // ConvertPointDataGridGroupOp + +template +struct CalculatePositionBounds +{ + CalculatePositionBounds(const PositionArrayT& positions, + const math::Mat4d& inverse) + : mPositions(positions) + , mInverseMat(inverse) + , mMin(std::numeric_limits::max()) + , mMax(-std::numeric_limits::max()) {} + + CalculatePositionBounds(const CalculatePositionBounds& other, tbb::split) + : mPositions(other.mPositions) + , mInverseMat(other.mInverseMat) + , mMin(std::numeric_limits::max()) + , mMax(-std::numeric_limits::max()) {} + + void operator()(const tbb::blocked_range& range) { + VecT pos; + for (size_t n = range.begin(), N = range.end(); n != N; ++n) { + mPositions.getPos(n, pos); + pos = mInverseMat.transform(pos); + mMin = math::minComponent(mMin, pos); + mMax = math::maxComponent(mMax, pos); + } + } + + void join(const CalculatePositionBounds& other) { + mMin = math::minComponent(mMin, other.mMin); + mMax = math::maxComponent(mMax, other.mMax); + } + + BBoxd getBoundingBox() const { + return BBoxd(mMin, mMax); + } + +private: + const PositionArrayT& mPositions; + const math::Mat4d& mInverseMat; + VecT mMin, mMax; +}; + +} // namespace point_conversion_internal + +/// @endcond + +//////////////////////////////////////// + + +template +inline typename PointDataGridT::Ptr +createPointDataGrid(const PointIndexGridT& pointIndexGrid, + const PositionArrayT& positions, + const math::Transform& xform, + const Metadata* positionDefaultValue) +{ + using PointDataTreeT = typename PointDataGridT::TreeType; + using LeafT = typename PointDataTreeT::LeafNodeType; + using PointIndexLeafT = typename PointIndexGridT::TreeType::LeafNodeType; + using PointIndexT = typename PointIndexLeafT::ValueType; + using LeafManagerT = typename tree::LeafManager; + using PositionAttributeT = TypedAttributeArray; + + const NamePair positionType = PositionAttributeT::attributeType(); + + // construct the Tree using a topology copy of the PointIndexGrid + + const auto& pointIndexTree = pointIndexGrid.tree(); + typename PointDataTreeT::Ptr treePtr(new PointDataTreeT(pointIndexTree)); + + // create attribute descriptor from position type + + auto descriptor = AttributeSet::Descriptor::create(positionType); + + // add default value for position if provided + + if (positionDefaultValue) descriptor->setDefaultValue("P", *positionDefaultValue); + + // retrieve position index + + const size_t positionIndex = descriptor->find("P"); + assert(positionIndex != AttributeSet::INVALID_POS); + + // acquire registry lock to avoid locking when appending attributes in parallel + + AttributeArray::ScopedRegistryLock lock; + + // populate position attribute + + LeafManagerT leafManager(*treePtr); + leafManager.foreach( + [&](LeafT& leaf, size_t /*idx*/) { + + // obtain the PointIndexLeafNode (using the origin of the current leaf) + + const auto* pointIndexLeaf = pointIndexTree.probeConstLeaf(leaf.origin()); + assert(pointIndexLeaf); + + // initialise the attribute storage + + Index pointCount(static_cast(pointIndexLeaf->indices().size())); + leaf.initializeAttributes(descriptor, pointCount, &lock); + + // create write handle for position + + auto attributeWriteHandle = AttributeWriteHandle::create( + leaf.attributeArray(positionIndex)); + + Index index = 0; + + const PointIndexT + *begin = static_cast(nullptr), + *end = static_cast(nullptr); + + // iterator over every active voxel in the point index leaf + + for (auto iter = pointIndexLeaf->cbeginValueOn(); iter; ++iter) { + + // find the voxel center + + const Coord& ijk = iter.getCoord(); + const Vec3d& positionCellCenter(ijk.asVec3d()); + + // obtain pointers for this voxel from begin to end in the indices array + + pointIndexLeaf->getIndices(ijk, begin, end); + + while (begin < end) { + + typename PositionArrayT::value_type positionWorldSpace; + positions.getPos(*begin, positionWorldSpace); + + // compute the index-space position and then subtract the voxel center + + const Vec3d positionIndexSpace = xform.worldToIndex(positionWorldSpace); + const Vec3f positionVoxelSpace(positionIndexSpace - positionCellCenter); + + attributeWriteHandle->set(index++, positionVoxelSpace); + + ++begin; + } + } + }, + /*threaded=*/true); + + auto grid = PointDataGridT::create(treePtr); + grid->setTransform(xform.copy()); + return grid; +} + + +//////////////////////////////////////// + + +template +inline typename PointDataGridT::Ptr +createPointDataGrid(const std::vector& positions, + const math::Transform& xform, + const Metadata* positionDefaultValue) +{ + const PointAttributeVector pointList(positions); + + tools::PointIndexGrid::Ptr pointIndexGrid = + tools::createPointIndexGrid(pointList, xform); + return createPointDataGrid( + *pointIndexGrid, pointList, xform, positionDefaultValue); +} + + +//////////////////////////////////////// + + +template +inline void +populateAttribute(PointDataTreeT& tree, const PointIndexTreeT& pointIndexTree, + const openvdb::Name& attributeName, const PointArrayT& data, const Index stride, + const bool insertMetadata) +{ + using point_conversion_internal::PopulateAttributeOp; + using ValueType = typename PointArrayT::value_type; + + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + const size_t index = iter->attributeSet().find(attributeName); + + if (index == AttributeSet::INVALID_POS) { + OPENVDB_THROW(KeyError, "Attribute not found to populate - " << attributeName << "."); + } + + if (insertMetadata) { + point_attribute_internal::MetadataStorage::add(tree, data); + } + + // populate attribute + + typename tree::LeafManager leafManager(tree); + + PopulateAttributeOp populate(pointIndexTree, data, index, stride); + tbb::parallel_for(leafManager.leafRange(), populate); +} + + +//////////////////////////////////////// + + +template +inline void +convertPointDataGridPosition( PositionAttribute& positionAttribute, + const PointDataGridT& grid, + const std::vector& pointOffsets, + const Index64 startOffset, + const FilterT& filter, + const bool inCoreOnly) +{ + using TreeType = typename PointDataGridT::TreeType; + using LeafManagerT = typename tree::LeafManager; + + using point_conversion_internal::ConvertPointDataGridPositionOp; + + const TreeType& tree = grid.tree(); + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + const size_t positionIndex = iter->attributeSet().find("P"); + + positionAttribute.expand(); + LeafManagerT leafManager(tree); + ConvertPointDataGridPositionOp convert( + positionAttribute, pointOffsets, startOffset, grid.transform(), positionIndex, + filter, inCoreOnly); + tbb::parallel_for(leafManager.leafRange(), convert); + positionAttribute.compact(); +} + + +//////////////////////////////////////// + + +template +inline void +convertPointDataGridAttribute( TypedAttribute& attribute, + const PointDataTreeT& tree, + const std::vector& pointOffsets, + const Index64 startOffset, + const unsigned arrayIndex, + const Index stride, + const FilterT& filter, + const bool inCoreOnly) +{ + using LeafManagerT = typename tree::LeafManager; + + using point_conversion_internal::ConvertPointDataGridAttributeOp; + + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + attribute.expand(); + LeafManagerT leafManager(tree); + ConvertPointDataGridAttributeOp convert( + attribute, pointOffsets, startOffset, arrayIndex, stride, + filter, inCoreOnly); + tbb::parallel_for(leafManager.leafRange(), convert); + attribute.compact(); +} + + +//////////////////////////////////////// + + +template +inline void +convertPointDataGridGroup( Group& group, + const PointDataTreeT& tree, + const std::vector& pointOffsets, + const Index64 startOffset, + const AttributeSet::Descriptor::GroupIndex index, + const FilterT& filter, + const bool inCoreOnly) +{ + using LeafManagerT= typename tree::LeafManager; + + using point_conversion_internal::ConvertPointDataGridGroupOp; + + auto iter = tree.cbeginLeaf(); + if (!iter) return; + + LeafManagerT leafManager(tree); + ConvertPointDataGridGroupOp convert( + group, pointOffsets, startOffset, index, + filter, inCoreOnly); + tbb::parallel_for(leafManager.leafRange(), convert); + + // must call this after modifying point groups in parallel + + group.finalize(); +} + +template +inline float +computeVoxelSize( const PositionWrapper& positions, + const uint32_t pointsPerVoxel, + const math::Mat4d transform, + const Index decimalPlaces, + InterrupterT* const interrupter) +{ + using namespace point_conversion_internal; + + struct Local { + + static bool voxelSizeFromVolume(const double volume, + const size_t estimatedVoxelCount, + float& voxelSize) + { + // dictated by the math::ScaleMap limit + static const double minimumVoxelVolume(3e-15); + static const double maximumVoxelVolume(std::numeric_limits::max()); + + double voxelVolume = volume / static_cast(estimatedVoxelCount); + bool valid = true; + + if (voxelVolume < minimumVoxelVolume) { + voxelVolume = minimumVoxelVolume; + valid = false; + } + else if (voxelVolume > maximumVoxelVolume) { + voxelVolume = maximumVoxelVolume; + valid = false; + } + + voxelSize = static_cast(math::Pow(voxelVolume, 1.0/3.0)); + return valid; + } + + static float truncate(const float voxelSize, Index decPlaces) + { + float truncatedVoxelSize = voxelSize; + + // attempt to truncate from decPlaces -> 11 + for (int i = decPlaces; i < 11; i++) { + truncatedVoxelSize = static_cast(math::Truncate(double(voxelSize), i)); + if (truncatedVoxelSize != 0.0f) break; + } + + return truncatedVoxelSize; + } + }; + + if (pointsPerVoxel == 0) OPENVDB_THROW(ValueError, "Points per voxel cannot be zero."); + + // constructed with the default voxel size as specified by openvdb interface values + + float voxelSize(0.1f); + + const size_t numPoints = positions.size(); + + // return the default voxel size if we have zero or only 1 point + + if (numPoints <= 1) return voxelSize; + + size_t targetVoxelCount(numPoints / size_t(pointsPerVoxel)); + if (targetVoxelCount == 0) targetVoxelCount++; + + // calculate the world space, transform-oriented bounding box + + math::Mat4d inverseTransform = transform.inverse(); + inverseTransform = math::unit(inverseTransform); + + tbb::blocked_range range(0, numPoints); + CalculatePositionBounds calculateBounds(positions, inverseTransform); + tbb::parallel_reduce(range, calculateBounds); + + BBoxd bbox = calculateBounds.getBoundingBox(); + + // return default size if points are coincident + + if (bbox.min() == bbox.max()) return voxelSize; + + double volume = bbox.volume(); + + // handle points that are collinear or coplanar by expanding the volume + + if (math::isApproxZero(volume)) { + Vec3d extents = bbox.extents().sorted().reversed(); + if (math::isApproxZero(extents[1])) { + // colinear (maxExtent^3) + volume = extents[0]*extents[0]*extents[0]; + } + else { + // coplanar (maxExtent*nextMaxExtent^2) + volume = extents[0]*extents[1]*extents[1]; + } + } + + double previousVolume = volume; + + if (!Local::voxelSizeFromVolume(volume, targetVoxelCount, voxelSize)) { + OPENVDB_LOG_DEBUG("Out of range, clamping voxel size."); + return voxelSize; + } + + size_t previousVoxelCount(0); + size_t voxelCount(1); + + if (interrupter) interrupter->start("Computing voxel size"); + + while (voxelCount > previousVoxelCount) + { + math::Transform::Ptr newTransform; + + if (!math::isIdentity(transform)) + { + // if using a custom transform, pre-scale by coefficients + // which define the new voxel size + + math::Mat4d matrix(transform); + matrix.preScale(Vec3d(voxelSize) / math::getScale(matrix)); + newTransform = math::Transform::createLinearTransform(matrix); + } + else + { + newTransform = math::Transform::createLinearTransform(voxelSize); + } + + // create a mask grid of the points from the calculated voxel size + // this is the same function call as tools::createPointMask() which has + // been duplicated to provide an interrupter + + MaskGrid::Ptr mask = createGrid(false); + mask->setTransform(newTransform); + tools::PointsToMask pointMaskOp(*mask, interrupter); + pointMaskOp.template addPoints(positions); + + if (interrupter && util::wasInterrupted(interrupter)) break; + + previousVoxelCount = voxelCount; + voxelCount = mask->activeVoxelCount(); + volume = math::Pow3(voxelSize) * static_cast(voxelCount); + + // stop if no change in the volume or the volume has increased + + if (volume >= previousVolume) break; + previousVolume = volume; + + const float previousVoxelSize = voxelSize; + + // compute the new voxel size and if invalid return the previous value + + if (!Local::voxelSizeFromVolume(volume, targetVoxelCount, voxelSize)) { + voxelSize = previousVoxelSize; + break; + } + + // halt convergence if the voxel size has decreased by less + // than 10% in this iteration + + if (voxelSize / previousVoxelSize > 0.9f) break; + } + + if (interrupter) interrupter->end(); + + // truncate the voxel size for readability and return the value + + return Local::truncate(voxelSize, decimalPlaces); +} + + +//////////////////////////////////////// + + +} // namespace points +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_POINTS_POINT_CONVERSION_IMPL_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/impl/PointCountImpl.h b/openvdb/openvdb/points/impl/PointCountImpl.h new file mode 100644 index 0000000000..e90c25812e --- /dev/null +++ b/openvdb/openvdb/points/impl/PointCountImpl.h @@ -0,0 +1,142 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 + +/// @author Dan Bailey +/// +/// @file PointCountImpl.h +/// + +#ifndef OPENVDB_POINTS_POINT_COUNT_IMPL_HAS_BEEN_INCLUDED +#define OPENVDB_POINTS_POINT_COUNT_IMPL_HAS_BEEN_INCLUDED + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace points { + +template +Index64 pointCount(const PointDataTreeT& tree, + const FilterT& filter, + const bool inCoreOnly, + const bool threaded) +{ + using LeafManagerT = tree::LeafManager; + using LeafRangeT = typename LeafManagerT::LeafRange; + + auto countLambda = + [&filter, &inCoreOnly] (const LeafRangeT& range, Index64 sum) -> Index64 { + for (const auto& leaf : range) { + if (inCoreOnly && leaf.buffer().isOutOfCore()) continue; + auto state = filter.state(leaf); + if (state == index::ALL) { + sum += leaf.pointCount(); + } else if (state != index::NONE) { + sum += iterCount(leaf.beginIndexAll(filter)); + } + } + return sum; + }; + + LeafManagerT leafManager(tree); + if (threaded) { + return tbb::parallel_reduce(leafManager.leafRange(), Index64(0), countLambda, + [] (Index64 n, Index64 m) -> Index64 { return n + m; }); + } + else { + return countLambda(leafManager.leafRange(), Index64(0)); + } +} + + +template +Index64 pointOffsets( std::vector& pointOffsets, + const PointDataTreeT& tree, + const FilterT& filter, + const bool inCoreOnly, + const bool threaded) +{ + using LeafT = typename PointDataTreeT::LeafNodeType; + using LeafManagerT = typename tree::LeafManager; + + // allocate and zero values in point offsets array + + pointOffsets.assign(tree.leafCount(), Index64(0)); + if (pointOffsets.empty()) return 0; + + // compute total points per-leaf + + LeafManagerT leafManager(tree); + leafManager.foreach( + [&pointOffsets, &filter, &inCoreOnly](const LeafT& leaf, size_t pos) { + if (inCoreOnly && leaf.buffer().isOutOfCore()) return; + auto state = filter.state(leaf); + if (state == index::ALL) { + pointOffsets[pos] = leaf.pointCount(); + } else if (state != index::NONE) { + pointOffsets[pos] = iterCount(leaf.beginIndexAll(filter)); + } + }, + threaded); + + // turn per-leaf totals into cumulative leaf totals + + Index64 pointOffset(pointOffsets[0]); + for (size_t n = 1; n < pointOffsets.size(); n++) { + pointOffset += pointOffsets[n]; + pointOffsets[n] = pointOffset; + } + + return pointOffset; +} + + +template +typename GridT::Ptr +pointCountGrid( const PointDataGridT& points, + const FilterT& filter) +{ + static_assert(std::is_integral::value || + std::is_floating_point::value, + "openvdb::points::pointCountGrid must return an integer or floating-point scalar grid"); + + using PointDataTreeT = typename PointDataGridT::TreeType; + using TreeT = typename GridT::TreeType; + + typename TreeT::Ptr tree = + point_mask_internal::convertPointsToScalar + (points.tree(), filter); + + typename GridT::Ptr grid(new GridT(tree)); + grid->setTransform(points.transform().copy()); + return grid; +} + + +template +typename GridT::Ptr +pointCountGrid( const PointDataGridT& points, + const openvdb::math::Transform& transform, + const FilterT& filter) +{ + static_assert( std::is_integral::value || + std::is_floating_point::value, + "openvdb::points::pointCountGrid must return an integer or floating-point scalar grid"); + + // This is safe because the PointDataGrid can only be modified by the deformer + using AdapterT = TreeAdapter; + auto& nonConstPoints = const_cast(points); + + NullDeformer deformer; + return point_mask_internal::convertPointsToScalar( + nonConstPoints, transform, filter, deformer); +} + + +//////////////////////////////////////// + + +} // namespace points +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_POINTS_POINT_COUNT_IMPL_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/impl/PointDeleteImpl.h b/openvdb/openvdb/points/impl/PointDeleteImpl.h new file mode 100644 index 0000000000..01f98c3dad --- /dev/null +++ b/openvdb/openvdb/points/impl/PointDeleteImpl.h @@ -0,0 +1,209 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 + +/// @author Nick Avramoussis, Francisco Gochez, Dan Bailey +/// +/// @file PointDeleteImpl.h +/// + +#ifndef OPENVDB_POINTS_POINT_DELETE_IMPL_HAS_BEEN_INCLUDED +#define OPENVDB_POINTS_POINT_DELETE_IMPL_HAS_BEEN_INCLUDED + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace points { + +/// @cond OPENVDB_DOCS_INTERNAL + +namespace point_delete_internal { + +struct VectorWrapper +{ + using T = std::vector>; + + VectorWrapper(const T& _data) : data(_data) { } + operator bool() const { return index < data.size(); } + VectorWrapper& operator++() { index++; return *this; } + Index sourceIndex() const { assert(*this); return data[index].first; } + Index targetIndex() const { assert(*this); return data[index].second; } + +private: + const T& data; + T::size_type index = 0; +}; // struct VectorWrapper + + +template +struct DeleteByFilterOp +{ + using LeafManagerT = tree::LeafManager; + using LeafRangeT = typename LeafManagerT::LeafRange; + using LeafNodeT = typename PointDataTreeT::LeafNodeType; + using ValueType = typename LeafNodeT::ValueType; + + DeleteByFilterOp(const FilterT& filter, + const AttributeArray::ScopedRegistryLock* lock) + : mFilter(filter) + , mLock(lock) { } + + void operator()(const LeafRangeT& range) const + { + for (auto leaf = range.begin(); leaf != range.end(); ++leaf) { + + const size_t newSize = + iterCount(leaf->template beginIndexAll(mFilter)); + + // if all points are being deleted, clear the leaf attributes + if (newSize == 0) { + leaf->clearAttributes(/*updateValueMask=*/true, mLock); + continue; + } + + // early exit if no points are being deleted + + const size_t currentSize = leaf->getLastValue(); + if (newSize == currentSize) continue; + + const AttributeSet& existingAttributeSet = leaf->attributeSet(); + AttributeSet* newAttributeSet = new AttributeSet( + existingAttributeSet, static_cast(newSize), mLock); + const size_t attributeSetSize = existingAttributeSet.size(); + + // cache the attribute arrays for efficiency + + std::vector newAttributeArrays; + std::vector existingAttributeArrays; + + for (size_t i = 0; i < attributeSetSize; i++) { + AttributeArray* newArray = newAttributeSet->get(i); + const AttributeArray* existingArray = existingAttributeSet.getConst(i); + + if (!newArray->hasConstantStride() || !existingArray->hasConstantStride()) { + OPENVDB_THROW(openvdb::NotImplementedError, + "Transfer of attribute values for dynamic arrays not currently supported."); + } + + if (newArray->stride() != existingArray->stride()) { + OPENVDB_THROW(openvdb::LookupError, + "Cannot transfer attribute values with mis-matching strides."); + } + + newAttributeArrays.push_back(newArray); + existingAttributeArrays.push_back(existingArray); + } + + Index attributeIndex = 0; + std::vector endOffsets; + + endOffsets.reserve(LeafNodeT::NUM_VALUES); + + // now construct new attribute arrays which exclude data from deleted points + + std::vector> indexMapping; + indexMapping.reserve(newSize); + + for (auto voxel = leaf->cbeginValueAll(); voxel; ++voxel) { + for (auto iter = leaf->beginIndexVoxel(voxel.getCoord(), mFilter); + iter; ++iter) { + indexMapping.emplace_back(*iter, attributeIndex++); + } + endOffsets.push_back(static_cast(attributeIndex)); + } + + for (size_t i = 0; i < attributeSetSize; i++) { + VectorWrapper indexMappingWrapper(indexMapping); + newAttributeArrays[i]->copyValues(*(existingAttributeArrays[i]), indexMappingWrapper); + } + + leaf->replaceAttributeSet(newAttributeSet); + leaf->setOffsets(endOffsets); + } + } + +private: + const FilterT& mFilter; + const AttributeArray::ScopedRegistryLock* mLock; +}; // struct DeleteByFilterOp + +} // namespace point_delete_internal + +/// @endcond + +//////////////////////////////////////// + +template +inline void deleteFromGroups(PointDataTreeT& pointTree, + const std::vector& groups, + bool invert, + bool drop) +{ + const typename PointDataTreeT::LeafCIter leafIter = pointTree.cbeginLeaf(); + + if (!leafIter) return; + + const openvdb::points::AttributeSet& attributeSet = leafIter->attributeSet(); + const AttributeSet::Descriptor& descriptor = attributeSet.descriptor(); + std::vector availableGroups; + + // determine which of the requested groups exist, and early exit + // if none are present in the tree + + for (const auto& groupName : groups) { + if (descriptor.hasGroup(groupName)) { + availableGroups.push_back(groupName); + } + } + + if (availableGroups.empty()) return; + + std::vector empty; + std::unique_ptr filter; + if (invert) { + filter.reset(new MultiGroupFilter(groups, empty, leafIter->attributeSet())); + } + else { + filter.reset(new MultiGroupFilter(empty, groups, leafIter->attributeSet())); + } + + { // acquire registry lock to avoid locking when appending attributes in parallel + + AttributeArray::ScopedRegistryLock lock; + + tree::LeafManager leafManager(pointTree); + point_delete_internal::DeleteByFilterOp deleteOp( + *filter, &lock); + tbb::parallel_for(leafManager.leafRange(), deleteOp); + } + + // remove empty leaf nodes + + tools::pruneInactive(pointTree); + + // drop the now-empty groups if requested (unless invert = true) + + if (drop && !invert) { + dropGroups(pointTree, availableGroups); + } +} + +template +inline void deleteFromGroup(PointDataTreeT& pointTree, + const std::string& group, + bool invert, + bool drop) +{ + std::vector groups(1, group); + + deleteFromGroups(pointTree, groups, invert, drop); +} + + +//////////////////////////////////////// + + +} // namespace points +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_POINTS_POINT_DELETE_IMPL_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/impl/PointGroupImpl.h b/openvdb/openvdb/points/impl/PointGroupImpl.h new file mode 100644 index 0000000000..885024e7da --- /dev/null +++ b/openvdb/openvdb/points/impl/PointGroupImpl.h @@ -0,0 +1,614 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 + +/// @author Dan Bailey +/// +/// @file PointGroupImpl.h +/// + +#ifndef OPENVDB_POINTS_POINT_GROUP_IMPL_HAS_BEEN_INCLUDED +#define OPENVDB_POINTS_POINT_GROUP_IMPL_HAS_BEEN_INCLUDED + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace points { + +/// @cond OPENVDB_DOCS_INTERNAL + +namespace point_group_internal { + + +/// Copy a group attribute value from one group offset to another +template +struct CopyGroupOp { + + using LeafManagerT = typename tree::LeafManager; + using LeafRangeT = typename LeafManagerT::LeafRange; + using GroupIndex = AttributeSet::Descriptor::GroupIndex; + + CopyGroupOp(const GroupIndex& targetIndex, + const GroupIndex& sourceIndex) + : mTargetIndex(targetIndex) + , mSourceIndex(sourceIndex) { } + + void operator()(const typename LeafManagerT::LeafRange& range) const { + + for (auto leaf = range.begin(); leaf; ++leaf) { + + GroupHandle sourceGroup = leaf->groupHandle(mSourceIndex); + GroupWriteHandle targetGroup = leaf->groupWriteHandle(mTargetIndex); + + for (auto iter = leaf->beginIndexAll(); iter; ++iter) { + const bool groupOn = sourceGroup.get(*iter); + targetGroup.set(*iter, groupOn); + } + } + } + + ////////// + + const GroupIndex mTargetIndex; + const GroupIndex mSourceIndex; +}; + + +/// Set membership on or off for the specified group +template +struct SetGroupOp +{ + using LeafManagerT = typename tree::LeafManager; + using GroupIndex = AttributeSet::Descriptor::GroupIndex; + + SetGroupOp(const AttributeSet::Descriptor::GroupIndex& index) + : mIndex(index) { } + + void operator()(const typename LeafManagerT::LeafRange& range) const + { + for (auto leaf = range.begin(); leaf; ++leaf) { + + // obtain the group attribute array + + GroupWriteHandle group(leaf->groupWriteHandle(mIndex)); + + // set the group value + + group.collapse(Member); + } + } + + ////////// + + const GroupIndex& mIndex; +}; // struct SetGroupOp + + +template +struct SetGroupFromIndexOp +{ + using LeafManagerT = typename tree::LeafManager; + using LeafRangeT = typename LeafManagerT::LeafRange; + using PointIndexLeafNode = typename PointIndexTreeT::LeafNodeType; + using IndexArray = typename PointIndexLeafNode::IndexArray; + using GroupIndex = AttributeSet::Descriptor::GroupIndex; + using MembershipArray = std::vector; + + SetGroupFromIndexOp(const PointIndexTreeT& indexTree, + const MembershipArray& membership, + const GroupIndex& index) + : mIndexTree(indexTree) + , mMembership(membership) + , mIndex(index) { } + + void operator()(const typename LeafManagerT::LeafRange& range) const + { + for (auto leaf = range.begin(); leaf; ++leaf) { + + // obtain the PointIndexLeafNode (using the origin of the current leaf) + + const PointIndexLeafNode* pointIndexLeaf = mIndexTree.probeConstLeaf(leaf->origin()); + + if (!pointIndexLeaf) continue; + + // obtain the group attribute array + + GroupWriteHandle group(leaf->groupWriteHandle(mIndex)); + + // initialise the attribute storage + + Index64 index = 0; + + const IndexArray& indices = pointIndexLeaf->indices(); + + for (const Index64 i: indices) { + if (Remove) { + group.set(static_cast(index), mMembership[i]); + } else if (mMembership[i] == short(1)) { + group.set(static_cast(index), short(1)); + } + index++; + } + + // attempt to compact the array + + group.compact(); + } + } + + ////////// + + const PointIndexTreeT& mIndexTree; + const MembershipArray& mMembership; + const GroupIndex& mIndex; +}; // struct SetGroupFromIndexOp + + +template +struct SetGroupByFilterOp +{ + using LeafManagerT = typename tree::LeafManager; + using LeafRangeT = typename LeafManagerT::LeafRange; + using LeafNodeT = typename PointDataTreeT::LeafNodeType; + using GroupIndex = AttributeSet::Descriptor::GroupIndex; + + SetGroupByFilterOp( const GroupIndex& index, const FilterT& filter) + : mIndex(index) + , mFilter(filter) { } + + void operator()(const typename LeafManagerT::LeafRange& range) const + { + for (auto leaf = range.begin(); leaf; ++leaf) { + + // obtain the group attribute array + + GroupWriteHandle group(leaf->groupWriteHandle(mIndex)); + + auto iter = leaf->template beginIndex(mFilter); + + for (; iter; ++iter) { + group.set(*iter, true); + } + + // attempt to compact the array + + group.compact(); + } + } + + ////////// + + const GroupIndex& mIndex; + const FilterT& mFilter; // beginIndex takes a copy of mFilter +}; // struct SetGroupByFilterOp + + +//////////////////////////////////////// + + +} // namespace point_group_internal + +/// @endcond + +//////////////////////////////////////// + + +inline void deleteMissingPointGroups( std::vector& groups, + const AttributeSet::Descriptor& descriptor) +{ + for (auto it = groups.begin(); it != groups.end();) { + if (!descriptor.hasGroup(*it)) it = groups.erase(it); + else ++it; + } +} + + +//////////////////////////////////////// + + +template +inline void appendGroup(PointDataTreeT& tree, const Name& group) +{ + if (group.empty()) { + OPENVDB_THROW(KeyError, "Cannot use an empty group name as a key."); + } + + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + const AttributeSet& attributeSet = iter->attributeSet(); + auto descriptor = attributeSet.descriptorPtr(); + + // don't add if group already exists + + if (descriptor->hasGroup(group)) return; + + const bool hasUnusedGroup = descriptor->unusedGroups() > 0; + + // add a new group attribute if there are no unused groups + + if (!hasUnusedGroup) { + + // find a new internal group name + + const Name groupName = descriptor->uniqueName("__group"); + + descriptor = descriptor->duplicateAppend(groupName, GroupAttributeArray::attributeType()); + const size_t pos = descriptor->find(groupName); + + // insert new group attribute + + tree::LeafManager leafManager(tree); + leafManager.foreach( + [&](typename PointDataTreeT::LeafNodeType& leaf, size_t /*idx*/) { + auto expected = leaf.attributeSet().descriptorPtr(); + leaf.appendAttribute(*expected, descriptor, pos); + }, /*threaded=*/true + ); + } + else { + // make the descriptor unique before we modify the group map + + makeDescriptorUnique(tree); + descriptor = attributeSet.descriptorPtr(); + } + + // ensure that there are now available groups + + assert(descriptor->unusedGroups() > 0); + + // find next unused offset + + const size_t offset = descriptor->unusedGroupOffset(); + + // add the group mapping to the descriptor + + descriptor->setGroup(group, offset); + + // if there was an unused group then we did not need to append a new attribute, so + // we must manually clear membership in the new group as its bits may have been + // previously set + + if (hasUnusedGroup) setGroup(tree, group, false); +} + + +//////////////////////////////////////// + + +template +inline void appendGroups(PointDataTreeT& tree, + const std::vector& groups) +{ + // TODO: could be more efficient by appending multiple groups at once + // instead of one-by-one, however this is likely not that common a use case + + for (const Name& name : groups) { + appendGroup(tree, name); + } +} + + +//////////////////////////////////////// + + +template +inline void dropGroup(PointDataTreeT& tree, const Name& group, const bool compact) +{ + using Descriptor = AttributeSet::Descriptor; + + if (group.empty()) { + OPENVDB_THROW(KeyError, "Cannot use an empty group name as a key."); + } + + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + const AttributeSet& attributeSet = iter->attributeSet(); + + // make the descriptor unique before we modify the group map + + makeDescriptorUnique(tree); + Descriptor::Ptr descriptor = attributeSet.descriptorPtr(); + + // now drop the group + + descriptor->dropGroup(group); + + if (compact) { + compactGroups(tree); + } +} + + +//////////////////////////////////////// + + +template +inline void dropGroups( PointDataTreeT& tree, + const std::vector& groups) +{ + for (const Name& name : groups) { + dropGroup(tree, name, /*compact=*/false); + } + + // compaction done once for efficiency + + compactGroups(tree); +} + + +//////////////////////////////////////// + + +template +inline void dropGroups( PointDataTreeT& tree) +{ + using Descriptor = AttributeSet::Descriptor; + + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + const AttributeSet& attributeSet = iter->attributeSet(); + + // make the descriptor unique before we modify the group map + + makeDescriptorUnique(tree); + Descriptor::Ptr descriptor = attributeSet.descriptorPtr(); + + descriptor->clearGroups(); + + // find all indices for group attribute arrays + + std::vector indices = attributeSet.groupAttributeIndices(); + + // drop these attributes arrays + + dropAttributes(tree, indices); +} + + +//////////////////////////////////////// + + +template +inline void compactGroups(PointDataTreeT& tree) +{ + using Descriptor = AttributeSet::Descriptor; + using GroupIndex = Descriptor::GroupIndex; + using LeafManagerT = typename tree::template LeafManager; + + using point_group_internal::CopyGroupOp; + + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + const AttributeSet& attributeSet = iter->attributeSet(); + + // early exit if not possible to compact + + if (!attributeSet.descriptor().canCompactGroups()) return; + + // make the descriptor unique before we modify the group map + + makeDescriptorUnique(tree); + Descriptor::Ptr descriptor = attributeSet.descriptorPtr(); + + // generate a list of group offsets and move them (one-by-one) + // TODO: improve this algorithm to move multiple groups per array at once + // though this is likely not that common a use case + + Name sourceName; + size_t sourceOffset, targetOffset; + + while (descriptor->requiresGroupMove(sourceName, sourceOffset, targetOffset)) { + + const GroupIndex sourceIndex = attributeSet.groupIndex(sourceOffset); + const GroupIndex targetIndex = attributeSet.groupIndex(targetOffset); + + CopyGroupOp copy(targetIndex, sourceIndex); + LeafManagerT leafManager(tree); + tbb::parallel_for(leafManager.leafRange(), copy); + + descriptor->setGroup(sourceName, targetOffset); + } + + // drop unused attribute arrays + + const std::vector indices = attributeSet.groupAttributeIndices(); + + const size_t totalAttributesToDrop = descriptor->unusedGroups() / descriptor->groupBits(); + + assert(totalAttributesToDrop <= indices.size()); + + const std::vector indicesToDrop(indices.end() - totalAttributesToDrop, + indices.end()); + + dropAttributes(tree, indicesToDrop); +} + + +//////////////////////////////////////// + + +template +inline void setGroup( PointDataTreeT& tree, + const PointIndexTreeT& indexTree, + const std::vector& membership, + const Name& group, + const bool remove) +{ + using Descriptor = AttributeSet::Descriptor; + using LeafManagerT = typename tree::LeafManager; + using point_group_internal::SetGroupFromIndexOp; + + auto iter = tree.cbeginLeaf(); + if (!iter) return; + + const AttributeSet& attributeSet = iter->attributeSet(); + const Descriptor& descriptor = attributeSet.descriptor(); + + if (!descriptor.hasGroup(group)) { + OPENVDB_THROW(LookupError, "Group must exist on Tree before defining membership."); + } + + { + // Check that that the largest index in the PointIndexTree is smaller than the size + // of the membership vector. The index tree will be used to lookup membership + // values. If the index tree was constructed with nan positions, this index will + // differ from the PointDataTree count + + using IndexTreeManager = tree::LeafManager; + IndexTreeManager leafManager(indexTree); + + const int64_t max = tbb::parallel_reduce(leafManager.leafRange(), -1, + [](const typename IndexTreeManager::LeafRange& range, int64_t value) -> int64_t { + for (auto leaf = range.begin(); leaf; ++leaf) { + auto it = std::max_element(leaf->indices().begin(), leaf->indices().end()); + value = std::max(value, static_cast(*it)); + } + return value; + }, + [](const int64_t a, const int64_t b) { + return std::max(a, b); + } + ); + + if (max != -1 && membership.size() <= static_cast(max)) { + OPENVDB_THROW(IndexError, "Group membership vector size must be larger than " + " the maximum index within the provided index tree."); + } + } + + const Descriptor::GroupIndex index = attributeSet.groupIndex(group); + LeafManagerT leafManager(tree); + + // set membership + + if (remove) { + SetGroupFromIndexOp + set(indexTree, membership, index); + tbb::parallel_for(leafManager.leafRange(), set); + } + else { + SetGroupFromIndexOp + set(indexTree, membership, index); + tbb::parallel_for(leafManager.leafRange(), set); + } +} + + +//////////////////////////////////////// + + +template +inline void setGroup( PointDataTreeT& tree, + const Name& group, + const bool member) +{ + using Descriptor = AttributeSet::Descriptor; + using LeafManagerT = typename tree::LeafManager; + + using point_group_internal::SetGroupOp; + + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + const AttributeSet& attributeSet = iter->attributeSet(); + const Descriptor& descriptor = attributeSet.descriptor(); + + if (!descriptor.hasGroup(group)) { + OPENVDB_THROW(LookupError, "Group must exist on Tree before defining membership."); + } + + const Descriptor::GroupIndex index = attributeSet.groupIndex(group); + LeafManagerT leafManager(tree); + + // set membership based on member variable + + if (member) tbb::parallel_for(leafManager.leafRange(), SetGroupOp(index)); + else tbb::parallel_for(leafManager.leafRange(), SetGroupOp(index)); +} + + +//////////////////////////////////////// + + +template +inline void setGroupByFilter( PointDataTreeT& tree, + const Name& group, + const FilterT& filter) +{ + using Descriptor = AttributeSet::Descriptor; + using LeafManagerT = typename tree::LeafManager; + + using point_group_internal::SetGroupByFilterOp; + + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + const AttributeSet& attributeSet = iter->attributeSet(); + const Descriptor& descriptor = attributeSet.descriptor(); + + if (!descriptor.hasGroup(group)) { + OPENVDB_THROW(LookupError, "Group must exist on Tree before defining membership."); + } + + const Descriptor::GroupIndex index = attributeSet.groupIndex(group); + + // set membership using filter + + SetGroupByFilterOp set(index, filter); + LeafManagerT leafManager(tree); + + tbb::parallel_for(leafManager.leafRange(), set); +} + + +//////////////////////////////////////// + + +template +inline void setGroupByRandomTarget( PointDataTreeT& tree, + const Name& group, + const Index64 targetPoints, + const unsigned int seed = 0) +{ + using RandomFilter = RandomLeafFilter; + + RandomFilter filter(tree, targetPoints, seed); + + setGroupByFilter(tree, group, filter); +} + + +//////////////////////////////////////// + + +template +inline void setGroupByRandomPercentage( PointDataTreeT& tree, + const Name& group, + const float percentage = 10.0f, + const unsigned int seed = 0) +{ + using RandomFilter = RandomLeafFilter; + + const int currentPoints = static_cast(pointCount(tree)); + const int targetPoints = int(math::Round((percentage * float(currentPoints))/100.0f)); + + RandomFilter filter(tree, targetPoints, seed); + + setGroupByFilter(tree, group, filter); +} + +} // namespace points +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + + +#endif // OPENVDB_POINTS_POINT_GROUP_IMPL_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/impl/PointMaskImpl.h b/openvdb/openvdb/points/impl/PointMaskImpl.h new file mode 100644 index 0000000000..b6634f9a48 --- /dev/null +++ b/openvdb/openvdb/points/impl/PointMaskImpl.h @@ -0,0 +1,358 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 + +/// @file PointMaskImpl.h +/// +/// @author Dan Bailey +/// + +#ifndef OPENVDB_POINTS_POINT_MASK_IMPL_HAS_BEEN_INCLUDED +#define OPENVDB_POINTS_POINT_MASK_IMPL_HAS_BEEN_INCLUDED + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace points { + +/// @cond OPENVDB_DOCS_INTERNAL + +namespace point_mask_internal { + +template +void voxelSum(LeafT& leaf, const Index offset, const typename LeafT::ValueType& value) +{ + leaf.modifyValue(offset, tools::valxform::SumOp(value)); +} + +// overload PointDataLeaf access to use setOffsetOn(), as modifyValue() +// is intentionally disabled to avoid accidental usage + +template +void voxelSum(PointDataLeafNode& leaf, const Index offset, + const typename PointDataLeafNode::ValueType& value) +{ + leaf.setOffsetOn(offset, leaf.getValue(offset) + value); +} + + +/// @brief Combines multiple grids into one by stealing leaf nodes and summing voxel values +/// This class is designed to work with thread local storage containers such as tbb::combinable +template +struct GridCombinerOp +{ + using CombinableT = typename tbb::combinable; + + using TreeT = typename GridT::TreeType; + using LeafT = typename TreeT::LeafNodeType; + using ValueType = typename TreeT::ValueType; + using SumOp = tools::valxform::SumOp; + + GridCombinerOp(GridT& grid) + : mTree(grid.tree()) {} + + void operator()(const GridT& grid) + { + for (auto leaf = grid.tree().beginLeaf(); leaf; ++leaf) { + auto* newLeaf = mTree.probeLeaf(leaf->origin()); + if (!newLeaf) { + // if the leaf doesn't yet exist in the new tree, steal it + auto& tree = const_cast(grid).tree(); + mTree.addLeaf(tree.template stealNode(leaf->origin(), + zeroVal(), false)); + } + else { + // otherwise increment existing values + for (auto iter = leaf->cbeginValueOn(); iter; ++iter) { + voxelSum(*newLeaf, iter.offset(), ValueType(*iter)); + } + } + } + } + +private: + TreeT& mTree; +}; // struct GridCombinerOp + + +/// @brief Compute scalar grid from PointDataGrid while evaluating the point filter +template +struct PointsToScalarOp +{ + using LeafT = typename TreeT::LeafNodeType; + using ValueT = typename LeafT::ValueType; + // This method is also used by PointCount so ValueT may not be bool + static constexpr bool IsBool = + std::is_same::value; + + PointsToScalarOp(const PointDataTreeT& tree, + const FilterT& filter) + : mPointDataAccessor(tree) + , mFilter(filter) {} + + void operator()(LeafT& leaf, size_t /*idx*/) const + { + // assumes matching topology + const auto* const pointLeaf = + mPointDataAccessor.probeConstLeaf(leaf.origin()); + assert(pointLeaf); + + for (auto value = leaf.beginValueOn(); value; ++value) { + const auto iter = pointLeaf->beginIndexVoxel(value.getCoord(), mFilter); + if (IsBool) { + if (!iter) value.setValueOn(false); + } + else { + const Index64 count = points::iterCount(iter); + if (count > Index64(0)) value.setValue(ValueT(count)); + else value.setValueOn(false); + } + } + } + +private: + const tree::ValueAccessor mPointDataAccessor; + const FilterT& mFilter; +}; // struct PointsToScalarOp + + +/// @brief Compute scalar grid from PointDataGrid using a different transform +/// and while evaluating the point filter +template +struct PointsToTransformedScalarOp +{ + using PointDataLeafT = typename PointDataGridT::TreeType::LeafNodeType; + using ValueT = typename GridT::TreeType::ValueType; + using HandleT = AttributeHandle; + using CombinableT = typename GridCombinerOp::CombinableT; + + PointsToTransformedScalarOp(const math::Transform& targetTransform, + const math::Transform& sourceTransform, + const FilterT& filter, + const DeformerT& deformer, + CombinableT& combinable) + : mTargetTransform(targetTransform) + , mSourceTransform(sourceTransform) + , mFilter(filter) + , mDeformer(deformer) + , mCombinable(combinable) { } + + void operator()(const PointDataLeafT& leaf, size_t idx) const + { + DeformerT deformer(mDeformer); + + auto& grid = mCombinable.local(); + auto& countTree = grid.tree(); + tree::ValueAccessor accessor(countTree); + + deformer.reset(leaf, idx); + + auto handle = HandleT::create(leaf.constAttributeArray("P")); + + for (auto iter = leaf.beginIndexOn(mFilter); iter; iter++) { + + // extract index-space position + + Vec3d position = handle->get(*iter) + iter.getCoord().asVec3d(); + + // if deformer is designed to be used in index-space, perform deformation prior + // to transforming position to world-space, otherwise perform deformation afterwards + + if (DeformerTraits::IndexSpace) { + deformer.template apply(position, iter); + position = mSourceTransform.indexToWorld(position); + } + else { + position = mSourceTransform.indexToWorld(position); + deformer.template apply(position, iter); + } + + // determine coord of target grid + + const Coord ijk = mTargetTransform.worldToIndexCellCentered(position); + + // increment count in target voxel + + auto* newLeaf = accessor.touchLeaf(ijk); + assert(newLeaf); + voxelSum(*newLeaf, newLeaf->coordToOffset(ijk), ValueT(1)); + } + } + +private: + const openvdb::math::Transform& mTargetTransform; + const openvdb::math::Transform& mSourceTransform; + const FilterT& mFilter; + const DeformerT& mDeformer; + CombinableT& mCombinable; +}; // struct PointsToTransformedScalarOp + + +template +inline typename TreeT::Ptr convertPointsToScalar( + const PointDataTreeT& points, + const FilterT& filter, + bool threaded = true) +{ + using point_mask_internal::PointsToScalarOp; + + using ValueT = typename TreeT::ValueType; + + // copy the topology from the points tree + + typename TreeT::Ptr tree(new TreeT(/*background=*/false)); + tree->topologyUnion(points); + + // early exit if no leaves + + if (points.leafCount() == 0) return tree; + + // early exit if mask and no group logic + + if (std::is_same::value && filter.state() == index::ALL) return tree; + + // evaluate point group filters to produce a subset of the generated mask + + tree::LeafManager leafManager(*tree); + + if (filter.state() == index::ALL) { + NullFilter nullFilter; + PointsToScalarOp pointsToScalarOp( + points, nullFilter); + leafManager.foreach(pointsToScalarOp, threaded); + } else { + // build mask from points in parallel only where filter evaluates to true + PointsToScalarOp pointsToScalarOp( + points, filter); + leafManager.foreach(pointsToScalarOp, threaded); + } + + return tree; +} + + +template +inline typename GridT::Ptr convertPointsToScalar( + PointDataGridT& points, + const math::Transform& transform, + const FilterT& filter, + const DeformerT& deformer, + bool threaded = true) +{ + using point_mask_internal::PointsToTransformedScalarOp; + using point_mask_internal::GridCombinerOp; + + using CombinerOpT = GridCombinerOp; + using CombinableT = typename GridCombinerOp::CombinableT; + + typename GridT::Ptr grid = GridT::create(); + grid->setTransform(transform.copy()); + + // use the simpler method if the requested transform matches the existing one + + const math::Transform& pointsTransform = points.constTransform(); + + if (transform == pointsTransform && std::is_same()) { + using TreeT = typename GridT::TreeType; + typename TreeT::Ptr tree = + convertPointsToScalar(points.tree(), filter, threaded); + grid->setTree(tree); + return grid; + } + + // early exit if no leaves + + if (points.constTree().leafCount() == 0) return grid; + + // compute mask grids in parallel using new transform + + CombinableT combiner; + + tree::LeafManager leafManager(points.tree()); + + if (filter.state() == index::ALL) { + NullFilter nullFilter; + PointsToTransformedScalarOp pointsToScalarOp( + transform, pointsTransform, nullFilter, deformer, combiner); + leafManager.foreach(pointsToScalarOp, threaded); + } else { + PointsToTransformedScalarOp pointsToScalarOp( + transform, pointsTransform, filter, deformer, combiner); + leafManager.foreach(pointsToScalarOp, threaded); + } + + // combine the mask grids into one + + CombinerOpT combineOp(*grid); + combiner.combine_each(combineOp); + + return grid; +} + + +} // namespace point_mask_internal + +/// @endcond + +//////////////////////////////////////// + + +template +inline typename std::enable_if::value && + std::is_same::value, typename MaskTreeT::Ptr>::type +convertPointsToMask(const PointDataTreeT& tree, + const FilterT& filter, + bool threaded) +{ + return point_mask_internal::convertPointsToScalar( + tree, filter, threaded); +} + + +template +inline typename std::enable_if::value && + std::is_same::value, typename MaskGridT::Ptr>::type +convertPointsToMask( + const PointDataGridT& points, + const FilterT& filter, + bool threaded) +{ + using PointDataTreeT = typename PointDataGridT::TreeType; + using MaskTreeT = typename MaskGridT::TreeType; + + typename MaskTreeT::Ptr tree = + convertPointsToMask + (points.tree(), filter, threaded); + + typename MaskGridT::Ptr grid(new MaskGridT(tree)); + grid->setTransform(points.transform().copy()); + return grid; +} + + +template +inline typename std::enable_if::value, + typename MaskT::Ptr>::type +convertPointsToMask( + const PointDataGridT& points, + const openvdb::math::Transform& transform, + const FilterT& filter, + bool threaded) +{ + // This is safe because the PointDataGrid can only be modified by the deformer + using AdapterT = TreeAdapter; + auto& nonConstPoints = const_cast(points); + + NullDeformer deformer; + return point_mask_internal::convertPointsToScalar( + nonConstPoints, transform, filter, deformer, threaded); +} + + +//////////////////////////////////////// + + +} // namespace points +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_POINTS_POINT_MASK_IMPL_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/impl/PointMoveImpl.h b/openvdb/openvdb/points/impl/PointMoveImpl.h new file mode 100644 index 0000000000..984c901f79 --- /dev/null +++ b/openvdb/openvdb/points/impl/PointMoveImpl.h @@ -0,0 +1,766 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 + +/// @author Dan Bailey +/// +/// @file PointMoveImpl.h +/// + +#ifndef OPENVDB_POINTS_POINT_MOVE_IMPL_HAS_BEEN_INCLUDED +#define OPENVDB_POINTS_POINT_MOVE_IMPL_HAS_BEEN_INCLUDED + +class TestPointMove; + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace points { + +// define leaf index in use as 32-bit +namespace point_move_internal { using LeafIndex = Index32; } + + +/// @brief A Deformer that caches the resulting positions from evaluating another Deformer +template +class CachedDeformer +{ +public: + using LeafIndex = point_move_internal::LeafIndex; + using Vec3T = typename math::Vec3; + using LeafVecT = std::vector; + using LeafMapT = std::unordered_map; + + // Internal data cache to allow the deformer to offer light-weight copying + struct Cache + { + struct Leaf + { + /// @brief clear data buffers and reset counter + void clear() { + vecData.clear(); + mapData.clear(); + totalSize = 0; + } + + LeafVecT vecData; + LeafMapT mapData; + Index totalSize = 0; + }; // struct Leaf + + std::vector leafs; + }; // struct Cache + + /// Cache is expected to be persistent for the lifetime of the CachedDeformer + explicit CachedDeformer(Cache& cache); + + /// Caches the result of evaluating the supplied point grid using the deformer and filter + /// @param grid the points to be moved + /// @param deformer the deformer to apply to the points + /// @param filter the point filter to use when evaluating the points + /// @param threaded enable or disable threading (threading is enabled by default) + template + void evaluate(PointDataGridT& grid, DeformerT& deformer, const FilterT& filter, + bool threaded = true); + + /// Stores pointers to the vector or map and optionally expands the map into a vector + /// @throw IndexError if idx is out-of-range of the leafs in the cache + template + void reset(const LeafT& leaf, size_t idx); + + /// Retrieve the new position from the cache + template + void apply(Vec3d& position, const IndexIterT& iter) const; + +private: + friend class ::TestPointMove; + + Cache& mCache; + const LeafVecT* mLeafVec = nullptr; + const LeafMapT* mLeafMap = nullptr; +}; // class CachedDeformer + + +//////////////////////////////////////// + + +namespace point_move_internal { + +using IndexArray = std::vector; + +using IndexTriple = std::tuple; +using IndexTripleArray = tbb::concurrent_vector; +using GlobalPointIndexMap = std::vector; +using GlobalPointIndexIndices = std::vector; + +using IndexPair = std::pair; +using IndexPairArray = std::vector; +using LocalPointIndexMap = std::vector; + +using LeafIndexArray = std::vector; +using LeafOffsetArray = std::vector; +using LeafMap = std::unordered_map; + + +template +struct BuildMoveMapsOp +{ + using LeafT = typename TreeT::LeafNodeType; + using LeafArrayT = std::vector; + using LeafManagerT = typename tree::LeafManager; + + BuildMoveMapsOp(const DeformerT& deformer, + GlobalPointIndexMap& globalMoveLeafMap, + LocalPointIndexMap& localMoveLeafMap, + const LeafMap& targetLeafMap, + const math::Transform& targetTransform, + const math::Transform& sourceTransform, + const FilterT& filter) + : mDeformer(deformer) + , mGlobalMoveLeafMap(globalMoveLeafMap) + , mLocalMoveLeafMap(localMoveLeafMap) + , mTargetLeafMap(targetLeafMap) + , mTargetTransform(targetTransform) + , mSourceTransform(sourceTransform) + , mFilter(filter) { } + + void operator()(LeafT& leaf, size_t idx) const + { + DeformerT deformer(mDeformer); + deformer.reset(leaf, idx); + + // determine source leaf node origin and offset in the source leaf vector + + Coord sourceLeafOrigin = leaf.origin(); + + auto sourceHandle = AttributeWriteHandle::create(leaf.attributeArray("P")); + + for (auto iter = leaf.beginIndexOn(mFilter); iter; iter++) { + + const bool useIndexSpace = DeformerTraits::IndexSpace; + + // extract index-space position and apply index-space deformation (if applicable) + + Vec3d positionIS = sourceHandle->get(*iter) + iter.getCoord().asVec3d(); + if (useIndexSpace) { + deformer.apply(positionIS, iter); + } + + // transform to world-space position and apply world-space deformation (if applicable) + + Vec3d positionWS = mSourceTransform.indexToWorld(positionIS); + if (!useIndexSpace) { + deformer.apply(positionWS, iter); + } + + // transform to index-space position of target grid + + positionIS = mTargetTransform.worldToIndex(positionWS); + + // determine target voxel and offset + + Coord targetVoxel = Coord::round(positionIS); + Index targetOffset = LeafT::coordToOffset(targetVoxel); + + // set new local position in source transform space (if point has been deformed) + + Vec3d voxelPosition(positionIS - targetVoxel.asVec3d()); + sourceHandle->set(*iter, voxelPosition); + + // determine target leaf node origin and offset in the target leaf vector + + Coord targetLeafOrigin = targetVoxel & ~(LeafT::DIM - 1); + assert(mTargetLeafMap.find(targetLeafOrigin) != mTargetLeafMap.end()); + const LeafIndex targetLeafOffset(mTargetLeafMap.at(targetLeafOrigin)); + + // insert into move map based on whether point ends up in a new leaf node or not + + if (targetLeafOrigin == sourceLeafOrigin) { + mLocalMoveLeafMap[targetLeafOffset].emplace_back(targetOffset, *iter); + } + else { + mGlobalMoveLeafMap[targetLeafOffset].push_back(IndexTriple( + LeafIndex(static_cast(idx)), targetOffset, *iter)); + } + } + } + +private: + const DeformerT& mDeformer; + GlobalPointIndexMap& mGlobalMoveLeafMap; + LocalPointIndexMap& mLocalMoveLeafMap; + const LeafMap& mTargetLeafMap; + const math::Transform& mTargetTransform; + const math::Transform& mSourceTransform; + const FilterT& mFilter; +}; // struct BuildMoveMapsOp + +template +inline Index +indexOffsetFromVoxel(const Index voxelOffset, const LeafT& leaf, IndexArray& offsets) +{ + // compute the target point index by summing the point index of the previous + // voxel with the current number of points added to this voxel, tracked by the + // offsets array + + Index targetOffset = offsets[voxelOffset]++; + if (voxelOffset > 0) { + targetOffset += static_cast(leaf.getValue(voxelOffset - 1)); + } + return targetOffset; +} + + +template +struct GlobalMovePointsOp +{ + using LeafT = typename TreeT::LeafNodeType; + using LeafArrayT = std::vector; + using LeafManagerT = typename tree::LeafManager; + using AttributeArrays = std::vector; + + GlobalMovePointsOp(LeafOffsetArray& offsetMap, + LeafManagerT& sourceLeafManager, + const Index attributeIndex, + const GlobalPointIndexMap& moveLeafMap, + const GlobalPointIndexIndices& moveLeafIndices) + : mOffsetMap(offsetMap) + , mSourceLeafManager(sourceLeafManager) + , mAttributeIndex(attributeIndex) + , mMoveLeafMap(moveLeafMap) + , mMoveLeafIndices(moveLeafIndices) { } + + // A CopyIterator is designed to use the indices in a GlobalPointIndexMap for this leaf + // and match the interface required for AttributeArray::copyValues() + struct CopyIterator + { + CopyIterator(const LeafT& leaf, const IndexArray& sortedIndices, + const IndexTripleArray& moveIndices, IndexArray& offsets) + : mLeaf(leaf) + , mSortedIndices(sortedIndices) + , mMoveIndices(moveIndices) + , mOffsets(offsets) { } + + operator bool() const { return bool(mIt); } + + void reset(Index startIndex, Index endIndex) + { + mIndex = startIndex; + mEndIndex = endIndex; + this->advance(); + } + + CopyIterator& operator++() + { + this->advance(); + return *this; + } + + Index leafIndex(Index i) const + { + if (i < mSortedIndices.size()) { + return std::get<0>(this->leafIndexTriple(i)); + } + return std::numeric_limits::max(); + } + + Index sourceIndex() const + { + assert(mIt); + return std::get<2>(*mIt); + } + + Index targetIndex() const + { + assert(mIt); + return indexOffsetFromVoxel(std::get<1>(*mIt), mLeaf, mOffsets); + } + + private: + void advance() + { + if (mIndex >= mEndIndex || mIndex >= mSortedIndices.size()) { + mIt = nullptr; + } + else { + mIt = &this->leafIndexTriple(mIndex); + } + ++mIndex; + } + + const IndexTriple& leafIndexTriple(Index i) const + { + return mMoveIndices[mSortedIndices[i]]; + } + + private: + const LeafT& mLeaf; + Index mIndex; + Index mEndIndex; + const IndexArray& mSortedIndices; + const IndexTripleArray& mMoveIndices; + IndexArray& mOffsets; + const IndexTriple* mIt = nullptr; + }; // struct CopyIterator + + void operator()(LeafT& leaf, size_t idx) const + { + const IndexTripleArray& moveIndices = mMoveLeafMap[idx]; + if (moveIndices.empty()) return; + const IndexArray& sortedIndices = mMoveLeafIndices[idx]; + + // extract per-voxel offsets for this leaf + + LeafIndexArray& offsets = mOffsetMap[idx]; + + // extract target array and ensure data is out-of-core and non-uniform + + auto& targetArray = leaf.attributeArray(mAttributeIndex); + targetArray.loadData(); + targetArray.expand(); + + // perform the copy + + CopyIterator copyIterator(leaf, sortedIndices, moveIndices, offsets); + + // use the sorted indices to track the index of the source leaf + + Index sourceLeafIndex = copyIterator.leafIndex(0); + Index startIndex = 0; + + for (size_t i = 1; i <= sortedIndices.size(); i++) { + Index endIndex = static_cast(i); + Index newSourceLeafIndex = copyIterator.leafIndex(endIndex); + + // when it changes, do a batch-copy of all the indices that lie within this range + // TODO: this step could use nested parallelization for cases where there are a + // large number of points being moved per attribute + + if (newSourceLeafIndex > sourceLeafIndex) { + copyIterator.reset(startIndex, endIndex); + + const LeafT& sourceLeaf = mSourceLeafManager.leaf(sourceLeafIndex); + const auto& sourceArray = sourceLeaf.constAttributeArray(mAttributeIndex); + sourceArray.loadData(); + + targetArray.copyValuesUnsafe(sourceArray, copyIterator); + + sourceLeafIndex = newSourceLeafIndex; + startIndex = endIndex; + } + } + } + +private: + LeafOffsetArray& mOffsetMap; + LeafManagerT& mSourceLeafManager; + const Index mAttributeIndex; + const GlobalPointIndexMap& mMoveLeafMap; + const GlobalPointIndexIndices& mMoveLeafIndices; +}; // struct GlobalMovePointsOp + + +template +struct LocalMovePointsOp +{ + using LeafT = typename TreeT::LeafNodeType; + using LeafArrayT = std::vector; + using LeafManagerT = typename tree::LeafManager; + using AttributeArrays = std::vector; + + LocalMovePointsOp( LeafOffsetArray& offsetMap, + const LeafIndexArray& sourceIndices, + LeafManagerT& sourceLeafManager, + const Index attributeIndex, + const LocalPointIndexMap& moveLeafMap) + : mOffsetMap(offsetMap) + , mSourceIndices(sourceIndices) + , mSourceLeafManager(sourceLeafManager) + , mAttributeIndex(attributeIndex) + , mMoveLeafMap(moveLeafMap) { } + + // A CopyIterator is designed to use the indices in a LocalPointIndexMap for this leaf + // and match the interface required for AttributeArray::copyValues() + struct CopyIterator + { + CopyIterator(const LeafT& leaf, const IndexPairArray& indices, IndexArray& offsets) + : mLeaf(leaf) + , mIndices(indices) + , mOffsets(offsets) { } + + operator bool() const { return mIndex < static_cast(mIndices.size()); } + + CopyIterator& operator++() { ++mIndex; return *this; } + + Index sourceIndex() const + { + return mIndices[mIndex].second; + } + + Index targetIndex() const + { + return indexOffsetFromVoxel(mIndices[mIndex].first, mLeaf, mOffsets); + } + + private: + const LeafT& mLeaf; + const IndexPairArray& mIndices; + IndexArray& mOffsets; + int mIndex = 0; + }; // struct CopyIterator + + void operator()(LeafT& leaf, size_t idx) const + { + const IndexPairArray& moveIndices = mMoveLeafMap[idx]; + if (moveIndices.empty()) return; + + // extract per-voxel offsets for this leaf + + LeafIndexArray& offsets = mOffsetMap[idx]; + + // extract source array that has the same origin as the target leaf + + assert(idx < mSourceIndices.size()); + const Index sourceLeafOffset(mSourceIndices[idx]); + LeafT& sourceLeaf = mSourceLeafManager.leaf(sourceLeafOffset); + const auto& sourceArray = sourceLeaf.constAttributeArray(mAttributeIndex); + sourceArray.loadData(); + + // extract target array and ensure data is out-of-core and non-uniform + + auto& targetArray = leaf.attributeArray(mAttributeIndex); + targetArray.loadData(); + targetArray.expand(); + + // perform the copy + + CopyIterator copyIterator(leaf, moveIndices, offsets); + targetArray.copyValuesUnsafe(sourceArray, copyIterator); + } + +private: + LeafOffsetArray& mOffsetMap; + const LeafIndexArray& mSourceIndices; + LeafManagerT& mSourceLeafManager; + const Index mAttributeIndex; + const LocalPointIndexMap& mMoveLeafMap; +}; // struct LocalMovePointsOp + + +} // namespace point_move_internal + + +//////////////////////////////////////// + + +template +inline void movePoints( PointDataGridT& points, + const math::Transform& transform, + DeformerT& deformer, + const FilterT& filter, + future::Advect* objectNotInUse, + bool threaded) +{ + using LeafIndex = point_move_internal::LeafIndex; + using PointDataTreeT = typename PointDataGridT::TreeType; + using LeafT = typename PointDataTreeT::LeafNodeType; + using LeafManagerT = typename tree::LeafManager; + + using namespace point_move_internal; + + // this object is for future use only + assert(!objectNotInUse); + (void)objectNotInUse; + + PointDataTreeT& tree = points.tree(); + + // early exit if no LeafNodes + + auto iter = tree.cbeginLeaf(); + + if (!iter) return; + + // build voxel topology taking into account any point group deletion + + auto newPoints = point_mask_internal::convertPointsToScalar( + points, transform, filter, deformer, threaded); + auto& newTree = newPoints->tree(); + + // create leaf managers for both trees + + LeafManagerT sourceLeafManager(tree); + LeafManagerT targetLeafManager(newTree); + + // extract the existing attribute set + const auto& existingAttributeSet = points.tree().cbeginLeaf()->attributeSet(); + + // build a coord -> index map for looking up target leafs by origin and a faster + // unordered map for finding the source index from a target index + + LeafMap targetLeafMap; + LeafIndexArray sourceIndices(targetLeafManager.leafCount(), + std::numeric_limits::max()); + + LeafOffsetArray offsetMap(targetLeafManager.leafCount()); + + { + LeafMap sourceLeafMap; + auto sourceRange = sourceLeafManager.leafRange(); + for (auto leaf = sourceRange.begin(); leaf; ++leaf) { + sourceLeafMap.insert({leaf->origin(), LeafIndex(static_cast(leaf.pos()))}); + } + auto targetRange = targetLeafManager.leafRange(); + for (auto leaf = targetRange.begin(); leaf; ++leaf) { + targetLeafMap.insert({leaf->origin(), LeafIndex(static_cast(leaf.pos()))}); + } + + // acquire registry lock to avoid locking when appending attributes in parallel + + AttributeArray::ScopedRegistryLock lock; + + // perform four independent per-leaf operations in parallel + targetLeafManager.foreach( + [&](LeafT& leaf, size_t idx) { + // map frequency => cumulative histogram + auto* buffer = leaf.buffer().data(); + for (Index i = 1; i < leaf.buffer().size(); i++) { + buffer[i] = buffer[i-1] + buffer[i]; + } + // replace attribute set with a copy of the existing one + leaf.replaceAttributeSet( + new AttributeSet(existingAttributeSet, leaf.getLastValue(), &lock), + /*allowMismatchingDescriptors=*/true); + // store the index of the source leaf in a corresponding target leaf array + const auto it = sourceLeafMap.find(leaf.origin()); + if (it != sourceLeafMap.end()) { + sourceIndices[idx] = it->second; + } + // allocate offset maps + offsetMap[idx].resize(LeafT::SIZE); + }, + threaded); + } + + // moving leaf + + GlobalPointIndexMap globalMoveLeafMap(targetLeafManager.leafCount()); + LocalPointIndexMap localMoveLeafMap(targetLeafManager.leafCount()); + + // build global and local move leaf maps and update local positions + + if (filter.state() == index::ALL) { + NullFilter nullFilter; + BuildMoveMapsOp op(deformer, + globalMoveLeafMap, localMoveLeafMap, targetLeafMap, + transform, points.transform(), nullFilter); + sourceLeafManager.foreach(op, threaded); + } else { + BuildMoveMapsOp op(deformer, + globalMoveLeafMap, localMoveLeafMap, targetLeafMap, + transform, points.transform(), filter); + sourceLeafManager.foreach(op, threaded); + } + + // build a sorted index vector for each leaf that references the global move map + // indices in order of their source leafs and voxels to ensure determinism in the + // resulting point orders + + GlobalPointIndexIndices globalMoveLeafIndices(globalMoveLeafMap.size()); + + targetLeafManager.foreach( + [&](LeafT& /*leaf*/, size_t idx) { + const IndexTripleArray& moveIndices = globalMoveLeafMap[idx]; + if (moveIndices.empty()) return; + + IndexArray& sortedIndices = globalMoveLeafIndices[idx]; + sortedIndices.resize(moveIndices.size()); + std::iota(std::begin(sortedIndices), std::end(sortedIndices), 0); + std::sort(std::begin(sortedIndices), std::end(sortedIndices), + [&](int i, int j) + { + const Index& indexI0(std::get<0>(moveIndices[i])); + const Index& indexJ0(std::get<0>(moveIndices[j])); + if (indexI0 < indexJ0) return true; + if (indexI0 > indexJ0) return false; + return std::get<2>(moveIndices[i]) < std::get<2>(moveIndices[j]); + } + ); + }, + threaded); + + for (const auto& it : existingAttributeSet.descriptor().map()) { + + const Index attributeIndex = static_cast(it.second); + + // zero offsets + targetLeafManager.foreach( + [&offsetMap](const LeafT& /*leaf*/, size_t idx) { + std::fill(offsetMap[idx].begin(), offsetMap[idx].end(), 0); + }, + threaded); + + // move points between leaf nodes + + GlobalMovePointsOp globalMoveOp(offsetMap, + sourceLeafManager, attributeIndex, globalMoveLeafMap, globalMoveLeafIndices); + targetLeafManager.foreach(globalMoveOp, threaded); + + // move points within leaf nodes + + LocalMovePointsOp localMoveOp(offsetMap, + sourceIndices, sourceLeafManager, attributeIndex, localMoveLeafMap); + targetLeafManager.foreach(localMoveOp, threaded); + } + + points.setTree(newPoints->treePtr()); +} + + +template +inline void movePoints( PointDataGridT& points, + DeformerT& deformer, + const FilterT& filter, + future::Advect* objectNotInUse, + bool threaded) +{ + movePoints(points, points.transform(), deformer, filter, objectNotInUse, threaded); +} + + +//////////////////////////////////////// + + +template +CachedDeformer::CachedDeformer(Cache& cache) + : mCache(cache) { } + + +template +template +void CachedDeformer::evaluate(PointDataGridT& grid, DeformerT& deformer, const FilterT& filter, + bool threaded) +{ + using TreeT = typename PointDataGridT::TreeType; + using LeafT = typename TreeT::LeafNodeType; + using LeafManagerT = typename tree::LeafManager; + LeafManagerT leafManager(grid.tree()); + + // initialize cache + auto& leafs = mCache.leafs; + leafs.resize(leafManager.leafCount()); + + const auto& transform = grid.transform(); + + // insert deformed positions into the cache + + auto cachePositionsOp = [&](const LeafT& leaf, size_t idx) { + + const Index64 totalPointCount = leaf.pointCount(); + if (totalPointCount == 0) return; + + // deformer is copied to ensure that it is unique per-thread + + DeformerT newDeformer(deformer); + + newDeformer.reset(leaf, idx); + + auto handle = AttributeHandle::create(leaf.constAttributeArray("P")); + + auto& cache = leafs[idx]; + cache.clear(); + + // only insert into a vector directly if the filter evaluates all points + // and all points are stored in active voxels + const bool useVector = filter.state() == index::ALL && + (leaf.isDense() || (leaf.onPointCount() == leaf.pointCount())); + if (useVector) { + cache.vecData.resize(totalPointCount); + } + + for (auto iter = leaf.beginIndexOn(filter); iter; iter++) { + + // extract index-space position and apply index-space deformation (if defined) + + Vec3d position = handle->get(*iter) + iter.getCoord().asVec3d(); + + // if deformer is designed to be used in index-space, perform deformation prior + // to transforming position to world-space, otherwise perform deformation afterwards + + if (DeformerTraits::IndexSpace) { + newDeformer.apply(position, iter); + position = transform.indexToWorld(position); + } + else { + position = transform.indexToWorld(position); + newDeformer.apply(position, iter); + } + + // insert new position into the cache + + if (useVector) { + cache.vecData[*iter] = static_cast(position); + } + else { + cache.mapData.insert({*iter, static_cast(position)}); + } + } + + // store the total number of points to allow use of an expanded vector on access + + if (!cache.mapData.empty()) { + cache.totalSize = static_cast(totalPointCount); + } + }; + + leafManager.foreach(cachePositionsOp, threaded); +} + + +template +template +void CachedDeformer::reset(const LeafT& /*leaf*/, size_t idx) +{ + if (idx >= mCache.leafs.size()) { + if (mCache.leafs.empty()) { + throw IndexError("No leafs in cache, perhaps CachedDeformer has not been evaluated?"); + } else { + throw IndexError("Leaf index is out-of-range of cache leafs."); + } + } + auto& cache = mCache.leafs[idx]; + if (!cache.mapData.empty()) { + mLeafMap = &cache.mapData; + mLeafVec = nullptr; + } + else { + mLeafVec = &cache.vecData; + mLeafMap = nullptr; + } +} + + +template +template +void CachedDeformer::apply(Vec3d& position, const IndexIterT& iter) const +{ + assert(*iter >= 0); + + if (mLeafMap) { + auto it = mLeafMap->find(*iter); + if (it == mLeafMap->end()) return; + position = static_cast(it->second); + } + else { + assert(mLeafVec); + + if (mLeafVec->empty()) return; + assert(*iter < mLeafVec->size()); + position = static_cast((*mLeafVec)[*iter]); + } +} + + +} // namespace points +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_POINTS_POINT_MOVE_IMPL_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/impl/PointReplicateImpl.h b/openvdb/openvdb/points/impl/PointReplicateImpl.h new file mode 100644 index 0000000000..e817fc0c96 --- /dev/null +++ b/openvdb/openvdb/points/impl/PointReplicateImpl.h @@ -0,0 +1,287 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 + +/// @author Nick Avramoussis +/// +/// @file PointReplicateImpl.h +/// + +#ifndef OPENVDB_POINTS_POINT_REPLICATE_IMPL_HAS_BEEN_INCLUDED +#define OPENVDB_POINTS_POINT_REPLICATE_IMPL_HAS_BEEN_INCLUDED + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace points { + +template +typename PointDataGridT::Ptr +replicate(const PointDataGridT& source, + const Index multiplier, + const std::vector& attributes, + const std::string& scaleAttribute, + const std::string& replicationIndex) +{ + // The copy iterator, used to transfer array values from the source grid + // to the target (replicated grid). + struct CopyIter + { +#ifdef __clang__ + // Silence incorrect clang warning + _Pragma("clang diagnostic push") + _Pragma("clang diagnostic ignored \"-Wunused-local-typedef\"") + using GetIncrementCB = std::function; + _Pragma("clang diagnostic pop") +#else + using GetIncrementCB = std::function; +#endif + + CopyIter(const Index end, const GetIncrementCB& cb) + : mIt(0), mEnd(0), mSource(0), mSourceEnd(end), mCallback(cb) { + mEnd = mCallback(mSource); + } + + operator bool() const { return mSource < mSourceEnd; } + + CopyIter& operator++() + { + ++mIt; + // If we have hit the end for current source idx, increment the source idx + // moving end to the new position for the next source with a non zero + // number of new values + while (mIt >= mEnd) { + ++mSource; + if (*this) mEnd += mCallback(mSource); + else return *this; + } + + return *this; + } + + Index sourceIndex() const { assert(*this); return mSource; } + Index targetIndex() const { assert(*this); return mIt; } + + private: + Index mIt, mEnd, mSource; + const Index mSourceEnd; + const GetIncrementCB mCallback; + }; // struct CopyIter + + + // We want the topology and index values of the leaf nodes, but we + // DON'T want to copy the arrays. This should only shallow copy the + // descriptor and arrays + PointDataGrid::Ptr points = source.deepCopy(); + + auto iter = source.tree().cbeginLeaf(); + if (!iter) return points; + + const AttributeSet::Descriptor& sourceDescriptor = + iter->attributeSet().descriptor(); + + // verify position + + const size_t ppos = sourceDescriptor.find("P"); + assert(ppos != AttributeSet::INVALID_POS); + + // build new dummy attribute set + + AttributeSet::Ptr set; + std::vector attribsToDrop; + if (!attributes.empty()) { + for (const auto& nameIdxPair : sourceDescriptor.map()) { + if (std::find(attributes.begin(), attributes.end(), nameIdxPair.first) != attributes.end()) continue; + if (nameIdxPair.first == "P") continue; + attribsToDrop.emplace_back(nameIdxPair.second); + } + set.reset(new AttributeSet(iter->attributeSet(), 1)); + } + else { + set.reset(new AttributeSet(AttributeSet::Descriptor::create(sourceDescriptor.type(ppos)))); + } + + if (!replicationIndex.empty()) { + // don't copy replicationIndex attribute if it exists + // as it'll be overwritten and we create it after + const size_t replIdxIdx = sourceDescriptor.find(replicationIndex); + if (replIdxIdx != AttributeSet::INVALID_POS) attribsToDrop.emplace_back(replIdxIdx); + } + set->dropAttributes(attribsToDrop); + + // validate replication attributes + + size_t replicationIdx = AttributeSet::INVALID_POS; + if (!replicationIndex.empty()) { + set->appendAttribute(replicationIndex, TypedAttributeArray::attributeType()); + replicationIdx = set->descriptor().find(replicationIndex); + } + + AttributeSet::DescriptorPtr descriptor = set->descriptorPtr(); + + const size_t scaleIdx = !scaleAttribute.empty() ? + sourceDescriptor.find(scaleAttribute) : AttributeSet::INVALID_POS; + + openvdb::tree::LeafManager sourceManager(source.tree()); + openvdb::tree::LeafManager manager(points->tree()); + + manager.foreach( + [&](PointDataTree::LeafNodeType& leaf, size_t pos) + { + using ValueType = PointDataTree::ValueType; + + const auto& sourceLeaf = sourceManager.leaf(pos); + // @note This really shoudn't return uint64_t as AttributeArray's size is + // limited to the max of a uint32_t... + assert(sourceLeaf.pointCount() < Index64(std::numeric_limits::max())); + const Index sourceCount = static_cast(sourceLeaf.pointCount()); + + Index uniformMultiplier = multiplier; + AttributeHandle::UniquePtr scaleHandle(nullptr); + bool useScale = scaleIdx != AttributeSet::INVALID_POS; + if (useScale) { + scaleHandle = std::make_unique> + (sourceLeaf.constAttributeArray(scaleIdx)); + } + // small lambda that returns the amount of points to generate + // based on a scale. Should only be called if useScale is true, + // otherwise the scaleHandle will be reset or null + auto getPointsToGenerate = [&](const Index index) -> Index { + const float scale = std::max(0.0f, scaleHandle->get(index)); + return static_cast + (math::Round(static_cast(multiplier) * scale)); + }; + + // if uniform, update the multiplier and don't bother using the scale attribute + + if (useScale && scaleHandle->isUniform()) { + uniformMultiplier = getPointsToGenerate(0); + scaleHandle.reset(); + useScale = false; + } + + // get the new count and build the new offsets - do this in this loop so we + // don't have to cache the offset vector. Note that the leaf offsets become + // invalid until leaf.replaceAttributeSet is called and should not be used + + Index total = 0; + + if (useScale) { + for (auto iter = sourceLeaf.cbeginValueAll(); iter; ++iter) { + for (auto piter = sourceLeaf.beginIndexVoxel(iter.getCoord()); + piter; ++piter) { total += getPointsToGenerate(*piter); } + leaf.setOffsetOnly(iter.pos(), total); + } + } + else { + total = uniformMultiplier * sourceCount; + + // with a uniform multiplier, just multiply each voxel value + auto* data = leaf.buffer().data(); + for (size_t i = 0; i < leaf.buffer().size(); ++i) { + const ValueType::IntType value = data[i]; + data[i] = value * uniformMultiplier; + } + } + + // turn voxels off if no points + leaf.updateValueMask(); + const AttributeSet& sourceSet = sourceLeaf.attributeSet(); + + std::unique_ptr newSet + (new AttributeSet(*set, total)); + + auto copy = [&](const std::string& name) + { + const auto* sourceArray = sourceSet.getConst(name); + assert(sourceArray); + + // manually expand so that copyValues() doesn't expand and fill the array - + // we don't want to unnecessarily zero initialize the target values as we know + // we're going to write to all of them. + auto* array = newSet->get(name); + assert(array); + array->expand(/*fill*/false); + + if (useScale) { + const CopyIter iter(sourceCount, [&](const Index i) { return getPointsToGenerate(i); }); + array->copyValues(*sourceArray, iter); + } + else { + const CopyIter iter(sourceCount, [&](const Index) { return uniformMultiplier; }); + array->copyValues(*sourceArray, iter); + } + }; + + copy("P"); + for (const auto& iter : descriptor->map()) { + if (iter.first == "P") continue; + if (iter.first == replicationIndex) continue; + copy(iter.first); + } + + // assign the replication idx if requested + + if (replicationIdx != AttributeSet::INVALID_POS && total > 0) { + AttributeWriteHandle + idxHandle(*newSet->get(replicationIdx), /*expand*/false); + idxHandle.expand(/*fill*/false); + assert(idxHandle.size() == total); + + + Index offset = 0; + + if (useScale) { + for (Index i = 0; i < sourceCount; ++i) { + const Index pointRepCount = getPointsToGenerate(i); + for (Index j = 0; j < pointRepCount; ++j) { + idxHandle.set(offset++, j); + } + } + } + else { + while (offset < total) { + for (Index j = 0; j < uniformMultiplier; ++j) { + idxHandle.set(offset++, j); + } + } + } + } + + leaf.replaceAttributeSet(newSet.release(), /*mismatch*/true); + }); + + if (!scaleAttribute.empty()) { + tools::pruneInactive(points->tree()); + } + + return points; +} + +template +typename PointDataGridT::Ptr +replicate(const PointDataGridT& source, + const Index multiplier, + const std::string& scaleAttribute, + const std::string& replicationIndex) +{ + auto iter = source.tree().cbeginLeaf(); + if (!iter) return source.deepCopy(); + + const openvdb::points::AttributeSet::Descriptor& sourceDescriptor = + iter->attributeSet().descriptor(); + + std::vector attribs; + attribs.reserve(sourceDescriptor.map().size()); + for (const auto& namepos : sourceDescriptor.map()) { + attribs.emplace_back(namepos.first); + } + + return replicate(source, multiplier, attribs, scaleAttribute, replicationIndex); +} + + +} // namespace points +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_POINTS_POINT_REPLICATE_IMPL_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/impl/PointSampleImpl.h b/openvdb/openvdb/points/impl/PointSampleImpl.h new file mode 100644 index 0000000000..0b46c78ae4 --- /dev/null +++ b/openvdb/openvdb/points/impl/PointSampleImpl.h @@ -0,0 +1,457 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 + +/// @author Nick Avramoussis, Francisco Gochez, Dan Bailey +/// +/// @file PointSampleImpl.h +/// + +#ifndef OPENVDB_POINTS_POINT_SAMPLE_IMPL_HAS_BEEN_INCLUDED +#define OPENVDB_POINTS_POINT_SAMPLE_IMPL_HAS_BEEN_INCLUDED + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace points { + +/// @cond OPENVDB_DOCS_INTERNAL + +namespace point_sample_internal { + + +template +struct CompatibleTypes { enum { value = std::is_constructible::value }; }; + +// Specializations for types that can be converted from source grid to target attribute +template struct CompatibleTypes< + T, T> { enum { value = true }; }; +template struct CompatibleTypes< + T, math::Vec2> { enum { value = true }; }; +template struct CompatibleTypes< + T, math::Vec3> { enum { value = true }; }; +template struct CompatibleTypes< + T, math::Vec4> { enum { value = true }; }; +template struct CompatibleTypes< + math::Vec2, math::Vec2> { enum { value = true }; }; +template struct CompatibleTypes< + math::Vec3, math::Vec3> { enum { value = true }; }; +template struct CompatibleTypes< + math::Vec4, math::Vec4> { enum { value = true }; }; +template struct CompatibleTypes< + math::Vec2, math::Vec2> { enum { value = CompatibleTypes::value }; }; +template struct CompatibleTypes< + math::Vec3, math::Vec3> { enum { value = CompatibleTypes::value }; }; +template struct CompatibleTypes< + math::Vec4, math::Vec4> { enum { value = CompatibleTypes::value }; }; +template struct CompatibleTypes< + ValueMask, T> { enum { value = CompatibleTypes::value }; }; + + +// Ability to access the Order and Staggered template parameter from tools::Sampler +template struct SamplerTraits { + static const size_t Order = 0; + static const bool Staggered = false; +}; +template struct SamplerTraits> { + static const size_t Order = T0; + static const bool Staggered = T1; +}; + + +// default sampling is incompatible, so throw an error +template +struct SampleWithRoundingOp +{ + static inline void sample(ValueT&, const AccessorT&, const Vec3d&) + { + std::ostringstream ostr; + ostr << "Cannot sample a " << typeNameAsString() + << " grid on to a " << typeNameAsString() << " attribute"; + OPENVDB_THROW(TypeError, ostr.str()); + } +}; +// partial specialization to handle sampling and rounding of compatible conversion +template +struct SampleWithRoundingOp +{ + static inline void sample(ValueT& value, const AccessorT& accessor, const Vec3d& position) + { + value = ValueT(math::Round(SamplerT::sample(accessor, position))); + } +}; +// partial specialization to handle sampling and simple casting of compatible conversion +template +struct SampleWithRoundingOp +{ + static inline void sample(ValueT& value, const AccessorT& accessor, const Vec3d& position) + { + value = ValueT(SamplerT::sample(accessor, position)); + } +}; + + +template +class PointDataSampler +{ +public: + PointDataSampler(size_t order, + PointDataGridT& points, + const SamplerT& sampler, + const FilterT& filter, + InterrupterT* const interrupter, + const bool threaded) + : mOrder(order) + , mPoints(points) + , mSampler(sampler) + , mFilter(filter) + , mInterrupter(interrupter) + , mThreaded(threaded) { } + +private: + // No-op transformation + struct AlignedTransform + { + inline Vec3d transform(const Vec3d& position) const { return position; } + }; // struct AlignedTransform + + // Re-sample world-space position from source to target transforms + struct NonAlignedTransform + { + NonAlignedTransform(const math::Transform& source, const math::Transform& target) + : mSource(source) + , mTarget(target) { } + + inline Vec3d transform(const Vec3d& position) const + { + return mSource.worldToIndex(mTarget.indexToWorld(position)); + } + + private: + const math::Transform& mSource; + const math::Transform& mTarget; + }; // struct NonAlignedTransform + + // A simple convenience wrapper that contains the source grid accessor and the sampler + template + struct SamplerWrapper + { + using ValueType = ValueT; + using SourceValueType = typename SourceGridT::ValueType; + using SourceAccessorT = typename SourceGridT::ConstAccessor; + + // can only sample from a bool or mask grid using a PointSampler + static const bool SourceIsBool = std::is_same::value || + std::is_same::value; + static const bool OrderIsZero = SamplerTraits::Order == 0; + static const bool IsValid = !SourceIsBool || OrderIsZero; + + SamplerWrapper(const SourceGridT& sourceGrid, const SamplerT& sampler) + : mAccessor(sourceGrid.getConstAccessor()) + , mSampler(sampler) { } + + // note that creating a new accessor from the underlying tree is faster than + // copying an existing accessor + SamplerWrapper(const SamplerWrapper& other) + : mAccessor(other.mAccessor.tree()) + , mSampler(other.mSampler) { } + + template + inline typename std::enable_if::type + sample(const Vec3d& position) const { + return mSampler.template sample( + mAccessor, position); + } + + template + inline typename std::enable_if::type + sample(const Vec3d& /*position*/) const { + OPENVDB_THROW(RuntimeError, "Cannot sample bool grid with BoxSampler or QuadraticSampler."); + } + + private: + SourceAccessorT mAccessor; + const SamplerT& mSampler; + }; // struct SamplerWrapper + + template + inline void doSample(const SamplerWrapperT& sampleWrapper, const Index targetIndex, + const TransformerT& transformer) + { + using PointDataTreeT = typename PointDataGridT::TreeType; + using LeafT = typename PointDataTreeT::LeafNodeType; + using LeafManagerT = typename tree::LeafManager; + + const auto& filter(mFilter); + const auto& interrupter(mInterrupter); + + auto sampleLambda = [targetIndex, &sampleWrapper, &transformer, &filter, &interrupter]( + LeafT& leaf, size_t /*idx*/) + { + using TargetHandleT = AttributeWriteHandle; + + if (util::wasInterrupted(interrupter)) { + thread::cancelGroupExecution(); + return; + } + + SamplerWrapperT newSampleWrapper(sampleWrapper); + auto positionHandle = AttributeHandle::create(leaf.constAttributeArray("P")); + auto targetHandle = TargetHandleT::create(leaf.attributeArray(targetIndex)); + for (auto iter = leaf.beginIndexOn(filter); iter; ++iter) { + const Vec3d position = transformer.transform( + positionHandle->get(*iter) + iter.getCoord().asVec3d()); + targetHandle->set(*iter, newSampleWrapper.sample(position)); + } + }; + + LeafManagerT leafManager(mPoints.tree()); + + if (mInterrupter) mInterrupter->start(); + + leafManager.foreach(sampleLambda, mThreaded); + + if (mInterrupter) mInterrupter->end(); + } + + template + inline void resolveTransform(const SourceGridT& sourceGrid, const SamplerWrapperT& sampleWrapper, + const Index targetIndex) + { + const auto& sourceTransform = sourceGrid.constTransform(); + const auto& pointsTransform = mPoints.constTransform(); + + if (sourceTransform == pointsTransform) { + AlignedTransform transformer; + doSample(sampleWrapper, targetIndex, transformer); + } else { + NonAlignedTransform transformer(sourceTransform, pointsTransform); + doSample(sampleWrapper, targetIndex, transformer); + } + } + + template + inline void resolveStaggered(const SourceGridT& sourceGrid, const Index targetIndex) + { + using SamplerWrapperT = SamplerWrapper>; + using StaggeredSamplerWrapperT = SamplerWrapper>; + + using SourceValueType = typename SourceGridT::ValueType; + if (VecTraits::Size == 3 && sourceGrid.getGridClass() == GRID_STAGGERED) { + StaggeredSamplerWrapperT sampleWrapper(sourceGrid, mSampler); + resolveTransform(sourceGrid, sampleWrapper, targetIndex); + } else { + SamplerWrapperT sampleWrapper(sourceGrid, mSampler); + resolveTransform(sourceGrid, sampleWrapper, targetIndex); + } + } + +public: + template + inline void sample(const SourceGridT& sourceGrid, Index targetIndex) + { + using SourceValueType = typename SourceGridT::ValueType; + static const bool SourceIsMask = std::is_same::value || + std::is_same::value; + + if (SourceIsMask || mOrder == 0) { + resolveStaggered(sourceGrid, targetIndex); + } else if (mOrder == 1) { + resolveStaggered(sourceGrid, targetIndex); + } else if (mOrder == 2) { + resolveStaggered(sourceGrid, targetIndex); + } + } + +private: + size_t mOrder; + PointDataGridT& mPoints; + const SamplerT& mSampler; + const FilterT& mFilter; + InterrupterT* const mInterrupter; + const bool mThreaded; +}; // class PointDataSampler + + +template +struct AppendAttributeOp +{ + static void append(PointDataGridT& points, const Name& attribute) + { + appendAttribute(points.tree(), attribute); + } +}; +// partial specialization to disable attempts to append attribute type of DummySampleType +template +struct AppendAttributeOp +{ + static void append(PointDataGridT&, const Name&) { } +}; + +} // namespace point_sample_internal + +/// @endcond + +//////////////////////////////////////// + + +template +ValueT SampleWithRounding::sample(const AccessorT& accessor, const Vec3d& position) const +{ + using namespace point_sample_internal; + using SourceValueT = typename AccessorT::ValueType; + static const bool staggered = SamplerTraits::Staggered; + static const bool compatible = CompatibleTypes::value && + (!staggered || (staggered && VecTraits::Size == 3)); + static const bool round = std::is_floating_point::value && + std::is_integral::value; + ValueT value; + SampleWithRoundingOp::sample( + value, accessor, position); + return value; +} + + +//////////////////////////////////////// + + +template +inline void sampleGrid( size_t order, + PointDataGridT& points, + const SourceGridT& sourceGrid, + const Name& targetAttribute, + const FilterT& filter, + const SamplerT& sampler, + InterrupterT* const interrupter, + const bool threaded) +{ + using point_sample_internal::AppendAttributeOp; + using point_sample_internal::PointDataSampler; + + // use the name of the grid if no target attribute name supplied + Name attribute(targetAttribute); + if (targetAttribute.empty()) { + attribute = sourceGrid.getName(); + } + + // we do not allow sampling onto the "P" attribute + if (attribute == "P") { + OPENVDB_THROW(RuntimeError, "Cannot sample onto the \"P\" attribute"); + } + + auto leaf = points.tree().cbeginLeaf(); + if (!leaf) return; + + PointDataSampler pointDataSampler( + order, points, sampler, filter, interrupter, threaded); + + const auto& descriptor = leaf->attributeSet().descriptor(); + size_t targetIndex = descriptor.find(attribute); + const bool attributeExists = targetIndex != AttributeSet::INVALID_POS; + + if (std::is_same::value) { + if (!attributeExists) { + // append attribute of source grid value type + appendAttribute(points.tree(), attribute); + targetIndex = leaf->attributeSet().descriptor().find(attribute); + assert(targetIndex != AttributeSet::INVALID_POS); + + // sample using same type as source grid + pointDataSampler.template sample(sourceGrid, Index(targetIndex)); + } else { + auto targetIdx = static_cast(targetIndex); + // attempt to explicitly sample using type of existing attribute + const Name& targetType = descriptor.valueType(targetIndex); + if (targetType == typeNameAsString()) { + pointDataSampler.template sample(sourceGrid, targetIdx); + } else if (targetType == typeNameAsString()) { + pointDataSampler.template sample(sourceGrid, targetIdx); + } else if (targetType == typeNameAsString()) { + pointDataSampler.template sample(sourceGrid, targetIdx); + } else if (targetType == typeNameAsString()) { + pointDataSampler.template sample(sourceGrid, targetIdx); + } else if (targetType == typeNameAsString()) { + pointDataSampler.template sample(sourceGrid, targetIdx); + } else if (targetType == typeNameAsString()) { + pointDataSampler.template sample(sourceGrid, targetIdx); + } else if (targetType == typeNameAsString()) { + pointDataSampler.template sample(sourceGrid, targetIdx); + } else if (targetType == typeNameAsString()) { + pointDataSampler.template sample(sourceGrid, targetIdx); + } else if (targetType == typeNameAsString()) { + pointDataSampler.template sample(sourceGrid, targetIdx); + } else if (targetType == typeNameAsString()) { + pointDataSampler.template sample(sourceGrid, targetIdx); + } else { + std::ostringstream ostr; + ostr << "Cannot sample attribute of type - " << targetType; + OPENVDB_THROW(TypeError, ostr.str()); + } + } + } else { + if (!attributeExists) { + // append attribute of target value type + // (point_sample_internal wrapper disables the ability to use DummySampleType) + AppendAttributeOp::append(points, attribute); + targetIndex = leaf->attributeSet().descriptor().find(attribute); + assert(targetIndex != AttributeSet::INVALID_POS); + } + else { + const Name targetType = typeNameAsString(); + const Name attributeType = descriptor.valueType(targetIndex); + if (targetType != attributeType) { + std::ostringstream ostr; + ostr << "Requested attribute type " << targetType << " for sampling " + << " does not match existing attribute type " << attributeType; + OPENVDB_THROW(TypeError, ostr.str()); + } + } + + // sample using target value type + pointDataSampler.template sample( + sourceGrid, static_cast(targetIndex)); + } +} + +template +inline void pointSample(PointDataGridT& points, + const SourceGridT& sourceGrid, + const Name& targetAttribute, + const FilterT& filter, + InterrupterT* const interrupter) +{ + SampleWithRounding sampler; + sampleGrid(/*order=*/0, points, sourceGrid, targetAttribute, filter, sampler, interrupter); +} + +template +inline void boxSample( PointDataGridT& points, + const SourceGridT& sourceGrid, + const Name& targetAttribute, + const FilterT& filter, + InterrupterT* const interrupter) +{ + SampleWithRounding sampler; + sampleGrid(/*order=*/1, points, sourceGrid, targetAttribute, filter, sampler, interrupter); +} + +template +inline void quadraticSample(PointDataGridT& points, + const SourceGridT& sourceGrid, + const Name& targetAttribute, + const FilterT& filter, + InterrupterT* const interrupter) +{ + SampleWithRounding sampler; + sampleGrid(/*order=*/2, points, sourceGrid, targetAttribute, filter, sampler, interrupter); +} + + +//////////////////////////////////////// + + +} // namespace points +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_POINTS_POINT_SAMPLE_IMPL_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/impl/PointScatterImpl.h b/openvdb/openvdb/points/impl/PointScatterImpl.h new file mode 100644 index 0000000000..ac95c6af17 --- /dev/null +++ b/openvdb/openvdb/points/impl/PointScatterImpl.h @@ -0,0 +1,424 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 + +/// @author Nick Avramoussis +/// +/// @file PointScatterImpl.h +/// + +#ifndef OPENVDB_POINTS_POINT_SCATTER_IMPL_HAS_BEEN_INCLUDED +#define OPENVDB_POINTS_POINT_SCATTER_IMPL_HAS_BEEN_INCLUDED + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace points { + +/// @cond OPENVDB_DOCS_INTERNAL + +namespace point_scatter_internal +{ + +/// @brief initialise the topology of a PointDataGrid and ensure +/// everything is voxelized +/// @param grid The source grid from which to base the topology generation +template +inline typename PointDataGridT::Ptr +initialisePointTopology(const GridT& grid) +{ + typename PointDataGridT::Ptr points(new PointDataGridT); + points->setTransform(grid.transform().copy()); + points->topologyUnion(grid); + if (points->tree().hasActiveTiles()) { + points->tree().voxelizeActiveTiles(); + } + + return points; +} + +/// @brief Generate random point positions for a leaf node +/// @param leaf The leaf node to initialize +/// @param descriptor The descriptor containing the position type +/// @param count The number of points to generate +/// @param spread The spread of points from the voxel center +/// @param rand01 The random number generator, expected to produce floating point +/// values between 0 and 1. +template +inline void +generatePositions(LeafNodeT& leaf, + const AttributeSet::Descriptor::Ptr& descriptor, + const Index64& count, + const float spread, + RandGenT& rand01) +{ + using PositionTraits = VecTraits; + using ValueType = typename PositionTraits::ElementType; + using PositionWriteHandle = AttributeWriteHandle; + + leaf.initializeAttributes(descriptor, static_cast(count)); + + // directly expand to avoid needlessly setting uniform values in the + // write handle + auto& array = leaf.attributeArray(0); + array.expand(/*fill*/false); + + PositionWriteHandle pHandle(array, /*expand*/false); + PositionType P; + for (Index64 index = 0; index < count; ++index) { + P[0] = (spread * (rand01() - ValueType(0.5))); + P[1] = (spread * (rand01() - ValueType(0.5))); + P[2] = (spread * (rand01() - ValueType(0.5))); + pHandle.set(static_cast(index), P); + } +} + +} // namespace point_scatter_internal + +/// @endcond + +//////////////////////////////////////// + + +template< + typename GridT, + typename RandGenT, + typename PositionArrayT, + typename PointDataGridT, + typename InterrupterT> +inline typename PointDataGridT::Ptr +uniformPointScatter(const GridT& grid, + const Index64 count, + const unsigned int seed, + const float spread, + InterrupterT* interrupter) +{ + using PositionType = typename PositionArrayT::ValueType; + using PositionTraits = VecTraits; + using ValueType = typename PositionTraits::ElementType; + using CodecType = typename PositionArrayT::Codec; + + using RandomGenerator = math::Rand01; + + using TreeType = typename PointDataGridT::TreeType; + using LeafNodeType = typename TreeType::LeafNodeType; + using LeafManagerT = tree::LeafManager; + + struct Local + { + /// @brief Get the prefixed voxel counts for each leaf node with an + /// additional value to represent the end voxel count. + /// See also LeafManager::getPrefixSum() + static void getPrefixSum(LeafManagerT& leafManager, + std::vector& offsets) + { + Index64 offset = 0; + offsets.reserve(leafManager.leafCount() + 1); + offsets.push_back(0); + const auto leafRange = leafManager.leafRange(); + for (auto leaf = leafRange.begin(); leaf; ++leaf) { + offset += leaf->onVoxelCount(); + offsets.push_back(offset); + } + } + }; + + static_assert(PositionTraits::IsVec && PositionTraits::Size == 3, + "Invalid Position Array type."); + + if (spread < 0.0f || spread > 1.0f) { + OPENVDB_THROW(ValueError, "Spread must be between 0 and 1."); + } + + if (interrupter) interrupter->start("Uniform scattering with fixed point count"); + + typename PointDataGridT::Ptr points = + point_scatter_internal::initialisePointTopology(grid); + TreeType& tree = points->tree(); + if (!tree.cbeginLeaf()) return points; + + LeafManagerT leafManager(tree); + const Index64 voxelCount = leafManager.activeLeafVoxelCount(); + assert(voxelCount != 0); + + const double pointsPerVolume = double(count) / double(voxelCount); + const Index32 pointsPerVoxel = static_cast(math::RoundDown(pointsPerVolume)); + const Index64 remainder = count - (pointsPerVoxel * voxelCount); + + if (remainder == 0) { + return denseUniformPointScatter< + GridT, RandGenT, PositionArrayT, PointDataGridT, InterrupterT>( + grid, float(pointsPerVoxel), seed, spread, interrupter); + } + + std::vector voxelOffsets, values; + std::thread worker(&Local::getPrefixSum, std::ref(leafManager), std::ref(voxelOffsets)); + + { + math::RandInt gen(seed, 0, voxelCount-1); + values.reserve(remainder); + for (Index64 i = 0; i < remainder; ++i) values.emplace_back(gen()); + } + + worker.join(); + + if (util::wasInterrupted(interrupter)) { + tree.clear(); + return points; + } + + tbb::parallel_sort(values.begin(), values.end()); + const bool fractionalOnly(pointsPerVoxel == 0); + + leafManager.foreach([&voxelOffsets, &values, fractionalOnly] + (LeafNodeType& leaf, const size_t idx) + { + const Index64 lowerOffset = voxelOffsets[idx]; // inclusive + const Index64 upperOffset = voxelOffsets[idx + 1]; // exclusive + assert(upperOffset > lowerOffset); + + const auto valuesEnd = values.end(); + auto lower = std::lower_bound(values.begin(), valuesEnd, lowerOffset); + + auto* const data = leaf.buffer().data(); + auto iter = leaf.beginValueOn(); + + Index32 currentOffset(0); + bool addedPoints(!fractionalOnly); + while (lower != valuesEnd) { + const Index64 vId = *lower; + if (vId >= upperOffset) break; + + const Index32 nextOffset = Index32(vId - lowerOffset); + iter.increment(nextOffset - currentOffset); + currentOffset = nextOffset; + assert(iter); + + auto& value = data[iter.pos()]; + value = value + 1; // no += operator support + addedPoints = true; + ++lower; + } + + // deactivate this leaf if no points were added. This will speed up + // the unthreaded rng + if (!addedPoints) leaf.setValuesOff(); + }); + + voxelOffsets.clear(); + values.clear(); + + if (fractionalOnly) { + tools::pruneInactive(tree); + leafManager.rebuild(); + } + + const AttributeSet::Descriptor::Ptr descriptor = + AttributeSet::Descriptor::create(PositionArrayT::attributeType()); + RandomGenerator rand01(seed); + + const auto leafRange = leafManager.leafRange(); + auto leaf = leafRange.begin(); + for (; leaf; ++leaf) { + if (util::wasInterrupted(interrupter)) break; + Index32 offset(0); + for (auto iter = leaf->beginValueAll(); iter; ++iter) { + if (iter.isValueOn()) { + const Index32 value = Index32(pointsPerVolume + Index32(*iter)); + if (value == 0) leaf->setValueOff(iter.pos()); + else offset += value; + } + // @note can't use iter.setValue(offset) on point grids + leaf->setOffsetOnly(iter.pos(), offset); + } + + // offset should always be non zero + assert(offset != 0); + point_scatter_internal::generatePositions + (*leaf, descriptor, offset, spread, rand01); + } + + // if interrupted, remove remaining leaf nodes + if (leaf) { + for (; leaf; ++leaf) leaf->setValuesOff(); + tools::pruneInactive(tree); + } + + if (interrupter) interrupter->end(); + return points; +} + + +//////////////////////////////////////// + + +template< + typename GridT, + typename RandGenT, + typename PositionArrayT, + typename PointDataGridT, + typename InterrupterT> +inline typename PointDataGridT::Ptr +denseUniformPointScatter(const GridT& grid, + const float pointsPerVoxel, + const unsigned int seed, + const float spread, + InterrupterT* interrupter) +{ + using PositionType = typename PositionArrayT::ValueType; + using PositionTraits = VecTraits; + using ValueType = typename PositionTraits::ElementType; + using CodecType = typename PositionArrayT::Codec; + + using RandomGenerator = math::Rand01; + + using TreeType = typename PointDataGridT::TreeType; + + static_assert(PositionTraits::IsVec && PositionTraits::Size == 3, + "Invalid Position Array type."); + + if (pointsPerVoxel < 0.0f) { + OPENVDB_THROW(ValueError, "Points per voxel must not be less than zero."); + } + + if (spread < 0.0f || spread > 1.0f) { + OPENVDB_THROW(ValueError, "Spread must be between 0 and 1."); + } + + if (interrupter) interrupter->start("Dense uniform scattering with fixed point count"); + + typename PointDataGridT::Ptr points = + point_scatter_internal::initialisePointTopology(grid); + TreeType& tree = points->tree(); + auto leafIter = tree.beginLeaf(); + if (!leafIter) return points; + + const Index32 pointsPerVoxelInt = math::Floor(pointsPerVoxel); + const double delta = pointsPerVoxel - float(pointsPerVoxelInt); + const bool fractional = !math::isApproxZero(delta, 1.0e-6); + const bool fractionalOnly = pointsPerVoxelInt == 0; + + const AttributeSet::Descriptor::Ptr descriptor = + AttributeSet::Descriptor::create(PositionArrayT::attributeType()); + RandomGenerator rand01(seed); + + for (; leafIter; ++leafIter) { + if (util::wasInterrupted(interrupter)) break; + Index32 offset(0); + for (auto iter = leafIter->beginValueAll(); iter; ++iter) { + if (iter.isValueOn()) { + offset += pointsPerVoxelInt; + if (fractional && rand01() < delta) ++offset; + else if (fractionalOnly) leafIter->setValueOff(iter.pos()); + } + // @note can't use iter.setValue(offset) on point grids + leafIter->setOffsetOnly(iter.pos(), offset); + } + + if (offset != 0) { + point_scatter_internal::generatePositions + (*leafIter, descriptor, offset, spread, rand01); + } + } + + // if interrupted, remove remaining leaf nodes + const bool prune(leafIter || fractionalOnly); + for (; leafIter; ++leafIter) leafIter->setValuesOff(); + + if (prune) tools::pruneInactive(tree); + if (interrupter) interrupter->end(); + return points; +} + + +//////////////////////////////////////// + + +template< + typename GridT, + typename RandGenT, + typename PositionArrayT, + typename PointDataGridT, + typename InterrupterT> +inline typename PointDataGridT::Ptr +nonUniformPointScatter(const GridT& grid, + const float pointsPerVoxel, + const unsigned int seed, + const float spread, + InterrupterT* interrupter) +{ + using PositionType = typename PositionArrayT::ValueType; + using PositionTraits = VecTraits; + using ValueType = typename PositionTraits::ElementType; + using CodecType = typename PositionArrayT::Codec; + + using RandomGenerator = math::Rand01; + + using TreeType = typename PointDataGridT::TreeType; + + static_assert(PositionTraits::IsVec && PositionTraits::Size == 3, + "Invalid Position Array type."); + static_assert(std::is_arithmetic::value, + "Scalar grid type required for weighted voxel scattering."); + + if (pointsPerVoxel < 0.0f) { + OPENVDB_THROW(ValueError, "Points per voxel must not be less than zero."); + } + + if (spread < 0.0f || spread > 1.0f) { + OPENVDB_THROW(ValueError, "Spread must be between 0 and 1."); + } + + if (interrupter) interrupter->start("Non-uniform scattering with local point density"); + + typename PointDataGridT::Ptr points = + point_scatter_internal::initialisePointTopology(grid); + TreeType& tree = points->tree(); + auto leafIter = tree.beginLeaf(); + if (!leafIter) return points; + + const AttributeSet::Descriptor::Ptr descriptor = + AttributeSet::Descriptor::create(PositionArrayT::attributeType()); + RandomGenerator rand01(seed); + const auto accessor = grid.getConstAccessor(); + + for (; leafIter; ++leafIter) { + if (util::wasInterrupted(interrupter)) break; + Index32 offset(0); + for (auto iter = leafIter->beginValueAll(); iter; ++iter) { + if (iter.isValueOn()) { + double fractional = + double(accessor.getValue(iter.getCoord())) * pointsPerVoxel; + fractional = std::max(0.0, fractional); + int count = int(fractional); + if (rand01() < (fractional - double(count))) ++count; + else if (count == 0) leafIter->setValueOff(iter.pos()); + offset += count; + } + // @note can't use iter.setValue(offset) on point grids + leafIter->setOffsetOnly(iter.pos(), offset); + } + + if (offset != 0) { + point_scatter_internal::generatePositions + (*leafIter, descriptor, offset, spread, rand01); + } + } + + // if interrupted, remove remaining leaf nodes + for (; leafIter; ++leafIter) leafIter->setValuesOff(); + + tools::pruneInactive(points->tree()); + if (interrupter) interrupter->end(); + return points; +} + + +} // namespace points +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + + +#endif // OPENVDB_POINTS_POINT_SCATTER_IMPL_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/points/impl/PointStatisticsImpl.h b/openvdb/openvdb/points/impl/PointStatisticsImpl.h new file mode 100644 index 0000000000..9fad4b0f01 --- /dev/null +++ b/openvdb/openvdb/points/impl/PointStatisticsImpl.h @@ -0,0 +1,544 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 +// +/// @author Nick Avramoussis +/// +/// @file PointStatisticsImpl.h +/// + +#ifndef OPENVDB_POINTS_STATISTICS_IMPL_HAS_BEEN_INCLUDED +#define OPENVDB_POINTS_STATISTICS_IMPL_HAS_BEEN_INCLUDED + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace points { + +/// @cond OPENVDB_DOCS_INTERNAL + +namespace statistics_internal +{ + +/// @brief Scalar extent op to evaluate the min/max values of a +/// single integral or floating point attribute type +template +struct ScalarMinMax +{ + using ExtentT = std::pair; + ScalarMinMax(const ValueT& init) : mMinMax(init, init) {} + ScalarMinMax(const ExtentT& init) : mMinMax(init) {} + inline void operator()(const ValueT& b) + { + mMinMax.first = std::min(mMinMax.first, b); + mMinMax.second = std::max(mMinMax.second, b); + } + inline void operator()(const ExtentT& b) + { + mMinMax.first = std::min(mMinMax.first, b.first); + mMinMax.second = std::max(mMinMax.second, b.second); + } + inline const ExtentT& get() const { return mMinMax; } + ExtentT mMinMax; +}; + +/// @brief Vector squared magnitude op to evaluate the min/max of a +/// vector attribute and return the result as a scalar of the +/// appropriate precision +template +struct MagnitudeExtent + : public ScalarMinMax::ElementType> +{ + using ElementT = typename ValueTraits::ElementType; + using ExtentT = typename ScalarMinMax::ExtentT; + using BaseT = ScalarMinMax; + MagnitudeExtent(const VecT& init) : BaseT(init.lengthSqr()) {} + MagnitudeExtent(const ExtentT& init) : BaseT(init) {} + inline void operator()(const VecT& b) { this->BaseT::operator()(b.lengthSqr()); } + inline void operator()(const ExtentT& b) { this->BaseT::operator()(b); } +}; + +/// @brief Vector squared magnitude op to evaluate the min/max of a +/// vector attribute and return the result as the original vector +template +struct MagnitudeExtent +{ + using ElementT = typename ValueTraits::ElementType; + using ExtentT = std::pair; + MagnitudeExtent(const VecT& init) + : mLengths(), mMinMax(init, init) { + mLengths.first = init.lengthSqr(); + mLengths.second = mLengths.first; + } + MagnitudeExtent(const ExtentT& init) + : mLengths(), mMinMax(init) { + mLengths.first = init.first.lengthSqr(); + mLengths.second = init.second.lengthSqr(); + } + inline const ExtentT& get() const { return mMinMax; } + inline void operator()(const VecT& b) + { + const ElementT l = b.lengthSqr(); + if (l < mLengths.first) { + mLengths.first = l; + mMinMax.first = b; + } + else if (l > mLengths.second) { + mLengths.second = l; + mMinMax.second = b; + } + } + inline void operator()(const ExtentT& b) + { + ElementT l = b.first.lengthSqr(); + if (l < mLengths.first) { + mLengths.first = l; + mMinMax.first = b.first; + } + l = b.second.lengthSqr(); + if (l > mLengths.second) { + mLengths.second = l; + mMinMax.second = b.second; + } + } + + std::pair mLengths; + ExtentT mMinMax; +}; + +/// @brief Vector component-wise op to evaluate the min/max of +/// vector components and return the result as a vector of +/// equal size and precision +template +struct ComponentExtent +{ + using ExtentT = std::pair; + ComponentExtent(const VecT& init) : mMinMax(init, init) {} + ComponentExtent(const ExtentT& init) : mMinMax(init) {} + inline const ExtentT& get() const { return mMinMax; } + inline void operator()(const VecT& b) + { + mMinMax.first = math::minComponent(mMinMax.first, b); + mMinMax.second = math::maxComponent(mMinMax.second, b); + } + inline void operator()(const ExtentT& b) + { + mMinMax.first = math::minComponent(mMinMax.first, b.first); + mMinMax.second = math::maxComponent(mMinMax.second, b.second); + } + + ExtentT mMinMax; +}; + +template +bool evalExtents(const PointDataTreeT& points, + const std::string& attribute, + typename ExtentOp::ExtentT& ext, + const FilterT& filter, + typename PointDataTreeT::template ValueConverter + ::Type* const minTree = nullptr, + typename PointDataTreeT::template ValueConverter + ::Type* const maxTree = nullptr) +{ + static_assert(std::is_base_of::value, + "PointDataTreeT in instantiation of evalExtents is not an openvdb Tree type"); + + struct ResultType { + typename ExtentOp::ExtentT ext; + bool data = false; + }; + + tree::LeafManager manager(points); + if (manager.leafCount() == 0) return false; + const size_t idx = manager.leaf(0).attributeSet().find(attribute); + if (idx == AttributeSet::INVALID_POS) return false; + + // track results per leaf for min/max trees + std::vector> values; + if (minTree || maxTree) values.resize(manager.leafCount()); + + const ResultType result = tbb::parallel_reduce(manager.leafRange(), + ResultType(), + [idx, &filter, &values] + (const auto& range, ResultType in) -> ResultType + { + for (auto leaf = range.begin(); leaf; ++leaf) { + AttributeHandle handle(leaf->constAttributeArray(idx)); + if (handle.size() == 0) continue; + if (filter.state() == index::ALL) { + const size_t size = handle.isUniform() ? 1 : handle.size(); + ExtentOp op(handle.get(0)); + for (size_t i = 1; i < size; ++i) { + assert(i < size_t(std::numeric_limits::max())); + op(handle.get(Index(i))); + } + if (!values.empty()) { + values[leaf.pos()].reset(new typename ExtentOp::ExtentT(op.get())); + } + if (in.data) op(in.ext); + in.data = true; + in.ext = op.get(); + } + else { + auto iter = leaf->beginIndexOn(filter); + if (!iter) continue; + ExtentOp op(handle.get(*iter)); + ++iter; + for (; iter; ++iter) op(handle.get(*iter)); + if (!values.empty()) { + values[leaf.pos()].reset(new typename ExtentOp::ExtentT(op.get())); + } + if (in.data) op(in.ext); + in.data = true; + in.ext = op.get(); + } + } + + return in; + }, + [](const ResultType& a, const ResultType& b) -> ResultType { + if (!b.data) return a; + if (!a.data) return b; + ExtentOp op(a.ext); op(b.ext); + ResultType t; + t.ext = op.get(); + t.data = true; + return t; + }); + + // set minmax trees only if a new value was set - if the value + // hasn't changed, leave it as inactive background (this is + // only possible if a point leaf exists with no points or if a + // filter is provided but is not hit for a given leaf) + if (minTree || maxTree) { + manager.foreach([minTree, maxTree, &values] + (const auto& leaf, const size_t idx) { + const auto& v = values[idx]; + if (v == nullptr) return; + const Coord& origin = leaf.origin(); + if (minTree) minTree->addTile(1, origin, v->first, true); + if (maxTree) maxTree->addTile(1, origin, v->second, true); + }, false); + } + + if (result.data) ext = result.ext; + return result.data; +} + +template ::IsVec, int>::type = 0> +bool evalExtents(const PointDataTreeT& points, + const std::string& attribute, + ValueT& min, + ValueT& max, + const FilterT& filter, + typename PointDataTreeT::template ValueConverter::Type* minTree, + typename PointDataTreeT::template ValueConverter::Type* maxTree) +{ + typename ComponentExtent::ExtentT ext; + const bool s = evalExtents, PointDataTreeT> + (points, attribute, ext, filter, minTree, maxTree); + if (s) min = ext.first, max = ext.second; + return s; +} + +template ::IsVec, int>::type = 0> +bool evalExtents(const PointDataTreeT& points, + const std::string& attribute, + ValueT& min, + ValueT& max, + const FilterT& filter, + typename PointDataTreeT::template ValueConverter::Type* minTree, + typename PointDataTreeT::template ValueConverter::Type* maxTree) +{ + typename ScalarMinMax::ExtentT ext; + const bool s = evalExtents, PointDataTreeT> + (points, attribute, ext, filter, minTree, maxTree); + if (s) min = ext.first, max = ext.second; + return s; +} + +} // namespace statistics_internal + +/// @endcond + +template +bool evalMinMax(const PointDataTreeT& points, + const std::string& attribute, + ValueT& min, + ValueT& max, + const FilterT& filter, + typename PointDataTreeT::template ValueConverter::Type* minTree, + typename PointDataTreeT::template ValueConverter::Type* maxTree) +{ + return statistics_internal::evalExtents + (points, attribute, min, max, filter, minTree, maxTree); +} + +template +bool evalAverage(const PointDataTreeT& points, + const std::string& attribute, + typename ConvertElementType::Type& average, + const FilterT& filter, + typename PointDataTreeT::template ValueConverter::Type* averageTree) +{ + using ResultT = typename ConvertElementType::Type; + + struct Sample + { + Sample(const ResultT& _avg, size_t _size) : avg(_avg), size(_size) {} + + void add(const ResultT& val) + { + ++size; + const ResultT delta = val - avg; + avg = avg + (delta / static_cast(size)); + } + + void add(const Sample& other) + { + assert(other.size > 0); + const double denom = 1.0 / static_cast(size + other.size); + const ResultT delta = other.avg - avg; + avg = avg + (denom * delta * static_cast(other.size)); + size += other.size; + } + + ResultT avg; size_t size; + }; + + static_assert(std::is_base_of::value, + "PointDataTreeT in instantiation of evalAverage is not an openvdb Tree type"); + static_assert(std::is_constructible::value, + "Target value in points::evalAverage is not constructible from the source value type."); + + tree::LeafManager manager(points); + if (manager.leafCount() == 0) return false; + const size_t idx = manager.leaf(0).attributeSet().find(attribute); + if (idx == AttributeSet::INVALID_POS) return false; + + std::vector> values; + values.resize(manager.leafCount()); + tbb::parallel_for(manager.leafRange(), + [idx, &filter, &values] (const auto& range) { + for (auto leaf = range.begin(); leaf; ++leaf) { + AttributeHandle handle(leaf->constAttributeArray(idx)); + size_t size = handle.size(); + if (size == 0) continue; + if (filter.state() == index::ALL) { + std::unique_ptr S(new Sample(ResultT(handle.get(0)), 1)); + if (handle.isUniform()) { + S->avg = S->avg / static_cast(size); + S->size = size; + } + else { + for (size_t i = 1; i < size; ++i) { + assert(i < size_t(std::numeric_limits::max())); + S->add(ResultT(handle.get(Index(i)))); + } + } + values[leaf.pos()] = std::move(S); + } + else { + auto iter = leaf->beginIndexOn(filter); + if (!iter) continue; + std::unique_ptr S(new Sample(ResultT(handle.get(*iter)), 1)); + ++iter; + for (; iter; ++iter, ++size) { + S->add(ResultT(handle.get(*iter))); + } + values[leaf.pos()] = std::move(S); + } + } + }); + + auto iter = values.cbegin(); + while (iter != values.cend() && !(*iter)) ++iter; + if (iter == values.cend()) return false; + assert(*iter); + + // serial deterministic reduction of floating point samples + Sample S = **iter; + ++iter; + for (; iter != values.cend(); ++iter) { + if (*iter) S.add(**iter); + } + average = S.avg; + + // set average tree only if a new value was set - if the value + // hasn't changed, leave it as inactive background (this is + // only possible if a point leaf exists with no points or if a + // filter is provided but is not hit for a given leaf) + if (averageTree) { + manager.foreach([averageTree, &values] + (const auto& leaf, const size_t idx) { + const auto& S = values[idx]; + if (S == nullptr) return; + const Coord& origin = leaf.origin(); + averageTree->addTile(1, origin, S->avg, true); + }, false); + } + + return true; +} + +template +bool accumulate(const PointDataTreeT& points, + const std::string& attribute, + typename PromoteType::Highest& total, + const FilterT& filter, + typename PointDataTreeT::template ValueConverter::Type* totalTree) +{ + using ResultT = typename PromoteType::Highest; + using ElementT = typename ValueTraits::ElementType; + + static_assert(std::is_base_of::value, + "PointDataTreeT in instantiation of accumulate is not an openvdb Tree type"); + static_assert(std::is_constructible::value, + "Target value in points::accumulate is not constructible from the source value type."); + + tree::LeafManager manager(points); + if (manager.leafCount() == 0) return false; + const size_t idx = manager.leaf(0).attributeSet().find(attribute); + if (idx == AttributeSet::INVALID_POS) return false; + + std::vector> values; + values.resize(manager.leafCount()); + tbb::parallel_for(manager.leafRange(), + [idx, &filter, &values](const auto& range) { + for (auto leaf = range.begin(); leaf; ++leaf) { + AttributeHandle handle(leaf->constAttributeArray(idx)); + if (handle.size() == 0) continue; + if (filter.state() == index::ALL) { + const size_t size = handle.isUniform() ? 1 : handle.size(); + auto total = ResultT(handle.get(0)); + for (size_t i = 1; i < size; ++i) { + assert(i < size_t(std::numeric_limits::max())); + total += ResultT(handle.get(Index(i))); + } + values[leaf.pos()].reset(new ResultT(total)); + } + else { + auto iter = leaf->beginIndexOn(filter); + if (!iter) continue; + auto total = ResultT(handle.get(*iter)); + ++iter; + for (; iter; ++iter) total += ResultT(handle.get(*iter)); + values[leaf.pos()].reset(new ResultT(total)); + } + } + }); + + auto iter = values.cbegin(); + while (iter != values.cend() && !(*iter)) ++iter; + if (iter == values.cend()) return false; + assert(*iter); + total = **iter; ++iter; + + if (std::is_integral::value) { + using RangeT = tbb::blocked_range*>; + // reasonable grain size for accumulation of single to matrix types + total = tbb::parallel_reduce(RangeT(&(*iter), (&values.back())+1, 32), total, + [](const RangeT& range, ResultT p) -> ResultT { + for (const auto& r : range) if (r) p += *r; + return p; + }, std::plus()); + } + else { + for (; iter != values.cend(); ++iter) { + if (*iter) total += (**iter); + } + } + + // set total tree only if a new value was set - if the value + // hasn't changed, leave it as inactive background (this is + // only possible if a point leaf exists with no points or if a + // filter is provided but is not hit for a given leaf) + if (totalTree) { + manager.foreach([totalTree, &values] + (const auto& leaf, const size_t idx) { + const auto& v = values[idx]; + if (v == nullptr) return; + const Coord& origin = leaf.origin(); + totalTree->addTile(1, origin, *v, true); + }, false); + } + + return true; +} + +template +std::pair +evalMinMax(const PointDataTreeT& points, + const std::string& attribute, + const FilterT& filter) +{ + std::pair results { + zeroVal(), zeroVal() + }; + evalMinMax + (points, attribute, results.first, results.second, filter); + return results; +} + +template +typename ConvertElementType::Type +evalAverage(const PointDataTreeT& points, + const std::string& attribute, + const FilterT& filter) +{ + using ConvertedT = typename ConvertElementType::Type; + ConvertedT result = zeroVal(); + evalAverage(points, attribute, result, filter); + return result; +} + +template +typename PromoteType::Highest +accumulate(const PointDataTreeT& points, + const std::string& attribute, + const FilterT& filter) +{ + using PromotedT = typename PromoteType::Highest; + PromotedT result = zeroVal(); + accumulate(points, attribute, result, filter); + return result; +} + +} // namespace points +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_POINTS_STATISTICS_IMPL_HAS_BEEN_INCLUDED From 47952ca88524d4f3004f861ff3c6b48056763714 Mon Sep 17 00:00:00 2001 From: Andre Pradhana Date: Fri, 10 Mar 2023 18:14:09 -0800 Subject: [PATCH 06/17] Addresses warning/error: integer conversion resulted in truncation as reported and solved by C. Horvath. Signed-off-by: Andre Pradhana --- openvdb/openvdb/points/AttributeSet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvdb/openvdb/points/AttributeSet.h b/openvdb/openvdb/points/AttributeSet.h index 7cb1c9317d..50a9a56a9c 100644 --- a/openvdb/openvdb/points/AttributeSet.h +++ b/openvdb/openvdb/points/AttributeSet.h @@ -38,7 +38,7 @@ using GroupType = uint8_t; class OPENVDB_API AttributeSet { public: - enum { INVALID_POS = std::numeric_limits::max() }; + enum AttributePositionLabel : size_t { INVALID_POS = std::numeric_limits::max() }; using Ptr = std::shared_ptr; using ConstPtr = std::shared_ptr; From 27e821f9024f85a506d6981ccbf8b1e66cbb2889 Mon Sep 17 00:00:00 2001 From: Jeff Lait Date: Tue, 4 Apr 2023 16:37:33 -0400 Subject: [PATCH 07/17] Fix off by one when initializing old-style uuids. Signed-off-by: Jeff Lait --- openvdb/openvdb/io/Archive.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvdb/openvdb/io/Archive.cc b/openvdb/openvdb/io/Archive.cc index 6c763c9d2b..905ebe7977 100644 --- a/openvdb/openvdb/io/Archive.cc +++ b/openvdb/openvdb/io/Archive.cc @@ -1038,7 +1038,7 @@ Archive::readHeader(std::istream& is) uuidStr[i*2] = to_hex(uuidBytes[i] >> 4); uuidStr[i*2+1] = to_hex(uuidBytes[i]); } - uuidStr[33] = 0; + uuidStr[32] = 0; mUuid = uuidStr; } From b70115f70a3c93beac3052add2c39334948c8b59 Mon Sep 17 00:00:00 2001 From: Jeff Lait Date: Tue, 4 Apr 2023 16:40:12 -0400 Subject: [PATCH 08/17] Add release notes for the change. Signed-off-by: Jeff Lait --- pendingchanges/remove_boost_uuid.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pendingchanges/remove_boost_uuid.txt diff --git a/pendingchanges/remove_boost_uuid.txt b/pendingchanges/remove_boost_uuid.txt new file mode 100644 index 0000000000..7554087119 --- /dev/null +++ b/pendingchanges/remove_boost_uuid.txt @@ -0,0 +1,3 @@ +Improvements: +- boost::uuid removed from Archive, instead std::random_device is used + directly to generate UUID-like objects. From cf38e7144136bda470ac6ce441fa897962694189 Mon Sep 17 00:00:00 2001 From: Jeff Lait Date: Wed, 5 Apr 2023 14:09:46 -0400 Subject: [PATCH 09/17] Add explicit (char) cast to output to silence narrowing warnings. Signed-off-by: Jeff Lait --- openvdb/openvdb/io/Archive.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvdb/openvdb/io/Archive.cc b/openvdb/openvdb/io/Archive.cc index 905ebe7977..a85a2398df 100644 --- a/openvdb/openvdb/io/Archive.cc +++ b/openvdb/openvdb/io/Archive.cc @@ -1079,8 +1079,8 @@ Archive::writeHeader(std::ostream& os, bool seekable) const auto to_hex = [](unsigned int c) -> char { c &= 0xf; - if (c < 10) return '0' + c; - return c - 10 + 'A'; + if (c < 10) return (char)('0' + c); + return (char)(c - 10 + 'A'); }; try From 7c29811be4bdfd776ca862615c7c3e0db5e10c7d Mon Sep 17 00:00:00 2001 From: Jeff Lait Date: Tue, 11 Apr 2023 12:00:37 -0400 Subject: [PATCH 10/17] More casting to make char -> int conversion happy. Signed-off-by: Jeff Lait --- openvdb/openvdb/io/Archive.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvdb/openvdb/io/Archive.cc b/openvdb/openvdb/io/Archive.cc index a85a2398df..d795870511 100644 --- a/openvdb/openvdb/io/Archive.cc +++ b/openvdb/openvdb/io/Archive.cc @@ -1030,8 +1030,8 @@ Archive::readHeader(std::istream& is) auto to_hex = [](char c) -> char { c &= 0xf; - if (c < 10) return '0' + c; - return c - 10 + 'A'; + if (c < 10) return (char)('0' + c); + return (char)(c - 10 + 'A'); }; for (int i = 0; i < 16; i++) { From 3c2ee3d82d261b8b11882b3d2d6108cb4e52fcc5 Mon Sep 17 00:00:00 2001 From: Jeff Lait Date: Tue, 11 Apr 2023 16:13:44 -0400 Subject: [PATCH 11/17] Make the to_hex take an unsigned int in both cases as shifting right will promote char anyways. Signed-off-by: Jeff Lait --- openvdb/openvdb/io/Archive.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvdb/openvdb/io/Archive.cc b/openvdb/openvdb/io/Archive.cc index d795870511..ffb6f57726 100644 --- a/openvdb/openvdb/io/Archive.cc +++ b/openvdb/openvdb/io/Archive.cc @@ -1027,7 +1027,7 @@ Archive::readHeader(std::istream& is) char uuidBytes[16]; is.read(uuidBytes, 16); char uuidStr[33]; - auto to_hex = [](char c) -> char + auto to_hex = [](unsigned int c) -> char { c &= 0xf; if (c < 10) return (char)('0' + c); From 18b4f69ad6d34b765ea629b07aa8bb911e95155e Mon Sep 17 00:00:00 2001 From: Andre Pradhana Date: Tue, 2 May 2023 10:44:26 -0700 Subject: [PATCH 12/17] Add TSC Meeting Notes April 18, 2023. Signed-off-by: Andre Pradhana --- tsc/meetings/2023-04-18.md | 89 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tsc/meetings/2023-04-18.md diff --git a/tsc/meetings/2023-04-18.md b/tsc/meetings/2023-04-18.md new file mode 100644 index 0000000000..1325fe9b4b --- /dev/null +++ b/tsc/meetings/2023-04-18.md @@ -0,0 +1,89 @@ +Minutes from 167th OpenVDB TSC meeting, April 18th, 2023 + +Attendees: *Jeff* L., *Dan* B., *Ken* M., *Dan* B., *Rich* J, +*Andre* P. + +Additional Attendees: Sebastian Gaida + +Regrets: *Nick* A., *Greg* H. + +Agenda: + +1) Confirm quorum +2) Secretary +3) Forum +4) PR-1606: Boost UUID +5) VFX Reference Platform +6) Boost::any PR +7) Discussion on read-only data structure +8) VDB 11 +9) Next Meeting + +------------ + +1) Confirm quorum + +Quorum is present. + +2) Secretary + +Secretary is Andre Pradhana. + +3) Forum + +Nothing to discuss. + +4) PR-1606: Boost UUID + +It compiles. We agree to review the PR. + +5) VFX Reference Platform + +VFX Reference Platform asks for a release draft for 2024. We +agree to release VDB 11.0 in November. Our plan will be communicated +to VFX Reference Platform to confirm that we are moving to VDB 11 later +in the year. + +6) Boost::any PR + +Once this PR is approved, OpenVDB will optionally depend on boost. It makes +sense to have a lite-CI for OpenVDB that does not support delayed loading +nor depends on blosc. + +7) Discussion on read-only data structure + +Ideas discussed are: + - Having a read-only tree in OpenVDB, for example utilizing a ring + buffer, which replaces pages instead of loading new pages. The + argument against adding a ring-buffer based grid is that the OS + can do it for us if we use delayed/partial loading. However, this + approach makes the OS to do the swapping. This approach is similar + to NanoVDB. The advantage of it is that a user can define the allocated + memory footprint upfront. + - A refinement of the idea above is to store the evicted data locally. + - Another approach discussed is by reading/loading the data per- + bouding-box. + - Build a prefectching tools. + +Other points raised includes: + - The need to trim down the core code-base, so that it is easier + to add a new grid. Currently, there are a lot of virtual methods + and expected functions that are required to build a new grid type. + - In terms of workflow, Houdini uses viewport to control which + data should be loaded in memory. + - People read-only tree for rendering. + - Talked about strategy to parallelize an algorithm that delay-load + leaf nodes using mutex locks. + +8) VDB 11 +What can go to VDB 11: + - Updates to NanoVDB. + - Offset to recenter root node. + - Hollow VDB. + - VDB Merge needs to work for packed primitives. + - Example for fluids (due for SIGGRAPH). + - VDB Fillet. + +9) Next Meeting + +Next meeting is on April 25th, 2023. 2pm-3pm EDT (GMT-4). From d3d9e1bcdb3f79d0296c97741270ad3f7e4bc695 Mon Sep 17 00:00:00 2001 From: Jeff Lait Date: Tue, 2 May 2023 15:31:59 -0400 Subject: [PATCH 13/17] TSC Meeting notes. Signed-off-by: Jeff Lait --- tsc/meetings/2023-04-25.md | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tsc/meetings/2023-04-25.md diff --git a/tsc/meetings/2023-04-25.md b/tsc/meetings/2023-04-25.md new file mode 100644 index 0000000000..8d9f6c0657 --- /dev/null +++ b/tsc/meetings/2023-04-25.md @@ -0,0 +1,52 @@ +Minutes from 168th OpenVDB TSC meeting, April 25th, 2023 + +Attendees: *Jeff* L., *Andre* P, *Dan* B., *Rich* J. + +Regrets: +Attendees: *Ken* M., *Nick* A., *Greg* H. + +Additional Attendees: None + +Agenda: + +1) Confirm quorum +2) Secretary +3) Forum +4) Python Bindings +5) Hollow VDB +6) UUID Update +7) ASWF Annual Meeting +8) VDB Merge Max +9) Next meeting + +------------ + +1) Confirm quorum + +Quorum is present. + +2) Secretary + +Secretary is Jeff Lait. + +3) Forum + +OpenVDB added to Conda Forge. Asked if we want to be involved as +maintainers. We appreciate the work but do not want to maintain +things we are not directly involved with. + +VDB View was reported not working on Windows. This is likely not +surprising. In the past this sort of error was from missing some +environment that could be solved with a .bat. If we can reproduce we +can reply to that effect. Finding TBB was the problem with vdb_tool. +CMake is trying to pick up GLU libraries. + +4) Boost UUID + +Are we able to merge this? This has to look like a UUID as the old +code had to load it. We reviewed this and approved during the +meeting. + +5) Next meeting + +Next meeting is on May 2nd, 2023. 2pm-3pm EDT. From 0cf46fb90fbde601c61c47e9b002d03bce3437de Mon Sep 17 00:00:00 2001 From: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> Date: Sat, 25 Feb 2023 16:42:45 +0000 Subject: [PATCH 14/17] Fixed a number of global constructor issues which could cause SIOF problems Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> --- cmake/config/OpenVDBCXX.cmake | 4 ++ openvdb/openvdb/CMakeLists.txt | 1 - openvdb/openvdb/io/Archive.cc | 69 ++++++++++++----------- openvdb/openvdb/math/Coord.h | 4 +- openvdb/openvdb/math/Maps.cc | 49 +++++++--------- openvdb/openvdb/math/Maps.h | 3 - openvdb/openvdb/openvdb.cc | 12 ++-- openvdb/openvdb/points/AttributeArray.h | 14 ++--- openvdb/openvdb/tree/LeafBuffer.h | 7 +-- openvdb/openvdb/unittest/TestLaplacian.cc | 1 - openvdb/openvdb/util/Util.cc | 46 --------------- openvdb/openvdb/util/Util.h | 34 ++++++++++- openvdb_ax/openvdb_ax/ast/Parse.cc | 9 ++- openvdb_ax/openvdb_ax/ax.cc | 13 +++-- pendingchanges/siof.txt | 3 + 15 files changed, 125 insertions(+), 144 deletions(-) delete mode 100644 openvdb/openvdb/util/Util.cc create mode 100644 pendingchanges/siof.txt diff --git a/cmake/config/OpenVDBCXX.cmake b/cmake/config/OpenVDBCXX.cmake index a1303e7dcc..9560417800 100644 --- a/cmake/config/OpenVDBCXX.cmake +++ b/cmake/config/OpenVDBCXX.cmake @@ -178,6 +178,10 @@ if(OPENVDB_CXX_STRICT) add_compile_options("$<$:-Wall>") add_compile_options("$<$:-Wextra>") add_compile_options("$<$:-Wconversion>") + # Only check global constructors for libraries (we should really check for + # executables too but gtest relies on these types of constructors for its + # framework). + add_compile_options("$<$,EXECUTABLE>>,$>:-Wglobal-constructors>") add_compile_options("$<$:-Wno-sign-conversion>") # GNU add_compile_options("$<$:-Werror>") diff --git a/openvdb/openvdb/CMakeLists.txt b/openvdb/openvdb/CMakeLists.txt index 79ffebc83d..1540e06b06 100644 --- a/openvdb/openvdb/CMakeLists.txt +++ b/openvdb/openvdb/CMakeLists.txt @@ -387,7 +387,6 @@ set(OPENVDB_LIBRARY_SOURCE_FILES points/StreamCompression.cc points/points.cc util/Formats.cc - util/Util.cc ) set(OPENVDB_LIBRARY_INCLUDE_FILES diff --git a/openvdb/openvdb/io/Archive.cc b/openvdb/openvdb/io/Archive.cc index ffb6f57726..cedcecffa3 100644 --- a/openvdb/openvdb/io/Archive.cc +++ b/openvdb/openvdb/io/Archive.cc @@ -99,8 +99,13 @@ struct StreamState int halfFloat; int mappedFile; int metadata; +}; + +inline StreamState& GetSteamState() +{ + static StreamState sStreamState; + return sStreamState; } -sStreamState; const long StreamState::MAGIC_NUMBER = long((uint64_t(OPENVDB_MAGIC) << 32) | (uint64_t(OPENVDB_MAGIC))); @@ -654,14 +659,14 @@ uint32_t getFormatVersion(std::ios_base& is) { /// @todo get from StreamMetadata - return static_cast(is.iword(sStreamState.fileVersion)); + return static_cast(is.iword(GetSteamState().fileVersion)); } void Archive::setFormatVersion(std::istream& is) { - is.iword(sStreamState.fileVersion) = mFileVersion; ///< @todo remove + is.iword(GetSteamState().fileVersion) = mFileVersion; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(is)) { meta->setFileVersion(mFileVersion); } @@ -673,8 +678,8 @@ 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)); + version.first = static_cast(is.iword(GetSteamState().libraryMajorVersion)); + version.second = static_cast(is.iword(GetSteamState().libraryMinorVersion)); return version; } @@ -682,8 +687,8 @@ getLibraryVersion(std::ios_base& is) void Archive::setLibraryVersion(std::istream& is) { - is.iword(sStreamState.libraryMajorVersion) = mLibraryVersion.first; ///< @todo remove - is.iword(sStreamState.libraryMinorVersion) = mLibraryVersion.second; ///< @todo remove + is.iword(GetSteamState().libraryMajorVersion) = mLibraryVersion.first; ///< @todo remove + is.iword(GetSteamState().libraryMinorVersion) = mLibraryVersion.second; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(is)) { meta->setLibraryVersion(mLibraryVersion); } @@ -703,9 +708,9 @@ getVersion(std::ios_base& is) 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 + is.iword(GetSteamState().fileVersion) = OPENVDB_FILE_VERSION; ///< @todo remove + is.iword(GetSteamState().libraryMajorVersion) = OPENVDB_LIBRARY_MAJOR_VERSION; ///< @todo remove + is.iword(GetSteamState().libraryMinorVersion) = OPENVDB_LIBRARY_MINOR_VERSION; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(is)) { meta->setFileVersion(OPENVDB_FILE_VERSION); meta->setLibraryVersion(VersionId( @@ -717,9 +722,9 @@ setCurrentVersion(std::istream& is) 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 + strm.iword(GetSteamState().fileVersion) = fileVersion; ///< @todo remove + strm.iword(GetSteamState().libraryMajorVersion) = libraryVersion.first; ///< @todo remove + strm.iword(GetSteamState().libraryMinorVersion) = libraryVersion.second; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setFileVersion(fileVersion); meta->setLibraryVersion(libraryVersion); @@ -743,14 +748,14 @@ uint32_t getDataCompression(std::ios_base& strm) { /// @todo get from StreamMetadata - return uint32_t(strm.iword(sStreamState.dataCompression)); + return uint32_t(strm.iword(GetSteamState().dataCompression)); } void setDataCompression(std::ios_base& strm, uint32_t c) { - strm.iword(sStreamState.dataCompression) = c; ///< @todo remove + strm.iword(GetSteamState().dataCompression) = c; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setCompression(c); } @@ -832,14 +837,14 @@ bool getWriteGridStatsMetadata(std::ios_base& strm) { /// @todo get from StreamMetadata - return strm.iword(sStreamState.writeGridStatsMetadata) != 0; + return strm.iword(GetSteamState().writeGridStatsMetadata) != 0; } void setWriteGridStatsMetadata(std::ios_base& strm, bool writeGridStats) { - strm.iword(sStreamState.writeGridStatsMetadata) = writeGridStats; ///< @todo remove + strm.iword(GetSteamState().writeGridStatsMetadata) = writeGridStats; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setWriteGridStats(writeGridStats); } @@ -853,7 +858,7 @@ uint32_t getGridClass(std::ios_base& strm) { /// @todo get from StreamMetadata - const uint32_t val = static_cast(strm.iword(sStreamState.gridClass)); + const uint32_t val = static_cast(strm.iword(GetSteamState().gridClass)); if (val >= NUM_GRID_CLASSES) return GRID_UNKNOWN; return val; } @@ -862,7 +867,7 @@ getGridClass(std::ios_base& strm) void setGridClass(std::ios_base& strm, uint32_t cls) { - strm.iword(sStreamState.gridClass) = long(cls); ///< @todo remove + strm.iword(GetSteamState().gridClass) = long(cls); ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setGridClass(cls); } @@ -873,14 +878,14 @@ bool getHalfFloat(std::ios_base& strm) { /// @todo get from StreamMetadata - return strm.iword(sStreamState.halfFloat) != 0; + return strm.iword(GetSteamState().halfFloat) != 0; } void setHalfFloat(std::ios_base& strm, bool halfFloat) { - strm.iword(sStreamState.halfFloat) = halfFloat; ///< @todo remove + strm.iword(GetSteamState().halfFloat) = halfFloat; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setHalfFloat(halfFloat); } @@ -891,14 +896,14 @@ const void* getGridBackgroundValuePtr(std::ios_base& strm) { /// @todo get from StreamMetadata - return strm.pword(sStreamState.gridBackground); + return strm.pword(GetSteamState().gridBackground); } void setGridBackgroundValuePtr(std::ios_base& strm, const void* background) { - strm.pword(sStreamState.gridBackground) = const_cast(background); ///< @todo remove + strm.pword(GetSteamState().gridBackground) = const_cast(background); ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setBackgroundPtr(background); } @@ -909,7 +914,7 @@ setGridBackgroundValuePtr(std::ios_base& strm, const void* background) MappedFile::Ptr getMappedFilePtr(std::ios_base& strm) { - if (const void* ptr = strm.pword(sStreamState.mappedFile)) { + if (const void* ptr = strm.pword(GetSteamState().mappedFile)) { return *static_cast(ptr); } return MappedFile::Ptr(); @@ -919,7 +924,7 @@ getMappedFilePtr(std::ios_base& strm) void setMappedFilePtr(std::ios_base& strm, io::MappedFile::Ptr& mappedFile) { - strm.pword(sStreamState.mappedFile) = &mappedFile; + strm.pword(GetSteamState().mappedFile) = &mappedFile; } #endif // OPENVDB_USE_DELAYED_LOADING @@ -927,7 +932,7 @@ setMappedFilePtr(std::ios_base& strm, io::MappedFile::Ptr& mappedFile) StreamMetadata::Ptr getStreamMetadataPtr(std::ios_base& strm) { - if (const void* ptr = strm.pword(sStreamState.metadata)) { + if (const void* ptr = strm.pword(GetSteamState().metadata)) { return *static_cast(ptr); } return StreamMetadata::Ptr(); @@ -937,7 +942,7 @@ getStreamMetadataPtr(std::ios_base& strm) void setStreamMetadataPtr(std::ios_base& strm, StreamMetadata::Ptr& meta, bool transfer) { - strm.pword(sStreamState.metadata) = &meta; + strm.pword(GetSteamState().metadata) = &meta; if (transfer && meta) meta->transferTo(strm); } @@ -946,7 +951,7 @@ StreamMetadata::Ptr clearStreamMetadataPtr(std::ios_base& strm) { StreamMetadata::Ptr result = getStreamMetadataPtr(strm); - strm.pword(sStreamState.metadata) = nullptr; + strm.pword(GetSteamState().metadata) = nullptr; return result; } @@ -1201,8 +1206,8 @@ doReadGrid(GridBase::Ptr grid, const GridDescriptor& gd, std::istream& is, const // 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; } + OnExit(std::ios_base& strm_): strm(&strm_), ptr(strm_.pword(GetSteamState().metadata)) {} + ~OnExit() { strm->pword(GetSteamState().metadata) = ptr; } std::ios_base* strm; void* ptr; }; @@ -1426,8 +1431,8 @@ Archive::writeGrid(GridDescriptor& gd, GridBase::ConstPtr grid, { // 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; } + OnExit(std::ios_base& strm_): strm(&strm_), ptr(strm_.pword(GetSteamState().metadata)) {} + ~OnExit() { strm->pword(GetSteamState().metadata) = ptr; } std::ios_base* strm; void* ptr; }; diff --git a/openvdb/openvdb/math/Coord.h b/openvdb/openvdb/math/Coord.h index c688e4fcb8..54d3294355 100644 --- a/openvdb/openvdb/math/Coord.h +++ b/openvdb/openvdb/math/Coord.h @@ -33,8 +33,8 @@ class Coord using Limits = std::numeric_limits; Coord(): mVec{{0, 0, 0}} {} - explicit Coord(Int32 xyz): mVec{{xyz, xyz, xyz}} {} - Coord(Int32 x, Int32 y, Int32 z): mVec{{x, y, z}} {} + constexpr explicit Coord(Int32 xyz): mVec{{xyz, xyz, xyz}} {} + constexpr Coord(Int32 x, Int32 y, Int32 z): mVec{{x, y, z}} {} explicit Coord(const Vec3i& v): mVec{{v[0], v[1], v[2]}} {} explicit Coord(const Vec3I& v): mVec{{Int32(v[0]), Int32(v[1]), Int32(v[2])}} {} explicit Coord(const Int32* v): mVec{{v[0], v[1], v[2]}} {} diff --git a/openvdb/openvdb/math/Maps.cc b/openvdb/openvdb/math/Maps.cc index f6e4771f53..3ee74249cd 100644 --- a/openvdb/openvdb/math/Maps.cc +++ b/openvdb/openvdb/math/Maps.cc @@ -10,42 +10,33 @@ namespace OPENVDB_VERSION_NAME { namespace math { namespace { - -// 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! -std::mutex sInitMapRegistryMutex; - +inline std::mutex& GetMapRegistryMutex() +{ + static std::mutex sInitMapRegistryMutex; + return sInitMapRegistryMutex; +}; } // unnamed namespace //////////////////////////////////////// -// Caller is responsible for calling this function serially. MapRegistry* -MapRegistry::staticInstance() +MapRegistry::instance() { static MapRegistry registry; return ®istry; } -MapRegistry* -MapRegistry::instance() -{ - std::lock_guard lock(sInitMapRegistryMutex); - return staticInstance(); -} - - MapBase::Ptr MapRegistry::createMap(const Name& name) { - std::lock_guard lock(sInitMapRegistryMutex); - MapDictionary::const_iterator iter = staticInstance()->mMap.find(name); + std::lock_guard lock(GetMapRegistryMutex()); + auto* reg = MapRegistry::instance(); + MapDictionary::const_iterator iter = reg->mMap.find(name); - if (iter == staticInstance()->mMap.end()) { + if (iter == reg->mMap.end()) { OPENVDB_THROW(LookupError, "Cannot create map of unregistered type " << name); } @@ -56,37 +47,39 @@ MapRegistry::createMap(const Name& name) bool MapRegistry::isRegistered(const Name& name) { - std::lock_guard lock(sInitMapRegistryMutex); - return (staticInstance()->mMap.find(name) != staticInstance()->mMap.end()); + std::lock_guard lock(GetMapRegistryMutex()); + const auto* reg = MapRegistry::instance(); + return (reg->mMap.find(name) != reg->mMap.end()); } void MapRegistry::registerMap(const Name& name, MapBase::MapFactory factory) { - std::lock_guard lock(sInitMapRegistryMutex); + std::lock_guard lock(GetMapRegistryMutex()); + auto* reg = MapRegistry::instance(); - if (staticInstance()->mMap.find(name) != staticInstance()->mMap.end()) { + if (reg->mMap.find(name) != reg->mMap.end()) { OPENVDB_THROW(KeyError, "Map type " << name << " is already registered"); } - staticInstance()->mMap[name] = factory; + reg->mMap[name] = factory; } void MapRegistry::unregisterMap(const Name& name) { - std::lock_guard lock(sInitMapRegistryMutex); - staticInstance()->mMap.erase(name); + std::lock_guard lock(GetMapRegistryMutex()); + MapRegistry::instance()->mMap.erase(name); } void MapRegistry::clear() { - std::lock_guard lock(sInitMapRegistryMutex); - staticInstance()->mMap.clear(); + std::lock_guard lock(GetMapRegistryMutex()); + MapRegistry::instance()->mMap.clear(); } diff --git a/openvdb/openvdb/math/Maps.h b/openvdb/openvdb/math/Maps.h index 0b6e3b2b0c..707ae8413c 100644 --- a/openvdb/openvdb/math/Maps.h +++ b/openvdb/openvdb/math/Maps.h @@ -283,9 +283,6 @@ class OPENVDB_API MapRegistry private: MapRegistry() {} - - static MapRegistry* staticInstance(); - MapDictionary mMap; }; diff --git a/openvdb/openvdb/openvdb.cc b/openvdb/openvdb/openvdb.cc index 59f09be328..dfcd4a2618 100644 --- a/openvdb/openvdb/openvdb.cc +++ b/openvdb/openvdb/openvdb.cc @@ -47,8 +47,12 @@ OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace { -// Declare this at file scope to ensure thread-safe initialization. -std::mutex sInitMutex; +inline std::mutex& GetInitMutex() +{ + static std::mutex sInitMutex; + return sInitMutex; +}; + std::atomic sIsInitialized{false}; } @@ -61,7 +65,7 @@ void initialize() { if (sIsInitialized.load(std::memory_order_acquire)) return; - std::lock_guard lock(sInitMutex); + std::lock_guard lock(GetInitMutex()); if (sIsInitialized.load(std::memory_order_acquire)) return; // Double-checked lock logging::initialize(); @@ -110,7 +114,7 @@ __pragma(warning(default:1711)) void uninitialize() { - std::lock_guard lock(sInitMutex); + std::lock_guard lock(GetInitMutex()); #ifdef __ICC // Disable ICC "assignment to statically allocated variable" warning. // This assignment is mutex-protected and therefore thread-safe. diff --git a/openvdb/openvdb/points/AttributeArray.h b/openvdb/openvdb/points/AttributeArray.h index 467a1a4890..f363a6d8b1 100644 --- a/openvdb/openvdb/points/AttributeArray.h +++ b/openvdb/openvdb/points/AttributeArray.h @@ -819,7 +819,6 @@ class TypedAttributeArray final: public AttributeArray return TypedAttributeArray::create(n, strideOrTotalSize, constantStride, metadata); } - static std::unique_ptr sTypeName; std::unique_ptr mData; Index mSize; Index mStrideOrTotalSize; @@ -1125,9 +1124,6 @@ void AttributeArray::copyValues(const AttributeArray& sourceArray, const IterT& // TypedAttributeArray implementation -template -std::unique_ptr TypedAttributeArray::sTypeName; - template TypedAttributeArray::TypedAttributeArray( @@ -1209,12 +1205,10 @@ template inline const NamePair& TypedAttributeArray::attributeType() { - static std::once_flag once; - std::call_once(once, []() - { - sTypeName.reset(new NamePair(typeNameAsString(), Codec::name())); - }); - return *sTypeName; + static NamePair sTypeName = []() { + return NamePair(typeNameAsString(), Codec::name()); + }(); + return sTypeName; } diff --git a/openvdb/openvdb/tree/LeafBuffer.h b/openvdb/openvdb/tree/LeafBuffer.h index 54383cd163..e685b733a3 100644 --- a/openvdb/openvdb/tree/LeafBuffer.h +++ b/openvdb/openvdb/tree/LeafBuffer.h @@ -164,8 +164,6 @@ class LeafBuffer tbb::spin_mutex mMutex; // 1 byte //int8_t mReserved[3]; // padding for alignment - static const ValueType sZero; - friend class ::TestLeaf; // Allow the parent LeafNode to access this buffer's data pointer. template friend class LeafNode; @@ -175,10 +173,6 @@ class LeafBuffer //////////////////////////////////////// -template -const T LeafBuffer::sZero = zeroVal(); - - template inline LeafBuffer::LeafBuffer(const ValueType& val) @@ -383,6 +377,7 @@ template inline const typename LeafBuffer::ValueType& LeafBuffer::at(Index i) const { + static const ValueType sZero = zeroVal(); assert(i < SIZE); this->loadValues(); // We can't use the ternary operator here, otherwise Visual C++ returns diff --git a/openvdb/openvdb/unittest/TestLaplacian.cc b/openvdb/openvdb/unittest/TestLaplacian.cc index 5413e2f891..56a16e3fc2 100644 --- a/openvdb/openvdb/unittest/TestLaplacian.cc +++ b/openvdb/openvdb/unittest/TestLaplacian.cc @@ -322,7 +322,6 @@ TEST_F(TestLaplacian, testOldStyleStencils) EXPECT_TRUE(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); diff --git a/openvdb/openvdb/util/Util.cc b/openvdb/openvdb/util/Util.cc deleted file mode 100644 index ec56bf80da..0000000000 --- a/openvdb/openvdb/util/Util.cc +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright Contributors to the OpenVDB Project -// SPDX-License-Identifier: MPL-2.0 - -#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 diff --git a/openvdb/openvdb/util/Util.h b/openvdb/openvdb/util/Util.h index b7eaee3bbb..7344269ab7 100644 --- a/openvdb/openvdb/util/Util.h +++ b/openvdb/openvdb/util/Util.h @@ -9,17 +9,45 @@ #include #include // for tree::pruneInactive +#include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { -OPENVDB_API extern const Index32 INVALID_IDX; +inline constexpr Index32 INVALID_IDX = std::numeric_limits::max(); /// @brief coordinate offset table for neighboring voxels -OPENVDB_API extern const Coord COORD_OFFSETS[26]; - +inline constexpr 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) +}; //////////////////////////////////////// diff --git a/openvdb_ax/openvdb_ax/ast/Parse.cc b/openvdb_ax/openvdb_ax/ast/Parse.cc index a71fda1325..1e6c8e12f9 100644 --- a/openvdb_ax/openvdb_ax/ast/Parse.cc +++ b/openvdb_ax/openvdb_ax/ast/Parse.cc @@ -19,8 +19,11 @@ #include namespace { -// Declare this at file scope to ensure thread-safe initialization. -std::mutex sInitMutex; +inline std::mutex& GetInitMutex() +{ + static std::mutex sInitMutex; + return sInitMutex; +}; } openvdb::ax::Logger* axlog = nullptr; @@ -39,7 +42,7 @@ openvdb::ax::ast::Tree::ConstPtr openvdb::ax::ast::parse(const char* code, openvdb::ax::Logger& logger) { - std::lock_guard lock(sInitMutex); + std::lock_guard lock(GetInitMutex()); axlog = &logger; // for lexer errs logger.setSourceCode(code); diff --git a/openvdb_ax/openvdb_ax/ax.cc b/openvdb_ax/openvdb_ax/ax.cc index 6d12780fad..50f8f73d19 100644 --- a/openvdb_ax/openvdb_ax/ax.cc +++ b/openvdb_ax/openvdb_ax/ax.cc @@ -108,21 +108,24 @@ void run(const char* ax, openvdb::GridPtrVec& grids, const AttributeBindings& bi } namespace { -// Declare this at file scope to ensure thread-safe initialization. -std::mutex sInitMutex; +inline std::mutex& GetInitMutex() +{ + static std::mutex sInitMutex; + return sInitMutex; +}; bool sIsInitialized = false; bool sShutdown = false; } bool isInitialized() { - std::lock_guard lock(sInitMutex); + std::lock_guard lock(GetInitMutex()); return sIsInitialized; } void initialize() { - std::lock_guard lock(sInitMutex); + std::lock_guard lock(GetInitMutex()); if (sIsInitialized) return; if (sShutdown) { @@ -177,7 +180,7 @@ void initialize() void uninitialize() { - std::lock_guard lock(sInitMutex); + std::lock_guard lock(GetInitMutex()); if (!sIsInitialized) return; // @todo consider replacing with storage to Support/InitLLVM diff --git a/pendingchanges/siof.txt b/pendingchanges/siof.txt new file mode 100644 index 0000000000..6e18b0a669 --- /dev/null +++ b/pendingchanges/siof.txt @@ -0,0 +1,3 @@ +OpenVDB: + Improvements: + - Moved all cases of file static/global variables which relied on non-trivial construction into function scopes as static locals. These would previously initialize themselves on start-up in a non deterministic, compiler dictated order (static-initialization-order-fiasco). This order is now defined by the program's execution. From e0bc5f46209f9d1e311f824d2f9b06db70555cbf Mon Sep 17 00:00:00 2001 From: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> Date: Thu, 25 May 2023 22:16:54 +1200 Subject: [PATCH 15/17] Enabled some more useful compiler warnings around fallthrough switches and missing virtual methods Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> --- cmake/config/OpenVDBCXX.cmake | 5 +++++ openvdb/openvdb/tools/ParticlesToLevelSet.h | 2 +- openvdb/openvdb/unittest/TestFindActiveValues.cc | 8 ++++---- openvdb_cmd/vdb_ax/cli.h | 13 +++++++++++++ 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/cmake/config/OpenVDBCXX.cmake b/cmake/config/OpenVDBCXX.cmake index 9560417800..5ad5d18725 100644 --- a/cmake/config/OpenVDBCXX.cmake +++ b/cmake/config/OpenVDBCXX.cmake @@ -178,6 +178,10 @@ if(OPENVDB_CXX_STRICT) add_compile_options("$<$:-Wall>") add_compile_options("$<$:-Wextra>") add_compile_options("$<$:-Wconversion>") + add_compile_options("$<$:-Wnon-virtual-dtor>") + add_compile_options("$<$:-Wover-aligned>") + add_compile_options("$<$:-Wimplicit-fallthrough>") + add_compile_options("$<$,$,9.3.0>>:-Wimplicit-fallthrough>") # Only check global constructors for libraries (we should really check for # executables too but gtest relies on these types of constructors for its # framework). @@ -193,6 +197,7 @@ if(OPENVDB_CXX_STRICT) add_compile_options("$<$:-Wconversion>") add_compile_options("$<$:-Wdisabled-optimization>") add_compile_options("$<$:-Woverloaded-virtual>") + add_compile_options("$<$:-Wnon-virtual-dtor>") else() # NO OPENVDB_CXX_STRICT, suppress some warnings if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") diff --git a/openvdb/openvdb/tools/ParticlesToLevelSet.h b/openvdb/openvdb/tools/ParticlesToLevelSet.h index bcf1b1702a..be78d533dd 100644 --- a/openvdb/openvdb/tools/ParticlesToLevelSet.h +++ b/openvdb/openvdb/tools/ParticlesToLevelSet.h @@ -686,7 +686,7 @@ struct ParticlesToLevelSet::Raster const Vec3R Nrml = -V * invSpeed; // inverse normalized direction Vec3R P = P0; // local position of instance Real R = R0, d = 0; // local radius and length of trail - for (size_t m = 0; run && d <= speed ; ++m) { + while (run && d <= speed) { run = this->makeSphere(P, R, att, acc); P += 0.5 * delta * R * Nrml; // adaptive offset along inverse velocity direction d = (P - P0).length(); // current length of trail diff --git a/openvdb/openvdb/unittest/TestFindActiveValues.cc b/openvdb/openvdb/unittest/TestFindActiveValues.cc index e5d8f5cd24..7dd6415777 100644 --- a/openvdb/openvdb/unittest/TestFindActiveValues.cc +++ b/openvdb/openvdb/unittest/TestFindActiveValues.cc @@ -149,9 +149,9 @@ TEST_F(TestFindActiveValues, testSphere2) openvdb::tools::FindActiveValues op(tree); auto bbox = openvdb::CoordBBox::createCube(openvdb::Coord(0), 1); //openvdb::util::CpuTimer timer("\nInscribed cube (class)"); - int count = 0; + //int count = 0; while(op.noActiveValues(bbox)) { - ++count; + //++count; bbox.expand(1); } //const double t = timer.stop(); @@ -165,10 +165,10 @@ TEST_F(TestFindActiveValues, testSphere2) {// find largest inscribed cube in index space containing NO active values auto bbox = openvdb::CoordBBox::createCube(openvdb::Coord(0), 1); //openvdb::util::CpuTimer timer("\nInscribed cube (func)"); - int count = 0; + //int count = 0; while(!openvdb::tools::anyActiveValues(tree, bbox)) { bbox.expand(1); - ++count; + //++count; } //const double t = timer.stop(); //std::cerr << "Inscribed bbox = " << bbox << std::endl; diff --git a/openvdb_cmd/vdb_ax/cli.h b/openvdb_cmd/vdb_ax/cli.h index 3ce8a5e509..555c4f132e 100644 --- a/openvdb_cmd/vdb_ax/cli.h +++ b/openvdb_cmd/vdb_ax/cli.h @@ -181,6 +181,7 @@ inline void usage(std::ostream& os, struct ParamBase { ParamBase() = default; + virtual ~ParamBase() = default; inline const std::vector& opts() const { return mOpts; } inline const char* doc() const { return mDoc; } virtual void init(const char* arg, const uint32_t idx = 0) = 0; @@ -198,6 +199,12 @@ struct BasicParam using CB = std::function; BasicParam(T&& v) : mValue(std::move(v)) {} BasicParam(const T& v) : mValue(v) {} + + BasicParam(const BasicParam&) = default; + BasicParam(BasicParam&&) = default; + BasicParam& operator=(const BasicParam&) = default; + BasicParam& operator=(BasicParam&&) = default; + inline void set(const T& v) { mValue = v; } inline T& get() { return mValue; } inline const T& get() const { return mValue; } @@ -212,6 +219,12 @@ struct BasicParam template struct Param : public BasicParam, ParamBase { + ~Param() override = default; + Param(const Param&) = default; + Param(Param&&) = default; + Param& operator=(const Param&) = default; + Param& operator=(Param&&) = default; + // CB1 callback passes the value to store // CB2 callback passed the value and the argument provided // CB3 callback passed the value, the argument provided and the index to From ee4f8fa9f9b15034982715ac9a18a604d6d8fffa Mon Sep 17 00:00:00 2001 From: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> Date: Thu, 25 May 2023 22:43:12 +1200 Subject: [PATCH 16/17] Fixed another occurance of static non-trivial construction Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> --- openvdb/openvdb/math/ConjGradient.h | 6 +----- openvdb/openvdb/math/Math.h | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/openvdb/openvdb/math/ConjGradient.h b/openvdb/openvdb/math/ConjGradient.h index 4569ebed79..e922005445 100644 --- a/openvdb/openvdb/math/ConjGradient.h +++ b/openvdb/openvdb/math/ConjGradient.h @@ -245,7 +245,7 @@ class SparseStencilMatrix class ConstRow; class RowEditor; - static const ValueType sZeroValue; + static inline constexpr ValueType sZeroValue = zeroVal(); /// Construct an @a n x @a n matrix with at most @a STENCIL_SIZE nonzero elements per row. SparseStencilMatrix(SizeType n); @@ -814,10 +814,6 @@ Vector::str() const //////////////////////////////////////// -template -const ValueType SparseStencilMatrix::sZeroValue = zeroVal(); - - template inline SparseStencilMatrix::SparseStencilMatrix(SizeType numRows) diff --git a/openvdb/openvdb/math/Math.h b/openvdb/openvdb/math/Math.h index 19bd464b3a..8f2e705973 100644 --- a/openvdb/openvdb/math/Math.h +++ b/openvdb/openvdb/math/Math.h @@ -67,11 +67,11 @@ namespace OPENVDB_VERSION_NAME { /// @note A zeroVal() specialization must be defined for each @c ValueType T /// that cannot be constructed using the form @c T(0). For example, @c std::string(0) /// treats 0 as @c nullptr and throws a @c std::logic_error. -template inline T zeroVal() { return T(0); } +template inline constexpr 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; } +template<> inline constexpr bool zeroVal() { return false; } namespace math { From 52c7b9ab675b548419136a994874c2d3aee1d805 Mon Sep 17 00:00:00 2001 From: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> Date: Fri, 26 May 2023 12:54:16 +1200 Subject: [PATCH 17/17] pedantic fixes Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com> --- openvdb/openvdb/math/Maps.cc | 2 +- openvdb/openvdb/openvdb.cc | 2 +- openvdb_ax/openvdb_ax/ast/Parse.cc | 2 +- openvdb_ax/openvdb_ax/ax.cc | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openvdb/openvdb/math/Maps.cc b/openvdb/openvdb/math/Maps.cc index 3ee74249cd..0be1904b8d 100644 --- a/openvdb/openvdb/math/Maps.cc +++ b/openvdb/openvdb/math/Maps.cc @@ -14,7 +14,7 @@ inline std::mutex& GetMapRegistryMutex() { static std::mutex sInitMapRegistryMutex; return sInitMapRegistryMutex; -}; +} } // unnamed namespace diff --git a/openvdb/openvdb/openvdb.cc b/openvdb/openvdb/openvdb.cc index dfcd4a2618..4a9d08d635 100644 --- a/openvdb/openvdb/openvdb.cc +++ b/openvdb/openvdb/openvdb.cc @@ -51,7 +51,7 @@ inline std::mutex& GetInitMutex() { static std::mutex sInitMutex; return sInitMutex; -}; +} std::atomic sIsInitialized{false}; } diff --git a/openvdb_ax/openvdb_ax/ast/Parse.cc b/openvdb_ax/openvdb_ax/ast/Parse.cc index 1e6c8e12f9..4230b6e106 100644 --- a/openvdb_ax/openvdb_ax/ast/Parse.cc +++ b/openvdb_ax/openvdb_ax/ast/Parse.cc @@ -23,7 +23,7 @@ inline std::mutex& GetInitMutex() { static std::mutex sInitMutex; return sInitMutex; -}; +} } openvdb::ax::Logger* axlog = nullptr; diff --git a/openvdb_ax/openvdb_ax/ax.cc b/openvdb_ax/openvdb_ax/ax.cc index 50f8f73d19..f885cc789b 100644 --- a/openvdb_ax/openvdb_ax/ax.cc +++ b/openvdb_ax/openvdb_ax/ax.cc @@ -112,7 +112,7 @@ inline std::mutex& GetInitMutex() { static std::mutex sInitMutex; return sInitMutex; -}; +} bool sIsInitialized = false; bool sShutdown = false; }