1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 //
4 /// @file Prune.h
5 ///
6 /// @brief Defined various multi-threaded utility functions for trees
7 ///
8 /// @author Ken Museth
9 
10 #ifndef OPENVDB_TOOLS_PRUNE_HAS_BEEN_INCLUDED
11 #define OPENVDB_TOOLS_PRUNE_HAS_BEEN_INCLUDED
12 
13 #include <openvdb/math/Math.h> // for isNegative and negative
14 #include <openvdb/Types.h>
15 #include <openvdb/tree/NodeManager.h>
16 #include <openvdb/openvdb.h>
17 #include <algorithm> // for std::nth_element()
18 #include <type_traits>
19 
20 namespace openvdb {
21 OPENVDB_USE_VERSION_NAMESPACE
22 namespace OPENVDB_VERSION_NAME {
23 namespace tools {
24 
25 /// @brief Reduce the memory footprint of a @a tree by replacing with tiles
26 /// any nodes whose values are all the same (optionally to within a tolerance)
27 /// and have the same active state.
28 ///
29 /// @note For trees with non-boolean values a child node with (approximately)
30 /// constant values are replaced with a tile value corresponding to the median
31 /// of the values in said child node.
32 ///
33 /// @param tree       the tree to be pruned
34 /// @param tolerance  tolerance within which values are considered to be equal
35 /// @param threaded   enable or disable threading (threading is enabled by default)
36 /// @param grainSize  used to control the threading granularity (default is 1)
37 template<typename TreeT>
38 void
39 prune(TreeT& tree,
40       typename TreeT::ValueType tolerance = zeroVal<typename TreeT::ValueType>(),
41       bool threaded = true,
42       size_t grainSize = 1);
43 
44 
45 /// @brief Reduce the memory footprint of a @a tree by replacing with tiles
46 /// any non-leaf nodes whose values are all the same (optionally to within a tolerance)
47 /// and have the same active state.
48 ///
49 /// @param tree       the tree to be pruned
50 /// @param tolerance  tolerance within which values are considered to be equal
51 /// @param threaded   enable or disable threading (threading is enabled by default)
52 /// @param grainSize  used to control the threading granularity (default is 1)
53 template<typename TreeT>
54 void
55 pruneTiles(TreeT& tree,
56            typename TreeT::ValueType tolerance = zeroVal<typename TreeT::ValueType>(),
57            bool threaded = true,
58            size_t grainSize = 1);
59 
60 
61 /// @brief Reduce the memory footprint of a @a tree by replacing with
62 /// background tiles any nodes whose values are all inactive.
63 ///
64 /// @param tree       the tree to be pruned
65 /// @param threaded   enable or disable threading (threading is enabled by default)
66 /// @param grainSize  used to control the threading granularity (default is 1)
67 template<typename TreeT>
68 void
69 pruneInactive(TreeT& tree, bool threaded = true, size_t grainSize = 1);
70 
71 
72 /// @brief Reduce the memory footprint of a @a tree by replacing any nodes
73 /// whose values are all inactive with tiles of the given @a value.
74 ///
75 /// @param tree       the tree to be pruned
76 /// @param value      value assigned to inactive tiles created during pruning
77 /// @param threaded   enable or disable threading (threading is enabled by default)
78 /// @param grainSize  used to control the threading granularity (default is 1)
79 template<typename TreeT>
80 void
81 pruneInactiveWithValue(
82     TreeT& tree,
83     const typename TreeT::ValueType& value,
84     bool threaded = true,
85     size_t grainSize = 1);
86 
87 
88 /// @brief Reduce the memory footprint of a @a tree by replacing nodes
89 /// whose values are all inactive with inactive tiles having a value equal to
90 /// the first value encountered in the (inactive) child.
91 /// @details This method is faster than tolerance-based prune and
92 /// useful for narrow-band level set applications where inactive
93 /// values are limited to either an inside or an outside value.
94 ///
95 /// @param tree       the tree to be pruned
96 /// @param threaded   enable or disable threading (threading is enabled by default)
97 /// @param grainSize  used to control the threading granularity (default is 1)
98 ///
99 /// @throw ValueError if the background of the @a tree is negative (as defined by math::isNegative)
100 template<typename TreeT>
101 void
102 pruneLevelSet(TreeT& tree,
103               bool threaded = true,
104               size_t grainSize = 1);
105 
106 
107 /// @brief Reduce the memory footprint of a @a tree by replacing nodes whose voxel values
108 /// are all inactive with inactive tiles having the value -| @a insideWidth |
109 /// if the voxel values are negative and | @a outsideWidth | otherwise.
110 ///
111 /// @details This method is faster than tolerance-based prune and
112 /// useful for narrow-band level set applications where inactive
113 /// values are limited to either an inside or an outside value.
114 ///
115 /// @param tree          the tree to be pruned
116 /// @param outsideWidth  the width of the outside of the narrow band
117 /// @param insideWidth   the width of the inside of the narrow band
118 /// @param threaded      enable or disable threading (threading is enabled by default)
119 /// @param grainSize     used to control the threading granularity (default is 1)
120 ///
121 /// @throw ValueError if @a outsideWidth is negative or @a insideWidth is
122 /// not negative (as defined by math::isNegative).
123 template<typename TreeT>
124 void
125 pruneLevelSet(TreeT& tree,
126               const typename TreeT::ValueType& outsideWidth,
127               const typename TreeT::ValueType& insideWidth,
128               bool threaded = true,
129               size_t grainSize = 1);
130 
131 
132 ////////////////////////////////////////////////
133 
134 
135 template<typename TreeT, Index TerminationLevel = 0>
136 class InactivePruneOp
137 {
138 public:
139     using ValueT = typename TreeT::ValueType;
140     using RootT = typename TreeT::RootNodeType;
141     using LeafT = typename TreeT::LeafNodeType;
142     static_assert(RootT::LEVEL > TerminationLevel, "TerminationLevel out of range");
143 
InactivePruneOp(TreeT & tree)144     InactivePruneOp(TreeT& tree) : mValue(tree.background())
145     {
146         tree.clearAllAccessors();//clear cache of nodes that could be pruned
147     }
148 
InactivePruneOp(TreeT & tree,const ValueT & v)149     InactivePruneOp(TreeT& tree, const ValueT& v) : mValue(v)
150     {
151         tree.clearAllAccessors();//clear cache of nodes that could be pruned
152     }
153 
154     // Nothing to do at the leaf node level
operator()155     void operator()(LeafT&) const {}
156 
157     // Prune the child nodes of the internal nodes
158     template<typename NodeT>
operator()159     void operator()(NodeT& node) const
160     {
161         if (NodeT::LEVEL > TerminationLevel) {
162             for (typename NodeT::ChildOnIter it=node.beginChildOn(); it; ++it) {
163                 if (it->isInactive()) node.addTile(it.pos(), mValue, false);
164             }
165         }
166     }
167 
168     // Prune the child nodes of the root node
operator()169     void operator()(RootT& root) const
170     {
171         for (typename RootT::ChildOnIter it = root.beginChildOn(); it; ++it) {
172             if (it->isInactive()) root.addTile(it.getCoord(), mValue, false);
173         }
174         root.eraseBackgroundTiles();
175     }
176 
177 private:
178     const ValueT mValue;
179 };// InactivePruneOp
180 
181 
182 template<typename TreeT, Index TerminationLevel = 0>
183 class TolerancePruneOp
184 {
185 public:
186     using ValueT = typename TreeT::ValueType;
187     using RootT = typename TreeT::RootNodeType;
188     using LeafT = typename TreeT::LeafNodeType;
189     static_assert(RootT::LEVEL > TerminationLevel, "TerminationLevel out of range");
190 
TolerancePruneOp(TreeT & tree,const ValueT & tol)191     TolerancePruneOp(TreeT& tree, const ValueT& tol) : mTolerance(tol)
192     {
193         tree.clearAllAccessors();//clear cache of nodes that could be pruned
194     }
195 
196     // Prune the child nodes of the root node
operator()197     inline void operator()(RootT& root) const
198     {
199         ValueT value;
200         bool   state;
201         for (typename RootT::ChildOnIter it = root.beginChildOn(); it; ++it) {
202             if (this->isConstant(*it, value, state)) root.addTile(it.getCoord(), value, state);
203         }
204         root.eraseBackgroundTiles();
205     }
206 
207     // Prune the child nodes of the internal nodes
208     template<typename NodeT>
operator()209     inline void operator()(NodeT& node) const
210     {
211         if (NodeT::LEVEL > TerminationLevel) {
212             ValueT value;
213             bool   state;
214             for (typename NodeT::ChildOnIter it=node.beginChildOn(); it; ++it) {
215                 if (this->isConstant(*it, value, state)) node.addTile(it.pos(), value, state);
216             }
217         }
218     }
219 
220     // Nothing to do at the leaf node level
operator()221     inline void operator()(LeafT&) const {}
222 
223 private:
224     // Private method specialized for leaf nodes
median(LeafT & leaf)225     inline ValueT median(LeafT& leaf) const {return leaf.medianAll(leaf.buffer().data());}
226 
227     // Private method for internal nodes
228     template<typename NodeT>
median(NodeT & node)229     inline typename NodeT::ValueType median(NodeT& node) const
230     {
231         using UnionT = typename NodeT::UnionType;
232         UnionT* data = const_cast<UnionT*>(node.getTable());//never do this at home kids :)
233         static const size_t midpoint = (NodeT::NUM_VALUES - 1) >> 1;
234         auto op = [](const UnionT& a, const UnionT& b){return a.getValue() < b.getValue();};
235         std::nth_element(data, data + midpoint, data + NodeT::NUM_VALUES, op);
236         return data[midpoint].getValue();
237     }
238 
239     // Specialization to nodes templated on booleans values
240     template<typename NodeT>
241     inline
242     typename std::enable_if<std::is_same<bool, typename NodeT::ValueType>::value, bool>::type
isConstant(NodeT & node,bool & value,bool & state)243     isConstant(NodeT& node, bool& value, bool& state) const
244     {
245         return node.isConstant(value, state, mTolerance);
246     }
247 
248     // Nodes templated on non-boolean values
249     template<typename NodeT>
250     inline
251     typename std::enable_if<!std::is_same<bool, typename NodeT::ValueType>::value, bool>::type
isConstant(NodeT & node,ValueT & value,bool & state)252     isConstant(NodeT& node, ValueT& value, bool& state) const
253     {
254         ValueT tmp;
255         const bool test = node.isConstant(value, tmp, state, mTolerance);
256         if (test) value = this->median(node);
257         return test;
258     }
259 
260     const ValueT mTolerance;
261 };// TolerancePruneOp
262 
263 
264 template<typename TreeT, Index TerminationLevel = 0>
265 class LevelSetPruneOp
266 {
267 public:
268     using ValueT = typename TreeT::ValueType;
269     using RootT = typename TreeT::RootNodeType;
270     using LeafT = typename TreeT::LeafNodeType;
271     static_assert(RootT::LEVEL > TerminationLevel, "TerminationLevel out of range");
272 
LevelSetPruneOp(TreeT & tree)273     LevelSetPruneOp(TreeT& tree)
274         : mOutside(tree.background())
275         , mInside(math::negative(mOutside))
276     {
277         if (math::isNegative(mOutside)) {
278             OPENVDB_THROW(ValueError,
279                           "LevelSetPruneOp: the background value cannot be negative!");
280         }
281         tree.clearAllAccessors();//clear cache of nodes that could be pruned
282     }
283 
LevelSetPruneOp(TreeT & tree,const ValueT & outside,const ValueT & inside)284     LevelSetPruneOp(TreeT& tree, const ValueT& outside, const ValueT& inside)
285         : mOutside(outside)
286         , mInside(inside)
287     {
288         if (math::isNegative(mOutside)) {
289             OPENVDB_THROW(ValueError,
290                           "LevelSetPruneOp: the outside value cannot be negative!");
291         }
292         if (!math::isNegative(mInside)) {
293             OPENVDB_THROW(ValueError,
294                           "LevelSetPruneOp: the inside value must be negative!");
295         }
296         tree.clearAllAccessors();//clear cache of nodes that could be pruned
297     }
298 
299     // Nothing to do at the leaf node level
operator()300     void operator()(LeafT&) const {}
301 
302     // Prune the child nodes of the internal nodes
303     template<typename NodeT>
operator()304     void operator()(NodeT& node) const
305     {
306         if (NodeT::LEVEL > TerminationLevel) {
307             for (typename NodeT::ChildOnIter it=node.beginChildOn(); it; ++it) {
308                 if (it->isInactive()) node.addTile(it.pos(), this->getTileValue(it), false);
309             }
310         }
311     }
312 
313     // Prune the child nodes of the root node
operator()314     void operator()(RootT& root) const
315     {
316         for (typename RootT::ChildOnIter it = root.beginChildOn(); it; ++it) {
317             if (it->isInactive()) root.addTile(it.getCoord(), this->getTileValue(it), false);
318         }
319         root.eraseBackgroundTiles();
320     }
321 
322 private:
323     template <typename IterT>
getTileValue(const IterT & iter)324     inline ValueT getTileValue(const IterT& iter) const
325     {
326         return  math::isNegative(iter->getFirstValue()) ? mInside : mOutside;
327     }
328 
329     const ValueT mOutside, mInside;
330 };// LevelSetPruneOp
331 
332 
333 template<typename TreeT>
334 void
prune(TreeT & tree,typename TreeT::ValueType tol,bool threaded,size_t grainSize)335 prune(TreeT& tree, typename TreeT::ValueType tol, bool threaded, size_t grainSize)
336 {
337     tree::NodeManager<TreeT, TreeT::DEPTH-2> nodes(tree);
338     TolerancePruneOp<TreeT> op(tree, tol);
339     nodes.foreachBottomUp(op, threaded, grainSize);
340 }
341 
342 
343 template<typename TreeT>
344 void
pruneTiles(TreeT & tree,typename TreeT::ValueType tol,bool threaded,size_t grainSize)345 pruneTiles(TreeT& tree, typename TreeT::ValueType tol, bool threaded, size_t grainSize)
346 {
347     tree::NodeManager<TreeT, TreeT::DEPTH-3> nodes(tree);
348     TolerancePruneOp<TreeT> op(tree, tol);
349     nodes.foreachBottomUp(op, threaded, grainSize);
350 }
351 
352 
353 template<typename TreeT>
354 void
pruneInactive(TreeT & tree,bool threaded,size_t grainSize)355 pruneInactive(TreeT& tree, bool threaded, size_t grainSize)
356 {
357     tree::NodeManager<TreeT, TreeT::DEPTH-2> nodes(tree);
358     InactivePruneOp<TreeT> op(tree);
359     nodes.foreachBottomUp(op, threaded, grainSize);
360 }
361 
362 
363 template<typename TreeT>
364 void
pruneInactiveWithValue(TreeT & tree,const typename TreeT::ValueType & v,bool threaded,size_t grainSize)365 pruneInactiveWithValue(TreeT& tree, const typename TreeT::ValueType& v,
366     bool threaded, size_t grainSize)
367 {
368     tree::NodeManager<TreeT, TreeT::DEPTH-2> nodes(tree);
369     InactivePruneOp<TreeT> op(tree, v);
370     nodes.foreachBottomUp(op, threaded, grainSize);
371 }
372 
373 
374 template<typename TreeT>
375 void
pruneLevelSet(TreeT & tree,const typename TreeT::ValueType & outside,const typename TreeT::ValueType & inside,bool threaded,size_t grainSize)376 pruneLevelSet(TreeT& tree,
377               const typename TreeT::ValueType& outside,
378               const typename TreeT::ValueType& inside,
379               bool threaded,
380               size_t grainSize)
381 {
382     tree::NodeManager<TreeT, TreeT::DEPTH-2> nodes(tree);
383     LevelSetPruneOp<TreeT> op(tree, outside, inside);
384     nodes.foreachBottomUp(op, threaded, grainSize);
385 }
386 
387 
388 template<typename TreeT>
389 void
pruneLevelSet(TreeT & tree,bool threaded,size_t grainSize)390 pruneLevelSet(TreeT& tree, bool threaded, size_t grainSize)
391 {
392     tree::NodeManager<TreeT, TreeT::DEPTH-2> nodes(tree);
393     LevelSetPruneOp<TreeT> op(tree);
394     nodes.foreachBottomUp(op, threaded, grainSize);
395 }
396 
397 
398 ////////////////////////////////////////
399 
400 
401 // Explicit Template Instantiation
402 
403 #ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION
404 
405 #ifdef OPENVDB_INSTANTIATE_PRUNE
406 #include <openvdb/util/ExplicitInstantiation.h>
407 #endif
408 
409 #define _FUNCTION(TreeT) \
410     void prune(TreeT&, TreeT::ValueType, bool, size_t)
411 OPENVDB_VOLUME_TREE_INSTANTIATE(_FUNCTION)
412 #undef _FUNCTION
413 
414 #define _FUNCTION(TreeT) \
415     void pruneTiles(TreeT&, TreeT::ValueType, bool, size_t)
416 OPENVDB_VOLUME_TREE_INSTANTIATE(_FUNCTION)
417 #undef _FUNCTION
418 
419 #define _FUNCTION(TreeT) \
420     void pruneInactive(TreeT&, bool, size_t)
421 OPENVDB_VOLUME_TREE_INSTANTIATE(_FUNCTION)
422 #undef _FUNCTION
423 
424 #define _FUNCTION(TreeT) \
425     void pruneInactiveWithValue(TreeT&, const TreeT::ValueType&, bool, size_t)
426 OPENVDB_VOLUME_TREE_INSTANTIATE(_FUNCTION)
427 #undef _FUNCTION
428 
429 #define _FUNCTION(TreeT) \
430     void pruneLevelSet(TreeT&, bool, size_t)
431 OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
432 #undef _FUNCTION
433 
434 #define _FUNCTION(TreeT) \
435     void pruneLevelSet(TreeT&, const TreeT::ValueType&, const TreeT::ValueType&, bool, size_t)
436 OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
437 #undef _FUNCTION
438 
439 #endif // OPENVDB_USE_EXPLICIT_INSTANTIATION
440 
441 
442 } // namespace tools
443 } // namespace OPENVDB_VERSION_NAME
444 } // namespace openvdb
445 
446 #endif // OPENVDB_TOOLS_PRUNE_HAS_BEEN_INCLUDED
447