OpenVDB  12.1.0
PointRasterizeEllipsoidsSDFImpl.h
Go to the documentation of this file.
1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 
4 /// @author Richard Jones, Nick Avramoussis
5 ///
6 /// @file PointRasterizeEllipsoidsSDFImpl.h
7 ///
8 
9 #ifndef OPENVDB_POINTS_RASTERIZE_ELLIPSOIDS_SDF_IMPL_HAS_BEEN_INCLUDED
10 #define OPENVDB_POINTS_RASTERIZE_ELLIPSOIDS_SDF_IMPL_HAS_BEEN_INCLUDED
11 
12 /// @brief Dev option to experiment with the ellipsoid kernel
13 /// - 0 Distance to unit sphere
14 /// - 1 Project unit distance on elipsoid normal
15 /// - 2 Distance to axis-aligned ellipse
16 #define OPENVDB_ELLIPSOID_KERNEL_MODE 2
17 
18 namespace openvdb {
20 namespace OPENVDB_VERSION_NAME {
21 namespace points {
22 
23 namespace rasterize_sdf_internal
24 {
25 
26 inline math::Vec3d
27 calcUnitEllipsoidBoundMaxSq(const math::Mat3s& ellipsoidTransform)
28 {
29  math::Vec3d boundMax;
30  for (int i = 0; i < 3; i++) {
31  boundMax[i] =
32  ellipsoidTransform(i,0) * ellipsoidTransform(i,0) +
33  ellipsoidTransform(i,1) * ellipsoidTransform(i,1) +
34  ellipsoidTransform(i,2) * ellipsoidTransform(i,2);
35  }
36  return boundMax;
37 }
38 
39 // compute a tight axis-aligned ellipsoid bounding box
40 // based on https://tavianator.com/exact-bounding-boxes-for-spheres-ellipsoids/
41 inline math::Vec3d
42 calcEllipsoidBoundMax(const math::Mat3s& ellipsoidTransform)
43 {
44  math::Vec3d boundMax = calcUnitEllipsoidBoundMaxSq(ellipsoidTransform);
45  boundMax[0] = std::sqrt(boundMax[0]);
46  boundMax[1] = std::sqrt(boundMax[1]);
47  boundMax[2] = std::sqrt(boundMax[2]);
48  return boundMax;
49 }
50 
52 {
53  EllipseIndicies(const points::AttributeSet::Descriptor& desc,
54  const std::string& rotation,
55  const std::string& pws)
56  : rotation(EllipseIndicies::getAttributeIndex<Mat3s>(desc, rotation, false))
57  , positionws(EllipseIndicies::getAttributeIndex<Vec3d>(desc, pws, true)) {}
58 
59  bool hasWorldSpacePosition() const { return positionws != std::numeric_limits<size_t>::max(); }
60 
61  const size_t rotation, positionws;
62 
63 private:
64  template<typename ValueT>
65  static inline size_t
66  getAttributeIndex(const points::AttributeSet::Descriptor& desc,
67  const std::string& name,
68  const bool allowMissing)
69  {
70  const size_t idx = desc.find(name);
71  if (idx == points::AttributeSet::INVALID_POS) {
72  if (allowMissing) return std::numeric_limits<size_t>::max();
73  throw std::runtime_error("Missing attribute - " + name);
74  }
75  if (typeNameAsString<ValueT>() != desc.valueType(idx)) {
76  throw std::runtime_error("Wrong attribute type for attribute " + name
77  + ", expected " + typeNameAsString<ValueT>());
78  }
79  return idx;
80  }
81 };
82 
83 template <typename SdfT,
84  typename PositionCodecT,
85  typename RadiusType,
86  typename FilterT,
87  bool CPG>
89  public rasterize_sdf_internal::SphericalTransfer<SdfT, PositionCodecT, RadiusType, FilterT, CPG>
90 {
91  using BaseT = rasterize_sdf_internal::SphericalTransfer<SdfT, PositionCodecT, RadiusType, FilterT, CPG>;
92  using typename BaseT::TreeT;
93  using typename BaseT::ValueT;
94 
95  static const Index DIM = TreeT::LeafNodeType::DIM;
96  static const Index LOG2DIM = TreeT::LeafNodeType::LOG2DIM;
97  // The precision of the kernel arithmetic
98  using RealT = double;
99 
103 
104  EllipsoidTransfer(const size_t pidx,
105  const Vec3i width,
106  const RadiusType& rt,
107  const math::Transform& source,
108  const FilterT& filter,
109  util::NullInterrupter* interrupt,
110  SdfT& surface,
111  const EllipseIndicies& indices,
112  Int64Tree* cpg = nullptr,
113  const std::unordered_map<const PointDataTree::LeafNodeType*, Index>* ids = nullptr)
114  : BaseT(pidx, width, rt, source, filter, interrupt, surface, cpg, ids)
115  , mIndices(indices)
116  , mRotationHandle()
117  , mPositionWSHandle() {}
118 
120  : BaseT(other)
121  , mIndices(other.mIndices)
122  , mRotationHandle()
123  , mPositionWSHandle() {}
124 
126  {
127  const bool ret = this->BaseT::startPointLeaf(leaf);
128  mRotationHandle = std::make_unique<RotationHandleT>(leaf.constAttributeArray(mIndices.rotation));
129  if (mIndices.hasWorldSpacePosition()) {
130  mPositionWSHandle = std::make_unique<PwsHandleT>(leaf.constAttributeArray(mIndices.positionws));
131  }
132  return ret;
133  }
134 
135  inline void rasterizePoint(const Coord& ijk,
136  const Index id,
137  const CoordBBox& bounds)
138  {
139  if (!BaseT::filter(id)) return;
140 
141  Vec3d P;
142  if (this->mPositionWSHandle) {
143  // Position may have been smoothed so we need to use the ws handle
144  P = this->targetTransform().worldToIndex(this->mPositionWSHandle->get(id));
145  }
146  else {
147  const Vec3d PWS = this->sourceTransform().indexToWorld(ijk.asVec3d() + Vec3d(this->mPosition->get(id)));
148  P = this->targetTransform().worldToIndex(PWS);
149  }
150 
151  const auto& r = this->mRadius.eval(id);
152  Vec3f radius = r.get(); // index space radius
153 
154  // If we have a uniform radius, treat as a sphere
155  // @todo worth using a tolerance here in relation to the voxel size?
156  if ((radius.x() == radius.y()) && (radius.x() == radius.z())) {
157  const FixedBandRadius<float> fixed(radius.x(), r.halfband());
158  this->BaseT::rasterizePoint(P, id, bounds, fixed);
159  return;
160  }
161 
162 #if OPENVDB_ELLIPSOID_KERNEL_MODE == 0
163  const FixedBandRadius<RealT> fbr(1.0, r.halfband());
164  // If min2 == 0.0, then the index space radius is equal to or less than
165  // the desired half band. In this case each sphere interior always needs
166  // to be filled with distance values as we won't ever reach the negative
167  // background value. If, however, a point overlaps a voxel coord exactly,
168  // x2y2z2 will be 0.0. Forcing min2 to be less than zero here avoids
169  // incorrectly setting these voxels to inactive -background values as
170  // x2y2z2 will never be < 0.0. We still want the lteq logic in the
171  // (x2y2z2 <= min2) check as this is valid when min2 > 0.0.
172  const RealT min2 = fbr.minSq() == 0.0 ? -1.0 : fbr.minSq();
173  const RealT max2 = fbr.maxSq();
174 #endif
175 
176  const math::Mat3s rotation = mRotationHandle->get(id);
177  const math::Mat3s ellipsoidTransform = rotation.timesDiagonal(radius);
178  // @note Extending the search by the halfband in this way will produce
179  // the desired halfband width, but will not necessarily mean that
180  // the ON values will be levelset up to positive (exterior) background
181  // value due to elliptical coordinates not being a constant distance
182  // apart
183  const Vec3d max = calcEllipsoidBoundMax(ellipsoidTransform) + r.halfband();
184  CoordBBox intersectBox(Coord::round(P - max), Coord::round(P + max));
185  intersectBox.intersect(bounds);
186  if (intersectBox.empty()) return;
187 
188  auto* const data = this->template buffer<0>();
189  [[maybe_unused]] auto* const cpg = CPG ? this->template buffer<CPG ? 1 : 0>() : nullptr;
190  auto& mask = *(this->template mask<0>());
191 
192  // construct inverse transformation to create sphere out of an ellipsoid
193  const Vec3d radInv = 1.0f / radius;
194 
195 #if OPENVDB_ELLIPSOID_KERNEL_MODE != 2
196  // Instead of trying to compute the distance from a point to an ellips,
197  // stamp the ellipsoid by deforming the distance to the iterated voxel
198  // by the inverse ellipsoid transform, then modifying it by projecting
199  // it back to our normal coordinate system.
200  math::Mat3s invDiag;
201  invDiag.setSymmetric(radInv, Vec3f(0));
202  const math::Mat3s ellipsoidInverse = invDiag * rotation.transpose();
203 #else
204  // Instead of trying to compute the distance from a point to a rotated
205  // ellipse, stamp the ellipsoid by deforming the distance to the
206  // iterated voxel by the inverse rotation. Then calculate the distance
207  // to the axis-aligned ellipse.
208  const Vec3d radInv2 = 1.0f / math::Pow2(radius);
209  const math::Mat3s ellipsoidInverse = rotation.transpose();
210 #endif
211 
212  // We cache the multiples matrix for each axis component but essentially
213  // this resolves to the invMat multiplied by the x,y,z position
214  // difference (i.e. c-p):
215  // pointOnUnitSphere = ellipsoidInverse * Vec3d(x,y,z);
216  const Coord& a(intersectBox.min());
217  const Coord& b(intersectBox.max());
218  const float* inv = ellipsoidInverse.asPointer(); // @todo do at RealT precision?
219  for (Coord c = a; c.x() <= b.x(); ++c.x()) {
220  const RealT x = static_cast<RealT>(c.x() - P[0]);
221  const Vec3d xradial(x*inv[0], x*inv[3], x*inv[6]);
222  const Index i = ((c.x() & (DIM-1u)) << 2*LOG2DIM); // unsigned bit shift mult
223  for (c.y() = a.y(); c.y() <= b.y(); ++c.y()) {
224  const RealT y = static_cast<RealT>(c.y() - P[1]);
225  const Vec3d xyradial = xradial + Vec3d(y*inv[1], y*inv[4], y*inv[7]);
226  const Index ij = i + ((c.y() & (DIM-1u)) << LOG2DIM);
227  for (c.z() = a.z(); c.z() <= b.z(); ++c.z()) {
228  const Index offset = ij + /*k*/(c.z() & (DIM-1u));
229  if (!mask.isOn(offset)) continue; // inside existing level set or not in range
230  const RealT z = static_cast<RealT>(c.z() - P[2]);
231  // Transform by inverse of ellipsoidal transform to
232  // calculate distance to deformed surface
233  const Vec3d pointOnUnitSphere = (xyradial + Vec3d(z*inv[2], z*inv[5], z*inv[8]));
234 
235  ValueT d;
236 
237 #if OPENVDB_ELLIPSOID_KERNEL_MODE == 0
238  RealT len = pointOnUnitSphere.lengthSqr();
239  // Note that the transformation of the ellipse in this way
240  // also deforms the narrow band width - we may want to do
241  // something about this.
242  if (len >= max2) continue; //outside narrow band of particle in positive direction
243  if (len <= min2) { //outside narrow band of the particle in negative direction. can disable this to fill interior
244  data[offset] = -(this->mBackground);
245  mask.setOff(offset);
246  continue;
247  }
248  d = ValueT(this->mDx * (math::Sqrt(len) - 1.0)); // back to world space
249 #elif OPENVDB_ELLIPSOID_KERNEL_MODE == 1
250  RealT len = pointOnUnitSphere.lengthSqr();
251  // @todo There may be a way to map the min2/max2 checks
252  // (or at least the min2 check) onto this length to avoid
253  // the sqrts and projections when outside the half band
254  len = math::Sqrt(len);
255  if (OPENVDB_UNLIKELY(len == 0)) {
256  // The minimum radius of this ellipse in world space. Used only to store
257  // a distance when a given voxel's ijk coordinates overlaps exactly with
258  // the center of an ellipse
259  d = -ValueT(std::min(radius.x(), std::min(radius.y(), radius.z()))) * ValueT(this->mDx);
260  }
261  else {
262  Vec3d ellipseNormal = (ellipsoidInverse.transpose() * pointOnUnitSphere);
263  ellipseNormal.normalize();
264  // Project xyz onto the ellipse normal, scale length by
265  // the offset correction based on the distance from the
266  // unit sphere surface and finally convert back to
267  // world space
268  //
269  // Invert the length to represent a proportional offset to
270  // the final distance when the above sphere point is
271  // projected back onto the ellipse. If the length iz zero,
272  // then this voxel's ijk is the center of the ellipse.
273  d = static_cast<ValueT>(
274  ((x * ellipseNormal.x()) +
275  (y * ellipseNormal.y()) +
276  (z * ellipseNormal.z())) // dot product
277  * (1.0 - (RealT(1.0)/len)) // scale
278  * this->mDx); // world space
279  }
280 
281  if (d >= this->mBackground) continue; //outside narrow band of particle in positive direction
282  if (d <= -this->mBackground) { //outside narrow band of the particle in negative direction. can disable this to fill interior
283  data[offset] = -(this->mBackground);
284  mask.setOff(offset);
285  continue;
286  }
287 #elif OPENVDB_ELLIPSOID_KERNEL_MODE == 2
288  const RealT k2 = (pointOnUnitSphere * radInv2).length();
289  if (OPENVDB_UNLIKELY(k2 == 0)) {
290  // The minimum radius of this ellipse in world space. Used only to store
291  // a distance when a given voxel's ijk coordinates overlaps exactly with
292  // the center of an ellipse
293  d = -ValueT(std::min(radius.x(), std::min(radius.y(), radius.z()))) * ValueT(this->mDx);
294  }
295  else {
296  const RealT k1 = (pointOnUnitSphere * radInv).length();
297  OPENVDB_ASSERT(k1 > 0);
298  // calc distance and then scale by voxelsize to convert to ws
299  d = static_cast<ValueT>((k1 * (k1 - RealT(1.0)) / k2) * this->mDx);
300  }
301  if (d >= this->mBackground) continue; //outside narrow band of particle in positive direction
302  if (d <= -this->mBackground) { //outside narrow band of the particle in negative direction. can disable this to fill interior
303  data[offset] = -(this->mBackground);
304  mask.setOff(offset);
305  continue;
306  }
307 #endif
308  OPENVDB_ASSERT(std::isfinite(d));
309  ValueT& v = data[offset];
310  if (d < v) {
311  v = d;
312  if constexpr(CPG) cpg[offset] = Int64(this->mPLeafMask | Index64(id));
313  }
314  }
315  }
316  }
317  }
318 
319 private:
320  const EllipseIndicies& mIndices;
321  std::unique_ptr<RotationHandleT> mRotationHandle;
322  std::unique_ptr<PwsHandleT> mPositionWSHandle;
323 };
324 
325 
326 template<typename RadiusType, typename MaskTreeT>
328  : public rasterize_sdf_internal::SurfaceMaskOp<MaskTreeT>
329 {
330  using BaseT = rasterize_sdf_internal::SurfaceMaskOp<MaskTreeT>;
333  using RadiusT = typename RadiusType::ValueType;
334  static const Index DIM = points::PointDataTree::LeafNodeType::DIM;
335 
337  const math::Transform& src,
338  const math::Transform& trg,
339  const RadiusType& rad,
340  const Real halfband,
341  const EllipseIndicies& indices)
342  : BaseT(src, trg, nullptr)
343  , mRadius(rad)
344  , mHalfband(halfband)
345  , mIndices(indices)
346  , mMaxDist(0) {}
347 
349  : BaseT(other)
350  , mRadius(other.mRadius)
351  , mHalfband(other.mHalfband)
352  , mIndices(other.mIndices)
353  , mMaxDist(0) {}
354 
355  Vec3i getMaxDist() const { return mMaxDist; }
356 
358  {
359  mMaxDist = math::maxComponent(mMaxDist, other.mMaxDist);
360  this->BaseT::join(other);
361  }
362 
363  /// @brief Fill activity by analyzing the radius values on points in this
364  /// leaf. Ignored ellipsoid rotations which results in faster but over
365  /// zealous activation region.
366  void fillFromStretch(const typename LeafManagerT::LeafNodeType& leaf)
367  {
368  Vec3f maxr(0);
369  RadiusType rad(mRadius);
370  rad.reset(leaf);
371  if constexpr(RadiusType::Fixed) {
372  maxr = rad.eval(0).get();
373  }
374  else {
375  for (Index i = 0; i < Index(rad.size()); ++i) {
376  maxr = math::maxComponent(maxr, rad.eval(i).get());
377  }
378  }
379 
380  // The max stretch coefficient. We can't analyze each xyz component
381  // individually as we don't take into account the ellipse rotation, so
382  // have to expand the worst case uniformly
383  const Real maxRadius = std::max(maxr.x(), std::max(maxr.y(), maxr.z()));
384 
385  // @note This addition of the halfband here doesn't take into account
386  // the squash on the halfband itself. The subsequent rasterizer
387  // squashes the halfband but probably shouldn't, so although this
388  // expansion is more then necessary, I'm leaving the logic here for
389  // now. We ignore stretches as these are capped to the half band
390  // length anyway
391  const Vec3i dist = Vec3i(static_cast<int32_t>(math::Round(maxRadius + mHalfband)));
392  if (!mIndices.hasWorldSpacePosition()) {
393  if (!this->activate(leaf, dist)) return; // empty node
394  /// @todo deactivate min
395  mMaxDist = math::maxComponent(mMaxDist, dist);
396  }
397  else {
398  // Positions may have been smoothed, so we need to account for that
399  points::AttributeHandle<Vec3d> pwsHandle(leaf.constAttributeArray(mIndices.positionws));
400  if (pwsHandle.size() == 0) return; // no positions?
401  Vec3d maxPos(std::numeric_limits<Real>::lowest()),
403  for (Index i = 0; i < Index(pwsHandle.size()); ++i)
404  {
405  const Vec3d Pws = pwsHandle.get(i);
406  minPos = math::minComponent(minPos, Pws);
407  maxPos = math::maxComponent(maxPos, Pws);
408  }
409 
410  // Convert point bounds to surface transform, expand and fill
411  CoordBBox surfaceBounds(
412  Coord::round(this->mSurfaceTransform.worldToIndex(minPos)),
413  Coord::round(this->mSurfaceTransform.worldToIndex(maxPos)));
414  surfaceBounds.min() -= Coord(dist);
415  surfaceBounds.max() += Coord(dist);
416  this->activate(surfaceBounds);
417  /// @todo deactivate min
418  this->updateMaxLookup(minPos, maxPos, dist, leaf);
419  }
420  }
421 
422  /// @brief Fill activity by analyzing the axis aligned ellipse bounding
423  /// boxes on points in this leaf. Slightly slower than just looking at
424  /// ellipse stretches but produces a more accurate/tighter activation
425  /// result
427  {
428  RadiusType rad(mRadius);
429  rad.reset(leaf);
430  const Vec3f radius0 = rad.eval(0).get();
431 
432  if constexpr(RadiusType::Fixed) {
433  // If the radius is fixed and uniform, don't bother evaluating the
434  // rotations (we could just fall back to the spherical transfer...)
435  const bool isSphere = (radius0.x() == radius0.y()) && (radius0.x() == radius0.z());
436  if (isSphere) {
437  this->fillFromStretch(leaf);
438  return;
439  }
440  }
441 
442  Vec3d maxPos(std::numeric_limits<Real>::lowest()),
444 
445  // Compute min/max point leaf positions
446  if (!mIndices.hasWorldSpacePosition())
447  {
448  const CoordBBox box = this->getActiveBoundingBox(leaf);
449  if (box.empty()) return;
450  minPos = this->mPointsTransform.indexToWorld(box.min().asVec3d() - 0.5);
451  maxPos = this->mPointsTransform.indexToWorld(box.max().asVec3d() + 0.5);
452  }
453  else
454  {
455  // positions may have been smoothed, so we need to account for that too
456  points::AttributeHandle<Vec3d> pwsHandle(leaf.constAttributeArray(mIndices.positionws));
457  if (pwsHandle.size() == 0) return;
458  for (Index i = 0; i < pwsHandle.size(); ++i)
459  {
460  const Vec3d Pws = pwsHandle.get(i);
461  minPos = math::minComponent(minPos, Pws);
462  maxPos = math::maxComponent(maxPos, Pws);
463  }
464  }
465 
466  // Compute max ellipse bounds
467  points::AttributeHandle<math::Mat3s> rotHandle(leaf.constAttributeArray(mIndices.rotation));
468  float maxUniformRadius(0);
469  Vec3f r(radius0);
470  Vec3d maxBounds(0);
471 
472  for (Index i = 0; i < rotHandle.size(); ++i)
473  {
474  // If the radius is Fixed, we know we have non-uniform components
475  // If the radius isn't fixed, check uniformity
476  if constexpr(!RadiusType::Fixed)
477  {
478  r = rad.eval(i).get();
479  const bool isSphere = (r.x() == r.y()) && (r.x() == r.z());
480  if (isSphere) {
481  // If this point is a sphere, we don't need to look at the rotations
482  maxUniformRadius = std::max(maxUniformRadius, float(r.x()));
483  continue;
484  }
485  }
486 
487  // compute AABB of ellipse
488  const math::Mat3s rotation = rotHandle.get(i);
489  const math::Mat3s ellipsoidTransform = rotation.timesDiagonal(r);
490  const Vec3d bounds = calcUnitEllipsoidBoundMaxSq(ellipsoidTransform);
491  maxBounds = math::maxComponent(maxBounds, bounds);
492  }
493 
494  for (size_t i = 0; i < 3; ++i) {
495  // We don't do the sqrt per point so resolve the actual maxBounds now
496  maxBounds[i] = std::sqrt(maxBounds[i]);
497  // Account for uniform stretch values - compare the ellipse to isolated
498  // points and choose the largest radius of the two
499  maxBounds[i] = std::max(double(maxUniformRadius), maxBounds[i]);
500  }
501 
502  // @note This addition of the halfband here doesn't take into account
503  // the squash on the halfband itself. The subsequent rasterizer
504  // squashes the halfband but probably shouldn't, so although this
505  // expansion is more then necessary, I'm leaving the logic here for
506  // now. We ignore stretches as these are capped to the half band
507  // length anyway
508  const Coord dist = Coord::round(maxBounds + mHalfband);
509  // Convert point bounds to surface transform, expand and fill
510  CoordBBox surfaceBounds(
511  Coord::round(this->mSurfaceTransform.worldToIndex(minPos)),
512  Coord::round(this->mSurfaceTransform.worldToIndex(maxPos)));
513  surfaceBounds.min() -= dist;
514  surfaceBounds.max() += dist;
515  this->activate(surfaceBounds);
516  /// @todo deactivate min
517  this->updateMaxLookup(minPos, maxPos, dist.asVec3i(), leaf);
518  }
519 
520  void operator()(const typename LeafManagerT::LeafRange& range)
521  {
522  for (auto leaf = range.begin(); leaf; ++leaf) {
523  this->fillFromStretchAndRotation(*leaf);
524  }
525  }
526 
527 private:
528  void updateMaxLookup(const Vec3d& minWs,
529  const Vec3d& maxWs,
530  const Vec3i dist,
531  const typename LeafManagerT::LeafNodeType& leaf)
532  {
533  // Compute the maximum lookup required if points have moved outside of
534  // this node by finding the voxel furthest away from our node and using
535  // it's maximum index coordinate as the distance we need to search
536  Coord minIdx = this->mPointsTransform.worldToIndexCellCentered(minWs);
537  Coord maxIdx = this->mPointsTransform.worldToIndexCellCentered(maxWs);
538  const auto bounds = leaf.getNodeBoundingBox();
539 
540  // If any of the ijk coords are > 0 then we need to subtract
541  // the dimension of the current leaf node from the offset distance.
542  // Note that min and max can both be in the negative or positive
543  // direction
544  if (!bounds.isInside(maxIdx)) {
545  maxIdx -= leaf.origin();
546  if (maxIdx.x() > 0) maxIdx.x() -= DIM;
547  if (maxIdx.y() > 0) maxIdx.y() -= DIM;
548  if (maxIdx.z() > 0) maxIdx.z() -= DIM;
549  maxIdx = Abs(maxIdx);
550  }
551  else {
552  maxIdx.reset(0);
553  }
554  if (!bounds.isInside(minIdx))
555  {
556  minIdx -= leaf.origin();
557  if (minIdx.x() > 0) minIdx.x() -= DIM;
558  if (minIdx.y() > 0) minIdx.y() -= DIM;
559  if (minIdx.z() > 0) minIdx.z() -= DIM;
560  minIdx = Abs(minIdx);
561  }
562  else {
563  minIdx.reset(0);
564  }
565 
566  // Now compute the max offset
567  maxIdx.maxComponent(minIdx);
568  mMaxDist = math::maxComponent(mMaxDist, dist + maxIdx.asVec3i());
569  }
570 
571 private:
572  const RadiusType& mRadius;
573  const Real mHalfband;
574  const EllipseIndicies& mIndices;
575  Vec3i mMaxDist;
576 };
577 
578 template <typename PointDataGridT,
579  typename SdfT,
580  typename SettingsT>
582 rasterizeEllipsoids(const PointDataGridT& points,
583  const SettingsT& settings,
584  const typename SettingsT::FilterType& filter)
585 {
588 
589  using namespace rasterize_sdf_internal;
590 
591  using PointDataTreeT = typename PointDataGridT::TreeType;
592  using MaskTreeT = typename SdfT::TreeType::template ValueConverter<ValueMask>::Type;
593  using AttributeTypes = typename SettingsT::AttributeTypes;
594  using FilterT = typename SettingsT::FilterType;
595 
596  const std::vector<std::string>& attributes = settings.attributes;
597  const Real halfband = settings.halfband;
598  const Vec3d radiusScale = settings.radiusScale;
599  auto* interrupter = settings.interrupter;
600 
601  math::Transform::Ptr transform = settings.transform;
602  if (!transform) transform = points.transform().copy();
603  const Real vs = transform->voxelSize()[0];
604  const typename SdfT::ValueType background =
605  static_cast<typename SdfT::ValueType>(vs * halfband);
606 
607  // early exit here if no points
608  const auto leaf = points.constTree().cbeginLeaf();
609  if (!leaf) {
610  typename SdfT::Ptr surface = SdfT::create(background);
611  surface->setTransform(transform);
612  surface->setGridClass(GRID_LEVEL_SET);
613  return GridPtrVec(1, surface);
614  }
615 
616  // Get attributes
617  const EllipseIndicies indices(leaf->attributeSet().descriptor(),
618  settings.rotation,
619  settings.pws); // pws is optional
620 
621  typename SdfT::Ptr surface;
622  GridPtrVec grids;
623 
624  if (settings.radius.empty()) {
625  // Initial Index Space radius
626  FixedBandRadius<Vec3f> rad(Vec3f(radiusScale / vs), float(halfband));
627 
628  // pre-compute ellipsoidal transform bounds and surface mask. Points that
629  // are not in the ellipses group are treated as spheres and follow
630  // the same logic as that of the Fixed/VaryingSurfaceMaskOps. Ellipsoids
631  // instead compute the max axis-aligned bounding boxes. The maximum extents
632  // of the spheres/ellipses in a leaf is used for the maximum mask/lookup.
633  // The minimum extent is always the smallest spherical radius.
634  // @todo Is the min extent correct?
635  Vec3i width;
636  {
637  if (interrupter) interrupter->start("Generating ellipsoidal surface topology");
638 
639  tree::LeafManager<const PointDataTreeT> manager(points.tree());
640  // pass radius scale as index space
641 
643  op(points.transform(), *transform, rad, halfband, indices);
644  tbb::parallel_reduce(manager.leafRange(), op);
645 
646  surface = rasterize_sdf_internal::initSdfFromMasks<SdfT, MaskTreeT>
647  (transform, background, op.mask(), op.maskoff());
648  // max possible index space radius
649  width = op.getMaxDist();
650 
651  if (interrupter) interrupter->end();
652  }
653 
654  if (interrupter) interrupter->start("Rasterizing particles to level set using ellipses and fixed spheres");
655 
656  grids = doRasterizeSurface<SdfT, EllipsoidTransfer, AttributeTypes, FilterT>
657  (points, attributes, *surface,
658  width, rad, points.transform(), filter, interrupter, *surface, indices); // args
659  }
660  else {
661  using RadiusT = typename SettingsT::RadiusAttributeType;
662 
663  const size_t ridx = leaf->attributeSet().find(settings.radius);
664  if (ridx == AttributeSet::INVALID_POS) {
665  OPENVDB_THROW(RuntimeError, "Failed to find radius attribute \"" + settings.radius + "\"");
666  }
667 
668  // Initial varying Index Space radius
669  VaryingBandRadius<RadiusT, Vec3f> rad(ridx, float(halfband), Vec3f(radiusScale / vs));
670 
671  // pre-compute ellipsoidal transform bounds and surface mask. Points that
672  // are not in the ellipse group are treated as spheres and follow
673  // the same logic as that of the Fixed/VaryingSurfaceMaskOps. Ellipsoids
674  // instead compute the max axis-aligned bounding boxes. The maximum extents
675  // of the spheres/ellipses in a leaf is used for the maximum mask/lookup.
676  // The minimum extent is always the smallest spherical radius.
677  // @todo Is the min extent correct?
678  Vec3i width;
679  {
680  if (interrupter) interrupter->start("Generating variable ellipsoidal surface topology");
681 
682  tree::LeafManager<const PointDataTreeT> manager(points.tree());
683 
684  // pass radius scale as index space
686  op(points.transform(), *transform, rad, halfband, indices);
687  tbb::parallel_reduce(manager.leafRange(), op);
688 
689  surface = rasterize_sdf_internal::initSdfFromMasks<SdfT, MaskTreeT>
690  (transform, background, op.mask(), op.maskoff());
691  // max possible index space radius
692  width = op.getMaxDist();
693 
694  if (interrupter) interrupter->end();
695  }
696 
697  if (interrupter) interrupter->start("Rasterizing particles to level set using variable ellipses");
698 
699  grids = doRasterizeSurface<SdfT, EllipsoidTransfer, AttributeTypes, FilterT>
700  (points, attributes, *surface,
701  width, rad, points.transform(), filter, interrupter, *surface, indices); // args
702  }
703 
704  if (interrupter) interrupter->end();
705  tools::pruneLevelSet(surface->tree());
706  grids.insert(grids.begin(), surface);
707  return grids;
708 }
709 
710 } // namespace rasterize_sdf_internal
711 
712 }
713 }
714 }
715 
716 #endif // OPENVDB_POINTS_RASTERIZE_ELLIPSOIDS_SDF_IMPL_HAS_BEEN_INCLUDED
EllipseIndicies(const points::AttributeSet::Descriptor &desc, const std::string &rotation, const std::string &pws)
Definition: PointRasterizeEllipsoidsSDFImpl.h:53
typename RadiusType::ValueType RadiusT
Definition: PointRasterizeEllipsoidsSDFImpl.h:333
Helper metafunction used to determine if the first template parameter is a specialization of the clas...
Definition: Types.h:263
T & y()
Definition: Vec3.h:87
Vec2< T > minComponent(const Vec2< T > &v1, const Vec2< T > &v2)
Return component-wise minimum of the two vectors.
Definition: Vec2.h:504
points::PointDataTree::LeafNodeType PointDataLeaf
Definition: PointRasterizeEllipsoidsSDFImpl.h:331
Iterator begin() const
Definition: LeafManager.h:156
EllipsoidTransfer(const size_t pidx, const Vec3i width, const RadiusType &rt, const math::Transform &source, const FilterT &filter, util::NullInterrupter *interrupt, SdfT &surface, const EllipseIndicies &indices, Int64Tree *cpg=nullptr, const std::unordered_map< const PointDataTree::LeafNodeType *, Index > *ids=nullptr)
Definition: PointRasterizeEllipsoidsSDFImpl.h:104
Definition: Types.h:526
#define OPENVDB_THROW(exception, message)
Definition: Exceptions.h:74
uint64_t Index64
Definition: Types.h:53
void operator()(const typename LeafManagerT::LeafRange &range)
Definition: PointRasterizeEllipsoidsSDFImpl.h:520
Coord Abs(const Coord &xyz)
Definition: Coord.h:518
std::vector< GridBase::Ptr > GridPtrVec
Definition: Grid.h:508
Definition: PointRasterizeEllipsoidsSDFImpl.h:88
float Sqrt(float x)
Return the square root of a floating-point value.
Definition: Math.h:761
EllipseSurfaceMaskOp(const EllipseSurfaceMaskOp &other, tbb::split)
Definition: PointRasterizeEllipsoidsSDFImpl.h:348
typename RootNodeType::LeafNodeType LeafNodeType
Definition: Tree.h:203
Mat3 transpose() const
returns transpose of this
Definition: Mat3.h:454
math::Vec3d calcEllipsoidBoundMax(const math::Mat3s &ellipsoidTransform)
Definition: PointRasterizeEllipsoidsSDFImpl.h:42
OutGridT XformOp & op
Definition: ValueTransformer.h:139
Index32 Index
Definition: Types.h:54
void setSymmetric(const Vec3< T > &vdiag, const Vec3< T > &vtri)
Set diagonal and symmetric triangular components.
Definition: Mat3.h:223
T & z()
Definition: Vec3.h:88
double RealT
Definition: PointRasterizeEllipsoidsSDFImpl.h:98
bool startPointLeaf(const PointDataTree::LeafNodeType &leaf)
Definition: PointRasterizeEllipsoidsSDFImpl.h:125
rasterize_sdf_internal::SurfaceMaskOp< MaskTreeT > BaseT
Definition: PointRasterizeEllipsoidsSDFImpl.h:330
Mat3< float > Mat3s
Definition: Mat3.h:832
Vec3< int32_t > Vec3i
Definition: Vec3.h:662
EllipsoidTransfer(const EllipsoidTransfer &other)
Definition: PointRasterizeEllipsoidsSDFImpl.h:119
void fillFromStretchAndRotation(const typename LeafManagerT::LeafNodeType &leaf)
Fill activity by analyzing the axis aligned ellipse bounding boxes on points in this leaf...
Definition: PointRasterizeEllipsoidsSDFImpl.h:426
#define OPENVDB_UNLIKELY(x)
Definition: Platform.h:92
Base class for interrupters.
Definition: NullInterrupter.h:25
Definition: AttributeArray.h:763
T & x()
Reference to the component, e.g. v.x() = 4.5f;.
Definition: Vec3.h:86
EllipseSurfaceMaskOp(const math::Transform &src, const math::Transform &trg, const RadiusType &rad, const Real halfband, const EllipseIndicies &indices)
Definition: PointRasterizeEllipsoidsSDFImpl.h:336
This class manages a linear array of pointers to a given tree&#39;s leaf nodes, as well as optional auxil...
Definition: LeafManager.h:85
#define OPENVDB_ASSERT(X)
Definition: Assert.h:41
SharedPtr< Transform > Ptr
Definition: Transform.h:42
Definition: PointRasterizeEllipsoidsSDFImpl.h:51
Definition: Exceptions.h:63
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
GridPtrVec rasterizeEllipsoids(const PointDataGridT &points, const SettingsT &settings, const typename SettingsT::FilterType &filter)
Definition: PointRasterizeEllipsoidsSDFImpl.h:582
void rasterizePoint(const Coord &ijk, const Index id, const CoordBBox &bounds)
Definition: PointRasterizeEllipsoidsSDFImpl.h:135
LeafType LeafNodeType
Definition: LeafManager.h:93
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
double Real
Definition: Types.h:60
rasterize_sdf_internal::SphericalTransfer< SdfT, PositionCodecT, RadiusType, FilterT, CPG > BaseT
Definition: PointRasterizeEllipsoidsSDFImpl.h:91
const size_t rotation
Definition: PointRasterizeEllipsoidsSDFImpl.h:61
Vec2< T > maxComponent(const Vec2< T > &v1, const Vec2< T > &v2)
Return component-wise maximum of the two vectors.
Definition: Vec2.h:513
T * asPointer()
Direct access to the internal data.
Definition: Mat.h:101
int64_t Int64
Definition: Types.h:57
Definition: Transform.h:39
MatType rotation(const Quat< typename MatType::value_type > &q, typename MatType::value_type eps=static_cast< typename MatType::value_type >(1.0e-8))
Return the rotation matrix specified by the given quaternion.
Definition: Mat.h:172
math::Vec3d calcUnitEllipsoidBoundMaxSq(const math::Mat3s &ellipsoidTransform)
Definition: PointRasterizeEllipsoidsSDFImpl.h:27
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition: version.h.in:121
void join(EllipseSurfaceMaskOp &other)
Definition: PointRasterizeEllipsoidsSDFImpl.h:357
void split(ContainerT &out, const std::string &in, const char delim)
Definition: Name.h:43
Mat3 timesDiagonal(const Vec3< T > &diag) const
Treat diag as a diagonal matrix and return the product of this matrix with diag (from the right)...
Definition: Mat3.h:521
const std::enable_if<!VecTraits< T >::IsVec, T >::type & max(const T &a, const T &b)
Definition: Composite.h:110
bool hasWorldSpacePosition() const
Definition: PointRasterizeEllipsoidsSDFImpl.h:59
Definition: PointRasterizeEllipsoidsSDFImpl.h:327
void fillFromStretch(const typename LeafManagerT::LeafNodeType &leaf)
Fill activity by analyzing the radius values on points in this leaf. Ignored ellipsoid rotations whic...
Definition: PointRasterizeEllipsoidsSDFImpl.h:366
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
Vec3i getMaxDist() const
Definition: PointRasterizeEllipsoidsSDFImpl.h:355
3x3 matrix class.
Definition: Mat3.h:28
#define OPENVDB_USE_VERSION_NAMESPACE
Definition: version.h.in:218
Definition: Tree.h:194