OpenVDB  1.2.0
ParticlesToLevelSet.h
Go to the documentation of this file.
1 //
3 // Copyright (c) 2012-2013 DreamWorks Animation LLC
4 //
5 // All rights reserved. This software is distributed under the
6 // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ )
7 //
8 // Redistributions of source code must retain the above copyright
9 // and license notice and the following restrictions and disclaimer.
10 //
11 // * Neither the name of DreamWorks Animation nor the names of
12 // its contributors may be used to endorse or promote products derived
13 // from this software without specific prior written permission.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
20 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE
27 // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00.
28 //
30 //
96 
97 #ifndef OPENVDB_TOOLS_PARTICLES_TO_LEVELSET_HAS_BEEN_INCLUDED
98 #define OPENVDB_TOOLS_PARTICLES_TO_LEVELSET_HAS_BEEN_INCLUDED
99 
100 #include <tbb/parallel_reduce.h>
101 #include <tbb/blocked_range.h>
102 #include <boost/bind.hpp>
103 #include <boost/function.hpp>
104 #include <boost/type_traits/is_floating_point.hpp>
105 #include <boost/utility/enable_if.hpp>
106 #include <boost/mpl/if.hpp>
107 #include <openvdb/util/Util.h>
108 #include <openvdb/Types.h>
109 #include <openvdb/Grid.h>
110 #include <openvdb/math/Math.h>
111 #include <openvdb/math/Transform.h>
112 #include <openvdb/util/NullInterrupter.h>
113 #include "Composite.h" // for csgUnion()
114 
115 namespace openvdb {
117 namespace OPENVDB_VERSION_NAME {
118 namespace tools {
119 
120 
121 // This is a simple type that combines a distance value and a particle
122 // attribute. It's required for attribute transfer which is performed
123 // in the Raster class below.
124 namespace local {template <typename VisibleT, typename BlindT> class BlindData;}
125 
126 template<typename SdfGridT,
127  typename AttributeT = void,
128  typename InterrupterT = util::NullInterrupter>
130 {
131 public:
132 
133  typedef typename boost::is_void<AttributeT>::type DisableT;
134  typedef InterrupterT InterrupterType;
135 
136  typedef SdfGridT SdfGridType;
137  typedef typename SdfGridT::ValueType SdfType;
138 
139  typedef typename boost::mpl::if_<DisableT, size_t, AttributeT>::type AttType;
140  typedef typename SdfGridT::template ValueConverter<AttType>::Type AttGridType;
141 
142  BOOST_STATIC_ASSERT(boost::is_floating_point<SdfType>::value);
143 
165  explicit ParticlesToLevelSet(SdfGridT& grid, InterrupterT* interrupt = NULL);
166 
168  ~ParticlesToLevelSet() { delete mBlindGrid; }
169 
177  void finalize();
178 
184  typename AttGridType::Ptr attributeGrid() { return mAttGrid; }
185 
187  Real getVoxelSize() const { return mDx; }
188 
190  Real getHalfWidth() const { return mHalfWidth; }
191 
193  Real getRmin() const { return mRmin; }
195  Real getRmax() const { return mRmax; }
196 
198  bool ignoredParticles() const { return mMinCount>0 || mMaxCount>0; }
200  size_t getMinCount() const { return mMinCount; }
202  size_t getMaxCount() const { return mMaxCount; }
203 
205  void setRmin(Real Rmin) { mRmin = math::Max(Real(0),Rmin); }
207  void setRmax(Real Rmax) { mRmax = math::Max(mRmin,Rmax); }
208 
210  int getGrainSize() const { return mGrainSize; }
213  void setGrainSize(int grainSize) { mGrainSize = grainSize; }
214 
219  template <typename ParticleListT>
220  void rasterizeSpheres(const ParticleListT& pa);
221 
227  template <typename ParticleListT>
228  void rasterizeSpheres(const ParticleListT& pa, Real radius);
229 
246  template <typename ParticleListT>
247  void rasterizeTrails(const ParticleListT& pa, Real delta=1.0);
248 
249 private:
250 
251  typedef local::BlindData<SdfType, AttType> BlindType;
252  typedef typename SdfGridT::template ValueConverter<BlindType>::Type BlindGridType;
253 
255  template<typename ParticleListT, typename GridT> struct Raster;
256 
257  SdfGridType* mSdfGrid;
258  typename AttGridType::Ptr mAttGrid;
259  BlindGridType* mBlindGrid;
260  InterrupterT* mInterrupter;
261  Real mDx, mHalfWidth;
262  Real mRmin, mRmax;//ignore particles outside this range of radii in voxel
263  size_t mMinCount, mMaxCount;//counters for ignored particles!
264  int mGrainSize;
265 
266 };//end of ParticlesToLevelSet class
267 
268 template<typename SdfGridT, typename AttributeT, typename InterrupterT>
269 inline ParticlesToLevelSet<SdfGridT, AttributeT, InterrupterT>::
270 ParticlesToLevelSet(SdfGridT& grid, InterrupterT* interrupter) :
271  mSdfGrid(&grid),
272  mBlindGrid(NULL),
273  mInterrupter(interrupter),
274  mDx(grid.voxelSize()[0]),
275  mHalfWidth(grid.background()/mDx),
276  mRmin(1.5),// corresponds to the Nyquist grid sampling frequency
277  mRmax(100.0),// corresponds to a huge particle (probably too large!)
278  mMinCount(0),
279  mMaxCount(0),
280  mGrainSize(1)
281 {
282  if (!mSdfGrid->hasUniformVoxels() ) {
284  "ParticlesToLevelSet only supports uniform voxels!");
285  }
286  if (mSdfGrid->getGridClass() != GRID_LEVEL_SET) {
288  "ParticlesToLevelSet only supports level sets!"
289  "\nUse Grid::setGridClass(openvdb::GRID_LEVEL_SET)");
290  }
291 
292  if (!DisableT::value) {
293  mBlindGrid = new BlindGridType(BlindType(grid.background()));
294  mBlindGrid->setTransform(mSdfGrid->transform().copy());
295  }
296 }
297 
298 template<typename SdfGridT, typename AttributeT, typename InterrupterT>
299 template <typename ParticleListT>
301 rasterizeSpheres(const ParticleListT& pa)
302 {
303  if (DisableT::value) {
304  Raster<ParticleListT, SdfGridT> r(*this, mSdfGrid, pa);
305  r.rasterizeSpheres();
306  } else {
307  Raster<ParticleListT, BlindGridType> r(*this, mBlindGrid, pa);
308  r.rasterizeSpheres();
309  }
310 }
311 
312 template<typename SdfGridT, typename AttributeT, typename InterrupterT>
313 template <typename ParticleListT>
315 rasterizeSpheres(const ParticleListT& pa, Real radius)
316 {
317  if (DisableT::value) {
318  Raster<ParticleListT, SdfGridT> r(*this, mSdfGrid, pa);
319  r.rasterizeSpheres(radius/mDx);
320  } else {
321  Raster<ParticleListT, BlindGridType> r(*this, mBlindGrid, pa);
322  r.rasterizeSpheres(radius/mDx);
323  }
324 }
325 
326 template<typename SdfGridT, typename AttributeT, typename InterrupterT>
327 template <typename ParticleListT>
329 rasterizeTrails(const ParticleListT& pa, Real delta)
330 {
331  if (DisableT::value) {
332  Raster<ParticleListT, SdfGridT> r(*this, mSdfGrid, pa);
333  r.rasterizeTrails(delta);
334  } else {
335  Raster<ParticleListT, BlindGridType> r(*this, mBlindGrid, pa);
336  r.rasterizeTrails(delta);
337  }
338 }
339 
340 template<typename SdfGridT, typename AttributeT, typename InterrupterT>
343 {
344  if (mBlindGrid==NULL) return;
345 
346  typedef typename SdfGridType::TreeType SdfTreeT;
347  typedef typename AttGridType::TreeType AttTreeT;
348  typedef typename BlindGridType::TreeType BlindTreeT;
349  // Use topology copy constructors since output grids have the same topology as mBlindDataGrid
350  const BlindTreeT& tree = mBlindGrid->tree();
351 
352  // New level set tree
353  typename SdfTreeT::Ptr sdfTree(new SdfTreeT(tree, tree.background().visible(), openvdb::TopologyCopy()));
354 
355  // Note this overwrites any existing attribute grids!
356  typename AttTreeT::Ptr attTree(new AttTreeT(tree, tree.background().blind(), openvdb::TopologyCopy()));
357  mAttGrid = typename AttGridType::Ptr(new AttGridType(attTree));
358  mAttGrid->setTransform(mBlindGrid->transform().copy());
359 
360  // Extract the level set and IDs from mBlindDataGrid. We will
361  // explore the fact that by design active values always live
362  // at the leaf node level, i.e. no active tiles exist in level sets
363  typedef typename BlindTreeT::LeafCIter LeafIterT;
364  typedef typename BlindTreeT::LeafNodeType LeafT;
365  typedef typename SdfTreeT::LeafNodeType SdfLeafT;
366  typedef typename AttTreeT::LeafNodeType AttLeafT;
367  for (LeafIterT n = tree.cbeginLeaf(); n; ++n) {
368  const LeafT& leaf = *n;
369  const openvdb::Coord xyz = leaf.getOrigin();
370  // Get leafnodes that were allocated during topology contruction!
371  SdfLeafT* sdfLeaf = sdfTree->probeLeaf(xyz);
372  AttLeafT* attLeaf = attTree->probeLeaf(xyz);
373  for (typename LeafT::ValueOnCIter m=leaf.cbeginValueOn(); m; ++m) {
374  // Use linear offset (vs coordinate) access for better performance!
375  const openvdb::Index k = m.pos();
376  const BlindType& v = *m;
377  sdfLeaf->setValueOnly(k, v.visible());
378  attLeaf->setValueOnly(k, v.blind());
379  }
380  }
381  sdfTree->signedFloodFill();//required since we only transferred active voxels!
382 
383  if (mSdfGrid->empty()) {
384  mSdfGrid->setTree(sdfTree);
385  } else {
386  tools::csgUnion(mSdfGrid->tree(), *sdfTree, /*prune=*/true);
387  }
388 }
389 
391 
392 template<typename SdfGridT, typename AttributeT, typename InterrupterT>
393 template<typename ParticleListT, typename GridT>
394 struct ParticlesToLevelSet<SdfGridT, AttributeT, InterrupterT>::Raster
395 {
396  typedef typename boost::is_void<AttributeT>::type DisableT;
397  typedef ParticlesToLevelSet<SdfGridT, AttributeT, InterrupterT> ParticlesToLevelSetT;
398  typedef typename ParticlesToLevelSetT::SdfType SdfT;//type of signed distance values
399  typedef typename ParticlesToLevelSetT::AttType AttT;//type of particle attribute
400  typedef typename GridT::ValueType ValueT;
401  typedef typename GridT::Accessor AccessorT;
402 
404  Raster(ParticlesToLevelSetT& parent, GridT* grid, const ParticleListT& particles)
405  : mParent(parent),
406  mParticles(particles),
407  mGrid(grid),
408  mMap(*(mGrid->transform().baseMap())),
409  mMinCount(0),
410  mMaxCount(0),
411  mOwnsGrid(false)
412  {
413  }
414 
416  Raster(Raster& other, tbb::split)
417  : mParent(other.mParent),
418  mParticles(other.mParticles),
419  mGrid(new GridT(*other.mGrid, openvdb::ShallowCopy())),
420  mMap(other.mMap),
421  mMinCount(0),
422  mMaxCount(0),
423  mTask(other.mTask),
424  mOwnsGrid(true)
425  {
426  mGrid->newTree();
427  }
428 
429  virtual ~Raster() { if (mOwnsGrid) delete mGrid; }
430 
433  void rasterizeSpheres()
434  {
435  mMinCount = mMaxCount = 0;
436  if (mParent.mInterrupter) mParent.mInterrupter->start("Rasterizing particles to level set using spheres");
437  mTask = boost::bind(&Raster::rasterSpheres, _1, _2);
438  this->cook();
439  if (mParent.mInterrupter) mParent.mInterrupter->end();
440  }
444  void rasterizeSpheres(Real radius)
445  {
446  mMinCount = radius < mParent.mRmin ? mParticles.size() : 0;
447  mMaxCount = radius > mParent.mRmax ? mParticles.size() : 0;
448  if (mMinCount>0 || mMaxCount>0) {//skipping all particles!
449  mParent.mMinCount = mMinCount;
450  mParent.mMaxCount = mMaxCount;
451  } else {
452  if (mParent.mInterrupter) mParent.mInterrupter->start("Rasterizing particles to level set using const spheres");
453  mTask = boost::bind(&Raster::rasterFixedSpheres, _1, _2, SdfT(radius));
454  this->cook();
455  if (mParent.mInterrupter) mParent.mInterrupter->end();
456  }
457  }
472  void rasterizeTrails(Real delta=1.0)
473  {
474  mMinCount = mMaxCount = 0;
475  if (mParent.mInterrupter) mParent.mInterrupter->start("Rasterizing particles to level set using trails");
476  mTask = boost::bind(&Raster::rasterTrails, _1, _2, SdfT(delta));
477  this->cook();
478  if (mParent.mInterrupter) mParent.mInterrupter->end();
479  }
480 
482  void operator()(const tbb::blocked_range<size_t>& r)
483  {
484  assert(mTask);
485  mTask(this, r);
486  mParent.mMinCount = mMinCount;
487  mParent.mMaxCount = mMaxCount;
488  }
489 
491  void join(Raster& other)
492  {
493  tools::csgUnion(*mGrid, *other.mGrid, /*prune=*/true);
494  mMinCount += other.mMinCount;
495  mMaxCount += other.mMaxCount;
496  }
497 private:
499  Raster& operator=(const Raster& other) { return *this; }
500 
502  bool ignoreParticle(SdfT R)
503  {
504  if (R < mParent.mRmin) {// below the cutoff radius
505  ++mMinCount;
506  return true;
507  }
508  if (R > mParent.mRmax) {// above the cutoff radius
509  ++mMaxCount;
510  return true;
511  }
512  return false;
513  }
518  void rasterSpheres(const tbb::blocked_range<size_t>& r)
519  {
520  AccessorT acc = mGrid->getAccessor(); // local accessor
521  bool run = true;
522  const SdfT invDx = 1/mParent.mDx;
523  AttT att;
524  Vec3R pos;
525  Real rad;
526  for (Index32 id = r.begin(), e=r.end(); run && id != e; ++id) {
527  mParticles.getPosRad(id, pos, rad);
528  const SdfT R = invDx * rad;// in voxel units
529  if (this->ignoreParticle(R)) continue;
530  const Vec3R P = mMap.applyInverseMap(pos);
531  this->getAtt<DisableT>(id, att);
532  run = this->makeSphere(P, R, att, acc);
533  }//end loop over particles
534  }
539  void rasterFixedSpheres(const tbb::blocked_range<size_t>& r, SdfT R)
540  {
541  const SdfT dx = mParent.mDx, w = mParent.mHalfWidth;// in voxel units
542  AccessorT acc = mGrid->getAccessor(); // local accessor
543  const ValueT inside = -mGrid->background();
544  const SdfT max = R + w;// maximum distance in voxel units
545  const SdfT max2 = math::Pow2(max);//square of maximum distance in voxel units
546  const SdfT min2 = math::Pow2(math::Max(SdfT(0), R - w));//square of minimum distance
547  ValueT v;
548  size_t count = 0;
549  AttT att;
550  Vec3R pos;
551  for (size_t id = r.begin(), e=r.end(); id != e; ++id) {
552  this->getAtt<DisableT>(id, att);
553  mParticles.getPos(id, pos);
554  const Vec3R P = mMap.applyInverseMap(pos);
555  const Coord a(math::Floor(P[0]-max),math::Floor(P[1]-max),math::Floor(P[2]-max));
556  const Coord b(math::Ceil( P[0]+max),math::Ceil( P[1]+max),math::Ceil( P[2]+max));
557  for ( Coord c = a; c.x() <= b.x(); ++c.x() ) {
558  //only check interrupter every 32'th scan in x
559  if (!(count++ & (1<<5)-1) && util::wasInterrupted(mParent.mInterrupter)) {
560  tbb::task::self().cancel_group_execution();
561  return;
562  }
563  SdfT x2 = math::Pow2( c.x() - P[0] );
564  for ( c.y() = a.y(); c.y() <= b.y(); ++c.y() ) {
565  SdfT x2y2 = x2 + math::Pow2( c.y() - P[1] );
566  for ( c.z() = a.z(); c.z() <= b.z(); ++c.z() ) {
567  SdfT x2y2z2 = x2y2 + math::Pow2( c.z() - P[2] );//square distance from c to P
568  if ( x2y2z2 >= max2 || (!acc.probeValue(c,v) && v<ValueT(0)) )
569  continue;//outside narrow band of the particle or inside existing level set
570  if ( x2y2z2 <= min2 ) {//inside narrow band of the particle.
571  acc.setValueOff(c, inside);
572  continue;
573  }
574  // convert signed distance from voxel units to world units
575  const ValueT d=Merge(dx*(math::Sqrt(x2y2z2) - R), att);
576  if (d < v) acc.setValue(c, d);//CSG union
577  }//end loop over z
578  }//end loop over y
579  }//end loop over x
580  }//end loop over particles
581  }
586  void rasterTrails(const tbb::blocked_range<size_t>& r, SdfT delta)
587  {
588  AccessorT acc = mGrid->getAccessor(); // local accessor
589  bool run = true;
590  AttT att;
591  Vec3R pos, vel;
592  Real rad;
593  const Vec3R origin = mMap.applyInverseMap(Vec3R(0,0,0));
594  const SdfT Rmin = mParent.mRmin, invDx = 1/mParent.mDx;
595  for (size_t id = r.begin(), e=r.end(); run && id != e; ++id) {
596  mParticles.getPosRadVel(id, pos, rad, vel);
597  const SdfT R0 = invDx*rad;
598  if (this->ignoreParticle(R0)) continue;
599  this->getAtt<DisableT>(id, att);
600  const Vec3R P0 = mMap.applyInverseMap(pos);
601  const Vec3R V = mMap.applyInverseMap(vel) - origin;//exclude translation
602  const SdfT speed = V.length(), inv_speed=1.0/speed;
603  const Vec3R N = -V*inv_speed;// inverse normalized direction
604  Vec3R P = P0;// local position of instance
605  SdfT R = R0, d=0;// local radius and length of trail
606  for (size_t m=0; run && d <= speed ; ++m) {
607  run = this->makeSphere(P, R, att, acc);
608  P += 0.5*delta*R*N;// adaptive offset along inverse velocity direction
609  d = (P-P0).length();// current length of trail
610  R = R0-(R0-Rmin)*d*inv_speed;// R = R0 -> mRmin(e.g. 1.5)
611  }//end loop over sphere instances
612  }//end loop over particles
613  }
614 
615  void cook()
616  {
617  if (mParent.mGrainSize>0) {
618  tbb::parallel_reduce(tbb::blocked_range<size_t>(0,mParticles.size(),mParent.mGrainSize), *this);
619  } else {
620  (*this)(tbb::blocked_range<size_t>(0, mParticles.size()));
621  }
622  }
623 
639  bool makeSphere(const Vec3R &P, SdfT R, const AttT& att, AccessorT& acc)
640  {
641  const ValueT inside = -mGrid->background();
642  const SdfT dx = mParent.mDx, w = mParent.mHalfWidth;
643  const SdfT max = R + w;// maximum distance in voxel units
644  const Coord a(math::Floor(P[0]-max),math::Floor(P[1]-max),math::Floor(P[2]-max));
645  const Coord b(math::Ceil( P[0]+max),math::Ceil( P[1]+max),math::Ceil( P[2]+max));
646  const SdfT max2 = math::Pow2(max);//square of maximum distance in voxel units
647  const SdfT min2 = math::Pow2(math::Max(SdfT(0), R - w));//square of minimum distance
648  ValueT v;
649  size_t count = 0;
650  for ( Coord c = a; c.x() <= b.x(); ++c.x() ) {
651  //only check interrupter every 32'th scan in x
652  if (!(count++ & (1<<5)-1) && util::wasInterrupted(mParent.mInterrupter)) {
653  tbb::task::self().cancel_group_execution();
654  return false;
655  }
656  SdfT x2 = math::Pow2( c.x() - P[0] );
657  for ( c.y() = a.y(); c.y() <= b.y(); ++c.y() ) {
658  SdfT x2y2 = x2 + math::Pow2( c.y() - P[1] );
659  for ( c.z() = a.z(); c.z() <= b.z(); ++c.z() ) {
660  SdfT x2y2z2 = x2y2 + math::Pow2( c.z() - P[2] );//square distance from c to P
661  if ( x2y2z2 >= max2 || (!acc.probeValue(c,v) && v<ValueT(0)) )
662  continue;//outside narrow band of the particle or inside existing level set
663  if ( x2y2z2 <= min2 ) {//inside narrow band of the particle.
664  acc.setValueOff(c, inside);
665  continue;
666  }
667  // convert signed distance from voxel units to world units
668  //const ValueT d=dx*(math::Sqrt(x2y2z2) - R);
669  const ValueT d=Merge(dx*(math::Sqrt(x2y2z2) - R), att);
670  if (d < v) acc.setValue(c, d);//CSG union
671  }//end loop over z
672  }//end loop over y
673  }//end loop over x
674  return true;
675  }
676  typedef typename boost::function<void (Raster*, const tbb::blocked_range<size_t>&)> FuncType;
677 
678  template <typename DisableType>
679  typename boost::enable_if<DisableType>::type
680  getAtt(size_t, AttT&) const {;}
681 
682  template <typename DisableType>
683  typename boost::disable_if<DisableType>::type
684  getAtt(size_t n, AttT& a) const {mParticles.getAtt(n, a);}
685 
686  template <typename T>
687  typename boost::enable_if<boost::is_same<T,ValueT>, ValueT>::type
688  Merge(T s, const AttT&) const { return s; }
689 
690  template <typename T>
691  typename boost::disable_if<boost::is_same<T,ValueT>, ValueT>::type
692  Merge(T s, const AttT& a) const { return ValueT(s,a); }
693 
694  ParticlesToLevelSetT& mParent;
695  const ParticleListT& mParticles;//list of particles
696  GridT* mGrid;
697  const math::MapBase& mMap;
698  size_t mMinCount, mMaxCount;//counters for ignored particles!
699  FuncType mTask;
700  const bool mOwnsGrid;
701 };//end of Raster struct
702 
703 
705 
706 namespace local {
707 // This is a simple type that combines a distance value and a particle
708 // attribute. It's required for attribute transfer which is defined in the
709 // Raster class above.
710 template <typename VisibleT, typename BlindT>
711 class BlindData
712 {
713  public:
714  typedef VisibleT type;
715  typedef VisibleT VisibleType;
716  typedef BlindT BlindType;
717  explicit BlindData() {}
718  explicit BlindData(VisibleT v) : mVisible(v) {}
719  explicit BlindData(VisibleT v, BlindT b) : mVisible(v), mBlind(b) {}
721  {
722  mVisible = rhs.mVisible;
723  mBlind = rhs.mBlind;
724  return *this;
725  }
726  const VisibleT& visible() const { return mVisible; }
727  const BlindT& blind() const { return mBlind; }
728  bool operator==(const BlindData& rhs) const { return mVisible == rhs.mVisible; }
729  bool operator< (const BlindData& rhs) const { return mVisible < rhs.mVisible; };
730  bool operator> (const BlindData& rhs) const { return mVisible > rhs.mVisible; };
731  BlindData operator+(const BlindData& rhs) const { return BlindData(mVisible + rhs.mVisible); };
732  BlindData operator+(const VisibleT& rhs) const { return BlindData(mVisible + rhs); };
733  BlindData operator-(const BlindData& rhs) const { return BlindData(mVisible - rhs.mVisible); };
734  BlindData operator-() const { return BlindData(-mVisible, mBlind); }
735 protected:
736  VisibleT mVisible;
737  BlindT mBlind;
738 };
739 // Required by several of the tree nodes
740 template <typename VisibleT, typename BlindT>
741 inline std::ostream& operator<<(std::ostream& ostr, const BlindData<VisibleT, BlindT>& rhs)
742 {
743  ostr << rhs.visible();
744  return ostr;
745 }
746 // Required by math::Abs
747 template <typename VisibleT, typename BlindT>
749 {
750  return BlindData<VisibleT, BlindT>(math::Abs(x.visible()), x.blind());
751 }
752 }// local namespace
753 
755 
756 } // namespace tools
757 } // namespace OPENVDB_VERSION_NAME
758 } // namespace openvdb
759 
760 #endif // OPENVDB_TOOLS_PARTICLES_TO_LEVELSET_HAS_BEEN_INCLUDED
761 
762 // Copyright (c) 2012-2013 DreamWorks Animation LLC
763 // All rights reserved. This software is distributed under the
764 // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ )