HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
DenseSparseTools.h
Go to the documentation of this file.
1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 
4 #ifndef OPENVDB_TOOLS_DENSESPARSETOOLS_HAS_BEEN_INCLUDED
5 #define OPENVDB_TOOLS_DENSESPARSETOOLS_HAS_BEEN_INCLUDED
6 
7 #include <tbb/parallel_reduce.h>
8 #include <tbb/blocked_range3d.h>
9 #include <tbb/blocked_range2d.h>
10 #include <tbb/blocked_range.h>
11 #include <openvdb/Types.h>
13 #include "Dense.h"
14 #include <algorithm> // for std::min()
15 #include <vector>
16 
17 
18 namespace openvdb {
20 namespace OPENVDB_VERSION_NAME {
21 namespace tools {
22 
23 /// @brief Selectively extract and transform data from a dense grid, producing a
24 /// sparse tree with leaf nodes only (e.g. create a tree from the square
25 /// of values greater than a cutoff.)
26 /// @param dense A dense grid that acts as a data source
27 /// @param functor A functor that selects and transforms data for output
28 /// @param background The background value of the resulting sparse grid
29 /// @param threaded Option to use threaded or serial code path
30 /// @return @c Ptr to tree with the valuetype and configuration defined
31 /// by typedefs in the @c functor.
32 /// @note To achieve optimal sparsity consider calling the prune()
33 /// method on the result.
34 /// @note To simply copy the all the data from a Dense grid to a
35 /// OpenVDB Grid, use tools::copyFromDense() for better performance.
36 ///
37 /// The type of the sparse tree is determined by the specified OtpType
38 /// functor by means of the typedef OptType::ResultTreeType
39 ///
40 /// The OptType function is responsible for the the transformation of
41 /// dense grid data to sparse grid data on a per-voxel basis.
42 ///
43 /// Only leaf nodes with active values will be added to the sparse grid.
44 ///
45 /// The OpType must struct that defines a the minimal form
46 /// @code
47 /// struct ExampleOp
48 /// {
49 /// using ResultTreeType = DesiredTreeType;
50 ///
51 /// template<typename IndexOrCoord>
52 /// void OpType::operator() (const DenseValueType a, const IndexOrCoord& ijk,
53 /// ResultTreeType::LeafNodeType* leaf);
54 /// };
55 /// @endcode
56 ///
57 /// For example, to generate a <ValueType, 5, 4, 3> tree with valuesOn
58 /// at locations greater than a given maskvalue
59 /// @code
60 /// template<typename ValueType>
61 /// class Rule
62 /// {
63 /// public:
64 /// // Standard tree type (e.g. MaskTree or FloatTree in openvdb.h)
65 /// using ResultTreeType = typename openvdb::tree::Tree4<ValueType, 5, 4, 3>::Type;
66 ///
67 /// using ResultLeafNodeType = typename ResultTreeType::LeafNodeType;
68 /// using ResultValueType = typename ResultTreeType::ValueType;
69 ///
70 /// using DenseValueType = float;
71 ///
72 /// using Index = openvdb::Coord::ValueType;
73 ///
74 /// Rule(const DenseValueType& value): mMaskValue(value){};
75 ///
76 /// template<typename IndexOrCoord>
77 /// void operator()(const DenseValueType& a, const IndexOrCoord& offset,
78 /// ResultLeafNodeType* leaf) const
79 /// {
80 /// if (a > mMaskValue) {
81 /// leaf->setValueOn(offset, a);
82 /// }
83 /// }
84 ///
85 /// private:
86 /// const DenseValueType mMaskValue;
87 /// };
88 /// @endcode
89 template<typename OpType, typename DenseType>
90 typename OpType::ResultTreeType::Ptr
91 extractSparseTree(const DenseType& dense, const OpType& functor,
92  const typename OpType::ResultValueType& background,
93  bool threaded = true);
94 
95 /// This struct that aids template resolution of a new tree type
96 /// has the same configuration at TreeType, but the ValueType from
97 /// DenseType.
98 template<typename DenseType, typename TreeType>
100 {
101  using ValueType = typename DenseType::ValueType;
102  using Type = typename TreeType::template ValueConverter<ValueType>::Type;
103 };
104 
105 
106 /// @brief Copy data from the intersection of a sparse tree and a dense input grid.
107 /// The resulting tree has the same configuration as the sparse tree, but holds
108 /// the data type specified by the dense input.
109 /// @param dense A dense grid that acts as a data source
110 /// @param mask The active voxels and tiles intersected with dense define iteration mask
111 /// @param background The background value of the resulting sparse grid
112 /// @param threaded Option to use threaded or serial code path
113 /// @return @c Ptr to tree with the same configuration as @c mask but of value type
114 /// defined by @c dense.
115 template<typename DenseType, typename MaskTreeType>
117 extractSparseTreeWithMask(const DenseType& dense,
118  const MaskTreeType& mask,
119  const typename DenseType::ValueType& background,
120  bool threaded = true);
121 
122 
123 /// Apply a point-wise functor to the intersection of a dense grid and a given bounding box
124 /// @param dense A dense grid to be transformed
125 /// @param bbox Index space bounding box, define region where the transformation is applied
126 /// @param op A functor that acts on the dense grid value type
127 /// @param parallel Used to select multithreaded or single threaded
128 /// Minimally, the @c op class has to support a @c operator() method,
129 /// @code
130 /// // Square values in a grid
131 /// struct Op
132 /// {
133 /// ValueT operator()(const ValueT& in) const
134 /// {
135 /// // do work
136 /// ValueT result = in * in;
137 ///
138 /// return result;
139 /// }
140 /// };
141 /// @endcode
142 /// NB: only Dense grids with memory layout zxy are supported
143 template<typename ValueT, typename OpType>
145  const openvdb::CoordBBox& bbox, const OpType& op, bool parallel=true);
146 
147 /// We currrently support the following operations when compositing sparse
148 /// data into a dense grid.
151 };
152 
153 /// @brief Composite data from a sparse tree into a dense array of the same value type.
154 /// @param dense Dense grid to be altered by the operation
155 /// @param source Sparse data to composite into @c dense
156 /// @param alpha Sparse Alpha mask used in compositing operations.
157 /// @param beta Constant multiplier on src
158 /// @param strength Constant multiplier on alpha
159 /// @param threaded Enable threading for this operation.
160 template<DSCompositeOp, typename TreeT>
161 void compositeToDense(Dense<typename TreeT::ValueType, LayoutZYX>& dense,
162  const TreeT& source,
163  const TreeT& alpha,
164  const typename TreeT::ValueType beta,
165  const typename TreeT::ValueType strength,
166  bool threaded = true);
167 
168 
169 /// @brief Functor-based class used to extract data that satisfies some
170 /// criteria defined by the embedded @c OpType functor. The @c extractSparseTree
171 /// function wraps this class.
172 template<typename OpType, typename DenseType>
174 {
175 public:
176  using Index = openvdb::math::Coord::ValueType;
177 
178  using DenseValueType = typename DenseType::ValueType;
179  using ResultTreeType = typename OpType::ResultTreeType;
180  using ResultValueType = typename ResultTreeType::ValueType;
181  using ResultLeafNodeType = typename ResultTreeType::LeafNodeType;
182  using MaskTree = typename ResultTreeType::template ValueConverter<ValueMask>::Type;
183 
184  using Range3d = tbb::blocked_range3d<Index, Index, Index>;
185 
186 private:
187  const DenseType& mDense;
188  const OpType& mFunctor;
189  const ResultValueType mBackground;
190  const openvdb::math::CoordBBox mBBox;
191  const Index mWidth;
192  typename ResultTreeType::Ptr mMask;
193  openvdb::math::Coord mMin;
194 
195 public:
196  SparseExtractor(const DenseType& dense, const OpType& functor,
197  const ResultValueType background) :
198  mDense(dense), mFunctor(functor),
199  mBackground(background),
200  mBBox(dense.bbox()),
201  mWidth(ResultLeafNodeType::DIM),
202  mMask( new ResultTreeType(mBackground))
203  {}
204 
205  SparseExtractor(const DenseType& dense,
206  const openvdb::math::CoordBBox& bbox,
207  const OpType& functor,
208  const ResultValueType background) :
209  mDense(dense), mFunctor(functor),
210  mBackground(background),
211  mBBox(bbox),
212  mWidth(ResultLeafNodeType::DIM),
213  mMask( new ResultTreeType(mBackground))
214  {
215  // mBBox must be inside the coordinate rage of the dense grid
216  if (!dense.bbox().isInside(mBBox)) {
217  OPENVDB_THROW(ValueError, "Data extraction window out of bound");
218  }
219  }
220 
222  mDense(other.mDense), mFunctor(other.mFunctor),
223  mBackground(other.mBackground), mBBox(other.mBBox),
224  mWidth(other.mWidth),
225  mMask(new ResultTreeType(mBackground)),
226  mMin(other.mMin)
227  {}
228 
229  typename ResultTreeType::Ptr extract(bool threaded = true)
230  {
231  // Construct 3D range of leaf nodes that
232  // intersect mBBox.
233 
234  // Snap the bbox to nearest leaf nodes min and max
235 
236  openvdb::math::Coord padded_min = mBBox.min();
237  openvdb::math::Coord padded_max = mBBox.max();
238 
239 
240  padded_min &= ~(mWidth - 1);
241  padded_max &= ~(mWidth - 1);
242 
243  padded_max[0] += mWidth - 1;
244  padded_max[1] += mWidth - 1;
245  padded_max[2] += mWidth - 1;
246 
247 
248  // number of leaf nodes in each direction
249  // division by leaf width, e.g. 8 in most cases
250 
251  const Index xleafCount = ( padded_max.x() - padded_min.x() + 1 ) / mWidth;
252  const Index yleafCount = ( padded_max.y() - padded_min.y() + 1 ) / mWidth;
253  const Index zleafCount = ( padded_max.z() - padded_min.z() + 1 ) / mWidth;
254 
255  mMin = padded_min;
256 
257  Range3d leafRange(0, xleafCount, 1,
258  0, yleafCount, 1,
259  0, zleafCount, 1);
260 
261  // Iterate over the leafnodes applying *this as a functor.
262  if (threaded) {
263  tbb::parallel_reduce(leafRange, *this);
264  } else {
265  (*this)(leafRange);
266  }
267 
268  return mMask;
269  }
270 
271  void operator()(const Range3d& range)
272  {
273  ResultLeafNodeType* leaf = nullptr;
274 
275  // Unpack the range3d item.
276  const Index imin = range.pages().begin();
277  const Index imax = range.pages().end();
278 
279  const Index jmin = range.rows().begin();
280  const Index jmax = range.rows().end();
281 
282  const Index kmin = range.cols().begin();
283  const Index kmax = range.cols().end();
284 
285 
286  // loop over all the candidate leafs. Adding only those with 'true' values
287  // to the tree
288 
289  for (Index i = imin; i < imax; ++i) {
290  for (Index j = jmin; j < jmax; ++j) {
291  for (Index k = kmin; k < kmax; ++k) {
292 
293  // Calculate the origin of candidate leaf
294  const openvdb::math::Coord origin =
295  mMin + openvdb::math::Coord(mWidth * i,
296  mWidth * j,
297  mWidth * k );
298 
299  if (leaf == nullptr) {
300  leaf = new ResultLeafNodeType(origin, mBackground);
301  } else {
302  leaf->setOrigin(origin);
303  leaf->fill(mBackground);
304  leaf->setValuesOff();
305  }
306 
307  // The bounding box for this leaf
308 
309  openvdb::math::CoordBBox localBBox = leaf->getNodeBoundingBox();
310 
311  // Shrink to the intersection with mBBox (i.e. the dense
312  // volume)
313 
314  localBBox.intersect(mBBox);
315 
316  // Early out for non-intersecting leafs
317 
318  if (localBBox.empty()) continue;
319 
320 
321  const openvdb::math::Coord start = localBBox.getStart();
322  const openvdb::math::Coord end = localBBox.getEnd();
323 
324  // Order the looping to respect the memory layout in
325  // the Dense source
326 
327  if (mDense.memoryLayout() == openvdb::tools::LayoutZYX) {
328 
329  openvdb::math::Coord ijk;
330  Index offset;
331  const DenseValueType* dp;
332  for (ijk[0] = start.x(); ijk[0] < end.x(); ++ijk[0] ) {
333  for (ijk[1] = start.y(); ijk[1] < end.y(); ++ijk[1] ) {
334  for (ijk[2] = start.z(),
335  offset = ResultLeafNodeType::coordToOffset(ijk),
336  dp = &mDense.getValue(ijk);
337  ijk[2] < end.z(); ++ijk[2], ++offset, ++dp) {
338 
339  mFunctor(*dp, offset, leaf);
340  }
341  }
342  }
343 
344  } else {
345 
346  openvdb::math::Coord ijk;
347  const DenseValueType* dp;
348  for (ijk[2] = start.z(); ijk[2] < end.z(); ++ijk[2]) {
349  for (ijk[1] = start.y(); ijk[1] < end.y(); ++ijk[1]) {
350  for (ijk[0] = start.x(),
351  dp = &mDense.getValue(ijk);
352  ijk[0] < end.x(); ++ijk[0], ++dp) {
353 
354  mFunctor(*dp, ijk, leaf);
355 
356  }
357  }
358  }
359  }
360 
361  // Only add non-empty leafs (empty is defined as all inactive)
362 
363  if (!leaf->isEmpty()) {
364  mMask->addLeaf(leaf);
365  leaf = nullptr;
366  }
367 
368  }
369  }
370  }
371 
372  // Clean up an unused leaf.
373 
374  delete leaf;
375  }
376 
377  void join(SparseExtractor& rhs) {
378  mMask->merge(*rhs.mMask);
379  }
380 }; // class SparseExtractor
381 
382 
383 template<typename OpType, typename DenseType>
384 typename OpType::ResultTreeType::Ptr
385 extractSparseTree(const DenseType& dense, const OpType& functor,
386  const typename OpType::ResultValueType& background,
387  bool threaded)
388 {
389  // Construct the mask using a parallel reduce pattern.
390  // Each thread computes disjoint mask-trees. The join merges
391  // into a single tree.
392 
393  SparseExtractor<OpType, DenseType> extractor(dense, functor, background);
394 
395  return extractor.extract(threaded);
396 }
397 
398 
399 /// @brief Functor-based class used to extract data from a dense grid, at
400 /// the index-space intersection with a supplied mask in the form of a sparse tree.
401 /// The @c extractSparseTreeWithMask function wraps this class.
402 template<typename DenseType, typename MaskTreeType>
404 {
405 public:
408  using ResultLeafNodeType = typename ResultTreeType::LeafNodeType;
409  using ResultValueType = typename ResultTreeType::ValueType;
411 
412  using MaskTree = typename ResultTreeType::template ValueConverter<ValueMask>::Type;
414  using MaskLeafVec = std::vector<const typename MaskTree::LeafNodeType*>;
415 
416 
417  SparseMaskedExtractor(const DenseType& dense,
418  const ResultValueType& background,
419  const MaskLeafVec& leafVec
420  ):
421  mDense(dense), mBackground(background), mBBox(dense.bbox()),
422  mLeafVec(leafVec),
423  mResult(new ResultTreeType(mBackground))
424  {}
425 
427  mDense(other.mDense), mBackground(other.mBackground), mBBox(other.mBBox),
428  mLeafVec(other.mLeafVec), mResult( new ResultTreeType(mBackground))
429  {}
430 
431  typename ResultTreeType::Ptr extract(bool threaded = true)
432  {
433  tbb::blocked_range<size_t> range(0, mLeafVec.size());
434 
435  if (threaded) {
436  tbb::parallel_reduce(range, *this);
437  } else {
438  (*this)(range);
439  }
440 
441  return mResult;
442  }
443 
444  // Used in looping over leaf nodes in the masked grid
445  // and using the active mask to select data to
446  void operator()(const tbb::blocked_range<size_t>& range)
447  {
448  ResultLeafNodeType* leaf = nullptr;
449 
450  // loop over all the candidate leafs. Adding only those with 'true' values
451  // to the tree
452 
453  for (size_t idx = range.begin(); idx < range.end(); ++ idx) {
454 
455  const typename MaskTree::LeafNodeType* maskLeaf = mLeafVec[idx];
456 
457  // The bounding box for this leaf
458 
459  openvdb::math::CoordBBox localBBox = maskLeaf->getNodeBoundingBox();
460 
461  // Shrink to the intersection with the dense volume
462 
463  localBBox.intersect(mBBox);
464 
465  // Early out if there was no intersection
466 
467  if (localBBox.empty()) continue;
468 
469  // Reset or allocate the target leaf
470 
471  if (leaf == nullptr) {
472  leaf = new ResultLeafNodeType(maskLeaf->origin(), mBackground);
473  } else {
474  leaf->setOrigin(maskLeaf->origin());
475  leaf->fill(mBackground);
476  leaf->setValuesOff();
477  }
478 
479  // Iterate over the intersecting bounding box
480  // copying active values to the result tree
481 
482  const openvdb::math::Coord start = localBBox.getStart();
483  const openvdb::math::Coord end = localBBox.getEnd();
484 
485  openvdb::math::Coord ijk;
486 
487  if (mDense.memoryLayout() == openvdb::tools::LayoutZYX
488  && maskLeaf->isDense()) {
489 
490  Index offset;
491  const DenseValueType* src;
492  for (ijk[0] = start.x(); ijk[0] < end.x(); ++ijk[0] ) {
493  for (ijk[1] = start.y(); ijk[1] < end.y(); ++ijk[1] ) {
494  for (ijk[2] = start.z(),
495  offset = ResultLeafNodeType::coordToOffset(ijk),
496  src = &mDense.getValue(ijk);
497  ijk[2] < end.z(); ++ijk[2], ++offset, ++src) {
498 
499  // copy into leaf
500  leaf->setValueOn(offset, *src);
501  }
502 
503  }
504  }
505 
506  } else {
507 
508  Index offset;
509  for (ijk[0] = start.x(); ijk[0] < end.x(); ++ijk[0] ) {
510  for (ijk[1] = start.y(); ijk[1] < end.y(); ++ijk[1] ) {
511  for (ijk[2] = start.z(),
512  offset = ResultLeafNodeType::coordToOffset(ijk);
513  ijk[2] < end.z(); ++ijk[2], ++offset) {
514 
515  if (maskLeaf->isValueOn(offset)) {
516  const ResultValueType denseValue = mDense.getValue(ijk);
517  leaf->setValueOn(offset, denseValue);
518  }
519  }
520  }
521  }
522  }
523  // Only add non-empty leafs (empty is defined as all inactive)
524 
525  if (!leaf->isEmpty()) {
526  mResult->addLeaf(leaf);
527  leaf = nullptr;
528  }
529  }
530 
531  // Clean up an unused leaf.
532 
533  delete leaf;
534  }
535 
537  mResult->merge(*rhs.mResult);
538  }
539 
540 
541 private:
542  const DenseType& mDense;
543  const ResultValueType mBackground;
544  const openvdb::math::CoordBBox& mBBox;
545  const MaskLeafVec& mLeafVec;
546 
547  typename ResultTreeType::Ptr mResult;
548 
549 }; // class SparseMaskedExtractor
550 
551 
552 /// @brief a simple utility class used by @c extractSparseTreeWithMask
553 template<typename _ResultTreeType, typename DenseValueType>
555 {
556  using ResultTreeType = _ResultTreeType;
557  using ResultLeafNodeType = typename ResultTreeType::LeafNodeType;
558 
559  template<typename CoordOrIndex> inline void
560  operator()(const DenseValueType& a, const CoordOrIndex& offset, ResultLeafNodeType* leaf) const
561  {
562  leaf->setValueOn(offset, a);
563  }
564 };
565 
566 
567 template<typename DenseType, typename MaskTreeType>
568 typename DSConverter<DenseType, MaskTreeType>::Type::Ptr
569 extractSparseTreeWithMask(const DenseType& dense,
570  const MaskTreeType& maskProxy,
571  const typename DenseType::ValueType& background,
572  bool threaded)
573 {
575  using DenseValueType = typename LeafExtractor::DenseValueType;
576  using ResultTreeType = typename LeafExtractor::ResultTreeType;
577  using MaskLeafVec = typename LeafExtractor::MaskLeafVec;
578  using MaskTree = typename LeafExtractor::MaskTree;
579  using MaskLeafCIter = typename LeafExtractor::MaskLeafCIter;
580  using ExtractionRule = ExtractAll<ResultTreeType, DenseValueType>;
581 
582  // Use Mask tree to hold the topology
583 
584  MaskTree maskTree(maskProxy, false, TopologyCopy());
585 
586  // Construct an array of pointers to the mask leafs.
587 
588  const size_t leafCount = maskTree.leafCount();
589  MaskLeafVec leafarray(leafCount);
590  MaskLeafCIter leafiter = maskTree.cbeginLeaf();
591  for (size_t n = 0; n != leafCount; ++n, ++leafiter) {
592  leafarray[n] = leafiter.getLeaf();
593  }
594 
595 
596  // Extract the data that is masked leaf nodes in the mask.
597 
598  LeafExtractor leafextractor(dense, background, leafarray);
599  typename ResultTreeType::Ptr resultTree = leafextractor.extract(threaded);
600 
601 
602  // Extract data that is masked by tiles in the mask.
603 
604 
605  // Loop over the mask tiles, extracting the data into new trees.
606  // These trees will be leaf-orthogonal to the leafTree (i.e. no leaf
607  // nodes will overlap). Merge these trees into the result.
608 
609  typename MaskTreeType::ValueOnCIter tileIter(maskProxy);
610  tileIter.setMaxDepth(MaskTreeType::ValueOnCIter::LEAF_DEPTH - 1);
611 
612  // Return the leaf tree if the mask had no tiles
613 
614  if (!tileIter) return resultTree;
615 
616  ExtractionRule allrule;
617 
618  // Loop over the tiles in series, but the actual data extraction
619  // is in parallel.
620 
621  CoordBBox bbox;
622  for ( ; tileIter; ++tileIter) {
623 
624  // Find the intersection of the tile with the dense grid.
625 
626  tileIter.getBoundingBox(bbox);
627  bbox.intersect(dense.bbox());
628 
629  if (bbox.empty()) continue;
630 
631  SparseExtractor<ExtractionRule, DenseType> copyData(dense, bbox, allrule, background);
632  typename ResultTreeType::Ptr fromTileTree = copyData.extract(threaded);
633  resultTree->merge(*fromTileTree);
634  }
635 
636  return resultTree;
637 }
638 
639 
640 /// @brief Class that applies a functor to the index space intersection
641 /// of a prescribed bounding box and the dense grid.
642 /// NB: This class only supports DenseGrids with ZYX memory layout.
643 template<typename _ValueT, typename OpType>
645 {
646 public:
647  using ValueT = _ValueT;
649  using IntType = openvdb::math::Coord::ValueType;
650  using RangeType = tbb::blocked_range2d<IntType, IntType>;
651 
652 private:
653  DenseT& mDense;
654  const OpType& mOp;
655  openvdb::math::CoordBBox mBBox;
656 
657 public:
658  DenseTransformer(DenseT& dense, const openvdb::math::CoordBBox& bbox, const OpType& functor):
659  mDense(dense), mOp(functor), mBBox(dense.bbox())
660  {
661  // The iteration space is the intersection of the
662  // input bbox and the index-space covered by the dense grid
663  mBBox.intersect(bbox);
664  }
665 
667  mDense(other.mDense), mOp(other.mOp), mBBox(other.mBBox) {}
668 
669  void apply(bool threaded = true) {
670 
671  // Early out if the iteration space is empty
672 
673  if (mBBox.empty()) return;
674 
675 
676  const openvdb::math::Coord start = mBBox.getStart();
677  const openvdb::math::Coord end = mBBox.getEnd();
678 
679  // The iteration range only the slower two directions.
680  const RangeType range(start.x(), end.x(), 1,
681  start.y(), end.y(), 1);
682 
683  if (threaded) {
684  tbb::parallel_for(range, *this);
685  } else {
686  (*this)(range);
687  }
688  }
689 
690  void operator()(const RangeType& range) const {
691 
692  // The stride in the z-direction.
693  // Note: the bbox is [inclusive, inclusive]
694 
695  const size_t zlength = size_t(mBBox.max().z() - mBBox.min().z() + 1);
696 
697  const IntType imin = range.rows().begin();
698  const IntType imax = range.rows().end();
699  const IntType jmin = range.cols().begin();
700  const IntType jmax = range.cols().end();
701 
702 
703  openvdb::math::Coord xyz(imin, jmin, mBBox.min().z());
704  for (xyz[0] = imin; xyz[0] != imax; ++xyz[0]) {
705  for (xyz[1] = jmin; xyz[1] != jmax; ++xyz[1]) {
706 
707  mOp.transform(mDense, xyz, zlength);
708  }
709  }
710  }
711 }; // class DenseTransformer
712 
713 
714 /// @brief a wrapper struct used to avoid unnecessary computation of
715 /// memory access from @c Coord when all offsets are guaranteed to be
716 /// within the dense grid.
717 template<typename ValueT, typename PointWiseOp>
719 {
720  ContiguousOp(const PointWiseOp& op) : mOp(op){}
721 
723  inline void transform(DenseT& dense, openvdb::math::Coord& ijk, size_t size) const
724  {
725  ValueT* dp = const_cast<ValueT*>(&dense.getValue(ijk));
726 
727  for (size_t offset = 0; offset < size; ++offset) {
728  dp[offset] = mOp(dp[offset]);
729  }
730  }
731 
732  const PointWiseOp mOp;
733 };
734 
735 
736 /// Apply a point-wise functor to the intersection of a dense grid and a given bounding box
737 template<typename ValueT, typename PointwiseOpT>
738 void
740  const openvdb::CoordBBox& bbox,
741  const PointwiseOpT& functor, bool parallel)
742 {
744 
745  // Convert the Op so it operates on a contiguous line in memory
746 
747  OpT op(functor);
748 
749  // Apply to the index space intersection in the dense grid
750  DenseTransformer<ValueT, OpT> transformer(dense, bbox, op);
751  transformer.apply(parallel);
752 }
753 
754 
755 template<typename CompositeMethod, typename _TreeT>
757 {
758 public:
759  using TreeT = _TreeT;
760  using ValueT = typename TreeT::ValueType;
761  using LeafT = typename TreeT::LeafNodeType;
762  using MaskTreeT = typename TreeT::template ValueConverter<ValueMask>::Type;
763  using MaskLeafT = typename MaskTreeT::LeafNodeType;
765  using Index = openvdb::math::Coord::ValueType;
766  using Range3d = tbb::blocked_range3d<Index, Index, Index>;
767 
769  const ValueT beta, const ValueT strength) :
770  mDense(dense), mSource(source), mAlpha(alpha), mBeta(beta), mStrength(strength)
771  {}
772 
774  mDense(other.mDense), mSource(other.mSource), mAlpha(other.mAlpha),
775  mBeta(other.mBeta), mStrength(other.mStrength) {}
776 
777 
778  void sparseComposite(bool threaded)
779  {
780  const ValueT beta = mBeta;
781  const ValueT strength = mStrength;
782 
783  // construct a tree that defines the iteration space
784 
785  MaskTreeT maskTree(mSource, false /*background*/, openvdb::TopologyCopy());
786  maskTree.topologyUnion(mAlpha);
787 
788  // Composite regions that are represented by leafnodes in either mAlpha or mSource
789  // Parallelize over bool-leafs
790 
791  openvdb::tree::LeafManager<const MaskTreeT> maskLeafs(maskTree);
792  maskLeafs.foreach(*this, threaded);
793 
794  // Composite regions that are represented by tiles
795  // Parallelize within each tile.
796 
797  typename MaskTreeT::ValueOnCIter citer = maskTree.cbeginValueOn();
798  citer.setMaxDepth(MaskTreeT::ValueOnCIter::LEAF_DEPTH - 1);
799 
800  if (!citer) return;
801 
802  typename tree::ValueAccessor<const TreeT> alphaAccessor(mAlpha);
803  typename tree::ValueAccessor<const TreeT> sourceAccessor(mSource);
804 
805  for (; citer; ++citer) {
806 
807  const openvdb::math::Coord org = citer.getCoord();
808 
809  // Early out if both alpha and source are zero in this tile.
810 
811  const ValueT alphaValue = alphaAccessor.getValue(org);
812  const ValueT sourceValue = sourceAccessor.getValue(org);
813 
814  if (openvdb::math::isZero(alphaValue) &&
815  openvdb::math::isZero(sourceValue)) continue;
816 
817  // Compute overlap of tile with the dense grid
818 
819  openvdb::math::CoordBBox localBBox = citer.getBoundingBox();
820  localBBox.intersect(mDense.bbox());
821 
822  // Early out if there is no intersection
823 
824  if (localBBox.empty()) continue;
825 
826  // Composite the tile-uniform values into the dense grid.
827  compositeFromTile(mDense, localBBox, sourceValue,
828  alphaValue, beta, strength, threaded);
829  }
830  }
831 
832  // Composites leaf values where the alpha values are active.
833  // Used in sparseComposite
834  void inline operator()(const MaskLeafT& maskLeaf, size_t /*i*/) const
835  {
836  using ULeaf = UniformLeaf;
837  openvdb::math::CoordBBox localBBox = maskLeaf.getNodeBoundingBox();
838  localBBox.intersect(mDense.bbox());
839 
840  // Early out for non-overlapping leafs
841 
842  if (localBBox.empty()) return;
843 
844  const openvdb::math::Coord org = maskLeaf.origin();
845  const LeafT* alphaLeaf = mAlpha.probeLeaf(org);
846  const LeafT* sourceLeaf = mSource.probeLeaf(org);
847 
848  if (!sourceLeaf) {
849 
850  // Create a source leaf proxy with the correct value
851  ULeaf uniformSource(mSource.getValue(org));
852 
853  if (!alphaLeaf) {
854 
855  // Create an alpha leaf proxy with the correct value
856  ULeaf uniformAlpha(mAlpha.getValue(org));
857 
858  compositeFromLeaf(mDense, localBBox, uniformSource, uniformAlpha,
859  mBeta, mStrength);
860  } else {
861 
862  compositeFromLeaf(mDense, localBBox, uniformSource, *alphaLeaf,
863  mBeta, mStrength);
864  }
865  } else {
866  if (!alphaLeaf) {
867 
868  // Create an alpha leaf proxy with the correct value
869  ULeaf uniformAlpha(mAlpha.getValue(org));
870 
871  compositeFromLeaf(mDense, localBBox, *sourceLeaf, uniformAlpha,
872  mBeta, mStrength);
873  } else {
874 
875  compositeFromLeaf(mDense, localBBox, *sourceLeaf, *alphaLeaf,
876  mBeta, mStrength);
877  }
878  }
879  }
880  // i.e. it assumes that all valueOff Alpha voxels have value 0.
881 
882  template<typename LeafT1, typename LeafT2>
883  inline static void compositeFromLeaf(DenseT& dense, const openvdb::math::CoordBBox& bbox,
884  const LeafT1& source, const LeafT2& alpha,
885  const ValueT beta, const ValueT strength)
886  {
887  using IntType = openvdb::math::Coord::ValueType;
888 
889  const ValueT sbeta = strength * beta;
890  openvdb::math::Coord ijk = bbox.min();
891 
892 
893  if (alpha.isDense() /*all active values*/) {
894 
895  // Optimal path for dense alphaLeaf
896  const IntType size = bbox.max().z() + 1 - bbox.min().z();
897 
898  for (ijk[0] = bbox.min().x(); ijk[0] < bbox.max().x() + 1; ++ijk[0]) {
899  for (ijk[1] = bbox.min().y(); ijk[1] < bbox.max().y() + 1; ++ijk[1]) {
900 
901  ValueT* d = const_cast<ValueT*>(&dense.getValue(ijk));
902  const ValueT* a = &alpha.getValue(ijk);
903  const ValueT* s = &source.getValue(ijk);
904 
905  for (IntType idx = 0; idx < size; ++idx) {
906  d[idx] = CompositeMethod::apply(d[idx], a[idx], s[idx],
907  strength, beta, sbeta);
908  }
909  }
910  }
911  } else {
912 
913  // AlphaLeaf has non-active cells.
914 
915  for (ijk[0] = bbox.min().x(); ijk[0] < bbox.max().x() + 1; ++ijk[0]) {
916  for (ijk[1] = bbox.min().y(); ijk[1] < bbox.max().y() + 1; ++ijk[1]) {
917  for (ijk[2] = bbox.min().z(); ijk[2] < bbox.max().z() + 1; ++ijk[2]) {
918 
919  if (alpha.isValueOn(ijk)) {
920  dense.setValue(ijk, CompositeMethod::apply(dense.getValue(ijk),
921  alpha.getValue(ijk), source.getValue(ijk), strength, beta, sbeta));
922  }
923  }
924  }
925  }
926  }
927  }
928 
929  inline static void compositeFromTile(DenseT& dense, openvdb::math::CoordBBox& bbox,
930  const ValueT& sourceValue, const ValueT& alphaValue,
931  const ValueT& beta, const ValueT& strength,
932  bool threaded)
933  {
934  using TileTransformer = UniformTransformer;
935  TileTransformer functor(sourceValue, alphaValue, beta, strength);
936 
937  // Transform the data inside the bbox according to the TileTranformer.
938 
939  transformDense(dense, bbox, functor, threaded);
940  }
941 
942  void denseComposite(bool threaded)
943  {
944  /// Construct a range that corresponds to the
945  /// bounding box of the dense volume
946  const openvdb::math::CoordBBox& bbox = mDense.bbox();
947 
948  Range3d range(bbox.min().x(), bbox.max().x(), LeafT::DIM,
949  bbox.min().y(), bbox.max().y(), LeafT::DIM,
950  bbox.min().z(), bbox.max().z(), LeafT::DIM);
951 
952  // Iterate over the range, compositing into
953  // the dense grid using value accessors for
954  // sparse the grids.
955  if (threaded) {
956  tbb::parallel_for(range, *this);
957  } else {
958  (*this)(range);
959  }
960  }
961 
962  // Composites a dense region using value accessors
963  // into a dense grid
964  void operator()(const Range3d& range) const
965  {
966  // Use value accessors to alpha and source
967 
968  typename tree::ValueAccessor<const TreeT> alphaAccessor(mAlpha);
969  typename tree::ValueAccessor<const TreeT> sourceAccessor(mSource);
970 
971  const ValueT strength = mStrength;
972  const ValueT beta = mBeta;
973  const ValueT sbeta = strength * beta;
974 
975  // Unpack the range3d item.
976  const Index imin = range.pages().begin();
977  const Index imax = range.pages().end();
978 
979  const Index jmin = range.rows().begin();
980  const Index jmax = range.rows().end();
981 
982  const Index kmin = range.cols().begin();
983  const Index kmax = range.cols().end();
984 
985  openvdb::Coord ijk;
986  for (ijk[0] = imin; ijk[0] < imax; ++ijk[0]) {
987  for (ijk[1] = jmin; ijk[1] < jmax; ++ijk[1]) {
988  for (ijk[2] = kmin; ijk[2] < kmax; ++ijk[2]) {
989  const ValueT d_old = mDense.getValue(ijk);
990  const ValueT& alpha = alphaAccessor.getValue(ijk);
991  const ValueT& src = sourceAccessor.getValue(ijk);
992 
993  mDense.setValue(ijk,
994  CompositeMethod::apply(d_old, alpha, src, strength, beta, sbeta));
995  }
996  }
997  }
998  }
999 
1000 private:
1001  // Internal class that wraps the templated composite method
1002  // for use when both alpha and source are uniform over
1003  // a prescribed bbox (e.g. a tile).
1004  class UniformTransformer
1005  {
1006  public:
1007  UniformTransformer(const ValueT& source, const ValueT& alpha, const ValueT& _beta,
1008  const ValueT& _strength) :
1009  mSource(source), mAlpha(alpha), mBeta(_beta),
1010  mStrength(_strength), mSBeta(_strength * _beta)
1011  {}
1012 
1013  ValueT operator()(const ValueT& input) const
1014  {
1015  return CompositeMethod::apply(input, mAlpha, mSource, mStrength, mBeta, mSBeta);
1016  }
1017 
1018  private:
1019  const ValueT mSource;
1020  const ValueT mAlpha;
1021  const ValueT mBeta;
1022  const ValueT mStrength;
1023  const ValueT mSBeta;
1024  };
1025 
1026 
1027  // Simple Class structure that mimics a leaf
1028  // with uniform values. Holds LeafT::DIM copies
1029  // of a value in an array.
1030  struct Line { ValueT mValues[LeafT::DIM]; };
1031  class UniformLeaf : private Line
1032  {
1033  public:
1034  using ValueT = typename LeafT::ValueType;
1035 
1036  using BaseT = Line;
1037  UniformLeaf(const ValueT& value) : BaseT(init(value)) {}
1038 
1039  static const BaseT init(const ValueT& value) {
1040  BaseT tmp;
1041  for (openvdb::Index i = 0; i < LeafT::DIM; ++i) {
1042  tmp.mValues[i] = value;
1043  }
1044  return tmp;
1045  }
1046 
1047  bool isDense() const { return true; }
1048  bool isValueOn(openvdb::math::Coord&) const { return true; }
1049 
1050  const ValueT& getValue(const openvdb::math::Coord&) const { return BaseT::mValues[0]; }
1051  };
1052 
1053 private:
1054  DenseT& mDense;
1055  const TreeT& mSource;
1056  const TreeT& mAlpha;
1057  ValueT mBeta;
1058  ValueT mStrength;
1059 }; // class SparseToDenseCompositor
1060 
1061 
1062 namespace ds
1063 {
1064  //@{
1065  /// @brief Point wise methods used to apply various compositing operations.
1066  template<typename ValueT>
1067  struct OpOver
1068  {
1069  static inline ValueT apply(const ValueT u, const ValueT alpha,
1070  const ValueT v,
1071  const ValueT strength,
1072  const ValueT beta,
1073  const ValueT /*sbeta*/)
1074  { return (u + strength * alpha * (beta * v - u)); }
1075  };
1076 
1077  template<typename ValueT>
1078  struct OpAdd
1079  {
1080  static inline ValueT apply(const ValueT u, const ValueT alpha,
1081  const ValueT v,
1082  const ValueT /*strength*/,
1083  const ValueT /*beta*/,
1084  const ValueT sbeta)
1085  { return (u + sbeta * alpha * v); }
1086  };
1087 
1088  template<typename ValueT>
1089  struct OpSub
1090  {
1091  static inline ValueT apply(const ValueT u, const ValueT alpha,
1092  const ValueT v,
1093  const ValueT /*strength*/,
1094  const ValueT /*beta*/,
1095  const ValueT sbeta)
1096  { return (u - sbeta * alpha * v); }
1097  };
1098 
1099  template<typename ValueT>
1100  struct OpMin
1101  {
1102  static inline ValueT apply(const ValueT u, const ValueT alpha,
1103  const ValueT v,
1104  const ValueT s /*trength*/,
1105  const ValueT beta,
1106  const ValueT /*sbeta*/)
1107  { return ( ( 1 - s * alpha) * u + s * alpha * std::min(u, beta * v) ); }
1108  };
1109 
1110  template<typename ValueT>
1111  struct OpMax
1112  {
1113  static inline ValueT apply(const ValueT u, const ValueT alpha,
1114  const ValueT v,
1115  const ValueT s/*trength*/,
1116  const ValueT beta,
1117  const ValueT /*sbeta*/)
1118  { return ( ( 1 - s * alpha ) * u + s * alpha * std::min(u, beta * v) ); }
1119  };
1120 
1121  template<typename ValueT>
1122  struct OpMult
1123  {
1124  static inline ValueT apply(const ValueT u, const ValueT alpha,
1125  const ValueT v,
1126  const ValueT s/*trength*/,
1127  const ValueT /*beta*/,
1128  const ValueT sbeta)
1129  { return ( ( 1 + alpha * (sbeta * v - s)) * u ); }
1130  };
1131  //@}
1132 
1133  //@{
1134  /// Translator that converts an enum to compositing functor types
1135  template<DSCompositeOp OP, typename ValueT>
1137 
1138  template<typename ValueT>
1140 
1141  template<typename ValueT>
1143 
1144  template<typename ValueT>
1146 
1147  template<typename ValueT>
1149 
1150  template<typename ValueT>
1152 
1153  template<typename ValueT>
1155  //@}
1156 
1157 } // namespace ds
1158 
1159 
1160 template<DSCompositeOp OpT, typename TreeT>
1161 inline void
1164  const TreeT& source, const TreeT& alpha,
1165  const typename TreeT::ValueType beta,
1166  const typename TreeT::ValueType strength,
1167  bool threaded)
1168 {
1169  using ValueT = typename TreeT::ValueType;
1171  using Method = typename Translator::OpT;
1172 
1173  if (openvdb::math::isZero(strength)) return;
1174 
1175  SparseToDenseCompositor<Method, TreeT> tool(dense, source, alpha, beta, strength);
1176 
1177  if (openvdb::math::isZero(alpha.background()) &&
1178  openvdb::math::isZero(source.background()))
1179  {
1180  // Use the sparsity of (alpha U source) as the iteration space.
1181  tool.sparseComposite(threaded);
1182  } else {
1183  // Use the bounding box of dense as the iteration space.
1184  tool.denseComposite(threaded);
1185  }
1186 }
1187 
1188 } // namespace tools
1189 } // namespace OPENVDB_VERSION_NAME
1190 } // namespace openvdb
1191 
1192 #endif //OPENVDB_TOOLS_DENSESPARSETOOLS_HAS_BEEN_INCLUDED
typename ResultTreeType::ValueType ResultValueType
void parallel_for(int64_t start, int64_t end, std::function< void(int64_t index)> &&task, parallel_options opt=parallel_options(0, Split_Y, 1))
Definition: parallel.h:127
typename DSConverter< DenseType, MaskTreeType >::Type _ResultTreeType
void operator()(const tbb::blocked_range< size_t > &range)
GLenum GLint * range
Definition: glcorearb.h:1925
static void compositeFromTile(DenseT &dense, openvdb::math::CoordBBox &bbox, const ValueT &sourceValue, const ValueT &alphaValue, const ValueT &beta, const ValueT &strength, bool threaded)
LeafCIter cbeginLeaf() const
Return an iterator over all leaf nodes in this tree.
Definition: Tree.h:977
DSConverter< DenseType, MaskTreeType >::Type::Ptr extractSparseTreeWithMask(const DenseType &dense, const MaskTreeType &mask, const typename DenseType::ValueType &background, bool threaded=true)
Copy data from the intersection of a sparse tree and a dense input grid. The resulting tree has the s...
OpType::ResultTreeType::Ptr extractSparseTree(const DenseType &dense, const OpType &functor, const typename OpType::ResultValueType &background, bool threaded=true)
Selectively extract and transform data from a dense grid, producing a sparse tree with leaf nodes onl...
const GLdouble * v
Definition: glcorearb.h:837
GLuint start
Definition: glcorearb.h:475
GLsizei const GLfloat * value
Definition: glcorearb.h:824
DenseTransformer(DenseT &dense, const openvdb::math::CoordBBox &bbox, const OpType &functor)
typename ResultTreeType::template ValueConverter< ValueMask >::Type MaskTree
Functor-based class used to extract data from a dense grid, at the index-space intersection with a su...
SparseMaskedExtractor(const SparseMaskedExtractor &other, tbb::split)
GLboolean GLboolean GLboolean GLboolean a
Definition: glcorearb.h:1222
GLdouble s
Definition: glad.h:3009
#define OPENVDB_USE_VERSION_NAMESPACE
Definition: version.h:239
tree::Tree4< ValueMask, 5, 4, 3 >::Type MaskTree
Definition: openvdb.h:58
ImageBuf OIIO_API min(Image_or_Const A, Image_or_Const B, ROI roi={}, int nthreads=0)
void compositeToDense(Dense< typename TreeT::ValueType, LayoutZYX > &dense, const TreeT &source, const TreeT &alpha, const typename TreeT::ValueType beta, const typename TreeT::ValueType strength, bool threaded=true)
Composite data from a sparse tree into a dense array of the same value type.
The Value Accessor Implementation and API methods. The majoirty of the API matches the API of a compa...
Definition: ValueAccessor.h:68
void setValue(size_t offset, const ValueT &value)
Set the value of the voxel at the given array offset.
Definition: Dense.h:260
static ValueT apply(const ValueT u, const ValueT alpha, const ValueT v, const ValueT s, const ValueT beta, const ValueT)
Class that applies a functor to the index space intersection of a prescribed bounding box and the den...
Dense< ValueT, openvdb::tools::LayoutZYX > DenseT
static ValueT apply(const ValueT u, const ValueT alpha, const ValueT v, const ValueT strength, const ValueT beta, const ValueT)
void transformDense(Dense< ValueT, openvdb::tools::LayoutZYX > &dense, const openvdb::CoordBBox &bbox, const OpType &op, bool parallel=true)
GLdouble n
Definition: glcorearb.h:2008
void transform(DenseT &dense, openvdb::math::Coord &ijk, size_t size) const
static ValueT apply(const ValueT u, const ValueT alpha, const ValueT v, const ValueT s, const ValueT beta, const ValueT)
GLintptr offset
Definition: glcorearb.h:665
This file defines a simple dense grid and efficient converters to and from VDB grids.
typename ResultTreeType::LeafNodeType ResultLeafNodeType
Translator that converts an enum to compositing functor types.
std::vector< const typename MaskTree::LeafNodeType * > MaskLeafVec
GLuint GLuint end
Definition: glcorearb.h:475
LeafIteratorBase< const Tree, typename RootNodeType::ChildOnCIter > LeafCIter
Iterator over all leaf nodes in this tree.
Definition: Tree.h:963
Index32 leafCount() const override
Return the number of leaf nodes.
Definition: Tree.h:340
GLsizei GLsizei GLchar * source
Definition: glcorearb.h:803
GLint GLuint mask
Definition: glcorearb.h:124
typename ResultTreeType::LeafNodeType ResultLeafNodeType
Dense is a simple dense grid API used by the CopyToDense and CopyFromDense classes defined below...
Definition: Dense.h:182
void operator()(const MaskLeafT &maskLeaf, size_t) const
tbb::blocked_range2d< IntType, IntType > RangeType
GLfloat GLfloat GLfloat alpha
Definition: glcorearb.h:112
SparseExtractor(SparseExtractor &other, tbb::split)
tbb::blocked_range3d< Index, Index, Index > Range3d
Functor-based class used to extract data that satisfies some criteria defined by the embedded OpType ...
static ValueT apply(const ValueT u, const ValueT alpha, const ValueT v, const ValueT s, const ValueT, const ValueT sbeta)
SparseMaskedExtractor(const DenseType &dense, const ResultValueType &background, const MaskLeafVec &leafVec)
a wrapper struct used to avoid unnecessary computation of memory access from Coord when all offsets a...
typename TreeType::template ValueConverter< ValueType >::Type Type
ResultTreeType::Ptr extract(bool threaded=true)
GLint j
Definition: glad.h:2733
GLsizeiptr size
Definition: glcorearb.h:664
a simple utility class used by extractSparseTreeWithMask
typename RootNodeType::LeafNodeType LeafNodeType
Definition: Tree.h:186
SparseExtractor(const DenseType &dense, const OpType &functor, const ResultValueType background)
static ValueT apply(const ValueT u, const ValueT alpha, const ValueT v, const ValueT, const ValueT, const ValueT sbeta)
tbb::blocked_range3d< Index, Index, Index > Range3d
const ValueT & getValue(size_t offset) const
Return a const reference to the value of the voxel at the given array offset.
Definition: Dense.h:263
typename ResultTreeType::LeafNodeType ResultLeafNodeType
typename TreeT::template ValueConverter< ValueMask >::Type MaskTreeT
void operator()(const DenseValueType &a, const CoordOrIndex &offset, ResultLeafNodeType *leaf) const
static ValueT apply(const ValueT u, const ValueT alpha, const ValueT v, const ValueT, const ValueT, const ValueT sbeta)
Definition: core.h:1131
Tag dispatch class that distinguishes topology copy constructors from deep copy constructors.
Definition: Types.h:683
A LeafManager manages a linear array of pointers to a given tree's leaf nodes, as well as optional au...
void OIIO_UTIL_API split(string_view str, std::vector< string_view > &result, string_view sep=string_view(), int maxsplit=-1)
const CoordBBox & bbox() const
Return the bounding box of the signed index domain of this grid.
Definition: Dense.h:251
LeafNodeT * getLeaf() const
Return the leaf node to which the iterator is pointing.
const ValueType & getValue(const Coord &xyz) const
Return the value of the voxel at the given coordinates.
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition: version.h:119
static void compositeFromLeaf(DenseT &dense, const openvdb::math::CoordBBox &bbox, const LeafT1 &source, const LeafT2 &alpha, const ValueT beta, const ValueT strength)
SparseToDenseCompositor(const SparseToDenseCompositor &other)
typename ResultTreeType::template ValueConverter< ValueMask >::Type MaskTree
bool isZero(const Type &x)
Return true if x is exactly equal to zero.
Definition: Math.h:337
Point wise methods used to apply various compositing operations.
#define OPENVDB_THROW(exception, message)
Definition: Exceptions.h:74
SparseToDenseCompositor(DenseT &dense, const TreeT &source, const TreeT &alpha, const ValueT beta, const ValueT strength)
SparseExtractor(const DenseType &dense, const openvdb::math::CoordBBox &bbox, const OpType &functor, const ResultValueType background)
GLenum src
Definition: glcorearb.h:1793