From 8325b8ba590dcb6074008dce4b9189d6ad9da73f Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Tue, 15 Dec 2020 12:22:13 -0800 Subject: [PATCH 01/21] Fix undeclared size_t error in gcc10 by adding missing cstddef header Signed-off-by: Dan Bailey --- openvdb/openvdb/version.h | 1 + 1 file changed, 1 insertion(+) diff --git a/openvdb/openvdb/version.h b/openvdb/openvdb/version.h index 480ff5f2b8..79b55e370d 100644 --- a/openvdb/openvdb/version.h +++ b/openvdb/openvdb/version.h @@ -43,6 +43,7 @@ #define OPENVDB_VERSION_HAS_BEEN_INCLUDED #include "Platform.h" +#include // size_t #include // uint32_t From 9ffa9bd6363bc594402dc219d383cc127ecbc0e1 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Tue, 15 Dec 2020 22:07:17 -0800 Subject: [PATCH 02/21] Add new GCC 10 and Clang 10 builds to CI Signed-off-by: Dan Bailey --- .github/workflows/build.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d7c518f11..e9d119052e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,6 +127,40 @@ jobs: - name: test run: ./ci/test.sh + testabi8gcc10: + runs-on: ubuntu-20.04 + env: + CXX: g++-10 + steps: + - uses: actions/checkout@v1 + - name: install_boost + run: sudo apt-get -q install -y libboost-dev libboost-system-dev libboost-iostreams-dev + - name: install_tbb + run: sudo apt-get -q install -y libtbb-dev + - name: install_gtest + run: sudo apt-get -q install -y libgtest-dev + - name: build + run: ./ci/build.sh Release 8 OFF None "core,test" -DUSE_ZLIB=OFF -DDISABLE_DEPENDENCY_VERSION_CHECKS=ON -DCMAKE_INSTALL_PREFIX=`pwd` + - name: test + run: ./ci/test.sh + + testabi8clang10: + runs-on: ubuntu-20.04 + env: + CXX: clang++-10 + steps: + - uses: actions/checkout@v1 + - name: install_boost + run: sudo apt-get -q install -y libboost-dev libboost-system-dev libboost-iostreams-dev + - name: install_tbb + run: sudo apt-get -q install -y libtbb-dev + - name: install_gtest + run: sudo apt-get -q install -y libgtest-dev + - name: build + run: ./ci/build.sh Release 8 OFF None "core,test" -DUSE_ZLIB=OFF -DDISABLE_DEPENDENCY_VERSION_CHECKS=ON -DCMAKE_INSTALL_PREFIX=`pwd` + - name: test + run: ./ci/test.sh + testwindows2019md: runs-on: windows-2019 env: From 1b053d74fd2782bf226baa86c64d31ff7f9b2efe Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Wed, 23 Dec 2020 20:58:13 -0800 Subject: [PATCH 03/21] Fix determinism bug in NodeManager Signed-off-by: Dan Bailey --- openvdb/openvdb/tree/NodeManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvdb/openvdb/tree/NodeManager.h b/openvdb/openvdb/tree/NodeManager.h index 21fc5661b6..f32036a7da 100644 --- a/openvdb/openvdb/tree/NodeManager.h +++ b/openvdb/openvdb/tree/NodeManager.h @@ -329,7 +329,7 @@ class NodeList OpT::template eval(mNodeOp, it); } } - const NodeOp& mNodeOp; + const NodeOp mNodeOp; };// NodeList::NodeTransformer // Private struct of NodeList that performs parallel_reduce From e25a3aec6be0c7d7e4543f222d5ed7b620bdc135 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Wed, 23 Dec 2020 21:11:37 -0800 Subject: [PATCH 04/21] OVDB-150: Bump version to 7.2.1 Signed-off-by: Dan Bailey --- CHANGES | 10 ++++++++++ doc/CMakeLists.txt | 2 +- doc/changes.txt | 15 +++++++++++++++ openvdb/openvdb/version.h | 2 +- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index bcf2a05365..06f201c92d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,16 @@ OpenVDB Version History ======================= +Version 7.2.1 - December 23, 2020 + + Bug fixes: + - Fixed a determinism bug in NodeManager when using non-thread-safe + functor members. + + Build: + - Added a missing header include to resolve an undefined size_t build error + on GCC10. + Version 7.2.0 - December 9, 2020 New features: diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index d821e70ed9..7e155e0f6d 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.2.0") +set(DOXYGEN_PROJECT_NUMBER "7.2.1") set(DOXYGEN_PROJECT_BRIEF "") set(DOXYGEN_FILE_PATTERNS "*.h") # headers only set(DOXYGEN_IMAGE_PATH "doc/img") diff --git a/doc/changes.txt b/doc/changes.txt index e8b346f212..3175832504 100644 --- a/doc/changes.txt +++ b/doc/changes.txt @@ -2,6 +2,21 @@ @page changes Release Notes +@htmlonly @endhtmlonly +@par +Version 7.2.1 - December 23, 2020 + +@par +Bug fixes: +- Fixed a determinism bug in @vdblink::tree::NodeManager NodeManager@endlink + when using non-thread-safe functor members. + +@par +Build: +- Added a missing header include to resolve an undefined size_t build error + on GCC10. + + @htmlonly @endhtmlonly @par Version 7.2.0 - December 9, 2020 diff --git a/openvdb/openvdb/version.h b/openvdb/openvdb/version.h index 79b55e370d..81a82c9c0f 100644 --- a/openvdb/openvdb/version.h +++ b/openvdb/openvdb/version.h @@ -50,7 +50,7 @@ // Library major, minor and patch version numbers #define OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER 7 #define OPENVDB_LIBRARY_MINOR_VERSION_NUMBER 2 -#define OPENVDB_LIBRARY_PATCH_VERSION_NUMBER 0 +#define OPENVDB_LIBRARY_PATCH_VERSION_NUMBER 1 // If OPENVDB_ABI_VERSION_NUMBER is already defined (e.g., via -DOPENVDB_ABI_VERSION_NUMBER=N) // use that ABI version. Otherwise, use this library version's default ABI. From 44ad7a9c5c832e692f04317245db350fb6cc5fb2 Mon Sep 17 00:00:00 2001 From: jlait Date: Mon, 14 Dec 2020 11:36:26 -0500 Subject: [PATCH 05/21] VDB To Spheres SOP had bad logic in the resolution of obsolete parameters, it was using the mere existence of a parm to trigger an update if the world size was set. Obsolete parms exist even if the .hip file lacked a parm for them; the only way to tell if it was an old .hip file is by checking factory defaults. Signed-off-by: jlait --- openvdb_houdini/openvdb_houdini/SOP_OpenVDB_To_Spheres.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_To_Spheres.cc b/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_To_Spheres.cc index f78271317a..04ecabe30a 100644 --- a/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_To_Spheres.cc +++ b/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_To_Spheres.cc @@ -241,7 +241,9 @@ SOP_OpenVDB_To_Spheres::resolveObsoleteParms(PRM_ParmList* obsoleteParms) resolveRenamedParm(*obsoleteParms, "minradius", "radiusmin"); // If world units are enabled, use the old world-space radius bounds if they exist. - if (worldUnits && obsoleteParms->getParmPtr("minradiusworld")) { + if (worldUnits + && obsoleteParms->getParmPtr("minradiusworld") + && !obsoleteParms->getParmPtr("minradiusworld")->isFactoryDefault()) { setFloat("radiusmin", 0, time, obsoleteParms->evalFloat("minradiusworld", 0, time)); } { From 8c4c2d21e4372d646319aa314a237d9af20cf9dc Mon Sep 17 00:00:00 2001 From: jlait Date: Fri, 18 Dec 2020 15:13:24 -0500 Subject: [PATCH 06/21] Attempt to also fix the lack of default check on OpenVDB Write Signed-off-by: jlait --- openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Write.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Write.cc b/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Write.cc index 70883497dd..9a32fa135f 100644 --- a/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Write.cc +++ b/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Write.cc @@ -196,7 +196,8 @@ SOP_OpenVDB_Write::resolveObsoleteParms(PRM_ParmList* obsoleteParms) } #else #ifdef OPENVDB_USE_ZLIB - if (nullptr != obsoleteParms->getParmPtr("compression")) { + if (nullptr != obsoleteParms->getParmPtr("compression") + && !obsoleteParms->getParmPtr("compression")->isFactoryDefault()) { UT_String compression; obsoleteParms->evalString(compression, "compression", 0, /*time=*/0.0); setInt("compress_zip", 0, 0.0, (compression == "zip" ? 1 : 0)); From e85976691dcfaf61d81372f991d64e35a8d393a5 Mon Sep 17 00:00:00 2001 From: jlait Date: Sat, 19 Dec 2020 11:52:32 -0500 Subject: [PATCH 07/21] Replace tab with spaces. Signed-off-by: jlait --- openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Write.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Write.cc b/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Write.cc index 9a32fa135f..c575d18fac 100644 --- a/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Write.cc +++ b/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Write.cc @@ -197,7 +197,7 @@ SOP_OpenVDB_Write::resolveObsoleteParms(PRM_ParmList* obsoleteParms) #else #ifdef OPENVDB_USE_ZLIB if (nullptr != obsoleteParms->getParmPtr("compression") - && !obsoleteParms->getParmPtr("compression")->isFactoryDefault()) { + && !obsoleteParms->getParmPtr("compression")->isFactoryDefault()) { UT_String compression; obsoleteParms->evalString(compression, "compression", 0, /*time=*/0.0); setInt("compress_zip", 0, 0.0, (compression == "zip" ? 1 : 0)); From 505cff4a841390359e8976778a9718eaba894ba4 Mon Sep 17 00:00:00 2001 From: Tom Cnops Date: Tue, 13 Oct 2020 11:58:39 +0200 Subject: [PATCH 08/21] Fix order of operations Signed-off-by: Tom Cnops --- openvdb/openvdb/tools/GridTransformer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvdb/openvdb/tools/GridTransformer.h b/openvdb/openvdb/tools/GridTransformer.h index 5c7856d091..b2898a2d82 100644 --- a/openvdb/openvdb/tools/GridTransformer.h +++ b/openvdb/openvdb/tools/GridTransformer.h @@ -546,7 +546,7 @@ GridTransformer::GridTransformer(const Mat4R& xform): if (local_util::decompose(mTransform, scale, rotate, translate)) { // If the transform can be decomposed into affine components, // use them to set up a mipmapping-like scheme for downsampling. - init(mPivot, scale, rotate, translate, "srt", "zyx"); + init(mPivot, scale, rotate, translate, "rst", "zyx"); } } From c399e6aa014174658f0c0875961b29f0048288ae Mon Sep 17 00:00:00 2001 From: Tom Cnops Date: Thu, 17 Dec 2020 20:01:58 +0100 Subject: [PATCH 09/21] Add test to check that GridTransformer transform matches constructor argument Signed-off-by: Tom Cnops --- openvdb/openvdb/unittest/TestGridTransformer.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openvdb/openvdb/unittest/TestGridTransformer.cc b/openvdb/openvdb/unittest/TestGridTransformer.cc index 0d3c37faba..3eb5567580 100644 --- a/openvdb/openvdb/unittest/TestGridTransformer.cc +++ b/openvdb/openvdb/unittest/TestGridTransformer.cc @@ -277,6 +277,8 @@ TEST_F(TestGridTransformer, testDecomposition) outM.setTranslation(outT); EXPECT_TRUE(outM.eq(m)); } + tools::GridTransformer transformer(m); + CPPUNIT_ASSERT(transformer.getTransform().eq(m)); } } } From b73e1edf78ffd6caa7d5e7d55854e1d66438e106 Mon Sep 17 00:00:00 2001 From: Tom Cnops Date: Thu, 17 Dec 2020 21:11:38 +0100 Subject: [PATCH 10/21] Nest assert in its own scope Signed-off-by: Tom Cnops --- openvdb/openvdb/unittest/TestGridTransformer.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openvdb/openvdb/unittest/TestGridTransformer.cc b/openvdb/openvdb/unittest/TestGridTransformer.cc index 3eb5567580..bf16d0d298 100644 --- a/openvdb/openvdb/unittest/TestGridTransformer.cc +++ b/openvdb/openvdb/unittest/TestGridTransformer.cc @@ -277,8 +277,11 @@ TEST_F(TestGridTransformer, testDecomposition) outM.setTranslation(outT); EXPECT_TRUE(outM.eq(m)); } - tools::GridTransformer transformer(m); - CPPUNIT_ASSERT(transformer.getTransform().eq(m)); + { + tools::GridTransformer transformer(m); + const bool transform_unchanged = transformer.getTransform().eq(m); + CPPUNIT_ASSERT(transform_unchanged); + } } } } From 356779781b4519baed828de00d60e76a77f44087 Mon Sep 17 00:00:00 2001 From: Tom Cnops Date: Fri, 18 Dec 2020 00:11:17 +0100 Subject: [PATCH 11/21] Use gtest Signed-off-by: Tom Cnops --- openvdb/openvdb/unittest/TestGridTransformer.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openvdb/openvdb/unittest/TestGridTransformer.cc b/openvdb/openvdb/unittest/TestGridTransformer.cc index bf16d0d298..a0d2e26070 100644 --- a/openvdb/openvdb/unittest/TestGridTransformer.cc +++ b/openvdb/openvdb/unittest/TestGridTransformer.cc @@ -277,11 +277,9 @@ TEST_F(TestGridTransformer, testDecomposition) outM.setTranslation(outT); EXPECT_TRUE(outM.eq(m)); } - { - tools::GridTransformer transformer(m); - const bool transform_unchanged = transformer.getTransform().eq(m); - CPPUNIT_ASSERT(transform_unchanged); - } + tools::GridTransformer transformer(m); + const bool transformUnchanged = transformer.getTransform().eq(m); + EXPECT_TRUE(transformUnchanged); } } } From 3df274e1f97e3bbdba6fba3f6108a906ac9d0451 Mon Sep 17 00:00:00 2001 From: jlait Date: Thu, 7 Jan 2021 16:05:18 -0500 Subject: [PATCH 12/21] Use a templated function to avoid invoking csg operations on bool grids. These aren't supported on MSVC as the unary negation operation is invalid for bool. Signed-off-by: jlait --- .../openvdb_houdini/SOP_OpenVDB_Combine.cc | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Combine.cc b/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Combine.cc index c2bb8c68bf..a40e084524 100644 --- a/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Combine.cc +++ b/openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Combine.cc @@ -1158,6 +1158,22 @@ struct SOP_OpenVDB_Combine::CombineOp } } + template + void doUnion(GridT &result, GridT &temp) + { + openvdb::tools::csgUnion(result, temp); + } + template + void doIntersection(GridT &result, GridT &temp) + { + openvdb::tools::csgIntersection(result, temp); + } + template + void doDifference(GridT &result, GridT &temp) + { + openvdb::tools::csgDifference(result, temp); + } + // Combine two grids of the same type. template void combineSameType() @@ -1265,19 +1281,19 @@ struct SOP_OpenVDB_Combine::CombineOp case OP_UNION: MulAdd(aMult).process(*aGrid, resultGrid); MulAdd(bMult).process(*bGrid, tempGrid); - openvdb::tools::csgUnion(*resultGrid, *tempGrid); + doUnion(*resultGrid, *tempGrid); break; case OP_INTERSECTION: MulAdd(aMult).process(*aGrid, resultGrid); MulAdd(bMult).process(*bGrid, tempGrid); - openvdb::tools::csgIntersection(*resultGrid, *tempGrid); + doIntersection(*resultGrid, *tempGrid); break; case OP_DIFFERENCE: MulAdd(aMult).process(*aGrid, resultGrid); MulAdd(bMult).process(*bGrid, tempGrid); - openvdb::tools::csgDifference(*resultGrid, *tempGrid); + doDifference(*resultGrid, *tempGrid); break; case OP_REPLACE: @@ -1428,6 +1444,19 @@ struct SOP_OpenVDB_Combine::CombineOp } }; // struct CombineOp +template <> +void SOP_OpenVDB_Combine::CombineOp::doUnion(openvdb::BoolGrid &result, openvdb::BoolGrid &temp) +{ +} +template <> +void SOP_OpenVDB_Combine::CombineOp::doIntersection(openvdb::BoolGrid &result, openvdb::BoolGrid &temp) +{ +} +template <> +void SOP_OpenVDB_Combine::CombineOp::doDifference(openvdb::BoolGrid &result, openvdb::BoolGrid &temp) +{ +} + template template From 569a9666e6a830bbf4c3d6490d3ae609c570cee3 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Thu, 21 Jan 2021 23:14:41 -0800 Subject: [PATCH 13/21] Fix CSG intersection Signed-off-by: Dan Bailey --- openvdb/openvdb/tools/Merge.h | 66 ++++++++++++++++++++++++++- openvdb/openvdb/unittest/TestMerge.cc | 28 ++++++++---- 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/openvdb/openvdb/tools/Merge.h b/openvdb/openvdb/tools/Merge.h index 9fdc2ae43b..6014fe7895 100644 --- a/openvdb/openvdb/tools/Merge.h +++ b/openvdb/openvdb/tools/Merge.h @@ -501,11 +501,64 @@ struct UnallocatedBuffer template bool CsgUnionOrIntersectionOp::operator()(RootT& root, size_t) const { + const bool Intersect = !Union; + if (this->empty()) return false; // store the background value if (!mBackground) mBackground = &root.background(); + // does the key exist in the root node? + auto keyExistsInRoot = [&](const Coord& key) -> bool + { + return root.getValueDepth(key) > -1; + }; + + // does the key exist in all merge tree root nodes? + auto keyExistsInAllTrees = [&](const Coord& key) -> bool + { + for (TreeToMerge& mergeTree : mTreesToMerge) { + const auto* mergeRoot = mergeTree.rootPtr(); + if (!mergeRoot) return false; + if (mergeRoot->getValueDepth(key) == -1) return false; + } + return true; + }; + + // for intersection, delete any root node keys that are not present in all trees + if (Intersect) { + // find all tile coordinates to delete + std::vector toDelete; + for (auto valueIter = root.cbeginValueAll(); valueIter; ++valueIter) { + const Coord& key = valueIter.getCoord(); + if (!keyExistsInAllTrees(key)) toDelete.push_back(key); + } + // find all child coordinates to delete + for (auto childIter = root.cbeginChildOn(); childIter; ++childIter) { + const Coord& key = childIter.getCoord(); + if (!keyExistsInAllTrees(key)) toDelete.push_back(key); + } + // mark any background tiles as active to prevent them from being deleted + std::vector toPreserve; + for (auto valueIter = root.beginValueAll(); valueIter; ++valueIter) { + const Coord& key = valueIter.getCoord(); + // background tiles are always inactive + if (valueIter.isValueOff() && + math::isApproxEqual(*valueIter, root.background())) { + toPreserve.push_back(key); + valueIter.setValueOn(); + } + } + // only mechanism to delete elements in root node is to delete background tiles, + // so insert background tiles (which will replace any child nodes) and then delete + for (Coord& key : toDelete) root.addTile(key, *mBackground, false); + root.eraseBackgroundTiles(); + // now re-insert the background tiles + for (Coord& key : toPreserve) { + root.addTile(key, *mBackground, false); + } + } + // 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 @@ -569,7 +622,10 @@ bool CsgUnionOrIntersectionOp::operator()(RootT& root, size_t) con if (flag & INSIDE_STATE) { const Coord& key = it.first; const bool state = flag & ACTIVE_TILE; - root.addTile(key, insideBackground, state); + // for intersection, only add the tile if the key already exists in the tree + if (Union || keyExistsInRoot(key)) { + root.addTile(key, insideBackground, state); + } } } @@ -593,6 +649,9 @@ bool CsgUnionOrIntersectionOp::operator()(RootT& root, size_t) con for (auto childIter = mergeRoot->cbeginChildOn(); childIter; ++childIter) { const Coord& key = childIter.getCoord(); + // for intersection, only add child nodes if the key already exists in the tree + if (Intersect && !keyExistsInRoot(key)) continue; + // if child already exists, merge recursion will need to continue to resolve conflict if (children.count(key)) { continueRecurse = true; @@ -618,7 +677,10 @@ bool CsgUnionOrIntersectionOp::operator()(RootT& root, size_t) con const Coord& key = it.first; if (!children.count(key)) { const bool state = flag & ACTIVE_TILE; - root.addTile(key, outsideBackground, state); + // for intersection, only add the tile if the key already exists in the tree + if (Union || keyExistsInRoot(key)) { + root.addTile(key, outsideBackground, state); + } } } } diff --git a/openvdb/openvdb/unittest/TestMerge.cc b/openvdb/openvdb/unittest/TestMerge.cc index 5503f34aa1..867b490081 100644 --- a/openvdb/openvdb/unittest/TestMerge.cc +++ b/openvdb/openvdb/unittest/TestMerge.cc @@ -838,6 +838,8 @@ TEST_F(TestMerge, testCsgIntersection) { // merge two different outside root tiles from one grid into an empty grid FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), -grid->background(), false); + root.addTile(Coord(8192, 0, 0), -grid->background(), false); FloatGrid::Ptr grid2 = createLevelSet(); auto& root2 = grid2->tree().root(); root2.addTile(Coord(0, 0, 0), grid->background(), false); @@ -865,11 +867,15 @@ TEST_F(TestMerge, testCsgIntersection) { // merge two different outside root tiles from two grids into an empty grid FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), /*background=*/-100.0f, false); + root.addTile(Coord(8192, 0, 0), /*background=*/-100.0f, false); FloatGrid::Ptr grid2 = createLevelSet(); auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), /*background=*/123.0f, false); + root2.addTile(Coord(8192, 0, 0), /*background=*/-100.0f, false); FloatGrid::Ptr grid3 = createLevelSet(); auto& root3 = grid3->tree().root(); - root2.addTile(Coord(0, 0, 0), /*background=*/123.0f, false); + root3.addTile(Coord(0, 0, 0), /*background=*/-100.0f, false); root3.addTile(Coord(8192, 0, 0), /*background=*/0.1f, true); std::vector trees{&grid2->tree(), &grid3->tree()}; @@ -894,11 +900,12 @@ TEST_F(TestMerge, testCsgIntersection) { // merge the same outside root tiles from two grids into an empty grid FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), -grid->background(), false); FloatGrid::Ptr grid2 = createLevelSet(); auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), grid->background(), true); FloatGrid::Ptr grid3 = createLevelSet(); auto& root3 = grid3->tree().root(); - root2.addTile(Coord(0, 0, 0), grid->background(), true); root3.addTile(Coord(0, 0, 0), grid->background(), false); std::vector trees{&grid2->tree(), &grid3->tree()}; @@ -913,6 +920,7 @@ TEST_F(TestMerge, testCsgIntersection) EXPECT_EQ(Index(0), getInactiveTileCount(root)); root.clear(); + root.addTile(Coord(0, 0, 0), -grid->background(), false); // reverse tree order std::vector trees2{&grid3->tree(), &grid2->tree()}; tools::CsgIntersectionOp mergeOp2(trees2, Steal()); @@ -968,14 +976,12 @@ TEST_F(TestMerge, testCsgIntersection) { // merge two grids with an outside and an inside tile, outside takes precedence FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), -grid->background(), true); FloatGrid::Ptr grid2 = createLevelSet(); auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), -grid->background(), true); - FloatGrid::Ptr grid3 = createLevelSet(); - auto& root3 = grid3->tree().root(); - root3.addTile(Coord(0, 0, 0), /*outside*/0.1f, false); + root2.addTile(Coord(0, 0, 0), /*outside*/0.1f, false); - std::vector trees{&grid2->tree(), &grid3->tree()}; + std::vector trees{&grid2->tree(), &grid2->tree()}; tools::CsgIntersectionOp mergeOp(trees, Steal()); tree::DynamicNodeManager nodeManager(grid->tree()); nodeManager.foreachTopDown(mergeOp); @@ -994,6 +1000,8 @@ TEST_F(TestMerge, testCsgIntersection) FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), -grid->background(), true); + root.addTile(Coord(8192, 0, 0), -grid->background(), true); FloatGrid::Ptr grid2 = createLevelSet(); auto& root2 = grid2->tree().root(); root2.addChild(new RootChildType(Coord(0, 0, 0), 1.0f, false)); @@ -1040,8 +1048,10 @@ TEST_F(TestMerge, testCsgIntersection) FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); root.addChild(new RootChildType(Coord(0, 0, 0), 123.0f, false)); + root.addTile(Coord(8192, 0, 0), -123.0f, true); FloatGrid::Ptr grid2 = createLevelSet(); auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), -123.0f, true); root2.addChild(new RootChildType(Coord(8192, 0, 0), 1.9f, false)); EXPECT_EQ(Index(1), getChildCount(root)); @@ -1159,8 +1169,8 @@ TEST_F(TestMerge, testCsgIntersection) { // merge a leaf node into an empty grid FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -1.0f, false); FloatGrid::Ptr grid2 = createLevelSet(); - grid2->tree().touchLeaf(Coord(0, 0, 0)); EXPECT_EQ(Index32(0), grid->tree().leafCount()); @@ -1234,8 +1244,8 @@ TEST_F(TestMerge, testCsgIntersection) { // merge a leaf node into an empty grid from a const grid FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -1.0f, false); FloatGrid::Ptr grid2 = createLevelSet(); - grid2->tree().touchLeaf(Coord(0, 0, 0)); EXPECT_EQ(Index32(0), grid->tree().leafCount()); From 91e495c2dbf1ea5c55b603d10f447c7c312910de Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Tue, 26 Jan 2021 22:02:04 -0800 Subject: [PATCH 14/21] Fixed bugs and improved unit tests Signed-off-by: Dan Bailey --- openvdb/openvdb/tools/Merge.h | 33 +- openvdb/openvdb/unittest/TestMerge.cc | 2323 +++++++++++++++++-------- 2 files changed, 1599 insertions(+), 757 deletions(-) diff --git a/openvdb/openvdb/tools/Merge.h b/openvdb/openvdb/tools/Merge.h index 6014fe7895..812b31c572 100644 --- a/openvdb/openvdb/tools/Merge.h +++ b/openvdb/openvdb/tools/Merge.h @@ -525,6 +525,9 @@ bool CsgUnionOrIntersectionOp::operator()(RootT& root, size_t) con return true; }; + // delete any background tiles + root.eraseBackgroundTiles(); + // for intersection, delete any root node keys that are not present in all trees if (Intersect) { // find all tile coordinates to delete @@ -538,25 +541,10 @@ bool CsgUnionOrIntersectionOp::operator()(RootT& root, size_t) con const Coord& key = childIter.getCoord(); if (!keyExistsInAllTrees(key)) toDelete.push_back(key); } - // mark any background tiles as active to prevent them from being deleted - std::vector toPreserve; - for (auto valueIter = root.beginValueAll(); valueIter; ++valueIter) { - const Coord& key = valueIter.getCoord(); - // background tiles are always inactive - if (valueIter.isValueOff() && - math::isApproxEqual(*valueIter, root.background())) { - toPreserve.push_back(key); - valueIter.setValueOn(); - } - } // only mechanism to delete elements in root node is to delete background tiles, // so insert background tiles (which will replace any child nodes) and then delete for (Coord& key : toDelete) root.addTile(key, *mBackground, false); root.eraseBackgroundTiles(); - // now re-insert the background tiles - for (Coord& key : toPreserve) { - root.addTile(key, *mBackground, false); - } } // find all tile values in this root and track inside/outside and active state @@ -663,6 +651,7 @@ bool CsgUnionOrIntersectionOp::operator()(RootT& root, size_t) con if (it != tiles.end() && it->second == INSIDE_STATE) continue; auto childPtr = mergeTree.template stealOrDeepCopyNode(key); + childPtr->resetBackground(mergeRoot->background(), root.background()); if (childPtr) root.addChild(childPtr.release()); children.insert(key); @@ -685,6 +674,9 @@ bool CsgUnionOrIntersectionOp::operator()(RootT& root, size_t) con } } + // finish by removing any background tiles + root.eraseBackgroundTiles(); + return continueRecurse; } @@ -754,7 +746,10 @@ bool CsgUnionOrIntersectionOp::operator()(NodeT& node, size_t) con mergeTree.template addTile(ijk, outsideBackground, false); } else if (invalidTile.isOn(pos)) { auto childPtr = mergeTree.template stealOrDeepCopyNode(ijk); - if (childPtr) node.addChild(childPtr.release()); + if (childPtr) { + childPtr->resetBackground(mergeTree.rootPtr()->background(), this->background()); + node.addChild(childPtr.release()); + } invalidTile.setOff(pos); } else { // if both source and target are child nodes, merge recursion needs to continue @@ -843,6 +838,9 @@ bool CsgDifferenceOp::operator()(RootT& root, size_t) const return flag; }; + // delete any background tiles + root.eraseBackgroundTiles(); + std::unordered_map flags; if (root.getTableSize() > 0) { @@ -897,6 +895,9 @@ bool CsgDifferenceOp::operator()(RootT& root, size_t) const } } + // finish by removing any background tiles + root.eraseBackgroundTiles(); + return continueRecurse; } diff --git a/openvdb/openvdb/unittest/TestMerge.cc b/openvdb/openvdb/unittest/TestMerge.cc index 867b490081..65c6981588 100644 --- a/openvdb/openvdb/unittest/TestMerge.cc +++ b/openvdb/openvdb/unittest/TestMerge.cc @@ -62,6 +62,15 @@ auto getChildCount = [](const auto& node) -> Index return node.childCount(); }; +auto hasOnlyInactiveNegativeBackgroundTiles = [](const auto& node) -> bool +{ + if (getActiveTileCount(node) > Index(0)) return false; + for (auto iter = node.cbeginValueAll(); iter; ++iter) { + if (iter.getValue() != -node.background()) return false; + } + return true; +}; + } // namespace @@ -246,6 +255,10 @@ TEST_F(TestMerge, testTreeToMerge) TEST_F(TestMerge, testCsgUnion) { + using RootChildType = FloatTree::RootNodeType::ChildNodeType; + using LeafParentType = RootChildType::ChildNodeType; + using LeafT = FloatTree::LeafNodeType; + { // construction FloatTree tree1; FloatTree tree2; @@ -295,6 +308,8 @@ TEST_F(TestMerge, testCsgUnion) } } + ///////////////////////////////////////////////////////////////////////// + { // empty merge trees FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); @@ -309,469 +324,829 @@ TEST_F(TestMerge, testCsgUnion) EXPECT_EQ(Index(0), root.getTableSize()); } - { // merge two different outside root tiles from one grid into an empty grid - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), grid->background(), false); - root2.addTile(Coord(8192, 0, 0), grid->background(), true); + ///////////////////////////////////////////////////////////////////////// - EXPECT_EQ(Index(2), root2.getTableSize()); - EXPECT_EQ(Index(2), getTileCount(root2)); - EXPECT_EQ(Index(1), getActiveTileCount(root2)); - EXPECT_EQ(Index(1), getInactiveTileCount(root2)); + { // test one tile or one child - // test container constructor here - std::vector trees{&grid2->tree()}; - tools::CsgUnionOp mergeOp(trees, Steal()); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // test one background tile + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(2), getTileCount(root)); - EXPECT_EQ(Index(1), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); - EXPECT_EQ(Index(0), getInsideTileCount(root)); - EXPECT_EQ(Index(2), getOutsideTileCount(root)); - } + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - { // merge two different outside root tiles from two grids into an empty grid - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - FloatGrid::Ptr grid3 = createLevelSet(); - auto& root3 = grid3->tree().root(); - root2.addTile(Coord(0, 0, 0), /*background=*/123.0f, false); - root3.addTile(Coord(8192, 0, 0), /*background=*/0.1f, true); + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } - std::vector trees{&grid2->tree(), &grid3->tree()}; - tools::CsgUnionOp mergeOp(trees, Steal()); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // test one background tile + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), grid2->background(), false); - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(2), getTileCount(root)); - EXPECT_EQ(Index(1), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); - EXPECT_EQ(Index(0), getInsideTileCount(root)); - EXPECT_EQ(Index(2), getOutsideTileCount(root)); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - // background values of merge trees should be ignored, only important - // that the values are greater than zero and thus an outside tile - for (auto iter = root.cbeginValueAll(); iter; ++iter) { - EXPECT_EQ(grid->background(), iter.getValue()); + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); } - } - { // merge the same outside root tiles from two grids into an empty grid - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - FloatGrid::Ptr grid3 = createLevelSet(); - auto& root3 = grid3->tree().root(); - root2.addTile(Coord(0, 0, 0), grid->background(), true); - root3.addTile(Coord(0, 0, 0), grid->background(), false); + { // test one background tile + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); - std::vector trees{&grid2->tree(), &grid3->tree()}; - tools::CsgUnionOp mergeOp(trees, Steal()); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(1), root.getTableSize()); - EXPECT_EQ(Index(1), getTileCount(root)); - // merge order is important - tile should be active - EXPECT_EQ(Index(1), getActiveTileCount(root)); - EXPECT_EQ(Index(0), getInactiveTileCount(root)); + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getTileCount(root)); + EXPECT_EQ(-grid->background(), grid->cbeginValueAll().getValue()); + } - root.clear(); - // reverse tree order - std::vector trees2{&grid3->tree(), &grid2->tree()}; - tools::CsgUnionOp mergeOp2(trees2, Steal()); - nodeManager.foreachTopDown(mergeOp2); - // merge order is important - tile should now be inactive - EXPECT_EQ(Index(0), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); - } + { // test one background tile + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), -grid2->background(), false); - { // merge an outside root tile to a grid which already has this tile - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), grid->background(), false); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), grid->background(), true); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getTileCount(root)); + EXPECT_EQ(-grid->background(), grid->cbeginValueAll().getValue()); + } - EXPECT_EQ(Index(1), root.getTableSize()); - EXPECT_EQ(Index(1), getTileCount(root)); - // tile in merge grid should not replace existing tile - tile should remain inactive - EXPECT_EQ(Index(0), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); - } + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid->background(), false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); - { // merge an inside root tile to a grid which has an outside tile, inside takes precedence - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), grid->background(), false); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), -123.0f, true); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(0), getInsideTileCount(root)); - EXPECT_EQ(Index(1), getOutsideTileCount(root)); + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getChildCount(root)); + EXPECT_EQ(grid->background(), root.cbeginChildOn()->getFirstValue()); + } - tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp, true); + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), -grid->background(), false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); - EXPECT_EQ(Index(1), root.getTableSize()); - EXPECT_EQ(Index(1), getTileCount(root)); - // tile in merge grid replace existing tile - tile should now be active and inside - EXPECT_EQ(Index(1), getActiveTileCount(root)); - EXPECT_EQ(Index(0), getInactiveTileCount(root)); - EXPECT_EQ(Index(1), getInsideTileCount(root)); - EXPECT_EQ(Index(0), getOutsideTileCount(root)); - } + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - { // merge two grids with an outside and an inside tile, inside takes precedence - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), grid->background(), true); - FloatGrid::Ptr grid3 = createLevelSet(); - auto& root3 = grid3->tree().root(); - root3.addTile(Coord(0, 0, 0), /*inside*/-0.1f, false); + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getChildCount(root)); + EXPECT_EQ(-grid->background(), root.cbeginChildOn()->getFirstValue()); + } - std::vector trees{&grid2->tree(), &grid3->tree()}; - tools::CsgUnionOp mergeOp(trees, Steal()); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), 1.0, false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); - EXPECT_EQ(Index(1), root.getTableSize()); - EXPECT_EQ(Index(1), getTileCount(root)); - // tile in merge grid should not replace existing tile - tile should remain inactive - EXPECT_EQ(Index(0), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); - EXPECT_EQ(Index(1), getInsideTileCount(root)); - EXPECT_EQ(Index(0), getOutsideTileCount(root)); - } + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - { // merge two child nodes into an empty grid - using RootChildType = FloatTree::RootNodeType::ChildNodeType; + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getChildCount(root)); + EXPECT_EQ(1.0, root.cbeginChildOn()->getFirstValue()); + } - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addChild(new RootChildType(Coord(0, 0, 0), 1.0f, false)); - root2.addChild(new RootChildType(Coord(8192, 0, 0), -123.0f, true)); + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), 1.0, true)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); - EXPECT_EQ(Index(2), root2.getTableSize()); - EXPECT_EQ(Index(0), getTileCount(root2)); - EXPECT_EQ(Index(2), getChildCount(root2)); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getChildCount(root)); + EXPECT_EQ(1.0, root.cbeginChildOn()->getFirstValue()); + } - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(0), getTileCount(root)); - EXPECT_EQ(Index(2), getChildCount(root)); - } + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), false)); - { // merge a child node into a grid with an outside tile - using RootChildType = FloatTree::RootNodeType::ChildNodeType; + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), 123.0f, true); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addChild(new RootChildType(Coord(0, 0, 0), 1.0f, false)); + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getChildCount(root)); + EXPECT_EQ(grid->background(), root.cbeginChildOn()->getFirstValue()); + } - EXPECT_EQ(Index(1), getTileCount(root)); - EXPECT_EQ(Index(1), getChildCount(root2)); + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), -grid2->background(), false)); - tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(1), root.getTableSize()); - EXPECT_EQ(Index(1), getChildCount(root)); - EXPECT_EQ(Index(0), getTileCount(root)); + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getChildCount(root)); + EXPECT_EQ(-grid->background(), root.cbeginChildOn()->getFirstValue()); + } + + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), 1.0, false)); + + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getChildCount(root)); + EXPECT_EQ(1.0, root.cbeginChildOn()->getFirstValue()); + } } - { // merge a child node into a grid with an existing child node - using RootChildType = FloatTree::RootNodeType::ChildNodeType; + ///////////////////////////////////////////////////////////////////////// - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addChild(new RootChildType(Coord(0, 0, 0), 123.0f, false)); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addChild(new RootChildType(Coord(8192, 0, 0), 1.9f, false)); + { // test two tiles - EXPECT_EQ(Index(1), getChildCount(root)); - EXPECT_EQ(Index(1), getChildCount(root2)); + { // test outside background tiles + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(); + grid2->tree().root().addTile(Coord(0, 0, 0), grid2->background(), false); - tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(2), getChildCount(root)); - EXPECT_TRUE(root.cbeginChildOn()->cbeginValueAll()); - EXPECT_EQ(123.0f, root.cbeginChildOn()->cbeginValueAll().getItem(0)); - EXPECT_EQ(1.9f, (++root.cbeginChildOn())->cbeginValueAll().getItem(0)); - } + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } - { // merge an inside tile and an outside tile into a grid with two child nodes - using RootChildType = FloatTree::RootNodeType::ChildNodeType; + { // test inside vs outside background tiles + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(); + grid2->tree().root().addTile(Coord(0, 0, 0), grid2->background(), false); - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addChild(new RootChildType(Coord(0, 0, 0), 123.0f, false)); - root.addChild(new RootChildType(Coord(8192, 0, 0), 1.9f, false)); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), 15.0f, false); // should not replace child - root2.addTile(Coord(8192, 0, 0), -25.0f, false); // should replace child + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(2), getChildCount(root)); - EXPECT_EQ(Index(2), getTileCount(root2)); + const auto& root = grid->tree().root(); + EXPECT_TRUE(hasOnlyInactiveNegativeBackgroundTiles(root)); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + } - tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // test inside vs outside background tiles + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(); + grid2->tree().root().addTile(Coord(0, 0, 0), -grid2->background(), false); - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(1), getChildCount(root)); - EXPECT_EQ(Index(1), getTileCount(root)); - EXPECT_TRUE(root.cbeginChildAll().isChildNode()); - EXPECT_TRUE(!(++root.cbeginChildAll()).isChildNode()); - EXPECT_EQ(123.0f, root.cbeginChildOn()->getFirstValue()); - // inside tile value replaced with negative background - EXPECT_EQ(-grid->background(), root.cbeginValueAll().getValue()); - } + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - { // merge two child nodes into a grid with an inside tile and an outside tile - using RootChildType = FloatTree::RootNodeType::ChildNodeType; + const auto& root = grid->tree().root(); + EXPECT_TRUE(hasOnlyInactiveNegativeBackgroundTiles(root)); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + } - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), 15.0f, false); // should be replaced by child - root.addTile(Coord(8192, 0, 0), -25.0f, false); // should not be replaced by child - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addChild(new RootChildType(Coord(0, 0, 0), 123.0f, false)); - root2.addChild(new RootChildType(Coord(8192, 0, 0), 1.9f, false)); + { // test inside background tiles + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(); + grid2->tree().root().addTile(Coord(0, 0, 0), -grid2->background(), false); - EXPECT_EQ(Index(2), getTileCount(root)); - EXPECT_EQ(Index(2), getChildCount(root2)); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + const auto& root = grid->tree().root(); + EXPECT_TRUE(hasOnlyInactiveNegativeBackgroundTiles(root)); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + } - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(1), getChildCount(root)); - EXPECT_EQ(Index(1), getTileCount(root)); - EXPECT_TRUE(root.cbeginChildAll().isChildNode()); - EXPECT_TRUE(!(++root.cbeginChildAll()).isChildNode()); - EXPECT_EQ(123.0f, root.cbeginChildOn()->getFirstValue()); - EXPECT_EQ(-grid->background(), root.cbeginValueAll().getValue()); - } + { // test outside background tiles (different background values) + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), grid2->background(), false); - { // merge two internal nodes into a grid with an inside tile and an outside tile - using RootChildType = FloatTree::RootNodeType::ChildNodeType; - using LeafParentType = RootChildType::ChildNodeType; + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - auto rootChild = std::make_unique(Coord(0, 0, 0), 123.0f, false); - rootChild->addTile(1, -14.0f, false); - root.addChild(rootChild.release()); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - auto rootChild2 = std::make_unique(Coord(0, 0, 0), 55.0f, false); + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } - rootChild2->addChild(new LeafParentType(Coord(0, 0, 0), 29.0f, false)); - rootChild2->addChild(new LeafParentType(Coord(0, 0, 128), 31.0f, false)); - rootChild2->addTile(2, 17.0f, true); - rootChild2->addTile(9, -19.0f, true); - root2.addChild(rootChild2.release()); + { // test inside vs outside background tiles (different background values) + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), grid2->background(), false); - EXPECT_EQ(Index(1), getInsideTileCount(*root.cbeginChildOn())); - EXPECT_EQ(Index(0), getActiveTileCount(*root.cbeginChildOn())); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(2), getChildCount(*root2.cbeginChildOn())); - EXPECT_EQ(Index(1), getInsideTileCount(*root2.cbeginChildOn())); - EXPECT_EQ(Index(2), getActiveTileCount(*root2.cbeginChildOn())); + const auto& root = grid->tree().root(); + EXPECT_TRUE(hasOnlyInactiveNegativeBackgroundTiles(root)); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + } - tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // test inside vs outside background tiles (different background values) + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), -grid2->background(), false); - EXPECT_EQ(Index(1), getChildCount(*root.cbeginChildOn())); - EXPECT_EQ(Index(2), getInsideTileCount(*root.cbeginChildOn())); - EXPECT_EQ(Index(1), getActiveTileCount(*root.cbeginChildOn())); - EXPECT_TRUE(root.cbeginChildOn()->isChildMaskOn(0)); - EXPECT_TRUE(!root.cbeginChildOn()->isChildMaskOn(1)); - EXPECT_EQ(29.0f, root.cbeginChildOn()->cbeginChildOn()->getFirstValue()); - EXPECT_EQ(-14.0f, root.cbeginChildOn()->cbeginValueAll().getValue()); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(0), getChildCount(*root2.cbeginChildOn())); - } + const auto& root = grid->tree().root(); + EXPECT_TRUE(hasOnlyInactiveNegativeBackgroundTiles(root)); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + } - { // merge two internal nodes into a grid with an inside tile and an outside tile - using RootChildType = FloatTree::RootNodeType::ChildNodeType; - using LeafParentType = RootChildType::ChildNodeType; + { // test inside background tiles (different background values) + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), -grid2->background(), false); - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - auto rootChild = std::make_unique(Coord(0, 0, 0), 123.0f, false); - rootChild->addTile(1, -14.0f, false); - root.addChild(rootChild.release()); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - auto rootChild2 = std::make_unique(Coord(0, 0, 0), 55.0f, false); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - rootChild2->addChild(new LeafParentType(Coord(0, 0, 0), 29.0f, false)); - rootChild2->addChild(new LeafParentType(Coord(0, 0, 128), 31.0f, false)); - rootChild2->addTile(2, 17.0f, true); - rootChild2->addTile(9, -19.0f, true); - root2.addChild(rootChild2.release()); + const auto& root = grid->tree().root(); + EXPECT_TRUE(hasOnlyInactiveNegativeBackgroundTiles(root)); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + } + } - EXPECT_EQ(Index(1), getInsideTileCount(*root.cbeginChildOn())); - EXPECT_EQ(Index(0), getActiveTileCount(*root.cbeginChildOn())); + ///////////////////////////////////////////////////////////////////////// - EXPECT_EQ(Index(2), getChildCount(*root2.cbeginChildOn())); - EXPECT_EQ(Index(1), getInsideTileCount(*root2.cbeginChildOn())); - EXPECT_EQ(Index(2), getActiveTileCount(*root2.cbeginChildOn())); + { // test one tile, one child - tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // test background tiles vs child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), false)); - EXPECT_EQ(Index(1), getChildCount(*root.cbeginChildOn())); - EXPECT_EQ(Index(2), getInsideTileCount(*root.cbeginChildOn())); - EXPECT_EQ(Index(1), getActiveTileCount(*root.cbeginChildOn())); - EXPECT_TRUE(root.cbeginChildOn()->isChildMaskOn(0)); - EXPECT_TRUE(!root.cbeginChildOn()->isChildMaskOn(1)); - EXPECT_EQ(29.0f, root.cbeginChildOn()->cbeginChildOn()->getFirstValue()); - EXPECT_EQ(-14.0f, root.cbeginChildOn()->cbeginValueAll().getValue()); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + } + + { // test background tiles vs child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), false)); - EXPECT_EQ(Index(0), getChildCount(*root2.cbeginChildOn())); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(Index(1), getChildCount(root)); + EXPECT_EQ(grid->background(), root.cbeginChildOn()->getFirstValue()); + } + + { // test background tiles vs child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), -grid2->background(), false)); + + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(Index(1), getChildCount(root)); + EXPECT_EQ(-grid->background(), root.cbeginChildOn()->getFirstValue()); + } + + { // test background tiles vs child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid->background(), false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), -grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + } } - { // merge a leaf node into an empty grid - FloatGrid::Ptr grid = createLevelSet(); - FloatGrid::Ptr grid2 = createLevelSet(); + ///////////////////////////////////////////////////////////////////////// - grid2->tree().touchLeaf(Coord(0, 0, 0)); + { // test two children - EXPECT_EQ(Index32(0), grid->tree().leafCount()); - EXPECT_EQ(Index32(1), grid2->tree().leafCount()); + { // test two child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid->background(), false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), true)); - tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index32(1), grid->tree().leafCount()); - EXPECT_EQ(Index32(0), grid2->tree().leafCount()); + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(grid->background(), root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(false, root.cbeginChildOn()->isValueOn(0)); + } + + { // test two child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid->background(), true)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), false)); + + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(grid->background(), root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(true, root.cbeginChildOn()->isValueOn(0)); + } + + { // test two child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), -grid->background(), false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), true)); + + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(-grid->background(), root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(false, root.cbeginChildOn()->isValueOn(0)); + } + + { // test two child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid->background(), true)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), -grid2->background(), false)); + + std::vector trees{&grid2->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(-grid->background(), root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(false, root.cbeginChildOn()->isValueOn(0)); + } } - { // merge a leaf node into a grid with a partially constructed leaf node - using LeafT = FloatTree::LeafNodeType; + ///////////////////////////////////////////////////////////////////////// - FloatGrid::Ptr grid = createLevelSet(); - FloatGrid::Ptr grid2 = createLevelSet(); + { // test multiple root node elements - grid->tree().addLeaf(new LeafT(PartialCreate(), Coord(0, 0, 0))); - auto* leaf = grid2->tree().touchLeaf(Coord(0, 0, 0)); - leaf->setValueOnly(10, -2.3f); + { // merge a child node into a grid with an existing child node + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addChild(new RootChildType(Coord(0, 0, 0), 1.0f, false)); + root.addTile(Coord(8192, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), grid2->background(), false); + root2.addChild(new RootChildType(Coord(8192, 0, 0), 2.0f, false)); + + tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(2), getChildCount(root)); + EXPECT_TRUE(root.cbeginChildOn()->cbeginValueAll()); + EXPECT_EQ(1.0f, root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(2.0f, (++root.cbeginChildOn())->getFirstValue()); + } - tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // merge a child node into a grid with an existing child node + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), grid->background(), false); + root.addChild(new RootChildType(Coord(8192, 0, 0), 2.0f, false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addChild(new RootChildType(Coord(0, 0, 0), 1.0f, false)); + root2.addTile(Coord(8192, 0, 0), grid2->background(), false); + + tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(2), getChildCount(root)); + EXPECT_TRUE(root.cbeginChildOn()->cbeginValueAll()); + EXPECT_EQ(1.0f, root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(2.0f, (++root.cbeginChildOn())->getFirstValue()); + } - const auto* testLeaf = grid->tree().probeConstLeaf(Coord(0, 0, 0)); - EXPECT_EQ(-2.3f, testLeaf->getValue(10)); + { // merge background tiles and child nodes + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addChild(new RootChildType(Coord(0, 0, 0), 1.0f, false)); + root.addTile(Coord(8192, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), -grid2->background(), false); + root2.addChild(new RootChildType(Coord(8192, 0, 0), 2.0f, false)); + + tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(2), getTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + EXPECT_EQ(-grid->background(), *(++root.cbeginValueOff())); + } } - { // merge three leaf nodes from different grids - FloatGrid::Ptr grid = createLevelSet(); - FloatGrid::Ptr grid2 = createLevelSet(); - FloatGrid::Ptr grid3 = createLevelSet(); + ///////////////////////////////////////////////////////////////////////// - auto* leaf = grid->tree().touchLeaf(Coord(0, 0, 0)); - auto* leaf2 = grid2->tree().touchLeaf(Coord(0, 0, 0)); - auto* leaf3 = grid3->tree().touchLeaf(Coord(0, 0, 0)); + { // test merging internal node children - // active state from the voxel with the minimum value preserved + { // merge two internal nodes into a grid with an inside tile and an outside tile + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + auto rootChild = std::make_unique(Coord(0, 0, 0), -123.0f, false); + rootChild->addTile(0, grid->background(), false); + root.addChild(rootChild.release()); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + auto rootChild2 = std::make_unique(Coord(0, 0, 0), 55.0f, false); + + rootChild2->addChild(new LeafParentType(Coord(0, 0, 0), 29.0f, false)); + rootChild2->addChild(new LeafParentType(Coord(0, 0, 128), 31.0f, false)); + rootChild2->addTile(2, -grid->background(), false); + root2.addChild(rootChild2.release()); + + tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(1), getChildCount(*root.cbeginChildOn())); + EXPECT_EQ(Index(0), getOutsideTileCount(*root.cbeginChildOn())); + EXPECT_TRUE(root.cbeginChildOn()->isChildMaskOn(0)); + EXPECT_TRUE(!root.cbeginChildOn()->isChildMaskOn(1)); + EXPECT_EQ(29.0f, root.cbeginChildOn()->cbeginChildOn()->getFirstValue()); + EXPECT_EQ(-123.0f, root.cbeginChildOn()->cbeginValueAll().getValue()); + } - leaf->setValueOnly(5, 4.0f); - leaf2->setValueOnly(5, 2.0f); - leaf2->setValueOn(5); - leaf3->setValueOnly(5, 3.0f); + { // merge two internal nodes into a grid with an inside tile and an outside tile + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + auto rootChild = std::make_unique(Coord(0, 0, 0), 123.0f, false); + root.addChild(rootChild.release()); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + auto rootChild2 = std::make_unique(Coord(0, 0, 0), -55.0f, false); + + rootChild2->addChild(new LeafParentType(Coord(0, 0, 0), 29.0f, false)); + rootChild2->addChild(new LeafParentType(Coord(0, 0, 128), 31.0f, false)); + rootChild2->addTile(2, -140.0f, false); + rootChild2->addTile(3, grid2->background(), false); + root2.addChild(rootChild2.release()); + + tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(2), getChildCount(*root.cbeginChildOn())); + EXPECT_EQ(Index(1), getOutsideTileCount(*root.cbeginChildOn())); + EXPECT_TRUE(root.cbeginChildOn()->isChildMaskOn(0)); + EXPECT_TRUE(root.cbeginChildOn()->isChildMaskOn(1)); + EXPECT_TRUE(!root.cbeginChildOn()->isChildMaskOn(2)); + EXPECT_TRUE(!root.cbeginChildOn()->isChildMaskOn(3)); + EXPECT_EQ(29.0f, root.cbeginChildOn()->cbeginChildOn()->getFirstValue()); + EXPECT_EQ(-grid->background(), root.cbeginChildOn()->cbeginValueAll().getItem(2)); + EXPECT_EQ(123.0f, root.cbeginChildOn()->cbeginValueAll().getItem(3)); + } + } - leaf->setValueOnly(7, 2.0f); - leaf->setValueOn(7); - leaf2->setValueOnly(7, 3.0f); - leaf3->setValueOnly(7, 4.0f); + ///////////////////////////////////////////////////////////////////////// - leaf->setValueOnly(9, 4.0f); - leaf->setValueOn(9); - leaf2->setValueOnly(9, 3.0f); - leaf3->setValueOnly(9, 2.0f); + { // test merging leaf nodes - std::vector trees{&grid2->tree(), &grid3->tree()}; - tools::CsgUnionOp mergeOp(trees, Steal()); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // merge a leaf node into an empty grid + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().touchLeaf(Coord(0, 0, 0)); - const auto* testLeaf = grid->tree().probeConstLeaf(Coord(0, 0, 0)); - EXPECT_EQ(2.0f, testLeaf->getValue(5)); - EXPECT_TRUE(testLeaf->isValueOn(5)); - EXPECT_EQ(2.0f, testLeaf->getValue(7)); - EXPECT_TRUE(testLeaf->isValueOn(7)); - EXPECT_EQ(2.0f, testLeaf->getValue(9)); - EXPECT_TRUE(!testLeaf->isValueOn(9)); + tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index32(1), grid->tree().leafCount()); + } + + { // merge a leaf node into a grid with an outside tile + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -10.0f, false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().touchLeaf(Coord(0, 0, 0)); + + tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + } + + { // merge a leaf node into a grid with an outside tile + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().touchLeaf(Coord(0, 0, 0)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), -10.0f, false); + + tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + } + + { // merge a leaf node into a grid with an internal node inside tile + FloatGrid::Ptr grid = createLevelSet(); + auto rootChild = std::make_unique(Coord(0, 0, 0), grid->background(), false); + grid->tree().root().addChild(rootChild.release()); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto* leaf = grid2->tree().touchLeaf(Coord(0, 0, 0)); + + leaf->setValueOnly(11, grid2->background()); + leaf->setValueOnly(12, -grid2->background()); + + tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index32(1), grid->tree().leafCount()); + EXPECT_EQ(Index32(0), grid2->tree().leafCount()); + + // test background values are remapped + + const auto* testLeaf = grid->tree().probeConstLeaf(Coord(0, 0, 0)); + EXPECT_EQ(grid->background(), testLeaf->getValue(11)); + EXPECT_EQ(-grid->background(), testLeaf->getValue(12)); + } + + { // merge a leaf node into a grid with a partially constructed leaf node + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(); + + grid->tree().addLeaf(new LeafT(PartialCreate(), Coord(0, 0, 0))); + auto* leaf = grid2->tree().touchLeaf(Coord(0, 0, 0)); + leaf->setValueOnly(10, -2.3f); + + tools::CsgUnionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto* testLeaf = grid->tree().probeConstLeaf(Coord(0, 0, 0)); + EXPECT_EQ(-2.3f, testLeaf->getValue(10)); + } + + { // merge three leaf nodes from different grids + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + FloatGrid::Ptr grid3 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/7); + + auto* leaf = grid->tree().touchLeaf(Coord(0, 0, 0)); + auto* leaf2 = grid2->tree().touchLeaf(Coord(0, 0, 0)); + auto* leaf3 = grid3->tree().touchLeaf(Coord(0, 0, 0)); + + // active state from the voxel with the minimum value preserved + + leaf->setValueOnly(5, 4.0f); + leaf2->setValueOnly(5, 2.0f); + leaf2->setValueOn(5); + leaf3->setValueOnly(5, 3.0f); + + leaf->setValueOnly(7, 2.0f); + leaf->setValueOn(7); + leaf2->setValueOnly(7, 3.0f); + leaf3->setValueOnly(7, 4.0f); + + leaf->setValueOnly(9, 4.0f); + leaf->setValueOn(9); + leaf2->setValueOnly(9, 3.0f); + leaf3->setValueOnly(9, 2.0f); + + std::vector trees{&grid2->tree(), &grid3->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto* testLeaf = grid->tree().probeConstLeaf(Coord(0, 0, 0)); + EXPECT_EQ(2.0f, testLeaf->getValue(5)); + EXPECT_TRUE(testLeaf->isValueOn(5)); + EXPECT_EQ(2.0f, testLeaf->getValue(7)); + EXPECT_TRUE(testLeaf->isValueOn(7)); + EXPECT_EQ(2.0f, testLeaf->getValue(9)); + EXPECT_TRUE(!testLeaf->isValueOn(9)); + } + + { // merge a leaf node into an empty grid from a const grid + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), 1.0f, false); + FloatGrid::Ptr grid2 = createLevelSet(); + grid2->tree().touchLeaf(Coord(0, 0, 0)); + + EXPECT_EQ(Index32(0), grid->tree().leafCount()); + EXPECT_EQ(Index32(1), grid2->tree().leafCount()); + + // merge from a const tree + + std::vector> treesToMerge; + treesToMerge.emplace_back(grid2->constTree(), DeepCopy()); + tools::CsgUnionOp mergeOp(treesToMerge); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index32(1), grid->tree().leafCount()); + // leaf has been deep copied not stolen + EXPECT_EQ(Index32(1), grid2->tree().leafCount()); + } } - { // merge a leaf node into an empty grid from a const grid - FloatGrid::Ptr grid = createLevelSet(); - FloatGrid::Ptr grid2 = createLevelSet(); + ///////////////////////////////////////////////////////////////////////// - grid2->tree().touchLeaf(Coord(0, 0, 0)); + { // merge multiple grids - EXPECT_EQ(Index32(0), grid->tree().leafCount()); - EXPECT_EQ(Index32(1), grid2->tree().leafCount()); + { // merge two background root tiles from two different grids + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), /*background=*/grid2->background(), false); + root2.addTile(Coord(8192, 0, 0), /*background=*/-grid2->background(), false); + FloatGrid::Ptr grid3 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/7); + auto& root3 = grid3->tree().root(); + root3.addTile(Coord(0, 0, 0), /*background=*/-grid3->background(), false); + root3.addTile(Coord(8192, 0, 0), /*background=*/grid3->background(), false); + + std::vector trees{&grid2->tree(), &grid3->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(2), getTileCount(root)); - // merge from a const tree + EXPECT_EQ(-grid->background(), root.cbeginValueAll().getValue()); + EXPECT_EQ(-grid->background(), (++root.cbeginValueAll()).getValue()); + } - std::vector> treesToMerge; - treesToMerge.emplace_back(grid2->constTree(), DeepCopy()); + { // merge two outside root tiles from two different grids + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), /*background=*/-10.0f, false); + root2.addTile(Coord(8192, 0, 0), /*background=*/grid2->background(), false); + FloatGrid::Ptr grid3 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/7); + auto& root3 = grid3->tree().root(); + root3.addTile(Coord(0, 0, 0), /*background=*/grid3->background(), false); + root3.addTile(Coord(8192, 0, 0), /*background=*/-11.0f, false); + + std::vector trees{&grid2->tree(), &grid3->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - tools::CsgUnionOp mergeOp(treesToMerge); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + EXPECT_EQ(Index(2), getTileCount(root)); - EXPECT_EQ(Index32(1), grid->tree().leafCount()); - // leaf has been deep copied not stolen - EXPECT_EQ(Index32(1), grid2->tree().leafCount()); + EXPECT_EQ(-grid->background(), root.cbeginValueAll().getValue()); + EXPECT_EQ(-grid->background(), (++root.cbeginValueAll()).getValue()); + } + + { // merge two active, outside root tiles from two different grids + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), /*background=*/grid->background(), false); + root.addTile(Coord(8192, 0, 0), /*background=*/grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), /*background=*/-10.0f, true); + root2.addTile(Coord(8192, 0, 0), /*background=*/grid2->background(), false); + FloatGrid::Ptr grid3 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/7); + auto& root3 = grid3->tree().root(); + root3.addTile(Coord(0, 0, 0), /*background=*/grid3->background(), false); + root3.addTile(Coord(8192, 0, 0), /*background=*/-11.0f, true); + + std::vector trees{&grid2->tree(), &grid3->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(2), getTileCount(root)); + + EXPECT_EQ(-grid->background(), root.cbeginValueAll().getValue()); + EXPECT_EQ(-grid->background(), (++root.cbeginValueAll()).getValue()); + } + + { // merge three root tiles, one of which is a background tile + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), grid->background(), true); + FloatGrid::Ptr grid2 = createLevelSet(); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), -grid2->background(), true); + FloatGrid::Ptr grid3 = createLevelSet(); + auto& root3 = grid3->tree().root(); + root3.addTile(Coord(0, 0, 0), -grid3->background(), false); + + std::vector trees{&grid2->tree(), &grid3->tree()}; + tools::CsgUnionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(1), getTileCount(root)); + EXPECT_EQ(-grid->background(), root.cbeginValueOn().getValue()); + } } } TEST_F(TestMerge, testCsgIntersection) { + using RootChildType = FloatTree::RootNodeType::ChildNodeType; + using LeafParentType = RootChildType::ChildNodeType; + using LeafT = FloatTree::LeafNodeType; + { // construction FloatTree tree1; FloatTree tree2; @@ -821,6 +1196,8 @@ TEST_F(TestMerge, testCsgIntersection) } } + ///////////////////////////////////////////////////////////////////////// + { // empty merge trees FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); @@ -835,440 +1212,868 @@ TEST_F(TestMerge, testCsgIntersection) EXPECT_EQ(Index(0), root.getTableSize()); } - { // merge two different outside root tiles from one grid into an empty grid - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), -grid->background(), false); - root.addTile(Coord(8192, 0, 0), -grid->background(), false); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), grid->background(), false); - root2.addTile(Coord(8192, 0, 0), grid->background(), true); + ///////////////////////////////////////////////////////////////////////// - EXPECT_EQ(Index(2), root2.getTableSize()); - EXPECT_EQ(Index(2), getTileCount(root2)); - EXPECT_EQ(Index(1), getActiveTileCount(root2)); - EXPECT_EQ(Index(1), getInactiveTileCount(root2)); + { // test one tile or one child - // test container constructor here - std::vector trees{&grid2->tree()}; - tools::CsgIntersectionOp mergeOp(trees, Steal()); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // test one background tile + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(2), getTileCount(root)); - EXPECT_EQ(Index(1), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); - EXPECT_EQ(Index(0), getInsideTileCount(root)); - EXPECT_EQ(Index(2), getOutsideTileCount(root)); + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test one background tile + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test one background tile + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test one background tile + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), -grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid->background(), false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), -grid->background(), false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), 1.0, false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), 1.0, true)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), false)); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), -grid2->background(), false)); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test one child node + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), 1.0, false)); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } } - { // merge two different outside root tiles from two grids into an empty grid - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), /*background=*/-100.0f, false); - root.addTile(Coord(8192, 0, 0), /*background=*/-100.0f, false); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), /*background=*/123.0f, false); - root2.addTile(Coord(8192, 0, 0), /*background=*/-100.0f, false); - FloatGrid::Ptr grid3 = createLevelSet(); - auto& root3 = grid3->tree().root(); - root3.addTile(Coord(0, 0, 0), /*background=*/-100.0f, false); - root3.addTile(Coord(8192, 0, 0), /*background=*/0.1f, true); - - std::vector trees{&grid2->tree(), &grid3->tree()}; - tools::CsgIntersectionOp mergeOp(trees, Steal()); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + ///////////////////////////////////////////////////////////////////////// - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(2), getTileCount(root)); - EXPECT_EQ(Index(1), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); - EXPECT_EQ(Index(0), getInsideTileCount(root)); - EXPECT_EQ(Index(2), getOutsideTileCount(root)); + { // test two tiles + + { // test outside background tiles + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(); + grid2->tree().root().addTile(Coord(0, 0, 0), grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test inside vs outside background tiles + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(); + grid2->tree().root().addTile(Coord(0, 0, 0), grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test inside vs outside background tiles + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(); + grid2->tree().root().addTile(Coord(0, 0, 0), -grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test inside background tiles + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(); + grid2->tree().root().addTile(Coord(0, 0, 0), -grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_TRUE(hasOnlyInactiveNegativeBackgroundTiles(root)); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); + } + + { // test outside background tiles (different background values) + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test inside vs outside background tiles (different background values) + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test inside vs outside background tiles (different background values) + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), -grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test inside background tiles (different background values) + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), -grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - // background values of merge trees should be ignored, only important - // that the values are greater than zero and thus an outside tile - for (auto iter = root.cbeginValueAll(); iter; ++iter) { - EXPECT_EQ(grid->background(), iter.getValue()); + const auto& root = grid->tree().root(); + EXPECT_TRUE(hasOnlyInactiveNegativeBackgroundTiles(root)); + EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(-grid->background(), *root.cbeginValueOff()); } } - { // merge the same outside root tiles from two grids into an empty grid - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), -grid->background(), false); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), grid->background(), true); - FloatGrid::Ptr grid3 = createLevelSet(); - auto& root3 = grid3->tree().root(); - root3.addTile(Coord(0, 0, 0), grid->background(), false); + ///////////////////////////////////////////////////////////////////////// - std::vector trees{&grid2->tree(), &grid3->tree()}; - tools::CsgIntersectionOp mergeOp(trees, Steal()); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // test one tile, one child - EXPECT_EQ(Index(1), root.getTableSize()); - EXPECT_EQ(Index(1), getTileCount(root)); - // merge order is important - tile should be active - EXPECT_EQ(Index(1), getActiveTileCount(root)); - EXPECT_EQ(Index(0), getInactiveTileCount(root)); + { // test background tiles vs child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), false)); - root.clear(); - root.addTile(Coord(0, 0, 0), -grid->background(), false); - // reverse tree order - std::vector trees2{&grid3->tree(), &grid2->tree()}; - tools::CsgIntersectionOp mergeOp2(trees2, Steal()); - nodeManager.foreachTopDown(mergeOp2); - // merge order is important - tile should now be inactive - EXPECT_EQ(Index(0), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test background tiles vs child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), false)); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(grid->background(), root.cbeginChildOn()->getFirstValue()); + } + + { // test background tiles vs child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), -grid2->background(), false)); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } + + { // test background tiles vs child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), -grid2->background(), false)); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(-grid->background(), root.cbeginChildOn()->getFirstValue()); + } + + { // test background tiles vs child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid->background(), false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), grid2->background(), false); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } } - { // merge an outside root tile to a grid which already has this tile - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), grid->background(), false); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), grid->background(), true); + ///////////////////////////////////////////////////////////////////////// - tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // test two children - EXPECT_EQ(Index(1), root.getTableSize()); - EXPECT_EQ(Index(1), getTileCount(root)); - // tile in merge grid should not replace existing tile - tile should remain inactive - EXPECT_EQ(Index(0), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); + { // test two child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid->background(), false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), true)); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(grid->background(), root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(false, root.cbeginChildOn()->isValueOn(0)); + } + + { // test two child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid->background(), true)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), false)); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(grid->background(), root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(true, root.cbeginChildOn()->isValueOn(0)); + } + + { // test two child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), -grid->background(), false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid2->background(), true)); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(grid->background(), root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(true, root.cbeginChildOn()->isValueOn(0)); + } + + { // test two child nodes + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addChild(new RootChildType(Coord(0, 0, 0), grid->background(), true)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addChild(new RootChildType(Coord(0, 0, 0), -grid2->background(), false)); + + std::vector trees{&grid2->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto& root = grid->tree().root(); + EXPECT_EQ(Index(0), getTileCount(root)); + EXPECT_EQ(grid->background(), root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(true, root.cbeginChildOn()->isValueOn(0)); + } } - { // merge an outside root tile to a grid which has an inside tile, outside takes precedence - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), -grid->background(), false); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), 123.0f, true); + ///////////////////////////////////////////////////////////////////////// - EXPECT_EQ(Index(1), getInsideTileCount(root)); - EXPECT_EQ(Index(0), getOutsideTileCount(root)); + { // test multiple root node elements - tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp, true); + { // merge a child node into a grid with an existing child node + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addChild(new RootChildType(Coord(0, 0, 0), 1.0f, false)); + root.addTile(Coord(8192, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), -grid2->background(), false); + root2.addChild(new RootChildType(Coord(8192, 0, 0), 2.0f, false)); + + tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(2), getChildCount(root)); + EXPECT_TRUE(root.cbeginChildOn()->cbeginValueAll()); + EXPECT_EQ(1.0f, root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(2.0f, (++root.cbeginChildOn())->getFirstValue()); + } - EXPECT_EQ(Index(1), root.getTableSize()); - EXPECT_EQ(Index(1), getTileCount(root)); - // tile in merge grid replace existing tile - tile should now be active and outside - EXPECT_EQ(Index(1), getActiveTileCount(root)); - EXPECT_EQ(Index(0), getInactiveTileCount(root)); - EXPECT_EQ(Index(0), getInsideTileCount(root)); - EXPECT_EQ(Index(1), getOutsideTileCount(root)); + { // merge a child node into a grid with an existing child node + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), -grid->background(), false); + root.addChild(new RootChildType(Coord(8192, 0, 0), 2.0f, false)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addChild(new RootChildType(Coord(0, 0, 0), 1.0f, false)); + root2.addTile(Coord(8192, 0, 0), -grid2->background(), false); + + tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(2), getChildCount(root)); + EXPECT_TRUE(root.cbeginChildOn()->cbeginValueAll()); + EXPECT_EQ(1.0f, root.cbeginChildOn()->getFirstValue()); + EXPECT_EQ(2.0f, (++root.cbeginChildOn())->getFirstValue()); + } + + { // merge background tiles and child nodes + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addChild(new RootChildType(Coord(0, 0, 0), 1.0f, false)); + root.addTile(Coord(8192, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), grid2->background(), false); + root2.addChild(new RootChildType(Coord(8192, 0, 0), 2.0f, false)); + + tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), getTileCount(root)); + } } - { // merge two grids with an outside and an inside tile, outside takes precedence - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), -grid->background(), true); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), /*outside*/0.1f, false); + ///////////////////////////////////////////////////////////////////////// - std::vector trees{&grid2->tree(), &grid2->tree()}; - tools::CsgIntersectionOp mergeOp(trees, Steal()); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // test merging internal node children - EXPECT_EQ(Index(1), root.getTableSize()); - EXPECT_EQ(Index(1), getTileCount(root)); - // tile in merge grid should not replace existing tile - tile should remain inactive - EXPECT_EQ(Index(0), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); - EXPECT_EQ(Index(0), getInsideTileCount(root)); - EXPECT_EQ(Index(1), getOutsideTileCount(root)); + { // merge two internal nodes into a grid with an inside tile and an outside tile + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + auto rootChild = std::make_unique(Coord(0, 0, 0), 123.0f, false); + rootChild->addTile(0, -grid->background(), false); + root.addChild(rootChild.release()); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + auto rootChild2 = std::make_unique(Coord(0, 0, 0), 55.0f, false); + + rootChild2->addChild(new LeafParentType(Coord(0, 0, 0), 29.0f, false)); + rootChild2->addChild(new LeafParentType(Coord(0, 0, 128), 31.0f, false)); + rootChild2->addTile(2, -grid->background(), false); + root2.addChild(rootChild2.release()); + + tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(1), getChildCount(*root.cbeginChildOn())); + EXPECT_EQ(Index(0), getInsideTileCount(*root.cbeginChildOn())); + EXPECT_TRUE(root.cbeginChildOn()->isChildMaskOn(0)); + EXPECT_TRUE(!root.cbeginChildOn()->isChildMaskOn(1)); + EXPECT_EQ(29.0f, root.cbeginChildOn()->cbeginChildOn()->getFirstValue()); + EXPECT_EQ(123.0f, root.cbeginChildOn()->cbeginValueAll().getValue()); + } + + { // merge two internal nodes into a grid with an inside tile and an outside tile + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + auto rootChild = std::make_unique(Coord(0, 0, 0), -123.0f, false); + root.addChild(rootChild.release()); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + auto rootChild2 = std::make_unique(Coord(0, 0, 0), 55.0f, false); + + rootChild2->addChild(new LeafParentType(Coord(0, 0, 0), 29.0f, false)); + rootChild2->addChild(new LeafParentType(Coord(0, 0, 128), 31.0f, false)); + rootChild2->addTile(2, 140.0f, false); + rootChild2->addTile(3, -grid2->background(), false); + root2.addChild(rootChild2.release()); + + tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(2), getChildCount(*root.cbeginChildOn())); + EXPECT_EQ(Index(1), getInsideTileCount(*root.cbeginChildOn())); + EXPECT_TRUE(root.cbeginChildOn()->isChildMaskOn(0)); + EXPECT_TRUE(root.cbeginChildOn()->isChildMaskOn(1)); + EXPECT_TRUE(!root.cbeginChildOn()->isChildMaskOn(2)); + EXPECT_TRUE(!root.cbeginChildOn()->isChildMaskOn(3)); + EXPECT_EQ(29.0f, root.cbeginChildOn()->cbeginChildOn()->getFirstValue()); + EXPECT_EQ(grid->background(), root.cbeginChildOn()->cbeginValueAll().getItem(2)); + EXPECT_EQ(-123.0f, root.cbeginChildOn()->cbeginValueAll().getItem(3)); + } } - { // merge two child nodes into an empty grid - using RootChildType = FloatTree::RootNodeType::ChildNodeType; + ///////////////////////////////////////////////////////////////////////// + + { // test merging leaf nodes + + { // merge a leaf node into an empty grid + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().touchLeaf(Coord(0, 0, 0)); + + tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), -grid->background(), true); - root.addTile(Coord(8192, 0, 0), -grid->background(), true); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addChild(new RootChildType(Coord(0, 0, 0), 1.0f, false)); - root2.addChild(new RootChildType(Coord(8192, 0, 0), -123.0f, true)); + EXPECT_EQ(Index32(0), grid->tree().leafCount()); + } - EXPECT_EQ(Index(2), root2.getTableSize()); - EXPECT_EQ(Index(0), getTileCount(root2)); - EXPECT_EQ(Index(2), getChildCount(root2)); + { // merge a leaf node into a grid with a background tile + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().touchLeaf(Coord(0, 0, 0)); - tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(0), getTileCount(root)); - EXPECT_EQ(Index(2), getChildCount(root)); - } + EXPECT_EQ(Index32(0), grid->tree().leafCount()); + } - { // merge a child node into a grid with an outside tile - using RootChildType = FloatTree::RootNodeType::ChildNodeType; + { // merge a leaf node into a grid with an outside tile + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), 10.0f, false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().touchLeaf(Coord(0, 0, 0)); - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), 123.0f, true); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addChild(new RootChildType(Coord(0, 0, 0), 1.0f, false)); + tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(1), getTileCount(root)); - EXPECT_EQ(Index(1), getChildCount(root2)); + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } - tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // merge a leaf node into a grid with an outside tile + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().touchLeaf(Coord(0, 0, 0)); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + grid2->tree().root().addTile(Coord(0, 0, 0), 10.0f, false); - EXPECT_EQ(Index(1), root.getTableSize()); - EXPECT_EQ(Index(1), getChildCount(root)); - EXPECT_EQ(Index(0), getTileCount(root)); - } + tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - { // merge a child node into a grid with an existing child node - using RootChildType = FloatTree::RootNodeType::ChildNodeType; + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addChild(new RootChildType(Coord(0, 0, 0), 123.0f, false)); - root.addTile(Coord(8192, 0, 0), -123.0f, true); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), -123.0f, true); - root2.addChild(new RootChildType(Coord(8192, 0, 0), 1.9f, false)); + { // merge a leaf node into a grid with an internal node inside tile + FloatGrid::Ptr grid = createLevelSet(); + auto rootChild = std::make_unique(Coord(0, 0, 0), -grid->background(), false); + grid->tree().root().addChild(rootChild.release()); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto* leaf = grid2->tree().touchLeaf(Coord(0, 0, 0)); - EXPECT_EQ(Index(1), getChildCount(root)); - EXPECT_EQ(Index(1), getChildCount(root2)); + leaf->setValueOnly(11, grid2->background()); + leaf->setValueOnly(12, -grid2->background()); - tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(2), getChildCount(root)); - EXPECT_TRUE(root.cbeginChildOn()->cbeginValueAll()); - EXPECT_EQ(123.0f, root.cbeginChildOn()->cbeginValueAll().getItem(0)); - EXPECT_EQ(1.9f, (++root.cbeginChildOn())->cbeginValueAll().getItem(0)); - } + EXPECT_EQ(Index32(1), grid->tree().leafCount()); + EXPECT_EQ(Index32(0), grid2->tree().leafCount()); - { // merge an inside tile and an outside tile into a grid with two child nodes - using RootChildType = FloatTree::RootNodeType::ChildNodeType; + // test background values are remapped - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addChild(new RootChildType(Coord(0, 0, 0), 123.0f, false)); - root.addChild(new RootChildType(Coord(8192, 0, 0), 1.9f, false)); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), -15.0f, false); // should not replace child - root2.addTile(Coord(8192, 0, 0), 25.0f, false); // should replace child + const auto* testLeaf = grid->tree().probeConstLeaf(Coord(0, 0, 0)); + EXPECT_EQ(grid->background(), testLeaf->getValue(11)); + EXPECT_EQ(-grid->background(), testLeaf->getValue(12)); + } - EXPECT_EQ(Index(2), getChildCount(root)); - EXPECT_EQ(Index(2), getTileCount(root2)); + { // merge a leaf node into a grid with a partially constructed leaf node + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(); - tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + grid->tree().addLeaf(new LeafT(PartialCreate(), Coord(0, 0, 0))); + auto* leaf = grid2->tree().touchLeaf(Coord(0, 0, 0)); + leaf->setValueOnly(10, 6.4f); - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(1), getChildCount(root)); - EXPECT_EQ(Index(1), getTileCount(root)); - EXPECT_TRUE(root.cbeginChildAll().isChildNode()); - EXPECT_TRUE(!(++root.cbeginChildAll()).isChildNode()); - EXPECT_EQ(123.0f, root.cbeginChildOn()->getFirstValue()); - // outside tile value replaced with background - EXPECT_EQ(grid->background(), root.cbeginValueAll().getValue()); - } + tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - { // merge two child nodes into a grid with an inside tile and an outside tile - using RootChildType = FloatTree::RootNodeType::ChildNodeType; + const auto* testLeaf = grid->tree().probeConstLeaf(Coord(0, 0, 0)); + EXPECT_EQ(6.4f, testLeaf->getValue(10)); + } - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), -15.0f, false); // should be replaced by child - root.addTile(Coord(8192, 0, 0), 25.0f, false); // should not be replaced by child - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - root2.addChild(new RootChildType(Coord(0, 0, 0), 123.0f, false)); - root2.addChild(new RootChildType(Coord(8192, 0, 0), 1.9f, false)); + { // merge three leaf nodes from different grids + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + FloatGrid::Ptr grid3 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/7); - EXPECT_EQ(Index(2), getTileCount(root)); - EXPECT_EQ(Index(2), getChildCount(root2)); + auto* leaf = grid->tree().touchLeaf(Coord(0, 0, 0)); + auto* leaf2 = grid2->tree().touchLeaf(Coord(0, 0, 0)); + auto* leaf3 = grid3->tree().touchLeaf(Coord(0, 0, 0)); - tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + // active state from the voxel with the maximum value preserved - EXPECT_EQ(Index(2), root.getTableSize()); - EXPECT_EQ(Index(1), getChildCount(root)); - EXPECT_EQ(Index(1), getTileCount(root)); - EXPECT_TRUE(root.cbeginChildAll().isChildNode()); - EXPECT_TRUE(!(++root.cbeginChildAll()).isChildNode()); - EXPECT_EQ(123.0f, root.cbeginChildOn()->getFirstValue()); - EXPECT_EQ(grid->background(), root.cbeginValueAll().getValue()); - } + leaf->setValueOnly(5, 4.0f); + leaf2->setValueOnly(5, 2.0f); + leaf2->setValueOn(5); + leaf3->setValueOnly(5, 3.0f); - { // merge two internal nodes into a grid with an inside tile and an outside tile - using RootChildType = FloatTree::RootNodeType::ChildNodeType; - using LeafParentType = RootChildType::ChildNodeType; + leaf->setValueOnly(7, 2.0f); + leaf->setValueOn(7); + leaf2->setValueOnly(7, 3.0f); + leaf3->setValueOnly(7, 4.0f); - FloatGrid::Ptr grid = createLevelSet(); - auto& root = grid->tree().root(); - auto rootChild = std::make_unique(Coord(0, 0, 0), 123.0f, false); - rootChild->addTile(0, -14.0f, false); - rootChild->addTile(1, 15.0f, false); - root.addChild(rootChild.release()); - FloatGrid::Ptr grid2 = createLevelSet(); - auto& root2 = grid2->tree().root(); - auto rootChild2 = std::make_unique(Coord(0, 0, 0), 55.0f, false); + leaf->setValueOnly(9, 4.0f); + leaf->setValueOn(9); + leaf2->setValueOnly(9, 3.0f); + leaf3->setValueOnly(9, 2.0f); - rootChild2->addChild(new LeafParentType(Coord(0, 0, 0), 29.0f, false)); - rootChild2->addChild(new LeafParentType(Coord(0, 0, 128), 31.0f, false)); - rootChild2->addTile(2, -17.0f, true); - rootChild2->addTile(9, 19.0f, true); - root2.addChild(rootChild2.release()); + std::vector trees{&grid2->tree(), &grid3->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + const auto* testLeaf = grid->tree().probeConstLeaf(Coord(0, 0, 0)); + EXPECT_EQ(4.0f, testLeaf->getValue(5)); + EXPECT_TRUE(!testLeaf->isValueOn(5)); + EXPECT_EQ(4.0f, testLeaf->getValue(7)); + EXPECT_TRUE(!testLeaf->isValueOn(7)); + EXPECT_EQ(4.0f, testLeaf->getValue(9)); + EXPECT_TRUE(testLeaf->isValueOn(9)); + } - EXPECT_EQ(Index(1), getInsideTileCount(*root.cbeginChildOn())); - EXPECT_EQ(Index(0), getActiveTileCount(*root.cbeginChildOn())); + { // merge a leaf node into an empty grid from a const grid + FloatGrid::Ptr grid = createLevelSet(); + grid->tree().root().addTile(Coord(0, 0, 0), -1.0f, false); + FloatGrid::Ptr grid2 = createLevelSet(); + grid2->tree().touchLeaf(Coord(0, 0, 0)); - EXPECT_EQ(Index(2), getChildCount(*root2.cbeginChildOn())); - EXPECT_EQ(Index(1), getInsideTileCount(*root2.cbeginChildOn())); - EXPECT_EQ(Index(2), getActiveTileCount(*root2.cbeginChildOn())); + EXPECT_EQ(Index32(0), grid->tree().leafCount()); + EXPECT_EQ(Index32(1), grid2->tree().leafCount()); - tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + // merge from a const tree - EXPECT_EQ(Index(1), getChildCount(*root.cbeginChildOn())); - EXPECT_EQ(Index(0), getInsideTileCount(*root.cbeginChildOn())); - EXPECT_EQ(Index(0), getActiveTileCount(*root.cbeginChildOn())); - EXPECT_TRUE(root.cbeginChildOn()->isChildMaskOn(0)); - EXPECT_TRUE(!root.cbeginChildOn()->isChildMaskOn(1)); - EXPECT_EQ(29.0f, root.cbeginChildOn()->cbeginChildOn()->getFirstValue()); - EXPECT_EQ(15.0f, root.cbeginChildOn()->cbeginValueAll().getValue()); + std::vector> treesToMerge; + treesToMerge.emplace_back(grid2->constTree(), DeepCopy()); + tools::CsgIntersectionOp mergeOp(treesToMerge); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(0), getChildCount(*root2.cbeginChildOn())); - } + EXPECT_EQ(Index32(1), grid->tree().leafCount()); + // leaf has been deep copied not stolen + EXPECT_EQ(Index32(1), grid2->tree().leafCount()); + } - { // merge a leaf node into an empty grid - FloatGrid::Ptr grid = createLevelSet(); - grid->tree().root().addTile(Coord(0, 0, 0), -1.0f, false); - FloatGrid::Ptr grid2 = createLevelSet(); - grid2->tree().touchLeaf(Coord(0, 0, 0)); + { // merge three leaf nodes from four grids + FloatGrid::Ptr grid = createLevelSet(); + FloatGrid::Ptr grid2 = createLevelSet(); + FloatGrid::Ptr grid3 = createLevelSet(); + FloatGrid::Ptr grid4 = createLevelSet(); - EXPECT_EQ(Index32(0), grid->tree().leafCount()); - EXPECT_EQ(Index32(1), grid2->tree().leafCount()); + auto* leaf = grid->tree().touchLeaf(Coord(0, 0, 0)); + auto* leaf2 = grid2->tree().touchLeaf(Coord(0, 0, 0)); + auto* leaf3 = grid3->tree().touchLeaf(Coord(0, 0, 0)); - tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + // active state from the voxel with the maximum value preserved - EXPECT_EQ(Index32(1), grid->tree().leafCount()); - EXPECT_EQ(Index32(0), grid2->tree().leafCount()); - } + leaf->setValueOnly(5, 4.0f); + leaf2->setValueOnly(5, 2.0f); + leaf2->setValueOn(5); + leaf3->setValueOnly(5, 3.0f); - { // merge a leaf node into a grid with a partially constructed leaf node - using LeafT = FloatTree::LeafNodeType; + leaf->setValueOnly(7, 2.0f); + leaf->setValueOn(7); + leaf2->setValueOnly(7, 3.0f); + leaf3->setValueOnly(7, 4.0f); - FloatGrid::Ptr grid = createLevelSet(); - FloatGrid::Ptr grid2 = createLevelSet(); + leaf->setValueOnly(9, 4.0f); + leaf->setValueOn(9); + leaf2->setValueOnly(9, 3.0f); + leaf3->setValueOnly(9, 2.0f); - grid->tree().addLeaf(new LeafT(PartialCreate(), Coord(0, 0, 0))); - auto* leaf = grid2->tree().touchLeaf(Coord(0, 0, 0)); - leaf->setValueOnly(10, 6.4f); + std::vector trees{&grid2->tree(), &grid3->tree(), &grid4->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - tools::CsgIntersectionOp mergeOp{grid2->tree(), Steal()}; - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + EXPECT_EQ(Index(0), grid->tree().root().getTableSize()); + } - const auto* testLeaf = grid->tree().probeConstLeaf(Coord(0, 0, 0)); - EXPECT_EQ(6.4f, testLeaf->getValue(10)); } - { // merge three leaf nodes from different grids - FloatGrid::Ptr grid = createLevelSet(); - FloatGrid::Ptr grid2 = createLevelSet(); - FloatGrid::Ptr grid3 = createLevelSet(); + ///////////////////////////////////////////////////////////////////////// - auto* leaf = grid->tree().touchLeaf(Coord(0, 0, 0)); - auto* leaf2 = grid2->tree().touchLeaf(Coord(0, 0, 0)); - auto* leaf3 = grid3->tree().touchLeaf(Coord(0, 0, 0)); + { // merge multiple grids - // active state from the voxel with the maximum value preserved + { // merge two background root tiles from two different grids + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), /*background=*/-grid->background(), false); + root.addTile(Coord(8192, 0, 0), /*background=*/-grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), /*background=*/grid2->background(), false); + root2.addTile(Coord(8192, 0, 0), /*background=*/-grid2->background(), false); + FloatGrid::Ptr grid3 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/7); + auto& root3 = grid3->tree().root(); + root3.addTile(Coord(0, 0, 0), /*background=*/-grid3->background(), false); + root3.addTile(Coord(8192, 0, 0), /*background=*/grid3->background(), false); + + std::vector trees{&grid2->tree(), &grid3->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - leaf->setValueOnly(5, 4.0f); - leaf2->setValueOnly(5, 2.0f); - leaf2->setValueOn(5); - leaf3->setValueOnly(5, 3.0f); + EXPECT_EQ(Index(0), root.getTableSize()); + } - leaf->setValueOnly(7, 2.0f); - leaf->setValueOn(7); - leaf2->setValueOnly(7, 3.0f); - leaf3->setValueOnly(7, 4.0f); + { // merge two outside root tiles from two different grids + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), /*background=*/-grid->background(), false); + root.addTile(Coord(8192, 0, 0), /*background=*/-grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), /*background=*/10.0f, false); + root2.addTile(Coord(8192, 0, 0), /*background=*/-grid2->background(), false); + FloatGrid::Ptr grid3 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/7); + auto& root3 = grid3->tree().root(); + root3.addTile(Coord(0, 0, 0), /*background=*/-grid3->background(), false); + root3.addTile(Coord(8192, 0, 0), /*background=*/11.0f, false); + + std::vector trees{&grid2->tree(), &grid3->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - leaf->setValueOnly(9, 4.0f); - leaf->setValueOn(9); - leaf2->setValueOnly(9, 3.0f); - leaf3->setValueOnly(9, 2.0f); + EXPECT_EQ(Index(0), root.getTableSize()); + } - std::vector trees{&grid2->tree(), &grid3->tree()}; - tools::CsgIntersectionOp mergeOp(trees, Steal()); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + { // merge two active, outside root tiles from two different grids + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), /*background=*/-grid->background(), false); + root.addTile(Coord(8192, 0, 0), /*background=*/-grid->background(), false); + FloatGrid::Ptr grid2 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/5); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), /*background=*/10.0f, true); + root2.addTile(Coord(8192, 0, 0), /*background=*/-grid2->background(), false); + FloatGrid::Ptr grid3 = createLevelSet(/*voxelSize=*/1.0, /*narrowBandWidth=*/7); + auto& root3 = grid3->tree().root(); + root3.addTile(Coord(0, 0, 0), /*background=*/-grid3->background(), false); + root3.addTile(Coord(8192, 0, 0), /*background=*/11.0f, true); + + std::vector trees{&grid2->tree(), &grid3->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - const auto* testLeaf = grid->tree().probeConstLeaf(Coord(0, 0, 0)); - EXPECT_EQ(4.0f, testLeaf->getValue(5)); - EXPECT_TRUE(!testLeaf->isValueOn(5)); - EXPECT_EQ(4.0f, testLeaf->getValue(7)); - EXPECT_TRUE(!testLeaf->isValueOn(7)); - EXPECT_EQ(4.0f, testLeaf->getValue(9)); - EXPECT_TRUE(testLeaf->isValueOn(9)); - } + EXPECT_EQ(Index(2), getTileCount(root)); - { // merge a leaf node into an empty grid from a const grid - FloatGrid::Ptr grid = createLevelSet(); - grid->tree().root().addTile(Coord(0, 0, 0), -1.0f, false); - FloatGrid::Ptr grid2 = createLevelSet(); - grid2->tree().touchLeaf(Coord(0, 0, 0)); + EXPECT_EQ(grid->background(), root.cbeginValueAll().getValue()); + EXPECT_EQ(grid->background(), (++root.cbeginValueAll()).getValue()); + } - EXPECT_EQ(Index32(0), grid->tree().leafCount()); - EXPECT_EQ(Index32(1), grid2->tree().leafCount()); + { // merge three root tiles, one of which is a background tile + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), -grid->background(), true); + FloatGrid::Ptr grid2 = createLevelSet(); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), grid2->background(), true); + FloatGrid::Ptr grid3 = createLevelSet(); + auto& root3 = grid3->tree().root(); + root3.addTile(Coord(0, 0, 0), grid3->background(), false); + + std::vector trees{&grid2->tree(), &grid3->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - // merge from a const tree + EXPECT_EQ(Index(1), root.getTableSize()); + EXPECT_EQ(Index(1), getTileCount(root)); + } - std::vector> treesToMerge; - treesToMerge.emplace_back(grid2->constTree(), DeepCopy()); + { // merge three root tiles, one of which is a background tile + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), -grid->background(), true); + FloatGrid::Ptr grid2 = createLevelSet(); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), grid2->background(), false); + FloatGrid::Ptr grid3 = createLevelSet(); + auto& root3 = grid3->tree().root(); + root3.addTile(Coord(0, 0, 0), grid3->background(), true); + + std::vector trees{&grid2->tree(), &grid3->tree()}; + tools::CsgIntersectionOp mergeOp(trees, Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); - tools::CsgIntersectionOp mergeOp(treesToMerge); - tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp); + EXPECT_EQ(Index(0), root.getTableSize()); + } - EXPECT_EQ(Index32(1), grid->tree().leafCount()); - // leaf has been deep copied not stolen - EXPECT_EQ(Index32(1), grid2->tree().leafCount()); } } TEST_F(TestMerge, testCsgDifference) { using RootChildType = FloatTree::RootNodeType::ChildNodeType; + using LeafParentType = RootChildType::ChildNodeType; + using LeafT = FloatTree::LeafNodeType; { // construction FloatTree tree1; @@ -1343,11 +2148,26 @@ TEST_F(TestMerge, testCsgDifference) tree::DynamicNodeManager nodeManager(grid->tree()); nodeManager.foreachTopDown(mergeOp); + EXPECT_EQ(Index(0), root.getTableSize()); + } + + { // merge an outside root tile to a grid which already has this tile + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), grid->background(), true); + FloatGrid::Ptr grid2 = createLevelSet(); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), grid->background(), false); + + tools::CsgDifferenceOp mergeOp(grid2->tree(), Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + EXPECT_EQ(Index(1), root.getTableSize()); EXPECT_EQ(Index(1), getTileCount(root)); // tile in merge grid should not replace existing tile - tile should remain inactive - EXPECT_EQ(Index(0), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(Index(1), getActiveTileCount(root)); + EXPECT_EQ(Index(0), getInactiveTileCount(root)); EXPECT_EQ(Index(0), getInsideTileCount(root)); EXPECT_EQ(Index(1), getOutsideTileCount(root)); } @@ -1365,7 +2185,7 @@ TEST_F(TestMerge, testCsgDifference) tools::CsgDifferenceOp mergeOp(grid2->tree(), Steal()); tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp, true); + nodeManager.foreachTopDown(mergeOp); EXPECT_EQ(Index(1), root.getTableSize()); EXPECT_EQ(Index(1), getTileCount(root)); @@ -1387,7 +2207,7 @@ TEST_F(TestMerge, testCsgDifference) tools::CsgDifferenceOp mergeOp(grid2->tree(), Steal()); tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp, true); + nodeManager.foreachTopDown(mergeOp); EXPECT_EQ(Index(1), root.getTableSize()); EXPECT_EQ(Index(0), getTileCount(root)); @@ -1407,7 +2227,7 @@ TEST_F(TestMerge, testCsgDifference) tools::CsgDifferenceOp mergeOp(grid2->tree(), Steal()); tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp, true); + nodeManager.foreachTopDown(mergeOp); EXPECT_EQ(Index(1), root.getTableSize()); EXPECT_EQ(Index(1), getTileCount(root)); @@ -1421,7 +2241,7 @@ TEST_F(TestMerge, testCsgDifference) { // merge an inside root tile to a grid which has an outside tile (noop) FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), grid->background(), false); + root.addTile(Coord(0, 0, 0), grid->background(), true); FloatGrid::Ptr grid2 = createLevelSet(); auto& root2 = grid2->tree().root(); root2.addTile(Coord(0, 0, 0), -123.0f, true); @@ -1431,23 +2251,23 @@ TEST_F(TestMerge, testCsgDifference) tools::CsgDifferenceOp mergeOp(grid2->tree(), Steal()); tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp, true); + nodeManager.foreachTopDown(mergeOp); EXPECT_EQ(Index(1), root.getTableSize()); EXPECT_EQ(Index(1), getTileCount(root)); - EXPECT_EQ(Index(0), getActiveTileCount(root)); - EXPECT_EQ(Index(1), getInactiveTileCount(root)); + EXPECT_EQ(Index(1), getActiveTileCount(root)); + EXPECT_EQ(Index(0), getInactiveTileCount(root)); EXPECT_EQ(Index(0), getInsideTileCount(root)); EXPECT_EQ(Index(1), getOutsideTileCount(root)); } - { // merge two grids with inside tiles, active state should be carried across + { // merge two grids with outside tiles, active state should be carried across FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); - root.addTile(Coord(0, 0, 0), -0.1f, true); + root.addTile(Coord(0, 0, 0), 0.1f, false); FloatGrid::Ptr grid2 = createLevelSet(); auto& root2 = grid2->tree().root(); - root2.addTile(Coord(0, 0, 0), -0.2f, false); + root2.addTile(Coord(0, 0, 0), 0.2f, true); tools::CsgDifferenceOp mergeOp(grid2->tree(), Steal()); tree::DynamicNodeManager nodeManager(grid->tree()); @@ -1455,13 +2275,28 @@ TEST_F(TestMerge, testCsgDifference) EXPECT_EQ(Index(1), root.getTableSize()); EXPECT_EQ(Index(1), getTileCount(root)); - // inside tile should now be inactive + // outside tile should now be inactive EXPECT_EQ(Index(0), getActiveTileCount(root)); EXPECT_EQ(Index(1), getInactiveTileCount(root)); EXPECT_EQ(Index(0), getInsideTileCount(root)); EXPECT_EQ(Index(1), getOutsideTileCount(root)); - EXPECT_EQ(grid->background(), root.cbeginValueAll().getValue()); + EXPECT_EQ(0.1f, root.cbeginValueAll().getValue()); + } + + { // merge two grids with outside tiles, active state should be carried across + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addTile(Coord(0, 0, 0), -0.1f, true); + FloatGrid::Ptr grid2 = createLevelSet(); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), -0.2f, false); + + tools::CsgDifferenceOp mergeOp(grid2->tree(), Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(0), root.getTableSize()); } { // merge an inside root tile to a grid which has a child, inside tile has precedence @@ -1476,7 +2311,7 @@ TEST_F(TestMerge, testCsgDifference) tools::CsgDifferenceOp mergeOp(grid2->tree(), Steal()); tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp, true); + nodeManager.foreachTopDown(mergeOp); EXPECT_EQ(Index(1), root.getTableSize()); EXPECT_EQ(Index(1), getTileCount(root)); @@ -1505,7 +2340,7 @@ TEST_F(TestMerge, testCsgDifference) tools::CsgDifferenceOp mergeOp(grid2->tree(), Steal()); tree::DynamicNodeManager nodeManager(grid->tree()); - nodeManager.foreachTopDown(mergeOp, true); + nodeManager.foreachTopDown(mergeOp); EXPECT_EQ(Index(1), root.getTableSize()); EXPECT_EQ(Index(0), getTileCount(root)); @@ -1522,8 +2357,6 @@ TEST_F(TestMerge, testCsgDifference) } { // merge two child nodes into a grid with two inside tiles - using RootChildType = FloatTree::RootNodeType::ChildNodeType; - FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); root.addTile(Coord(0, 0, 0), -2.0f, false); @@ -1547,8 +2380,28 @@ TEST_F(TestMerge, testCsgDifference) } { // merge an inside tile and an outside tile into a grid with two child nodes - using RootChildType = FloatTree::RootNodeType::ChildNodeType; + FloatGrid::Ptr grid = createLevelSet(); + auto& root = grid->tree().root(); + root.addChild(new RootChildType(Coord(0, 0, 0), 123.0f, false)); + root.addChild(new RootChildType(Coord(8192, 0, 0), 1.9f, false)); + FloatGrid::Ptr grid2 = createLevelSet(); + auto& root2 = grid2->tree().root(); + root2.addTile(Coord(0, 0, 0), 15.0f, true); // should not replace child + root2.addTile(Coord(8192, 0, 0), -25.0f, true); // should replace child + + tools::CsgDifferenceOp mergeOp(grid2->tree(), Steal()); + tree::DynamicNodeManager nodeManager(grid->tree()); + nodeManager.foreachTopDown(mergeOp); + + EXPECT_EQ(Index(1), getChildCount(root)); + EXPECT_EQ(Index(1), getTileCount(root)); + EXPECT_EQ(123.0f, root.cbeginChildOn()->getFirstValue()); + EXPECT_TRUE(root.cbeginChildAll().isChildNode()); + EXPECT_TRUE(!(++root.cbeginChildAll()).isChildNode()); + EXPECT_EQ(grid->background(), root.cbeginValueOn().getValue()); + } + { // merge an inside tile and an outside tile into a grid with two child nodes FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); root.addChild(new RootChildType(Coord(0, 0, 0), 123.0f, false)); @@ -1565,22 +2418,12 @@ TEST_F(TestMerge, testCsgDifference) tree::DynamicNodeManager nodeManager(grid->tree()); nodeManager.foreachTopDown(mergeOp); - EXPECT_EQ(Index(2), root.getTableSize()); EXPECT_EQ(Index(1), getChildCount(root)); - EXPECT_EQ(Index(1), getTileCount(root)); - EXPECT_EQ(Index(0), getInsideTileCount(root)); - EXPECT_EQ(Index(1), getOutsideTileCount(root)); - EXPECT_TRUE(root.cbeginChildAll().isChildNode()); - EXPECT_TRUE(!(++root.cbeginChildAll()).isChildNode()); + EXPECT_EQ(Index(0), getTileCount(root)); EXPECT_EQ(123.0f, root.cbeginChildOn()->getFirstValue()); - // outside tile value replaced with negative background - EXPECT_EQ(grid->background(), root.cbeginValueAll().getValue()); } { // merge two internal nodes into a grid with an inside tile and an outside tile - using RootChildType = FloatTree::RootNodeType::ChildNodeType; - using LeafParentType = RootChildType::ChildNodeType; - FloatGrid::Ptr grid = createLevelSet(); auto& root = grid->tree().root(); auto rootChild = std::make_unique(Coord(0, 0, 0), 123.0f, false); @@ -1658,8 +2501,6 @@ TEST_F(TestMerge, testCsgDifference) } { // merge a leaf node into a grid with a partially constructed leaf node - using LeafT = FloatTree::LeafNodeType; - FloatGrid::Ptr grid = createLevelSet(); FloatGrid::Ptr grid2 = createLevelSet(); From 7ded2b71cb7d90dcbd947eb95b84b3af90096ff7 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Wed, 3 Feb 2021 14:23:31 -0800 Subject: [PATCH 15/21] Update change notes Signed-off-by: Dan Bailey --- CHANGES | 16 ++++++++++++++++ doc/changes.txt | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/CHANGES b/CHANGES index 06f201c92d..959aab6c56 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,22 @@ OpenVDB Version History ======================= +Version 7.2.2 - February 4, 2020 + + Bug fixes: + - Fixed a bug in the new CSG intersection merge algorithm where data + outside of the intersection region was not being removed. + - Fix GridTransformer construction to use correct rotate-scale-translate + order when recomposing matrix components. + [Contributed by Tom Cnops]. + + Houdini: + - VDB Combine SOP no longer attempts to invoke SDF CSG operations on + bool grids because unary negation is undefined on bools in the + template expansion. + - VDB to Spheres SOP doesn't reset the radius when in worldspace mode. + VDB Write likewise should not reset the compression values. + Version 7.2.1 - December 23, 2020 Bug fixes: diff --git a/doc/changes.txt b/doc/changes.txt index 3175832504..3a8a835eb8 100644 --- a/doc/changes.txt +++ b/doc/changes.txt @@ -2,6 +2,27 @@ @page changes Release Notes +@htmlonly @endhtmlonly +@par +Version 7.2.2 - February 4, 2020 + +@par +Bug fixes: +- Fixed a bug in the new CSG intersection merge algorithm where data outside of + the intersection region was not being removed. +- Fix @vdblink::tools::GridTransformer grid transformer tool@endlink + construction to use correct rotate-scale-translate order when recomposing + matrix components. + [Contributed by Tom Cnops] + +@par +Houdini: +- VDB Combine SOP no longer attempts to invoke SDF CSG operations on bool grids + because unary negation is undefined on bools in the template expansion. +- VDB to Spheres SOP doesn't reset the radius when in worldspace mode. + VDB Write SOP should likewise not reset the compression values. + + @htmlonly @endhtmlonly @par Version 7.2.1 - December 23, 2020 From ca2564991e22d0c6937b1290ee78554f93de2b9b Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Wed, 3 Feb 2021 14:35:07 -0800 Subject: [PATCH 16/21] Downgrade ABI=8 build from CI to ABI=7 Signed-off-by: Dan Bailey --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e9d119052e..2c0653ae08 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,7 +127,7 @@ jobs: - name: test run: ./ci/test.sh - testabi8gcc10: + testabi7gcc10: runs-on: ubuntu-20.04 env: CXX: g++-10 @@ -140,11 +140,11 @@ jobs: - name: install_gtest run: sudo apt-get -q install -y libgtest-dev - name: build - run: ./ci/build.sh Release 8 OFF None "core,test" -DUSE_ZLIB=OFF -DDISABLE_DEPENDENCY_VERSION_CHECKS=ON -DCMAKE_INSTALL_PREFIX=`pwd` + run: ./ci/build.sh Release 7 OFF None "core,test" -DUSE_ZLIB=OFF -DDISABLE_DEPENDENCY_VERSION_CHECKS=ON -DCMAKE_INSTALL_PREFIX=`pwd` - name: test run: ./ci/test.sh - testabi8clang10: + testabi7clang10: runs-on: ubuntu-20.04 env: CXX: clang++-10 @@ -157,7 +157,7 @@ jobs: - name: install_gtest run: sudo apt-get -q install -y libgtest-dev - name: build - run: ./ci/build.sh Release 8 OFF None "core,test" -DUSE_ZLIB=OFF -DDISABLE_DEPENDENCY_VERSION_CHECKS=ON -DCMAKE_INSTALL_PREFIX=`pwd` + run: ./ci/build.sh Release 7 OFF None "core,test" -DUSE_ZLIB=OFF -DDISABLE_DEPENDENCY_VERSION_CHECKS=ON -DCMAKE_INSTALL_PREFIX=`pwd` - name: test run: ./ci/test.sh From 95e7c51157fc9f86eb511169a0e9c08dceae361a Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Wed, 3 Feb 2021 14:54:56 -0800 Subject: [PATCH 17/21] Drop H17.5 build as there is no longer a build cache Signed-off-by: Dan Bailey --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2c0653ae08..9215e89941 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,7 +68,6 @@ jobs: strategy: matrix: config: - - { cxx: clang++, image: '2018', hou: '17_5', build: 'Release', extras: 'ON' } - { cxx: clang++, image: '2019', hou: '18_0', build: 'Release', extras: 'ON' } - { cxx: clang++, image: '2020', hou: '18_5', build: 'Release', extras: 'ON' } - { cxx: clang++, image: '2019', hou: '18_5', build: 'Debug', extras: 'OFF' } From 6f517c5a31ffc77b635682911184341584ce4cc9 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Thu, 4 Feb 2021 12:13:54 -0800 Subject: [PATCH 18/21] Bump version numbers Signed-off-by: Dan Bailey --- doc/CMakeLists.txt | 2 +- openvdb/openvdb/version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 7e155e0f6d..fe5aa5e12f 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.2.1") +set(DOXYGEN_PROJECT_NUMBER "7.2.2") set(DOXYGEN_PROJECT_BRIEF "") set(DOXYGEN_FILE_PATTERNS "*.h") # headers only set(DOXYGEN_IMAGE_PATH "doc/img") diff --git a/openvdb/openvdb/version.h b/openvdb/openvdb/version.h index 81a82c9c0f..5bdeba10aa 100644 --- a/openvdb/openvdb/version.h +++ b/openvdb/openvdb/version.h @@ -50,7 +50,7 @@ // Library major, minor and patch version numbers #define OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER 7 #define OPENVDB_LIBRARY_MINOR_VERSION_NUMBER 2 -#define OPENVDB_LIBRARY_PATCH_VERSION_NUMBER 1 +#define OPENVDB_LIBRARY_PATCH_VERSION_NUMBER 2 // If OPENVDB_ABI_VERSION_NUMBER is already defined (e.g., via -DOPENVDB_ABI_VERSION_NUMBER=N) // use that ABI version. Otherwise, use this library version's default ABI. From b92bf3b2d50286d7c82af4381f5ed771db2e0405 Mon Sep 17 00:00:00 2001 From: jlait Date: Thu, 4 Feb 2021 12:34:40 -0500 Subject: [PATCH 19/21] TSC Notes for 2021-02-02 Merged existing pendingchanges into Changes. Signed-off-by: jlait --- CHANGES | 4 + doc/changes.txt | 4 + pendingchanges/fix_csg_intersection.txt | 3 - tsc/meetings/2021-02-02.md | 102 ++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 3 deletions(-) delete mode 100644 pendingchanges/fix_csg_intersection.txt create mode 100644 tsc/meetings/2021-02-02.md diff --git a/CHANGES b/CHANGES index 630d7e2580..e778a73edd 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,10 @@ OpenVDB Version History Version 8.0.1 - In Development + Bug fixes: + - Fixed a bug in the new CSG intersection merge algorithm where data + outside of the intersection region was not being removed. + Build: - Fixed various incorrect RPATH directory paths in CMake - Dropped the minimum boost requirement back to 1.61. diff --git a/doc/changes.txt b/doc/changes.txt index 008129c1b5..f917850df9 100644 --- a/doc/changes.txt +++ b/doc/changes.txt @@ -7,6 +7,10 @@ Version 8.0.1 - In Development @par +Bug fixes: +- Fixed a bug in the new CSG intersection merge algorithm where data + outside of the intersection region was not being removed. + Build: - Fixed various incorrect RPATH directory paths in CMake - Dropped the minimum boost requirement back to 1.61. diff --git a/pendingchanges/fix_csg_intersection.txt b/pendingchanges/fix_csg_intersection.txt deleted file mode 100644 index ab9cdea130..0000000000 --- a/pendingchanges/fix_csg_intersection.txt +++ /dev/null @@ -1,3 +0,0 @@ -Bug fixes: - - Fixed a bug in the new CSG intersection merge algorithm where data - outside of the intersection region was not being removed. diff --git a/tsc/meetings/2021-02-02.md b/tsc/meetings/2021-02-02.md new file mode 100644 index 0000000000..d0edcab6a1 --- /dev/null +++ b/tsc/meetings/2021-02-02.md @@ -0,0 +1,102 @@ +Minutes from 78th OpenVDB TSC meeting, Feb 2nd, 2021, (EDT) + +Attendees: *Nick* A., *Jeff* L., *Ken* M., *Dan* B, *Andre* P. + +Additional Attendees: Johannes Meng (Intel), JT Nelson (Blender), +Bruce Chernaik (Intel), Laura Lediaev (ImageWorks) + +Regrets: none + +Agenda: + +1) Confirm Quorum +2) Secretary +3) Blender Update +4) GitHub Issues +5) Removal of EXR from Core +6) Half Update +7) NanoVDB +8) CSG Intersection Fix +9) Update Documentation Process +10) Boost Minimum Requirements +11) AX SOP +12) Next meeting + + +1) Confirm Quorum + +Quorum is present. + +2) Secretary + +Secretary is Jeff Lait. + +3) Blender Update + +A brief update on the current integration status of VDB with Blender, +a more full update will be done when the documentation and code are +properly in sync. + +4) GitHub Issues + +We have an issue with issues. We have too many and it is growing. Some issues are things like tbb 2021 not being supported. Others are things we have already triaged - but they stay on the list. For example, the suggestion for a proxy for meshes. Moving to Jira would clear our Issue list, but mean there are many places to look (and submit) for the current status. We find the Issues are mixture of bugs, ideas, and questions. One option is a discussion tab. But this again is another place to go, and what happens when a discussion becomes a bug? Submitters should not be expected to know where things go. + +There is a flow of new issues from Unprocessed to In Process to Discussed. Should we make this explicit so we can ensure all issues have been Discussed? + +There is a risk that if we leave discussed issues active, our project looks incompetent as there are many open issues. + +Bug vs Enhancement labels is something we should probably add as a result of discussion rather than from the submitter. The How To Submit Issues should talk about how we use labels and what they mean. + +Do we want a tag for issues we are currently working on? + +We decided to continute the discussion offline. Nick will provide an initial google doc seeded with the Jira workflow. + +5) Removal of EXR from Core + +Problematic as people use it. However, known use cases are with the command +line render tool, which will be unaffected. Could we have it still kept +without support in our CMake? But this leaves people little better off, +if they can alter the CMake to add the library support, they could as +easily add the explicit saveEXR code from the command line utility. +We are now in agreement to remove the saveEXR from the core library. + +6) Half Update + +CMake now configures BuildConfig.h. This stores if you built with EXR half. Still needs to be validated that this works with external Half implementations. It was proposed the flag refer to IMath half rather than Exr as it is planned to move to an external library. Or maybe it should be an external half flag? + +7) NanoVDB + +How to store half in memory? Could quantize on statistics? Ie, the leaf nodes know their min/max, so if had 8bits of precision could store values within that. If the quantization is stored per leaf node, this would require the codec switch to be done per leaf node. The suggestion is instead that the entire tree gets quantized with a fixed codec. + +It was noted you need to dither the quantization. Raytracing soft fog can become very sensitive to quantization jumps. It was pointed out Bayer dithering does not work for this as it is optimal for area integrals, but volumes need to be optimal for arbitrary line integrals. + +NanoVDB stores tile offsets rather than byte offsets, so cannot have varying codec in the leaf nodes as leave nodes must be fixed size. Points tried varying codecs, but not very useful. Maybe leave room for a codec per leaf node? + +Maybe a global range to avoid jumps in quality between neighbour leaves. Having each leaf have its own quantization can result in neighbour leaves having very different qualities - imagine a single stray 1000 value that crushes low values around it; versus a leaf with only low density that is preserved. Out of range values could then be clamped, letting an artist control quality with an a priori quality metric. + +A big question is do we have half as a type or a codec? Discussion is tabled until a later meeting. + +8) CSG Intersection Fix + + +Fix is complete. Resolves the root node problem. Updated the old unit tests and verified against the old composite header. One change is to make the operations commutative in an rare case, but this is likely more correct. + +Does this need migration documentation? The migration document is required for the leaf manager, not for this. This merely fixes what should have been a drop in replacement. + +9) Update Documentation Process + +The 7.2.2 update is a chance to streamline this. Will try to build documentation via github actions. Goal is to get into github pages. Then website can point to the github pages. + +10) Boost Minimum Requirements + +We need to support 1.66, but do not need to prohibit 1.61. While Houdini is technically correct to have 1.61 in 18.0 because it is hboost, not boost, in practice we pass void * across from the built OpenVDB with the native Houdini OpenVDB; so if there are any internal boost structures they need to be binary compatible. So moving forward the Houdini boost should sync with the vfx reference platform even though it is hboosted. 18.5 has already moved forward. + +In the short term we can change the cmake to only require 1.61 so you can use 18.0. We should consider adding a CI test for Houdini 18.0 compatibility to explicitly use 1.61 rather than 1.66, however, to verify we do not introduce a 1.61 incompatibility. + +11) AX SOP + +Ready to go, needs approval + +12) Next meeting + +Next meeting is Feb 9th, 2021. 12pm-1pm EST (GMT-5). From 5093051ec1ecc342863ce37fecd2485f5439dc15 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Thu, 4 Feb 2021 19:54:24 -0800 Subject: [PATCH 20/21] Update change notes Signed-off-by: Dan Bailey --- CHANGES | 4 +++- doc/changes.txt | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index e778a73edd..9d6ca1eaa5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ OpenVDB Version History ======================= -Version 8.0.1 - In Development +Version 8.0.1 - February 5, 2021 Bug fixes: - Fixed a bug in the new CSG intersection merge algorithm where data @@ -10,6 +10,8 @@ Version 8.0.1 - In Development Build: - Fixed various incorrect RPATH directory paths in CMake - Dropped the minimum boost requirement back to 1.61. + - Documentation installed by the doc target is now installed to the + share/doc/OpenVDB prefix Houdini: - VDB Combine SOP no longer attempts to invoke SDF CSG operations on diff --git a/doc/changes.txt b/doc/changes.txt index f917850df9..b87e7b1378 100644 --- a/doc/changes.txt +++ b/doc/changes.txt @@ -4,16 +4,19 @@ @htmlonly @endhtmlonly @par -Version 8.0.1 - In Development +Version 8.0.1 - February 5, 2021 @par Bug fixes: - Fixed a bug in the new CSG intersection merge algorithm where data outside of the intersection region was not being removed. +@par Build: - Fixed various incorrect RPATH directory paths in CMake - Dropped the minimum boost requirement back to 1.61. +- Documentation installed by the doc target is now installed to the + share/doc/OpenVDB prefix @par Houdini: From 3539f6ee9e27631018eecd1ca97962f27af2d75b Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Thu, 4 Feb 2021 19:55:44 -0800 Subject: [PATCH 21/21] Fix change notes date Signed-off-by: Dan Bailey --- CHANGES | 2 +- doc/changes.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 959aab6c56..f64274a878 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ OpenVDB Version History ======================= -Version 7.2.2 - February 4, 2020 +Version 7.2.2 - February 4, 2021 Bug fixes: - Fixed a bug in the new CSG intersection merge algorithm where data diff --git a/doc/changes.txt b/doc/changes.txt index 3a8a835eb8..6428c41d7e 100644 --- a/doc/changes.txt +++ b/doc/changes.txt @@ -4,7 +4,7 @@ @htmlonly @endhtmlonly @par -Version 7.2.2 - February 4, 2020 +Version 7.2.2 - February 4, 2021 @par Bug fixes: