1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 //
4 /// @author Ken Museth
5 ///
6 /// @file tools/PointScatter.h
7 ///
8 /// @brief We offer three different algorithms (each in its own class)
9 ///        for scattering of points in active voxels:
10 ///
11 /// 1) UniformPointScatter. Has two modes: Either randomly distributes
12 ///    a fixed number of points into the active voxels, or the user can
13 ///    specify a fixed probability of having a points per unit of volume.
14 ///
15 /// 2) DenseUniformPointScatter. Randomly distributes points into active
16 ///    voxels using a fixed number of points per voxel.
17 ///
18 /// 3) NonIniformPointScatter. Define the local probability of having
19 ///    a point in a voxel as the product of a global density and the
20 ///    value of the voxel itself.
21 
22 #ifndef OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED
23 #define OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED
24 
25 #include <openvdb/Types.h>
26 #include <openvdb/Grid.h>
27 #include <openvdb/math/Math.h>
28 #include <openvdb/util/NullInterrupter.h>
29 #include <tbb/parallel_sort.h>
30 #include <tbb/parallel_for.h>
31 #include <iostream>
32 #include <memory>
33 #include <string>
34 
35 namespace openvdb {
36 OPENVDB_USE_VERSION_NAMESPACE
37 namespace OPENVDB_VERSION_NAME {
38 namespace tools {
39 
40 /// Forward declaration of base class
41 template<typename PointAccessorType,
42          typename RandomGenerator,
43          typename InterruptType = util::NullInterrupter>
44 class BasePointScatter;
45 
46 /// @brief The two point scatters UniformPointScatter and
47 /// NonUniformPointScatter depend on the following two classes:
48 ///
49 /// The @c PointAccessorType template argument below refers to any class
50 /// with the following interface:
51 /// @code
52 /// class PointAccessor {
53 ///   ...
54 /// public:
55 ///   void add(const openvdb::Vec3R &pos);// appends point with world positions pos
56 /// };
57 /// @endcode
58 ///
59 ///
60 /// The @c InterruptType template argument below refers to any class
61 /// with the following interface:
62 /// @code
63 /// class Interrupter {
64 ///   ...
65 /// public:
66 ///   void start(const char* name = nullptr) // called when computations begin
67 ///   void end()                             // called when computations end
68 ///   bool wasInterrupted(int percent=-1)    // return true to break computation
69 ///};
70 /// @endcode
71 ///
72 /// @note If no template argument is provided for this InterruptType
73 /// the util::NullInterrupter is used which implies that all
74 /// interrupter calls are no-ops (i.e. incurs no computational overhead).
75 
76 
77 /// @brief Uniformly scatters points in the active voxels.
78 /// The point count is either explicitly defined or implicitly
79 /// through the specification of a global density (=points-per-volume)
80 ///
81 /// @note This uniform scattering technique assumes that the number of
82 /// points is generally smaller than the number of active voxels
83 /// (including virtual active voxels in active tiles).
84 template<typename PointAccessorType,
85          typename RandomGenerator,
86          typename InterruptType = util::NullInterrupter>
87 class UniformPointScatter : public BasePointScatter<PointAccessorType,
88                                                     RandomGenerator,
89                                                     InterruptType>
90 {
91 public:
92     using BaseT = BasePointScatter<PointAccessorType, RandomGenerator, InterruptType>;
93 
94     UniformPointScatter(PointAccessorType& points,
95                         Index64 pointCount,
96                         RandomGenerator& randGen,
97                         double spread = 1.0,
98                         InterruptType* interrupt = nullptr)
BaseT(points,randGen,spread,interrupt)99         : BaseT(points, randGen, spread, interrupt)
100         , mTargetPointCount(pointCount)
101         , mPointsPerVolume(0.0f)
102     {
103     }
104     UniformPointScatter(PointAccessorType& points,
105                         float pointsPerVolume,
106                         RandomGenerator& randGen,
107                         double spread = 1.0,
108                         InterruptType* interrupt = nullptr)
BaseT(points,randGen,spread,interrupt)109         : BaseT(points, randGen, spread, interrupt)
110         , mTargetPointCount(0)
111         , mPointsPerVolume(pointsPerVolume)
112     {
113     }
114 
115     /// This is the main functor method implementing the actual scattering of points.
116     template<typename GridT>
operator()117     bool operator()(const GridT& grid)
118     {
119         mVoxelCount = grid.activeVoxelCount();
120         if (mVoxelCount == 0) return false;
121 
122         const auto voxelVolume = grid.transform().voxelVolume();
123         if (mPointsPerVolume > 0) {
124             BaseT::start("Uniform scattering with fixed point density");
125             mTargetPointCount = Index64(mPointsPerVolume * voxelVolume * double(mVoxelCount));
126         } else if (mTargetPointCount > 0) {
127             BaseT::start("Uniform scattering with fixed point count");
128             mPointsPerVolume = float(mTargetPointCount) / float(voxelVolume * double(mVoxelCount));
129         } else {
130             return false;
131         }
132 
133         std::unique_ptr<Index64[]> idList{new Index64[mTargetPointCount]};
134         math::RandInt<Index64, RandomGenerator> rand(BaseT::mRand01.engine(), 0, mVoxelCount-1);
135         for (Index64 i=0; i<mTargetPointCount; ++i) idList[i] = rand();
136         tbb::parallel_sort(idList.get(), idList.get() + mTargetPointCount);
137 
138         CoordBBox bbox;
139         const Vec3R offset(0.5, 0.5, 0.5);
140         typename GridT::ValueOnCIter valueIter = grid.cbeginValueOn();
141 
142         for (Index64 i=0, n=valueIter.getVoxelCount() ; i != mTargetPointCount; ++i) {
143             if (BaseT::interrupt()) return false;
144             const Index64 voxelId = idList[i];
145             while ( n <= voxelId ) {
146                 ++valueIter;
147                 n += valueIter.getVoxelCount();
148             }
149             if (valueIter.isVoxelValue()) {// a majority is expected to be voxels
150                 BaseT::addPoint(grid, valueIter.getCoord() - offset);
151             } else {// tiles contain multiple (virtual) voxels
152                 valueIter.getBoundingBox(bbox);
153                 BaseT::addPoint(grid, bbox.min() - offset, bbox.extents());
154             }
155         }//loop over all the active voxels and tiles
156         //}
157 
158         BaseT::end();
159         return true;
160     }
161 
162     // The following methods should only be called after the
163     // the operator() method was called
164     void print(const std::string &name, std::ostream& os = std::cout) const
165     {
166         os << "Uniformly scattered " << mPointCount << " points into " << mVoxelCount
167            << " active voxels in \"" << name << "\" corresponding to "
168            << mPointsPerVolume << " points per volume." << std::endl;
169     }
170 
getPointsPerVolume()171     float   getPointsPerVolume()  const { return mPointsPerVolume; }
getTargetPointCount()172     Index64 getTargetPointCount() const { return mTargetPointCount; }
173 
174 private:
175 
176     using BaseT::mPointCount;
177     using BaseT::mVoxelCount;
178     Index64 mTargetPointCount;
179     float mPointsPerVolume;
180 
181 }; // class UniformPointScatter
182 
183 /// @brief Scatters a fixed (and integer) number of points in all
184 /// active voxels and tiles.
185 template<typename PointAccessorType,
186          typename RandomGenerator,
187          typename InterruptType = util::NullInterrupter>
188 class DenseUniformPointScatter : public BasePointScatter<PointAccessorType,
189                                                          RandomGenerator,
190                                                          InterruptType>
191 {
192 public:
193     using BaseT = BasePointScatter<PointAccessorType, RandomGenerator, InterruptType>;
194 
195     DenseUniformPointScatter(PointAccessorType& points,
196                              float pointsPerVoxel,
197                              RandomGenerator& randGen,
198                              double spread = 1.0,
199                              InterruptType* interrupt = nullptr)
BaseT(points,randGen,spread,interrupt)200         : BaseT(points, randGen, spread, interrupt)
201         , mPointsPerVoxel(pointsPerVoxel)
202     {
203     }
204 
205     /// This is the main functor method implementing the actual scattering of points.
206     template<typename GridT>
operator()207     bool operator()(const GridT& grid)
208     {
209         using ValueIter = typename GridT::ValueOnCIter;
210         if (mPointsPerVoxel < 1.0e-6) return false;
211         mVoxelCount = grid.activeVoxelCount();
212         if (mVoxelCount == 0) return false;
213         BaseT::start("Dense uniform scattering with fixed point count");
214         CoordBBox bbox;
215         const Vec3R offset(0.5, 0.5, 0.5);
216 
217         const int ppv = math::Floor(mPointsPerVoxel);
218         const double delta = mPointsPerVoxel - float(ppv);
219         const bool fractional = !math::isApproxZero(delta, 1.0e-6);
220 
221         for (ValueIter iter = grid.cbeginValueOn(); iter; ++iter) {
222             if (BaseT::interrupt()) return false;
223             if (iter.isVoxelValue()) {// a majority is expected to be voxels
224                 const Vec3R dmin = iter.getCoord() - offset;
225                 for (int n = 0; n != ppv; ++n) BaseT::addPoint(grid, dmin);
226                 if (fractional && BaseT::getRand01() < delta) BaseT::addPoint(grid, dmin);
227             } else {// tiles contain multiple (virtual) voxels
228                 iter.getBoundingBox(bbox);
229                 const Coord size(bbox.extents());
230                 const Vec3R dmin = bbox.min() - offset;
231                 const double d = mPointsPerVoxel * float(iter.getVoxelCount());
232                 const int m = math::Floor(d);
233                 for (int n = 0; n != m; ++n)  BaseT::addPoint(grid, dmin, size);
234                 if (BaseT::getRand01() < d - m) BaseT::addPoint(grid, dmin, size);
235             }
236         }//loop over all the active voxels and tiles
237         //}
238         BaseT::end();
239         return true;
240     }
241 
242     // The following methods should only be called after the
243     // the operator() method was called
244     void print(const std::string &name, std::ostream& os = std::cout) const
245     {
246         os << "Dense uniformly scattered " << mPointCount << " points into " << mVoxelCount
247            << " active voxels in \"" << name << "\" corresponding to "
248            << mPointsPerVoxel << " points per voxel." << std::endl;
249     }
250 
getPointsPerVoxel()251     float getPointsPerVoxel() const { return mPointsPerVoxel; }
252 
253 private:
254     using BaseT::mPointCount;
255     using BaseT::mVoxelCount;
256     float mPointsPerVoxel;
257 }; // class DenseUniformPointScatter
258 
259 /// @brief Non-uniform scatters of point in the active voxels.
260 /// The local point count is implicitly defined as a product of
261 /// of a global density (called pointsPerVolume) and the local voxel
262 /// (or tile) value.
263 ///
264 /// @note This scattering technique can be significantly slower
265 /// than a uniform scattering since its computational complexity
266 /// is proportional to the active voxel (and tile) count.
267 template<typename PointAccessorType,
268          typename RandomGenerator,
269          typename InterruptType = util::NullInterrupter>
270 class NonUniformPointScatter : public BasePointScatter<PointAccessorType,
271                                                        RandomGenerator,
272                                                        InterruptType>
273 {
274 public:
275     using BaseT = BasePointScatter<PointAccessorType, RandomGenerator, InterruptType>;
276 
277     NonUniformPointScatter(PointAccessorType& points,
278                            float pointsPerVolume,
279                            RandomGenerator& randGen,
280                            double spread = 1.0,
281                            InterruptType* interrupt = nullptr)
BaseT(points,randGen,spread,interrupt)282         : BaseT(points, randGen, spread, interrupt)
283         , mPointsPerVolume(pointsPerVolume)//note this is merely a
284                                            //multiplier for the local point density
285     {
286     }
287 
288     /// This is the main functor method implementing the actual scattering of points.
289     template<typename GridT>
operator()290     bool operator()(const GridT& grid)
291     {
292         if (mPointsPerVolume <= 0.0f) return false;
293         mVoxelCount = grid.activeVoxelCount();
294         if (mVoxelCount == 0) return false;
295         BaseT::start("Non-uniform scattering with local point density");
296         const Vec3d dim = grid.voxelSize();
297         const double volumePerVoxel = dim[0]*dim[1]*dim[2],
298                      pointsPerVoxel = mPointsPerVolume * volumePerVoxel;
299         CoordBBox bbox;
300         const Vec3R offset(0.5, 0.5, 0.5);
301         for (typename GridT::ValueOnCIter iter = grid.cbeginValueOn(); iter; ++iter) {
302             if (BaseT::interrupt()) return false;
303             const double d = double(*iter) * pointsPerVoxel * double(iter.getVoxelCount());
304             const int n = int(d);
305             if (iter.isVoxelValue()) { // a majority is expected to be voxels
306                 const Vec3R dmin =iter.getCoord() - offset;
307                 for (int i = 0; i < n; ++i) BaseT::addPoint(grid, dmin);
308                 if (BaseT::getRand01() < (d - n)) BaseT::addPoint(grid, dmin);
309             } else { // tiles contain multiple (virtual) voxels
310                 iter.getBoundingBox(bbox);
311                 const Coord size(bbox.extents());
312                 const Vec3R dmin = bbox.min() - offset;
313                 for (int i = 0; i < n; ++i) BaseT::addPoint(grid, dmin, size);
314                 if (BaseT::getRand01() < (d - n)) BaseT::addPoint(grid, dmin, size);
315             }
316         }//loop over all the active voxels and tiles
317         BaseT::end();
318         return true;
319     }
320 
321     // The following methods should only be called after the
322     // the operator() method was called
323     void print(const std::string &name, std::ostream& os = std::cout) const
324     {
325         os << "Non-uniformly scattered " << mPointCount << " points into " << mVoxelCount
326            << " active voxels in \"" << name << "\"." << std::endl;
327     }
328 
getPointPerVolume()329     float getPointPerVolume() const { return mPointsPerVolume; }
330 
331 private:
332     using BaseT::mPointCount;
333     using BaseT::mVoxelCount;
334     float mPointsPerVolume;
335 
336 }; // class NonUniformPointScatter
337 
338 /// Base class of all the point scattering classes defined above
339 template<typename PointAccessorType,
340          typename RandomGenerator,
341          typename InterruptType>
342 class BasePointScatter
343 {
344 public:
345 
getPointCount()346     Index64 getPointCount() const { return mPointCount; }
getVoxelCount()347     Index64 getVoxelCount() const { return mVoxelCount; }
348 
349 protected:
350 
351     PointAccessorType&        mPoints;
352     InterruptType*            mInterrupter;
353     Index64                   mPointCount;
354     Index64                   mVoxelCount;
355     Index64                   mInterruptCount;
356     const double              mSpread;
357     math::Rand01<double, RandomGenerator> mRand01;
358 
359     /// This is a base class so the constructor is protected
360     BasePointScatter(PointAccessorType& points,
361                      RandomGenerator& randGen,
362                      double spread,
363                      InterruptType* interrupt = nullptr)
mPoints(points)364         : mPoints(points)
365         , mInterrupter(interrupt)
366         , mPointCount(0)
367         , mVoxelCount(0)
368         , mInterruptCount(0)
369         , mSpread(math::Clamp01(spread))
370         , mRand01(randGen)
371     {
372     }
373 
start(const char * name)374     inline void start(const char* name)
375     {
376         if (mInterrupter) mInterrupter->start(name);
377     }
378 
end()379     inline void end()
380     {
381         if (mInterrupter) mInterrupter->end();
382     }
383 
interrupt()384     inline bool interrupt()
385     {
386         //only check interrupter for every 32'th call
387         return !(mInterruptCount++ & ((1<<5)-1)) && util::wasInterrupted(mInterrupter);
388     }
389 
390     /// @brief Return a random floating point number between zero and one
getRand01()391     inline double getRand01() { return mRand01(); }
392 
393     /// @brief Return a random floating point number between 0.5 -+ mSpread/2
getRand()394     inline double getRand() { return 0.5 + mSpread * (mRand01() - 0.5); }
395 
396     template <typename GridT>
addPoint(const GridT & grid,const Vec3R & dmin)397     inline void addPoint(const GridT &grid, const Vec3R &dmin)
398     {
399         const Vec3R pos(dmin[0] + this->getRand(),
400                         dmin[1] + this->getRand(),
401                         dmin[2] + this->getRand());
402         mPoints.add(grid.indexToWorld(pos));
403         ++mPointCount;
404     }
405 
406     template <typename GridT>
addPoint(const GridT & grid,const Vec3R & dmin,const Coord & size)407     inline void addPoint(const GridT &grid, const Vec3R &dmin, const Coord &size)
408     {
409         const Vec3R pos(dmin[0] + size[0]*this->getRand(),
410                         dmin[1] + size[1]*this->getRand(),
411                         dmin[2] + size[2]*this->getRand());
412         mPoints.add(grid.indexToWorld(pos));
413         ++mPointCount;
414     }
415 };// class BasePointScatter
416 
417 } // namespace tools
418 } // namespace OPENVDB_VERSION_NAME
419 } // namespace openvdb
420 
421 #endif // OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED
422