1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 
4 /// @author Nick Avramoussis
5 ///
6 /// @file points/PointScatter.h
7 ///
8 /// @brief Various point scattering methods for generating VDB Points.
9 ///
10 ///  All random number calls are made to the same generator to produce
11 ///  temporarily consistent results in relation to the provided seed. This
12 ///  comes with some multi-threaded performance trade-offs.
13 
14 #ifndef OPENVDB_POINTS_POINT_SCATTER_HAS_BEEN_INCLUDED
15 #define OPENVDB_POINTS_POINT_SCATTER_HAS_BEEN_INCLUDED
16 
17 #include <type_traits>
18 #include <algorithm>
19 #include <thread>
20 #include <random>
21 
22 #include <openvdb/openvdb.h>
23 #include <openvdb/Types.h>
24 #include <openvdb/tree/LeafManager.h>
25 #include <openvdb/tools/Prune.h>
26 #include <openvdb/util/NullInterrupter.h>
27 
28 #include "AttributeArray.h"
29 #include "PointCount.h"
30 #include "PointDataGrid.h"
31 
32 #include <tbb/parallel_sort.h>
33 #include <tbb/parallel_for.h>
34 
35 namespace openvdb {
36 OPENVDB_USE_VERSION_NAMESPACE
37 namespace OPENVDB_VERSION_NAME {
38 namespace points {
39 
40 /// @brief The free functions depend on the following class:
41 ///
42 /// The @c InterrupterT template argument below refers to any class
43 /// with the following interface:
44 /// @code
45 /// class Interrupter {
46 ///   ...
47 /// public:
48 ///   void start(const char* name = nullptr) // called when computations begin
49 ///   void end()                             // called when computations end
50 ///   bool wasInterrupted(int percent=-1)    // return true to break computation
51 ///};
52 /// @endcode
53 ///
54 /// @note If no template argument is provided for this InterrupterT
55 /// the util::NullInterrupter is used which implies that all
56 /// interrupter calls are no-ops (i.e. incurs no computational overhead).
57 
58 
59 /// @brief Uniformly scatter a total amount of points in active regions
60 ///
61 /// @param grid         A source grid. The resulting PointDataGrid will copy this grids
62 ///                     transform and scatter in its active voxelized topology.
63 /// @param count        The total number of points to scatter
64 /// @param seed         A seed for the RandGenT
65 /// @param spread       The spread of points as a scale from each voxels center. A value of
66 ///                     1.0f indicates points can be placed anywhere within the voxel, where
67 ///                     as a value of 0.0f will force all points to be created exactly at the
68 ///                     centers of each voxel.
69 /// @param interrupter  An optional interrupter
70 /// @note returns the scattered PointDataGrid
71 template<
72     typename GridT,
73     typename RandGenT = std::mt19937,
74     typename PositionArrayT = TypedAttributeArray<Vec3f, NullCodec>,
75     typename PointDataGridT = Grid<
76         typename points::TreeConverter<typename GridT::TreeType>::Type>,
77     typename InterrupterT = util::NullInterrupter>
78 inline typename PointDataGridT::Ptr
79 uniformPointScatter(const GridT& grid,
80                     const Index64 count,
81                     const unsigned int seed = 0,
82                     const float spread = 1.0f,
83                     InterrupterT* interrupter = nullptr);
84 
85 /// @brief Uniformly scatter a fixed number of points per active voxel. If the pointsPerVoxel
86 ///        value provided is a fractional value, each voxel calculates a delta value of
87 ///        how likely it is to contain an extra point.
88 ///
89 /// @param grid            A source grid. The resulting PointDataGrid will copy this grids
90 ///                        transform and scatter in its active voxelized topology.
91 /// @param pointsPerVoxel  The number of points to scatter per voxel
92 /// @param seed            A seed for the RandGenT
93 /// @param spread          The spread of points as a scale from each voxels center. A value of
94 ///                        1.0f indicates points can be placed anywhere within the voxel, where
95 ///                        as a value of 0.0f will force all points to be created exactly at the
96 ///                        centers of each voxel.
97 /// @param interrupter     An optional interrupter
98 /// @note returns the scattered PointDataGrid
99 
100 template<
101     typename GridT,
102     typename RandGenT = std::mt19937,
103     typename PositionArrayT = TypedAttributeArray<Vec3f, NullCodec>,
104     typename PointDataGridT = Grid<
105         typename points::TreeConverter<typename GridT::TreeType>::Type>,
106     typename InterrupterT = util::NullInterrupter>
107 inline typename PointDataGridT::Ptr
108 denseUniformPointScatter(const GridT& grid,
109                          const float pointsPerVoxel,
110                          const unsigned int seed = 0,
111                          const float spread = 1.0f,
112                          InterrupterT* interrupter = nullptr);
113 
114 /// @brief Non uniformly scatter points per active voxel. The pointsPerVoxel value is used
115 ///        to weight each grids cell value to compute a fixed number of points for every
116 ///        active voxel. If the computed result is a fractional value, each voxel calculates
117 ///        a delta value of how likely it is to contain an extra point.
118 ///
119 /// @param grid            A source grid. The resulting PointDataGrid will copy this grids
120 ///                        transform, voxelized topology and use its values to compute a
121 ///                        target points per voxel. The grids ValueType must be convertible
122 ///                        to a scalar value. Only active and larger than zero values will
123 ///                        contain points.
124 /// @param pointsPerVoxel  The number of points to scatter per voxel
125 /// @param seed            A seed for the RandGenT
126 /// @param spread          The spread of points as a scale from each voxels center. A value of
127 ///                        1.0f indicates points can be placed anywhere within the voxel, where
128 ///                        as a value of 0.0f will force all points to be created exactly at the
129 ///                        centers of each voxel.
130 /// @param interrupter     An optional interrupter
131 /// @note returns the scattered PointDataGrid
132 template<
133     typename GridT,
134     typename RandGenT = std::mt19937,
135     typename PositionArrayT = TypedAttributeArray<Vec3f, NullCodec>,
136     typename PointDataGridT = Grid<
137         typename points::TreeConverter<typename GridT::TreeType>::Type>,
138     typename InterrupterT = util::NullInterrupter>
139 inline typename PointDataGridT::Ptr
140 nonUniformPointScatter(const GridT& grid,
141                        const float pointsPerVoxel,
142                        const unsigned int seed = 0,
143                        const float spread = 1.0f,
144                        InterrupterT* interrupter = nullptr);
145 
146 
147 ////////////////////////////////////////
148 
149 /// @cond OPENVDB_DOCS_INTERNAL
150 
151 namespace point_scatter_internal
152 {
153 
154 /// @brief initialise the topology of a PointDataGrid and ensure
155 /// everything is voxelized
156 /// @param grid   The source grid from which to base the topology generation
157 template<typename PointDataGridT, typename GridT>
158 inline typename PointDataGridT::Ptr
initialisePointTopology(const GridT & grid)159 initialisePointTopology(const GridT& grid)
160 {
161     typename PointDataGridT::Ptr points(new PointDataGridT);
162     points->setTransform(grid.transform().copy());
163     points->topologyUnion(grid);
164     if (points->tree().hasActiveTiles()) {
165         points->tree().voxelizeActiveTiles();
166     }
167 
168     return points;
169 }
170 
171 /// @brief Generate random point positions for a leaf node
172 /// @param leaf       The leaf node to initialize
173 /// @param descriptor The descriptor containing the position type
174 /// @param count      The number of points to generate
175 /// @param spread     The spread of points from the voxel center
176 /// @param rand01     The random number generator, expected to produce floating point
177 ///                   values between 0 and 1.
178 template<typename PositionType,
179          typename CodecT,
180          typename RandGenT,
181          typename LeafNodeT>
182 inline void
generatePositions(LeafNodeT & leaf,const AttributeSet::Descriptor::Ptr & descriptor,const Index64 & count,const float spread,RandGenT & rand01)183 generatePositions(LeafNodeT& leaf,
184                   const AttributeSet::Descriptor::Ptr& descriptor,
185                   const Index64& count,
186                   const float spread,
187                   RandGenT& rand01)
188 {
189     using PositionTraits = VecTraits<PositionType>;
190     using ValueType = typename PositionTraits::ElementType;
191     using PositionWriteHandle = AttributeWriteHandle<PositionType, CodecT>;
192 
193     leaf.initializeAttributes(descriptor, static_cast<Index>(count));
194 
195     // directly expand to avoid needlessly setting uniform values in the
196     // write handle
197     auto& array = leaf.attributeArray(0);
198     array.expand(/*fill*/false);
199 
200     PositionWriteHandle pHandle(array, /*expand*/false);
201     PositionType P;
202     for (Index64 index = 0; index < count; ++index) {
203         P[0] = (spread * (rand01() - ValueType(0.5)));
204         P[1] = (spread * (rand01() - ValueType(0.5)));
205         P[2] = (spread * (rand01() - ValueType(0.5)));
206         pHandle.set(static_cast<Index>(index), P);
207     }
208 }
209 
210 } // namespace point_scatter_internal
211 
212 /// @endcond
213 
214 ////////////////////////////////////////
215 
216 
217 template<
218     typename GridT,
219     typename RandGenT,
220     typename PositionArrayT,
221     typename PointDataGridT,
222     typename InterrupterT>
223 inline typename PointDataGridT::Ptr
uniformPointScatter(const GridT & grid,const Index64 count,const unsigned int seed,const float spread,InterrupterT * interrupter)224 uniformPointScatter(const GridT& grid,
225                     const Index64 count,
226                     const unsigned int seed,
227                     const float spread,
228                     InterrupterT* interrupter)
229 {
230     using PositionType = typename PositionArrayT::ValueType;
231     using PositionTraits = VecTraits<PositionType>;
232     using ValueType = typename PositionTraits::ElementType;
233     using CodecType = typename PositionArrayT::Codec;
234 
235     using RandomGenerator = math::Rand01<ValueType, RandGenT>;
236 
237     using TreeType = typename PointDataGridT::TreeType;
238     using LeafNodeType = typename TreeType::LeafNodeType;
239     using LeafManagerT = tree::LeafManager<TreeType>;
240 
241     struct Local
242     {
243         /// @brief Get the prefixed voxel counts for each leaf node with an
244         /// additional value to represent the end voxel count.
245         /// See also LeafManager::getPrefixSum()
246         static void getPrefixSum(LeafManagerT& leafManager,
247                                  std::vector<Index64>& offsets)
248         {
249             Index64 offset = 0;
250             offsets.reserve(leafManager.leafCount() + 1);
251             offsets.push_back(0);
252             const auto leafRange = leafManager.leafRange();
253             for (auto leaf = leafRange.begin(); leaf; ++leaf) {
254                 offset += leaf->onVoxelCount();
255                 offsets.push_back(offset);
256             }
257         }
258     };
259 
260     static_assert(PositionTraits::IsVec && PositionTraits::Size == 3,
261                   "Invalid Position Array type.");
262 
263     if (spread < 0.0f || spread > 1.0f) {
264         OPENVDB_THROW(ValueError, "Spread must be between 0 and 1.");
265     }
266 
267     if (interrupter) interrupter->start("Uniform scattering with fixed point count");
268 
269     typename PointDataGridT::Ptr points =
270         point_scatter_internal::initialisePointTopology<PointDataGridT>(grid);
271     TreeType& tree = points->tree();
272     if (!tree.cbeginLeaf()) return points;
273 
274     LeafManagerT leafManager(tree);
275     const Index64 voxelCount = leafManager.activeLeafVoxelCount();
276     assert(voxelCount != 0);
277 
278     const double pointsPerVolume = double(count) / double(voxelCount);
279     const Index32 pointsPerVoxel = static_cast<Index32>(math::RoundDown(pointsPerVolume));
280     const Index64 remainder = count - (pointsPerVoxel * voxelCount);
281 
282     if (remainder == 0) {
283         return denseUniformPointScatter<
284             GridT, RandGenT, PositionArrayT, PointDataGridT, InterrupterT>(
285                 grid, float(pointsPerVoxel), seed, spread, interrupter);
286     }
287 
288     std::vector<Index64> voxelOffsets, values;
289     std::thread worker(&Local::getPrefixSum, std::ref(leafManager), std::ref(voxelOffsets));
290 
291     {
292         math::RandInt<Index64, RandGenT> gen(seed, 0, voxelCount-1);
293         values.reserve(remainder);
294         for (Index64 i = 0; i < remainder; ++i) values.emplace_back(gen());
295     }
296 
297     worker.join();
298 
299     if (util::wasInterrupted<InterrupterT>(interrupter)) {
300         tree.clear();
301         return points;
302     }
303 
304     tbb::parallel_sort(values.begin(), values.end());
305     const bool fractionalOnly(pointsPerVoxel == 0);
306 
307     leafManager.foreach([&voxelOffsets, &values, fractionalOnly]
308                         (LeafNodeType& leaf, const size_t idx)
309     {
310         const Index64 lowerOffset = voxelOffsets[idx]; // inclusive
311         const Index64 upperOffset = voxelOffsets[idx + 1]; // exclusive
312         assert(upperOffset > lowerOffset);
313 
314         const auto valuesEnd = values.end();
315         auto lower = std::lower_bound(values.begin(), valuesEnd, lowerOffset);
316 
317         auto* const data = leaf.buffer().data();
318         auto iter = leaf.beginValueOn();
319 
320         Index32 currentOffset(0);
321         bool addedPoints(!fractionalOnly);
322         while (lower != valuesEnd) {
323             const Index64 vId = *lower;
324             if (vId >= upperOffset) break;
325 
326             const Index32 nextOffset = Index32(vId - lowerOffset);
327             iter.increment(nextOffset - currentOffset);
328             currentOffset = nextOffset;
329             assert(iter);
330 
331             auto& value = data[iter.pos()];
332             value = value + 1; // no += operator support
333             addedPoints = true;
334             ++lower;
335         }
336 
337         // deactivate this leaf if no points were added. This will speed up
338         // the unthreaded rng
339         if (!addedPoints) leaf.setValuesOff();
340     });
341 
342     voxelOffsets.clear();
343     values.clear();
344 
345     if (fractionalOnly) {
346         tools::pruneInactive(tree);
347         leafManager.rebuild();
348     }
349 
350     const AttributeSet::Descriptor::Ptr descriptor =
351         AttributeSet::Descriptor::create(PositionArrayT::attributeType());
352     RandomGenerator rand01(seed);
353 
354     const auto leafRange = leafManager.leafRange();
355     auto leaf = leafRange.begin();
356     for (; leaf; ++leaf) {
357         if (util::wasInterrupted<InterrupterT>(interrupter)) break;
358         Index32 offset(0);
359         for (auto iter = leaf->beginValueAll(); iter; ++iter) {
360             if (iter.isValueOn()) {
361                 const Index32 value = Index32(pointsPerVolume + Index32(*iter));
362                 if (value == 0) leaf->setValueOff(iter.pos());
363                 else            offset += value;
364             }
365             // @note can't use iter.setValue(offset) on point grids
366             leaf->setOffsetOnly(iter.pos(), offset);
367         }
368 
369         // offset should always be non zero
370         assert(offset != 0);
371         point_scatter_internal::generatePositions<PositionType, CodecType>
372             (*leaf, descriptor, offset, spread, rand01);
373     }
374 
375     // if interrupted, remove remaining leaf nodes
376     if (leaf) {
377         for (; leaf; ++leaf) leaf->setValuesOff();
378         tools::pruneInactive(tree);
379     }
380 
381     if (interrupter) interrupter->end();
382     return points;
383 }
384 
385 
386 ////////////////////////////////////////
387 
388 
389 template<
390     typename GridT,
391     typename RandGenT,
392     typename PositionArrayT,
393     typename PointDataGridT,
394     typename InterrupterT>
395 inline typename PointDataGridT::Ptr
denseUniformPointScatter(const GridT & grid,const float pointsPerVoxel,const unsigned int seed,const float spread,InterrupterT * interrupter)396 denseUniformPointScatter(const GridT& grid,
397                          const float pointsPerVoxel,
398                          const unsigned int seed,
399                          const float spread,
400                          InterrupterT* interrupter)
401 {
402     using PositionType = typename PositionArrayT::ValueType;
403     using PositionTraits = VecTraits<PositionType>;
404     using ValueType = typename PositionTraits::ElementType;
405     using CodecType = typename PositionArrayT::Codec;
406 
407     using RandomGenerator = math::Rand01<ValueType, RandGenT>;
408 
409     using TreeType = typename PointDataGridT::TreeType;
410 
411     static_assert(PositionTraits::IsVec && PositionTraits::Size == 3,
412                   "Invalid Position Array type.");
413 
414     if (pointsPerVoxel < 0.0f) {
415         OPENVDB_THROW(ValueError, "Points per voxel must not be less than zero.");
416     }
417 
418     if (spread < 0.0f || spread > 1.0f) {
419         OPENVDB_THROW(ValueError, "Spread must be between 0 and 1.");
420     }
421 
422     if (interrupter) interrupter->start("Dense uniform scattering with fixed point count");
423 
424     typename PointDataGridT::Ptr points =
425         point_scatter_internal::initialisePointTopology<PointDataGridT>(grid);
426     TreeType& tree = points->tree();
427     auto leafIter = tree.beginLeaf();
428     if (!leafIter) return points;
429 
430     const Index32 pointsPerVoxelInt = math::Floor(pointsPerVoxel);
431     const double delta = pointsPerVoxel - float(pointsPerVoxelInt);
432     const bool fractional = !math::isApproxZero(delta, 1.0e-6);
433     const bool fractionalOnly = pointsPerVoxelInt == 0;
434 
435     const AttributeSet::Descriptor::Ptr descriptor =
436         AttributeSet::Descriptor::create(PositionArrayT::attributeType());
437     RandomGenerator rand01(seed);
438 
439     for (; leafIter; ++leafIter) {
440         if (util::wasInterrupted<InterrupterT>(interrupter)) break;
441         Index32 offset(0);
442         for (auto iter = leafIter->beginValueAll(); iter; ++iter) {
443             if (iter.isValueOn()) {
444                 offset += pointsPerVoxelInt;
445                 if (fractional && rand01() < delta) ++offset;
446                 else if (fractionalOnly) leafIter->setValueOff(iter.pos());
447             }
448             // @note can't use iter.setValue(offset) on point grids
449             leafIter->setOffsetOnly(iter.pos(), offset);
450         }
451 
452         if (offset != 0) {
453             point_scatter_internal::generatePositions<PositionType, CodecType>
454                 (*leafIter, descriptor, offset, spread, rand01);
455         }
456     }
457 
458     // if interrupted, remove remaining leaf nodes
459     const bool prune(leafIter || fractionalOnly);
460     for (; leafIter; ++leafIter) leafIter->setValuesOff();
461 
462     if (prune) tools::pruneInactive(tree);
463     if (interrupter) interrupter->end();
464     return points;
465 }
466 
467 
468 ////////////////////////////////////////
469 
470 
471 template<
472     typename GridT,
473     typename RandGenT,
474     typename PositionArrayT,
475     typename PointDataGridT,
476     typename InterrupterT>
477 inline typename PointDataGridT::Ptr
nonUniformPointScatter(const GridT & grid,const float pointsPerVoxel,const unsigned int seed,const float spread,InterrupterT * interrupter)478 nonUniformPointScatter(const GridT& grid,
479                        const float pointsPerVoxel,
480                        const unsigned int seed,
481                        const float spread,
482                        InterrupterT* interrupter)
483 {
484     using PositionType = typename PositionArrayT::ValueType;
485     using PositionTraits = VecTraits<PositionType>;
486     using ValueType = typename PositionTraits::ElementType;
487     using CodecType = typename PositionArrayT::Codec;
488 
489     using RandomGenerator = math::Rand01<ValueType, RandGenT>;
490 
491     using TreeType = typename PointDataGridT::TreeType;
492 
493     static_assert(PositionTraits::IsVec && PositionTraits::Size == 3,
494                   "Invalid Position Array type.");
495     static_assert(std::is_arithmetic<typename GridT::ValueType>::value,
496                   "Scalar grid type required for weighted voxel scattering.");
497 
498     if (pointsPerVoxel < 0.0f) {
499         OPENVDB_THROW(ValueError, "Points per voxel must not be less than zero.");
500     }
501 
502     if (spread < 0.0f || spread > 1.0f) {
503         OPENVDB_THROW(ValueError, "Spread must be between 0 and 1.");
504     }
505 
506     if (interrupter) interrupter->start("Non-uniform scattering with local point density");
507 
508     typename PointDataGridT::Ptr points =
509         point_scatter_internal::initialisePointTopology<PointDataGridT>(grid);
510     TreeType& tree = points->tree();
511     auto leafIter = tree.beginLeaf();
512     if (!leafIter) return points;
513 
514     const AttributeSet::Descriptor::Ptr descriptor =
515         AttributeSet::Descriptor::create(PositionArrayT::attributeType());
516     RandomGenerator rand01(seed);
517     const auto accessor = grid.getConstAccessor();
518 
519     for (; leafIter; ++leafIter) {
520         if (util::wasInterrupted<InterrupterT>(interrupter)) break;
521         Index32 offset(0);
522         for (auto iter = leafIter->beginValueAll(); iter; ++iter) {
523             if (iter.isValueOn()) {
524                 double fractional =
525                     double(accessor.getValue(iter.getCoord())) * pointsPerVoxel;
526                 fractional = std::max(0.0, fractional);
527                 int count = int(fractional);
528                 if (rand01() < (fractional - double(count))) ++count;
529                 else if (count == 0) leafIter->setValueOff(iter.pos());
530                 offset += count;
531             }
532             // @note can't use iter.setValue(offset) on point grids
533             leafIter->setOffsetOnly(iter.pos(), offset);
534         }
535 
536         if (offset != 0) {
537             point_scatter_internal::generatePositions<PositionType, CodecType>
538                 (*leafIter, descriptor, offset, spread, rand01);
539         }
540     }
541 
542     // if interrupted, remove remaining leaf nodes
543     for (; leafIter; ++leafIter) leafIter->setValuesOff();
544 
545     tools::pruneInactive(points->tree());
546     if (interrupter) interrupter->end();
547     return points;
548 }
549 
550 
551 } // namespace points
552 } // namespace OPENVDB_VERSION_NAME
553 } // namespace openvdb
554 
555 
556 #endif // OPENVDB_POINTS_POINT_SCATTER_HAS_BEEN_INCLUDED
557