OpenVDB 13.0.1
Loading...
Searching...
No Matches
LevelSetUtil.h
Go to the documentation of this file.
1// Copyright Contributors to the OpenVDB Project
2// SPDX-License-Identifier: Apache-2.0
3
4/// @file tools/LevelSetUtil.h
5///
6/// @brief Miscellaneous utility methods that operate primarily
7/// or exclusively on level set grids.
8///
9/// @author Mihai Alden
10
11#ifndef OPENVDB_TOOLS_LEVEL_SET_UTIL_HAS_BEEN_INCLUDED
12#define OPENVDB_TOOLS_LEVEL_SET_UTIL_HAS_BEEN_INCLUDED
13
14#include "Count.h" // for minMax
15#include "LevelSetFilter.h" // for LevelSetFilter::normalize
16#include "MeshToVolume.h" // for traceExteriorBoundaries
17#include "Morphology.h" // for dilateActiveValues
18#include "Prune.h" // for pruneInactive
19#include "SignedFloodFill.h" // for signedFloodFillWithValues
20
21#include <openvdb/Types.h>
22#include <openvdb/Grid.h>
23#include <openvdb/openvdb.h>
25#include <tbb/blocked_range.h>
26#include <tbb/parallel_for.h>
27#include <tbb/parallel_reduce.h>
28#include <tbb/parallel_sort.h>
29#include <algorithm>
30#include <cmath>
31#include <cstdlib>
32#include <deque>
33#include <limits>
34#include <memory>
35#include <set>
36#include <vector>
37
38
39namespace openvdb {
41namespace OPENVDB_VERSION_NAME {
42namespace tools {
43
44// MS Visual C++ requires this extra level of indirection in order to compile
45// THIS MUST EXIST IN AN UNNAMED NAMESPACE IN ORDER TO COMPILE ON WINDOWS
46namespace {
47
48template<typename GridType>
49inline typename GridType::ValueType lsutilGridMax()
50{
51 return std::numeric_limits<typename GridType::ValueType>::max();
52}
53
54template<typename GridType>
55inline typename GridType::ValueType lsutilGridZero()
56{
57 return zeroVal<typename GridType::ValueType>();
58}
59
60} // unnamed namespace
61
62
63////////////////////////////////////////
64
65
66/// @brief Threaded method to convert a sparse level set/SDF into a sparse fog volume
67///
68/// @details For a level set, the active and negative-valued interior half of the
69/// narrow band becomes a linear ramp from 0 to 1; the inactive interior becomes
70/// active with a constant value of 1; and the exterior, including the background
71/// and the active exterior half of the narrow band, becomes inactive with a constant
72/// value of 0. The interior, though active, remains sparse.
73/// @details For a generic SDF, a specified cutoff distance determines the width
74/// of the ramp, but otherwise the result is the same as for a level set.
75///
76/// @param grid level set/SDF grid to transform
77/// @param cutoffDistance optional world space cutoff distance for the ramp
78/// (automatically clamped if greater than the interior
79/// narrow band width)
80template<class GridType>
81void
83 GridType& grid,
84 typename GridType::ValueType cutoffDistance = lsutilGridMax<GridType>());
85
86
87/// @brief Threaded method to construct a boolean mask that represents interior regions
88/// in a signed distance field.
89///
90/// @return A shared pointer to either a boolean grid or tree with the same tree
91/// configuration and potentially transform as the input @c volume and whose active
92/// and @c true values correspond to the interior of the input signed distance field.
93///
94/// @param volume Signed distance field / level set volume.
95/// @param isovalue Threshold below which values are considered part of the
96/// interior region.
97template<class GridOrTreeType>
98typename GridOrTreeType::template ValueConverter<bool>::Type::Ptr
100 const GridOrTreeType& volume,
101 typename GridOrTreeType::ValueType isovalue = lsutilGridZero<GridOrTreeType>());
102
103
104/// @brief Extracts the interior regions of a signed distance field and topologically enclosed
105/// (watertight) regions of value greater than the @a isovalue (cavities) that can arise
106/// as the result of CSG union operations between different shapes where at least one of
107/// the shapes has a concavity that is capped.
108///
109/// For example the enclosed region of a capped bottle would include the walls and
110/// the interior cavity.
111///
112/// @return A shared pointer to either a boolean grid or tree with the same tree configuration
113/// and potentially transform as the input @c volume and whose active and @c true values
114/// correspond to the interior and enclosed regions in the input signed distance field.
115///
116/// @param volume Signed distance field / level set volume.
117/// @param isovalue Threshold below which values are considered part of the interior region.
118/// @param fillMask Optional boolean tree, when provided enclosed cavity regions that are not
119/// completely filled by this mask are ignored.
120///
121/// For instance if the fill mask does not completely fill the bottle in the
122/// previous example only the walls and cap are returned and the interior
123/// cavity will be ignored.
124template<typename GridOrTreeType>
125typename GridOrTreeType::template ValueConverter<bool>::Type::Ptr
127 const GridOrTreeType& volume,
128 typename GridOrTreeType::ValueType isovalue = lsutilGridZero<GridOrTreeType>(),
129 const typename TreeAdapter<GridOrTreeType>::TreeType::template ValueConverter<bool>::Type*
130 fillMask = nullptr);
131
132
133/// @brief Return a mask of the voxels that intersect the implicit surface with
134/// the given @a isovalue.
135///
136/// @param volume Signed distance field / level set volume.
137/// @param isovalue The crossing point that is considered the surface.
138template<typename GridOrTreeType>
139typename GridOrTreeType::template ValueConverter<bool>::Type::Ptr
140extractIsosurfaceMask(const GridOrTreeType& volume, typename GridOrTreeType::ValueType isovalue);
141
142
143/// @brief Return a mask for each connected component of the given grid's active voxels.
144///
145/// @param volume Input grid or tree
146/// @param masks Output set of disjoint active topology masks sorted in descending order
147/// based on the active voxel count.
148template<typename GridOrTreeType>
149void
150extractActiveVoxelSegmentMasks(const GridOrTreeType& volume,
151 std::vector<typename GridOrTreeType::template ValueConverter<bool>::Type::Ptr>& masks);
152
153
154/// @brief Separates disjoint active topology components into distinct grids or trees.
155///
156/// @details Supports volumes with active tiles.
157///
158/// @param volume Input grid or tree
159/// @param segments Output set of disjoint active topology components sorted in
160/// descending order based on the active voxel count.
161template<typename GridOrTreeType>
162void
163segmentActiveVoxels(const GridOrTreeType& volume,
164 std::vector<typename GridOrTreeType::Ptr>& segments);
165
166
167/// @brief Separates disjoint SDF surfaces into distinct grids or trees.
168///
169/// @details Supports asymmetric interior / exterior narrowband widths and
170/// SDF volumes with dense interior regions.
171///
172/// @param volume Input signed distance field / level set volume
173/// @param segments Output set of disjoint SDF surfaces found in @a volume sorted in
174/// descending order based on the surface intersecting voxel count.
175template<typename GridOrTreeType>
176void
177segmentSDF(const GridOrTreeType& volume, std::vector<typename GridOrTreeType::Ptr>& segments);
178
179
180/// @brief Convert a distance field (unsigned or signed) into a proper signed
181/// distance field / level set.
182///
183/// @details Uses the same internal pipeline as meshToVolume to determine the
184/// sign of the distance field from the exterior boundary. The input
185/// values are first normalized into the squared index-space representation
186/// that the pipeline expects (boundary threshold = 0.75, i.e. (sqrt(3)/2)^2),
187/// then converted back to world-space signed distances.
188///
189/// @param grid Distance field grid to convert in place. The grid's
190/// transform is used to determine the voxel size.
191/// @param removeDisconnectedInterior When enabled, deactivate interior
192/// narrow-band voxels that are not topologically connected
193/// to the exterior boundary. This removes interior surfaces
194/// caused by overlapping or self-intersecting geometry.
195/// @param rebuildNarrowBand When enabled,rebuild a symmetric narrow band from
196/// the zero crossing via PDE renormalization.
197/// @param halfWidth half the width of the narrow band, in voxel units (default: 3).
198template<class GridType>
199void
201 GridType& grid,
202 bool removeDisconnectedInterior = false,
203 bool rebuildNarrowBand = true,
204 float halfWidth = 3.0f);
205
206
207////////////////////////////////////////////////////////////////////////////////
208////////////////////////////////////////////////////////////////////////////////
209
210// Internal utility objects and implementation details
211
212/// @cond OPENVDB_DOCS_INTERNAL
213
214namespace level_set_util_internal {
215
216
217template<typename LeafNodeType>
218struct MaskInteriorVoxels {
219
220 using ValueType = typename LeafNodeType::ValueType;
221 using BoolLeafNodeType = tree::LeafNode<bool, LeafNodeType::LOG2DIM>;
222
223 MaskInteriorVoxels(
224 ValueType isovalue, const LeafNodeType ** nodes, BoolLeafNodeType ** maskNodes)
225 : mNodes(nodes), mMaskNodes(maskNodes), mIsovalue(isovalue)
226 {
227 }
228
229 void operator()(const tbb::blocked_range<size_t>& range) const {
230
231 BoolLeafNodeType * maskNodePt = nullptr;
232
233 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
234
235 mMaskNodes[n] = nullptr;
236 const LeafNodeType& node = *mNodes[n];
237
238 if (!maskNodePt) {
239 maskNodePt = new BoolLeafNodeType(node.origin(), false);
240 } else {
241 maskNodePt->setOrigin(node.origin());
242 }
243
244 const ValueType* values = &node.getValue(0);
245 for (Index i = 0; i < LeafNodeType::SIZE; ++i) {
246 if (values[i] < mIsovalue) maskNodePt->setValueOn(i, true);
247 }
248
249 if (maskNodePt->onVoxelCount() > 0) {
250 mMaskNodes[n] = maskNodePt;
251 maskNodePt = nullptr;
252 }
253 }
254
255 delete maskNodePt;
256 }
257
258 LeafNodeType const * const * const mNodes;
259 BoolLeafNodeType ** const mMaskNodes;
260 ValueType const mIsovalue;
261}; // MaskInteriorVoxels
262
263
264template<typename TreeType, typename InternalNodeType>
265struct MaskInteriorTiles {
266
267 using ValueType = typename TreeType::ValueType;
268
269 MaskInteriorTiles(ValueType isovalue, const TreeType& tree, InternalNodeType ** maskNodes)
270 : mTree(&tree), mMaskNodes(maskNodes), mIsovalue(isovalue) { }
271
272 void operator()(const tbb::blocked_range<size_t>& range) const {
273 tree::ValueAccessor<const TreeType> acc(*mTree);
274 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
275 typename InternalNodeType::ValueAllIter it = mMaskNodes[n]->beginValueAll();
276 for (; it; ++it) {
277 if (acc.getValue(it.getCoord()) < mIsovalue) {
278 it.setValue(true);
279 it.setValueOn(true);
280 }
281 }
282 }
283 }
284
285 TreeType const * const mTree;
286 InternalNodeType ** const mMaskNodes;
287 ValueType const mIsovalue;
288}; // MaskInteriorTiles
289
290
291template<typename TreeType>
292struct PopulateTree {
293
294 using ValueType = typename TreeType::ValueType;
295 using LeafNodeType = typename TreeType::LeafNodeType;
296
297 PopulateTree(TreeType& tree, LeafNodeType** leafnodes,
298 const size_t * nodexIndexMap, ValueType background)
299 : mNewTree(background)
300 , mTreePt(&tree)
301 , mNodes(leafnodes)
302 , mNodeIndexMap(nodexIndexMap)
303 {
304 }
305
306 PopulateTree(PopulateTree& rhs, tbb::split)
307 : mNewTree(rhs.mNewTree.background())
308 , mTreePt(&mNewTree)
309 , mNodes(rhs.mNodes)
310 , mNodeIndexMap(rhs.mNodeIndexMap)
311 {
312 }
313
314 void operator()(const tbb::blocked_range<size_t>& range) {
315
316 tree::ValueAccessor<TreeType> acc(*mTreePt);
317
318 if (mNodeIndexMap) {
319 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
320 for (size_t i = mNodeIndexMap[n], I = mNodeIndexMap[n + 1]; i < I; ++i) {
321 if (mNodes[i] != nullptr) acc.addLeaf(mNodes[i]);
322 }
323 }
324 } else {
325 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
326 acc.addLeaf(mNodes[n]);
327 }
328 }
329 }
330
331 void join(PopulateTree& rhs) { mTreePt->merge(*rhs.mTreePt); }
332
333private:
334 TreeType mNewTree;
335 TreeType * const mTreePt;
336 LeafNodeType ** const mNodes;
337 size_t const * const mNodeIndexMap;
338}; // PopulateTree
339
340
341/// @brief Negative active values are set @c 0, everything else is set to @c 1.
342template<typename LeafNodeType>
343struct LabelBoundaryVoxels {
344
345 using ValueType = typename LeafNodeType::ValueType;
346 using CharLeafNodeType = tree::LeafNode<char, LeafNodeType::LOG2DIM>;
347
348 LabelBoundaryVoxels(
349 ValueType isovalue, const LeafNodeType ** nodes, CharLeafNodeType ** maskNodes)
350 : mNodes(nodes), mMaskNodes(maskNodes), mIsovalue(isovalue)
351 {
352 }
353
354 void operator()(const tbb::blocked_range<size_t>& range) const {
355
356 CharLeafNodeType * maskNodePt = nullptr;
357
358 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
359
360 mMaskNodes[n] = nullptr;
361 const LeafNodeType& node = *mNodes[n];
362
363 if (!maskNodePt) {
364 maskNodePt = new CharLeafNodeType(node.origin(), 1);
365 } else {
366 maskNodePt->setOrigin(node.origin());
367 }
368
369 typename LeafNodeType::ValueOnCIter it;
370 for (it = node.cbeginValueOn(); it; ++it) {
371 maskNodePt->setValueOn(it.pos(), ((*it - mIsovalue) < 0.0) ? 0 : 1);
372 }
373
374 if (maskNodePt->onVoxelCount() > 0) {
375 mMaskNodes[n] = maskNodePt;
376 maskNodePt = nullptr;
377 }
378 }
379
380 delete maskNodePt;
381 }
382
383 LeafNodeType const * const * const mNodes;
384 CharLeafNodeType ** const mMaskNodes;
385 ValueType const mIsovalue;
386}; // LabelBoundaryVoxels
387
388
389template<typename LeafNodeType>
390struct FlipRegionSign {
391 using ValueType = typename LeafNodeType::ValueType;
392
393 FlipRegionSign(LeafNodeType ** nodes) : mNodes(nodes) { }
394
395 void operator()(const tbb::blocked_range<size_t>& range) const {
396 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
397 ValueType* values = const_cast<ValueType*>(&mNodes[n]->getValue(0));
398 for (Index i = 0; i < LeafNodeType::SIZE; ++i) {
399 values[i] = values[i] < 0 ? 1 : -1;
400 }
401 }
402 }
403
404 LeafNodeType ** const mNodes;
405}; // FlipRegionSign
406
407
408template<typename LeafNodeType>
409struct FindMinVoxelValue {
410
411 using ValueType = typename LeafNodeType::ValueType;
412
413 FindMinVoxelValue(LeafNodeType const * const * const leafnodes)
414 : minValue(std::numeric_limits<ValueType>::max())
415 , mNodes(leafnodes)
416 {
417 }
418
419 FindMinVoxelValue(FindMinVoxelValue& rhs, tbb::split)
420 : minValue(std::numeric_limits<ValueType>::max())
421 , mNodes(rhs.mNodes)
422 {
423 }
424
425 void operator()(const tbb::blocked_range<size_t>& range) {
426 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
427 const ValueType* data = mNodes[n]->buffer().data();
428 for (Index i = 0; i < LeafNodeType::SIZE; ++i) {
429 minValue = std::min(minValue, data[i]);
430 }
431 }
432 }
433
434 void join(FindMinVoxelValue& rhs) { minValue = std::min(minValue, rhs.minValue); }
435
436 ValueType minValue;
437
438 LeafNodeType const * const * const mNodes;
439}; // FindMinVoxelValue
440
441
442template<typename InternalNodeType>
443struct FindMinTileValue {
444
445 using ValueType = typename InternalNodeType::ValueType;
446
447 FindMinTileValue(InternalNodeType const * const * const nodes)
448 : minValue(std::numeric_limits<ValueType>::max())
449 , mNodes(nodes)
450 {
451 }
452
453 FindMinTileValue(FindMinTileValue& rhs, tbb::split)
454 : minValue(std::numeric_limits<ValueType>::max())
455 , mNodes(rhs.mNodes)
456 {
457 }
458
459 void operator()(const tbb::blocked_range<size_t>& range) {
460 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
461 typename InternalNodeType::ValueAllCIter it = mNodes[n]->beginValueAll();
462 for (; it; ++it) {
463 minValue = std::min(minValue, *it);
464 }
465 }
466 }
467
468 void join(FindMinTileValue& rhs) { minValue = std::min(minValue, rhs.minValue); }
469
470 ValueType minValue;
471
472 InternalNodeType const * const * const mNodes;
473}; // FindMinTileValue
474
475
476template<typename LeafNodeType>
477struct SDFVoxelsToFogVolume {
478
479 using ValueType = typename LeafNodeType::ValueType;
480
481 SDFVoxelsToFogVolume(LeafNodeType ** nodes, ValueType cutoffDistance)
482 : mNodes(nodes), mWeight(ValueType(1.0) / cutoffDistance)
483 {
484 }
485
486 void operator()(const tbb::blocked_range<size_t>& range) const {
487
488 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
489
490 LeafNodeType& node = *mNodes[n];
491 node.setValuesOff();
492
493 ValueType* values = node.buffer().data();
494 for (Index i = 0; i < LeafNodeType::SIZE; ++i) {
495 values[i] = values[i] > ValueType(0.0) ? ValueType(0.0) : values[i] * mWeight;
496 if (values[i] > ValueType(0.0)) node.setValueOn(i);
497 }
498
499 if (node.onVoxelCount() == 0) {
500 delete mNodes[n];
501 mNodes[n] = nullptr;
502 }
503 }
504 }
505
506 LeafNodeType ** const mNodes;
507 ValueType const mWeight;
508}; // SDFVoxelsToFogVolume
509
510
511template<typename TreeType, typename InternalNodeType>
512struct SDFTilesToFogVolume {
513
514 SDFTilesToFogVolume(const TreeType& tree, InternalNodeType ** nodes)
515 : mTree(&tree), mNodes(nodes) { }
516
517 void operator()(const tbb::blocked_range<size_t>& range) const {
518
519 using ValueType = typename TreeType::ValueType;
520 tree::ValueAccessor<const TreeType> acc(*mTree);
521
522 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
523 typename InternalNodeType::ValueAllIter it = mNodes[n]->beginValueAll();
524 for (; it; ++it) {
525 if (acc.getValue(it.getCoord()) < ValueType(0.0)) {
526 it.setValue(ValueType(1.0));
527 it.setValueOn(true);
528 }
529 }
530 }
531 }
532
533 TreeType const * const mTree;
534 InternalNodeType ** const mNodes;
535}; // SDFTilesToFogVolume
536
537
538template<typename TreeType>
539struct FillMaskBoundary {
540
541 using ValueType = typename TreeType::ValueType;
542 using LeafNodeType = typename TreeType::LeafNodeType;
543 using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
544 using BoolLeafNodeType = typename BoolTreeType::LeafNodeType;
545
546 FillMaskBoundary(const TreeType& tree, ValueType isovalue, const BoolTreeType& fillMask,
547 const BoolLeafNodeType ** fillNodes, BoolLeafNodeType ** newNodes)
548 : mTree(&tree)
549 , mFillMask(&fillMask)
550 , mFillNodes(fillNodes)
551 , mNewNodes(newNodes)
552 , mIsovalue(isovalue)
553 {
554 }
555
556 void operator()(const tbb::blocked_range<size_t>& range) const {
557
558 tree::ValueAccessor<const BoolTreeType> maskAcc(*mFillMask);
559 tree::ValueAccessor<const TreeType> distAcc(*mTree);
560
561 std::unique_ptr<char[]> valueMask(new char[BoolLeafNodeType::SIZE]);
562
563 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
564
565 mNewNodes[n] = nullptr;
566 const BoolLeafNodeType& node = *mFillNodes[n];
567 const Coord& origin = node.origin();
568
569 const bool denseNode = node.isDense();
570
571 // possible early out if the fill mask is dense
572 if (denseNode) {
573
574 int denseNeighbors = 0;
575
576 const BoolLeafNodeType* neighborNode =
577 maskAcc.probeConstLeaf(origin.offsetBy(-1, 0, 0));
578 if (neighborNode && neighborNode->isDense()) ++denseNeighbors;
579
580 neighborNode = maskAcc.probeConstLeaf(origin.offsetBy(BoolLeafNodeType::DIM, 0, 0));
581 if (neighborNode && neighborNode->isDense()) ++denseNeighbors;
582
583 neighborNode = maskAcc.probeConstLeaf(origin.offsetBy(0, -1, 0));
584 if (neighborNode && neighborNode->isDense()) ++denseNeighbors;
585
586 neighborNode = maskAcc.probeConstLeaf(origin.offsetBy(0, BoolLeafNodeType::DIM, 0));
587 if (neighborNode && neighborNode->isDense()) ++denseNeighbors;
588
589 neighborNode = maskAcc.probeConstLeaf(origin.offsetBy(0, 0, -1));
590 if (neighborNode && neighborNode->isDense()) ++denseNeighbors;
591
592 neighborNode = maskAcc.probeConstLeaf(origin.offsetBy(0, 0, BoolLeafNodeType::DIM));
593 if (neighborNode && neighborNode->isDense()) ++denseNeighbors;
594
595 if (denseNeighbors == 6) continue;
596 }
597
598 // rest value mask
599 memset(valueMask.get(), 0, sizeof(char) * BoolLeafNodeType::SIZE);
600
601 const typename TreeType::LeafNodeType* distNode = distAcc.probeConstLeaf(origin);
602
603 // check internal voxel neighbors
604
605 bool earlyTermination = false;
606
607 if (!denseNode) {
608 if (distNode) {
609 evalInternalNeighborsP(valueMask.get(), node, *distNode);
610 evalInternalNeighborsN(valueMask.get(), node, *distNode);
611 } else if (distAcc.getValue(origin) > mIsovalue) {
612 earlyTermination = evalInternalNeighborsP(valueMask.get(), node);
613 if (!earlyTermination) {
614 earlyTermination = evalInternalNeighborsN(valueMask.get(), node);
615 }
616 }
617 }
618
619 // check external voxel neighbors
620
621 if (!earlyTermination) {
622 evalExternalNeighborsX<true>(valueMask.get(), node, maskAcc, distAcc);
623 evalExternalNeighborsX<false>(valueMask.get(), node, maskAcc, distAcc);
624 evalExternalNeighborsY<true>(valueMask.get(), node, maskAcc, distAcc);
625 evalExternalNeighborsY<false>(valueMask.get(), node, maskAcc, distAcc);
626 evalExternalNeighborsZ<true>(valueMask.get(), node, maskAcc, distAcc);
627 evalExternalNeighborsZ<false>(valueMask.get(), node, maskAcc, distAcc);
628 }
629
630 // Export marked boundary voxels.
631
632 int numBoundaryValues = 0;
633 for (Index i = 0, I = BoolLeafNodeType::SIZE; i < I; ++i) {
634 numBoundaryValues += valueMask[i] == 1;
635 }
636
637 if (numBoundaryValues > 0) {
638 mNewNodes[n] = new BoolLeafNodeType(origin, false);
639 for (Index i = 0, I = BoolLeafNodeType::SIZE; i < I; ++i) {
640 if (valueMask[i] == 1) mNewNodes[n]->setValueOn(i);
641 }
642 }
643 }
644 }
645
646private:
647 // Check internal voxel neighbors in positive {x, y, z} directions.
648 void evalInternalNeighborsP(char* valueMask, const BoolLeafNodeType& node,
649 const LeafNodeType& distNode) const
650 {
651 for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) {
652 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
653 for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) {
654 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
655 for (Index z = 0; z < BoolLeafNodeType::DIM - 1; ++z) {
656 const Index pos = yPos + z;
657
658 if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue;
659
660 if (!node.isValueOn(pos + 1) && distNode.getValue(pos + 1) > mIsovalue) {
661 valueMask[pos] = 1;
662 }
663 }
664 }
665 }
666
667 for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) {
668 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
669 for (Index y = 0; y < BoolLeafNodeType::DIM - 1; ++y) {
670 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
671 for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) {
672 const Index pos = yPos + z;
673
674 if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue;
675
676 if (!node.isValueOn(pos + BoolLeafNodeType::DIM) &&
677 distNode.getValue(pos + BoolLeafNodeType::DIM) > mIsovalue) {
678 valueMask[pos] = 1;
679 }
680 }
681 }
682 }
683
684 for (Index x = 0; x < BoolLeafNodeType::DIM - 1; ++x) {
685 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
686 for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) {
687 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
688 for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) {
689 const Index pos = yPos + z;
690
691 if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue;
692
693 if (!node.isValueOn(pos + BoolLeafNodeType::DIM * BoolLeafNodeType::DIM) &&
694 (distNode.getValue(pos + BoolLeafNodeType::DIM * BoolLeafNodeType::DIM)
695 > mIsovalue))
696 {
697 valueMask[pos] = 1;
698 }
699 }
700 }
701 }
702 }
703
704 bool evalInternalNeighborsP(char* valueMask, const BoolLeafNodeType& node) const {
705
706 for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) {
707 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
708 for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) {
709 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
710 for (Index z = 0; z < BoolLeafNodeType::DIM - 1; ++z) {
711 const Index pos = yPos + z;
712
713 if (node.isValueOn(pos) && !node.isValueOn(pos + 1)) {
714 valueMask[pos] = 1;
715 return true;
716 }
717 }
718 }
719 }
720
721 for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) {
722 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
723 for (Index y = 0; y < BoolLeafNodeType::DIM - 1; ++y) {
724 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
725 for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) {
726 const Index pos = yPos + z;
727
728 if (node.isValueOn(pos) && !node.isValueOn(pos + BoolLeafNodeType::DIM)) {
729 valueMask[pos] = 1;
730 return true;
731 }
732 }
733 }
734 }
735
736 for (Index x = 0; x < BoolLeafNodeType::DIM - 1; ++x) {
737 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
738 for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) {
739 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
740 for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) {
741 const Index pos = yPos + z;
742
743 if (node.isValueOn(pos) &&
744 !node.isValueOn(pos + BoolLeafNodeType::DIM * BoolLeafNodeType::DIM)) {
745 valueMask[pos] = 1;
746 return true;
747 }
748 }
749 }
750 }
751
752 return false;
753 }
754
755 // Check internal voxel neighbors in negative {x, y, z} directions.
756
757 void evalInternalNeighborsN(char* valueMask, const BoolLeafNodeType& node,
758 const LeafNodeType& distNode) const
759 {
760 for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) {
761 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
762 for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) {
763 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
764 for (Index z = 1; z < BoolLeafNodeType::DIM; ++z) {
765 const Index pos = yPos + z;
766
767 if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue;
768
769 if (!node.isValueOn(pos - 1) && distNode.getValue(pos - 1) > mIsovalue) {
770 valueMask[pos] = 1;
771 }
772 }
773 }
774 }
775
776 for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) {
777 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
778 for (Index y = 1; y < BoolLeafNodeType::DIM; ++y) {
779 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
780 for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) {
781 const Index pos = yPos + z;
782
783 if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue;
784
785 if (!node.isValueOn(pos - BoolLeafNodeType::DIM) &&
786 distNode.getValue(pos - BoolLeafNodeType::DIM) > mIsovalue) {
787 valueMask[pos] = 1;
788 }
789 }
790 }
791 }
792
793 for (Index x = 1; x < BoolLeafNodeType::DIM; ++x) {
794 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
795 for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) {
796 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
797 for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) {
798 const Index pos = yPos + z;
799
800 if (valueMask[pos] != 0 || !node.isValueOn(pos)) continue;
801
802 if (!node.isValueOn(pos - BoolLeafNodeType::DIM * BoolLeafNodeType::DIM) &&
803 (distNode.getValue(pos - BoolLeafNodeType::DIM * BoolLeafNodeType::DIM)
804 > mIsovalue))
805 {
806 valueMask[pos] = 1;
807 }
808 }
809 }
810 }
811 }
812
813
814 bool evalInternalNeighborsN(char* valueMask, const BoolLeafNodeType& node) const {
815
816 for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) {
817 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
818 for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) {
819 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
820 for (Index z = 1; z < BoolLeafNodeType::DIM; ++z) {
821 const Index pos = yPos + z;
822
823 if (node.isValueOn(pos) && !node.isValueOn(pos - 1)) {
824 valueMask[pos] = 1;
825 return true;
826 }
827 }
828 }
829 }
830
831 for (Index x = 0; x < BoolLeafNodeType::DIM; ++x) {
832 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
833 for (Index y = 1; y < BoolLeafNodeType::DIM; ++y) {
834 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
835 for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) {
836 const Index pos = yPos + z;
837
838 if (node.isValueOn(pos) && !node.isValueOn(pos - BoolLeafNodeType::DIM)) {
839 valueMask[pos] = 1;
840 return true;
841 }
842 }
843 }
844 }
845
846 for (Index x = 1; x < BoolLeafNodeType::DIM; ++x) {
847 const Index xPos = x << (2 * BoolLeafNodeType::LOG2DIM);
848 for (Index y = 0; y < BoolLeafNodeType::DIM; ++y) {
849 const Index yPos = xPos + (y << BoolLeafNodeType::LOG2DIM);
850 for (Index z = 0; z < BoolLeafNodeType::DIM; ++z) {
851 const Index pos = yPos + z;
852
853 if (node.isValueOn(pos) &&
854 !node.isValueOn(pos - BoolLeafNodeType::DIM * BoolLeafNodeType::DIM)) {
855 valueMask[pos] = 1;
856 return true;
857 }
858 }
859 }
860 }
861
862 return false;
863 }
864
865
866 // Check external voxel neighbors
867
868 // If UpWind is true check the X+ oriented node face, else the X- oriented face.
869 template<bool UpWind>
870 void evalExternalNeighborsX(char* valueMask, const BoolLeafNodeType& node,
871 const tree::ValueAccessor<const BoolTreeType>& maskAcc,
872 const tree::ValueAccessor<const TreeType>& distAcc) const {
873
874 const Coord& origin = node.origin();
875 Coord ijk(0, 0, 0), nijk;
876 int step = -1;
877
878 if (UpWind) {
879 step = 1;
880 ijk[0] = int(BoolLeafNodeType::DIM) - 1;
881 }
882
883 const Index xPos = ijk[0] << (2 * int(BoolLeafNodeType::LOG2DIM));
884
885 for (ijk[1] = 0; ijk[1] < int(BoolLeafNodeType::DIM); ++ijk[1]) {
886 const Index yPos = xPos + (ijk[1] << int(BoolLeafNodeType::LOG2DIM));
887
888 for (ijk[2] = 0; ijk[2] < int(BoolLeafNodeType::DIM); ++ijk[2]) {
889 const Index pos = yPos + ijk[2];
890
891 if (valueMask[pos] == 0 && node.isValueOn(pos)) {
892
893 nijk = origin + ijk.offsetBy(step, 0, 0);
894
895 if (!maskAcc.isValueOn(nijk) && distAcc.getValue(nijk) > mIsovalue) {
896 valueMask[pos] = 1;
897 }
898 }
899 }
900 }
901 }
902
903 // If UpWind is true check the Y+ oriented node face, else the Y- oriented face.
904 template<bool UpWind>
905 void evalExternalNeighborsY(char* valueMask, const BoolLeafNodeType& node,
906 const tree::ValueAccessor<const BoolTreeType>& maskAcc,
907 const tree::ValueAccessor<const TreeType>& distAcc) const {
908
909 const Coord& origin = node.origin();
910 Coord ijk(0, 0, 0), nijk;
911 int step = -1;
912
913 if (UpWind) {
914 step = 1;
915 ijk[1] = int(BoolLeafNodeType::DIM) - 1;
916 }
917
918 const Index yPos = ijk[1] << int(BoolLeafNodeType::LOG2DIM);
919
920 for (ijk[0] = 0; ijk[0] < int(BoolLeafNodeType::DIM); ++ijk[0]) {
921 const Index xPos = yPos + (ijk[0] << (2 * int(BoolLeafNodeType::LOG2DIM)));
922
923 for (ijk[2] = 0; ijk[2] < int(BoolLeafNodeType::DIM); ++ijk[2]) {
924 const Index pos = xPos + ijk[2];
925
926 if (valueMask[pos] == 0 && node.isValueOn(pos)) {
927
928 nijk = origin + ijk.offsetBy(0, step, 0);
929 if (!maskAcc.isValueOn(nijk) && distAcc.getValue(nijk) > mIsovalue) {
930 valueMask[pos] = 1;
931 }
932 }
933 }
934 }
935 }
936
937 // If UpWind is true check the Z+ oriented node face, else the Z- oriented face.
938 template<bool UpWind>
939 void evalExternalNeighborsZ(char* valueMask, const BoolLeafNodeType& node,
940 const tree::ValueAccessor<const BoolTreeType>& maskAcc,
941 const tree::ValueAccessor<const TreeType>& distAcc) const {
942
943 const Coord& origin = node.origin();
944 Coord ijk(0, 0, 0), nijk;
945 int step = -1;
946
947 if (UpWind) {
948 step = 1;
949 ijk[2] = int(BoolLeafNodeType::DIM) - 1;
950 }
951
952 for (ijk[0] = 0; ijk[0] < int(BoolLeafNodeType::DIM); ++ijk[0]) {
953 const Index xPos = ijk[0] << (2 * int(BoolLeafNodeType::LOG2DIM));
954
955 for (ijk[1] = 0; ijk[1] < int(BoolLeafNodeType::DIM); ++ijk[1]) {
956 const Index pos = ijk[2] + xPos + (ijk[1] << int(BoolLeafNodeType::LOG2DIM));
957
958 if (valueMask[pos] == 0 && node.isValueOn(pos)) {
959
960 nijk = origin + ijk.offsetBy(0, 0, step);
961 if (!maskAcc.isValueOn(nijk) && distAcc.getValue(nijk) > mIsovalue) {
962 valueMask[pos] = 1;
963 }
964 }
965 }
966 }
967 }
968
969 //////////
970
971 TreeType const * const mTree;
972 BoolTreeType const * const mFillMask;
973 BoolLeafNodeType const * const * const mFillNodes;
974 BoolLeafNodeType ** const mNewNodes;
975 ValueType const mIsovalue;
976}; // FillMaskBoundary
977
978
979/// @brief Constructs a memory light char tree that represents the exterior region with @c +1
980/// and the interior regions with @c -1.
981template <class TreeType>
982typename TreeType::template ValueConverter<char>::Type::Ptr
983computeEnclosedRegionMask(const TreeType& tree, typename TreeType::ValueType isovalue,
984 const typename TreeType::template ValueConverter<bool>::Type* fillMask)
985{
986 using LeafNodeType = typename TreeType::LeafNodeType;
987 using RootNodeType = typename TreeType::RootNodeType;
988 using NodeChainType = typename RootNodeType::NodeChainType;
989 using InternalNodeType = typename NodeChainType::template Get<1>;
990
991 using CharTreeType = typename TreeType::template ValueConverter<char>::Type;
992 using CharLeafNodeType = typename CharTreeType::LeafNodeType;
993
994 using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
995 using BoolLeafNodeType = typename BoolTreeType::LeafNodeType;
996
997 const TreeType* treePt = &tree;
998
999 size_t numLeafNodes = 0, numInternalNodes = 0;
1000
1001 std::vector<const LeafNodeType*> nodes;
1002 std::vector<size_t> leafnodeCount;
1003
1004 {
1005 // compute the prefix sum of the leafnode count in each internal node.
1006 std::vector<const InternalNodeType*> internalNodes;
1007 treePt->getNodes(internalNodes);
1008
1009 numInternalNodes = internalNodes.size();
1010
1011 leafnodeCount.push_back(0);
1012 for (size_t n = 0; n < numInternalNodes; ++n) {
1013 leafnodeCount.push_back(leafnodeCount.back() + internalNodes[n]->leafCount());
1014 }
1015
1016 numLeafNodes = leafnodeCount.back();
1017
1018 // extract all leafnodes
1019 nodes.reserve(numLeafNodes);
1020
1021 for (size_t n = 0; n < numInternalNodes; ++n) {
1022 internalNodes[n]->getNodes(nodes);
1023 }
1024 }
1025
1026 // create mask leafnodes
1027 std::unique_ptr<CharLeafNodeType*[]> maskNodes(new CharLeafNodeType*[numLeafNodes]);
1028
1029 tbb::parallel_for(tbb::blocked_range<size_t>(0, numLeafNodes),
1030 LabelBoundaryVoxels<LeafNodeType>(isovalue, nodes.data(), maskNodes.get()));
1031
1032 // create mask grid
1033 typename CharTreeType::Ptr maskTree(new CharTreeType(1));
1034
1035 PopulateTree<CharTreeType> populate(*maskTree, maskNodes.get(), leafnodeCount.data(), 1);
1036 tbb::parallel_reduce(tbb::blocked_range<size_t>(0, numInternalNodes), populate);
1037
1038 // optionally evaluate the fill mask
1039
1040 std::vector<CharLeafNodeType*> extraMaskNodes;
1041
1042 if (fillMask) {
1043
1044 std::vector<const BoolLeafNodeType*> fillMaskNodes;
1045 fillMask->getNodes(fillMaskNodes);
1046
1047 std::unique_ptr<BoolLeafNodeType*[]> boundaryMaskNodes(
1048 new BoolLeafNodeType*[fillMaskNodes.size()]);
1049
1050 tbb::parallel_for(tbb::blocked_range<size_t>(0, fillMaskNodes.size()),
1051 FillMaskBoundary<TreeType>(tree, isovalue, *fillMask, fillMaskNodes.data(),
1052 boundaryMaskNodes.get()));
1053
1054 tree::ValueAccessor<CharTreeType> maskAcc(*maskTree);
1055
1056 for (size_t n = 0, N = fillMaskNodes.size(); n < N; ++n) {
1057
1058 if (boundaryMaskNodes[n] == nullptr) continue;
1059
1060 const BoolLeafNodeType& boundaryNode = *boundaryMaskNodes[n];
1061 const Coord& origin = boundaryNode.origin();
1062
1063 CharLeafNodeType* maskNodePt = maskAcc.probeLeaf(origin);
1064
1065 if (!maskNodePt) {
1066 maskNodePt = maskAcc.touchLeaf(origin);
1067 extraMaskNodes.push_back(maskNodePt);
1068 }
1069
1070 char* data = maskNodePt->buffer().data();
1071
1072 typename BoolLeafNodeType::ValueOnCIter it = boundaryNode.cbeginValueOn();
1073 for (; it; ++it) {
1074 if (data[it.pos()] != 0) data[it.pos()] = -1;
1075 }
1076
1077 delete boundaryMaskNodes[n];
1078 }
1079 }
1080
1081 // eliminate enclosed regions
1082 tools::traceExteriorBoundaries(*maskTree);
1083
1084 // flip voxel sign to negative inside and positive outside.
1085 tbb::parallel_for(tbb::blocked_range<size_t>(0, numLeafNodes),
1086 FlipRegionSign<CharLeafNodeType>(maskNodes.get()));
1087
1088 if (!extraMaskNodes.empty()) {
1089 tbb::parallel_for(tbb::blocked_range<size_t>(0, extraMaskNodes.size()),
1090 FlipRegionSign<CharLeafNodeType>(extraMaskNodes.data()));
1091 }
1092
1093 // propagate sign information into tile region
1094 tools::signedFloodFill(*maskTree);
1095
1096 return maskTree;
1097} // computeEnclosedRegionMask()
1098
1099
1100template <class TreeType>
1101typename TreeType::template ValueConverter<bool>::Type::Ptr
1102computeInteriorMask(const TreeType& tree, typename TreeType::ValueType iso)
1103{
1104 using ValueType = typename TreeType::ValueType;
1105 using LeafNodeType = typename TreeType::LeafNodeType;
1106 using RootNodeType = typename TreeType::RootNodeType;
1107 using NodeChainType = typename RootNodeType::NodeChainType;
1108 using InternalNodeType = typename NodeChainType::template Get<1>;
1109
1110 using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
1111 using BoolLeafNodeType = typename BoolTreeType::LeafNodeType;
1112 using BoolRootNodeType = typename BoolTreeType::RootNodeType;
1113 using BoolNodeChainType = typename BoolRootNodeType::NodeChainType;
1114 using BoolInternalNodeType = typename BoolNodeChainType::template Get<1>;
1115
1116 /////
1117
1118 // Clamp the isovalue to the level set's background value minus epsilon.
1119 // (In a valid narrow-band level set, all voxels, including background voxels,
1120 // have values less than or equal to the background value, so an isovalue
1121 // greater than or equal to the background value would produce a mask with
1122 // effectively infinite extent.)
1123 iso = std::min(iso,
1124 static_cast<ValueType>(tree.background() - math::Tolerance<ValueType>::value()));
1125
1126 size_t numLeafNodes = 0, numInternalNodes = 0;
1127
1128 std::vector<const LeafNodeType*> nodes;
1129 std::vector<size_t> leafnodeCount;
1130
1131 {
1132 // compute the prefix sum of the leafnode count in each internal node.
1133 std::vector<const InternalNodeType*> internalNodes;
1134 tree.getNodes(internalNodes);
1135
1136 numInternalNodes = internalNodes.size();
1137
1138 leafnodeCount.push_back(0);
1139 for (size_t n = 0; n < numInternalNodes; ++n) {
1140 leafnodeCount.push_back(leafnodeCount.back() + internalNodes[n]->leafCount());
1141 }
1142
1143 numLeafNodes = leafnodeCount.back();
1144
1145 // extract all leafnodes
1146 nodes.reserve(numLeafNodes);
1147
1148 for (size_t n = 0; n < numInternalNodes; ++n) {
1149 internalNodes[n]->getNodes(nodes);
1150 }
1151 }
1152
1153 // create mask leafnodes
1154 std::unique_ptr<BoolLeafNodeType*[]> maskNodes(new BoolLeafNodeType*[numLeafNodes]);
1155
1156 tbb::parallel_for(tbb::blocked_range<size_t>(0, numLeafNodes),
1157 MaskInteriorVoxels<LeafNodeType>(iso, nodes.data(), maskNodes.get()));
1158
1159
1160 // create mask grid
1161 typename BoolTreeType::Ptr maskTree(new BoolTreeType(false));
1162
1163 PopulateTree<BoolTreeType> populate(*maskTree, maskNodes.get(), leafnodeCount.data(), false);
1164 tbb::parallel_reduce(tbb::blocked_range<size_t>(0, numInternalNodes), populate);
1165
1166
1167 // evaluate tile values
1168 std::vector<BoolInternalNodeType*> internalMaskNodes;
1169 maskTree->getNodes(internalMaskNodes);
1170
1171 tbb::parallel_for(tbb::blocked_range<size_t>(0, internalMaskNodes.size()),
1172 MaskInteriorTiles<TreeType, BoolInternalNodeType>(iso, tree, internalMaskNodes.data()));
1173
1174 tree::ValueAccessor<const TreeType> acc(tree);
1175
1176 typename BoolTreeType::ValueAllIter it(*maskTree);
1177 it.setMaxDepth(BoolTreeType::ValueAllIter::LEAF_DEPTH - 2);
1178
1179 for ( ; it; ++it) {
1180 if (acc.getValue(it.getCoord()) < iso) {
1181 it.setValue(true);
1182 it.setActiveState(true);
1183 }
1184 }
1185
1186 return maskTree;
1187} // computeInteriorMask()
1188
1189
1190template<typename InputTreeType>
1191struct MaskIsovalueCrossingVoxels
1192{
1193 using InputValueType = typename InputTreeType::ValueType;
1194 using InputLeafNodeType = typename InputTreeType::LeafNodeType;
1195 using BoolTreeType = typename InputTreeType::template ValueConverter<bool>::Type;
1196 using BoolLeafNodeType = typename BoolTreeType::LeafNodeType;
1197
1198 MaskIsovalueCrossingVoxels(
1199 const InputTreeType& inputTree,
1200 const std::vector<const InputLeafNodeType*>& inputLeafNodes,
1201 BoolTreeType& maskTree,
1202 InputValueType iso)
1203 : mInputAccessor(inputTree)
1204 , mInputNodes(!inputLeafNodes.empty() ? &inputLeafNodes.front() : nullptr)
1205 , mMaskTree(false)
1206 , mMaskAccessor(maskTree)
1207 , mIsovalue(iso)
1208 {
1209 }
1210
1211 MaskIsovalueCrossingVoxels(MaskIsovalueCrossingVoxels& rhs, tbb::split)
1212 : mInputAccessor(rhs.mInputAccessor.tree())
1213 , mInputNodes(rhs.mInputNodes)
1214 , mMaskTree(false)
1215 , mMaskAccessor(mMaskTree)
1216 , mIsovalue(rhs.mIsovalue)
1217 {
1218 }
1219
1220 void operator()(const tbb::blocked_range<size_t>& range) {
1221
1222 const InputValueType iso = mIsovalue;
1223 Coord ijk(0, 0, 0);
1224
1225 BoolLeafNodeType* maskNodePt = nullptr;
1226
1227 for (size_t n = range.begin(); mInputNodes && (n != range.end()); ++n) {
1228
1229 const InputLeafNodeType& node = *mInputNodes[n];
1230
1231 if (!maskNodePt) maskNodePt = new BoolLeafNodeType(node.origin(), false);
1232 else maskNodePt->setOrigin(node.origin());
1233
1234 bool collectedData = false;
1235
1236 for (typename InputLeafNodeType::ValueOnCIter it = node.cbeginValueOn(); it; ++it) {
1237
1238 bool isUnder = *it < iso;
1239
1240 ijk = it.getCoord();
1241
1242 ++ijk[2];
1243 bool signChange = isUnder != (mInputAccessor.getValue(ijk) < iso); // +z edge
1244 --ijk[2];
1245
1246 if (!signChange) {
1247 --ijk[2];
1248 signChange = isUnder != (mInputAccessor.getValue(ijk) < iso); // -z edge
1249 ++ijk[2];
1250 }
1251
1252 if (!signChange) {
1253 ++ijk[1];
1254 signChange = isUnder != (mInputAccessor.getValue(ijk) < iso); // +y edge
1255 --ijk[1];
1256 }
1257
1258 if (!signChange) {
1259 --ijk[1];
1260 signChange = isUnder != (mInputAccessor.getValue(ijk) < iso); // -y edge
1261 ++ijk[1];
1262 }
1263
1264 if (!signChange) {
1265 ++ijk[0];
1266 signChange = isUnder != (mInputAccessor.getValue(ijk) < iso); // +x edge
1267 --ijk[0];
1268 }
1269
1270 if (!signChange) {
1271 --ijk[0];
1272 signChange = isUnder != (mInputAccessor.getValue(ijk) < iso); // -x edge
1273 ++ijk[0];
1274 }
1275
1276 if (signChange) {
1277 collectedData = true;
1278 maskNodePt->setValueOn(it.pos(), true);
1279 }
1280 }
1281
1282 if (collectedData) {
1283 mMaskAccessor.addLeaf(maskNodePt);
1284 maskNodePt = nullptr;
1285 }
1286 }
1287
1288 delete maskNodePt;
1289 }
1290
1291 void join(MaskIsovalueCrossingVoxels& rhs) {
1292 mMaskAccessor.tree().merge(rhs.mMaskAccessor.tree());
1293 }
1294
1295private:
1296 tree::ValueAccessor<const InputTreeType> mInputAccessor;
1297 InputLeafNodeType const * const * const mInputNodes;
1298
1299 BoolTreeType mMaskTree;
1300 tree::ValueAccessor<BoolTreeType> mMaskAccessor;
1301
1302 InputValueType mIsovalue;
1303}; // MaskIsovalueCrossingVoxels
1304
1305
1306////////////////////////////////////////
1307
1308
1309template<typename NodeType>
1310struct NodeMaskSegment
1311{
1312 using Ptr = SharedPtr<NodeMaskSegment>;
1313 using NodeMaskType = typename NodeType::NodeMaskType;
1314
1315 NodeMaskSegment() : connections(), mask(false), origin(0,0,0), visited(false) {}
1316
1317 std::vector<NodeMaskSegment*> connections;
1318 NodeMaskType mask;
1319 Coord origin;
1320 bool visited;
1321}; // struct NodeMaskSegment
1322
1323
1324template<typename NodeType>
1325void
1326nodeMaskSegmentation(const NodeType& node,
1327 std::vector<typename NodeMaskSegment<NodeType>::Ptr>& segments)
1328{
1329 using NodeMaskType = typename NodeType::NodeMaskType;
1330 using NodeMaskSegmentType = NodeMaskSegment<NodeType>;
1331 using NodeMaskSegmentTypePtr = typename NodeMaskSegmentType::Ptr;
1332
1333 NodeMaskType nodeMask(node.getValueMask());
1334 std::deque<Index> indexList;
1335
1336 while (!nodeMask.isOff()) {
1337
1338 NodeMaskSegmentTypePtr segment(new NodeMaskSegmentType());
1339 segment->origin = node.origin();
1340
1341 NodeMaskType& mask = segment->mask;
1342
1343 indexList.push_back(nodeMask.findFirstOn());
1344 nodeMask.setOff(indexList.back()); // mark as visited
1345 Coord ijk(0, 0, 0);
1346
1347 while (!indexList.empty()) {
1348
1349 const Index pos = indexList.back();
1350 indexList.pop_back();
1351
1352 if (mask.isOn(pos)) continue;
1353 mask.setOn(pos);
1354
1355 ijk = NodeType::offsetToLocalCoord(pos);
1356
1357 Index npos = pos - 1;
1358 if (ijk[2] != 0 && nodeMask.isOn(npos)) {
1359 nodeMask.setOff(npos);
1360 indexList.push_back(npos);
1361 }
1362
1363 npos = pos + 1;
1364 if (ijk[2] != (NodeType::DIM - 1) && nodeMask.isOn(npos)) {
1365 nodeMask.setOff(npos);
1366 indexList.push_back(npos);
1367 }
1368
1369 npos = pos - NodeType::DIM;
1370 if (ijk[1] != 0 && nodeMask.isOn(npos)) {
1371 nodeMask.setOff(npos);
1372 indexList.push_back(npos);
1373 }
1374
1375 npos = pos + NodeType::DIM;
1376 if (ijk[1] != (NodeType::DIM - 1) && nodeMask.isOn(npos)) {
1377 nodeMask.setOff(npos);
1378 indexList.push_back(npos);
1379 }
1380
1381 npos = pos - NodeType::DIM * NodeType::DIM;
1382 if (ijk[0] != 0 && nodeMask.isOn(npos)) {
1383 nodeMask.setOff(npos);
1384 indexList.push_back(npos);
1385 }
1386
1387 npos = pos + NodeType::DIM * NodeType::DIM;
1388 if (ijk[0] != (NodeType::DIM - 1) && nodeMask.isOn(npos)) {
1389 nodeMask.setOff(npos);
1390 indexList.push_back(npos);
1391 }
1392
1393 }
1394
1395 segments.push_back(segment);
1396 }
1397}
1398
1399
1400template<typename NodeType>
1401struct SegmentNodeMask
1402{
1403 using NodeMaskSegmentType = NodeMaskSegment<NodeType>;
1404 using NodeMaskSegmentTypePtr = typename NodeMaskSegmentType::Ptr;
1405 using NodeMaskSegmentVector = typename std::vector<NodeMaskSegmentTypePtr>;
1406
1407 SegmentNodeMask(std::vector<NodeType*>& nodes, NodeMaskSegmentVector* nodeMaskArray)
1408 : mNodes(!nodes.empty() ? &nodes.front() : nullptr)
1409 , mNodeMaskArray(nodeMaskArray)
1410 {
1411 }
1412
1413 void operator()(const tbb::blocked_range<size_t>& range) const {
1414 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
1415 NodeType& node = *mNodes[n];
1416 nodeMaskSegmentation(node, mNodeMaskArray[n]);
1417
1418 // hack origin data to store array offset
1419 Coord& origin = const_cast<Coord&>(node.origin());
1420 origin[0] = static_cast<int>(n);
1421 }
1422 }
1423
1424 NodeType * const * const mNodes;
1425 NodeMaskSegmentVector * const mNodeMaskArray;
1426}; // struct SegmentNodeMask
1427
1428
1429template<typename TreeType, typename NodeType>
1430struct ConnectNodeMaskSegments
1431{
1432 using NodeMaskType = typename NodeType::NodeMaskType;
1433 using NodeMaskSegmentType = NodeMaskSegment<NodeType>;
1434 using NodeMaskSegmentTypePtr = typename NodeMaskSegmentType::Ptr;
1435 using NodeMaskSegmentVector = typename std::vector<NodeMaskSegmentTypePtr>;
1436
1437 ConnectNodeMaskSegments(const TreeType& tree, NodeMaskSegmentVector* nodeMaskArray)
1438 : mTree(&tree)
1439 , mNodeMaskArray(nodeMaskArray)
1440 {
1441 }
1442
1443 void operator()(const tbb::blocked_range<size_t>& range) const {
1444
1445 tree::ValueAccessor<const TreeType> acc(*mTree);
1446
1447 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
1448
1449 NodeMaskSegmentVector& segments = mNodeMaskArray[n];
1450 if (segments.empty()) continue;
1451
1452 std::vector<std::set<NodeMaskSegmentType*> > connections(segments.size());
1453
1454 Coord ijk = segments[0]->origin;
1455
1456 const NodeType* node = acc.template probeConstNode<NodeType>(ijk);
1457 if (!node) continue;
1458
1459 // get neighbour nodes
1460
1461 ijk[2] += NodeType::DIM;
1462 const NodeType* nodeZUp = acc.template probeConstNode<NodeType>(ijk);
1463 ijk[2] -= (NodeType::DIM + NodeType::DIM);
1464 const NodeType* nodeZDown = acc.template probeConstNode<NodeType>(ijk);
1465 ijk[2] += NodeType::DIM;
1466
1467 ijk[1] += NodeType::DIM;
1468 const NodeType* nodeYUp = acc.template probeConstNode<NodeType>(ijk);
1469 ijk[1] -= (NodeType::DIM + NodeType::DIM);
1470 const NodeType* nodeYDown = acc.template probeConstNode<NodeType>(ijk);
1471 ijk[1] += NodeType::DIM;
1472
1473 ijk[0] += NodeType::DIM;
1474 const NodeType* nodeXUp = acc.template probeConstNode<NodeType>(ijk);
1475 ijk[0] -= (NodeType::DIM + NodeType::DIM);
1476 const NodeType* nodeXDown = acc.template probeConstNode<NodeType>(ijk);
1477 ijk[0] += NodeType::DIM;
1478
1479 const Index startPos = node->getValueMask().findFirstOn();
1480 for (Index pos = startPos; pos < NodeMaskType::SIZE; ++pos) {
1481
1482 if (!node->isValueOn(pos)) continue;
1483
1484 ijk = NodeType::offsetToLocalCoord(pos);
1485
1486#ifdef _MSC_FULL_VER
1487 #if _MSC_FULL_VER >= 190000000 && _MSC_FULL_VER < 190024210
1488 // Visual Studio 2015 had a codegen bug that wasn't fixed until Update 3
1489 volatile Index npos = 0;
1490 #else
1491 Index npos = 0;
1492 #endif
1493#else
1494 Index npos = 0;
1495#endif
1496
1497 if (ijk[2] == 0) {
1498 npos = pos + (NodeType::DIM - 1);
1499 if (nodeZDown && nodeZDown->isValueOn(npos)) {
1500 NodeMaskSegmentType* nsegment =
1501 findNodeMaskSegment(mNodeMaskArray[getNodeOffset(*nodeZDown)], npos);
1502 const Index idx = findNodeMaskSegmentIndex(segments, pos);
1503 connections[idx].insert(nsegment);
1504 }
1505 } else if (ijk[2] == (NodeType::DIM - 1)) {
1506 npos = pos - (NodeType::DIM - 1);
1507 if (nodeZUp && nodeZUp->isValueOn(npos)) {
1508 NodeMaskSegmentType* nsegment =
1509 findNodeMaskSegment(mNodeMaskArray[getNodeOffset(*nodeZUp)], npos);
1510 const Index idx = findNodeMaskSegmentIndex(segments, pos);
1511 connections[idx].insert(nsegment);
1512 }
1513 }
1514
1515 if (ijk[1] == 0) {
1516 npos = pos + (NodeType::DIM - 1) * NodeType::DIM;
1517 if (nodeYDown && nodeYDown->isValueOn(npos)) {
1518 NodeMaskSegmentType* nsegment =
1519 findNodeMaskSegment(mNodeMaskArray[getNodeOffset(*nodeYDown)], npos);
1520 const Index idx = findNodeMaskSegmentIndex(segments, pos);
1521 connections[idx].insert(nsegment);
1522 }
1523 } else if (ijk[1] == (NodeType::DIM - 1)) {
1524 npos = pos - (NodeType::DIM - 1) * NodeType::DIM;
1525 if (nodeYUp && nodeYUp->isValueOn(npos)) {
1526 NodeMaskSegmentType* nsegment =
1527 findNodeMaskSegment(mNodeMaskArray[getNodeOffset(*nodeYUp)], npos);
1528 const Index idx = findNodeMaskSegmentIndex(segments, pos);
1529 connections[idx].insert(nsegment);
1530 }
1531 }
1532
1533 if (ijk[0] == 0) {
1534 npos = pos + (NodeType::DIM - 1) * NodeType::DIM * NodeType::DIM;
1535 if (nodeXDown && nodeXDown->isValueOn(npos)) {
1536 NodeMaskSegmentType* nsegment =
1537 findNodeMaskSegment(mNodeMaskArray[getNodeOffset(*nodeXDown)], npos);
1538 const Index idx = findNodeMaskSegmentIndex(segments, pos);
1539 connections[idx].insert(nsegment);
1540 }
1541 } else if (ijk[0] == (NodeType::DIM - 1)) {
1542 npos = pos - (NodeType::DIM - 1) * NodeType::DIM * NodeType::DIM;
1543 if (nodeXUp && nodeXUp->isValueOn(npos)) {
1544 NodeMaskSegmentType* nsegment =
1545 findNodeMaskSegment(mNodeMaskArray[getNodeOffset(*nodeXUp)], npos);
1546 const Index idx = findNodeMaskSegmentIndex(segments, pos);
1547 connections[idx].insert(nsegment);
1548 }
1549 }
1550 }
1551
1552 for (size_t i = 0, I = connections.size(); i < I; ++i) {
1553
1554 typename std::set<NodeMaskSegmentType*>::iterator
1555 it = connections[i].begin(), end = connections[i].end();
1556
1557 std::vector<NodeMaskSegmentType*>& segmentConnections = segments[i]->connections;
1558 segmentConnections.reserve(connections.size());
1559 for (; it != end; ++it) {
1560 segmentConnections.push_back(*it);
1561 }
1562 }
1563 } // end range loop
1564 }
1565
1566private:
1567
1568 static inline size_t getNodeOffset(const NodeType& node) {
1569 return static_cast<size_t>(node.origin()[0]);
1570 }
1571
1572 static inline NodeMaskSegmentType*
1573 findNodeMaskSegment(NodeMaskSegmentVector& segments, Index pos)
1574 {
1575 NodeMaskSegmentType* segment = nullptr;
1576
1577 for (size_t n = 0, N = segments.size(); n < N; ++n) {
1578 if (segments[n]->mask.isOn(pos)) {
1579 segment = segments[n].get();
1580 break;
1581 }
1582 }
1583
1584 return segment;
1585 }
1586
1587 static inline Index
1588 findNodeMaskSegmentIndex(NodeMaskSegmentVector& segments, Index pos)
1589 {
1590 for (Index n = 0, N = Index(segments.size()); n < N; ++n) {
1591 if (segments[n]->mask.isOn(pos)) return n;
1592 }
1593 return Index(-1);
1594 }
1595
1596 TreeType const * const mTree;
1597 NodeMaskSegmentVector * const mNodeMaskArray;
1598}; // struct ConnectNodeMaskSegments
1599
1600
1601template<typename TreeType>
1602struct MaskSegmentGroup
1603{
1604 using LeafNodeType = typename TreeType::LeafNodeType;
1605 using TreeTypePtr = typename TreeType::Ptr;
1606 using NodeMaskSegmentType = NodeMaskSegment<LeafNodeType>;
1607
1608 MaskSegmentGroup(const std::vector<NodeMaskSegmentType*>& segments)
1609 : mSegments(!segments.empty() ? &segments.front() : nullptr)
1610 , mTree(new TreeType(false))
1611 {
1612 }
1613
1614 MaskSegmentGroup(const MaskSegmentGroup& rhs, tbb::split)
1615 : mSegments(rhs.mSegments)
1616 , mTree(new TreeType(false))
1617 {
1618 }
1619
1620 TreeTypePtr& mask() { return mTree; }
1621
1622 void join(MaskSegmentGroup& rhs) { mTree->merge(*rhs.mTree); }
1623
1624 void operator()(const tbb::blocked_range<size_t>& range) {
1625
1626 tree::ValueAccessor<TreeType> acc(*mTree);
1627
1628 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
1629 NodeMaskSegmentType& segment = *mSegments[n];
1630 LeafNodeType* node = acc.touchLeaf(segment.origin);
1631 node->getValueMask() |= segment.mask;
1632 }
1633 }
1634
1635private:
1636 NodeMaskSegmentType * const * const mSegments;
1637 TreeTypePtr mTree;
1638}; // struct MaskSegmentGroup
1639
1640
1641////////////////////////////////////////
1642
1643
1644template<typename TreeType>
1645struct ExpandLeafNodeRegion
1646{
1647 using ValueType = typename TreeType::ValueType;
1648 using LeafNodeType = typename TreeType::LeafNodeType;
1649 using NodeMaskType = typename LeafNodeType::NodeMaskType;
1650
1651 using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
1652 using BoolLeafNodeType = typename BoolTreeType::LeafNodeType;
1653
1654 /////
1655
1656 ExpandLeafNodeRegion(const TreeType& distTree, BoolTreeType& maskTree,
1657 std::vector<BoolLeafNodeType*>& maskNodes)
1658 : mDistTree(&distTree)
1659 , mMaskTree(&maskTree)
1660 , mMaskNodes(!maskNodes.empty() ? &maskNodes.front() : nullptr)
1661 , mNewMaskTree(false)
1662 {
1663 }
1664
1665 ExpandLeafNodeRegion(const ExpandLeafNodeRegion& rhs, tbb::split)
1666 : mDistTree(rhs.mDistTree)
1667 , mMaskTree(rhs.mMaskTree)
1668 , mMaskNodes(rhs.mMaskNodes)
1669 , mNewMaskTree(false)
1670 {
1671 }
1672
1673 BoolTreeType& newMaskTree() { return mNewMaskTree; }
1674
1675 void join(ExpandLeafNodeRegion& rhs) { mNewMaskTree.merge(rhs.mNewMaskTree); }
1676
1677 void operator()(const tbb::blocked_range<size_t>& range) {
1678
1679 using NodeType = LeafNodeType;
1680
1681 tree::ValueAccessor<const TreeType> distAcc(*mDistTree);
1682 tree::ValueAccessor<const BoolTreeType> maskAcc(*mMaskTree);
1683 tree::ValueAccessor<BoolTreeType> newMaskAcc(mNewMaskTree);
1684
1685 NodeMaskType maskZUp, maskZDown, maskYUp, maskYDown, maskXUp, maskXDown;
1686
1687 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
1688
1689 BoolLeafNodeType& maskNode = *mMaskNodes[n];
1690 if (maskNode.isEmpty()) continue;
1691
1692 Coord ijk = maskNode.origin(), nijk;
1693
1694 const LeafNodeType* distNode = distAcc.probeConstLeaf(ijk);
1695 if (!distNode) continue;
1696
1697 const ValueType *dataZUp = nullptr, *dataZDown = nullptr,
1698 *dataYUp = nullptr, *dataYDown = nullptr,
1699 *dataXUp = nullptr, *dataXDown = nullptr;
1700
1701 ijk[2] += NodeType::DIM;
1702 getData(ijk, distAcc, maskAcc, maskZUp, dataZUp);
1703 ijk[2] -= (NodeType::DIM + NodeType::DIM);
1704 getData(ijk, distAcc, maskAcc, maskZDown, dataZDown);
1705 ijk[2] += NodeType::DIM;
1706
1707 ijk[1] += NodeType::DIM;
1708 getData(ijk, distAcc, maskAcc, maskYUp, dataYUp);
1709 ijk[1] -= (NodeType::DIM + NodeType::DIM);
1710 getData(ijk, distAcc, maskAcc, maskYDown, dataYDown);
1711 ijk[1] += NodeType::DIM;
1712
1713 ijk[0] += NodeType::DIM;
1714 getData(ijk, distAcc, maskAcc, maskXUp, dataXUp);
1715 ijk[0] -= (NodeType::DIM + NodeType::DIM);
1716 getData(ijk, distAcc, maskAcc, maskXDown, dataXDown);
1717 ijk[0] += NodeType::DIM;
1718
1719 for (typename BoolLeafNodeType::ValueOnIter it = maskNode.beginValueOn(); it; ++it) {
1720
1721 const Index pos = it.pos();
1722 const ValueType val = std::abs(distNode->getValue(pos));
1723
1724 ijk = BoolLeafNodeType::offsetToLocalCoord(pos);
1725 nijk = ijk + maskNode.origin();
1726
1727 if (dataZUp && ijk[2] == (BoolLeafNodeType::DIM - 1)) {
1728 const Index npos = pos - (NodeType::DIM - 1);
1729 if (maskZUp.isOn(npos) && std::abs(dataZUp[npos]) > val) {
1730 newMaskAcc.setValueOn(nijk.offsetBy(0, 0, 1));
1731 }
1732 } else if (dataZDown && ijk[2] == 0) {
1733 const Index npos = pos + (NodeType::DIM - 1);
1734 if (maskZDown.isOn(npos) && std::abs(dataZDown[npos]) > val) {
1735 newMaskAcc.setValueOn(nijk.offsetBy(0, 0, -1));
1736 }
1737 }
1738
1739 if (dataYUp && ijk[1] == (BoolLeafNodeType::DIM - 1)) {
1740 const Index npos = pos - (NodeType::DIM - 1) * NodeType::DIM;
1741 if (maskYUp.isOn(npos) && std::abs(dataYUp[npos]) > val) {
1742 newMaskAcc.setValueOn(nijk.offsetBy(0, 1, 0));
1743 }
1744 } else if (dataYDown && ijk[1] == 0) {
1745 const Index npos = pos + (NodeType::DIM - 1) * NodeType::DIM;
1746 if (maskYDown.isOn(npos) && std::abs(dataYDown[npos]) > val) {
1747 newMaskAcc.setValueOn(nijk.offsetBy(0, -1, 0));
1748 }
1749 }
1750
1751 if (dataXUp && ijk[0] == (BoolLeafNodeType::DIM - 1)) {
1752 const Index npos = pos - (NodeType::DIM - 1) * NodeType::DIM * NodeType::DIM;
1753 if (maskXUp.isOn(npos) && std::abs(dataXUp[npos]) > val) {
1754 newMaskAcc.setValueOn(nijk.offsetBy(1, 0, 0));
1755 }
1756 } else if (dataXDown && ijk[0] == 0) {
1757 const Index npos = pos + (NodeType::DIM - 1) * NodeType::DIM * NodeType::DIM;
1758 if (maskXDown.isOn(npos) && std::abs(dataXDown[npos]) > val) {
1759 newMaskAcc.setValueOn(nijk.offsetBy(-1, 0, 0));
1760 }
1761 }
1762
1763 } // end value on loop
1764 } // end range loop
1765 }
1766
1767private:
1768
1769 static inline void
1770 getData(const Coord& ijk, tree::ValueAccessor<const TreeType>& distAcc,
1771 tree::ValueAccessor<const BoolTreeType>& maskAcc, NodeMaskType& mask,
1772 const ValueType*& data)
1773 {
1774 const LeafNodeType* node = distAcc.probeConstLeaf(ijk);
1775 if (node) {
1776 data = node->buffer().data();
1777 mask = node->getValueMask();
1778 const BoolLeafNodeType* maskNodePt = maskAcc.probeConstLeaf(ijk);
1779 if (maskNodePt) mask -= maskNodePt->getValueMask();
1780 }
1781 }
1782
1783 TreeType const * const mDistTree;
1784 BoolTreeType * const mMaskTree;
1785 BoolLeafNodeType ** const mMaskNodes;
1786
1787 BoolTreeType mNewMaskTree;
1788}; // struct ExpandLeafNodeRegion
1789
1790
1791template<typename TreeType>
1792struct FillLeafNodeVoxels
1793{
1794 using ValueType = typename TreeType::ValueType;
1795 using LeafNodeType = typename TreeType::LeafNodeType;
1796 using NodeMaskType = typename LeafNodeType::NodeMaskType;
1797 using BoolLeafNodeType = tree::LeafNode<bool, LeafNodeType::LOG2DIM>;
1798
1799 FillLeafNodeVoxels(const TreeType& tree, std::vector<BoolLeafNodeType*>& maskNodes)
1800 : mTree(&tree), mMaskNodes(!maskNodes.empty() ? &maskNodes.front() : nullptr)
1801 {
1802 }
1803
1804 void operator()(const tbb::blocked_range<size_t>& range) const {
1805
1806 tree::ValueAccessor<const TreeType> distAcc(*mTree);
1807
1808 std::vector<Index> indexList;
1809 indexList.reserve(NodeMaskType::SIZE);
1810
1811 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
1812
1813 BoolLeafNodeType& maskNode = *mMaskNodes[n];
1814
1815 const LeafNodeType * distNode = distAcc.probeConstLeaf(maskNode.origin());
1816 if (!distNode) continue;
1817
1818 NodeMaskType mask(distNode->getValueMask());
1819 NodeMaskType& narrowbandMask = maskNode.getValueMask();
1820
1821 for (Index pos = narrowbandMask.findFirstOn(); pos < NodeMaskType::SIZE; ++pos) {
1822 if (narrowbandMask.isOn(pos)) indexList.push_back(pos);
1823 }
1824
1825 mask -= narrowbandMask; // bitwise difference
1826 narrowbandMask.setOff();
1827
1828 const ValueType* data = distNode->buffer().data();
1829 Coord ijk(0, 0, 0);
1830
1831 while (!indexList.empty()) {
1832
1833 const Index pos = indexList.back();
1834 indexList.pop_back();
1835
1836 if (narrowbandMask.isOn(pos)) continue;
1837 narrowbandMask.setOn(pos);
1838
1839 const ValueType dist = std::abs(data[pos]);
1840
1841 ijk = LeafNodeType::offsetToLocalCoord(pos);
1842
1843 Index npos = pos - 1;
1844 if (ijk[2] != 0 && mask.isOn(npos) && std::abs(data[npos]) > dist) {
1845 mask.setOff(npos);
1846 indexList.push_back(npos);
1847 }
1848
1849 npos = pos + 1;
1850 if ((ijk[2] != (LeafNodeType::DIM - 1)) && mask.isOn(npos)
1851 && std::abs(data[npos]) > dist)
1852 {
1853 mask.setOff(npos);
1854 indexList.push_back(npos);
1855 }
1856
1857 npos = pos - LeafNodeType::DIM;
1858 if (ijk[1] != 0 && mask.isOn(npos) && std::abs(data[npos]) > dist) {
1859 mask.setOff(npos);
1860 indexList.push_back(npos);
1861 }
1862
1863 npos = pos + LeafNodeType::DIM;
1864 if ((ijk[1] != (LeafNodeType::DIM - 1)) && mask.isOn(npos)
1865 && std::abs(data[npos]) > dist)
1866 {
1867 mask.setOff(npos);
1868 indexList.push_back(npos);
1869 }
1870
1871 npos = pos - LeafNodeType::DIM * LeafNodeType::DIM;
1872 if (ijk[0] != 0 && mask.isOn(npos) && std::abs(data[npos]) > dist) {
1873 mask.setOff(npos);
1874 indexList.push_back(npos);
1875 }
1876
1877 npos = pos + LeafNodeType::DIM * LeafNodeType::DIM;
1878 if ((ijk[0] != (LeafNodeType::DIM - 1)) && mask.isOn(npos)
1879 && std::abs(data[npos]) > dist)
1880 {
1881 mask.setOff(npos);
1882 indexList.push_back(npos);
1883 }
1884 } // end flood fill loop
1885 } // end range loop
1886 }
1887
1888 TreeType const * const mTree;
1889 BoolLeafNodeType ** const mMaskNodes;
1890}; // FillLeafNodeVoxels
1891
1892
1893template<typename TreeType>
1894struct ExpandNarrowbandMask
1895{
1896 using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
1897 using BoolLeafNodeType = typename BoolTreeType::LeafNodeType;
1898 using BoolTreeTypePtr = typename BoolTreeType::Ptr;
1899
1900 ExpandNarrowbandMask(const TreeType& tree, std::vector<BoolTreeTypePtr>& segments)
1901 : mTree(&tree), mSegments(!segments.empty() ? &segments.front() : nullptr)
1902 {
1903 }
1904
1905 void operator()(const tbb::blocked_range<size_t>& range) const {
1906
1907 const TreeType& distTree = *mTree;
1908 std::vector<BoolLeafNodeType*> nodes;
1909
1910 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
1911
1912 BoolTreeType& narrowBandMask = *mSegments[n];
1913
1914 BoolTreeType candidateMask(narrowBandMask, false, TopologyCopy());
1915
1916 while (true) {
1917
1918 nodes.clear();
1919 candidateMask.getNodes(nodes);
1920 if (nodes.empty()) break;
1921
1922 const tbb::blocked_range<size_t> nodeRange(0, nodes.size());
1923
1924 tbb::parallel_for(nodeRange, FillLeafNodeVoxels<TreeType>(distTree, nodes));
1925
1926 narrowBandMask.topologyUnion(candidateMask);
1927
1928 ExpandLeafNodeRegion<TreeType> op(distTree, narrowBandMask, nodes);
1929 tbb::parallel_reduce(nodeRange, op);
1930
1931 if (op.newMaskTree().empty()) break;
1932
1933 candidateMask.clear();
1934 candidateMask.merge(op.newMaskTree());
1935 } // end expand loop
1936 } // end range loop
1937 }
1938
1939 TreeType const * const mTree;
1940 BoolTreeTypePtr * const mSegments;
1941}; // ExpandNarrowbandMask
1942
1943
1944template<typename TreeType>
1945struct FloodFillSign
1946{
1947 using TreeTypePtr = typename TreeType::Ptr;
1948 using ValueType = typename TreeType::ValueType;
1949 using LeafNodeType = typename TreeType::LeafNodeType;
1950 using RootNodeType = typename TreeType::RootNodeType;
1951 using NodeChainType = typename RootNodeType::NodeChainType;
1952 using InternalNodeType = typename NodeChainType::template Get<1>;
1953
1954 FloodFillSign(const TreeType& tree, std::vector<TreeTypePtr>& segments)
1955 : mTree(&tree)
1956 , mSegments(!segments.empty() ? &segments.front() : nullptr)
1957 , mMinValue(ValueType(0.0))
1958 {
1959 ValueType minSDFValue = std::numeric_limits<ValueType>::max();
1960
1961 {
1962 std::vector<const InternalNodeType*> nodes;
1963 tree.getNodes(nodes);
1964
1965 if (!nodes.empty()) {
1966 FindMinTileValue<InternalNodeType> minOp(nodes.data());
1967 tbb::parallel_reduce(tbb::blocked_range<size_t>(0, nodes.size()), minOp);
1968 minSDFValue = std::min(minSDFValue, minOp.minValue);
1969 }
1970 }
1971
1972 if (minSDFValue > ValueType(0.0)) {
1973 std::vector<const LeafNodeType*> nodes;
1974 tree.getNodes(nodes);
1975 if (!nodes.empty()) {
1976 FindMinVoxelValue<LeafNodeType> minOp(nodes.data());
1977 tbb::parallel_reduce(tbb::blocked_range<size_t>(0, nodes.size()), minOp);
1978 minSDFValue = std::min(minSDFValue, minOp.minValue);
1979 }
1980 }
1981
1982 mMinValue = minSDFValue;
1983 }
1984
1985 void operator()(const tbb::blocked_range<size_t>& range) const {
1986 const ValueType interiorValue = -std::abs(mMinValue);
1987 const ValueType exteriorValue = std::abs(mTree->background());
1988 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
1989 tools::signedFloodFillWithValues(*mSegments[n], exteriorValue, interiorValue);
1990 }
1991 }
1992
1993private:
1994
1995 TreeType const * const mTree;
1996 TreeTypePtr * const mSegments;
1997 ValueType mMinValue;
1998}; // FloodFillSign
1999
2000
2001template<typename TreeType>
2002struct MaskedCopy
2003{
2004 using TreeTypePtr = typename TreeType::Ptr;
2005 using ValueType = typename TreeType::ValueType;
2006 using LeafNodeType = typename TreeType::LeafNodeType;
2007
2008 using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
2009 using BoolTreeTypePtr = typename BoolTreeType::Ptr;
2010 using BoolLeafNodeType = typename BoolTreeType::LeafNodeType;
2011
2012 MaskedCopy(const TreeType& tree, std::vector<TreeTypePtr>& segments,
2013 std::vector<BoolTreeTypePtr>& masks)
2014 : mTree(&tree)
2015 , mSegments(!segments.empty() ? &segments.front() : nullptr)
2016 , mMasks(!masks.empty() ? &masks.front() : nullptr)
2017 {
2018 }
2019
2020 void operator()(const tbb::blocked_range<size_t>& range) const {
2021
2022 std::vector<const BoolLeafNodeType*> nodes;
2023
2024 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
2025
2026 const BoolTreeType& mask = *mMasks[n];
2027
2028 nodes.clear();
2029 mask.getNodes(nodes);
2030
2031 Copy op(*mTree, nodes);
2032 tbb::parallel_reduce(tbb::blocked_range<size_t>(0, nodes.size()), op);
2033 mSegments[n] = op.outputTree();
2034 }
2035 }
2036
2037private:
2038
2039 struct Copy {
2040 Copy(const TreeType& inputTree, std::vector<const BoolLeafNodeType*>& maskNodes)
2041 : mInputTree(&inputTree)
2042 , mMaskNodes(!maskNodes.empty() ? &maskNodes.front() : nullptr)
2043 , mOutputTreePtr(new TreeType(inputTree.background()))
2044 {
2045 }
2046
2047 Copy(const Copy& rhs, tbb::split)
2048 : mInputTree(rhs.mInputTree)
2049 , mMaskNodes(rhs.mMaskNodes)
2050 , mOutputTreePtr(new TreeType(mInputTree->background()))
2051 {
2052 }
2053
2054 TreeTypePtr& outputTree() { return mOutputTreePtr; }
2055
2056 void join(Copy& rhs) { mOutputTreePtr->merge(*rhs.mOutputTreePtr); }
2057
2058 void operator()(const tbb::blocked_range<size_t>& range) {
2059
2060 tree::ValueAccessor<const TreeType> inputAcc(*mInputTree);
2061 tree::ValueAccessor<TreeType> outputAcc(*mOutputTreePtr);
2062
2063 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
2064
2065 const BoolLeafNodeType& maskNode = *mMaskNodes[n];
2066 if (maskNode.isEmpty()) continue;
2067
2068 const Coord& ijk = maskNode.origin();
2069
2070 const LeafNodeType* inputNode = inputAcc.probeConstLeaf(ijk);
2071 if (inputNode) {
2072
2073 LeafNodeType* outputNode = outputAcc.touchLeaf(ijk);
2074
2075 for (typename BoolLeafNodeType::ValueOnCIter it = maskNode.cbeginValueOn();
2076 it; ++it)
2077 {
2078 const Index idx = it.pos();
2079 outputNode->setValueOn(idx, inputNode->getValue(idx));
2080 }
2081 } else {
2082 const int valueDepth = inputAcc.getValueDepth(ijk);
2083 if (valueDepth >= 0) {
2084 outputAcc.addTile(TreeType::RootNodeType::LEVEL - valueDepth,
2085 ijk, inputAcc.getValue(ijk), true);
2086 }
2087 }
2088 }
2089 }
2090
2091 private:
2092 TreeType const * const mInputTree;
2093 BoolLeafNodeType const * const * const mMaskNodes;
2094 TreeTypePtr mOutputTreePtr;
2095 }; // struct Copy
2096
2097 TreeType const * const mTree;
2098 TreeTypePtr * const mSegments;
2099 BoolTreeTypePtr * const mMasks;
2100}; // MaskedCopy
2101
2102
2103////////////////////////////////////////
2104
2105
2106template<typename VolumePtrType>
2107struct ComputeActiveVoxelCount
2108{
2109 ComputeActiveVoxelCount(std::vector<VolumePtrType>& segments, size_t *countArray)
2110 : mSegments(!segments.empty() ? &segments.front() : nullptr)
2111 , mCountArray(countArray)
2112 {
2113 }
2114
2115 void operator()(const tbb::blocked_range<size_t>& range) const {
2116 for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
2117 mCountArray[n] = mSegments[n]->activeVoxelCount();
2118 }
2119 }
2120
2121 VolumePtrType * const mSegments;
2122 size_t * const mCountArray;
2123};
2124
2125
2126struct GreaterCount
2127{
2128 GreaterCount(const size_t *countArray) : mCountArray(countArray) {}
2129
2130 inline bool operator() (const size_t& lhs, const size_t& rhs) const
2131 {
2132 return (mCountArray[lhs] > mCountArray[rhs]);
2133 }
2134
2135 size_t const * const mCountArray;
2136};
2137
2138////////////////////////////////////////
2139
2140
2141template<typename TreeType>
2142struct GridOrTreeConstructor
2143{
2144 using TreeTypePtr = typename TreeType::Ptr;
2145 using BoolTreePtrType = typename TreeType::template ValueConverter<bool>::Type::Ptr;
2146
2147 static BoolTreePtrType constructMask(const TreeType&, BoolTreePtrType& maskTree)
2148 { return maskTree; }
2149 static TreeTypePtr construct(const TreeType&, TreeTypePtr& tree) { return tree; }
2150};
2151
2152
2153template<typename TreeType>
2154struct GridOrTreeConstructor<Grid<TreeType> >
2155{
2156 using GridType = Grid<TreeType>;
2157 using GridTypePtr = typename Grid<TreeType>::Ptr;
2158 using TreeTypePtr = typename TreeType::Ptr;
2159
2160 using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
2161 using BoolTreePtrType = typename BoolTreeType::Ptr;
2162 using BoolGridType = Grid<BoolTreeType>;
2163 using BoolGridPtrType = typename BoolGridType::Ptr;
2164
2165 static BoolGridPtrType constructMask(const GridType& grid, BoolTreePtrType& maskTree) {
2166 BoolGridPtrType maskGrid(BoolGridType::create(maskTree));
2167 maskGrid->setTransform(grid.transform().copy());
2168 return maskGrid;
2169 }
2170
2171 static GridTypePtr construct(const GridType& grid, TreeTypePtr& maskTree) {
2172 GridTypePtr maskGrid(GridType::create(maskTree));
2173 maskGrid->setTransform(grid.transform().copy());
2174 maskGrid->insertMeta(grid);
2175 return maskGrid;
2176 }
2177};
2178
2179
2180} // namespace level_set_util_internal
2181
2182
2183/// @endcond OPENVDB_DOCS_INTERNAL
2184
2185////////////////////////////////////////
2186
2187
2188template <class GridType>
2189void
2190sdfToFogVolume(GridType& grid, typename GridType::ValueType cutoffDistance)
2191{
2192 using ValueType = typename GridType::ValueType;
2193 using TreeType = typename GridType::TreeType;
2194 using LeafNodeType = typename TreeType::LeafNodeType;
2195 using RootNodeType = typename TreeType::RootNodeType;
2196 using NodeChainType = typename RootNodeType::NodeChainType;
2197 using InternalNodeType = typename NodeChainType::template Get<1>;
2198
2199 //////////
2200
2201 TreeType& tree = grid.tree();
2202
2203 size_t numLeafNodes = 0, numInternalNodes = 0;
2204
2205 std::vector<LeafNodeType*> nodes;
2206 std::vector<size_t> leafnodeCount;
2207
2208 {
2209 // Compute the prefix sum of the leafnode count in each internal node.
2210 std::vector<InternalNodeType*> internalNodes;
2211 tree.getNodes(internalNodes);
2212
2213 numInternalNodes = internalNodes.size();
2214
2215 leafnodeCount.push_back(0);
2216 for (size_t n = 0; n < numInternalNodes; ++n) {
2217 leafnodeCount.push_back(leafnodeCount.back() + internalNodes[n]->leafCount());
2218 }
2219
2220 numLeafNodes = leafnodeCount.back();
2221
2222 // Steal all leafnodes (Removes them from the tree and transfers ownership.)
2223 nodes.reserve(numLeafNodes);
2224
2225 for (size_t n = 0; n < numInternalNodes; ++n) {
2226 internalNodes[n]->stealNodes(nodes, tree.background(), false);
2227 }
2228
2229 // Clamp cutoffDistance to min sdf value
2230 ValueType minSDFValue = std::numeric_limits<ValueType>::max();
2231
2232 {
2233 level_set_util_internal::FindMinTileValue<InternalNodeType> minOp(internalNodes.data());
2234 tbb::parallel_reduce(tbb::blocked_range<size_t>(0, internalNodes.size()), minOp);
2235 minSDFValue = std::min(minSDFValue, minOp.minValue);
2236 }
2237
2238 if (minSDFValue > ValueType(0.0)) {
2239 level_set_util_internal::FindMinVoxelValue<LeafNodeType> minOp(nodes.data());
2240 tbb::parallel_reduce(tbb::blocked_range<size_t>(0, nodes.size()), minOp);
2241 minSDFValue = std::min(minSDFValue, minOp.minValue);
2242 }
2243
2244 cutoffDistance = -std::abs(cutoffDistance);
2245 cutoffDistance = minSDFValue > cutoffDistance ? minSDFValue : cutoffDistance;
2246 }
2247
2248 // Transform voxel values and delete leafnodes that are uniformly zero after the transformation.
2249 // (Positive values are set to zero with inactive state and negative values are remapped
2250 // from zero to one with active state.)
2251 tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()),
2252 level_set_util_internal::SDFVoxelsToFogVolume<LeafNodeType>(nodes.data(), cutoffDistance));
2253
2254 // Populate a new tree with the remaining leafnodes
2255 typename TreeType::Ptr newTree(new TreeType(ValueType(0.0)));
2256
2257 level_set_util_internal::PopulateTree<TreeType> populate(
2258 *newTree, nodes.data(), leafnodeCount.data(), 0);
2259 tbb::parallel_reduce(tbb::blocked_range<size_t>(0, numInternalNodes), populate);
2260
2261 // Transform tile values (Negative valued tiles are set to 1.0 with active state.)
2262 std::vector<InternalNodeType*> internalNodes;
2263 newTree->getNodes(internalNodes);
2264
2265 tbb::parallel_for(tbb::blocked_range<size_t>(0, internalNodes.size()),
2266 level_set_util_internal::SDFTilesToFogVolume<TreeType, InternalNodeType>(
2267 tree, internalNodes.data()));
2268
2269 {
2271
2272 typename TreeType::ValueAllIter it(*newTree);
2273 it.setMaxDepth(TreeType::ValueAllIter::LEAF_DEPTH - 2);
2274
2275 for ( ; it; ++it) {
2276 if (acc.getValue(it.getCoord()) < ValueType(0.0)) {
2277 it.setValue(ValueType(1.0));
2278 it.setActiveState(true);
2279 }
2280 }
2281 }
2282
2283 // Insert missing root level tiles. (The new tree is constructed from the remaining leafnodes
2284 // and will therefore not contain any root level tiles that may exist in the original tree.)
2285 {
2286 typename TreeType::ValueAllIter it(tree);
2287 it.setMaxDepth(TreeType::ValueAllIter::ROOT_DEPTH);
2288 for ( ; it; ++it) {
2289 if (it.getValue() < ValueType(0.0)) {
2290 newTree->addTile(TreeType::ValueAllIter::ROOT_LEVEL, it.getCoord(),
2291 ValueType(1.0), true);
2292 }
2293 }
2294 }
2295
2296 grid.setTree(newTree);
2297 grid.setGridClass(GRID_FOG_VOLUME);
2298}
2299
2300
2301////////////////////////////////////////
2302
2303
2304template <class GridOrTreeType>
2305typename GridOrTreeType::template ValueConverter<bool>::Type::Ptr
2306sdfInteriorMask(const GridOrTreeType& volume, typename GridOrTreeType::ValueType isovalue)
2307{
2308 using TreeType = typename TreeAdapter<GridOrTreeType>::TreeType;
2309 const TreeType& tree = TreeAdapter<GridOrTreeType>::tree(volume);
2310
2311 using BoolTreePtrType = typename TreeType::template ValueConverter<bool>::Type::Ptr;
2312 BoolTreePtrType mask = level_set_util_internal::computeInteriorMask(tree, isovalue);
2313
2314 return level_set_util_internal::GridOrTreeConstructor<GridOrTreeType>::constructMask(
2315 volume, mask);
2316}
2317
2318
2319template<typename GridOrTreeType>
2320typename GridOrTreeType::template ValueConverter<bool>::Type::Ptr
2321extractEnclosedRegion(const GridOrTreeType& volume,
2322 typename GridOrTreeType::ValueType isovalue,
2323 const typename TreeAdapter<GridOrTreeType>::TreeType::template ValueConverter<bool>::Type*
2324 fillMask)
2325{
2326 using TreeType = typename TreeAdapter<GridOrTreeType>::TreeType;
2327 const TreeType& tree = TreeAdapter<GridOrTreeType>::tree(volume);
2328
2329 using CharTreePtrType = typename TreeType::template ValueConverter<char>::Type::Ptr;
2330 CharTreePtrType regionMask = level_set_util_internal::computeEnclosedRegionMask(
2331 tree, isovalue, fillMask);
2332
2333 using BoolTreePtrType = typename TreeType::template ValueConverter<bool>::Type::Ptr;
2334 BoolTreePtrType mask = level_set_util_internal::computeInteriorMask(*regionMask, 0);
2335
2336 return level_set_util_internal::GridOrTreeConstructor<GridOrTreeType>::constructMask(
2337 volume, mask);
2338}
2339
2340
2341////////////////////////////////////////
2342
2343
2344template<typename GridOrTreeType>
2345typename GridOrTreeType::template ValueConverter<bool>::Type::Ptr
2346extractIsosurfaceMask(const GridOrTreeType& volume, typename GridOrTreeType::ValueType isovalue)
2347{
2348 using TreeType = typename TreeAdapter<GridOrTreeType>::TreeType;
2349 const TreeType& tree = TreeAdapter<GridOrTreeType>::tree(volume);
2350
2351 std::vector<const typename TreeType::LeafNodeType*> nodes;
2352 tree.getNodes(nodes);
2353
2354 using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
2355 typename BoolTreeType::Ptr mask(new BoolTreeType(false));
2356
2357 level_set_util_internal::MaskIsovalueCrossingVoxels<TreeType> op(tree, nodes, *mask, isovalue);
2358 tbb::parallel_reduce(tbb::blocked_range<size_t>(0, nodes.size()), op);
2359
2360 return level_set_util_internal::GridOrTreeConstructor<GridOrTreeType>::constructMask(
2361 volume, mask);
2362}
2363
2364
2365////////////////////////////////////////
2366
2367
2368template<typename GridOrTreeType>
2369void
2370extractActiveVoxelSegmentMasks(const GridOrTreeType& volume,
2371 std::vector<typename GridOrTreeType::template ValueConverter<bool>::Type::Ptr>& masks)
2372{
2373 using TreeType = typename TreeAdapter<GridOrTreeType>::TreeType;
2374 using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
2375 using BoolTreePtrType = typename BoolTreeType::Ptr;
2376 using BoolLeafNodeType = typename BoolTreeType::LeafNodeType;
2377 using BoolGridOrTreePtrType = typename GridOrTreeType::template ValueConverter<bool>::Type::Ptr;
2378
2379 using NodeMaskSegmentType = level_set_util_internal::NodeMaskSegment<BoolLeafNodeType>;
2380 using NodeMaskSegmentPtrType = typename NodeMaskSegmentType::Ptr;
2381 using NodeMaskSegmentPtrVector = typename std::vector<NodeMaskSegmentPtrType>;
2382 using NodeMaskSegmentRawPtrVector = typename std::vector<NodeMaskSegmentType*>;
2383
2384 /////
2385
2386 const TreeType& tree = TreeAdapter<GridOrTreeType>::tree(volume);
2387
2388 BoolTreeType topologyMask(tree, false, TopologyCopy());
2389
2390 // prune out any inactive leaf nodes or inactive tiles
2391 tools::pruneInactive(topologyMask);
2392
2393 if (topologyMask.hasActiveTiles()) {
2394 topologyMask.voxelizeActiveTiles();
2395 }
2396
2397 std::vector<BoolLeafNodeType*> leafnodes;
2398 topologyMask.getNodes(leafnodes);
2399
2400 if (leafnodes.empty()) return;
2401
2402 // 1. Split node masks into disjoint segments
2403 // Note: The LeafNode origin coord is modified to record the 'leafnodes' array offset.
2404
2405 std::unique_ptr<NodeMaskSegmentPtrVector[]> nodeSegmentArray(
2406 new NodeMaskSegmentPtrVector[leafnodes.size()]);
2407
2408 tbb::parallel_for(tbb::blocked_range<size_t>(0, leafnodes.size()),
2409 level_set_util_internal::SegmentNodeMask<BoolLeafNodeType>(
2410 leafnodes, nodeSegmentArray.get()));
2411
2412
2413 // 2. Compute segment connectivity
2414
2415 tbb::parallel_for(tbb::blocked_range<size_t>(0, leafnodes.size()),
2416 level_set_util_internal::ConnectNodeMaskSegments<BoolTreeType, BoolLeafNodeType>(
2417 topologyMask, nodeSegmentArray.get()));
2418
2419 topologyMask.clear();
2420
2421 size_t nodeSegmentCount = 0;
2422 for (size_t n = 0, N = leafnodes.size(); n < N; ++n) {
2423 nodeSegmentCount += nodeSegmentArray[n].size();
2424 }
2425
2426 // 3. Group connected segments
2427
2428 std::deque<NodeMaskSegmentRawPtrVector> nodeSegmentGroups;
2429
2430 NodeMaskSegmentType* nextSegment = nodeSegmentArray[0][0].get();
2431 while (nextSegment) {
2432
2433 nodeSegmentGroups.push_back(NodeMaskSegmentRawPtrVector());
2434
2435 std::vector<NodeMaskSegmentType*>& segmentGroup = nodeSegmentGroups.back();
2436 segmentGroup.reserve(nodeSegmentCount);
2437
2438 std::deque<NodeMaskSegmentType*> segmentQueue;
2439 segmentQueue.push_back(nextSegment);
2440 nextSegment = nullptr;
2441
2442 while (!segmentQueue.empty()) {
2443
2444 NodeMaskSegmentType* segment = segmentQueue.back();
2445 segmentQueue.pop_back();
2446
2447 if (segment->visited) continue;
2448 segment->visited = true;
2449
2450 segmentGroup.push_back(segment);
2451
2452 // queue connected segments
2453 std::vector<NodeMaskSegmentType*>& connections = segment->connections;
2454 for (size_t n = 0, N = connections.size(); n < N; ++n) {
2455 if (!connections[n]->visited) segmentQueue.push_back(connections[n]);
2456 }
2457 }
2458
2459 // find first unvisited segment
2460 for (size_t n = 0, N = leafnodes.size(); n < N; ++n) {
2461 NodeMaskSegmentPtrVector& nodeSegments = nodeSegmentArray[n];
2462 for (size_t i = 0, I = nodeSegments.size(); i < I; ++i) {
2463 if (!nodeSegments[i]->visited) nextSegment = nodeSegments[i].get();
2464 }
2465 }
2466 }
2467
2468 // 4. Mask segment groups
2469
2470 if (nodeSegmentGroups.size() == 1) {
2471
2472 BoolTreePtrType mask(new BoolTreeType(tree, false, TopologyCopy()));
2473
2474 tools::pruneInactive(*mask);
2475
2476 if (mask->hasActiveTiles()) {
2477 mask->voxelizeActiveTiles();
2478 }
2479
2480 masks.push_back(
2481 level_set_util_internal::GridOrTreeConstructor<GridOrTreeType>::constructMask(
2482 volume, mask));
2483
2484 } else if (nodeSegmentGroups.size() > 1) {
2485
2486 for (size_t n = 0, N = nodeSegmentGroups.size(); n < N; ++n) {
2487
2488 NodeMaskSegmentRawPtrVector& segmentGroup = nodeSegmentGroups[n];
2489
2490 level_set_util_internal::MaskSegmentGroup<BoolTreeType> op(segmentGroup);
2491 tbb::parallel_reduce(tbb::blocked_range<size_t>(0, segmentGroup.size()), op);
2492
2493 masks.push_back(
2494 level_set_util_internal::GridOrTreeConstructor<GridOrTreeType>::constructMask(
2495 volume, op.mask()));
2496 }
2497 }
2498
2499 // 5. Sort segments in descending order based on the active voxel count.
2500
2501 if (masks.size() > 1) {
2502 const size_t segmentCount = masks.size();
2503
2504 std::unique_ptr<size_t[]> segmentOrderArray(new size_t[segmentCount]);
2505 std::unique_ptr<size_t[]> voxelCountArray(new size_t[segmentCount]);
2506
2507 for (size_t n = 0; n < segmentCount; ++n) {
2508 segmentOrderArray[n] = n;
2509 }
2510
2511 tbb::parallel_for(tbb::blocked_range<size_t>(0, segmentCount),
2512 level_set_util_internal::ComputeActiveVoxelCount<BoolGridOrTreePtrType>(
2513 masks, voxelCountArray.get()));
2514
2515 size_t *begin = segmentOrderArray.get();
2516 tbb::parallel_sort(begin, begin + masks.size(), level_set_util_internal::GreaterCount(
2517 voxelCountArray.get()));
2518
2519 std::vector<BoolGridOrTreePtrType> orderedMasks;
2520 orderedMasks.reserve(masks.size());
2521
2522 for (size_t n = 0; n < segmentCount; ++n) {
2523 orderedMasks.push_back(masks[segmentOrderArray[n]]);
2524 }
2525
2526 masks.swap(orderedMasks);
2527 }
2528
2529} // extractActiveVoxelSegmentMasks()
2530
2531
2532template<typename GridOrTreeType>
2533void
2534segmentActiveVoxels(const GridOrTreeType& volume,
2535 std::vector<typename GridOrTreeType::Ptr>& segments)
2536{
2537 using TreeType = typename TreeAdapter<GridOrTreeType>::TreeType;
2538 using TreePtrType = typename TreeType::Ptr;
2539 using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
2540 using BoolTreePtrType = typename BoolTreeType::Ptr;
2541
2542 const TreeType& inputTree = TreeAdapter<GridOrTreeType>::tree(volume);
2543
2544 // 1. Segment active topology mask
2545 std::vector<BoolTreePtrType> maskSegmentArray;
2546 extractActiveVoxelSegmentMasks(inputTree, maskSegmentArray);
2547
2548 // 2. Export segments
2549
2550 const size_t numSegments = std::max(size_t(1), maskSegmentArray.size());
2551 std::vector<TreePtrType> outputSegmentArray(numSegments);
2552
2553 if (maskSegmentArray.empty()) {
2554 // if no active voxels in the original volume, copy just the background
2555 // value of the input tree
2556 outputSegmentArray[0] = TreePtrType(new TreeType(inputTree.background()));
2557 } else if (numSegments == 1) {
2558 // if there's only one segment with active voxels, copy the input tree
2559 TreePtrType segment(new TreeType(inputTree));
2560 // however, if the leaf counts do not match due to the pruning of inactive leaf
2561 // nodes in the mask, do a topology intersection to drop these inactive leafs
2562 if (segment->leafCount() != inputTree.leafCount()) {
2563 segment->topologyIntersection(*maskSegmentArray[0]);
2564 }
2565 outputSegmentArray[0] = segment;
2566 } else {
2567 const tbb::blocked_range<size_t> segmentRange(0, numSegments);
2568 tbb::parallel_for(segmentRange,
2569 level_set_util_internal::MaskedCopy<TreeType>(inputTree, outputSegmentArray,
2570 maskSegmentArray));
2571 }
2572
2573 for (auto& segment : outputSegmentArray) {
2574 segments.push_back(
2575 level_set_util_internal::GridOrTreeConstructor<GridOrTreeType>::construct(
2576 volume, segment));
2577 }
2578}
2579
2580
2581template<typename GridOrTreeType>
2582void
2583segmentSDF(const GridOrTreeType& volume, std::vector<typename GridOrTreeType::Ptr>& segments)
2584{
2585 using TreeType = typename TreeAdapter<GridOrTreeType>::TreeType;
2586 using TreePtrType = typename TreeType::Ptr;
2587 using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
2588 using BoolTreePtrType = typename BoolTreeType::Ptr;
2589
2590 const TreeType& inputTree = TreeAdapter<GridOrTreeType>::tree(volume);
2591
2592 // 1. Mask zero crossing voxels
2593 BoolTreePtrType mask = extractIsosurfaceMask(inputTree, lsutilGridZero<GridOrTreeType>());
2594
2595 // 2. Segment the zero crossing mask
2596 std::vector<BoolTreePtrType> maskSegmentArray;
2597 extractActiveVoxelSegmentMasks(*mask, maskSegmentArray);
2598
2599 const size_t numSegments = std::max(size_t(1), maskSegmentArray.size());
2600 std::vector<TreePtrType> outputSegmentArray(numSegments);
2601
2602 if (maskSegmentArray.empty()) {
2603 // if no active voxels in the original volume, copy just the background
2604 // value of the input tree
2605 outputSegmentArray[0] = TreePtrType(new TreeType(inputTree.background()));
2606 } else {
2607 const tbb::blocked_range<size_t> segmentRange(0, numSegments);
2608
2609 // 3. Expand zero crossing mask to capture sdf narrow band
2610 tbb::parallel_for(segmentRange,
2611 level_set_util_internal::ExpandNarrowbandMask<TreeType>(inputTree, maskSegmentArray));
2612
2613 // 4. Export sdf segments
2614
2615 tbb::parallel_for(segmentRange, level_set_util_internal::MaskedCopy<TreeType>(
2616 inputTree, outputSegmentArray, maskSegmentArray));
2617
2618 tbb::parallel_for(segmentRange,
2619 level_set_util_internal::FloodFillSign<TreeType>(inputTree, outputSegmentArray));
2620 }
2621
2622 for (auto& segment : outputSegmentArray) {
2623 segments.push_back(
2624 level_set_util_internal::GridOrTreeConstructor<GridOrTreeType>::construct(
2625 volume, segment));
2626 }
2627}
2628
2629
2630////////////////////////////////////////
2631
2632
2633template<class GridType>
2634void
2636 GridType& grid,
2637 bool removeDisconnectedInterior,
2638 bool rebuildNarrowBand,
2639 float halfWidth)
2640{
2641 using ValueType = typename GridType::ValueType;
2642 using TreeType = typename GridType::TreeType;
2643 using LeafNodeType = typename TreeType::LeafNodeType;
2644
2645 TreeType& distTree = grid.tree();
2646 const ValueType voxelSize = ValueType(grid.transform().voxelSize()[0]);
2647
2648 // Normalize: world-space distance to squared index-space distance.
2649 // The internal pipeline (traceExteriorBoundaries, ValidateIntersectingVoxels)
2650 // operates on squared distances where the boundary threshold 0.75 =
2651 // (sqrt(3)/2)^2 corresponds to the voxel half-diagonal.
2652 ValueType maxSqDist(0);
2653 {
2654 std::vector<LeafNodeType*> nodes;
2655 nodes.reserve(distTree.leafCount());
2656 distTree.getNodes(nodes);
2657
2658 const ValueType invVoxel = ValueType(1) / voxelSize;
2659
2660 tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()),
2661 [&](const tbb::blocked_range<size_t>& r) {
2662 for (size_t i = r.begin(); i != r.end(); ++i) {
2663 for (auto iter = nodes[i]->beginValueOn(); iter; ++iter) {
2664 ValueType d = *iter * invVoxel;
2665 ValueType sign = d < ValueType(0) ? ValueType(-1) : ValueType(1);
2666 iter.setValue(sign * d * d);
2667 }
2668 }
2669 });
2670
2671 const auto mm = minMax(distTree);
2672 maxSqDist = mm.max();
2673 }
2674
2675 // Trim wide bands before flood-fill for performance.
2676 // The squared 3-voxel half-diagonal in index-space units.
2677 const ValueType voxelDistSqLimit(0.75*9);
2678 if (maxSqDist > voxelDistSqLimit) {
2679 std::vector<LeafNodeType*> nodes;
2680 nodes.reserve(distTree.leafCount());
2681 distTree.getNodes(nodes);
2682
2683 tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()),
2684 [&](const tbb::blocked_range<size_t>& r) {
2685 for (size_t i = r.begin(); i != r.end(); ++i) {
2686 for (auto iter = nodes[i]->beginValueOn(); iter; ++iter) {
2687 if (std::abs(*iter) > voxelDistSqLimit) {
2688 nodes[i]->setValueOff(iter.pos());
2689 }
2690 }
2691 }
2692 });
2693
2694 pruneInactive(distTree, /*threading=*/true);
2695 }
2696
2697 // Sweep from exterior, negating reachable voxels. Stops at boundary
2698 // voxels (value <= 0.75).
2699 traceExteriorBoundaries(distTree);
2700
2701 // Remove interior narrow-band voxels not connected to the exterior.
2702 if (removeDisconnectedInterior) {
2703 std::vector<LeafNodeType*> nodes;
2704 nodes.reserve(distTree.leafCount());
2705 distTree.getNodes(nodes);
2706
2707 const tbb::blocked_range<size_t> nodeRange(0, nodes.size());
2708
2709 // Boundary voxels (0 < dist <= 0.75) with no negative neighbor
2710 // are disconnected from the exterior, push past the threshold.
2711 tbb::parallel_for(nodeRange,
2712 mesh_to_volume_internal::ValidateIntersectingVoxels<TreeType>(
2713 distTree, nodes));
2714
2715 // Deactivate voxels pushed past the boundary threshold.
2716 tbb::parallel_for(nodeRange,
2717 [&](const tbb::blocked_range<size_t>& r) {
2718 for (size_t i = r.begin(); i != r.end(); ++i) {
2719 for (auto iter = nodes[i]->beginValueOn(); iter; ++iter) {
2720 if (*iter > ValueType(0.75)) nodes[i]->setValueOff(iter.pos());
2721 }
2722 }
2723 });
2724
2725 pruneInactive(distTree, /*threading=*/true);
2726 }
2727
2728 // Denormalize: squared index-space to world-space signed distance.
2729 {
2730 std::vector<LeafNodeType*> nodes;
2731 nodes.reserve(distTree.leafCount());
2732 distTree.getNodes(nodes);
2733
2734 tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()),
2735 mesh_to_volume_internal::TransformValues<TreeType>(
2736 nodes, voxelSize, /*unsignedDist=*/false));
2737 }
2738
2739 // Set background and flood-fill sign into tile regions.
2740 const auto mm = minMax(distTree);
2741 const ValueType exteriorWidth = mm.max();
2742 const ValueType interiorWidth = mm.min();
2743
2744 distTree.root().setBackground(exteriorWidth, /*updateChildNodes=*/false);
2745 signedFloodFillWithValues(distTree, exteriorWidth, interiorWidth);
2746
2747 grid.setGridClass(GRID_LEVEL_SET);
2748
2749 // Optionally rebuild a symmetric narrow band via PDE renormalization.
2750 if (rebuildNarrowBand) {
2751 const int dilationCount = static_cast<int>(math::RoundUp(halfWidth));
2752 tools::dilateActiveValues(distTree, dilationCount,
2753 tools::NN_FACE, tools::PRESERVE_TILES);
2754
2755 util::NullInterrupter interrupter;
2756 LevelSetFilter<GridType, GridType, util::NullInterrupter> filter(grid, &interrupter);
2757#if 1
2758 filter.setSpatialScheme(math::FIRST_BIAS);
2759 filter.setTemporalScheme(math::TVD_RK1);
2760#else
2761 filter.setSpatialScheme(math::HJWENO5_BIAS);
2762 filter.setTemporalScheme(math::TVD_RK3);
2763#endif
2764 //filter.setSpatialScheme(math::FIRST_BIAS);// <-
2765 filter.setNormCount(dilationCount);
2766 filter.normalize();
2767 filter.prune();
2768
2769 const ValueType bandWidth = voxelSize * ValueType(halfWidth);
2770 tools::pruneLevelSet(distTree, bandWidth, -bandWidth);
2771 }
2772}// distanceFieldToSDF
2773
2774
2775////////////////////////////////////////
2776
2777
2778// Explicit Template Instantiation
2779
2780#ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION
2781
2782#ifdef OPENVDB_INSTANTIATE_LEVELSETUTIL
2784#endif
2785
2786#define _FUNCTION(TreeT) \
2787 void sdfToFogVolume(Grid<TreeT>&, TreeT::ValueType)
2789#undef _FUNCTION
2790
2791#define _FUNCTION(TreeT) \
2792 TreeT::ValueConverter<bool>::Type::Ptr sdfInteriorMask(const TreeT&, TreeT::ValueType)
2794#undef _FUNCTION
2795
2796#define _FUNCTION(TreeT) \
2797 Grid<TreeT>::ValueConverter<bool>::Type::Ptr sdfInteriorMask(const Grid<TreeT>&, TreeT::ValueType)
2799#undef _FUNCTION
2800
2801#define _FUNCTION(TreeT) \
2802 TreeT::ValueConverter<bool>::Type::Ptr extractEnclosedRegion(\
2803 const TreeT&, TreeT::ValueType, \
2804 const TreeAdapter<TreeT>::TreeType::ValueConverter<bool>::Type*)
2806#undef _FUNCTION
2807
2808#define _FUNCTION(TreeT) \
2809 Grid<TreeT>::ValueConverter<bool>::Type::Ptr extractEnclosedRegion(\
2810 const Grid<TreeT>&, TreeT::ValueType, \
2811 const TreeAdapter<Grid<TreeT>>::TreeType::ValueConverter<bool>::Type*)
2813#undef _FUNCTION
2814
2815#define _FUNCTION(TreeT) \
2816 TreeT::ValueConverter<bool>::Type::Ptr extractIsosurfaceMask(const TreeT&, TreeT::ValueType)
2818#undef _FUNCTION
2819
2820#define _FUNCTION(TreeT) \
2821 Grid<TreeT>::ValueConverter<bool>::Type::Ptr extractIsosurfaceMask(const Grid<TreeT>&, TreeT::ValueType)
2823#undef _FUNCTION
2824
2825#define _FUNCTION(TreeT) \
2826 void extractActiveVoxelSegmentMasks(\
2827 const TreeT&, std::vector<TreeT::ValueConverter<bool>::Type::Ptr>&)
2829#undef _FUNCTION
2830
2831#define _FUNCTION(TreeT) \
2832 void extractActiveVoxelSegmentMasks(\
2833 const Grid<TreeT>&, std::vector<Grid<TreeT>::ValueConverter<bool>::Type::Ptr>&)
2835#undef _FUNCTION
2836
2837#define _FUNCTION(TreeT) \
2838 void segmentActiveVoxels(const TreeT&, std::vector<TreeT::Ptr>&)
2840#undef _FUNCTION
2841
2842#define _FUNCTION(TreeT) \
2843 void segmentActiveVoxels(const Grid<TreeT>&, std::vector<Grid<TreeT>::Ptr>&)
2845#undef _FUNCTION
2846
2847#define _FUNCTION(TreeT) \
2848 void segmentSDF(const TreeT&, std::vector<TreeT::Ptr>&)
2850#undef _FUNCTION
2851
2852#define _FUNCTION(TreeT) \
2853 void segmentSDF(const Grid<TreeT>&, std::vector<Grid<TreeT>::Ptr>&)
2855#undef _FUNCTION
2856
2857#define _FUNCTION(TreeT) \
2858 void distanceFieldToSDF(Grid<TreeT>&, bool, bool, float)
2860#undef _FUNCTION
2861
2862#endif // OPENVDB_USE_EXPLICIT_INSTANTIATION
2863
2864
2865} // namespace tools
2866} // namespace OPENVDB_VERSION_NAME
2867} // namespace openvdb
2868
2869#endif // OPENVDB_TOOLS_LEVEL_SET_UTIL_HAS_BEEN_INCLUDED
Functions to count tiles, nodes or voxels in a grid.
Performs various types of level set deformations with interface tracking. These unrestricted deformat...
Convert polygonal meshes that consist of quads and/or triangles into signed or unsigned distance fiel...
Implementation of morphological dilation and erosion.
Attribute-owned data structure for points. Point attributes are stored in leaf nodes and ordered by v...
Defined various multi-threaded utility functions for trees.
Propagate the signs of distance values from the active voxels in the narrow band to the inactive valu...
Tag dispatch class that distinguishes topology copy constructors from deep copy constructors.
Definition Types.h:754
const ValueType & getValue(const Coord &xyz) const
Return the value of the voxel at the given coordinates.
Definition ValueAccessor.h:455
bool empty(const char *str)
tests if a c-string str is empty, that is its first value is '\0'
Definition Util.h:156
GridType
List of types that are currently supported by NanoVDB.
Definition NanoVDB.h:219
const std::enable_if<!VecTraits< T >::IsVec, T >::type & max(const T &a, const T &b)
Definition Composite.h:110
void signedFloodFillWithValues(TreeOrLeafManagerT &tree, const typename TreeOrLeafManagerT::ValueType &outsideWidth, const typename TreeOrLeafManagerT::ValueType &insideWidth, bool threaded=true, size_t grainSize=1, Index minLevel=0)
Set the values of all inactive voxels and tiles of a narrow-band level set from the signs of the acti...
Definition SignedFloodFill.h:253
GridOrTreeType::template ValueConverter< bool >::Type::Ptr extractEnclosedRegion(const GridOrTreeType &volume, typename GridOrTreeType::ValueType isovalue=lsutilGridZero< GridOrTreeType >(), const typename TreeAdapter< GridOrTreeType >::TreeType::template ValueConverter< bool >::Type *fillMask=nullptr)
Extracts the interior regions of a signed distance field and topologically enclosed (watertight) regi...
Definition LevelSetUtil.h:2321
void segmentActiveVoxels(const GridOrTreeType &volume, std::vector< typename GridOrTreeType::Ptr > &segments)
Separates disjoint active topology components into distinct grids or trees.
Definition LevelSetUtil.h:2534
math::MinMax< typename TreeT::ValueType > minMax(const TreeT &tree, bool threaded=true)
Return the minimum and maximum active values in this tree.
Definition Count.h:516
GridOrTreeType::template ValueConverter< bool >::Type::Ptr extractIsosurfaceMask(const GridOrTreeType &volume, typename GridOrTreeType::ValueType isovalue)
Return a mask of the voxels that intersect the implicit surface with the given isovalue.
Definition LevelSetUtil.h:2346
void extractActiveVoxelSegmentMasks(const GridOrTreeType &volume, std::vector< typename GridOrTreeType::template ValueConverter< bool >::Type::Ptr > &masks)
Return a mask for each connected component of the given grid's active voxels.
Definition LevelSetUtil.h:2370
void distanceFieldToSDF(GridType &grid, bool removeDisconnectedInterior=false, bool rebuildNarrowBand=true, float halfWidth=3.0f)
Convert a distance field (unsigned or signed) into a proper signed distance field / level set.
Definition LevelSetUtil.h:2635
GridOrTreeType::template ValueConverter< bool >::Type::Ptr sdfInteriorMask(const GridOrTreeType &volume, typename GridOrTreeType::ValueType isovalue=lsutilGridZero< GridOrTreeType >())
Threaded method to construct a boolean mask that represents interior regions in a signed distance fie...
Definition LevelSetUtil.h:2306
void sdfToFogVolume(GridType &grid, typename GridType::ValueType cutoffDistance=lsutilGridMax< GridType >())
Threaded method to convert a sparse level set/SDF into a sparse fog volume.
Definition LevelSetUtil.h:2190
void segmentSDF(const GridOrTreeType &volume, std::vector< typename GridOrTreeType::Ptr > &segments)
Separates disjoint SDF surfaces into distinct grids or trees.
Definition LevelSetUtil.h:2583
void pruneInactive(TreeT &tree, bool threaded=true, size_t grainSize=1)
Reduce the memory footprint of a tree by replacing with background tiles any nodes whose values are a...
Definition Prune.h:357
Definition PointDataGrid.h:170
ValueAccessorImpl< TreeType, IsSafe, MutexType, openvdb::make_index_sequence< CacheLevels > > ValueAccessor
Default alias for a ValueAccessor. This is simply a helper alias for the generic definition but takes...
Definition ValueAccessor.h:86
Index32 Index
Definition Types.h:34
@ GRID_FOG_VOLUME
Definition Types.h:527
openvdb::GridBase Grid
Definition Utils.h:43
Definition Exceptions.h:13
This adapter allows code that is templated on a Tree type to accept either a Tree type or a Grid type...
Definition Grid.h:1058
static NonConstTreeType & tree(NonConstTreeType &t)
Definition Grid.h:1074
_TreeType TreeType
Definition Grid.h:1059
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition version.h.in:121
#define OPENVDB_USE_VERSION_NAMESPACE
Definition version.h.in:284
#define OPENVDB_REAL_TREE_INSTANTIATE(Function)
Definition version.h.in:228
#define OPENVDB_ALL_TREE_INSTANTIATE(Function)
Definition version.h.in:232