OpenVDB 12.1.0
Loading...
Searching...
No Matches
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
12namespace openvdb {
14namespace OPENVDB_VERSION_NAME {
15namespace points {
16
17/// @cond OPENVDB_DOCS_INTERNAL
18
19namespace rasterize_sdf_internal
20{
21
22/// @brief Define a fixed index space radius for point rasterization
23template <typename ValueT>
24struct 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; }
33private:
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.
41template <typename ValueT>
42struct 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; }
70private:
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).
80template <>
81struct 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; }
91private:
92 const float mHalfBand;
93};
94
95/// @brief A varying per point radius with an optional scale
96template <typename ValueT, typename ScaleT = ValueT, typename CodecT = UnknownCodec>
97struct 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
123private:
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
130template <typename ValueT, typename ScaleT = ValueT, typename CodecT = UnknownCodec>
131struct 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
148private:
149 const float mHalfBand;
150};
151
152/// @brief Base class for SDF transfers which consolidates member data and
153/// some required interface methods.
154template <typename SdfT,
155 typename PositionCodecT,
156 typename RadiusType,
157 typename FilterT,
158 bool CPG>
159struct 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
199protected:
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
266protected:
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.
279template <typename SdfT,
280 typename PositionCodecT,
281 typename RadiusType,
282 typename FilterT,
283 bool CPG>
284struct 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
295 using ElemT = typename VecTraits<typename RadiusType::ValueType>::ElementType;
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
354protected:
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.
448template <typename SdfT,
449 typename PositionCodecT,
450 typename RadiusType,
451 typename FilterT,
452 bool CPG>
453struct 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
641protected:
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
662private:
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
669template <typename MaskTreeT = MaskTree>
670struct SurfaceMaskOp
671{
672public:
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
697protected:
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
819protected:
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
829template <typename MaskTreeT>
830struct 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
883private:
884 int32_t mMin, mMax;
885};
886
887/// @brief Initializes a variable activity mask
888template <typename RadiusTreeT, typename MaskTreeT>
889struct 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
931private:
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
963private:
964 const RadiusTreeT* const mMin;
965 const RadiusTreeT& mMax;
966 const Real mMinScale, mMaxScale, mHalfband;
967};
968
969template <typename SdfT, typename MaskTreeT>
970inline typename SdfT::Ptr
971initSdfFromMasks(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
983 tools::pruneInactive(*on);
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
1005template <typename SdfT, typename PointDataGridT>
1006inline typename SdfT::Ptr
1007initFixedSdf(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
1032template <typename SdfT,
1033 typename PointDataGridT,
1034 typename RadiusTreeT>
1035inline typename SdfT::Ptr
1036initVariableSdf(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
1064template <typename SdfT, typename PointDataGridT>
1065inline typename SdfT::Ptr
1066initFixedSmoothSdf(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
1093template <typename SdfT,
1094 typename PointDataGridT,
1095 typename RadiusTreeT>
1096inline typename SdfT::Ptr
1097initVariableSmoothSdf(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
1127template <typename PointDataTreeT,
1128 typename AttributeTypes>
1129inline GridPtrVec
1130transferAttributes(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() <
1140 size_t(std::numeric_limits<Index>::max()));
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
1216template <typename SdfT,
1217 template <typename, typename, typename, typename, bool> class TransferInterfaceT,
1218 typename AttributeTypes,
1219 typename FilterT,
1220 typename PointDataGridT,
1221 typename ...Args>
1222inline GridPtrVec
1223doRasterizeSurface(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
1287template <typename PointDataGridT,
1288 typename SdfT,
1289 typename SettingsT>
1291rasterizeSpheres(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
1377template <typename PointDataGridT,
1378 typename SdfT,
1379 typename SettingsT>
1381rasterizeSmoothSpheres(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
1511template <typename PointDataGridT, typename SdfT, typename SettingsT>
1512GridPtrVec rasterizeEllipsoids(const PointDataGridT&, const SettingsT&, const typename SettingsT::FilterType&);
1513
1514} // namespace rasterize_sdf_internal
1515
1516/// @endcond
1517
1518///////////////////////////////////////////////////
1519///////////////////////////////////////////////////
1520
1521template <typename PointDataGridT,
1522 typename SdfT,
1523 typename SettingsT>
1525rasterizeSdf(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 }
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
1574template <typename PointDataGridT,
1575 typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1576 typename FilterT = NullFilter>
1577typename SdfT::Ptr
1578rasterizeSpheres(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 =
1587 (points, radius, {}, halfband, transform, filter, interrupter);
1588 return StaticPtrCast<SdfT>(grids.front());
1589}
1590
1591template <typename PointDataGridT,
1592 typename RadiusT = float,
1593 typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1594 typename FilterT = NullFilter>
1595typename SdfT::Ptr
1596rasterizeSpheres(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
1610template <typename PointDataGridT,
1611 typename AttributeTypes,
1612 typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1613 typename FilterT = NullFilter>
1615rasterizeSpheres(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;
1632}
1633
1634template <typename PointDataGridT,
1635 typename AttributeTypes,
1636 typename RadiusT = float,
1637 typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1638 typename FilterT = NullFilter>
1640rasterizeSpheres(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;
1666}
1667
1668///////////////////////////////////////////////////
1669
1670template <typename PointDataGridT,
1671 typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1672 typename FilterT = NullFilter>
1673typename SdfT::Ptr
1674rasterizeSmoothSpheres(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 =
1684 (points, radius, searchRadius, {}, halfband, transform, filter, interrupter);
1685 return StaticPtrCast<SdfT>(grids.front());
1686}
1687
1688template <typename PointDataGridT,
1689 typename RadiusT = float,
1690 typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1691 typename FilterT = NullFilter>
1692typename SdfT::Ptr
1693rasterizeSmoothSpheres(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 =
1704 (points, radius, radiusScale, searchRadius, {}, halfband, transform, filter, interrupter);
1705 return StaticPtrCast<SdfT>(grids.front());
1706}
1707
1708template <typename PointDataGridT,
1709 typename AttributeTypes,
1710 typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1711 typename FilterT = NullFilter>
1713rasterizeSmoothSpheres(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;
1732}
1733
1734template <typename PointDataGridT,
1735 typename AttributeTypes,
1736 typename RadiusT = float,
1737 typename SdfT = typename PointDataGridT::template ValueConverter<float>::Type,
1738 typename FilterT = NullFilter>
1740rasterizeSmoothSpheres(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;
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
#define OPENVDB_ASSERT(X)
Definition Assert.h:41
#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
#define OPENVDB_NO_TYPE_CONVERSION_WARNING_END
Definition Platform.h:245
Definition Exceptions.h:63
SharedPtr< Transform > Ptr
Definition Transform.h:42
@ INVALID_POS
Definition AttributeSet.h:42
A no-op filter that can be used when iterating over all indices.
Definition IndexIterator.h:52
__hostdev__ uint32_t hash(uint32_t x)
Definition common.h:14
const Type & Max(const Type &a, const Type &b)
Return the maximum of two values.
Definition Math.h:595
MatType scale(const Vec3< typename MatType::value_type > &s)
Return a matrix that scales by s.
Definition Mat.h:615
Vec3< double > Vec3d
Definition Vec3.h:665
Vec3< int32_t > Vec3i
Definition Vec3.h:662
GridPtrVec rasterizeEllipsoids(const PointDataGridT &points, const SettingsT &settings, const typename SettingsT::FilterType &filter)
Definition PointRasterizeEllipsoidsSDFImpl.h:582
Definition AttributeArray.h:42
SdfT::Ptr rasterizeSpheres(const PointDataGridT &points, const Real radius, const Real halfband=LEVEL_SET_HALF_WIDTH, math::Transform::Ptr transform=nullptr, const FilterT &filter=NullFilter(), util::NullInterrupter *interrupter=nullptr)
Definition PointRasterizeSDFImpl.h:1578
GridPtrVec rasterizeSdf(const PointDataGridT &points, const SettingsT &settings)
Perform point rasterzation to produce a signed distance field.
Definition PointRasterizeSDFImpl.h:1525
SdfT::Ptr rasterizeSmoothSpheres(const PointDataGridT &points, const Real radius, const Real searchRadius, const Real halfband=LEVEL_SET_HALF_WIDTH, math::Transform::Ptr transform=nullptr, const FilterT &filter=NullFilter(), util::NullInterrupter *interrupter=nullptr)
Definition PointRasterizeSDFImpl.h:1674
const std::enable_if<!VecTraits< T >::IsVec, T >::type & max(const T &a, const T &b)
Definition Composite.h:110
const std::enable_if<!VecTraits< T >::IsVec, T >::type & min(const T &a, const T &b)
Definition Composite.h:106
OutGridT XformOp & op
Definition ValueTransformer.h:140
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 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
static const Real LEVEL_SET_HALF_WIDTH
Definition Types.h:532
std::vector< GridBase::Ptr > GridPtrVec
Definition Grid.h:508
Index32 Index
Definition Types.h:54
std::pair< Name, Name > NamePair
Definition AttributeArray.h:40
math::Vec3< float > Vec3f
Definition Types.h:74
double Real
Definition Types.h:60
int64_t Int64
Definition Types.h:57
OPENVDB_IMPORT void initialize()
Global registration of native Grid, Transform, Metadata and Point attribute types....
tree::Tree4< int64_t, 5, 4, 3 >::Type Int64Tree
Definition openvdb.h:57
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:178
math::BBox< Vec3d > BBoxd
Definition Types.h:84
uint64_t Index64
Definition Types.h:53
SharedPtr< T > StaticPtrCast(const SharedPtr< U > &ptr)
Return a new shared pointer that points to the same object as the given pointer after a static_cast.
Definition Types.h:146
Definition Exceptions.h:13
#define OPENVDB_THROW(exception, message)
Definition Exceptions.h:74
Helper metafunction used to determine if the first template parameter is a specialization of the clas...
Definition Types.h:263
Smoothed point distribution based sphere stamping with a uniform radius or varying radius and optiona...
Definition PointRasterizeSDF.h:237
std::vector< std::string > attributes
Definition PointRasterizeSDF.h:216
const FilterT * filter
Definition PointRasterizeSDF.h:221
math::Transform::Ptr transform
Definition PointRasterizeSDF.h:187
Real halfband
Definition PointRasterizeSDF.h:183
RadiusScaleT radiusScale
Definition PointRasterizeSDF.h:180
util::NullInterrupter * interrupter
Definition PointRasterizeSDF.h:224
Real searchRadius
Definition PointRasterizeSDF.h:266
std::string radius
Definition PointRasterizeSDF.h:169
Generic settings for narrow band spherical stamping with a uniform or varying radius and optionally w...
Definition PointRasterizeSDF.h:157
std::vector< std::string > attributes
Definition PointRasterizeSDF.h:216
const FilterT * filter
Definition PointRasterizeSDF.h:221
math::Transform::Ptr transform
Definition PointRasterizeSDF.h:187
Real halfband
Definition PointRasterizeSDF.h:183
RadiusScaleT radiusScale
Definition PointRasterizeSDF.h:180
util::NullInterrupter * interrupter
Definition PointRasterizeSDF.h:224
std::string radius
Definition PointRasterizeSDF.h:169
Base class for interrupters.
Definition NullInterrupter.h:26
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition version.h.in:121
#define OPENVDB_USE_VERSION_NAMESPACE
Definition version.h.in:218