OpenVDB  9.0.1
PointScatter.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 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>
25 #include <openvdb/tools/Prune.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 {
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
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
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
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) {
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
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
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.");
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
float RoundDown(float x)
Return x rounded down to the nearest integer.
Definition: Math.h:806
Simple random integer generator.
Definition: Math.h:201
Simple generator of random numbers over the range [0, 1)
Definition: Math.h:165
LeafRange leafRange(size_t grainsize=1) const
Return a TBB-compatible LeafRange.
Definition: LeafManager.h:345
#define OPENVDB_THROW(exception, message)
Definition: Exceptions.h:74
Attribute Array storage templated on type and compression codec.
Methods for counting points in VDB Point grids.
bool isApproxZero(const Type &x)
Return true if x is equal to zero to within the default floating-point comparison tolerance...
Definition: Math.h:350
Definition: Exceptions.h:65
PointDataGridT::Ptr denseUniformPointScatter(const GridT &grid, const float pointsPerVoxel, const unsigned int seed=0, const float spread=1.0f, InterrupterT *interrupter=nullptr)
Uniformly scatter a fixed number of points per active voxel. If the pointsPerVoxel value provided is ...
Definition: PointScatter.h:396
int Floor(float x)
Return the floor of x.
Definition: Math.h:851
void prune(TreeT &tree, typename TreeT::ValueType tolerance=zeroVal< typename TreeT::ValueType >(), bool threaded=true, size_t grainSize=1)
Reduce the memory footprint of a tree by replacing with tiles any nodes whose values are all the same...
Definition: Prune.h:335
const std::enable_if<!VecTraits< T >::IsVec, T >::type & max(const T &a, const T &b)
Definition: Composite.h:107
Defined various multi-threaded utility functions for trees.
PointDataGridT::Ptr nonUniformPointScatter(const GridT &grid, const float pointsPerVoxel, const unsigned int seed=0, const float spread=1.0f, InterrupterT *interrupter=nullptr)
Non uniformly scatter points per active voxel. The pointsPerVoxel value is used to weight each grids ...
Definition: PointScatter.h:478
Definition: Types.h:204
openvdb::GridBase Grid
Definition: Utils.h:34
PointDataGridT::Ptr uniformPointScatter(const GridT &grid, const Index64 count, const unsigned int seed=0, const float spread=1.0f, InterrupterT *interrupter=nullptr)
The free functions depend on the following class:
Definition: PointScatter.h:224
uint64_t Index64
Definition: Types.h:53
Definition: Exceptions.h:13
ValueT value
Definition: GridBuilder.h:1287
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:355
Codec
Optional compression codecs.
Definition: IO.h:61
Attribute-owned data structure for points. Point attributes are stored in leaf nodes and ordered by v...
uint32_t Index32
Definition: Types.h:52
A LeafManager manages a linear array of pointers to a given tree&#39;s leaf nodes, as well as optional au...
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition: version.h.in:116
#define OPENVDB_USE_VERSION_NAMESPACE
Definition: version.h.in:202