OpenVDB  12.1.0
PointRasterizeSDFImpl.h
Go to the documentation of this file.
1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: Apache-2.0
3 //
4 /// @author Nick Avramoussis
5 ///
6 /// @file PointRasterizeSDFImpl.h
7 ///
8 
9 #ifndef OPENVDB_POINTS_RASTERIZE_SDF_IMPL_HAS_BEEN_INCLUDED
10 #define OPENVDB_POINTS_RASTERIZE_SDF_IMPL_HAS_BEEN_INCLUDED
11 
12 namespace openvdb {
14 namespace OPENVDB_VERSION_NAME {
15 namespace points {
16 
17 /// @cond OPENVDB_DOCS_INTERNAL
18 
19 namespace rasterize_sdf_internal
20 {
21 
22 /// @brief Define a fixed index space radius for point rasterization
23 template <typename ValueT>
24 struct FixedRadius
25 {
26  static constexpr bool Fixed = true;
27  using ValueType = ValueT;
28 
29  FixedRadius(const ValueT ris) : mR(ris) {}
30  inline void reset(const PointDataTree::LeafNodeType&) const {}
31  inline const FixedRadius& eval(const Index) const { return *this; }
32  inline ValueT get() const { return mR; }
33 private:
34  const ValueT mR;
35 };
36 
37 /// @brief Define a fixed narrow band radius for point rasterization
38 /// @details Pass in an index space radius (relative to a PointDataGrid voxel
39 /// size) and the desired half band width of the target surface. The minimum
40 /// radius of influence is clamped to zero.
41 template <typename ValueT>
42 struct FixedBandRadius : public FixedRadius<ValueT>
43 {
44  static constexpr bool Fixed = true;
45  using ValueType = ValueT;
46 
47  FixedBandRadius(const ValueT ris, const float hb)
48  : FixedRadius<ValueT>(ris)
49  , mMinSearchIS(math::Max(ValueT(0.0), ris - ValueT(hb)))
50  , mMaxSearchIS(ris + ValueT(hb))
51  , mMinSearchSqIS(mMinSearchIS*mMinSearchIS)
52  , mMaxSearchSqIS(mMaxSearchIS*mMaxSearchIS)
53  , mHalfBand(hb) {}
54 
55  inline void reset(const PointDataTree::LeafNodeType&) const {}
56 
57  inline const FixedBandRadius eval(const Index, const ValueT scale) const
58  {
59  if (scale == 1.0) return *this;
60  return FixedBandRadius(this->get() * scale, this->halfband());
61  }
62 
63  inline const FixedBandRadius& eval(const Index) const { return *this; }
64  inline float halfband() const { return mHalfBand; }
65 
66  inline ValueT min() const { return mMinSearchIS; }
67  inline ValueT minSq() const { return mMinSearchSqIS; }
68  inline ValueT max() const { return mMaxSearchIS; }
69  inline ValueT maxSq() const { return mMaxSearchSqIS; }
70 private:
71  const ValueT mMinSearchIS, mMaxSearchIS;
72  const ValueT mMinSearchSqIS, mMaxSearchSqIS;
73  // @note Could technically recompute this value from the rest here
74  // but storing it alleviates any potential precision issues
75  const float mHalfBand;
76 };
77 
78 /// @brief A vector varying per point radius (used for the ellipsoid
79 /// rasterizer).
80 template <>
81 struct FixedBandRadius<Vec3f> : public FixedRadius<Vec3f>
82 {
83  static constexpr bool Fixed = true;
84  using ValueType = Vec3f;
85  FixedBandRadius(const Vec3f ris, const float hb)
86  : FixedRadius<Vec3f>(ris)
87  , mHalfBand(hb) {}
88  inline void reset(const PointDataTree::LeafNodeType&) const {}
89  inline const FixedBandRadius& eval(const Index) const { return *this; }
90  inline float halfband() const { return mHalfBand; }
91 private:
92  const float mHalfBand;
93 };
94 
95 /// @brief A varying per point radius with an optional scale
96 template <typename ValueT, typename ScaleT = ValueT, typename CodecT = UnknownCodec>
97 struct VaryingRadius
98 {
99  static constexpr bool Fixed = false;
100  using ValueType = ValueT;
101 
102  using RadiusHandleT = AttributeHandle<ValueT, CodecT>;
103  VaryingRadius(const size_t ridx, const ScaleT scale = ScaleT(1.0))
104  : mRIdx(ridx), mRHandle(), mScale(scale) {}
105  VaryingRadius(const VaryingRadius& other)
106  : mRIdx(other.mRIdx), mRHandle(), mScale(other.mScale) {}
107 
108  inline size_t size() const { return mRHandle->size(); }
109 
110  inline void reset(const PointDataTree::LeafNodeType& leaf)
111  {
112  mRHandle.reset(new RadiusHandleT(leaf.constAttributeArray(mRIdx)));
113  }
114 
115  /// @brief Compute a fixed radius for a specific point
116  inline auto eval(const Index id, const ScaleT scale = ScaleT(1.0)) const
117  {
118  OPENVDB_ASSERT(mRHandle);
119  auto x = mRHandle->get(id) * mScale * scale;
120  return FixedRadius<decltype(x)>(x);
121  }
122 
123 private:
124  const size_t mRIdx;
125  typename RadiusHandleT::UniquePtr mRHandle;
126  const ScaleT mScale;
127 };
128 
129 /// @brief A varying per point narrow band radius with an optional scale
130 template <typename ValueT, typename ScaleT = ValueT, typename CodecT = UnknownCodec>
131 struct VaryingBandRadius : public VaryingRadius<ValueT, ScaleT, CodecT>
132 {
133  static constexpr bool Fixed = false;
134  using ValueType = ValueT;
135 
136  using BaseT = VaryingRadius<ValueT, ScaleT, CodecT>;
137  VaryingBandRadius(const size_t ridx, const float halfband,
138  const ScaleT scale = ScaleT(1.0))
139  : BaseT(ridx, scale), mHalfBand(halfband) {}
140 
141  inline float halfband() const { return mHalfBand; }
142  inline auto eval(const Index id, const ScaleT scale = ScaleT(1.0)) const
143  {
144  auto r = this->BaseT::eval(id, scale).get();
145  return FixedBandRadius<decltype(r)>(r, mHalfBand);
146  }
147 
148 private:
149  const float mHalfBand;
150 };
151 
152 /// @brief Base class for SDF transfers which consolidates member data and
153 /// some required interface methods.
154 template <typename SdfT,
155  typename PositionCodecT,
156  typename RadiusType,
157  typename FilterT,
158  bool CPG>
159 struct SignedDistanceFieldTransfer :
160  public TransformTransfer, // for transformation support
161  public FilteredTransfer<FilterT>, // for point filtering
162  public InterruptableTransfer, // for interrupt-ability
163  public std::conditional<CPG, // for automatic buffer setup
164  VolumeTransfer<typename SdfT::TreeType, Int64Tree>,
165  VolumeTransfer<typename SdfT::TreeType>>::type
166 {
167  using TreeT = typename SdfT::TreeType;
168  using ValueT = typename TreeT::ValueType;
169  static_assert(std::is_floating_point<ValueT>::value,
170  "Spherical transfer only supports floating point values.");
171  static_assert(!std::is_reference<RadiusType>::value && !std::is_pointer<RadiusType>::value,
172  "Templated radius type must not be a reference or pointer");
173 
174  using VolumeTransferT = typename std::conditional<CPG,
175  VolumeTransfer<TreeT, Int64Tree>,
176  VolumeTransfer<TreeT>>::type;
177  using FilteredTransferT = FilteredTransfer<FilterT>;
178  using PositionHandleT = AttributeHandle<Vec3f, PositionCodecT>;
179 
180  // typically the max radius of all points rounded up
181  inline Vec3i range(const Coord&, size_t) const { return mMaxKernelWidth; }
182 
183  inline void initialize(const Coord& origin, const size_t idx, const CoordBBox& bounds)
184  {
185  VolumeTransferT::initialize(origin, idx, bounds);
186  FilteredTransferT::initialize(origin, idx, bounds);
187  }
188 
189  inline bool startPointLeaf(const PointDataTree::LeafNodeType& leaf)
190  {
191  FilteredTransferT::startPointLeaf(leaf);
192  mPosition = std::make_unique<PositionHandleT>(leaf.constAttributeArray(mPIdx));
193  mRadius.reset(leaf);
194  // if CPG, store leaf id in upper 32 bits of mask
195  if (CPG) mPLeafMask = (Index64(mIds->find(&leaf)->second) << 32);
196  return true;
197  }
198 
199 protected:
200  /// @brief Constructor to use when a closet point grid is not in use
201  template <bool EnableT = CPG>
202  SignedDistanceFieldTransfer(const size_t pidx,
203  const Vec3i width,
204  const RadiusType& rt,
205  const math::Transform& source,
206  const FilterT& filter,
207  util::NullInterrupter* interrupt,
208  SdfT& surface,
209  Int64Tree* cpg,
210  const std::unordered_map<const PointDataTree::LeafNodeType*, Index>* ids,
211  typename std::enable_if<EnableT>::type* = 0)
212  : TransformTransfer(source, surface.transform())
213  , FilteredTransferT(filter)
214  , InterruptableTransfer(interrupt)
215  , VolumeTransferT(&(surface.tree()), cpg)
216  , mPIdx(pidx)
217  , mPosition()
218  , mMaxKernelWidth(width)
219  , mRadius(rt)
220  , mBackground(surface.background())
221  , mDx(surface.voxelSize()[0])
222  , mIds(ids)
223  , mPLeafMask(0) {
224  OPENVDB_ASSERT(cpg && ids);
225  }
226 
227  /// @brief Constructor to use when a closet point grid is in use
228  template <bool EnableT = CPG>
229  SignedDistanceFieldTransfer(const size_t pidx,
230  const Vec3i width,
231  const RadiusType& rt,
232  const math::Transform& source,
233  const FilterT& filter,
234  util::NullInterrupter* interrupt,
235  SdfT& surface,
236  Int64Tree*,
237  const std::unordered_map<const PointDataTree::LeafNodeType*, Index>*,
238  typename std::enable_if<!EnableT>::type* = 0)
239  : TransformTransfer(source, surface.transform())
240  , FilteredTransferT(filter)
241  , InterruptableTransfer(interrupt)
242  , VolumeTransferT(surface.tree())
243  , mPIdx(pidx)
244  , mPosition()
245  , mMaxKernelWidth(width)
246  , mRadius(rt)
247  , mBackground(surface.background())
248  , mDx(surface.voxelSize()[0])
249  , mIds(nullptr)
250  , mPLeafMask(0) {}
251 
252  SignedDistanceFieldTransfer(const SignedDistanceFieldTransfer& other)
253  : TransformTransfer(other)
254  , FilteredTransferT(other)
255  , InterruptableTransfer(other)
256  , VolumeTransferT(other)
257  , mPIdx(other.mPIdx)
258  , mPosition()
259  , mMaxKernelWidth(other.mMaxKernelWidth)
260  , mRadius(other.mRadius)
261  , mBackground(other.mBackground)
262  , mDx(other.mDx)
263  , mIds(other.mIds)
264  , mPLeafMask(0) {}
265 
266 protected:
267  const size_t mPIdx;
268  typename PositionHandleT::UniquePtr mPosition;
269  const Vec3i mMaxKernelWidth;
270  RadiusType mRadius;
271  const ValueT mBackground;
272  const double mDx;
273  const std::unordered_map<const PointDataTree::LeafNodeType*, Index>* mIds;
274  Index64 mPLeafMask;
275 };
276 
277 /// @brief The transfer implementation for spherical stamping of narrow band
278 /// radius values.
279 template <typename SdfT,
280  typename PositionCodecT,
281  typename RadiusType,
282  typename FilterT,
283  bool CPG>
284 struct SphericalTransfer :
285  public SignedDistanceFieldTransfer<SdfT, PositionCodecT, RadiusType, FilterT, CPG>
286 {
287  using BaseT = SignedDistanceFieldTransfer<SdfT, PositionCodecT, RadiusType, FilterT, CPG>;
288  using typename BaseT::TreeT;
289  using typename BaseT::ValueT;
290  static const Index DIM = TreeT::LeafNodeType::DIM;
291  static const Index LOG2DIM = TreeT::LeafNodeType::LOG2DIM;
292  // The precision of the kernel arithmetic
293  using RealT = double;
294 
296 
297  SphericalTransfer(const size_t pidx,
298  const size_t width,
299  const RadiusType& rt,
300  const math::Transform& source,
301  const FilterT& filter,
302  util::NullInterrupter* interrupt,
303  SdfT& surface,
304  Int64Tree* cpg = nullptr,
305  const std::unordered_map<const PointDataTree::LeafNodeType*, Index>* ids = nullptr)
306  : SphericalTransfer(pidx, Vec3i(width), rt, source, filter, interrupt, surface, cpg, ids) {}
307 
308  /// @brief For each point, stamp a sphere with a given radius by running
309  /// over all intersecting voxels and calculating if this point is closer
310  /// than the currently held distance value. Note that the default value
311  /// of the surface buffer should be the background value of the surface.
312  inline void rasterizePoint(const Coord& ijk,
313  const Index id,
314  const CoordBBox& bounds)
315  {
316  if (!BaseT::filter(id)) return;
317 
318  // @note GCC 6.3 warns here in optimised builds
319 #if defined(__GNUC__) && !defined(__clang__)
320 #pragma GCC diagnostic push
321 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
322 #endif
323  Vec3d P = ijk.asVec3d() + Vec3d(this->mPosition->get(id));
324 #if defined(__GNUC__) && !defined(__clang__)
325 #pragma GCC diagnostic pop
326 #endif
327  P = this->transformSourceToTarget(P);
328  this->rasterizePoint(P, id, bounds, this->mRadius.eval(id));
329  }
330 
331  /// @brief Allow early termination if all voxels in the surface have been
332  /// deactivated (all interior)
333  inline bool endPointLeaf(const PointDataTree::LeafNodeType&)
334  {
335  // If the mask is off, terminate rasterization
336  return !(this->template mask<0>()->isOff());
337  }
338 
339  inline bool finalize(const Coord&, const size_t)
340  {
341  // loop over voxels in the outer cube diagonals which won't have been
342  // hit by point rasterizations - these will be on because of the mask
343  // fill technique and need to be turned off.
344  auto& mask = *(this->template mask<0>());
345  auto* const data = this->template buffer<0>();
346  for (auto iter = mask.beginOn(); iter; ++iter) {
347  if (data[iter.pos()] == this->mBackground) mask.setOff(iter.pos());
348  }
349  // apply sdf mask to other grids
350  if (CPG) *(this->template mask<CPG ? 1 : 0>()) = mask;
351  return true;
352  }
353 
354 protected:
355  /// @brief Allow derived transfer schemes to override the width with a
356  /// varying component (this transfer is explicitly for spheres so it
357  /// doesn't make sense to construct it directly, but derived transfers
358  /// may be utilizing this logic with other kernels).
359  SphericalTransfer(const size_t pidx,
360  const Vec3i width,
361  const RadiusType& rt,
362  const math::Transform& source,
363  const FilterT& filter,
364  util::NullInterrupter* interrupt,
365  SdfT& surface,
366  Int64Tree* cpg = nullptr,
367  const std::unordered_map<const PointDataTree::LeafNodeType*, Index>* ids = nullptr)
368  : BaseT(pidx, width, rt, source, filter, interrupt, surface, cpg, ids) {}
369 
370  /// @brief This hook simply exists for the Ellipsoid transfer to allow it
371  /// to pass a different P and scaled FixedBandRadius from its ellipsoid
372  /// path (as isolated points are stamped as spheres with a different
373  /// scale and positions may have been smoothed).
374  /// @todo I would prefer this second function wasn't necessary but there
375  /// is no easy way to allow differently scaled radii to exist in a more
376  /// efficient manner, nor use a different P.
377  inline void rasterizePoint(const Vec3d& P,
378  const Index id,
379  const CoordBBox& bounds,
380  const FixedBandRadius<ElemT>& r)
381  {
382  const RealT max = r.max();
383  CoordBBox intersectBox(Coord::round(P - max), Coord::round(P + max));
384  intersectBox.intersect(bounds);
385  if (intersectBox.empty()) return;
386 
387  auto* const data = this->template buffer<0>();
388  auto* const cpg = CPG ? this->template buffer<CPG ? 1 : 0>() : nullptr;
389  auto& mask = *(this->template mask<0>());
390 
391  // If min2 == 0.0, then the index space radius is equal to or less than
392  // the desired half band. In this case each sphere interior always needs
393  // to be filled with distance values as we won't ever reach the negative
394  // background value. If, however, a point overlaps a voxel coord exactly,
395  // x2y2z2 will be 0.0. Forcing min2 to be less than zero here avoids
396  // incorrectly setting these voxels to inactive -background values as
397  // x2y2z2 will never be < 0.0. We still want the lteq logic in the
398  // (x2y2z2 <= min2) check as this is valid when min2 > 0.0.
399  const RealT min2 = r.minSq() == 0.0 ? -1.0 : r.minSq();
400  const RealT max2 = r.maxSq();
401 
402  const Coord& a(intersectBox.min());
403  const Coord& b(intersectBox.max());
404  for (Coord c = a; c.x() <= b.x(); ++c.x()) {
405  const RealT x2 = static_cast<RealT>(math::Pow2(c.x() - P[0]));
406  const Index i = ((c.x() & (DIM-1u)) << 2*LOG2DIM); // unsigned bit shift mult
407  for (c.y() = a.y(); c.y() <= b.y(); ++c.y()) {
408  const RealT x2y2 = static_cast<RealT>(x2 + math::Pow2(c.y() - P[1]));
409  const Index ij = i + ((c.y() & (DIM-1u)) << LOG2DIM);
410  for (c.z() = a.z(); c.z() <= b.z(); ++c.z()) {
411  const Index offset = ij + /*k*/(c.z() & (DIM-1u));
412  if (!mask.isOn(offset)) continue; // inside existing level set or not in range
413 
414  const RealT x2y2z2 = static_cast<RealT>(x2y2 + math::Pow2(c.z() - P[2]));
415  if (x2y2z2 >= max2) continue; //outside narrow band of particle in positive direction
416  if (x2y2z2 <= min2) { //outside narrow band of the particle in negative direction. can disable this to fill interior
417  data[offset] = -(this->mBackground);
418  mask.setOff(offset);
419  continue;
420  }
421 
422  const ValueT d = ValueT(this->mDx * (math::Sqrt(x2y2z2) - r.get())); // back to world space
423  ValueT& v = data[offset];
424  if (d < v) {
425  v = d;
427  if (CPG) cpg[offset] = Int64(this->mPLeafMask | Index64(id));
429  // transfer attributes - we can't use this here as the exposed
430  // function signatures take vector of attributes (i.e. an unbounded
431  // size). If we instead clamped the attribute transfer to a fixed
432  // amount of attributes we could get rid of the closest point logic
433  // entirely. @todo consider adding another signature for increased
434  // performance
435  // this->foreach([&](auto* buffer, const size_t idx) {
436  // using Type = typename std::remove_pointer<decltype(buffer)>::type;
437  // buffer[offset] = mAttributes.template get<Type>(idx);
438  // })
439  }
440  }
441  }
442  } // outer sdf voxel
443  } // point idx
444 };
445 
446 /// @brief The transfer implementation for averaging of positions followed by
447 /// spherical stamping.
448 template <typename SdfT,
449  typename PositionCodecT,
450  typename RadiusType,
451  typename FilterT,
452  bool CPG>
453 struct AveragePositionTransfer :
454  public SignedDistanceFieldTransfer<SdfT, PositionCodecT, RadiusType, FilterT, CPG>
455 {
456  using BaseT = SignedDistanceFieldTransfer<SdfT, PositionCodecT, RadiusType, FilterT, CPG>;
457  using typename BaseT::TreeT;
458  using typename BaseT::ValueT;
459 
460  using VolumeTransferT = typename std::conditional<CPG,
461  VolumeTransfer<typename SdfT::TreeType, Int64Tree>,
462  VolumeTransfer<typename SdfT::TreeType>>::type;
463 
464  static const Index DIM = TreeT::LeafNodeType::DIM;
465  static const Index LOG2DIM = TreeT::LeafNodeType::LOG2DIM;
466  static const Index NUM_VALUES = TreeT::LeafNodeType::NUM_VALUES;
467  // The precision of the kernel arithmetic
468  using RealT = double;
469 
470  struct PosRadPair
471  {
472  template <typename S> inline void addP(const math::Vec3<S>& v) { P += v; }
473  template <typename S> inline void addR(const S r) { R += r; }
474  template <typename S> inline void multR(const S w) { R *= w; }
475  template <typename S> inline void multP(const S w) { P *= w; }
476  inline RealT length() const { return P.length() - R; }
477  math::Vec3<RealT> P = math::Vec3<RealT>(0.0);
478  RealT R = 0.0;
479  };
480 
481  AveragePositionTransfer(const size_t pidx,
482  const size_t width,
483  const RadiusType& rt,
484  const RealT search,
485  const math::Transform& source,
486  const FilterT& filter,
487  util::NullInterrupter* interrupt,
488  SdfT& surface,
489  Int64Tree* cpg = nullptr,
490  const std::unordered_map<const PointDataTree::LeafNodeType*, Index>* ids = nullptr)
491  : AveragePositionTransfer(pidx, Vec3i(width), rt, search, source, filter, interrupt, surface, cpg, ids) {}
492 
493  AveragePositionTransfer(const AveragePositionTransfer& other)
494  : BaseT(other)
495  , mMaxSearchIS(other.mMaxSearchIS)
496  , mMaxSearchSqIS(other.mMaxSearchSqIS)
497  , mWeights()
498  , mDist() {}
499 
500  inline void initialize(const Coord& origin, const size_t idx, const CoordBBox& bounds)
501  {
502  // init buffers
503  this->BaseT::initialize(origin, idx, bounds);
504  mWeights.assign(NUM_VALUES, PosRadPair());
505  if (CPG) mDist.assign(NUM_VALUES, std::numeric_limits<float>::max());
506  // We use the surface buffer to store the intermediate weights as
507  // defined by the sum of k(|x−xj|/R), where k(s) = max(0,(1−s^2)^3)
508  // and R is the maximum search distance. The active buffer currently
509  // holds background values. We could simply subtract the background away
510  // from the final result - however if the background value increases
511  // beyond 1, progressively larger floating point instabilities can be
512  // observed with the weight calculation. Instead, reset all active
513  // values to zero
514  // @todo The surface buffer may not be at RealT precision. Should we
515  // enforce this by storing the weights in another vector?
516  auto* const data = this->template buffer<0>();
517  const auto& mask = *(this->template mask<0>());
518  for (auto iter = mask.beginOn(); iter; ++iter) {
519  data[iter.pos()] = ValueT(0);
520  }
521  }
522 
523  inline void rasterizePoint(const Coord& ijk,
524  const Index id,
525  const CoordBBox& bounds)
526  {
527  if (!BaseT::filter(id)) return;
528 
529 #if defined(__GNUC__) && !defined(__clang__)
530 #pragma GCC diagnostic push
531 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
532 #endif
533  const Vec3d PWS = this->sourceTransform().indexToWorld(ijk.asVec3d() + Vec3d(this->mPosition->get(id)));
534 #if defined(__GNUC__) && !defined(__clang__)
535 #pragma GCC diagnostic pop
536 #endif
537  const Vec3d P = this->targetTransform().worldToIndex(PWS);
538 
539  CoordBBox intersectBox(Coord::round(P - mMaxSearchIS), Coord::round(P + mMaxSearchIS));
540  intersectBox.intersect(bounds);
541  if (intersectBox.empty()) return;
542 
543  auto* const data = this->template buffer<0>();
544  auto* const cpg = CPG ? this->template buffer<CPG ? 1 : 0>() : nullptr;
545  const auto& mask = *(this->template mask<0>());
546 
547  // index space radius
548  const auto& r = this->mRadius.eval(id);
549  const RealT rad = r.get();
550  const RealT invsq = 1.0 / mMaxSearchSqIS;
551 
552  const Coord& a(intersectBox.min());
553  const Coord& b(intersectBox.max());
554  for (Coord c = a; c.x() <= b.x(); ++c.x()) {
555  const RealT x2 = static_cast<RealT>(math::Pow2(c.x() - P[0]));
556  const Index i = ((c.x() & (DIM-1u)) << 2*LOG2DIM); // unsigned bit shift mult
557  for (c.y() = a.y(); c.y() <= b.y(); ++c.y()) {
558  const RealT x2y2 = static_cast<RealT>(x2 + math::Pow2(c.y() - P[1]));
559  const Index ij = i + ((c.y() & (DIM-1u)) << LOG2DIM);
560  for (c.z() = a.z(); c.z() <= b.z(); ++c.z()) {
561  RealT x2y2z2 = static_cast<RealT>(x2y2 + math::Pow2(c.z() - P[2]));
562  if (x2y2z2 >= mMaxSearchSqIS) continue; //outside search distance
563  const Index offset = ij + /*k*/(c.z() & (DIM-1u));
564  if (!mask.isOn(offset)) continue; // inside existing level set or not in range
565 
566  // @note this algorithm is unable to deactivate voxels within
567  // a computed narrow band during rasterization as all points must
568  // visit their affected voxels.
569 
570  if (CPG) {
571  // CPG still computed directly with each individual point
572  // @note Because voxels can't be discarded, it may be faster to
573  // do this as a post process (and avoid the sqrt per lookup)
574  // @note No need to scale back to world space
575  const float dist = static_cast<float>(math::Sqrt(x2y2z2) - r.get());
576  auto& d = mDist[offset];
577  if (dist < d) {
578  d = dist;
580  cpg[offset] = Int64(this->mPLeafMask | Index64(id));
582  }
583  }
584 
585  x2y2z2 *= invsq; // x2y2z2 = (x - xi) / R
586  // k(s) = max(0,(1−s^2)^3). note that the max is unecessary
587  // as we early terminate above with x2y2z2 >= mMaxSearchSqIS
588  x2y2z2 = math::Pow3(1.0 - x2y2z2);
589  OPENVDB_ASSERT(x2y2z2 >= 0.0);
590  // @todo The surface buffer may not be at RealT precision. Should we
591  // enforce this by storing the weights in another vector?
593  data[offset] += x2y2z2;
595  auto& wt = mWeights[offset];
596  wt.addP(PWS * x2y2z2);
597  wt.addR(rad * x2y2z2);
598  }
599  }
600  } // outer sdf voxel
601  } // point idx
602 
603  inline bool endPointLeaf(const PointDataTree::LeafNodeType&) { return true; }
604 
605  inline bool finalize(const Coord& origin, const size_t)
606  {
607  auto& mask = *(this->template mask<0>());
608  auto* const data = this->template buffer<0>();
609 
610  for (auto iter = mask.beginOn(); iter; ++iter) {
611  const Index idx = iter.pos();
612  auto& w = data[idx];
613  // if background, voxel was out of range. Guaranteed to be outside as
614  // all interior voxels will have at least a single point contribution
615  if (w == 0.0f) {
616  mask.setOff(idx);
617  w = this->mBackground;
618  }
619  else {
620  const Coord ijk = origin + TreeT::LeafNodeType::offsetToLocalCoord(idx);
621  const Vec3d ws = this->targetTransform().indexToWorld(ijk);
622  const RealT wi = RealT(1.0) / RealT(w); // wi
623  auto& wt = mWeights[idx];
624  wt.multP(wi); // sum of weighted positions
625  wt.multR(wi * this->mDx); // sum of weighted radii (scale to ws)
626  wt.addP(-ws); // (x - xi) (instead doing (-x + xi))
627  w = static_cast<typename SdfT::ValueType>(wt.length()); // (x - xi) - r
628  // clamp active region and value range to requested narrow band
629  if (std::fabs(w) >= this->mBackground) {
630  w = std::copysign(this->mBackground, w);
631  mask.setOff(idx);
632  }
633  }
634  }
635 
636  // apply sdf mask to other grids
637  if (CPG) *(this->template mask<CPG ? 1 : 0>()) = mask;
638  return true;
639  }
640 
641 protected:
642  /// @brief Allow derived transfer schemes to override the width with a
643  /// varying component (this transfer is explicitly for spheres so it
644  /// doesn't make sense to construct it directly, but derived transfers
645  /// may be utilizing this logic with other kernels).
646  AveragePositionTransfer(const size_t pidx,
647  const Vec3i width,
648  const RadiusType& rt,
649  const RealT search,
650  const math::Transform& source,
651  const FilterT& filter,
652  util::NullInterrupter* interrupt,
653  SdfT& surface,
654  Int64Tree* cpg = nullptr,
655  const std::unordered_map<const PointDataTree::LeafNodeType*, Index>* ids = nullptr)
656  : BaseT(pidx, width, rt, source, filter, interrupt, surface, cpg, ids)
657  , mMaxSearchIS(search)
658  , mMaxSearchSqIS(search*search)
659  , mWeights()
660  , mDist() {}
661 
662 private:
663  const RealT mMaxSearchIS, mMaxSearchSqIS;
664  std::vector<PosRadPair> mWeights;
665  std::vector<float> mDist;
666 };
667 
668 /// @brief Base class for surfacing mask initialization
669 template <typename MaskTreeT = MaskTree>
670 struct SurfaceMaskOp
671 {
672 public:
673  inline void join(SurfaceMaskOp& other)
674  {
675  if (mMask->leafCount() > other.mMask->leafCount()) {
676  mMask->topologyUnion(*other.mMask);
677  other.mMask.reset();
678  }
679  else {
680  other.mMask->topologyUnion(*mMask);
681  mMask.reset(other.mMask.release());
682  }
683 
684  if (mMaskOff->leafCount() > other.mMaskOff->leafCount()) {
685  mMaskOff->topologyUnion(*other.mMaskOff);
686  other.mMaskOff.reset();
687  }
688  else {
689  other.mMaskOff->topologyUnion(*mMaskOff);
690  mMaskOff.reset(other.mMaskOff.release());
691  }
692  }
693 
694  std::unique_ptr<MaskTreeT> mask() { return std::move(mMask); }
695  std::unique_ptr<MaskTreeT> maskoff() { return std::move(mMaskOff); }
696 
697 protected:
698  SurfaceMaskOp(const math::Transform& points,
699  const math::Transform& surface,
700  // Clip the surface to this bounds. Only used for the smooth
701  // raster workflow to limit to search radii topology init
702  const CoordBBox* const maxClipBounds = nullptr,
703  util::NullInterrupter* interrupter = nullptr)
704  : mMask(new MaskTreeT)
705  , mMaskOff(new MaskTreeT)
706  , mPointsTransform(points)
707  , mSurfaceTransform(surface)
708  , mMaxClipBounds(maxClipBounds ? this->toSurfaceBounds(*maxClipBounds) : CoordBBox::inf())
709  , mInterrupter(interrupter) {}
710 
711  SurfaceMaskOp(const SurfaceMaskOp& other)
712  : mMask(new MaskTreeT)
713  , mMaskOff(new MaskTreeT)
714  , mPointsTransform(other.mPointsTransform)
715  , mSurfaceTransform(other.mSurfaceTransform)
716  , mMaxClipBounds(other.mMaxClipBounds)
717  , mInterrupter(other.mInterrupter) {}
718 
719  // @brief Sparse fill a tree with activated bounding boxes expanded from
720  // each active voxel.
721  // @note This method used to fill from each individual voxel. Whilst more
722  // accurate, this was slower in comparison to using the active node bounds.
723  // As the rasterization is so fast (discarding of voxels out of range)
724  // this overzealous activation results in far superior performance overall.
725  template <typename LeafT>
726  inline bool activate(const LeafT& leaf, const int32_t dist)
727  {
728  CoordBBox bounds = this->toSurfaceBounds(this->getActiveBoundingBox(leaf));
729  if (bounds.empty()) return false;
730  // Expand by the desired surface index space distance
731  bounds.expand(dist);
732  this->activate(bounds);
733  return true;
734  }
735 
736  template <typename LeafT>
737  inline bool activate(const LeafT& leaf, const Vec3i dist)
738  {
739  CoordBBox bounds = this->toSurfaceBounds(this->getActiveBoundingBox(leaf));
740  if (bounds.empty()) return false;
741  // Expand by the desired surface index space distance
742  bounds.min() -= Coord(dist);
743  bounds.max() += Coord(dist);
744  this->activate(bounds);
745  return true;
746  }
747 
748  template <typename LeafT>
749  inline void deactivate(const LeafT& leaf, const int32_t dist)
750  {
751  OPENVDB_ASSERT((dist % MaskTreeT::LeafNodeType::DIM) == 0);
752  // We only deactivate in incrementd of leaf nodes, so as long as
753  // dist >= 0 we don't need a tight bounding box
754  CoordBBox bounds = this->toSurfaceBounds(leaf.getNodeBoundingBox());
755  // Expand by the desired surface index space distance
756  bounds.expand(dist);
757  this->deactivate(bounds);
758  }
759 
760  inline void activate(CoordBBox& bounds)
761  {
762  bounds.intersect(mMaxClipBounds);
763  mMask->sparseFill(bounds, true, true);
764  }
765 
766  inline void deactivate(const CoordBBox& bounds)
767  {
768  mMaskOff->sparseFill(bounds, true, true);
769  }
770 
771  inline bool interrupted()
772  {
773  if (util::wasInterrupted(mInterrupter)) {
774  thread::cancelGroupExecution();
775  return true;
776  }
777  return false;
778  }
779 
780  //
781 
782  template <typename LeafT>
783  inline CoordBBox getActiveBoundingBox(const LeafT& leaf) const
784  {
785  CoordBBox bounds;
786  const auto& mask = leaf.getValueMask();
787  if (mask.isOn()) {
788  // includes translation to leaf origin
789  bounds = leaf.getNodeBoundingBox();
790  }
791  else {
792  for (auto iter = mask.beginOn(); iter; ++iter) {
793  bounds.expand(leaf.offsetToLocalCoord(iter.pos()));
794  }
795  if (bounds.empty()) return bounds;
796  bounds.translate(leaf.origin());
797  }
798  return bounds;
799  }
800 
801  /// @brief Given a leaf node (and assuming the coordinate bounds of the
802  /// leaf come from the PointDataGrid in use), find the bounds of its
803  /// index space activity and return these bounds at the index space of
804  /// the target surface grid
805  inline CoordBBox toSurfaceBounds(const CoordBBox& bounds) const
806  {
807  if (bounds.empty()) return bounds;
808  // Offset the point leaf bounds to the actual position of this node's
809  // faces in index space (of the points), then convert this to the
810  // corresponding index space of the cloest node bounds in the target
811  // surface grid
812  const BBoxd wsbounds(
813  bounds.min().asVec3d() - 0.5,
814  bounds.max().asVec3d() + 0.5);
815  return mSurfaceTransform.worldToIndexCellCentered(
816  mPointsTransform.indexToWorld(wsbounds));
817  }
818 
819 protected:
820  std::unique_ptr<MaskTreeT> mMask;
821  std::unique_ptr<MaskTreeT> mMaskOff;
822  const math::Transform& mPointsTransform;
823  const math::Transform& mSurfaceTransform;
824  const CoordBBox mMaxClipBounds;
825  util::NullInterrupter* mInterrupter;
826 };
827 
828 /// @brief Initializes a fixed activity mask
829 template <typename MaskTreeT>
830 struct FixedSurfaceMaskOp
831  : public SurfaceMaskOp<MaskTreeT>
832 {
833  using BaseT = SurfaceMaskOp<MaskTreeT>;
834  using LeafManagerT =
835  tree::LeafManager<const points::PointDataTree>;
836 
837  FixedSurfaceMaskOp(const math::Transform& points,
838  const math::Transform& surface,
839  const double minBandRadius, // sdf index space
840  const double maxBandRadius, // sdf index space
841  const CoordBBox* const maxClipBounds,
842  util::NullInterrupter* interrupter = nullptr)
843  : BaseT(points, surface, maxClipBounds, interrupter)
844  , mMin(), mMax()
845  {
846  // calculate the min interior cube area of activity. this is the side
847  // of the largest possible cube that fits into the radius "min":
848  // d = 2r -> 2r = 3x^2 -> x = 2r / sqrt(3)
849  // Half side of the cube which fits into the sphere with radius minBandRadius
850  const Real halfside = ((2.0 * minBandRadius) / std::sqrt(3.0)) / 2.0;
851  OPENVDB_ASSERT(halfside >= 0.0); // minBandRadius shouldn't be negative
852  // Round down to avoid deactivating partially occluded voxels
853  const int32_t min = static_cast<int32_t>(std::max(0.0, halfside));
854  // mMin is the distance from the nodes bounding box that we can
855  // deactivate. Because we don't know the point positions here, we
856  // can only deactivate based on the worst scenario (that is, we can
857  // only deactivate entire leaf nodes, and we can only do so if we are
858  // sure they are going to be encompassed by any single sphere). So take
859  // the min distance and see how many leaf nodes the half distance
860  // encompasses entirely.
861  const int32_t nodes = min / MaskTreeT::LeafNodeType::DIM;
862  OPENVDB_ASSERT(nodes >= 0);
863  // Back to voxel dim (minus 1 as we expand out from a leaf node)
864  mMin = (nodes-1) * MaskTreeT::LeafNodeType::DIM;
865  mMax = static_cast<int32_t>(math::Round(maxBandRadius)); // furthest voxel
866  }
867 
868  FixedSurfaceMaskOp(const FixedSurfaceMaskOp& other, tbb::split)
869  : BaseT(other), mMin(other.mMin), mMax(other.mMax) {}
870 
871  void operator()(const typename LeafManagerT::LeafRange& range)
872  {
873  if (this->interrupted()) return;
874  for (auto leaf = range.begin(); leaf; ++leaf) {
875  this->activate(*leaf, mMax);
876  }
877  if (mMin < 0) return;
878  for (auto leaf = range.begin(); leaf; ++leaf) {
879  this->deactivate(*leaf, mMin);
880  }
881  }
882 
883 private:
884  int32_t mMin, mMax;
885 };
886 
887 /// @brief Initializes a variable activity mask
888 template <typename RadiusTreeT, typename MaskTreeT>
889 struct VariableSurfaceMaskOp
890  : public SurfaceMaskOp<MaskTreeT>
891 {
892  using BaseT = SurfaceMaskOp<MaskTreeT>;
893  using LeafManagerT =
894  tree::LeafManager<const points::PointDataTree>;
895 
896  VariableSurfaceMaskOp(const math::Transform& pointsTransform,
897  const math::Transform& surfaceTransform,
898  const RadiusTreeT* const min,
899  const RadiusTreeT& max,
900  const Real minScale,
901  const Real maxScale,
902  const Real halfband,
903  const CoordBBox* const maxClipBounds,
904  util::NullInterrupter* interrupter = nullptr)
905  : BaseT(pointsTransform, surfaceTransform, maxClipBounds, interrupter)
906  , mMin(min), mMax(max), mMinScale(minScale), mMaxScale(maxScale)
907  , mHalfband(halfband) {}
908 
909  VariableSurfaceMaskOp(const VariableSurfaceMaskOp&) = default;
910  VariableSurfaceMaskOp(const VariableSurfaceMaskOp& other, tbb::split)
911  : VariableSurfaceMaskOp(other) {}
912 
913  void operator()(const typename LeafManagerT::LeafRange& range)
914  {
915  if (this->interrupted()) return;
916  const tree::ValueAccessor<const RadiusTreeT> maxacc(mMax);
917  for (auto leafIter = range.begin(); leafIter; ++leafIter) {
918  const int32_t max = this->maxDist(maxacc.getValue(leafIter->origin()));
919  this->activate(*leafIter, max);
920  }
921 
922  if (!mMin) return;
923  const tree::ValueAccessor<const RadiusTreeT> minacc(*mMin);
924  for (auto leafIter = range.begin(); leafIter; ++leafIter) {
925  const int32_t min = this->minDist(minacc.getValue(leafIter->origin()));
926  if (min < 0) continue;
927  this->deactivate(*leafIter, min);
928  }
929  }
930 
931 private:
932  inline int32_t maxDist(const typename RadiusTreeT::ValueType& maxRadiusWs) const
933  {
934  // max radius in index space
935  const Real maxBandRadius = (Real(maxRadiusWs) * mMaxScale) + mHalfband;
936  return static_cast<int32_t>(math::Round(maxBandRadius)); // furthest voxel
937  }
938 
939  inline int32_t minDist(const typename RadiusTreeT::ValueType& minRadiusWs) const
940  {
941  // min radius in index space
942  Real minBandRadius = math::Max(0.0, (Real(minRadiusWs) * mMinScale) - mHalfband);
943  // calculate the min interior cube area of activity. this is the side
944  // of the largest possible cube that fits into the radius "min":
945  // d = 2r -> 2r = 3x^2 -> x = 2r / sqrt(3)
946  // Half side of the cube which fits into the sphere with radius minBandRadius
947  const Real halfside = ((2.0 * minBandRadius) / std::sqrt(3.0)) / 2.0;
948  OPENVDB_ASSERT(halfside >= 0.0); // minBandRadius shouldn't be negative
949  // Round down to avoid deactivating partially occluded voxels
950  const int32_t min = static_cast<int32_t>(std::max(0.0, halfside));
951  // mMin is the distance from the nodes bounding box that we can
952  // deactivate. Because we don't know the point positions here, we
953  // can only deactivate based on the worst scenario (that is, we can
954  // only deactivate entire leaf nodes if we are sure they are going
955  // to be encompassed by any single sphere). So take the min distance
956  // and see how many leaf nodes the half distance encompasses entirely.
957  const int32_t nodes = min / MaskTreeT::LeafNodeType::DIM;
958  OPENVDB_ASSERT(nodes >= 0);
959  // Back to voxel dim (minus 1 as we expand out from a leaf node)
960  return (nodes-1) * MaskTreeT::LeafNodeType::DIM;
961  }
962 
963 private:
964  const RadiusTreeT* const mMin;
965  const RadiusTreeT& mMax;
966  const Real mMinScale, mMaxScale, mHalfband;
967 };
968 
969 template <typename SdfT, typename MaskTreeT>
970 inline typename SdfT::Ptr
971 initSdfFromMasks(math::Transform::Ptr& transform,
972  const typename SdfT::ValueType bg,
973  std::unique_ptr<MaskTreeT> on,
974  std::unique_ptr<MaskTreeT> off)
975 {
976  typename SdfT::Ptr surface = SdfT::create(bg);
977  surface->setTransform(transform);
978  surface->setGridClass(GRID_LEVEL_SET);
979 
980  if (!off->empty()) {
981  on->topologyDifference(*off);
982  // union will copy empty nodes so prune them
984  surface->tree().topologyUnion(*on);
985  // set off values to -background
986  tree::ValueAccessor<const MaskTreeT> acc(*off);
987  auto setOffOp = [acc](auto& iter) {
988  if (acc.isValueOn(iter.getCoord())) {
989  iter.modifyValue([](auto& v) { v = -v; });
990  }
991  };
992  tools::foreach(surface->beginValueOff(), setOffOp,
993  /*thread=*/true, /*shared=*/false);
994  }
995  else {
996  surface->tree().topologyUnion(*on);
997  }
998 
999  on.reset();
1000  off.reset();
1001  surface->tree().voxelizeActiveTiles();
1002  return surface;
1003 }
1004 
1005 template <typename SdfT, typename PointDataGridT>
1006 inline typename SdfT::Ptr
1007 initFixedSdf(const PointDataGridT& points,
1008  math::Transform::Ptr transform,
1009  const typename SdfT::ValueType bg,
1010  const double minBandRadius,
1011  const double maxBandRadius,
1012  util::NullInterrupter* interrupter)
1013 {
1014  using LeafManagerT = tree::LeafManager<const typename PointDataGridT::TreeType>;
1015  using MaskTreeT = typename SdfT::TreeType::template ValueConverter<ValueMask>::Type;
1016 
1017  if (interrupter) interrupter->start("Generating uniform surface topology");
1018 
1019  FixedSurfaceMaskOp<MaskTreeT> op(points.transform(),
1020  *transform, minBandRadius, maxBandRadius, /*clipbounds=*/nullptr, interrupter);
1021 
1022  LeafManagerT manager(points.tree());
1023  tbb::parallel_reduce(manager.leafRange(), op);
1024 
1025  typename SdfT::Ptr surface =
1026  initSdfFromMasks<SdfT, MaskTreeT>(transform, bg, op.mask(), op.maskoff());
1027 
1028  if (interrupter) interrupter->end();
1029  return surface;
1030 }
1031 
1032 template <typename SdfT,
1033  typename PointDataGridT,
1034  typename RadiusTreeT>
1035 inline typename SdfT::Ptr
1036 initVariableSdf(const PointDataGridT& points,
1037  math::Transform::Ptr transform,
1038  const typename SdfT::ValueType bg,
1039  const RadiusTreeT& min,
1040  const RadiusTreeT& max,
1041  const Real scale,
1042  const Real halfband,
1043  util::NullInterrupter* interrupter)
1044 {
1045  using LeafManagerT = tree::LeafManager<const typename PointDataGridT::TreeType>;
1046  using MaskTreeT = typename SdfT::TreeType::template ValueConverter<ValueMask>::Type;
1047 
1048  if (interrupter) interrupter->start("Generating variable surface topology");
1049 
1050  VariableSurfaceMaskOp<RadiusTreeT, MaskTreeT>
1051  op(points.transform(), *transform, &min, max, scale, scale, halfband,
1052  /*clipbounds=*/nullptr, interrupter);
1053 
1054  LeafManagerT manager(points.tree());
1055  tbb::parallel_reduce(manager.leafRange(), op);
1056 
1057  typename SdfT::Ptr surface =
1058  initSdfFromMasks<SdfT, MaskTreeT>(transform, bg, op.mask(), op.maskoff());
1059 
1060  if (interrupter) interrupter->end();
1061  return surface;
1062 }
1063 
1064 template <typename SdfT, typename PointDataGridT>
1065 inline typename SdfT::Ptr
1066 initFixedSmoothSdf(const PointDataGridT& points,
1067  math::Transform::Ptr transform,
1068  const typename SdfT::ValueType bg,
1069  const Real maxBandRadius,
1070  const CoordBBox& bounds,
1071  util::NullInterrupter* interrupter)
1072 {
1073  using LeafManagerT = tree::LeafManager<const typename PointDataGridT::TreeType>;
1074  using MaskTreeT = typename SdfT::TreeType::template ValueConverter<ValueMask>::Type;
1075 
1076  if (interrupter) interrupter->start("Generating uniform surface topology");
1077 
1078  // @note Currently don't use min radii to deactivate, can't compute
1079  // this with the ZB kernel
1080  FixedSurfaceMaskOp<MaskTreeT> op(points.transform(),
1081  *transform, /*minBandRadius=*/0.0, maxBandRadius, &bounds, interrupter);
1082 
1083  LeafManagerT manager(points.tree());
1084  tbb::parallel_reduce(manager.leafRange(), op);
1085 
1086  typename SdfT::Ptr surface =
1087  initSdfFromMasks<SdfT, MaskTreeT>(transform, bg, op.mask(), op.maskoff());
1088 
1089  if (interrupter) interrupter->end();
1090  return surface;
1091 }
1092 
1093 template <typename SdfT,
1094  typename PointDataGridT,
1095  typename RadiusTreeT>
1096 inline typename SdfT::Ptr
1097 initVariableSmoothSdf(const PointDataGridT& points,
1098  math::Transform::Ptr transform,
1099  const typename SdfT::ValueType bg,
1100  const RadiusTreeT& maxTree,
1101  const Real scale,
1102  const Real halfband,
1103  const CoordBBox& bounds,
1104  util::NullInterrupter* interrupter)
1105 {
1106  using LeafManagerT = tree::LeafManager<const typename PointDataGridT::TreeType>;
1107  using MaskTreeT = typename SdfT::TreeType::template ValueConverter<ValueMask>::Type;
1108 
1109  if (interrupter) interrupter->start("Generating variable surface topology");
1110 
1111  // @note Currently don't use min radii/tree to deactivate, can't
1112  // compute this with the ZB kernel
1113  VariableSurfaceMaskOp<RadiusTreeT, MaskTreeT>
1114  op(points.transform(), *transform, nullptr, maxTree, /*minscale=*/1.0,
1115  scale, halfband, &bounds, interrupter);
1116 
1117  LeafManagerT manager(points.tree());
1118  tbb::parallel_reduce(manager.leafRange(), op);
1119 
1120  typename SdfT::Ptr surface =
1121  initSdfFromMasks<SdfT, MaskTreeT>(transform, bg, op.mask(), op.maskoff());
1122 
1123  if (interrupter) interrupter->end();
1124  return surface;
1125 }
1126 
1127 template <typename PointDataTreeT,
1128  typename AttributeTypes>
1129 inline GridPtrVec
1130 transferAttributes(const tree::LeafManager<const PointDataTreeT>& manager,
1131  const std::vector<std::string>& attributes,
1132  const Int64Tree& cpg,
1133  const math::Transform::Ptr transform)
1134 {
1135  OPENVDB_ASSERT(manager.leafCount() != 0);
1136  // masking uses upper 32 bits for leaf node id
1137  // @note we can use a point list impl to support larger counts
1138  // if necessary but this is far faster
1139  OPENVDB_ASSERT(manager.leafCount() <
1141 
1142  // linearise cpg to avoid having to probe data
1143  const tree::LeafManager<const Int64Tree> cpmanager(cpg);
1144 
1145  auto transfer = [&](auto& tree, const size_t attrIdx) {
1146  using TreeType = typename std::decay<decltype(tree)>::type;
1147  using HandleT = AttributeHandle<typename TreeType::ValueType>;
1148 
1149  // init topology
1150  tree.topologyUnion(cpg);
1151  tree::LeafManager<TreeType> lm(tree);
1152 
1153  // init values
1154  lm.foreach([&manager, &cpmanager, attrIdx]
1155  (auto& leaf, const size_t idx) {
1156  auto voxel = leaf.beginValueOn();
1157  if (!voxel) return;
1158 
1159  auto* data = leaf.buffer().data();
1160  const Int64* ids = cpmanager.leaf(idx).buffer().data();
1161  Index prev = Index(ids[voxel.pos()] >> 32);
1162  typename HandleT::UniquePtr handle =
1163  std::make_unique<HandleT>(manager.leaf(prev).constAttributeArray(attrIdx));
1164 
1165  for (; voxel; ++voxel) {
1166  const Int64 hash = ids[voxel.pos()];
1167  const Index lfid = Index(hash >> 32); // upper 32 bits to leaf id
1168  const Index ptid = static_cast<Index>(hash); // lower
1169  if (lfid != prev) {
1170  handle = std::make_unique<HandleT>(manager.leaf(lfid).constAttributeArray(attrIdx));
1171  prev = lfid;
1172  }
1173  data[voxel.pos()] = handle->get(ptid);
1174  }
1175  });
1176  };
1177 
1178  GridPtrVec grids;
1179  grids.reserve(attributes.size());
1180  const auto& attrSet = manager.leaf(0).attributeSet();
1181  tbb::task_group tasks;
1182 
1183  for (const auto& name : attributes) {
1184  const size_t attrIdx = attrSet.find(name);
1185  if (attrIdx == points::AttributeSet::INVALID_POS) continue;
1186  if (attrSet.get(attrIdx)->stride() != 1) {
1187  OPENVDB_THROW(RuntimeError, "Transfer of attribute " + name +
1188  " not supported since it is strided");
1189  }
1190 
1191  const std::string& type = attrSet.descriptor().valueType(attrIdx);
1192  GridBase::Ptr grid = nullptr;
1193  AttributeTypes::foreach([&](const auto& v) {
1194  using ValueType = typename std::remove_const<typename std::decay<decltype(v)>::type>::type;
1195  using TreeT = typename PointDataTreeT::template ValueConverter<ValueType>::Type;
1196  if (!grid && typeNameAsString<ValueType>() == type) {
1197  auto typed = Grid<TreeT>::create();
1198  grid = typed;
1199  typed->setName(name);
1200  typed->setTransform(transform);
1201  tasks.run([typed, attrIdx, transfer] { transfer(typed->tree(), attrIdx); });
1202  grids.emplace_back(grid);
1203  }
1204  });
1205 
1206  if (!grid) {
1207  OPENVDB_THROW(RuntimeError, "No support for attribute type " + type +
1208  " built during closest point surface transfer");
1209  }
1210  }
1211 
1212  tasks.wait();
1213  return grids;
1214 }
1215 
1216 template <typename SdfT,
1217  template <typename, typename, typename, typename, bool> class TransferInterfaceT,
1218  typename AttributeTypes,
1219  typename FilterT,
1220  typename PointDataGridT,
1221  typename ...Args>
1222 inline GridPtrVec
1223 doRasterizeSurface(const PointDataGridT& points,
1224  const std::vector<std::string>& attributes,
1225  SdfT& surface,
1226  Args&&... args)
1227 {
1228  using RadRefT = typename std::tuple_element<1, std::tuple<Args...>>::type;
1229  using RadT = typename std::remove_reference<RadRefT>::type;
1230 
1231  GridPtrVec grids;
1232  const auto leaf = points.constTree().cbeginLeaf();
1233  if (!leaf) return grids;
1234 
1235  const size_t pidx = leaf->attributeSet().find("P");
1236  if (pidx == AttributeSet::INVALID_POS) {
1237  OPENVDB_THROW(RuntimeError, "Failed to find position attribute");
1238  }
1239 
1240  // @note Can't split this out into a generic lambda yet as there
1241  // are compiler issues with capturing variadic arguments
1242  const NamePair& ptype = leaf->attributeSet().descriptor().type(pidx);
1243 
1244  if (attributes.empty()) {
1245  if (ptype.second == NullCodec::name()) {
1246  using TransferT = TransferInterfaceT<SdfT, NullCodec, RadT, FilterT, false>;
1247  TransferT transfer(pidx, args...);
1248  rasterize<PointDataGridT, TransferT>(points, transfer);
1249  }
1250  else {
1251  using TransferT = TransferInterfaceT<SdfT, UnknownCodec, RadT, FilterT, false>;
1252  TransferT transfer(pidx, args...);
1253  rasterize<PointDataGridT, TransferT>(points, transfer);
1254  }
1255  }
1256  else {
1257  Int64Tree cpg;
1258  cpg.topologyUnion(surface.tree());
1259  tree::LeafManager<const PointDataTree> manager(points.tree());
1260  // map point leaf nodes to their linear id
1261  // @todo sorted vector of leaf ptr-> index pair then lookup with binary search?
1262  std::unordered_map<const PointDataTree::LeafNodeType*, Index> ids;
1263  manager.foreach([&](auto& leafnode, size_t idx) { ids[&leafnode] = Index(idx); }, false);
1264 
1265  if (ptype.second == NullCodec::name()) {
1266  using TransferT = TransferInterfaceT<SdfT, NullCodec, RadT, FilterT, true>;
1267  TransferT transfer(pidx, args..., &cpg, &ids);
1268  rasterize<PointDataGridT, TransferT>(points, transfer);
1269  }
1270  else {
1271  using TransferT = TransferInterfaceT<SdfT, UnknownCodec, RadT, FilterT, true>;
1272  TransferT transfer(pidx, args..., &cpg, &ids);
1273  rasterize<PointDataGridT, TransferT>(points, transfer);
1274  }
1275 
1276  ids.clear();
1277  tools::pruneInactive(cpg);
1278  // Build attribute transfer grids
1279  grids = transferAttributes
1280  <typename PointDataGrid::TreeType, AttributeTypes>
1281  (manager, attributes, cpg, surface.transformPtr());
1282  }
1283 
1284  return grids;
1285 }
1286 
1287 template <typename PointDataGridT,
1288  typename SdfT,
1289  typename SettingsT>
1290 GridPtrVec
1291 rasterizeSpheres(const PointDataGridT& points,
1292  const SettingsT& settings,
1293  const typename SettingsT::FilterType& filter)
1294 {
1295  static_assert(IsSpecializationOf<PointDataGridT, Grid>::value);
1296  static_assert(IsSpecializationOf<SettingsT, SphereSettings>::value);
1297 
1298  using AttributeTypes = typename SettingsT::AttributeTypes;
1299  using FilterT = typename SettingsT::FilterType;
1300 
1301  const std::vector<std::string>& attributes = settings.attributes;
1302  const Real halfband = settings.halfband;
1303  auto* interrupter = settings.interrupter;
1304 
1305  math::Transform::Ptr transform = settings.transform;
1306  if (!transform) transform = points.transform().copy();
1307  const Real vs = transform->voxelSize()[0];
1308  const typename SdfT::ValueType background =
1309  static_cast<typename SdfT::ValueType>(vs * halfband);
1310 
1311  typename SdfT::Ptr surface;
1312  GridPtrVec grids;
1313 
1314  if (settings.radius.empty()) {
1315  // search distance at the SDF transform, including its half band
1316  const Real radiusIndexSpace = settings.radiusScale / vs;
1317  const FixedBandRadius<Real> rad(radiusIndexSpace, float(halfband));
1318  const Real minBandRadius = rad.min();
1319  const Real maxBandRadius = rad.max();
1320  const size_t width = static_cast<size_t>(math::RoundUp(maxBandRadius));
1321 
1322  surface = initFixedSdf<SdfT>
1323  (points, transform, background, minBandRadius, maxBandRadius, interrupter);
1324 
1325  if (interrupter) interrupter->start("Rasterizing particles to level set using constant Spheres");
1326 
1327  grids = doRasterizeSurface<SdfT, SphericalTransfer, AttributeTypes, FilterT>
1328  (points, attributes, *surface,
1329  width, rad, points.transform(), filter, interrupter, *surface); // args
1330  }
1331  else {
1332  using RadiusT = typename SettingsT::RadiusAttributeType;
1333  using PointDataTreeT = typename PointDataGridT::TreeType;
1334  using RadTreeT = typename PointDataTreeT::template ValueConverter<RadiusT>::Type;
1335 
1336  RadiusT min(0), max(0);
1337  typename RadTreeT::Ptr mintree(new RadTreeT), maxtree(new RadTreeT);
1338  points::evalMinMax<RadiusT, UnknownCodec>
1339  (points.tree(), settings.radius, min, max, filter, mintree.get(), maxtree.get());
1340 
1341  // search distance at the SDF transform
1342  const RadiusT indexSpaceScale = RadiusT(settings.radiusScale / vs);
1343  surface = initVariableSdf<SdfT>
1344  (points, transform, background, *mintree, *maxtree,
1345  indexSpaceScale, halfband, interrupter);
1346  mintree.reset();
1347  maxtree.reset();
1348 
1349  const auto leaf = points.constTree().cbeginLeaf();
1350  if (!leaf) return GridPtrVec(1, surface);
1351 
1352  // max possible index space radius
1353  const size_t width = static_cast<size_t>
1354  (math::RoundUp((Real(max) * indexSpaceScale) + Real(halfband)));
1355 
1356  const size_t ridx = leaf->attributeSet().find(settings.radius);
1357  if (ridx == AttributeSet::INVALID_POS) {
1358  OPENVDB_THROW(RuntimeError, "Failed to find radius attribute \"" + settings.radius + "\"");
1359  }
1360  VaryingBandRadius<RadiusT> rad(ridx, RadiusT(halfband), indexSpaceScale);
1361 
1362  if (interrupter) interrupter->start("Rasterizing particles to level set using variable Spheres");
1363 
1364  grids = doRasterizeSurface<SdfT, SphericalTransfer, AttributeTypes, FilterT>
1365  (points, attributes, *surface,
1366  width, rad, points.transform(), filter, interrupter, *surface); // args
1367  }
1368 
1369  if (interrupter) interrupter->end();
1370 
1371  tools::pruneLevelSet(surface->tree());
1372  grids.insert(grids.begin(), surface);
1373  return grids;
1374 }
1375 
1376 
1377 template <typename PointDataGridT,
1378  typename SdfT,
1379  typename SettingsT>
1380 GridPtrVec
1381 rasterizeSmoothSpheres(const PointDataGridT& points,
1382  const SettingsT& settings,
1383  const typename SettingsT::FilterType& filter)
1384 {
1385  static_assert(IsSpecializationOf<PointDataGridT, Grid>::value);
1386  static_assert(IsSpecializationOf<SettingsT, SmoothSphereSettings>::value);
1387 
1388  using AttributeTypes = typename SettingsT::AttributeTypes;
1389  using FilterT = typename SettingsT::FilterType;
1390 
1391  const std::vector<std::string>& attributes = settings.attributes;
1392  const Real halfband = settings.halfband;
1393  auto* interrupter = settings.interrupter;
1394 
1395  math::Transform::Ptr transform = settings.transform;
1396  if (!transform) transform = points.transform().copy();
1397  const Real vs = transform->voxelSize()[0];
1398  const typename SdfT::ValueType background =
1399  static_cast<typename SdfT::ValueType>(vs * halfband);
1400 
1401  const Real indexSpaceSearch = settings.searchRadius / vs;
1402  // max possible index space search radius
1403  const size_t width = static_cast<size_t>(math::RoundUp(indexSpaceSearch));
1404 
1405  // The topology we need to activate is at a distance based on the maximum
1406  // radii of each point and the uniform search radius. Even though we're
1407  // guaranteed to be generating new positions within the distribution of
1408  // point neighbours, these positions may end up outside of active topology
1409  // were we _only_ to use the radius of the particles for topology
1410  // activation.
1411  const Real maxActivationRadius = std::max(settings.searchRadius, settings.radiusScale) / vs;
1412  const auto leaf = points.constTree().cbeginLeaf();
1413 
1414  // Compute estimated max bounds for clipping. This is used if the search
1415  // radius is larger than the max particle radius (as we don't need to
1416  // activate topology further outside the bounds of the point data grid).
1417  // This bounds is expanded by the halfband + max radii
1418  CoordBBox bounds;
1419  points.tree().evalLeafBoundingBox(bounds);
1420 
1421  typename SdfT::Ptr surface;
1422  GridPtrVec grids;
1423 
1424  if (settings.radius.empty()) {
1425  // This is the max possible distance we need to activate, but we'll
1426  // clip this at the edges of the point bounds (as the ZB kernel will
1427  // only create positions in between points).
1428  const FixedBandRadius<Real> bands(maxActivationRadius, float(halfband));
1429  const Real max = bands.max();
1430 
1431  // Compute max radius in index space and expand bounds
1432  bounds.expand(static_cast<Coord::ValueType>(halfband + math::Round(settings.radiusScale / vs)));
1433  // init surface
1434  surface = initFixedSmoothSdf<SdfT>
1435  (points, transform, background, max, bounds, interrupter);
1436 
1437  if (!leaf) return GridPtrVec(1, surface);
1438 
1439  const FixedRadius<Real> rad(settings.radiusScale / vs);
1440  if (interrupter) interrupter->start("Rasterizing particles to level set using constant Zhu-Bridson");
1441 
1442  grids = doRasterizeSurface<SdfT, AveragePositionTransfer, AttributeTypes, FilterT>
1443  (points, attributes, *surface,
1444  width, rad, indexSpaceSearch, points.transform(), filter, interrupter, *surface); // args
1445  }
1446  else {
1447  using RadiusT = typename SettingsT::RadiusAttributeType;
1448  using PointDataTreeT = typename PointDataGridT::TreeType;
1449  using RadTreeT = typename PointDataTreeT::template ValueConverter<RadiusT>::Type;
1450 
1451  // We currently don't use the min values for the ZB kernel topology
1452  // activation.
1453  // @todo We should be able to deactivate on some metric
1454  RadiusT min(0), max(0);
1455  typename RadTreeT::Ptr maxtree(new RadTreeT);
1456  points::evalMinMax<RadiusT, UnknownCodec>
1457  (points.tree(), settings.radius, min, max, filter, nullptr, maxtree.get());
1458 
1459  if ((settings.searchRadius > settings.radiusScale) && (min < settings.searchRadius)) {
1460  // set radius tree values to search distances if they are less,
1461  // just for the surface topology initialization. This is the max
1462  // possible distance we need to activate, but we'll clip this at
1463  // the edges of the point bounds (as the ZB kernel will only
1464  // create positions in-between points).
1465  tools::foreach(maxtree->beginValueOn(),
1466  [&](auto& iter) {
1467  iter.modifyValue([&](auto& r) {
1468  if (r < settings.searchRadius) {
1469  // initVariableSmoothSdf scales radii by (settings.radiusScale/vs).
1470  // We don't want to scale the search radii by the radius scale, so
1471  // cancel it out here.
1472  r = static_cast<RadiusT>(settings.searchRadius / settings.radiusScale);
1473  }
1474  });
1475  }, /*thread=*/true, /*shared=*/true);
1476  }
1477 
1478  const RadiusT indexSpaceScale = RadiusT(settings.radiusScale / vs);
1479  // Compute max radius in index space and expand bounds
1480  bounds.expand(static_cast<Coord::ValueType>(halfband + math::Round(max * indexSpaceScale)));
1481 
1482  // init surface
1483  surface = initVariableSmoothSdf<SdfT>
1484  (points, transform, background, *maxtree,
1485  indexSpaceScale, halfband, bounds, interrupter);
1486  maxtree.reset();
1487 
1488  if (!leaf) return GridPtrVec(1, surface);
1489 
1490  const size_t ridx = leaf->attributeSet().find(settings.radius);
1491  if (ridx == AttributeSet::INVALID_POS) {
1492  OPENVDB_THROW(RuntimeError, "Failed to find radius attribute");
1493  }
1494 
1495  VaryingRadius<RadiusT> rad(ridx, indexSpaceScale);
1496  if (interrupter) interrupter->start("Rasterizing particles to level set using variable Zhu-Bridson");
1497 
1498  grids = doRasterizeSurface<SdfT, AveragePositionTransfer, AttributeTypes, FilterT>
1499  (points, attributes, *surface,
1500  width, rad, indexSpaceSearch, points.transform(), filter, interrupter, *surface); // args
1501  }
1502 
1503  if (interrupter) interrupter->end();
1504 
1505  tools::pruneInactive(surface->tree());
1506  grids.insert(grids.begin(), surface);
1507  return grids;
1508 }
1509 
1510 /// @brief prototype - definition lives in impl/PointRasterizeEllipsoidsSDF.h
1511 template <typename PointDataGridT, typename SdfT, typename SettingsT>
1512 GridPtrVec rasterizeEllipsoids(const PointDataGridT&, const SettingsT&, const typename SettingsT::FilterType&);
1513 
1514 } // namespace rasterize_sdf_internal
1515 
1516 /// @endcond
1517 
1518 ///////////////////////////////////////////////////
1519 ///////////////////////////////////////////////////
1520 
1521 template <typename PointDataGridT,
1522  typename SdfT,
1523  typename SettingsT>
1524 GridPtrVec
1525 rasterizeSdf(const PointDataGridT& points, const SettingsT& settings)
1526 {
1527  const typename SettingsT::FilterType* filter = settings.filter;
1528 
1529  if constexpr (!std::is_same<typename SettingsT::FilterType, NullFilter>::value) {
1530  // To avoid rasterizeSdf invoking (at compile time) its sub methods for
1531  // both NullFilter and a custom filter, disallow the filter value on the
1532  // settings structs to be a nullptr. We allow it for NullFilters where
1533  // we can create a trivial static instance below and use that instead.
1534  if (!filter) {
1536  "A nullptr for a custom point-filter cannot be passed to rasterizeSdf().");
1537  }
1538  }
1539  else {
1540  if (!filter) {
1541  // We create a dummy static instance for NullFilters if none has
1542  // been provided
1543  static const NullFilter sNullFilter;
1544  filter = &sNullFilter;
1545  }
1546  }
1547  OPENVDB_ASSERT(filter);
1548 
1550  return rasterize_sdf_internal::rasterizeSpheres<PointDataGridT, SdfT, SettingsT>(points, settings, *filter);
1551  }
1553  return rasterize_sdf_internal::rasterizeSmoothSpheres<PointDataGridT, SdfT, SettingsT>(points, settings, *filter);
1554  }
1556  return rasterize_sdf_internal::rasterizeEllipsoids<PointDataGridT, SdfT, SettingsT>(points, settings, *filter);
1557  }
1558  else {
1559  static_assert(!sizeof(SettingsT),
1560  "No valid implementation for provided rasterization settings exists.");
1561  return GridPtrVec(); // silences irrelevant compiler warnings
1562  }
1563 }
1564 
1565 ///////////////////////////////////////////////////
1566 ///////////////////////////////////////////////////
1567 
1568 /// @deprecated The following API calls are deprecated in favour of the more
1569 /// general rasterizeSdf<>() method which determines its behaviour based on
1570 /// the passed settings struct. These methods were introduced in VDB 9.1.
1571 /// so are not currently marked as deprecated but should be marked as such
1572 /// from the first minor release after OpenVDB 11.0.0.
1573 
1574 template <typename PointDataGridT,
1575  typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1576  typename FilterT = NullFilter>
1577 typename SdfT::Ptr
1578 rasterizeSpheres(const PointDataGridT& points,
1579  const Real radius,
1580  const Real halfband = LEVEL_SET_HALF_WIDTH,
1581  math::Transform::Ptr transform = nullptr,
1582  const FilterT& filter = NullFilter(),
1583  util::NullInterrupter* interrupter = nullptr)
1584 {
1585  auto grids =
1586  rasterizeSpheres<PointDataGridT, TypeList<>, SdfT, FilterT>
1587  (points, radius, {}, halfband, transform, filter, interrupter);
1588  return StaticPtrCast<SdfT>(grids.front());
1589 }
1590 
1591 template <typename PointDataGridT,
1592  typename RadiusT = float,
1593  typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1594  typename FilterT = NullFilter>
1595 typename SdfT::Ptr
1596 rasterizeSpheres(const PointDataGridT& points,
1597  const std::string& radius,
1598  const Real scale = 1.0,
1599  const Real halfband = LEVEL_SET_HALF_WIDTH,
1600  math::Transform::Ptr transform = nullptr,
1601  const FilterT& filter = NullFilter(),
1602  util::NullInterrupter* interrupter = nullptr)
1603 {
1604  auto grids =
1605  rasterizeSpheres<PointDataGridT, TypeList<>, RadiusT, SdfT, FilterT>
1606  (points, radius, {}, scale, halfband, transform, filter, interrupter);
1607  return StaticPtrCast<SdfT>(grids.front());
1608 }
1609 
1610 template <typename PointDataGridT,
1611  typename AttributeTypes,
1612  typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1613  typename FilterT = NullFilter>
1614 GridPtrVec
1615 rasterizeSpheres(const PointDataGridT& points,
1616  const Real radius,
1617  const std::vector<std::string>& attributes,
1618  const Real halfband = LEVEL_SET_HALF_WIDTH,
1619  math::Transform::Ptr transform = nullptr,
1620  const FilterT& filter = NullFilter(),
1621  util::NullInterrupter* interrupter = nullptr)
1622 {
1624  s.radius = "";
1625  s.radiusScale = radius;
1626  s.halfband = halfband;
1627  s.attributes = attributes;
1628  s.transform = transform;
1629  s.filter = &filter;
1630  s.interrupter = interrupter;
1631  return rasterizeSdf<PointDataGridT, SdfT>(points, s);
1632 }
1633 
1634 template <typename PointDataGridT,
1635  typename AttributeTypes,
1636  typename RadiusT = float,
1637  typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1638  typename FilterT = NullFilter>
1639 GridPtrVec
1640 rasterizeSpheres(const PointDataGridT& points,
1641  const std::string& radius,
1642  const std::vector<std::string>& attributes,
1643  const Real scale = 1.0,
1644  const Real halfband = LEVEL_SET_HALF_WIDTH,
1645  math::Transform::Ptr transform = nullptr,
1646  const FilterT& filter = NullFilter(),
1647  util::NullInterrupter* interrupter = nullptr)
1648 {
1649  // mimics old behaviour - rasterize_sdf_internal::rasterizeSmoothSpheres
1650  // will fall back to uniform rasterization if the attribute doesn't exist.
1651  if (auto leaf = points.constTree().cbeginLeaf()) {
1652  const size_t ridx = leaf->attributeSet().find(radius);
1653  if (ridx == AttributeSet::INVALID_POS) {
1654  OPENVDB_THROW(RuntimeError, "Failed to find radius attribute \"" + radius + "\"");
1655  }
1656  }
1658  s.radius = radius;
1659  s.radiusScale = scale;
1660  s.halfband = halfband;
1661  s.attributes = attributes;
1662  s.transform = transform;
1663  s.filter = &filter;
1664  s.interrupter = interrupter;
1665  return rasterizeSdf<PointDataGridT, SdfT>(points, s);
1666 }
1667 
1668 ///////////////////////////////////////////////////
1669 
1670 template <typename PointDataGridT,
1671  typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1672  typename FilterT = NullFilter>
1673 typename SdfT::Ptr
1674 rasterizeSmoothSpheres(const PointDataGridT& points,
1675  const Real radius,
1676  const Real searchRadius,
1677  const Real halfband = LEVEL_SET_HALF_WIDTH,
1678  math::Transform::Ptr transform = nullptr,
1679  const FilterT& filter = NullFilter(),
1680  util::NullInterrupter* interrupter = nullptr)
1681 {
1682  auto grids =
1683  rasterizeSmoothSpheres<PointDataGridT, TypeList<>, SdfT, FilterT>
1684  (points, radius, searchRadius, {}, halfband, transform, filter, interrupter);
1685  return StaticPtrCast<SdfT>(grids.front());
1686 }
1687 
1688 template <typename PointDataGridT,
1689  typename RadiusT = float,
1690  typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1691  typename FilterT = NullFilter>
1692 typename SdfT::Ptr
1693 rasterizeSmoothSpheres(const PointDataGridT& points,
1694  const std::string& radius,
1695  const Real radiusScale,
1696  const Real searchRadius,
1697  const Real halfband = LEVEL_SET_HALF_WIDTH,
1698  math::Transform::Ptr transform = nullptr,
1699  const FilterT& filter = NullFilter(),
1700  util::NullInterrupter* interrupter = nullptr)
1701 {
1702  auto grids =
1703  rasterizeSmoothSpheres<PointDataGridT, TypeList<>, RadiusT, SdfT, FilterT>
1704  (points, radius, radiusScale, searchRadius, {}, halfband, transform, filter, interrupter);
1705  return StaticPtrCast<SdfT>(grids.front());
1706 }
1707 
1708 template <typename PointDataGridT,
1709  typename AttributeTypes,
1710  typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1711  typename FilterT = NullFilter>
1712 GridPtrVec
1713 rasterizeSmoothSpheres(const PointDataGridT& points,
1714  const Real radius,
1715  const Real searchRadius,
1716  const std::vector<std::string>& attributes,
1717  const Real halfband = LEVEL_SET_HALF_WIDTH,
1718  math::Transform::Ptr transform = nullptr,
1719  const FilterT& filter = NullFilter(),
1720  util::NullInterrupter* interrupter = nullptr)
1721 {
1723  s.radius = "";
1724  s.radiusScale = radius;
1725  s.halfband = halfband;
1726  s.attributes = attributes;
1727  s.transform = transform;
1728  s.filter = &filter;
1729  s.interrupter = interrupter;
1730  s.searchRadius = searchRadius;
1731  return rasterizeSdf<PointDataGridT, SdfT>(points, s);
1732 }
1733 
1734 template <typename PointDataGridT,
1735  typename AttributeTypes,
1736  typename RadiusT = float,
1737  typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1738  typename FilterT = NullFilter>
1739 GridPtrVec
1740 rasterizeSmoothSpheres(const PointDataGridT& points,
1741  const std::string& radius,
1742  const Real radiusScale,
1743  const Real searchRadius,
1744  const std::vector<std::string>& attributes,
1745  const Real halfband = LEVEL_SET_HALF_WIDTH,
1746  math::Transform::Ptr transform = nullptr,
1747  const FilterT& filter = NullFilter(),
1748  util::NullInterrupter* interrupter = nullptr)
1749 {
1750  // mimics old behaviour - rasterize_sdf_internal::rasterizeSmoothSpheres
1751  // will fall back to uniform rasterization if the attribute doesn't exist.
1752  if (auto leaf = points.constTree().cbeginLeaf()) {
1753  const size_t ridx = leaf->attributeSet().find(radius);
1754  if (ridx == AttributeSet::INVALID_POS) {
1755  OPENVDB_THROW(RuntimeError, "Failed to find radius attribute \"" + radius + "\"");
1756  }
1757  }
1759  s.radius = radius;
1760  s.radiusScale = radiusScale;
1761  s.halfband = halfband;
1762  s.attributes = attributes;
1763  s.transform = transform;
1764  s.filter = &filter;
1765  s.interrupter = interrupter;
1766  s.searchRadius = searchRadius;
1767  return rasterizeSdf<PointDataGridT, SdfT>(points, s);
1768 }
1769 
1770 
1771 } // namespace points
1772 } // namespace OPENVDB_VERSION_NAME
1773 } // namespace openvdb
1774 
1775 #endif //OPENVDB_POINTS_RASTERIZE_SDF_IMPL_HAS_BEEN_INCLUDED
Helper metafunction used to determine if the first template parameter is a specialization of the clas...
Definition: Types.h:263
#define OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN
Bracket code with OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN/_END, to inhibit warnings about type conve...
Definition: Platform.h:244
Definition: Types.h:526
#define OPENVDB_THROW(exception, message)
Definition: Exceptions.h:74
uint64_t Index64
Definition: Types.h:53
OPENVDB_IMPORT void initialize()
Global registration of native Grid, Transform, Metadata and Point attribute types. Also initializes blosc (if enabled).
std::vector< GridBase::Ptr > GridPtrVec
Definition: Grid.h:508
static Ptr create()
Return a new grid with background value zero.
Definition: Grid.h:1343
float Sqrt(float x)
Return the square root of a floating-point value.
Definition: Math.h:761
Real searchRadius
Definition: PointRasterizeSDF.h:266
void deactivate(GridOrTree &, const typename GridOrTree::ValueType &value, const typename GridOrTree::ValueType &tolerance=zeroVal< typename GridOrTree::ValueType >(), const bool threaded=true)
Mark as inactive any active tiles or voxels in the given grid or tree whose values are equal to value...
Definition: Activate.h:203
void foreach(T &&t, const F &func, std::integer_sequence< size_t, Is... >)
Definition: PointTransfer.h:416
tree::Tree4< int64_t, 5, 4, 3 >::Type Int64Tree
Definition: openvdb.h:57
OutGridT XformOp & op
Definition: ValueTransformer.h:139
Index32 Index
Definition: Types.h:54
GridPtrVec rasterizeSdf(const PointDataGridT &points, const SettingsT &settings)
Perform point rasterzation to produce a signed distance field.
Definition: PointRasterizeSDFImpl.h:1525
std::string radius
Definition: PointRasterizeSDF.h:169
float RoundUp(float x)
Return x rounded up to the nearest integer.
Definition: Math.h:787
#define OPENVDB_NO_TYPE_CONVERSION_WARNING_END
Definition: Platform.h:245
A no-op filter that can be used when iterating over all indices.
Definition: IndexIterator.h:51
Vec3< int32_t > Vec3i
Definition: Vec3.h:662
const FilterT * filter
Definition: PointRasterizeSDF.h:221
Base class for interrupters.
Definition: NullInterrupter.h:25
MatType scale(const Vec3< typename MatType::value_type > &s)
Return a matrix that scales by s.
Definition: Mat.h:615
math::BBox< Vec3d > BBoxd
Definition: Types.h:84
#define OPENVDB_ASSERT(X)
Definition: Assert.h:41
GridPtrVec rasterizeSpheres(const PointDataGridT &points, const std::string &radius, const std::vector< std::string > &attributes, const Real scale=1.0, const Real halfband=LEVEL_SET_HALF_WIDTH, math::Transform::Ptr transform=nullptr, const FilterT &filter=NullFilter(), util::NullInterrupter *interrupter=nullptr)
Definition: PointRasterizeSDFImpl.h:1640
SharedPtr< Transform > Ptr
Definition: Transform.h:42
Definition: Exceptions.h:63
Smoothed point distribution based sphere stamping with a uniform radius or varying radius and optiona...
Definition: PointRasterizeSDF.h:235
math::Vec3< float > Vec3f
Definition: Types.h:74
const std::enable_if<!VecTraits< T >::IsVec, T >::type & min(const T &a, const T &b)
Definition: Composite.h:106
Type Pow2(Type x)
Return x2.
Definition: Math.h:548
NumericAttributeTypes::Append< Vec3AttributeTypes >::Append< Mat3AttributeTypes >::Append< Mat4AttributeTypes >::Append< QuatAttributeTypes >::Append< points::GroupAttributeArray >::Append< points::StringAttributeArray >::Append< points::TypedAttributeArray< bool >> AttributeTypes
The attribute array types which OpenVDB will register by default.
Definition: openvdb.h:186
Definition: Exceptions.h:13
float Round(float x)
Return x rounded to the nearest integer.
Definition: Math.h:819
RadiusScaleT radiusScale
Definition: PointRasterizeSDF.h:180
std::pair< Name, Name > NamePair
Definition: AttributeArray.h:40
GridPtrVec rasterizeEllipsoids(const PointDataGridT &points, const SettingsT &settings, const typename SettingsT::FilterType &filter)
Definition: PointRasterizeEllipsoidsSDFImpl.h:582
static const Real LEVEL_SET_HALF_WIDTH
Definition: Types.h:532
__hostdev__ uint32_t hash(uint32_t x)
Definition: common.h:14
void activate(GridOrTree &, const typename GridOrTree::ValueType &value, const typename GridOrTree::ValueType &tolerance=zeroVal< typename GridOrTree::ValueType >(), const bool threaded=true)
Mark as active any inactive tiles or voxels in the given grid or tree whose values are equal to value...
Definition: Activate.h:179
typename T::ValueType ElementType
Definition: Types.h:318
double Real
Definition: Types.h:60
bool wasInterrupted(T *i, int percent=-1)
Definition: NullInterrupter.h:49
Vec3< double > Vec3d
Definition: Vec3.h:665
std::vector< std::string > attributes
Definition: PointRasterizeSDF.h:216
math::Transform::Ptr transform
Definition: PointRasterizeSDF.h:187
GridPtrVec rasterizeSmoothSpheres(const PointDataGridT &points, const std::string &radius, const Real radiusScale, const Real searchRadius, const std::vector< std::string > &attributes, const Real halfband=LEVEL_SET_HALF_WIDTH, math::Transform::Ptr transform=nullptr, const FilterT &filter=NullFilter(), util::NullInterrupter *interrupter=nullptr)
Definition: PointRasterizeSDFImpl.h:1740
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
int64_t Int64
Definition: Types.h:57
Type Pow3(Type x)
Return x3.
Definition: Math.h:552
SharedPtr< GridBase > Ptr
Definition: Grid.h:80
const Type & Max(const Type &a, const Type &b)
Return the maximum of two values.
Definition: Math.h:595
Generic settings for narrow band spherical stamping with a uniform or varying radius and optionally w...
Definition: PointRasterizeSDF.h:156
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition: version.h.in:121
void split(ContainerT &out, const std::string &in, const char delim)
Definition: Name.h:43
Real halfband
Definition: PointRasterizeSDF.h:183
util::NullInterrupter * interrupter
Definition: PointRasterizeSDF.h:224
const std::enable_if<!VecTraits< T >::IsVec, T >::type & max(const T &a, const T &b)
Definition: Composite.h:110
void pruneLevelSet(TreeT &tree, bool threaded=true, size_t grainSize=1)
Reduce the memory footprint of a tree by replacing nodes whose values are all inactive with inactive ...
Definition: Prune.h:390
#define OPENVDB_USE_VERSION_NAMESPACE
Definition: version.h.in:218