diff --git a/.github/workflows/ax.yml b/.github/workflows/ax.yml
index 1d80168419..49a63cefa6 100644
--- a/.github/workflows/ax.yml
+++ b/.github/workflows/ax.yml
@@ -101,7 +101,7 @@ jobs:
matrix:
compiler: ['clang++']
build: ['Release']
- llvm: ['7','8','9','10']
+ llvm: ['7','8','9'] #@todo add back llvm 10 when the brew formula is fixed
# Extra builds
include:
- compiler: 'g++'
diff --git a/.github/workflows/houdini_cache_update.yml b/.github/workflows/houdini_cache_update.yml
index 2fa9024ed1..5f30d44034 100644
--- a/.github/workflows/houdini_cache_update.yml
+++ b/.github/workflows/houdini_cache_update.yml
@@ -11,6 +11,26 @@ on:
# succeeds, put it into the GitHub Actions cache
jobs:
+ houdini18_5:
+ runs-on: ubuntu-16.04
+ container:
+ image: aswf/ci-base:2020
+ steps:
+ - uses: actions/checkout@v2
+ - name: download_houdini
+ run: ./ci/download_houdini.sh 18.5 ON ON ${{ secrets.HOUDINI_CLIENT_ID }} ${{ secrets.HOUDINI_SECRET_KEY }}
+ - name: install_houdini
+ run: ./ci/install_houdini.sh
+ - name: build
+ run: ./ci/build_houdini.sh clang++ Release ON
+ - name: test
+ run: ./ci/test.sh
+ - name: write_houdini_cache
+ uses: actions/cache@v2
+ with:
+ path: hou
+ key: vdb1-houdini18_5-${{ hashFiles('hou/hou.tar.gz') }}
+
houdini18_0:
runs-on: ubuntu-16.04
container:
diff --git a/CHANGES b/CHANGES
index f30fea7ec8..6a2aef3cc6 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,10 +1,24 @@
OpenVDB Version History
=======================
-Version 7.1.1 - In Development
+Version 7.2.0 - In Development
+
+ New features:
+ - Added tree::DynamicNodeManager that lazily caches the nodes at each
+ level of the tree to allow for efficient threading of topology-changing
+ top-down workflows.
+ - Added tools::CsgUnionOp, tools::CsgIntersectionOp and
+ tools::CsgDifferenceOp that use a parallel breadth-first algorithm to
+ accelerate CSG union, intersection and difference operations.
+ - Added tools::TreeToMerge which provides methods to steal or deep-copy
+ from a tree based on the tag dispatch class used in its construction.
Improvements:
- util::CpuTimer now uses C++11 chrono instead of TBB.
+ - Threaded the construction of LeafManager and NodeManager linear arrays.
+ - tools::csgUnion, tools::csgIntersection and tools::csgDifference now use
+ the new parallel breadth-first algorithms for much faster performance.
+ - Extended tree::NodeManager to allow for use with a const tree.
Houdini:
- Fixed a bug in the OpenVDB Points Convert SOP where the auto voxel
@@ -18,6 +32,17 @@ Version 7.1.1 - In Development
Bug fixes:
- Fixed a bug which could cause recursive compile time instantiations of
TypeList objects, manifesting in longer compile times.
+ - Deprecated util::PagedArray::push_back due to a race condition. Instead
+ use util::PagedArray::ValueBuffer::push_back which is faster and thread-safe.
+
+ API changes:
+ - Deprecated tree::LeafManager::getNodes. This method is no longer used when
+ constructing a NodeManager from a LeafManager.
+ - Deprecated Tree::visitActiveBBox, Tree::visit and Tree::visit2 methods in
+ favor of using a tree::DynamicNodeManager.
+ - Removed tools::CsgVisitorBase, tools::CsgVisitorUnion,
+ tools::CsgVisitorIntersection and tools::CsgVisitorDifference. The CSG
+ tools now use the parallel breath-first algorithms.
Build:
- Removed the Makefiles.
diff --git a/ci/download_houdini.sh b/ci/download_houdini.sh
index 9fc482b5ba..a76e1fc65b 100755
--- a/ci/download_houdini.sh
+++ b/ci/download_houdini.sh
@@ -45,6 +45,7 @@ cp -r dsolib/libbz2* ../hou/dsolib/.
cp -r dsolib/libtbb* ../hou/dsolib/.
cp -r dsolib/libHalf* ../hou/dsolib/.
cp -r dsolib/libjemalloc* ../hou/dsolib/.
+cp -r dsolib/liblzma* ../hou/dsolib/.
# needed for < H18.0 (due to sesitag)
if [ "$HOUDINI_MAJOR" == "17.0" ] || [ "$HOUDINI_MAJOR" == "17.5" ]; then
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
index 1e7e1036e3..9c622ca0ab 100644
--- a/doc/CMakeLists.txt
+++ b/doc/CMakeLists.txt
@@ -51,7 +51,7 @@ set(DOXY_FILES
doc/python.txt)
set(DOXYGEN_PROJECT_NAME "OpenVDB")
-set(DOXYGEN_PROJECT_NUMBER "7.1.1")
+set(DOXYGEN_PROJECT_NUMBER "7.2.0")
set(DOXYGEN_PROJECT_BRIEF "")
set(DOXYGEN_FILE_PATTERNS "*.h") # headers only
set(DOXYGEN_IMAGE_PATH "doc/img")
@@ -86,7 +86,7 @@ set(DOXYGEN_ALIASES
[[ijk="(i, j, k)"]]
[[xyz="(x, y, z)"]]
[[const="const"]]
- [["vdbnamespace=openvdb::v7_1"]]
+ [["vdbnamespace=openvdb::v7_2"]]
[["hunamespace=houdini_utils"]]
[["hvdbnamespace=openvdb_houdini"]]
# Use this command to create a link to an OpenVDB class, function, etc.
@@ -109,7 +109,7 @@ set(DOXYGEN_ALIASES
)
set(DOXYGEN_PREDEFINED
- "OPENVDB_VERSION_NAME=v7_1"
+ "OPENVDB_VERSION_NAME=v7_2"
"OPENVDB_ABI_VERSION_NUMBER=7"
[[__declspec(x):= __attribute__(x):=]]
"OPENVDB_USE_LOG4CPLUS=")
diff --git a/doc/changes.txt b/doc/changes.txt
index 04a9513048..064d7b413f 100644
--- a/doc/changes.txt
+++ b/doc/changes.txt
@@ -2,13 +2,34 @@
@page changes Release Notes
-@htmlonly @endhtmlonly
+@htmlonly @endhtmlonly
@par
-Version 7.1.1 - In Development
+Version 7.2.0 - In Development
+
+@par
+New features:
+- Added @vdblink::tree::DynamicNodeManager DynamicNodeManager@endlink that
+ lazily caches the nodes at each level of the tree to allow for efficient
+ threading of topology-changing top-down workflows.
+- Added @vdblink::tools::CsgUnionOp CsgUnionOp@endlink,
+ @vdblink::tools::CsgIntersectionOp CsgIntersectionOp@endlink and
+ @vdblink::tools::CsgDifferenceOp CsgDifferenceOp@endlink that use a parallel
+ breadth-first algorithm to accelerate CSG union, intersection and difference
+ operations.
+- Added @vdblink::tools::TreeToMerge TreeToMerge@endlink which provides methods
+ to steal or deep-copy from a tree based on the tag dispatch class used in its
+ construction.
@par
Improvements:
- util::CpuTimer now uses C++11 chrono instead of TBB.
+- Threaded the construction of LeafManager and NodeManager linear arrays.
+- @vdblink::tools::csgUnion() csgUnion@endlink,
+ @vdblink::tools::csgIntersection() csgIntersection@endlink and
+ @vdblink::tools::csgDifference() csgDifference@endlink now use the new
+ parallel breadth-first algorithms for much faster performance.
+- Extended @vdblink::tree::NodeManager NodeManager@endlink to allow for use with
+ a const tree.
@par
Houdini:
@@ -24,6 +45,20 @@ Houdini:
Bug Fixes:
- Fixed a bug which could cause recursive compile time instantiations of
TypeList objects, manifesting in longer compile times.
+- Deprecated @vdblink::util::PagedArray::push_back util::PagedArray::push_back@endlink
+ due to a race condition. Instead use
+ @vdblink::util::PagedArray::ValueBuffer::push_back util::PagedArray::ValueBuffer::push_back@endlink
+ which is faster and thread-safe.
+
+@par
+API changes:
+- Deprecated tree::LeafManager::getNodes. This method is no longer used when
+ constructing a NodeManager from a LeafManager.
+- Deprecated Tree::visitActiveBBox, Tree::visit and Tree::visit2 methods in
+ favor of using a tree::DynamicNodeManager.
+- Removed tools::CsgVisitorBase, tools::CsgVisitorUnion,
+ tools::CsgVisitorIntersection and tools::CsgVisitorDifference. The CSG tools
+ now use the parallel breath-first algorithms.
@par
Build:
diff --git a/openvdb/openvdb/CMakeLists.txt b/openvdb/openvdb/CMakeLists.txt
index 036c7c259c..0ead889f52 100644
--- a/openvdb/openvdb/CMakeLists.txt
+++ b/openvdb/openvdb/CMakeLists.txt
@@ -318,6 +318,7 @@ set(OPENVDB_LIBRARY_TOOLS_INCLUDE_FILES
tools/LevelSetTracker.h
tools/LevelSetUtil.h
tools/Mask.h
+ tools/Merge.h
tools/MeshToVolume.h
tools/Morphology.h
tools/MultiResGrid.h
diff --git a/openvdb/openvdb/Types.h b/openvdb/openvdb/Types.h
index 13699e094a..fd30ef82ae 100644
--- a/openvdb/openvdb/Types.h
+++ b/openvdb/openvdb/Types.h
@@ -1045,6 +1045,10 @@ class ShallowCopy {};
/// @brief Tag dispatch class that distinguishes topology copy constructors
/// from deep copy constructors
class TopologyCopy {};
+/// @brief Tag dispatch class that distinguishes constructors that deep copy
+class DeepCopy {};
+/// @brief Tag dispatch class that distinguishes constructors that steal
+class Steal {};
/// @brief Tag dispatch class that distinguishes constructors during file input
class PartialCreate {};
diff --git a/openvdb/openvdb/tools/Composite.h b/openvdb/openvdb/tools/Composite.h
index cf401ac2ef..6bc170bf76 100644
--- a/openvdb/openvdb/tools/Composite.h
+++ b/openvdb/openvdb/tools/Composite.h
@@ -15,6 +15,7 @@
#include
#include
#include // for isExactlyEqual()
+#include "Merge.h"
#include "ValueTransformer.h" // for transformValues()
#include "Prune.h"// for prune
#include "SignedFloodFill.h" // for signedFloodFill()
@@ -722,6 +723,27 @@ struct CopyOp
};
/// @endcond
+template
+inline void validateLevelSet(const TreeT& tree, const std::string& gridName = std::string(""))
+{
+ using ValueT = typename TreeT::ValueType;
+ const ValueT zero = zeroVal();
+ if (!(tree.background() > zero)) {
+ std::stringstream ss;
+ ss << "expected grid ";
+ if (!gridName.empty()) ss << gridName << " ";
+ ss << "outside value > 0, got " << tree.background();
+ OPENVDB_THROW(ValueError, ss.str());
+ }
+ if (!(-tree.background() < zero)) {
+ std::stringstream ss;
+ ss << "expected grid ";
+ if (!gridName.empty()) ss << gridName << " ";
+ ss << "inside value < 0, got " << -tree.background();
+ OPENVDB_THROW(ValueError, ss.str());
+ }
+}
+
} // namespace composite
@@ -858,270 +880,6 @@ compReplace(GridOrTreeT& aTree, const GridOrTreeT& bTree)
////////////////////////////////////////
-/// Base visitor class for CSG operations
-/// (not intended to be used polymorphically, so no virtual functions)
-template
-class CsgVisitorBase
-{
-public:
- using TreeT = TreeType;
- using ValueT = typename TreeT::ValueType;
- using ChildIterT = typename TreeT::LeafNodeType::ChildAllIter;
-
- enum { STOP = 3 };
-
- CsgVisitorBase(const TreeT& aTree, const TreeT& bTree):
- mAOutside(aTree.background()),
- mAInside(math::negative(mAOutside)),
- mBOutside(bTree.background()),
- mBInside(math::negative(mBOutside))
- {
- const ValueT zero = zeroVal();
- if (!(mAOutside > zero)) {
- OPENVDB_THROW(ValueError,
- "expected grid A outside value > 0, got " << mAOutside);
- }
- if (!(mAInside < zero)) {
- OPENVDB_THROW(ValueError,
- "expected grid A inside value < 0, got " << mAInside);
- }
- if (!(mBOutside > zero)) {
- OPENVDB_THROW(ValueError,
- "expected grid B outside value > 0, got " << mBOutside);
- }
- if (!(mBInside < zero)) {
- OPENVDB_THROW(ValueError,
- "expected grid B outside value < 0, got " << mBOutside);
- }
- }
-
-protected:
- ValueT mAOutside, mAInside, mBOutside, mBInside;
-};
-
-
-////////////////////////////////////////
-
-
-template
-struct CsgUnionVisitor: public CsgVisitorBase
-{
- using TreeT = TreeType;
- using ValueT = typename TreeT::ValueType;
- using ChildIterT = typename TreeT::LeafNodeType::ChildAllIter;
-
- enum { STOP = CsgVisitorBase::STOP };
-
- CsgUnionVisitor(const TreeT& a, const TreeT& b): CsgVisitorBase(a, b) {}
-
- /// Don't process nodes that are at different tree levels.
- template
- inline int operator()(AIterT&, BIterT&) { return 0; }
-
- /// Process root and internal nodes.
- template
- inline int operator()(IterT& aIter, IterT& bIter)
- {
- ValueT aValue = zeroVal();
- typename IterT::ChildNodeType* aChild = aIter.probeChild(aValue);
- if (!aChild && aValue < zeroVal()) {
- // A is an inside tile. Leave it alone and stop traversing this branch.
- return STOP;
- }
-
- ValueT bValue = zeroVal();
- typename IterT::ChildNodeType* bChild = bIter.probeChild(bValue);
- if (!bChild && bValue < zeroVal()) {
- // B is an inside tile. Make A an inside tile and stop traversing this branch.
- aIter.setValue(this->mAInside);
- aIter.setValueOn(bIter.isValueOn());
- delete aChild;
- return STOP;
- }
-
- if (!aChild && aValue > zeroVal()) {
- // A is an outside tile. If B has a child, transfer it to A,
- // otherwise leave A alone.
- if (bChild) {
- bIter.setValue(this->mBOutside);
- bIter.setValueOff();
- bChild->resetBackground(this->mBOutside, this->mAOutside);
- aIter.setChild(bChild); // transfer child
- delete aChild;
- }
- return STOP;
- }
-
- // If A has a child and B is an outside tile, stop traversing this branch.
- // Continue traversal only if A and B both have children.
- return (aChild && bChild) ? 0 : STOP;
- }
-
- /// Process leaf node values.
- inline int operator()(ChildIterT& aIter, ChildIterT& bIter)
- {
- ValueT aValue, bValue;
- aIter.probeValue(aValue);
- bIter.probeValue(bValue);
- if (aValue > bValue) { // a = min(a, b)
- aIter.setValue(bValue);
- aIter.setValueOn(bIter.isValueOn());
- }
- return 0;
- }
-};
-
-
-
-////////////////////////////////////////
-
-
-template
-struct CsgIntersectVisitor: public CsgVisitorBase
-{
- using TreeT = TreeType;
- using ValueT = typename TreeT::ValueType;
- using ChildIterT = typename TreeT::LeafNodeType::ChildAllIter;
-
- enum { STOP = CsgVisitorBase::STOP };
-
- CsgIntersectVisitor(const TreeT& a, const TreeT& b): CsgVisitorBase(a, b) {}
-
- /// Don't process nodes that are at different tree levels.
- template
- inline int operator()(AIterT&, BIterT&) { return 0; }
-
- /// Process root and internal nodes.
- template
- inline int operator()(IterT& aIter, IterT& bIter)
- {
- ValueT aValue = zeroVal();
- typename IterT::ChildNodeType* aChild = aIter.probeChild(aValue);
- if (!aChild && !(aValue < zeroVal())) {
- // A is an outside tile. Leave it alone and stop traversing this branch.
- return STOP;
- }
-
- ValueT bValue = zeroVal();
- typename IterT::ChildNodeType* bChild = bIter.probeChild(bValue);
- if (!bChild && !(bValue < zeroVal())) {
- // B is an outside tile. Make A an outside tile and stop traversing this branch.
- aIter.setValue(this->mAOutside);
- aIter.setValueOn(bIter.isValueOn());
- delete aChild;
- return STOP;
- }
-
- if (!aChild && aValue < zeroVal()) {
- // A is an inside tile. If B has a child, transfer it to A,
- // otherwise leave A alone.
- if (bChild) {
- bIter.setValue(this->mBOutside);
- bIter.setValueOff();
- bChild->resetBackground(this->mBOutside, this->mAOutside);
- aIter.setChild(bChild); // transfer child
- delete aChild;
- }
- return STOP;
- }
-
- // If A has a child and B is an outside tile, stop traversing this branch.
- // Continue traversal only if A and B both have children.
- return (aChild && bChild) ? 0 : STOP;
- }
-
- /// Process leaf node values.
- inline int operator()(ChildIterT& aIter, ChildIterT& bIter)
- {
- ValueT aValue, bValue;
- aIter.probeValue(aValue);
- bIter.probeValue(bValue);
- if (aValue < bValue) { // a = max(a, b)
- aIter.setValue(bValue);
- aIter.setValueOn(bIter.isValueOn());
- }
- return 0;
- }
-};
-
-
-////////////////////////////////////////
-
-
-template
-struct CsgDiffVisitor: public CsgVisitorBase
-{
- using TreeT = TreeType;
- using ValueT = typename TreeT::ValueType;
- using ChildIterT = typename TreeT::LeafNodeType::ChildAllIter;
-
- enum { STOP = CsgVisitorBase::STOP };
-
- CsgDiffVisitor(const TreeT& a, const TreeT& b): CsgVisitorBase(a, b) {}
-
- /// Don't process nodes that are at different tree levels.
- template
- inline int operator()(AIterT&, BIterT&) { return 0; }
-
- /// Process root and internal nodes.
- template
- inline int operator()(IterT& aIter, IterT& bIter)
- {
- ValueT aValue = zeroVal();
- typename IterT::ChildNodeType* aChild = aIter.probeChild(aValue);
- if (!aChild && !(aValue < zeroVal())) {
- // A is an outside tile. Leave it alone and stop traversing this branch.
- return STOP;
- }
-
- ValueT bValue = zeroVal();
- typename IterT::ChildNodeType* bChild = bIter.probeChild(bValue);
- if (!bChild && bValue < zeroVal()) {
- // B is an inside tile. Make A an inside tile and stop traversing this branch.
- aIter.setValue(this->mAOutside);
- aIter.setValueOn(bIter.isValueOn());
- delete aChild;
- return STOP;
- }
-
- if (!aChild && aValue < zeroVal()) {
- // A is an inside tile. If B has a child, transfer it to A,
- // otherwise leave A alone.
- if (bChild) {
- bIter.setValue(this->mBOutside);
- bIter.setValueOff();
- bChild->resetBackground(this->mBOutside, this->mAOutside);
- aIter.setChild(bChild); // transfer child
- bChild->negate();
- delete aChild;
- }
- return STOP;
- }
-
- // If A has a child and B is an outside tile, stop traversing this branch.
- // Continue traversal only if A and B both have children.
- return (aChild && bChild) ? 0 : STOP;
- }
-
- /// Process leaf node values.
- inline int operator()(ChildIterT& aIter, ChildIterT& bIter)
- {
- ValueT aValue, bValue;
- aIter.probeValue(aValue);
- bIter.probeValue(bValue);
- bValue = math::negative(bValue);
- if (aValue < bValue) { // a = max(a, -b)
- aIter.setValue(bValue);
- aIter.setValueOn(bIter.isValueOn());
- }
- return 0;
- }
-};
-
-
-////////////////////////////////////////
-
-
template
inline void
csgUnion(GridOrTreeT& a, GridOrTreeT& b, bool prune)
@@ -1129,8 +887,11 @@ csgUnion(GridOrTreeT& a, GridOrTreeT& b, bool prune)
using Adapter = TreeAdapter;
using TreeT = typename Adapter::TreeType;
TreeT &aTree = Adapter::tree(a), &bTree = Adapter::tree(b);
- CsgUnionVisitor visitor(aTree, bTree);
- aTree.visit2(bTree, visitor);
+ composite::validateLevelSet(aTree, "A");
+ composite::validateLevelSet(bTree, "B");
+ CsgUnionOp op(bTree, Steal());
+ tree::DynamicNodeManager nodeManager(aTree);
+ nodeManager.foreachTopDown(op);
if (prune) tools::pruneLevelSet(aTree);
}
@@ -1141,8 +902,11 @@ csgIntersection(GridOrTreeT& a, GridOrTreeT& b, bool prune)
using Adapter = TreeAdapter;
using TreeT = typename Adapter::TreeType;
TreeT &aTree = Adapter::tree(a), &bTree = Adapter::tree(b);
- CsgIntersectVisitor visitor(aTree, bTree);
- aTree.visit2(bTree, visitor);
+ composite::validateLevelSet(aTree, "A");
+ composite::validateLevelSet(bTree, "B");
+ CsgIntersectionOp op(bTree, Steal());
+ tree::DynamicNodeManager nodeManager(aTree);
+ nodeManager.foreachTopDown(op);
if (prune) tools::pruneLevelSet(aTree);
}
@@ -1153,8 +917,11 @@ csgDifference(GridOrTreeT& a, GridOrTreeT& b, bool prune)
using Adapter = TreeAdapter;
using TreeT = typename Adapter::TreeType;
TreeT &aTree = Adapter::tree(a), &bTree = Adapter::tree(b);
- CsgDiffVisitor visitor(aTree, bTree);
- aTree.visit2(bTree, visitor);
+ composite::validateLevelSet(aTree, "A");
+ composite::validateLevelSet(bTree, "B");
+ CsgDifferenceOp op(bTree, Steal());
+ tree::DynamicNodeManager nodeManager(aTree);
+ nodeManager.foreachTopDown(op);
if (prune) tools::pruneLevelSet(aTree);
}
diff --git a/openvdb/openvdb/tools/Merge.h b/openvdb/openvdb/tools/Merge.h
new file mode 100644
index 0000000000..9fdc2ae43b
--- /dev/null
+++ b/openvdb/openvdb/tools/Merge.h
@@ -0,0 +1,956 @@
+// Copyright Contributors to the OpenVDB Project
+// SPDX-License-Identifier: MPL-2.0
+//
+/// @file Merge.h
+///
+/// @brief Functions to efficiently merge grids
+///
+/// @author Dan Bailey
+
+#ifndef OPENVDB_TOOLS_MERGE_HAS_BEEN_INCLUDED
+#define OPENVDB_TOOLS_MERGE_HAS_BEEN_INCLUDED
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace openvdb {
+OPENVDB_USE_VERSION_NAMESPACE
+namespace OPENVDB_VERSION_NAME {
+namespace tools {
+
+
+/// @brief Convenience class that contains a pointer to a tree to be stolen or
+/// deep copied depending on the tag dispatch class used and a subset of
+/// methods to retrieve data from the tree.
+///
+/// @details The primary purpose of this class is to be able to create an array
+/// of TreeToMerge objects that each store a tree to be stolen or a tree to be
+/// deep-copied in an arbitrary order. Certain operations such as floating-point
+/// addition are non-associative so the order in which they are merged is
+/// important for the operation to remain deterministic regardless of how the
+/// data is being extracted from the tree.
+///
+/// @note Stealing data requires a non-const tree pointer. There is a constructor
+/// to pass in a tree shared pointer for cases where it is desirable for this class
+/// to maintain shared ownership.
+template
+struct TreeToMerge
+{
+ using TreeType = std::remove_const_t;
+ using RootNodeType = typename TreeType::RootNodeType;
+ using ValueType = typename TreeType::ValueType;
+ using MaskTreeType = typename TreeT::template ValueConverter::Type;
+
+ TreeToMerge() = delete;
+
+ /// @brief Non-const pointer tree constructor for stealing data.
+ TreeToMerge(TreeType& tree, Steal)
+ : mTree(&tree), mSteal(true) { }
+ /// @brief Non-const shared pointer tree constructor for stealing data.
+ TreeToMerge(typename TreeType::Ptr treePtr, Steal)
+ : mTreePtr(treePtr), mTree(mTreePtr.get()), mSteal(true) { }
+
+ /// @brief Const tree pointer constructor for deep-copying data. As the
+ /// tree is not mutable and thus cannot be pruned, a lightweight mask tree
+ /// with the same topology is created that can be pruned to use as a
+ /// reference. Initialization of this mask tree can optionally be disabled
+ /// for delayed construction.
+ TreeToMerge(const TreeType& tree, DeepCopy, bool initialize = true)
+ : mTree(&tree), mSteal(false)
+ {
+ if (mTree && initialize) this->initializeMask();
+ }
+
+ /// @brief Non-const tree pointer constructor for deep-copying data. The
+ /// tree is not intended to be modified so is not pruned, instead a
+ /// lightweight mask tree with the same topology is created that can be
+ /// pruned to use as a reference. Initialization of this mask tree can
+ /// optionally be disabled for delayed construction.
+ TreeToMerge(TreeType& tree, DeepCopy tag, bool initialize = true)
+ : TreeToMerge(static_cast(tree), tag, initialize) { }
+
+ /// @brief Reset the non-const tree shared pointer. This is primarily
+ /// used to preserve the order of trees to merge in a container but have
+ /// the data in the tree be lazily loaded or resampled.
+ void reset(typename TreeType::Ptr treePtr, Steal);
+
+ /// @brief Return a pointer to the tree to be stolen.
+ TreeType* treeToSteal() { return mSteal ? const_cast(mTree) : nullptr; }
+ /// @brief Return a pointer to the tree to be deep-copied.
+ const TreeType* treeToDeepCopy() { return mSteal ? nullptr : mTree; }
+
+ /// @brief Retrieve a const pointer to the root node.
+ const RootNodeType* rootPtr() const;
+
+ /// @brief Return a pointer to the node of type @c NodeT that contains
+ /// voxel (x, y, z). If no such node exists, return @c nullptr.
+ template
+ const NodeT* probeConstNode(const Coord& ijk) const;
+
+ /// @brief Return a pointer to the node of type @c NodeT that contains voxel (x, y, z).
+ /// If the tree is non-const, steal the node and replace it with an inactive
+ /// background-value tile.
+ /// If the tree is const, deep-copy the node and modify the mask tree to prune the node.
+ template
+ std::unique_ptr stealOrDeepCopyNode(const Coord& ijk);
+
+ /// @brief Add a tile containing voxel (x, y, z) at the level of NodeT,
+ /// deleting the existing branch if necessary.
+ template
+ void addTile(const Coord& ijk, const ValueType& value, bool active);
+
+ // build a lightweight mask using a union of the const tree where leaf nodes
+ // are converted into active tiles
+ void initializeMask();
+
+ // returns true if mask has been initialized
+ bool hasMask() const;
+
+ // returns MaskTree pointer or nullptr
+ MaskTreeType* mask() { return mMaskTree.ptr.get(); }
+ const MaskTreeType* mask() const { return mMaskTree.ptr.get(); }
+
+private:
+ struct MaskPtr;
+ struct MaskUnionOp;
+
+ typename TreeType::Ptr mTreePtr;
+ const TreeType* mTree;
+ MaskPtr mMaskTree;
+ bool mSteal;
+}; // struct TreeToMerge
+
+
+/// @brief Wrapper around unique_ptr that deep-copies mask on copy construction
+template
+struct TreeToMerge::MaskPtr
+{
+ std::unique_ptr ptr;
+
+ MaskPtr() = default;
+ ~MaskPtr() = default;
+ MaskPtr(MaskPtr&& other) = default;
+ MaskPtr& operator=(MaskPtr&& other) = default;
+ MaskPtr(const MaskPtr& other)
+ : ptr(bool(other.ptr) ? std::make_unique(*other.ptr) : nullptr) { }
+ MaskPtr& operator=(const MaskPtr& other)
+ {
+ ptr.reset(bool(other.ptr) ? std::make_unique(*other.ptr) : nullptr);
+ return *this;
+ }
+};
+
+/// @brief DynamicNodeManager operator used to generate a mask of the input
+/// tree, but with dense leaf nodes replaced with active tiles for compactness
+template
+struct TreeToMerge::MaskUnionOp
+{
+ using MaskT = MaskTreeType;
+ using RootT = typename MaskT::RootNodeType;
+ using LeafT = typename MaskT::LeafNodeType;
+
+ explicit MaskUnionOp(const TreeT& tree) : mTree(tree) { }
+ bool operator()(RootT& root, size_t) const;
+ template
+ bool operator()(NodeT& node, size_t) const;
+ bool operator()(LeafT&, size_t) const { return false; }
+private:
+ const TreeT& mTree;
+}; // struct TreeToMerge::MaskUnionOp
+
+
+////////////////////////////////////////
+
+
+/// @brief DynamicNodeManager operator to merge trees using a CSG union or intersection.
+/// @note This class modifies the topology of the tree so is designed to be used
+/// from DynamicNodeManager::foreachTopDown().
+/// @details A union and an intersection are opposite operations to each other so
+/// implemented in a combined class. Use the CsgUnionOp and CsgIntersectionOp aliases
+/// for convenience.
+template
+struct CsgUnionOrIntersectionOp
+{
+ using ValueT = typename TreeT::ValueType;
+ using RootT = typename TreeT::RootNodeType;
+ using LeafT = typename TreeT::LeafNodeType;
+
+ /// @brief Convenience constructor to CSG union or intersect a single
+ /// non-const tree with another. This constructor takes a Steal or DeepCopy
+ /// tag dispatch class.
+ template
+ CsgUnionOrIntersectionOp(TreeT& tree, TagT tag) { mTreesToMerge.emplace_back(tree, tag); }
+
+ /// @brief Convenience constructor to CSG union or intersect a single
+ /// const tree with another. This constructor requires a DeepCopy tag
+ /// dispatch class.
+ CsgUnionOrIntersectionOp(const TreeT& tree, DeepCopy tag) { mTreesToMerge.emplace_back(tree, tag); }
+
+ /// @brief Constructor to CSG union or intersect a container of multiple
+ /// const or non-const tree pointers. A Steal tag requires a container of
+ /// non-const trees, a DeepCopy tag will accept either const or non-const
+ /// trees.
+ template
+ CsgUnionOrIntersectionOp(TreesT& trees, TagT tag)
+ {
+ for (auto* tree : trees) {
+ if (tree) {
+ mTreesToMerge.emplace_back(*tree, tag);
+ }
+ }
+ }
+
+ /// @brief Constructor to accept a vector of TreeToMerge objects, primarily
+ /// used when mixing const/non-const trees.
+ /// @note Union/intersection order is preserved.
+ explicit CsgUnionOrIntersectionOp(const std::vector>& trees)
+ : mTreesToMerge(trees) { }
+
+ /// @brief Constructor to accept a deque of TreeToMerge objects, primarily
+ /// used when mixing const/non-const trees.
+ /// @note Union/intersection order is preserved.
+ explicit CsgUnionOrIntersectionOp(const std::deque>& trees)
+ : mTreesToMerge(trees.cbegin(), trees.cend()) { }
+
+ /// @brief Return true if no trees being merged
+ bool empty() const { return mTreesToMerge.empty(); }
+
+ /// @brief Return the number of trees being merged
+ size_t size() const { return mTreesToMerge.size(); }
+
+ // Processes the root node. Required by the NodeManager
+ bool operator()(RootT& root, size_t idx) const;
+
+ // Processes the internal nodes. Required by the NodeManager
+ template
+ bool operator()(NodeT& node, size_t idx) const;
+
+ // Processes the leaf nodes. Required by the NodeManager
+ bool operator()(LeafT& leaf, size_t idx) const;
+
+private:
+ // on processing the root node, the background value is stored, retrieve it
+ // and check that the root node has already been processed
+ const ValueT& background() const;
+
+ mutable std::vector> mTreesToMerge;
+ mutable const ValueT* mBackground = nullptr;
+}; // struct CsgUnionOrIntersectionOp
+
+
+template
+using CsgUnionOp = CsgUnionOrIntersectionOp;
+
+template
+using CsgIntersectionOp = CsgUnionOrIntersectionOp;
+
+
+/// @brief DynamicNodeManager operator to merge two trees using a CSG difference.
+/// @note This class modifies the topology of the tree so is designed to be used
+/// from DynamicNodeManager::foreachTopDown().
+template
+struct CsgDifferenceOp
+{
+ using ValueT = typename TreeT::ValueType;
+ using RootT = typename TreeT::RootNodeType;
+ using LeafT = typename TreeT::LeafNodeType;
+
+ /// @brief Convenience constructor to CSG difference a single non-const
+ /// tree from another. This constructor takes a Steal or DeepCopy tag
+ /// dispatch class.
+ template
+ CsgDifferenceOp(TreeT& tree, TagT tag) : mTree(tree, tag) { }
+ /// @brief Convenience constructor to CSG difference a single const
+ /// tree from another. This constructor requires an explicit DeepCopy tag
+ /// dispatch class.
+ CsgDifferenceOp(const TreeT& tree, DeepCopy tag) : mTree(tree, tag) { }
+
+ /// @brief Constructor to CSG difference the tree in a TreeToMerge object
+ /// from another.
+ explicit CsgDifferenceOp(TreeToMerge& tree) : mTree(tree) { }
+
+ /// @brief Return the number of trees being merged (only ever 1)
+ size_t size() const { return 1; }
+
+ // Processes the root node. Required by the NodeManager
+ bool operator()(RootT& root, size_t idx) const;
+
+ // Processes the internal nodes. Required by the NodeManager
+ template
+ bool operator()(NodeT& node, size_t idx) const;
+
+ // Processes the leaf nodes. Required by the NodeManager
+ bool operator()(LeafT& leaf, size_t idx) const;
+
+private:
+ // on processing the root node, the background values are stored, retrieve them
+ // and check that the root nodes have already been processed
+ const ValueT& background() const;
+ const ValueT& otherBackground() const;
+
+ // note that this vector is copied in NodeTransformer every time a foreach call is made,
+ // however in typical use cases this cost will be dwarfed by the actual merge algorithm
+ mutable TreeToMerge mTree;
+ mutable const ValueT* mBackground = nullptr;
+ mutable const ValueT* mOtherBackground = nullptr;
+}; // struct CsgDifferenceOp
+
+
+////////////////////////////////////////
+
+
+template
+void TreeToMerge::initializeMask()
+{
+ if (mSteal) return;
+ mMaskTree.ptr.reset(new MaskTreeType);
+ MaskUnionOp op(*mTree);
+ tree::DynamicNodeManager manager(*this->mask());
+ manager.foreachTopDown(op);
+}
+
+template
+bool TreeToMerge::hasMask() const
+{
+ return bool(mMaskTree.ptr);
+}
+
+template
+void TreeToMerge::reset(typename TreeType::Ptr treePtr, Steal)
+{
+ if (!treePtr) {
+ OPENVDB_THROW(RuntimeError, "Cannot reset with empty Tree shared pointer.");
+ }
+ mSteal = true;
+ mTreePtr = treePtr;
+ mTree = mTreePtr.get();
+}
+
+template
+const typename TreeToMerge::RootNodeType*
+TreeToMerge::rootPtr() const
+{
+ return &mTree->root();
+}
+
+template
+template
+const NodeT*
+TreeToMerge::probeConstNode(const Coord& ijk) const
+{
+ // test mutable mask first, node may have already been pruned
+ if (!mSteal && !this->mask()->isValueOn(ijk)) return nullptr;
+ return mTree->template probeConstNode(ijk);
+}
+
+template
+template
+std::unique_ptr
+TreeToMerge::stealOrDeepCopyNode(const Coord& ijk)
+{
+ if (mSteal) {
+ TreeType* tree = const_cast(mTree);
+ return std::unique_ptr(
+ tree->root().template stealNode(ijk, mTree->root().background(), false)
+ );
+ } else {
+ auto* child = this->probeConstNode(ijk);
+ if (child) {
+ assert(this->hasMask());
+ auto result = std::make_unique(*child);
+ // prune mask tree
+ this->mask()->addTile(NodeT::LEVEL, ijk, false, false);
+ return result;
+ }
+ }
+ return std::unique_ptr();
+}
+
+template
+template
+void
+TreeToMerge::addTile(const Coord& ijk, const ValueType& value, bool active)
+{
+ // ignore leaf node tiles (values)
+ if (NodeT::LEVEL == 0) return;
+
+ if (mSteal) {
+ TreeType* tree = const_cast(mTree);
+ auto* node = tree->template probeNode(ijk);
+ if (node) {
+ const Index pos = NodeT::coordToOffset(ijk);
+ node->addTile(pos, value, active);
+ }
+ } else {
+ auto* node = mTree->template probeConstNode(ijk);
+ // prune mask tree
+ if (node) {
+ assert(this->hasMask());
+ this->mask()->addTile(NodeT::LEVEL, ijk, false, false);
+ }
+ }
+}
+
+
+////////////////////////////////////////
+
+
+template
+bool TreeToMerge::MaskUnionOp::operator()(RootT& root, size_t /*idx*/) const
+{
+ using ChildT = typename RootT::ChildNodeType;
+
+ const Index count = mTree.root().childCount();
+
+ std::vector> children(count);
+
+ // allocate new root children
+
+ tbb::parallel_for(
+ tbb::blocked_range(0, count),
+ [&](tbb::blocked_range& range)
+ {
+ for (Index i = range.begin(); i < range.end(); i++) {
+ children[i] = std::make_unique(Coord::max(), true, true);
+ }
+ }
+ );
+
+ // apply origins and add root children to new root node
+
+ size_t i = 0;
+ for (auto iter = mTree.root().cbeginChildOn(); iter; ++iter) {
+ children[i]->setOrigin(iter->origin());
+ root.addChild(children[i].release());
+ i++;
+ }
+
+ return true;
+}
+
+template
+template
+bool TreeToMerge::MaskUnionOp::operator()(NodeT& node, size_t /*idx*/) const
+{
+ using ChildT = typename NodeT::ChildNodeType;
+
+ const auto* otherNode = mTree.template probeConstNode(node.origin());
+ if (!otherNode) return false;
+
+ // this mask tree stores active tiles in place of leaf nodes for compactness
+
+ if (NodeT::LEVEL == 1) {
+ for (auto iter = otherNode->cbeginChildOn(); iter; ++iter) {
+ node.addTile(iter.pos(), true, true);
+ }
+ } else {
+ for (auto iter = otherNode->cbeginChildOn(); iter; ++iter) {
+ auto* child = new ChildT(iter->origin(), true, true);
+ node.addChild(child);
+ }
+ }
+
+ return true;
+}
+
+
+////////////////////////////////////////
+
+
+namespace merge_internal {
+
+
+template
+struct UnallocatedBuffer
+{
+ static void allocateAndFill(BufferT& buffer, const ValueT& background)
+ {
+ if (!buffer.isOutOfCore() && buffer.empty()) {
+ buffer.allocate();
+ buffer.fill(background);
+ }
+ }
+
+ static bool isPartiallyConstructed(const BufferT& buffer)
+ {
+ return !buffer.isOutOfCore() && buffer.empty();
+ }
+}; // struct AllocateAndFillBuffer
+
+template
+struct UnallocatedBuffer
+{
+ // do nothing for bool buffers as they cannot be unallocated
+ static void allocateAndFill(BufferT&, const bool&) { }
+ static bool isPartiallyConstructed(const BufferT&) { return false; }
+}; // struct AllocateAndFillBuffer
+
+
+} // namespace merge_internal
+
+
+////////////////////////////////////////
+
+
+template
+bool CsgUnionOrIntersectionOp::operator()(RootT& root, size_t) const
+{
+ if (this->empty()) return false;
+
+ // store the background value
+ if (!mBackground) mBackground = &root.background();
+
+ // find all tile values in this root and track inside/outside and active state
+ // note that level sets should never contain active tiles, but we handle them anyway
+
+ constexpr uint8_t ACTIVE_TILE = 0x1;
+ constexpr uint8_t INSIDE_TILE = 0x2;
+ constexpr uint8_t OUTSIDE_TILE = 0x4;
+
+ constexpr uint8_t INSIDE_STATE = Union ? INSIDE_TILE : OUTSIDE_TILE;
+ constexpr uint8_t OUTSIDE_STATE = Union ? OUTSIDE_TILE : INSIDE_TILE;
+
+ const ValueT insideBackground = Union ? -this->background() : this->background();
+ const ValueT outsideBackground = -insideBackground;
+
+ auto getTileFlag = [&](auto& valueIter) -> uint8_t
+ {
+ uint8_t flag(0);
+ const ValueT& value = valueIter.getValue();
+ if (value < zeroVal()) flag |= INSIDE_TILE;
+ else if (value > zeroVal()) flag |= OUTSIDE_TILE;
+ if (valueIter.isValueOn()) flag |= ACTIVE_TILE;
+ return flag;
+ };
+
+ std::unordered_map tiles;
+
+ if (root.getTableSize() > 0) {
+ for (auto valueIter = root.cbeginValueAll(); valueIter; ++valueIter) {
+ const Coord& key = valueIter.getCoord();
+ tiles.insert({key, getTileFlag(valueIter)});
+ }
+ }
+
+ // find all tiles values in other roots and replace outside tiles with inside tiles
+
+ for (TreeToMerge& mergeTree : mTreesToMerge) {
+ const auto* mergeRoot = mergeTree.rootPtr();
+ if (!mergeRoot) continue;
+ for (auto valueIter = mergeRoot->cbeginValueAll(); valueIter; ++valueIter) {
+ const Coord& key = valueIter.getCoord();
+ auto it = tiles.find(key);
+ if (it == tiles.end()) {
+ // if no tile with this key, insert it
+ tiles.insert({key, getTileFlag(valueIter)});
+ } else {
+ // replace an outside tile with an inside tile
+ const uint8_t flag = it->second;
+ if (flag & OUTSIDE_STATE) {
+ const uint8_t newFlag = getTileFlag(valueIter);
+ if (newFlag & INSIDE_STATE) {
+ it->second = newFlag;
+ }
+ }
+ }
+ }
+ }
+
+ // insert all inside tiles
+
+ for (auto it : tiles) {
+ const uint8_t flag = it.second;
+ if (flag & INSIDE_STATE) {
+ const Coord& key = it.first;
+ const bool state = flag & ACTIVE_TILE;
+ root.addTile(key, insideBackground, state);
+ }
+ }
+
+ std::unordered_set children;
+
+ if (root.getTableSize() > 0) {
+ for (auto childIter = root.cbeginChildOn(); childIter; ++childIter) {
+ const Coord& key = childIter.getCoord();
+ children.insert(key);
+ }
+ }
+
+ bool continueRecurse = false;
+
+ // find all children in other roots and insert them if a child or tile with this key
+ // does not already exist or if the child will replace an outside tile
+
+ for (TreeToMerge& mergeTree : mTreesToMerge) {
+ const auto* mergeRoot = mergeTree.rootPtr();
+ if (!mergeRoot) continue;
+ for (auto childIter = mergeRoot->cbeginChildOn(); childIter; ++childIter) {
+ const Coord& key = childIter.getCoord();
+
+ // if child already exists, merge recursion will need to continue to resolve conflict
+ if (children.count(key)) {
+ continueRecurse = true;
+ continue;
+ }
+
+ // if an inside tile exists, do nothing
+ auto it = tiles.find(key);
+ if (it != tiles.end() && it->second == INSIDE_STATE) continue;
+
+ auto childPtr = mergeTree.template stealOrDeepCopyNode(key);
+ if (childPtr) root.addChild(childPtr.release());
+
+ children.insert(key);
+ }
+ }
+
+ // insert all outside tiles that don't replace an inside tile or a child node
+
+ for (auto it : tiles) {
+ const uint8_t flag = it.second;
+ if (flag & OUTSIDE_STATE) {
+ const Coord& key = it.first;
+ if (!children.count(key)) {
+ const bool state = flag & ACTIVE_TILE;
+ root.addTile(key, outsideBackground, state);
+ }
+ }
+ }
+
+ return continueRecurse;
+}
+
+template
+template
+bool CsgUnionOrIntersectionOp::operator()(NodeT& node, size_t) const
+{
+ using NonConstNodeT = typename std::remove_const::type;
+
+ if (this->empty()) return false;
+
+ const ValueT insideBackground = Union ? -this->background() : this->background();
+ const ValueT outsideBackground = -insideBackground;
+
+ using NodeMaskT = typename NodeT::NodeMaskType;
+
+ // store temporary masks to track inside and outside tile states
+ NodeMaskT validTile;
+ NodeMaskT invalidTile;
+
+ auto isValid = [](const ValueT& value)
+ {
+ return Union ? value < zeroVal() : value > zeroVal();
+ };
+
+ auto isInvalid = [](const ValueT& value)
+ {
+ return Union ? value > zeroVal() : value < zeroVal();
+ };
+
+ for (auto iter = node.cbeginValueAll(); iter; ++iter) {
+ if (isValid(iter.getValue())) {
+ validTile.setOn(iter.pos());
+ } else if (isInvalid(iter.getValue())) {
+ invalidTile.setOn(iter.pos());
+ }
+ }
+
+ bool continueRecurse = false;
+
+ for (TreeToMerge& mergeTree : mTreesToMerge) {
+
+ auto* mergeNode = mergeTree.template probeConstNode(node.origin());
+
+ if (!mergeNode) continue;
+
+ // iterate over all tiles
+
+ for (auto iter = mergeNode->cbeginValueAll(); iter; ++iter) {
+ Index pos = iter.pos();
+ // source node contains an inside tile, so ignore
+ if (validTile.isOn(pos)) continue;
+ // this node contains an inside tile, so turn into an inside tile
+ if (isValid(iter.getValue())) {
+ node.addTile(pos, insideBackground, iter.isValueOn());
+ validTile.setOn(pos);
+ }
+ }
+
+ // iterate over all child nodes
+
+ for (auto iter = mergeNode->cbeginChildOn(); iter; ++iter) {
+ Index pos = iter.pos();
+ const Coord& ijk = iter.getCoord();
+ // source node contains an inside tile, so ensure other node has no child
+ if (validTile.isOn(pos)) {
+ mergeTree.template addTile(ijk, outsideBackground, false);
+ } else if (invalidTile.isOn(pos)) {
+ auto childPtr = mergeTree.template stealOrDeepCopyNode(ijk);
+ if (childPtr) node.addChild(childPtr.release());
+ invalidTile.setOff(pos);
+ } else {
+ // if both source and target are child nodes, merge recursion needs to continue
+ // along this branch to resolve the conflict
+ continueRecurse = true;
+ }
+ }
+ }
+
+ return continueRecurse;
+}
+
+template
+bool CsgUnionOrIntersectionOp::operator()(LeafT& leaf, size_t) const
+{
+ using LeafT = typename TreeT::LeafNodeType;
+ using ValueT = typename LeafT::ValueType;
+ using BufferT = typename LeafT::Buffer;
+
+ if (this->empty()) return false;
+
+ const ValueT background = Union ? this->background() : -this->background();
+
+ // if buffer is not out-of-core and empty, leaf node must have only been
+ // partially constructed, so allocate and fill with background value
+
+ merge_internal::UnallocatedBuffer::allocateAndFill(
+ leaf.buffer(), background);
+
+ for (TreeToMerge& mergeTree : mTreesToMerge) {
+ const LeafT* mergeLeaf = mergeTree.template probeConstNode(leaf.origin());
+ if (!mergeLeaf) continue;
+ // if buffer is not out-of-core yet empty, leaf node must have only been
+ // partially constructed, so skip merge
+ if (merge_internal::UnallocatedBuffer::isPartiallyConstructed(
+ mergeLeaf->buffer())) {
+ continue;
+ }
+
+ for (Index i = 0 ; i < LeafT::SIZE; i++) {
+ const ValueT& newValue = mergeLeaf->getValue(i);
+ const bool doMerge = Union ? newValue < leaf.getValue(i) : newValue > leaf.getValue(i);
+ if (doMerge) {
+ leaf.setValueOnly(i, newValue);
+ leaf.setActiveState(i, mergeLeaf->isValueOn(i));
+ }
+ }
+ }
+
+ return false;
+}
+
+template
+const typename CsgUnionOrIntersectionOp::ValueT&
+CsgUnionOrIntersectionOp::background() const
+{
+ // this operator is only intended to be used with foreachTopDown()
+ assert(mBackground);
+ return *mBackground;
+}
+
+
+////////////////////////////////////////
+
+
+template
+bool CsgDifferenceOp::operator()(RootT& root, size_t) const
+{
+ // store the background values
+ if (!mBackground) mBackground = &root.background();
+ if (!mOtherBackground) mOtherBackground = &mTree.rootPtr()->background();
+
+ // find all tile values in this root and track inside/outside and active state
+ // note that level sets should never contain active tiles, but we handle them anyway
+
+ constexpr uint8_t ACTIVE_TILE = 0x1;
+ constexpr uint8_t INSIDE_TILE = 0x2;
+ constexpr uint8_t CHILD = 0x4;
+
+ auto getTileFlag = [&](auto& valueIter) -> uint8_t
+ {
+ uint8_t flag(0);
+ const ValueT& value = valueIter.getValue();
+ if (value < zeroVal()) flag |= INSIDE_TILE;
+ if (valueIter.isValueOn()) flag |= ACTIVE_TILE;
+ return flag;
+ };
+
+ std::unordered_map flags;
+
+ if (root.getTableSize() > 0) {
+ for (auto valueIter = root.cbeginValueAll(); valueIter; ++valueIter) {
+ const Coord& key = valueIter.getCoord();
+ const uint8_t flag = getTileFlag(valueIter);
+ if (flag & INSIDE_TILE) {
+ flags.insert({key, getTileFlag(valueIter)});
+ }
+ }
+
+ for (auto childIter = root.cbeginChildOn(); childIter; ++childIter) {
+ const Coord& key = childIter.getCoord();
+ flags.insert({key, CHILD});
+ }
+ }
+
+ bool continueRecurse = false;
+
+ const auto* mergeRoot = mTree.rootPtr();
+
+ if (mergeRoot) {
+ for (auto valueIter = mergeRoot->cbeginValueAll(); valueIter; ++valueIter) {
+ const Coord& key = valueIter.getCoord();
+ const uint8_t flag = getTileFlag(valueIter);
+ if (flag & INSIDE_TILE) {
+ auto it = flags.find(key);
+ if (it != flags.end()) {
+ const bool state = flag & ACTIVE_TILE;
+ root.addTile(key, this->background(), state);
+ }
+ }
+ }
+
+ for (auto childIter = mergeRoot->cbeginChildOn(); childIter; ++childIter) {
+ const Coord& key = childIter.getCoord();
+ auto it = flags.find(key);
+ if (it != flags.end()) {
+ const uint8_t otherFlag = it->second;
+ if (otherFlag & CHILD) {
+ // if child already exists, merge recursion will need to continue to resolve conflict
+ continueRecurse = true;
+ } else if (otherFlag & INSIDE_TILE) {
+ auto childPtr = mTree.template stealOrDeepCopyNode(key);
+ if (childPtr) {
+ childPtr->resetBackground(this->otherBackground(), this->background());
+ childPtr->negate();
+ root.addChild(childPtr.release());
+ }
+ }
+ }
+ }
+ }
+
+ return continueRecurse;
+}
+
+template
+template
+bool CsgDifferenceOp::operator()(NodeT& node, size_t) const
+{
+ using NonConstNodeT = typename std::remove_const::type;
+
+ using NodeMaskT = typename NodeT::NodeMaskType;
+
+ // store temporary mask to track inside tile state
+
+ NodeMaskT insideTile;
+ for (auto iter = node.cbeginValueAll(); iter; ++iter) {
+ if (iter.getValue() < zeroVal()) {
+ insideTile.setOn(iter.pos());
+ }
+ }
+
+ bool continueRecurse = false;
+
+ auto* mergeNode = mTree.template probeConstNode(node.origin());
+
+ if (!mergeNode) return continueRecurse;
+
+ // iterate over all tiles
+
+ for (auto iter = mergeNode->cbeginValueAll(); iter; ++iter) {
+ Index pos = iter.pos();
+ if (iter.getValue() < zeroVal()) {
+ if (insideTile.isOn(pos) || node.isChildMaskOn(pos)) {
+ node.addTile(pos, this->background(), iter.isValueOn());
+ }
+ }
+ }
+
+ // iterate over all children
+
+ for (auto iter = mergeNode->cbeginChildOn(); iter; ++iter) {
+ Index pos = iter.pos();
+ const Coord& ijk = iter.getCoord();
+ if (insideTile.isOn(pos)) {
+ auto childPtr = mTree.template stealOrDeepCopyNode(ijk);
+ if (childPtr) {
+ childPtr->resetBackground(this->otherBackground(), this->background());
+ childPtr->negate();
+ node.addChild(childPtr.release());
+ }
+ } else if (node.isChildMaskOn(pos)) {
+ // if both source and target are child nodes, merge recursion needs to continue
+ // along this branch to resolve the conflict
+ continueRecurse = true;
+ }
+ }
+
+ return continueRecurse;
+}
+
+template
+bool CsgDifferenceOp::operator()(LeafT& leaf, size_t) const
+{
+ using LeafT = typename TreeT::LeafNodeType;
+ using ValueT = typename LeafT::ValueType;
+ using BufferT = typename LeafT::Buffer;
+
+ // if buffer is not out-of-core and empty, leaf node must have only been
+ // partially constructed, so allocate and fill with background value
+
+ merge_internal::UnallocatedBuffer::allocateAndFill(
+ leaf.buffer(), this->background());
+
+ const LeafT* mergeLeaf = mTree.template probeConstNode(leaf.origin());
+ if (!mergeLeaf) return false;
+
+ // if buffer is not out-of-core yet empty, leaf node must have only been
+ // partially constructed, so skip merge
+
+ if (merge_internal::UnallocatedBuffer::isPartiallyConstructed(
+ mergeLeaf->buffer())) {
+ return false;
+ }
+
+ for (Index i = 0 ; i < LeafT::SIZE; i++) {
+ const ValueT& aValue = leaf.getValue(i);
+ ValueT bValue = math::negative(mergeLeaf->getValue(i));
+ if (aValue < bValue) { // a = max(a, -b)
+ leaf.setValueOnly(i, bValue);
+ leaf.setActiveState(i, mergeLeaf->isValueOn(i));
+ }
+ }
+
+ return false;
+}
+
+template
+const typename CsgDifferenceOp::ValueT&
+CsgDifferenceOp::background() const
+{
+ // this operator is only intended to be used with foreachTopDown()
+ assert(mBackground);
+ return *mBackground;
+}
+
+template
+const typename CsgDifferenceOp::ValueT&
+CsgDifferenceOp::otherBackground() const
+{
+ // this operator is only intended to be used with foreachTopDown()
+ assert(mOtherBackground);
+ return *mOtherBackground;
+}
+
+
+} // namespace tools
+} // namespace OPENVDB_VERSION_NAME
+} // namespace openvdb
+
+#endif // OPENVDB_TOOLS_MERGE_HAS_BEEN_INCLUDED
diff --git a/openvdb/openvdb/tree/LeafManager.h b/openvdb/openvdb/tree/LeafManager.h
index 06b913659b..696cfda929 100644
--- a/openvdb/openvdb/tree/LeafManager.h
+++ b/openvdb/openvdb/tree/LeafManager.h
@@ -16,9 +16,11 @@
#define OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED
#include
+#include // for NodeChain
#include
#include
#include
+#include
#include
#include
@@ -192,10 +194,6 @@ class LeafManager
, mLeafCount(0)
, mAuxBufferCount(0)
, mAuxBuffersPerLeaf(auxBuffersPerLeaf)
- , mLeafs(nullptr)
- , mAuxBuffers(nullptr)
- , mTask(nullptr)
- , mIsMaster(true)
{
this->rebuild(serial);
}
@@ -209,10 +207,8 @@ class LeafManager
, mLeafCount(end-begin)
, mAuxBufferCount(0)
, mAuxBuffersPerLeaf(auxBuffersPerLeaf)
- , mLeafs(new LeafType*[mLeafCount])
- , mAuxBuffers(nullptr)
- , mTask(nullptr)
- , mIsMaster(true)
+ , mLeafPtrs(new LeafType*[mLeafCount])
+ , mLeafs(mLeafPtrs.get())
{
size_t n = mLeafCount;
LeafType **target = mLeafs, **source = begin;
@@ -231,17 +227,10 @@ class LeafManager
, mLeafs(other.mLeafs)
, mAuxBuffers(other.mAuxBuffers)
, mTask(other.mTask)
- , mIsMaster(false)
{
}
- virtual ~LeafManager()
- {
- if (mIsMaster) {
- delete [] mLeafs;
- delete [] mAuxBuffers;
- }
- }
+ virtual ~LeafManager() { }
/// @brief (Re)initialize by resizing (if necessary) and repopulating the leaf array
/// and by deleting existing auxiliary buffers and allocating new ones.
@@ -250,7 +239,7 @@ class LeafManager
/// of corresponding leaf node buffers.
void rebuild(bool serial=false)
{
- this->initLeafArray();
+ this->initLeafArray(serial);
this->initAuxBuffers(serial);
}
//@{
@@ -285,10 +274,10 @@ class LeafManager
void removeAuxBuffers() { this->rebuildAuxBuffers(0); }
/// @brief Remove the auxiliary buffers and rebuild the leaf array.
- void rebuildLeafArray()
+ void rebuildLeafArray(bool serial = false)
{
this->removeAuxBuffers();
- this->initLeafArray();
+ this->initLeafArray(serial);
}
/// @brief Return the total number of allocated auxiliary buffers.
@@ -548,13 +537,8 @@ class LeafManager
transform.run(this->leafRange(grainSize), threaded);
}
-
- /// @brief Insert pointers to nodes of the specified type into the array.
- /// @details The type of node pointer is defined by the type
- /// ArrayT::value_type. If the node type is a LeafNode the nodes
- /// are inserted from this LeafManager, else of the corresponding tree.
template
- void getNodes(ArrayT& array)
+ [[deprecated("Use Tree::getNodes()")]] void getNodes(ArrayT& array)
{
using T = typename ArrayT::value_type;
static_assert(std::is_pointer::value, "argument to getNodes() must be a pointer array");
@@ -571,12 +555,8 @@ class LeafManager
OPENVDB_NO_UNREACHABLE_CODE_WARNING_END
}
- /// @brief Insert node pointers of the specified type into the array.
- /// @details The type of node pointer is defined by the type
- /// ArrayT::value_type. If the node type is a LeafNode the nodes
- /// are inserted from this LeafManager, else of the corresponding tree.
template
- void getNodes(ArrayT& array) const
+ [[deprecated("Use Tree::getNodes()")]] void getNodes(ArrayT& array) const
{
using T = typename ArrayT::value_type;
static_assert(std::is_pointer::value, "argument to getNodes() must be a pointer array");
@@ -635,34 +615,102 @@ class LeafManager
private:
- // This a simple wrapper for a c-style array so it mimics the api
- // of a std container, e.g. std::vector or std::deque, and can be
- // passed to Tree::getNodes().
- struct MyArray {
- using value_type = LeafType*;//required by Tree::getNodes
- value_type* ptr;
- MyArray(value_type* array) : ptr(array) {}
- void push_back(value_type leaf) { *ptr++ = leaf; }//required by Tree::getNodes
- };
-
- void initLeafArray()
+ void initLeafArray(bool serial = false)
{
- const size_t leafCount = mTree->leafCount();
+ // Build an array of all nodes that have leaf nodes as their immediate children
+
+ using NodeChainT = typename NodeChain::Type;
+ using NonConstLeafParentT = typename NodeChainT::template Get*Level=*/1>;
+ using LeafParentT = typename CopyConstness::Type;
+
+ std::deque leafParents;
+ mTree->getNodes(leafParents);
+
+ // Compute the leaf counts for each node
+
+ std::vector leafCounts;
+ if (serial) {
+ leafCounts.reserve(leafParents.size());
+ for (LeafParentT* leafParent : leafParents) {
+ leafCounts.push_back(leafParent->childCount());
+ }
+ } else {
+ leafCounts.resize(leafParents.size());
+ tbb::parallel_for(
+ // with typical node sizes and SSE enabled, there are only a handful
+ // of instructions executed per-operation with a default grainsize
+ // of 1, so increase to 64 to reduce parallel scheduling overhead
+ tbb::blocked_range(0, leafParents.size(), /*grainsize=*/64),
+ [&](tbb::blocked_range& range)
+ {
+ for (size_t i = range.begin(); i < range.end(); i++) {
+ leafCounts[i] = leafParents[i]->childCount();
+ }
+ }
+ );
+ }
+
+ // Turn leaf counts into a cumulative histogram and obtain total leaf count
+
+ for (size_t i = 1; i < leafCounts.size(); i++) {
+ leafCounts[i] += leafCounts[i-1];
+ }
+
+ const size_t leafCount = leafCounts.empty() ? 0 : leafCounts.back();
+
+ // Allocate (or deallocate) the leaf pointer array
+
if (leafCount != mLeafCount) {
- delete [] mLeafs;
- mLeafs = (leafCount == 0) ? nullptr : new LeafType*[leafCount];
+ if (leafCount > 0) {
+ mLeafPtrs.reset(new LeafType*[leafCount]);
+ mLeafs = mLeafPtrs.get();
+ } else {
+ mLeafPtrs.reset();
+ mLeafs = nullptr;
+ }
mLeafCount = leafCount;
}
- MyArray a(mLeafs);
- mTree->getNodes(a);
+
+ if (mLeafCount == 0) return;
+
+ // Populate the leaf node pointers
+
+ if (serial) {
+ LeafType** leafPtr = mLeafs;
+ for (LeafParentT* leafParent : leafParents) {
+ for (auto iter = leafParent->beginChildOn(); iter; ++iter) {
+ *leafPtr++ = &iter.getValue();
+ }
+ }
+ } else {
+ tbb::parallel_for(
+ tbb::blocked_range(0, leafParents.size()),
+ [&](tbb::blocked_range& range)
+ {
+ size_t i = range.begin();
+ LeafType** leafPtr = mLeafs;
+ if (i > 0) leafPtr += leafCounts[i-1];
+ for ( ; i < range.end(); i++) {
+ for (auto iter = leafParents[i]->beginChildOn(); iter; ++iter) {
+ *leafPtr++ = &iter.getValue();
+ }
+ }
+ }
+ );
+ }
}
void initAuxBuffers(bool serial)
{
const size_t auxBufferCount = mLeafCount * mAuxBuffersPerLeaf;
if (auxBufferCount != mAuxBufferCount) {
- delete [] mAuxBuffers;
- mAuxBuffers = (auxBufferCount == 0) ? nullptr : new NonConstBufferType[auxBufferCount];
+ if (auxBufferCount > 0) {
+ mAuxBufferPtrs.reset(new NonConstBufferType[auxBufferCount]);
+ mAuxBuffers = mAuxBufferPtrs.get();
+ } else {
+ mAuxBufferPtrs.reset();
+ mAuxBuffers = nullptr;
+ }
mAuxBufferCount = auxBufferCount;
}
this->syncAllBuffers(serial);
@@ -745,14 +793,14 @@ class LeafManager
template
struct LeafReducer
{
- LeafReducer(LeafOp &leafOp) : mLeafOp(&leafOp), mOwnsOp(false)
+ LeafReducer(LeafOp &leafOp) : mLeafOp(&leafOp)
{
}
LeafReducer(const LeafReducer &other, tbb::split)
- : mLeafOp(new LeafOp(*(other.mLeafOp), tbb::split())), mOwnsOp(true)
+ : mLeafOpPtr(std::make_unique(*(other.mLeafOp), tbb::split()))
+ , mLeafOp(mLeafOpPtr.get())
{
}
- ~LeafReducer() { if (mOwnsOp) delete mLeafOp; }
void run(const LeafRange& range, bool threaded)
{
threaded ? tbb::parallel_reduce(range, *this) : (*this)(range);
@@ -763,8 +811,8 @@ class LeafManager
for (typename LeafRange::Iterator it = range.begin(); it; ++it) op(*it, it.pos());
}
void join(const LeafReducer& other) { mLeafOp->join(*(other.mLeafOp)); }
- LeafOp *mLeafOp;
- const bool mOwnsOp;
+ std::unique_ptr mLeafOpPtr;
+ LeafOp *mLeafOp = nullptr;
};// LeafReducer
// Helper class to compute a prefix sum of offsets to active voxels
@@ -790,12 +838,13 @@ class LeafManager
using FuncType = typename std::function;
- TreeType* mTree;
- size_t mLeafCount, mAuxBufferCount, mAuxBuffersPerLeaf;
- LeafType** mLeafs;//array of LeafNode pointers
- NonConstBufferType* mAuxBuffers;//array of auxiliary buffers
- FuncType mTask;
- const bool mIsMaster;
+ TreeType* mTree;
+ size_t mLeafCount, mAuxBufferCount, mAuxBuffersPerLeaf;
+ std::unique_ptr mLeafPtrs;
+ LeafType** mLeafs = nullptr;//array of LeafNode pointers
+ std::unique_ptr mAuxBufferPtrs;
+ NonConstBufferType* mAuxBuffers = nullptr;//array of auxiliary buffers
+ FuncType mTask = nullptr;
};//end of LeafManager class
diff --git a/openvdb/openvdb/tree/NodeManager.h b/openvdb/openvdb/tree/NodeManager.h
index 8cca7f2575..21fc5661b6 100644
--- a/openvdb/openvdb/tree/NodeManager.h
+++ b/openvdb/openvdb/tree/NodeManager.h
@@ -3,13 +3,12 @@
/// @file tree/NodeManager.h
///
-/// @author Ken Museth
+/// @authors Ken Museth, Dan Bailey
///
/// @brief NodeManager produces linear arrays of all tree nodes
/// allowing for efficient threading and bottom-up processing.
///
/// @note A NodeManager can be constructed from a Tree or LeafManager.
-/// The latter is slightly more efficient since the cached leaf nodes will be reused.
#ifndef OPENVDB_TREE_NODEMANAGER_HAS_BEEN_INCLUDED
#define OPENVDB_TREE_NODEMANAGER_HAS_BEEN_INCLUDED
@@ -31,9 +30,23 @@ template
+class DynamicNodeManager;
+
+
////////////////////////////////////////
+// This is a dummy node filtering class used by the NodeManager class to match
+// the internal filtering interface used by the DynamicNodeManager.
+struct NodeFilter
+{
+ static bool valid(size_t) { return true; }
+}; // struct NodeFilter
+
+
/// @brief This class caches tree nodes of a specific type in a linear array.
///
/// @note It is for internal use and should rarely be used directly.
@@ -41,22 +54,135 @@ template
class NodeList
{
public:
- using value_type = NodeT*;
- using ListT = std::deque;
+ NodeList() = default;
+
+ NodeT& operator()(size_t n) const { assert(n
+ bool initRootChildren(RootT& root)
+ {
+ // Allocate (or deallocate) the node pointer array
- NodeT*& operator[](size_t n) { assert(n 0) {
+ mNodePtrs.reset(new NodeT*[nodeCount]);
+ mNodes = mNodePtrs.get();
+ } else {
+ mNodePtrs.reset();
+ mNodes = nullptr;
+ }
+ mNodeCount = nodeCount;
+ }
- void clear() { mList.clear(); }
+ if (mNodeCount == 0) return false;
+
+ // Populate the node pointers
- void resize(size_t n) { mList.resize(n); }
+ NodeT** nodePtr = mNodes;
+ for (auto iter = root.beginChildOn(); iter; ++iter) {
+ *nodePtr++ = &iter.getValue();
+ }
+
+ return true;
+ }
+
+ // initialize this node list from another node list containing the parent nodes
+ template
+ bool initNodeChildren(ParentsT& parents, const NodeFilterT& nodeFilter = NodeFilterT(), bool serial = false)
+ {
+ // Compute the node counts for each node
+
+ std::vector nodeCounts;
+ if (serial) {
+ nodeCounts.reserve(parents.nodeCount());
+ for (size_t i = 0; i < parents.nodeCount(); i++) {
+ if (!nodeFilter.valid(i)) nodeCounts.push_back(0);
+ else nodeCounts.push_back(parents(i).childCount());
+ }
+ } else {
+ nodeCounts.resize(parents.nodeCount());
+ tbb::parallel_for(
+ // with typical node sizes and SSE enabled, there are only a handful
+ // of instructions executed per-operation with a default grainsize
+ // of 1, so increase to 64 to reduce parallel scheduling overhead
+ tbb::blocked_range(0, parents.nodeCount(), /*grainsize=*/64),
+ [&](tbb::blocked_range& range)
+ {
+ for (Index64 i = range.begin(); i < range.end(); i++) {
+ if (!nodeFilter.valid(i)) nodeCounts[i] = 0;
+ else nodeCounts[i] = parents(i).childCount();
+ }
+ }
+ );
+ }
+
+ // Turn node counts into a cumulative histogram and obtain total node count
+
+ for (size_t i = 1; i < nodeCounts.size(); i++) {
+ nodeCounts[i] += nodeCounts[i-1];
+ }
+
+ const size_t nodeCount = nodeCounts.empty() ? 0 : nodeCounts.back();
+
+ // Allocate (or deallocate) the node pointer array
+
+ if (nodeCount != mNodeCount) {
+ if (nodeCount > 0) {
+ mNodePtrs.reset(new NodeT*[nodeCount]);
+ mNodes = mNodePtrs.get();
+ } else {
+ mNodePtrs.reset();
+ mNodes = nullptr;
+ }
+ mNodeCount = nodeCount;
+ }
+
+ if (mNodeCount == 0) return false;
+
+ // Populate the node pointers
+
+ if (serial) {
+ NodeT** nodePtr = mNodes;
+ for (size_t i = 0; i < parents.nodeCount(); i++) {
+ if (!nodeFilter.valid(i)) continue;
+ for (auto iter = parents(i).beginChildOn(); iter; ++iter) {
+ *nodePtr++ = &iter.getValue();
+ }
+ }
+ } else {
+ tbb::parallel_for(
+ tbb::blocked_range(0, parents.nodeCount()),
+ [&](tbb::blocked_range& range)
+ {
+ Index64 i = range.begin();
+ NodeT** nodePtr = mNodes;
+ if (i > 0) nodePtr += nodeCounts[i-1];
+ for ( ; i < range.end(); i++) {
+ if (!nodeFilter.valid(i)) continue;
+ for (auto iter = parents(i).beginChildOn(); iter; ++iter) {
+ *nodePtr++ = &iter.getValue();
+ }
+ }
+ }
+ );
+ }
+
+ return true;
+ }
class NodeRange
{
@@ -152,10 +278,42 @@ class NodeList
transform.run(this->nodeRange(grainSize), threaded);
}
+ // identical to foreach except the operator() method has a node index
+ template
+ void foreachWithIndex(const NodeOp& op, bool threaded = true, size_t grainSize=1)
+ {
+ NodeTransformer transform(op);
+ transform.run(this->nodeRange(grainSize), threaded);
+ }
+
+ // identical to reduce except the operator() method has a node index
+ template
+ void reduceWithIndex(NodeOp& op, bool threaded = true, size_t grainSize=1)
+ {
+ NodeReducer transform(op);
+ transform.run(this->nodeRange(grainSize), threaded);
+ }
+
private:
+ // default execution in the NodeManager ignores the node index
+ // given by the iterator position
+ struct OpWithoutIndex
+ {
+ template
+ static void eval(T& node, typename NodeRange::Iterator& iter) { node(*iter); }
+ };
+
+ // execution in the DynamicNodeManager matches that of the LeafManager in
+ // passing through the node index given by the iterator position
+ struct OpWithIndex
+ {
+ template
+ static void eval(T& node, typename NodeRange::Iterator& iter) { node(*iter, iter.pos()); }
+ };
+
// Private struct of NodeList that performs parallel_for
- template
+ template
struct NodeTransformer
{
NodeTransformer(const NodeOp& nodeOp) : mNodeOp(nodeOp)
@@ -167,43 +325,48 @@ class NodeList
}
void operator()(const NodeRange& range) const
{
- for (typename NodeRange::Iterator it = range.begin(); it; ++it) mNodeOp(*it);
+ for (typename NodeRange::Iterator it = range.begin(); it; ++it) {
+ OpT::template eval(mNodeOp, it);
+ }
}
- const NodeOp mNodeOp;
+ const NodeOp& mNodeOp;
};// NodeList::NodeTransformer
// Private struct of NodeList that performs parallel_reduce
- template
+ template
struct NodeReducer
{
- NodeReducer(NodeOp& nodeOp) : mNodeOp(&nodeOp), mOwnsOp(false)
+ NodeReducer(NodeOp& nodeOp) : mNodeOp(&nodeOp)
{
}
- NodeReducer(const NodeReducer& other, tbb::split) :
- mNodeOp(new NodeOp(*(other.mNodeOp), tbb::split())), mOwnsOp(true)
+ NodeReducer(const NodeReducer& other, tbb::split)
+ : mNodeOpPtr(std::make_unique(*(other.mNodeOp), tbb::split()))
+ , mNodeOp(mNodeOpPtr.get())
{
}
- ~NodeReducer() { if (mOwnsOp) delete mNodeOp; }
void run(const NodeRange& range, bool threaded = true)
{
threaded ? tbb::parallel_reduce(range, *this) : (*this)(range);
}
void operator()(const NodeRange& range)
{
- NodeOp &op = *mNodeOp;
- for (typename NodeRange::Iterator it = range.begin(); it; ++it) op(*it);
+ for (typename NodeRange::Iterator it = range.begin(); it; ++it) {
+ OpT::template eval(*mNodeOp, it);
+ }
}
void join(const NodeReducer& other)
{
mNodeOp->join(*(other.mNodeOp));
}
- NodeOp *mNodeOp;
- const bool mOwnsOp;
+ std::unique_ptr mNodeOpPtr;
+ NodeOp *mNodeOp = nullptr;
};// NodeList::NodeReducer
protected:
- ListT mList;
+ size_t mNodeCount = 0;
+ std::unique_ptr mNodePtrs;
+ NodeT** mNodes = nullptr;
};// NodeList
@@ -218,25 +381,25 @@ template
class NodeManagerLink
{
public:
- NodeManagerLink() {}
+ using NonConstChildNodeType = typename NodeT::ChildNodeType;
+ using ChildNodeType = typename CopyConstness::Type;
- virtual ~NodeManagerLink() {}
+ NodeManagerLink() = default;
void clear() { mList.clear(); mNext.clear(); }
- template
- void init(ParentT& parent, TreeOrLeafManagerT& tree)
+ template
+ void initRootChildren(RootT& root, bool serial = false)
{
- parent.getNodes(mList);
- for (size_t i=0, n=mList.nodeCount(); i
- void rebuild(ParentT& parent)
+ template
+ void initNodeChildren(ParentsT& parents, bool serial = false)
{
- mList.clear();
- parent.getNodes(mList);
- for (size_t i=0, n=mList.nodeCount(); i mList;
- NodeManagerLink mNext;
+ NodeManagerLink mNext;
};// NodeManagerLink class
@@ -290,15 +453,16 @@ template
class NodeManagerLink
{
public:
- NodeManagerLink() {}
-
- virtual ~NodeManagerLink() {}
+ NodeManagerLink() = default;
/// @brief Clear all the cached tree nodes
void clear() { mList.clear(); }
- template
- void rebuild(ParentT& parent) { mList.clear(); parent.getNodes(mList); }
+ template
+ void initRootChildren(RootT& root, bool /*serial*/ = false) { mList.initRootChildren(root); }
+
+ template