OpenVDB 10.0.1
Loading...
Searching...
No Matches
LevelSetFilter.h
Go to the documentation of this file.
1// Copyright Contributors to the OpenVDB Project
2// SPDX-License-Identifier: MPL-2.0
3//
4/// @author Ken Museth
5///
6/// @file tools/LevelSetFilter.h
7///
8/// @brief Performs various types of level set deformations with
9/// interface tracking. These unrestricted deformations include
10/// surface smoothing (e.g., Laplacian flow), filtering (e.g., mean
11/// value) and morphological operations (e.g., morphological opening).
12/// All these operations can optionally be masked with another grid that
13/// acts as an alpha-mask.
14
15#ifndef OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED
16#define OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED
17
18#include "LevelSetTracker.h"
19#include "Interpolation.h"
20#include <algorithm> // for std::max()
21#include <functional>
22#include <type_traits>
23
24
25namespace openvdb {
27namespace OPENVDB_VERSION_NAME {
28namespace tools {
29
30/// @brief Filtering (e.g. diffusion) of narrow-band level sets. An
31/// optional scalar field can be used to produce a (smooth) alpha mask
32/// for the filtering.
33///
34/// @note This class performs proper interface tracking which allows
35/// for unrestricted surface deformations
36template<typename GridT,
37 typename MaskT = typename GridT::template ValueConverter<float>::Type,
38 typename InterruptT = util::NullInterrupter>
39class LevelSetFilter : public LevelSetTracker<GridT, InterruptT>
40{
41public:
43 using GridType = GridT;
44 using MaskType = MaskT;
45 using TreeType = typename GridType::TreeType;
46 using ValueType = typename TreeType::ValueType;
47 using AlphaType = typename MaskType::ValueType;
48 static_assert(std::is_floating_point<AlphaType>::value,
49 "LevelSetFilter requires a mask grid with floating-point values");
50
51 /// @brief Main constructor from a grid
52 /// @param grid The level set to be filtered.
53 /// @param interrupt Optional interrupter.
54 LevelSetFilter(GridType& grid, InterruptT* interrupt = nullptr)
55 : BaseType(grid, interrupt)
56 , mMinMask(0)
57 , mMaxMask(1)
58 , mInvertMask(false)
59 {
60 }
61 /// @brief Default destructor
62 ~LevelSetFilter() override {}
63
64 /// @brief Return the minimum value of the mask to be used for the
65 /// derivation of a smooth alpha value.
66 AlphaType minMask() const { return mMinMask; }
67 /// @brief Return the maximum value of the mask to be used for the
68 /// derivation of a smooth alpha value.
69 AlphaType maxMask() const { return mMaxMask; }
70 /// @brief Define the range for the (optional) scalar mask.
71 /// @param min Minimum value of the range.
72 /// @param max Maximum value of the range.
73 /// @details Mask values outside the range maps to alpha values of
74 /// respectfully zero and one, and values inside the range maps
75 /// smoothly to 0->1 (unless of course the mask is inverted).
76 /// @throw ValueError if @a min is not smaller than @a max.
78 {
79 if (!(min < max)) OPENVDB_THROW(ValueError, "Invalid mask range (expects min < max)");
80 mMinMask = min;
81 mMaxMask = max;
82 }
83
84 /// @brief Return true if the mask is inverted, i.e. min->max in the
85 /// original mask maps to 1->0 in the inverted alpha mask.
86 bool isMaskInverted() const { return mInvertMask; }
87 /// @brief Invert the optional mask, i.e. min->max in the original
88 /// mask maps to 1->0 in the inverted alpha mask.
89 void invertMask(bool invert=true) { mInvertMask = invert; }
90
91 /// @brief One iteration of mean-curvature flow of the level set.
92 /// @param mask Optional alpha mask.
93 void meanCurvature(const MaskType* mask = nullptr)
94 {
95 Filter f(this, mask); f.meanCurvature();
96 }
97
98 /// @brief One iteration of Laplacian flow of the level set.
99 /// @param mask Optional alpha mask.
100 void laplacian(const MaskType* mask = nullptr)
101 {
102 Filter f(this, mask); f.laplacian();
103 }
104
105 /// @brief One iteration of a fast separable Gaussian filter.
106 /// @param width Width of the Gaussian kernel in voxel units.
107 /// @param mask Optional alpha mask.
108 ///
109 /// @note This is approximated as 4 iterations of a separable mean filter
110 /// which typically leads an approximation that's better than 95%!
111 void gaussian(int width = 1, const MaskType* mask = nullptr)
112 {
113 Filter f(this, mask); f.gaussian(width);
114 }
115
116 /// @brief Offset the level set by the specified (world) distance.
117 /// @param offset Value of the offset.
118 /// @param mask Optional alpha mask.
119 void offset(ValueType offset, const MaskType* mask = nullptr)
120 {
121 Filter f(this, mask); f.offset(offset);
122 }
123
124 /// @brief One iteration of median-value flow of the level set.
125 /// @param width Width of the median-value kernel in voxel units.
126 /// @param mask Optional alpha mask.
127 ///
128 /// @warning This filter is not separable and is hence relatively
129 /// slow!
130 void median(int width = 1, const MaskType* mask = nullptr)
131 {
132 Filter f(this, mask); f.median(width);
133 }
134
135 /// @brief One iteration of mean-value flow of the level set.
136 /// @param width Width of the mean-value kernel in voxel units.
137 /// @param mask Optional alpha mask.
138 ///
139 /// @note This filter is separable so it's fast!
140 void mean(int width = 1, const MaskType* mask = nullptr)
141 {
142 Filter f(this, mask); f.mean(width);
143 }
144
145private:
146 // disallow copy construction and copy by assignment!
147 LevelSetFilter(const LevelSetFilter&);// not implemented
148 LevelSetFilter& operator=(const LevelSetFilter&);// not implemented
149
150 // Private struct that implements all the filtering.
151 struct Filter
152 {
153 using LeafT = typename TreeType::LeafNodeType;
154 using VoxelIterT = typename LeafT::ValueOnIter;
155 using VoxelCIterT = typename LeafT::ValueOnCIter;
156 using BufferT = typename tree::LeafManager<TreeType>::BufferType;
157 using LeafRange = typename tree::LeafManager<TreeType>::LeafRange;
158 using LeafIterT = typename LeafRange::Iterator;
159 using AlphaMaskT = tools::AlphaMask<GridT, MaskT>;
160
161 Filter(LevelSetFilter* parent, const MaskType* mask) : mParent(parent), mMask(mask) {}
162 Filter(const Filter&) = default;
163 ~Filter() {}
164
165 void box(int width);
166 void median(int width);
167 void mean(int width);
168 void gaussian(int width);
169 void laplacian();
170 void meanCurvature();
171 void offset(ValueType value);
172 void operator()(const LeafRange& r) const
173 {
174 if (mTask) mTask(const_cast<Filter*>(this), r);
175 else OPENVDB_THROW(ValueError, "task is undefined - don\'t call this method directly");
176 }
177 void cook(bool swap)
178 {
179 const int n = mParent->getGrainSize();
180 if (n>0) {
181 tbb::parallel_for(mParent->leafs().leafRange(n), *this);
182 } else {
183 (*this)(mParent->leafs().leafRange());
184 }
185 if (swap) mParent->leafs().swapLeafBuffer(1, n==0);
186 }
187
188 template <size_t Axis>
189 struct Avg {
190 Avg(const GridT& grid, Int32 w) :
191 acc(grid.tree()), width(w), frac(1/ValueType(2*w+1)) {}
193 {
194 ValueType sum = zeroVal<ValueType>();
195 Int32& i = xyz[Axis], j = i + width;
196 for (i -= width; i <= j; ++i) sum += acc.getValue(xyz);
197 return sum*frac;
198 }
199 typename GridT::ConstAccessor acc;
202 };
203
204 template<typename AvgT>
205 void boxImpl(const LeafRange& r, Int32 w);
206
207 void boxXImpl(const LeafRange& r, Int32 w) { this->boxImpl<Avg<0> >(r,w); }
208 void boxZImpl(const LeafRange& r, Int32 w) { this->boxImpl<Avg<1> >(r,w); }
209 void boxYImpl(const LeafRange& r, Int32 w) { this->boxImpl<Avg<2> >(r,w); }
210
211 void medianImpl(const LeafRange&, int);
212 void meanCurvatureImpl(const LeafRange&);
213 void laplacianImpl(const LeafRange&);
214 void offsetImpl(const LeafRange&, ValueType);
215
216 LevelSetFilter* mParent;
217 const MaskType* mMask;
218 typename std::function<void (Filter*, const LeafRange&)> mTask;
219 }; // end of private Filter struct
220
221 AlphaType mMinMask, mMaxMask;
222 bool mInvertMask;
223
224}; // end of LevelSetFilter class
225
226
227////////////////////////////////////////
228
229template<typename GridT, typename MaskT, typename InterruptT>
230inline void
231LevelSetFilter<GridT, MaskT, InterruptT>::Filter::median(int width)
232{
233 mParent->startInterrupter("Median-value flow of level set");
234
235 mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0);
236
237 mTask = std::bind(&Filter::medianImpl,
238 std::placeholders::_1, std::placeholders::_2, std::max(1, width));
239 this->cook(true);
240
241 mParent->track();
242
243 mParent->endInterrupter();
244}
245
246template<typename GridT, typename MaskT, typename InterruptT>
247inline void
248LevelSetFilter<GridT, MaskT, InterruptT>::Filter::mean(int width)
249{
250 mParent->startInterrupter("Mean-value flow of level set");
251
252 this->box(width);
253
254 mParent->endInterrupter();
255}
256
257template<typename GridT, typename MaskT, typename InterruptT>
258inline void
259LevelSetFilter<GridT, MaskT, InterruptT>::Filter::gaussian(int width)
260{
261 mParent->startInterrupter("Gaussian flow of level set");
262
263 for (int n=0; n<4; ++n) this->box(width);
264
265 mParent->endInterrupter();
266}
267
268template<typename GridT, typename MaskT, typename InterruptT>
269inline void
270LevelSetFilter<GridT, MaskT, InterruptT>::Filter::box(int width)
271{
272 mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0);
273
274 width = std::max(1, width);
275
276 mTask = std::bind(&Filter::boxXImpl, std::placeholders::_1, std::placeholders::_2, width);
277 this->cook(true);
278
279 mTask = std::bind(&Filter::boxYImpl, std::placeholders::_1, std::placeholders::_2, width);
280 this->cook(true);
281
282 mTask = std::bind(&Filter::boxZImpl, std::placeholders::_1, std::placeholders::_2, width);
283 this->cook(true);
284
285 mParent->track();
286}
287
288template<typename GridT, typename MaskT, typename InterruptT>
289inline void
290LevelSetFilter<GridT, MaskT, InterruptT>::Filter::meanCurvature()
291{
292 mParent->startInterrupter("Mean-curvature flow of level set");
293
294 mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0);
295
296 mTask = std::bind(&Filter::meanCurvatureImpl, std::placeholders::_1, std::placeholders::_2);
297 this->cook(true);
298
299 mParent->track();
300
301 mParent->endInterrupter();
302}
303
304template<typename GridT, typename MaskT, typename InterruptT>
305inline void
306LevelSetFilter<GridT, MaskT, InterruptT>::Filter::laplacian()
307{
308 mParent->startInterrupter("Laplacian flow of level set");
309
310 mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0);
311
312 mTask = std::bind(&Filter::laplacianImpl, std::placeholders::_1, std::placeholders::_2);
313 this->cook(true);
314
315 mParent->track();
316
317 mParent->endInterrupter();
318}
319
320template<typename GridT, typename MaskT, typename InterruptT>
321inline void
322LevelSetFilter<GridT, MaskT, InterruptT>::Filter::offset(ValueType value)
323{
324 mParent->startInterrupter("Offsetting level set");
325
326 mParent->leafs().removeAuxBuffers();// no auxiliary buffers required
327
328 const ValueType CFL = ValueType(0.5) * mParent->voxelSize(), offset = openvdb::math::Abs(value);
329 ValueType dist = 0.0;
330 while (offset-dist > ValueType(0.001)*CFL && mParent->checkInterrupter()) {
331 const ValueType delta = openvdb::math::Min(offset-dist, CFL);
332 dist += delta;
333
334 mTask = std::bind(&Filter::offsetImpl,
335 std::placeholders::_1, std::placeholders::_2, copysign(delta, value));
336 this->cook(false);
337
338 mParent->track();
339 }
340
341 mParent->endInterrupter();
342}
343
344
345///////////////////////// PRIVATE METHODS //////////////////////
346
347/// Performs parabolic mean-curvature diffusion
348template<typename GridT, typename MaskT, typename InterruptT>
349inline void
350LevelSetFilter<GridT, MaskT, InterruptT>::Filter::meanCurvatureImpl(const LeafRange& range)
351{
352 mParent->checkInterrupter();
353 //const float CFL = 0.9f, dt = CFL * mDx * mDx / 6.0f;
354 const ValueType dx = mParent->voxelSize(), dt = math::Pow2(dx) / ValueType(3.0);
355 math::CurvatureStencil<GridType> stencil(mParent->grid(), dx);
356 if (mMask) {
357 typename AlphaMaskT::FloatType a, b;
358 AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(),
359 mParent->maxMask(), mParent->isMaskInverted());
360 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
361 ValueType* buffer = leafIter.buffer(1).data();
362 for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
363 if (alpha(iter.getCoord(), a, b)) {
364 stencil.moveTo(iter);
365 const ValueType phi0 = *iter, phi1 = phi0 + dt*stencil.meanCurvatureNormGrad();
366 buffer[iter.pos()] = b * phi0 + a * phi1;
367 }
368 }
369 }
370 } else {
371 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
372 ValueType* buffer = leafIter.buffer(1).data();
373 for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
374 stencil.moveTo(iter);
375 buffer[iter.pos()] = *iter + dt*stencil.meanCurvatureNormGrad();
376 }
377 }
378 }
379}
380
381/// Performs Laplacian diffusion. Note if the grids contains a true
382/// signed distance field (e.g. a solution to the Eikonal equation)
383/// Laplacian diffusions (e.g. geometric heat equation) is actually
384/// identical to mean curvature diffusion, yet less computationally
385/// expensive! In other words if you're performing renormalization
386/// anyway (e.g. rebuilding the narrow-band) you should consider
387/// performing Laplacian diffusion over mean curvature flow!
388template<typename GridT, typename MaskT, typename InterruptT>
389inline void
390LevelSetFilter<GridT, MaskT, InterruptT>::Filter::laplacianImpl(const LeafRange& range)
391{
392 mParent->checkInterrupter();
393 //const float CFL = 0.9f, half_dt = CFL * mDx * mDx / 12.0f;
394 const ValueType dx = mParent->voxelSize(), dt = math::Pow2(dx) / ValueType(6.0);
395 math::GradStencil<GridType> stencil(mParent->grid(), dx);
396 if (mMask) {
397 typename AlphaMaskT::FloatType a, b;
398 AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(),
399 mParent->maxMask(), mParent->isMaskInverted());
400 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
401 ValueType* buffer = leafIter.buffer(1).data();
402 for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
403 if (alpha(iter.getCoord(), a, b)) {
404 stencil.moveTo(iter);
405 const ValueType phi0 = *iter, phi1 = phi0 + dt*stencil.laplacian();
406 buffer[iter.pos()] = b * phi0 + a * phi1;
407 }
408 }
409 }
410 } else {
411 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
412 ValueType* buffer = leafIter.buffer(1).data();
413 for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
414 stencil.moveTo(iter);
415 buffer[iter.pos()] = *iter + dt*stencil.laplacian();
416 }
417 }
418 }
419}
420
421/// Offsets the values by a constant
422template<typename GridT, typename MaskT, typename InterruptT>
423inline void
424LevelSetFilter<GridT, MaskT, InterruptT>::Filter::offsetImpl(
425 const LeafRange& range, ValueType offset)
426{
427 mParent->checkInterrupter();
428 if (mMask) {
429 typename AlphaMaskT::FloatType a, b;
430 AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(),
431 mParent->maxMask(), mParent->isMaskInverted());
432 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
433 for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) {
434 if (alpha(iter.getCoord(), a, b)) iter.setValue(*iter + a*offset);
435 }
436 }
437 } else {
438 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
439 for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) {
440 iter.setValue(*iter + offset);
441 }
442 }
443 }
444}
445
446/// Performs simple but slow median-value diffusion
447template<typename GridT, typename MaskT, typename InterruptT>
448inline void
449LevelSetFilter<GridT, MaskT, InterruptT>::Filter::medianImpl(const LeafRange& range, int width)
450{
451 mParent->checkInterrupter();
452 typename math::DenseStencil<GridType> stencil(mParent->grid(), width);//creates local cache!
453 if (mMask) {
454 typename AlphaMaskT::FloatType a, b;
455 AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(),
456 mParent->maxMask(), mParent->isMaskInverted());
457 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
458 ValueType* buffer = leafIter.buffer(1).data();
459 for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
460 if (alpha(iter.getCoord(), a, b)) {
461 stencil.moveTo(iter);
462 buffer[iter.pos()] = b * (*iter) + a * stencil.median();
463 }
464 }
465 }
466 } else {
467 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
468 ValueType* buffer = leafIter.buffer(1).data();
469 for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
470 stencil.moveTo(iter);
471 buffer[iter.pos()] = stencil.median();
472 }
473 }
474 }
475}
476
477/// One dimensional convolution of a separable box filter
478template<typename GridT, typename MaskT, typename InterruptT>
479template <typename AvgT>
480inline void
481LevelSetFilter<GridT, MaskT, InterruptT>::Filter::boxImpl(const LeafRange& range, Int32 w)
482{
483 mParent->checkInterrupter();
484 AvgT avg(mParent->grid(), w);
485 if (mMask) {
486 typename AlphaMaskT::FloatType a, b;
487 AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(),
488 mParent->maxMask(), mParent->isMaskInverted());
489 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
490 ValueType* buffer = leafIter.buffer(1).data();
491 for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
492 const Coord xyz = iter.getCoord();
493 if (alpha(xyz, a, b)) buffer[iter.pos()] = b * (*iter)+ a * avg(xyz);
494 }
495 }
496 } else {
497 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
498 ValueType* buffer = leafIter.buffer(1).data();
499 for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
500 buffer[iter.pos()] = avg(iter.getCoord());
501 }
502 }
503 }
504}
505
506
507////////////////////////////////////////
508
509
510// Explicit Template Instantiation
511
512#ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION
513
514#ifdef OPENVDB_INSTANTIATE_LEVELSETFILTER
516#endif
517
518OPENVDB_INSTANTIATE_CLASS LevelSetFilter<FloatGrid, FloatGrid, util::NullInterrupter>;
519OPENVDB_INSTANTIATE_CLASS LevelSetFilter<DoubleGrid, FloatGrid, util::NullInterrupter>;
520
521#endif // OPENVDB_USE_EXPLICIT_INSTANTIATION
522
523
524} // namespace tools
525} // namespace OPENVDB_VERSION_NAME
526} // namespace openvdb
527
528#endif // OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED
ValueT value
Definition: GridBuilder.h:1290
Performs multi-threaded interface tracking of narrow band level sets. This is the building-block for ...
Definition: Exceptions.h:65
Signed (x, y, z) 32-bit integer coordinates.
Definition: Coord.h:25
Definition: Interpolation.h:544
Volume filtering (e.g., diffusion) with optional alpha masking.
Definition: Filter.h:46
Filtering (e.g. diffusion) of narrow-band level sets. An optional scalar field can be used to produce...
Definition: LevelSetFilter.h:40
void laplacian(const MaskType *mask=nullptr)
One iteration of Laplacian flow of the level set.
Definition: LevelSetFilter.h:100
typename TreeType::ValueType ValueType
Definition: LevelSetFilter.h:46
void gaussian(int width=1, const MaskType *mask=nullptr)
One iteration of a fast separable Gaussian filter.
Definition: LevelSetFilter.h:111
typename MaskType::ValueType AlphaType
Definition: LevelSetFilter.h:47
AlphaType minMask() const
Return the minimum value of the mask to be used for the derivation of a smooth alpha value.
Definition: LevelSetFilter.h:66
void meanCurvature(const MaskType *mask=nullptr)
One iteration of mean-curvature flow of the level set.
Definition: LevelSetFilter.h:93
void invertMask(bool invert=true)
Invert the optional mask, i.e. min->max in the original mask maps to 1->0 in the inverted alpha mask.
Definition: LevelSetFilter.h:89
void median(int width=1, const MaskType *mask=nullptr)
One iteration of median-value flow of the level set.
Definition: LevelSetFilter.h:130
typename GridType::TreeType TreeType
Definition: LevelSetFilter.h:45
void setMaskRange(AlphaType min, AlphaType max)
Define the range for the (optional) scalar mask.
Definition: LevelSetFilter.h:77
void mean(int width=1, const MaskType *mask=nullptr)
One iteration of mean-value flow of the level set.
Definition: LevelSetFilter.h:140
AlphaType maxMask() const
Return the maximum value of the mask to be used for the derivation of a smooth alpha value.
Definition: LevelSetFilter.h:69
void offset(ValueType offset, const MaskType *mask=nullptr)
Offset the level set by the specified (world) distance.
Definition: LevelSetFilter.h:119
MaskT MaskType
Definition: LevelSetFilter.h:44
bool isMaskInverted() const
Return true if the mask is inverted, i.e. min->max in the original mask maps to 1->0 in the inverted ...
Definition: LevelSetFilter.h:86
LevelSetFilter(GridType &grid, InterruptT *interrupt=nullptr)
Main constructor from a grid.
Definition: LevelSetFilter.h:54
~LevelSetFilter() override
Default destructor.
Definition: LevelSetFilter.h:62
GridT GridType
Definition: LevelSetFilter.h:43
Performs multi-threaded interface tracking of narrow band level sets.
Definition: LevelSetTracker.h:58
typename LeafManagerType::LeafRange LeafRange
Definition: LevelSetTracker.h:67
typename CopyConstness< TreeType, NonConstBufferType >::Type BufferType
Definition: LeafManager.h:95
int32_t Int32
Definition: Types.h:56
Definition: Exceptions.h:13
#define OPENVDB_THROW(exception, message)
Definition: Exceptions.h:74
GridT::ConstAccessor acc
Definition: LevelSetFilter.h:199
ValueType operator()(Coord xyz)
Definition: LevelSetFilter.h:192
const ValueType frac
Definition: LevelSetFilter.h:201
Avg(const GridT &grid, Int32 w)
Definition: LevelSetFilter.h:190
const Int32 width
Definition: LevelSetFilter.h:200
#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:212
#define OPENVDB_INSTANTIATE_CLASS
Definition: version.h.in:153