OpenVDB  12.1.0
LevelSetDilatedMeshImpl.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 Greg Hurst
5 ///
6 /// @file LevelSetDilatedMeshImpl.h
7 ///
8 /// @brief Generate a narrow-band level set of a dilated surface mesh.
9 ///
10 /// @note By definition a level set has a fixed narrow band width
11 /// (the half width is defined by LEVEL_SET_HALF_WIDTH in Types.h),
12 /// whereas an SDF can have a variable narrow band width.
13 
14 #ifndef OPENVDB_TOOLS_LEVELSETDILATEDMESHIMPL_HAS_BEEN_INCLUDED
15 #define OPENVDB_TOOLS_LEVELSETDILATEDMESHIMPL_HAS_BEEN_INCLUDED
16 
17 #include "ConvexVoxelizer.h"
18 
21 #include <openvdb/tools/Prune.h>
22 
23 #include <openvdb/Grid.h>
24 #include <openvdb/math/Math.h>
26 
27 #include <tbb/parallel_for.h>
28 #include <tbb/parallel_reduce.h>
29 
30 #include <vector>
31 #include <type_traits>
32 
33 
34 namespace openvdb {
36 namespace OPENVDB_VERSION_NAME {
37 namespace tools {
38 
39 namespace lvlset {
40 
41 /// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set
42 /// representation of an _open_ prism.
43 /// The only parts of the level set populated are along both normals of the triangle.
44 /// Negative background tiles that fit inside the closed dilated triangle are also populated.
45 ///
46 /// @note @c GridType::ValueType must be a floating-point scalar.
47 template <typename GridType, typename InterruptT = util::NullInterrupter>
49  : public ConvexVoxelizer<
50  GridType,
51  OpenTriangularPrismVoxelizer<GridType, InterruptT>,
52  InterruptT>
53 {
54  using GridPtr = typename GridType::Ptr;
55 
56  using BaseT = ConvexVoxelizer<
57  GridType,
59  InterruptT
60  >;
61 
62  using BaseT::mXYData;
63  using BaseT::tileCeil;
64 
65  using ValueT = typename BaseT::ValueT;
66  using Vec3T = typename BaseT::Vec3T;
67 
68 public:
69 
70  friend class ConvexVoxelizer<
71  GridType,
72  OpenTriangularPrismVoxelizer<GridType, InterruptT>,
73  InterruptT
74  >;
75 
76  /// @brief Constructor
77  ///
78  /// @param grid scalar grid to populate the level set in
79  /// @param threaded center of the sphere in world units
80  /// @param interrupter pointer to optional interrupter. Use template
81  /// argument util::NullInterrupter if no interruption is desired.
82  ///
83  /// @note The voxel size and half width are determined from the input grid,
84  /// meaning the voxel size and background value need to be set prior to voxelization
86  const bool& threaded = false,
87  InterruptT* interrupter = nullptr)
88  : BaseT(grid, threaded, interrupter)
89  {
90  }
91 
92  /// @brief Create an open prism
93  ///
94  /// @param pt1 point 1 of the triangle in world units
95  /// @param pt2 point 2 of the triangle in world units
96  /// @param pt3 point 3 of the triangle in world units
97  /// @param radius radius of the open prism in world units
98  template<typename ScalarType>
99  void
101  const math::Vec3<ScalarType>& pt3, const ScalarType& radius)
102  {
103  static_assert(std::is_floating_point<ScalarType>::value);
104 
105  if (initialize(pt1, pt2, pt3, radius))
106  BaseT::iterate();
107  }
108 
109 private:
110 
111  inline void
112  setXYRangeData(const Index& step = 1)
113  {
114  const ValueT &x1 = mPts[0].x(), &x2 = mPts[1].x(), &x3 = mPts[2].x(),
115  &x4 = mPts[3].x(), &x5 = mPts[4].x(), &x6 = mPts[5].x();
116 
117  const ValueT xmin = math::Min(x1, x2, x3, x4, x5, x6);
118  const ValueT xmax = math::Max(x1, x2, x3, x4, x5, x6);
119  mXYData.reset(xmin, xmax, step);
120 
121  // TODO add logic to ignore edges in the interior of the projection
122  // TODO add logic that classifies each segment as being either on 'top' or 'bottom'
123 
124  setXYSegmentRangeData<0,1,0>(step);
125  setXYSegmentRangeData<1,2,0>(step);
126  setXYSegmentRangeData<2,0,0>(step);
127 
128  setXYSegmentRangeData<3,4,0>(step);
129  setXYSegmentRangeData<4,5,0>(step);
130  setXYSegmentRangeData<5,3,0>(step);
131 
132  setXYSegmentRangeData<0,3,0>(step);
133  setXYSegmentRangeData<1,4,0>(step);
134  setXYSegmentRangeData<2,5,0>(step);
135  }
136 
137  template<Index i, Index j, int MinMax = 0>
138  inline void
139  setXYSegmentRangeData(const Index& step = 1)
140  {
141  const ValueT &x1 = mPts[i].x(), &x2 = mPts[j].x();
142 
143  // nothing to do if segment does not span across more than on voxel in x
144  // other segments will handle this segment's range
145  if (tileCeil(x1, step) == tileCeil(x2, step))
146  return;
147 
148  const ValueT x_start = tileCeil(math::Min(x1, x2), step),
149  x_end = math::Max(x1, x2),
150  stepv = ValueT(step);
151 
152  for (ValueT x = x_start; x <= x_end; x += stepv) {
153  if constexpr (MinMax <= 0)
154  mXYData.expandYMin(x, line2D<i,j>(x));
155  if constexpr (MinMax >= 0)
156  mXYData.expandYMax(x, line2D<i,j>(x));
157  }
158  }
159 
160  // simply offset distance to the center plane, we may assume any CPQ falls in inside the prism
161  inline ValueT
162  signedDistance(const Vec3T& p) const
163  {
164  return math::Abs(mTriNrml.dot(p - mA)) - mRad;
165  }
166 
167  // allows for tiles to poke outside of the open prism into the tubes
168  // adaptation of udTriangle at https://iquilezles.org/articles/distfunctions/
169  inline ValueT
170  tilePointSignedDistance(const Vec3T& p) const
171  {
172  const Vec3T pa = p - mA,
173  pb = p - mB,
174  pc = p - mC;
175 
176  const ValueT udist =
177  math::Sign(mBAXNrml.dot(pa)) +
178  math::Sign(mCBXNrml.dot(pb)) +
179  math::Sign(mACXNrml.dot(pc)) < 2
180  ?
182  (mBA * math::Clamp01(mBANorm2.dot(pa)) - pa).lengthSqr(),
183  (mCB * math::Clamp01(mCBNorm2.dot(pb)) - pb).lengthSqr(),
184  (mAC * math::Clamp01(mACNorm2.dot(pc)) - pc).lengthSqr()
185  ))
186  :
187  math::Abs(mTriNrml.dot(p - mA));
188 
189  return udist - mRad;
190  }
191 
192  inline bool
193  tileCanFit(const Index& dim) const
194  {
195  return mRad >= BaseT::halfWidth() + ValueT(0.5) * (ValueT(dim)-ValueT(1));
196  }
197 
198  std::function<bool(ValueT&, ValueT&, const ValueT&, const ValueT&)> prismBottomTop =
199  [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y)
200  {
201  zb = std::numeric_limits<ValueT>::lowest();
203 
204  // TODO with proper book keeping we can know apriori which 2 indexes will set zb & zt
205  // basically figure out a poor man's cylindrical decomposition...
206  setPlaneBottomTop<0>(zb, zt, x, y);
207  setPlaneBottomTop<1>(zb, zt, x, y);
208  setPlaneBottomTop<2>(zb, zt, x, y);
209  setPlaneBottomTop<3>(zb, zt, x, y);
210  setPlaneBottomTop<4>(zb, zt, x, y);
211 
212  return true;
213  };
214 
215  template<Index i>
216  inline void
217  setPlaneBottomTop(ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y) const
218  {
219  if (math::isApproxZero(mFaceNrmls[i].z()))
220  return;
221 
222  const ValueT z = mPlaneXCoeffs[i]*x + mPlaneYCoeffs[i]*y + mPlaneOffsets[i];
223 
224  if (mFaceNrmls[i].z() < 0) {
225  if (zb < z)
226  zb = z;
227  } else {
228  if (zt > z)
229  zt = z;
230  }
231  }
232 
233  // world space points and radius inputs
234  // initializes class members in index space
235  template<typename ScalarType>
236  inline bool
238  const math::Vec3<ScalarType>& pt3, const ScalarType& r)
239  {
240  const ValueT vx = BaseT::voxelSize(),
241  hw = BaseT::halfWidth();
242 
243  mA = Vec3T(pt1)/vx;
244  mB = Vec3T(pt2)/vx;
245  mC = Vec3T(pt3)/vx;
246 
247  mRad = ValueT(r)/vx;
248 
249  mBA = mB-mA;
250  mCB = mC-mB;
251  mAC = mA-mC;
252 
253  mTriNrml = mBA.cross(mC-mA);
254 
255  mBAXNrml = mTriNrml.cross(mBA);
256  mCBXNrml = mTriNrml.cross(mCB);
257  mACXNrml = mTriNrml.cross(mAC);
258 
259  mBANorm2 = math::isApproxZero(mBA.lengthSqr()) ? mBA : mBA/mBA.lengthSqr();
260  mCBNorm2 = math::isApproxZero(mCB.lengthSqr()) ? mCB : mCB/mCB.lengthSqr();
261  mACNorm2 = math::isApproxZero(mAC.lengthSqr()) ? mAC : mAC/mAC.lengthSqr();
262 
263  const ValueT len = mTriNrml.length();
264  if (math::isApproxZero(len)) {
265  return false; // nothing to voxelize, prism has no volume
266  } else {
267  mTriNrml /= len;
268  }
269 
270  const ValueT hwRad = mRad + hw;
271  if (math::isApproxZero(hwRad) || hwRad < 0)
272  return false; // nothing to voxelize, prism has no volume
273 
274  mPts = {
275  mA + hwRad * mTriNrml, mB + hwRad * mTriNrml, mC + hwRad * mTriNrml,
276  mA - hwRad * mTriNrml, mB - hwRad * mTriNrml, mC - hwRad * mTriNrml
277  };
278 
279  // tri1, tri2, quad1, quad2, quad3
280  mFaceNrmls = {
281  mTriNrml,
282  -mTriNrml,
283  mTriNrml.cross(mA-mB).unitSafe(),
284  mTriNrml.cross(mB-mC).unitSafe(),
285  mTriNrml.cross(mC-mA).unitSafe()
286  };
287 
288  {
289  static const std::vector<Index> p_ind = {0, 3, 0, 1, 2};
290 
291  mPlaneXCoeffs.assign(5, ValueT(0));
292  mPlaneYCoeffs.assign(5, ValueT(0));
293  mPlaneOffsets.assign(5, ValueT(0));
294 
295  for (Index i = 0; i < 5; ++i) {
296  if (!math::isApproxZero(mFaceNrmls[i].z())) {
297  const ValueT cx = mFaceNrmls[i].x()/mFaceNrmls[i].z(),
298  cy = mFaceNrmls[i].y()/mFaceNrmls[i].z();
299  const Vec3T p = mPts[p_ind[i]];
300  mPlaneXCoeffs[i] = -cx;
301  mPlaneYCoeffs[i] = -cy;
302  mPlaneOffsets[i] = p.x()*cx + p.y()*cy + p.z();
303  }
304  }
305  }
306 
307  BaseT::bottomTop = prismBottomTop;
308 
309  return true;
310  }
311 
312  // ------------ general utilities ------------
313 
314  template <Index i, Index j>
315  ValueT
316  line2D(const ValueT& x) const
317  {
318  const ValueT &x1 = mPts[i].x(), &y1 = mPts[i].y(),
319  &x2 = mPts[j].x(), &y2 = mPts[j].y();
320 
321  const ValueT m = (y2-y1)/(x2-x1);
322 
323  return y1 + m * (x-x1);
324  }
325 
326  // ------------ private members ------------
327 
328  Vec3T mA, mB, mC;
329  ValueT mRad;
330 
331  Vec3T mBA, mCB, mAC;
332  Vec3T mBAXNrml, mCBXNrml, mACXNrml;
333  Vec3T mBANorm2, mCBNorm2, mACNorm2;
334 
335  std::vector<Vec3T> mPts = std::vector<Vec3T>(6);
336 
337  Vec3T mTriNrml;
338  std::vector<Vec3T> mFaceNrmls = std::vector<Vec3T>(5);
339 
340  std::vector<ValueT> mPlaneXCoeffs = std::vector<ValueT>(5),
341  mPlaneYCoeffs = std::vector<ValueT>(5),
342  mPlaneOffsets = std::vector<ValueT>(5);
343 
344 }; // class OpenTriangularPrismVoxelizer
345 
346 /// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set
347 /// representation of an _open_ wedge.
348 /// The only parts of the level set populated are within a sector of a capsule.
349 /// The sector is defined by the intersection of two half spaces.
350 ///
351 /// @note @c GridType::ValueType must be a floating-point scalar.
352 template <typename GridType, typename InterruptT = util::NullInterrupter>
354  : public ConvexVoxelizer<
355  GridType,
356  OpenCapsuleWedgeVoxelizer<GridType, InterruptT>,
357  InterruptT>
358 {
359  using GridPtr = typename GridType::Ptr;
360 
361  using BaseT = ConvexVoxelizer<
362  GridType,
364  InterruptT
365  >;
366 
367  using BaseT::mXYData;
368  using BaseT::tileCeil;
369 
370  using ValueT = typename BaseT::ValueT;
371  using Vec3T = typename BaseT::Vec3T;
372  using Vec2T = typename BaseT::Vec2T;
373 
374 public:
375 
376  friend class ConvexVoxelizer<
377  GridType,
378  OpenCapsuleWedgeVoxelizer<GridType, InterruptT>,
379  InterruptT
380  >;
381 
382  /// @brief Constructor
383  ///
384  /// @param grid scalar grid to populate the level set in
385  /// @param threaded center of the sphere in world units
386  /// @param interrupter pointer to optional interrupter. Use template
387  /// argument util::NullInterrupter if no interruption is desired.
388  ///
389  /// @note The voxel size and half width are determined from the input grid,
390  /// meaning the voxel size and background value need to be set prior to voxelization
391  OpenCapsuleWedgeVoxelizer(GridPtr& grid, const bool& threaded = false,
392  InterruptT* interrupter = nullptr)
393  : BaseT(grid, threaded, interrupter)
394  {
395  }
396 
397  /// @brief Create an open wedge
398  ///
399  /// @param pt1 first endpoint open wedge in world units
400  /// @param pt2 second endpoint open wedge in world units
401  /// @param radius radius of the open prism in world units
402  /// @param nrml1 normal of a half space the the capsule is clipped with to form the open wedge
403  /// @param nrml2 normal of the other half space the the capsule is clipped with to form the open wedge
404  ///
405  /// @note The normal vectors @f$n @f$ point outward from the open wedge,
406  /// and the clipping half space is defined by the set of points @f$p @f$ that satisfy @f$n . (p - pt1) \leq 0@f$.
407  template<typename ScalarType>
408  void
410  const ScalarType& radius, const math::Vec3<ScalarType>& nrml1,
411  const math::Vec3<ScalarType>& nrml2)
412  {
413  static_assert(std::is_floating_point<ScalarType>::value);
414 
415  if (initialize(pt1, pt2, radius, nrml1, nrml2))
416  BaseT::iterate();
417  }
418 
419 private:
420 
421  // computes *approximate* xy-range data: the projected caps might contain over-inclusive values
422  inline void
423  setXYRangeData(const Index& step = 1)
424  {
425  const ValueT stepv = ValueT(step);
426 
427  // degenerate
428  if (mX1 - mORad > mX2 + mORad) {
429  mXYData.clear();
430  return;
431  }
432 
433  // short circuit a vertical cylinder
434  if (mIsVertical) {
435  mXYData.reset(mX1 - mORad, mX1 + mORad, step);
436 
437  for (ValueT x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += stepv)
438  mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x));
439 
440  intersectWithXYWedgeLines();
441  return;
442  }
443 
444  const ValueT v = math::Min(mORad, mORad * math::Abs(mYdiff)/mXYNorm);
445 
446  const ValueT a0 = mX1 - mORad,
447  a1 = mX1 - v,
448  a2 = mX1 + v,
449  a3 = mX2 - v,
450  a4 = mX2 + v,
451  a5 = mX2 + mORad;
452 
453  const ValueT tc0 = tileCeil(a0, step),
454  tc1 = tileCeil(a1, step),
455  tc2 = tileCeil(a2, step),
456  tc3 = tileCeil(a3, step),
457  tc4 = tileCeil(a4, step);
458 
459  mXYData.reset(a0, a5, step);
460 
461  for (ValueT x = tc0; x <= a1; x += stepv)
462  mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x));
463 
464  if (!math::isApproxZero(mXdiff)) {
465  if (mY1 > mY2) {
466  for (ValueT x = tc1; x <= math::Min(a2, a3); x += stepv)
467  mXYData.expandYRange(x, lineBottom(x), circle1Top(x));
468  } else {
469  for (ValueT x = tc1; x <= math::Min(a2, a3); x += stepv)
470  mXYData.expandYRange(x, circle1Bottom(x), lineTop(x));
471  }
472  }
473 
474  if (a2 < a3) {
475  for (ValueT x = tc2; x <= a3; x += stepv)
476  mXYData.expandYRange(x, lineBottom(x), lineTop(x));
477  } else {
478  if (mY2 <= mY1) {
479  for (ValueT x = tc3; x <= a2; x += stepv)
480  mXYData.expandYRange(x, circle2Bottom(x), circle1Top(x));
481  } else {
482  for (ValueT x = tc3; x <= a2; x += stepv)
483  mXYData.expandYRange(x, circle1Bottom(x), circle2Top(x));
484  }
485  }
486 
487  if (!math::isApproxZero(mXdiff)) {
488  if (mY1 > mY2) {
489  for (ValueT x = math::Max(tc2, tc3); x <= a4; x += stepv)
490  mXYData.expandYRange(x, circle2Bottom(x), lineTop(x));
491  } else {
492  for (ValueT x = math::Max(tc2, tc3); x <= a4; x += stepv)
493  mXYData.expandYRange(x, lineBottom(x), circle2Top(x));
494  }
495  }
496 
497  for (ValueT x = tc4; x <= a5; x += stepv)
498  mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x));
499 
500  intersectWithXYStrip();
501  intersectWithXYWedgeLines();
502  }
503 
504  inline void
505  intersectWithXYStrip()
506  {
507  // these strips are vertical when the capsule is
508  if (mIsVertical)
509  return;
510 
511  const Vec3T &pp1 = mPlanePts[0], &pp2 = mPlanePts[1];
512  const ValueT &vx = mV.x(), &vy = mV.y();
513 
514  Vec2T n = Vec2T(-vy, vx).unitSafe();
515  Vec3T cvec = mORad * Vec3T(-vy, vx, ValueT(0)).unitSafe();
516 
517  if (math::isApproxZero(vy))
518  cvec.y() = math::Abs(cvec.y());
519  else if (vy > 0)
520  cvec *= ValueT(-1);
521 
522  const Vec3T cpmin(mPt1 - cvec), cpmax(mPt1 + cvec);
523 
524  if (math::isApproxZero(mXdiff)) {
525  const ValueT px = mPt1.x(),
526  xmin = math::Min(px, pp1.x(), pp2.x()),
527  xmax = math::Max(px, pp1.x(), pp2.x());
528 
529  if (!inWedge(cpmin))
530  intersectWithXYHalfSpace(n.x() < 0 ? n : -n, Vec2T(xmin, ValueT(0)));
531 
532  if (!inWedge(cpmax))
533  intersectWithXYHalfSpace(n.x() > 0 ? n : -n, Vec2T(xmax, ValueT(0)));
534  } else {
535  const ValueT m = mYdiff/mXdiff;
536  const ValueT y1 = mPt1.y() - m * mPt1.x(),
537  y2 = pp1.y() - m * pp1.x(),
538  y3 = pp2.y() - m * pp2.x();
539  const ValueT ymin = math::Min(y1, y2, y3),
540  ymax = math::Max(y1, y2, y3);
541 
542  if (!inWedge(vy <= 0 ? cpmin : cpmax))
543  intersectWithXYHalfSpace(n.y() < 0 ? n : -n, Vec2T(ValueT(0), ymin));
544 
545  if (!inWedge(vy > 0 ? cpmin : cpmax))
546  intersectWithXYHalfSpace(n.y() > 0 ? n : -n, Vec2T(ValueT(0), ymax));
547  }
548  }
549 
550  inline void
551  intersectWithXYWedgeLines()
552  {
553  const Vec3T v(mORad * mV.unitSafe()),
554  p1(mPt1 - v),
555  p2(mPt2 + v);
556 
557  const Vec2T p1_2d(p1.x(), p1.y()), p2_2d(p2.x(), p2.y());
558 
559  Vec2T d(-mPlaneNrmls[0].x() - mPlaneNrmls[1].x(),
560  -mPlaneNrmls[0].y() - mPlaneNrmls[1].y());
561 
562  Vec2T n0(-mDirVectors[0].y(), mDirVectors[0].x()),
563  n1(-mDirVectors[1].y(), mDirVectors[1].x());
564 
565  if (n0.dot(d) > 0)
566  n0 *= ValueT(-1);
567  if (n1.dot(d) > 0)
568  n1 *= ValueT(-1);
569 
570  if (!math::isApproxZero(n0.lengthSqr()))
571  intersectWithXYHalfSpace(n0, n0.dot(p2_2d - p1_2d) < 0 ? p1_2d : p2_2d);
572 
573  if (!math::isApproxZero(n1.lengthSqr()))
574  intersectWithXYHalfSpace(n1, n1.dot(p2_2d - p1_2d) < 0 ? p1_2d : p2_2d);
575  }
576 
577  inline void
578  intersectWithXYHalfSpace(const Vec2T& n, const Vec2T& p)
579  {
580  if (mXYData.size() == 0)
581  return;
582 
583  if (math::isApproxZero(n.y())) {
584  const ValueT &px = p.x();
585  if (n.x() < 0) {
586  const Index m = mXYData.size();
587  for (Index i = 0; i < m; ++i) {
588  const ValueT x = mXYData.getX(i);
589 
590  if (x < px) mXYData.clearYRange(x);
591  else break;
592  }
593  } else {
594  Index i = mXYData.size()-1;
595  while (true) {
596  const ValueT x = mXYData.getX(i);
597 
598  if (x > px) mXYData.clearYRange(x);
599  else break;
600 
601  if (i != 0) --i;
602  else break;
603  }
604  }
605  } else {
606  const bool set_min = n.y() < 0;
607  const Index m = mXYData.size();
608 
609  const ValueT b = -n.x()/n.y();
610  const ValueT a = p.y() - b * p.x();
611 
612  ValueT x, ymin, ymax;
613  for (Index i = 0; i < m; ++i) {
614  mXYData.XYData(x, ymin, ymax, i);
615  const ValueT yint = a + b * x;
616 
617  if (ymin <= yint && yint <= ymax) {
618  if (set_min) mXYData.setYMin(x, yint);
619  else mXYData.setYMax(x, yint);
620  } else {
621  if (set_min ? yint > ymax : yint < ymin)
622  mXYData.clearYRange(x);
623  }
624  }
625  }
626 
627  mXYData.trim();
628  }
629 
630  // distance in index space
631  inline ValueT
632  signedDistance(const Vec3T& p) const
633  {
634  const Vec3T w = p - mPt1;
635  const ValueT dot = w.dot(mV);
636 
637  // carefully short circuit with a fuzzy tolerance, which avoids division by small mVLenSqr
638  if (dot <= math::Tolerance<ValueT>::value())
639  return w.length() - mRad;
640 
641  if (dot >= mVLenSqr)
642  return (p - mPt2).length() - mRad;
643 
644  const ValueT t = w.dot(mV)/mVLenSqr;
645 
646  return (w - t * mV).length() - mRad;
647  }
648 
649  inline bool
650  tileCanFit(const Index& dim) const
651  {
652  return mRad >= BaseT::halfWidth() + ValueT(0.5) * (ValueT(dim)-ValueT(1));
653  }
654 
655  std::function<bool(ValueT&, ValueT&, const ValueT&, const ValueT&)> capsuleBottomTopVertical =
656  [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y)
657  {
658  zb = BaseT::sphereBottom(mX1, mY1, math::Min(mZ1, mZ2), mORad, x, y);
659  zt = BaseT::sphereTop(mX2, mY2, math::Max(mZ1, mZ2), mORad, x, y);
660 
661  return std::isfinite(zb) && std::isfinite(zt);
662  };
663 
664  std::function<bool(ValueT&, ValueT&, const ValueT&, const ValueT&)> capsuleBottomTop =
665  [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y)
666  {
667  ValueT cylptb, cylptt;
668  if (!infiniteCylinderBottomTop(cylptb, cylptt, x, y))
669  return false;
670 
671  const ValueT dotb = (Vec3T(x, y, cylptb) - mPt1).dot(mV);
672  const ValueT dott = (Vec3T(x, y, cylptt) - mPt1).dot(mV);
673 
674  if (dotb < 0)
675  zb = sphere1Bottom(x, y);
676  else if (dotb > mVLenSqr)
677  zb = sphere2Bottom(x, y);
678  else
679  zb = cylptb;
680 
681  if (dott < 0)
682  zt = sphere1Top(x, y);
683  else if (dott > mVLenSqr)
684  zt = sphere2Top(x, y);
685  else
686  zt = cylptt;
687 
688  if (!std::isfinite(zb) || !std::isfinite(zt))
689  return false;
690 
691  intersectWedge<0,1>(zb, zt, x, y);
692  intersectWedge<1,0>(zb, zt, x, y);
693 
694  return inWedge(x, y, ValueT(0.5)*(zb+zt));
695  };
696 
697  template<Index i, Index j>
698  inline void
699  intersectWedge(ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y)
700  {
701  const Vec3T& n0 = mPlaneNrmls[i];
702 
703  if (math::isApproxZero(n0.z()))
704  return;
705 
706  const ValueT zp = mPlaneXCoeffs[i]*x + mPlaneYCoeffs[i]*y + mPlaneOffsets[i];
707 
708  if (zb <= zp && zp <= zt && inHalfSpace<j>(Vec3T(x, y, zp))) {
709  if (n0.z() < 0)
710  zb = zp;
711  else
712  zt = zp;
713  }
714  }
715 
716  inline bool
717  inWedge(const ValueT& x, const ValueT& y, const ValueT& z)
718  {
719  return inWedge(Vec3T(x, y, z));
720  }
721 
722  inline bool
723  inWedge(const Vec3T& pt)
724  {
725  return inHalfSpace<0>(pt) && inHalfSpace<1>(pt);
726  }
727 
728  template<Index i>
729  inline bool
730  inHalfSpace(const Vec3T& pt)
731  {
732  // allow points within a fuzzy fractional (index space) distance to the halfspace
733  // this ensures the seams between open wedges and open prisms are completely filled in
734  // assumes mPlaneNrmls[i] is a unit vector
735  static const ValueT VOXFRAC = 0.125;
736 
737  return mPlaneNrmls[i].dot(pt-mPt1) <= VOXFRAC;
738  }
739 
740  // assumes tube is not vertical!
741  inline bool
742  infiniteCylinderBottomTop(ValueT& cylptb, ValueT& cylptt,
743  const ValueT& x, const ValueT& y) const
744  {
745  const Vec2T q(x, y);
746 
747  const Vec2T qproj = mPt12d + mV2d*((q - mPt12d).dot(mV2d))/mXYNorm2;
748 
749  const ValueT t = mX1 != mX2 ? (qproj[0] - mX1)/mXdiff : (qproj[1] - mY1)/mYdiff;
750 
751  const Vec3T qproj3D = mPt1 + t * mV;
752 
753  const ValueT d2 = (q - qproj).lengthSqr();
754 
755  // outside of cylinder's 2D projection
756  if (mORad2 < d2)
757  return false;
758 
759  const ValueT h = math::Sqrt((mORad2 - d2) * mVLenSqr/mXYNorm2);
760 
761  cylptb = qproj3D[2] - h;
762  cylptt = qproj3D[2] + h;
763 
764  return true;
765  }
766 
767  inline ValueT
768  lineBottom(const ValueT& x) const
769  {
770  return mY1 + (mYdiff*(x-mX1) - mORad * mXYNorm)/mXdiff;
771  }
772 
773  inline ValueT
774  lineTop(const ValueT& x) const
775  {
776  return mY1 + (mYdiff*(x-mX1) + mORad * mXYNorm)/mXdiff;
777  }
778 
779  inline ValueT
780  circle1Bottom(const ValueT& x) const
781  {
782  return BaseT::circleBottom(mX1, mY1, mORad, x);
783  }
784 
785  inline ValueT
786  circle1Top(const ValueT& x) const
787  {
788  return BaseT::circleTop(mX1, mY1, mORad, x);
789  }
790 
791  inline ValueT
792  circle2Bottom(const ValueT& x) const
793  {
794  return BaseT::circleBottom(mX2, mY2, mORad, x);
795  }
796 
797  inline ValueT
798  circle2Top(const ValueT& x) const
799  {
800  return BaseT::circleTop(mX2, mY2, mORad, x);
801  }
802 
803  inline ValueT
804  sphere1Bottom(const ValueT& x, const ValueT& y) const
805  {
806  return BaseT::sphereBottom(mX1, mY1, mZ1, mORad, x, y);
807  }
808 
809  inline ValueT
810  sphere1Top(const ValueT& x, const ValueT& y) const
811  {
812  return BaseT::sphereTop(mX1, mY1, mZ1, mORad, x, y);
813  }
814 
815  inline ValueT
816  sphere2Bottom(const ValueT& x, const ValueT& y) const
817  {
818  return BaseT::sphereBottom(mX2, mY2, mZ2, mORad, x, y);
819  }
820 
821  inline ValueT
822  sphere2Top(const ValueT& x, const ValueT& y) const
823  {
824  return BaseT::sphereTop(mX2, mY2, mZ2, mORad, x, y);
825  }
826 
827  // world space points and radius inputs
828  // initializes class members in index space
829  template<typename ScalarType>
830  inline bool
832  const ScalarType& r, const math::Vec3<ScalarType>& nrml1,
833  const math::Vec3<ScalarType>& nrml2)
834  {
835  const ValueT vx = BaseT::voxelSize(),
836  hw = BaseT::halfWidth();
837 
838  // forces x1 <= x2
839  if (pt1[0] <= pt2[0]) {
840  mPt1 = Vec3T(pt1)/vx;
841  mPt2 = Vec3T(pt2)/vx;
842  } else {
843  mPt1 = Vec3T(pt2)/vx;
844  mPt2 = Vec3T(pt1)/vx;
845  }
846 
847  mRad = ValueT(r)/vx;
848 
849  // padded radius used to populate the outer halfwidth of the sdf
850  mORad = mRad + hw;
851  mORad2 = mORad * mORad;
852 
853  // tube has no volume
854  if (math::isApproxZero(mORad) || mORad < 0)
855  return false;
856 
857  mV = mPt2 - mPt1;
858  mVLenSqr = mV.lengthSqr();
859 
860  // no direction to form the wedge on a sphere
861  if (math::isApproxZero(mVLenSqr))
862  return false;
863 
864  mX1 = mPt1[0]; mY1 = mPt1[1]; mZ1 = mPt1[2];
865  mX2 = mPt2[0]; mY2 = mPt2[1]; mZ2 = mPt2[2];
866 
867  mXdiff = mX2 - mX1;
868  mYdiff = mY2 - mY1;
869  mZdiff = mZ2 - mZ1;
870 
871  mPt12d = Vec2T(mX1, mY1);
872  mPt22d = Vec2T(mX2, mY2);
873  mV2d = mPt22d - mPt12d;
874 
875  mXYNorm2 = math::Pow2(mXdiff) + math::Pow2(mYdiff);
876  mXYNorm = math::Sqrt(mXYNorm2);
877  mIsVertical = math::isApproxZero(mXYNorm);
878 
879  {
880  const Vec3T n1 = Vec3T(nrml1), n2 = Vec3T(nrml2);
881 
882  // no direction to form the wedge
883  if (math::isApproxZero(n1.lengthSqr()) || math::isApproxZero(n2.lengthSqr()))
884  return false;
885 
886  mPlaneNrmls[0] = (n1 - n1.projection(mV)).unitSafe();
887  mPlaneNrmls[1] = (n2 - n2.projection(mV)).unitSafe();
888 
889  // degenerate wedge
890  if (approxAntiParallel(mPlaneNrmls[0], mPlaneNrmls[1]))
891  return false;
892 
893  mDirVectors[0] = mORad * mV.cross(mPlaneNrmls[0]).unitSafe();
894  mDirVectors[1] = mORad * mV.cross(mPlaneNrmls[1]).unitSafe();
895 
896  if (approxParallel(mPlaneNrmls[0], mPlaneNrmls[1])) {
897  mDirVectors[1] = -mDirVectors[0];
898  } else {
899  if (mPlaneNrmls[1].dot(mDirVectors[0]) > 0)
900  mDirVectors[0] *= ValueT(-1);
901  if (mPlaneNrmls[0].dot(mDirVectors[1]) > 0)
902  mDirVectors[1] *= ValueT(-1);
903  }
904 
905  mPlanePts[0] = mPt1 + mDirVectors[0] + ValueT(0.025) * mPlaneNrmls[0];
906  mPlanePts[1] = mPt1 + mDirVectors[1] + ValueT(0.025) * mPlaneNrmls[1];
907  }
908 
909  {
910  mPlaneXCoeffs.assign(2, ValueT(0));
911  mPlaneYCoeffs.assign(2, ValueT(0));
912  mPlaneOffsets.assign(2, ValueT(0));
913 
914  for (Index i = 0; i < 2; ++i) {
915  if (!math::isApproxZero(mPlaneNrmls[i].z())) {
916  const ValueT cx = mPlaneNrmls[i].x()/mPlaneNrmls[i].z(),
917  cy = mPlaneNrmls[i].y()/mPlaneNrmls[i].z();
918  const Vec3T p = mPlanePts[i];
919  mPlaneXCoeffs[i] = -cx;
920  mPlaneYCoeffs[i] = -cy;
921  mPlaneOffsets[i] = p.x()*cx + p.y()*cy + p.z();
922  }
923  }
924  }
925 
926  BaseT::bottomTop = mIsVertical ? capsuleBottomTopVertical : capsuleBottomTop;
927 
928  return true;
929  }
930 
931  inline bool
932  approxAntiParallel(const Vec3T& n1, const Vec3T& n2)
933  {
934  return approxParallel(n1, -n2);
935  }
936 
937  inline bool
938  approxParallel(const Vec3T& n1, const Vec3T& n2)
939  {
940  return n1.unitSafe().eq(n2.unitSafe());
941  }
942 
943  // ------------ private members ------------
944 
945  // wedge data -- populated via initialize()
946 
947  Vec3T mPt1, mPt2, mV;
948 
949  Vec2T mPt12d, mPt22d, mV2d;
950 
951  ValueT mORad, mORad2, mRad, mVLenSqr, mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2;
952 
953  ValueT mX1, mY1, mZ1, mX2, mY2, mZ2;
954 
955  bool mIsVertical;
956 
957  std::vector<Vec3T> mPlaneNrmls = std::vector<Vec3T>(2),
958  mDirVectors = std::vector<Vec3T>(2),
959  mPlanePts = std::vector<Vec3T>(2);
960 
961  std::vector<ValueT> mPlaneXCoeffs = std::vector<ValueT>(2),
962  mPlaneYCoeffs = std::vector<ValueT>(2),
963  mPlaneOffsets = std::vector<ValueT>(2);
964 
965 }; // class OpenCapsuleWedgeVoxelizer
966 
967 
968 /// @brief Class representing the connectivity of edges in a triangle mesh,
969 /// where each edge is associated with the cells (triangles) sharing it.
970 /// Provides methods to retrieve adjacent cells,
971 /// vertex coordinates, normals, and other geometric properties.
972 template<typename ValueT>
974 
975  static_assert(std::is_floating_point<ValueT>::value);
976 
977  using Vec3T = math::Vec3<ValueT>;
978 
979 public:
980 
981  /// @brief Constructs the TriangleMeshEdgeConnectivity object with given coordinates and cell data.
982  /// Populates edge-to-cell adjacency and computes cell normals.
983  ///
984  /// @param coords Vector of vertex coordinates.
985  /// @param cells Vector of cell (triangle) indices.
986  TriangleMeshEdgeConnectivity(const std::vector<Vec3T>& coords,
987  const std::vector<Vec3I>& cells)
988  : mCoords(coords), mCells(cells)
989  {
990  const Index n = Index(coords.size());
991 
992  mNormals.resize(cells.size());
993 
994  for (Index i = 0; i < cells.size(); ++i) {
995  const Vec3I& cell = mCells[i];
996 
997  Edge edges[3] = {
998  Edge(cell[0], cell[1]),
999  Edge(cell[1], cell[2]),
1000  Edge(cell[2], cell[0])
1001  };
1002 
1003  for (const Edge& edge : edges) {
1004  mEdgeMap[edge].push_back(i);
1005  }
1006 
1007  if (cell[0] >= n || cell[1] >= n || cell[2] >= n)
1008  OPENVDB_THROW(ValueError, "out of bounds index");
1009 
1010  const Vec3T &p1 = mCoords[cell[0]],
1011  &p2 = mCoords[cell[1]],
1012  &p3 = mCoords[cell[2]];
1013 
1014  mNormals[i] = (p2 - p1).cross(p3 - p1).unitSafe();
1015  }
1016 
1017  for (auto& [edge, cells] : mEdgeMap)
1018  sortAdjacentCells(edge, cells);
1019  }
1020 
1021  /// @brief Retrieves the IDs of cells adjacent to an edge formed by two vertices.
1022  ///
1023  /// @param v1 First vertex index.
1024  /// @param v2 Second vertex index.
1025  /// @param cellIds Output vector to hold the IDs of adjacent cells.
1026  /// @return True if adjacent cells are found, false otherwise.
1027  bool
1028  getAdjacentCells(const Index& v1, const Index& v2, std::vector<Index>& cellIds) const
1029  {
1030  Edge edge(v1, v2);
1031  auto it = mEdgeMap.find(edge);
1032  if (it != mEdgeMap.end()) {
1033  cellIds = it->second;
1034  return true;
1035  }
1036  return false;
1037  }
1038 
1039  /// @brief Retrieves the 3D coordinate at a given index.
1040  /// @tparam T Any integral type (int, unsigned int, size_t, etc.)
1041  /// @param i Index of the vertex.
1042  /// @return The 3D coordinate as a constant reference to Vec3T.
1043  template <typename T>
1044  inline const Vec3T&
1045  getCoord(const T& i) const
1046  {
1047  static_assert(std::is_integral<T>::value, "Index must be an integral type");
1048 
1049  return mCoords[i];
1050  }
1051 
1052  /// @brief Retrieves the cell (triangle) at a given index.
1053  /// @tparam T Any integral type (int, unsigned int, size_t, etc.)
1054  /// @param i Index of the cell.
1055  /// @return Constant reference to the triangle's vertex indices.
1056  template <typename T>
1057  inline const Vec3I&
1058  getCell(const T& i) const
1059  {
1060  static_assert(std::is_integral<T>::value, "Index must be an integral type");
1061 
1062  return mCells[i];
1063  }
1064 
1065  /// @brief Retrieves the 3D coordinates of the vertices forming a
1066  /// primitive (triangle) at a given cell index.
1067  /// @tparam T Any integral type (int, unsigned int, size_t, etc.)
1068  /// @param i Index of the cell (triangle).
1069  /// @return A vector of three Vec3T representing the coordinates of the triangle's vertices.
1070  template <typename T>
1071  inline std::vector<Vec3T>
1072  getPrimitive(const T& i) const
1073  {
1074  static_assert(std::is_integral<T>::value, "Index must be an integral type");
1075 
1076  const Vec3I cell = mCells[i];
1077 
1078  return {mCoords[cell[0]], mCoords[cell[1]], mCoords[cell[2]]};
1079  }
1080 
1081  /// @brief Retrieves the unit normal vector of a cell (triangle) at a given index.
1082  /// @tparam T Any integral type (int, unsigned int, size_t, etc.)
1083  /// @param i Index of the cell.
1084  /// @return The normal vector of the triangle as a Vec3T.
1085  template <typename T>
1086  inline Vec3T
1087  getNormal(const T& i) const
1088  {
1089  static_assert(std::is_integral<T>::value, "Index must be an integral type");
1090 
1091  return mNormals[i];
1092  }
1093 
1094  /// @brief Retrieves the total number of coordinates in the mesh.
1095  ///
1096  /// @return The number of coordinates as an Index.
1097  inline Index64
1098  coordCount() const
1099  {
1100  return mCoords.size();
1101  }
1102 
1103  /// @brief Retrieves the total number of cells (triangles) in the mesh.
1104  ///
1105  /// @return The number of cells as an Index.
1106  inline Index64
1107  cellCount() const
1108  {
1109  return mCells.size();
1110  }
1111 
1112 private:
1113  struct Edge {
1114  Index mV1, mV2;
1115 
1116  Edge(Index v1, Index v2)
1117  : mV1(std::min(v1, v2)), mV2(std::max(v1, v2))
1118  {
1119  }
1120 
1121  bool operator<(const Edge& e) const
1122  {
1123  return mV1 < e.mV1 || (mV1 == e.mV1 && mV2 < e.mV2);
1124  }
1125  };
1126 
1127  inline Vec3T
1128  centroid(Index cellIdx) const
1129  {
1130  const Vec3I cell = mCells[cellIdx];
1131  return (mCoords[cell[0]] + mCoords[cell[1]] + mCoords[cell[2]]) / 3.0;
1132  }
1133 
1134  inline bool
1135  onSameHalfPlane(const Vec3T &n, const Vec3T& p0, const Vec3T &p1, const Vec3T &p2)
1136  {
1137  return math::Abs(math::Sign(n.dot(p1-p0)) - math::Sign(n.dot(p2-p0))) != 2;
1138  }
1139 
1140  inline void
1141  sortAdjacentCells(const Edge& edge, std::vector<Index>& cells)
1142  {
1143  if (cells.size() <= 2) return;
1144 
1145  const Vec3I &base_cell = mCells[cells[0]];
1146  const Index offset = edge.mV1 + edge.mV2;
1147 
1148  const Index p1Ind = base_cell[0] + base_cell[1] + base_cell[2] - offset;
1149 
1150  const Vec3T &p1 = mCoords[p1Ind],
1151  &n1 = mNormals[cells[0]];
1152 
1153  const Vec3T p0 = mCoords[edge.mV1];
1154 
1155  Vec3T bi_nrml = n1.cross(p0 - mCoords[edge.mV2]);
1156  if (bi_nrml.dot(p1 - p0) > 0)
1157  bi_nrml *= ValueT(-1);
1158 
1159  auto windingamount = [&](Index cellIdx)
1160  {
1161  if (cellIdx == 0) return 0.0f;
1162 
1163  const Vec3I &cell = mCells[cellIdx];
1164  const Index p2Ind = cell[0] + cell[1] + cell[2] - offset;
1165 
1166  const Vec3T &p2 = mCoords[p2Ind],
1167  &n2 = mNormals[cellIdx];
1168 
1169  const ValueT cos_theta = math::Abs(n1.dot(n2));
1170  const int sgn = math::Sign(n1.dot(p2 - p1)),
1171  sgn2 = math::Sign(bi_nrml.dot(p2 - p0));
1172 
1173  return sgn != 0
1174  ? (sgn == 1
1175  ? ValueT(1) + ValueT(sgn2) * cos_theta
1176  : ValueT(3) - ValueT(sgn2) * cos_theta
1177  )
1178  : (onSameHalfPlane(bi_nrml, p0, p1, p2) ? ValueT(0) : ValueT(2));
1179  };
1180 
1181  std::sort(cells.begin(), cells.end(), [&](const Index& t1, const Index& t2) {
1182  return windingamount(t1) < windingamount(t2);
1183  });
1184  }
1185 
1186  // ------------ private members ------------
1187 
1188  const std::vector<Vec3T>& mCoords;
1189  const std::vector<Vec3I>& mCells;
1190 
1191  std::vector<Vec3T> mNormals;
1192 
1193  std::map<Edge, std::vector<Index>> mEdgeMap;
1194 
1195 }; // class TriangleMeshEdgeConnectivity
1196 
1197 
1198 /// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set
1199 /// representation of a dilated mesh (surface mesh dilated by a radius in all directions).
1200 ///
1201 /// @note @c GridType::ValueType must be a floating-point scalar.
1202 /// @note @c ScalarType represents the mesh vertex and radius type
1203 /// and must be a floating-point scalar.
1204 template <typename GridType, typename ScalarType = float,
1205  typename InterruptT = util::NullInterrupter, bool PtPartition = true>
1207 
1208  using GridPtr = typename GridType::Ptr;
1209  using TreeT = typename GridType::TreeType;
1210  using LeafT = typename TreeT::LeafNodeType;
1211 
1213 
1216 
1218 
1219  using Vec3T = math::Vec3<ScalarType>;
1220 
1221  static_assert(std::is_floating_point<ScalarType>::value);
1222 
1223 public:
1224 
1225  /// @brief Constructor for constant radius
1226  ///
1227  /// @param vertices vertices of the mesh in world units
1228  /// @param triangles triangle indices indices in the mesh
1229  /// @param radius radius of all faces in world units
1230  /// @param voxelSize voxel size in world units
1231  /// @param halfWidth half-width in voxel units
1232  /// @param interrupter pointer to optional interrupter. Use template
1233  /// argument util::NullInterrupter if no interruption is desired.
1234  DilatedMeshVoxelizer(const std::vector<Vec3T>& vertices, const std::vector<Vec3I>& triangles,
1235  ScalarType radius, float voxelSize, float halfWidth, InterruptT* interrupter)
1236  : mMesh(std::make_shared<const MeshConnectivity>(MeshConnectivity(vertices, triangles)))
1237  , mVox(voxelSize), mHw(halfWidth), mRad(radius)
1238  , mInterrupter(interrupter)
1239  {
1240  initializeGrid();
1241 
1242  if constexpr (PtPartition)
1243  initializePartitioner();
1244 
1245  mPVoxelizer = std::make_shared<PrismVoxelizer>(mGrid, false);
1246  mWVoxelizer = std::make_shared<WedgeVoxelizer>(mGrid, false);
1247  }
1248 
1250  : mMesh(other.mMesh), mVox(other.mVox), mHw(other.mHw)
1251  , mRad(other.mRad), mInterrupter(other.mInterrupter)
1252  , mPtPartitioner(other.mPtPartitioner)
1253  {
1254  initializeGrid();
1255 
1256  mPVoxelizer = std::make_shared<PrismVoxelizer>(mGrid, false);
1257  mWVoxelizer = std::make_shared<WedgeVoxelizer>(mGrid, false);
1258  }
1259 
1260  void operator()(const tbb::blocked_range<size_t>& rng)
1261  {
1262  if (!checkInterrupter())
1263  return;
1264 
1265  if constexpr (PtPartition) {
1266  for (size_t i = rng.begin(); i < rng.end(); ++i)
1267  for (auto it = mPtPartitioner->indices(i); it; ++it)
1268  voxelizeTriangle(*it);
1269  } else {
1270  for (size_t i = rng.begin(); i < rng.end(); ++i)
1271  voxelizeTriangle(i);
1272  }
1273  }
1274 
1276  {
1277  tools::CsgUnionOp<TreeT> op(other.mGrid->tree(), Steal());
1278  tree::DynamicNodeManager<TreeT> nodeManager(mGrid->tree());
1279  nodeManager.foreachTopDown(op, true);
1280 
1281  other.mGrid = nullptr;
1282  }
1283 
1284  inline Index64 bucketSize() const { return mPtPartitioner->size(); }
1285 
1286  inline Index64 cellSize() const { return mMesh->cellCount(); }
1287 
1288  inline GridPtr getGrid() const { return mGrid; }
1289 
1290 private:
1291 
1292  inline bool
1293  affinelyIndependent(const Vec3T& p1, const Vec3T& p2, const Vec3T& p3) const
1294  {
1295  const Vec3T n = (p2-p1).cross(p3-p1);
1296  return !math::isApproxZero(n.x())
1297  || !math::isApproxZero(n.y())
1298  || !math::isApproxZero(n.z());
1299  }
1300 
1301  inline void
1302  voxelizeTriangle(const size_t& i)
1303  {
1304  const Vec3I &cell = mMesh->getCell(i);
1305  const std::vector<Vec3T> pts = mMesh->getPrimitive(i);
1306 
1307  // degenerate triangle
1308  if (!affinelyIndependent(pts[0], pts[1], pts[2])) {
1309  voxelizeCapsule(pts[0], pts[1], pts[2]);
1310  return;
1311  }
1312 
1313  // prism
1314  (*mPVoxelizer)(pts[0], pts[1], pts[2], mRad);
1315 
1316  std::vector<Index> cellIds;
1317  Vec3T n1, n2;
1318 
1319  // wedges
1320  for (Index j = 0; j < 3; ++j) {
1321  const bool success = mMesh->getAdjacentCells(cell[j], cell[(j+1) % 3], cellIds);
1322  if (success && cellIds[0] == i) {
1323  if (findWedgeNormals(Index(i), j, cellIds, n1, n2))
1324  (*mWVoxelizer)(pts[j], pts[(j+1) % 3], mRad, n1, n2);
1325  }
1326  }
1327  }
1328 
1329  inline void
1330  voxelizeCapsule(const Vec3T& p1, const Vec3T& p2, const Vec3T& p3)
1331  {
1332  lvlset::CapsuleVoxelizer<GridType, InterruptT> voxelizer(mGrid, false);
1333 
1334  ScalarType d1 = (p2-p1).lengthSqr(),
1335  d2 = (p3-p2).lengthSqr(),
1336  d3 = (p1-p3).lengthSqr();
1337 
1338  ScalarType maxd = math::Max(d1, d2, d3);
1339 
1340  if (maxd == d1)
1341  voxelizer(p1, p2, mRad);
1342  else if (maxd == d2)
1343  voxelizer(p2, p3, mRad);
1344  else
1345  voxelizer(p3, p1, mRad);
1346  }
1347 
1348  inline bool
1349  findWedgeNormals(const Index& cellIdx, const Index& vIdx,
1350  const std::vector<Index>& cellIds, Vec3T& n1, Vec3T& n2) const
1351  {
1352  if (cellIds.size() == 1)
1353  return findWedgeNormals1(cellIdx, vIdx, n1, n2);
1354  else if (cellIds.size() == 2)
1355  return findWedgeNormals2(cellIdx, vIdx, cellIds[1], n1, n2);
1356  else if (cellIds.size() > 2)
1357  return findWedgeNormals3(cellIdx, vIdx, cellIds, n1, n2);
1358 
1359  return false;
1360  }
1361 
1362  inline bool
1363  findWedgeNormals1(const Index& cellIdx, const Index& vIdx,
1364  Vec3T& n1, Vec3T& n2) const
1365  {
1366  const Vec3I &cell = mMesh->getCell(cellIdx);
1367  const Vec3T &p1 = mMesh->getCoord(cell[vIdx]),
1368  &p2 = mMesh->getCoord(cell[(vIdx+1) % 3]),
1369  &p3 = mMesh->getCoord(cell[(vIdx+2) % 3]);
1370 
1371  const Vec3T &n = mMesh->getNormal(cellIdx);
1372 
1373  n1 = n.cross(p2-p1).unitSafe();
1374  if (n1.dot(p3-p1) < 0) n1 *= -1.0f;
1375 
1376  n2 = n1;
1377 
1378  return true;
1379  }
1380 
1381  inline bool
1382  findWedgeNormals2(const Index& cellIdx, const Index& vIdx,
1383  const Index& cellIdx2, Vec3T& n1, Vec3T& n2) const
1384  {
1385  const Vec3I &cell = mMesh->getCell(cellIdx),
1386  &cell2 = mMesh->getCell(cellIdx2);
1387 
1388  const Index cIdx2 = cell2[0] + cell2[1] + cell2[2] - cell[vIdx] - cell[(vIdx+1) % 3];
1389 
1390  const Vec3T &p1 = mMesh->getCoord(cell[vIdx]),
1391  &p2 = mMesh->getCoord(cell[(vIdx+1) % 3]),
1392  &p3 = mMesh->getCoord(cell[(vIdx+2) % 3]),
1393  &p4 = mMesh->getCoord(cIdx2);
1394 
1395  const Vec3T &nrml1 = mMesh->getNormal(cellIdx),
1396  &nrml2 = mMesh->getNormal(cellIdx2);
1397 
1398  n1 = nrml1.cross(p2-p1).unitSafe(),
1399  n2 = nrml2.cross(p2-p1).unitSafe();
1400 
1401  if (n1.dot(p3-p1) < 0) n1 *= -1.0f;
1402  if (n2.dot(p4-p1) < 0) n2 *= -1.0f;
1403 
1404  return true;
1405  }
1406 
1407  inline bool
1408  findWedgeNormals3(const Index& cellIdx, const Index& vIdx,
1409  const std::vector<Index>& cellIds, Vec3T& n1, Vec3T& n2) const
1410  {
1411  const Vec3I &cell = mMesh->getCell(cellIdx);
1412 
1413  const Index64 n = cellIds.size();
1414  const Index offset = cell[vIdx] + cell[(vIdx+1) % 3];
1415 
1416  for (Index64 i = 0; i < n; ++i) {
1417  const Vec3I &cell0 = mMesh->getCell(cellIds[i]),
1418  &cell1 = mMesh->getCell(cellIds[(i+1) % n]),
1419  &cell2 = mMesh->getCell(cellIds[(i+2) % n]);
1420 
1421  const Index cIdx0 = cell0[0] + cell0[1] + cell0[2] - offset,
1422  cIdx1 = cell1[0] + cell1[1] + cell1[2] - offset,
1423  cIdx2 = cell2[0] + cell2[1] + cell2[2] - offset;
1424 
1425  const Vec3T &p0 = mMesh->getCoord(cIdx0),
1426  &p1 = mMesh->getCoord(cIdx1),
1427  &p2 = mMesh->getCoord(cIdx2);
1428 
1429  Vec3T nrml0 = mMesh->getNormal(cellIds[i]),
1430  nrml1 = mMesh->getNormal(cellIds[(i+1) % n]);
1431 
1432  if (nrml0.dot(p1-p0) > 0) nrml0 *= ScalarType(-1);
1433  if (nrml1.dot(p0-p1) > 0) nrml1 *= ScalarType(-1);
1434 
1435  if (nrml0.dot(p2-p0) > 0 || nrml1.dot(p2-p1) > 0)
1436  continue;
1437 
1438  Index vIdxi;
1439  if (cell0[0] == cell[vIdx])
1440  vIdxi = cell0[1] == cell[(vIdx+1) % 3] ? 0 : 2;
1441  else if (cell0[1] == cell[vIdx])
1442  vIdxi = cell0[2] == cell[(vIdx+1) % 3] ? 1 : 0;
1443  else
1444  vIdxi = cell0[0] == cell[(vIdx+1) % 3] ? 2 : 1;
1445 
1446  return findWedgeNormals2(cellIds[i], vIdxi, cellIds[(i+1) % n], n1, n2);
1447  }
1448 
1449  return false;
1450  }
1451 
1452  inline void
1453  computeCentroids(std::vector<Vec3T>& centroids)
1454  {
1455  centroids.resize(mMesh->cellCount());
1456 
1457  tbb::parallel_for(tbb::blocked_range<size_t>(0, centroids.size()),
1458  [&](const tbb::blocked_range<size_t>& r) {
1459  for (size_t i = r.begin(); i != r.end(); ++i) {
1460  const std::vector<Vec3T> prim = mMesh->getPrimitive(i);
1461  centroids[i] = (prim[0] + prim[1] + prim[2]) / ScalarType(3);
1462  }
1463  });
1464  }
1465 
1466  inline void
1467  initializeGrid()
1468  {
1469  mGrid = createLevelSet<GridType>(mVox, mHw);
1470  }
1471 
1472  inline void
1473  initializePartitioner()
1474  {
1475  std::vector<Vec3T> centroids;
1476  computeCentroids(centroids);
1477 
1478  lvlset::PointArray<Vec3T> points(centroids);
1479 
1480  mPtPartitioner = std::make_shared<PartitionerT>();
1481  mPtPartitioner->construct(points, mGrid->transform());
1482  }
1483 
1484  inline bool
1485  checkInterrupter()
1486  {
1487  if (util::wasInterrupted(mInterrupter)) {
1488  openvdb::thread::cancelGroupExecution();
1489  return false;
1490  }
1491  return true;
1492  }
1493 
1494  // ------------ private members ------------
1495 
1496  std::shared_ptr<const MeshConnectivity> mMesh;
1497 
1498  const float mVox, mHw;
1499 
1500  const ScalarType mRad;
1501 
1502  InterruptT* mInterrupter;
1503 
1504  GridPtr mGrid;
1505 
1506  std::shared_ptr<PartitionerT> mPtPartitioner;
1507 
1508  std::shared_ptr<PrismVoxelizer> mPVoxelizer;
1509  std::shared_ptr<WedgeVoxelizer> mWVoxelizer;
1510 
1511 }; // class DilatedMeshVoxelizer
1512 
1513 } // namespace lvlset
1514 
1515 
1516 // ------------ createLevelSetDilatedMesh ------------- //
1517 
1518 template <typename GridType, typename ScalarType, typename InterruptT>
1519 typename GridType::Ptr
1521  const std::vector<math::Vec3<ScalarType>>& vertices, const std::vector<Vec3I>& triangles,
1522  ScalarType radius, float voxelSize, float halfWidth, InterruptT* interrupter)
1523 {
1524  static_assert(std::is_floating_point<ScalarType>::value);
1525 
1526  using GridPtr = typename GridType::Ptr;
1527  using ValueT = typename GridType::ValueType;
1528 
1530 
1531  static_assert(std::is_floating_point<ValueT>::value,
1532  "createLevelSetDilatedMesh must return a scalar grid");
1533 
1534  if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive");
1535  if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive");
1536 
1537  Voxelizer op(vertices, triangles, radius, voxelSize, halfWidth, interrupter);
1538 
1539  const tbb::blocked_range<size_t> triangleRange(0, op.bucketSize());
1540  tbb::parallel_reduce(triangleRange, op);
1541 
1542  GridPtr grid = op.getGrid();
1543  tools::pruneLevelSet(grid->tree());
1544 
1545  return grid;
1546 }
1547 
1548 template <typename GridType, typename ScalarType, typename InterruptT>
1549 typename GridType::Ptr
1551  const std::vector<math::Vec3<ScalarType>>& vertices, const std::vector<Vec4I>& quads,
1552  ScalarType radius, float voxelSize, float halfWidth, InterruptT* interrupter)
1553 {
1554  static_assert(std::is_floating_point<ScalarType>::value);
1555 
1556  using ValueT = typename GridType::ValueType;
1557 
1558  static_assert(std::is_floating_point<ValueT>::value,
1559  "createLevelSetDilatedMesh must return a scalar grid");
1560 
1561  if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive");
1562  if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive");
1563 
1564  const Index64 n = quads.size();
1565  std::vector<Vec3I> triangles(2*n);
1566 
1567  tbb::parallel_for(tbb::blocked_range<size_t>(0, n),
1568  [&](const tbb::blocked_range<size_t>& r) {
1569  for (Index64 i = r.begin(); i != r.end(); ++i) {
1570  const Vec4I& q = quads[i];
1571  triangles[i] = Vec3I(q.x(), q.y(), q.z());
1572  triangles[i + n] = Vec3I(q.x(), q.z(), q.w());
1573  }
1574  });
1575 
1576  return createLevelSetDilatedMesh<GridType, ScalarType, InterruptT>(
1577  vertices, triangles, radius, voxelSize, halfWidth, interrupter);
1578 }
1579 
1580 template <typename GridType, typename ScalarType, typename InterruptT>
1581 typename GridType::Ptr
1583  const std::vector<Vec3I>& triangles, const std::vector<Vec4I>& quads,
1584  ScalarType radius, float voxelSize, float halfWidth, InterruptT* interrupter)
1585 {
1586  static_assert(std::is_floating_point<ScalarType>::value);
1587 
1588  using ValueT = typename GridType::ValueType;
1589 
1590  static_assert(std::is_floating_point<ValueT>::value,
1591  "createLevelSetDilatedMesh must return a scalar grid");
1592 
1593  if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive");
1594  if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive");
1595 
1596  if (quads.empty())
1597  return createLevelSetDilatedMesh<GridType, ScalarType, InterruptT>(
1598  vertices, triangles, radius, voxelSize, halfWidth, interrupter);
1599 
1600  const Index64 tn = triangles.size(), qn = quads.size();
1601  const Index64 qn2 = tn + qn;
1602  std::vector<Vec3I> tris(tn + 2*qn);
1603 
1604  tbb::parallel_for(tbb::blocked_range<size_t>(0, tn),
1605  [&](const tbb::blocked_range<size_t>& r) {
1606  for (Index64 i = r.begin(); i != r.end(); ++i) {
1607  tris[i] = triangles[i];
1608  }
1609  });
1610 
1611  tbb::parallel_for(tbb::blocked_range<size_t>(0, qn),
1612  [&](const tbb::blocked_range<size_t>& r) {
1613  for (Index64 i = r.begin(); i != r.end(); ++i) {
1614  const Vec4I& q = quads[i];
1615  tris[i + tn] = Vec3I(q.x(), q.y(), q.z());
1616  tris[i + qn2] = Vec3I(q.x(), q.z(), q.w());
1617  }
1618  });
1619 
1620  return createLevelSetDilatedMesh<GridType, ScalarType, InterruptT>(
1621  vertices, tris, radius, voxelSize, halfWidth, interrupter);
1622 }
1623 
1624 } // namespace tools
1625 } // namespace OPENVDB_VERSION_NAME
1626 } // namespace openvdb
1627 
1628 #endif // OPENVDB_TOOLS_LEVELSETDILATEDMESHIMPL_HAS_BEEN_INCLUDED
T & y()
Definition: Vec3.h:87
T & z()
Definition: Vec4.h:80
GridPtr getGrid() const
Definition: LevelSetDilatedMeshImpl.h:1288
Definition: NodeManager.h:37
typename GridType::ValueType ValueT
Definition: ConvexVoxelizer.h:184
#define OPENVDB_THROW(exception, message)
Definition: Exceptions.h:74
const Vec3I & getCell(const T &i) const
Retrieves the cell (triangle) at a given index.
Definition: LevelSetDilatedMeshImpl.h:1058
uint64_t Index64
Definition: Types.h:53
Coord Abs(const Coord &xyz)
Definition: Coord.h:518
General-purpose arithmetic and comparison routines, most of which accept arbitrary value types (or at...
OPENVDB_IMPORT void initialize()
Global registration of native Grid, Transform, Metadata and Point attribute types. Also initializes blosc (if enabled).
float Sqrt(float x)
Return the square root of a floating-point value.
Definition: Math.h:761
Type Clamp01(Type x)
Return x clamped to [0, 1].
Definition: Math.h:271
void join(DilatedMeshVoxelizer &other)
Definition: LevelSetDilatedMeshImpl.h:1275
T & w()
Definition: Vec4.h:81
DilatedMeshVoxelizer(const std::vector< Vec3T > &vertices, const std::vector< Vec3I > &triangles, ScalarType radius, float voxelSize, float halfWidth, InterruptT *interrupter)
Constructor for constant radius.
Definition: LevelSetDilatedMeshImpl.h:1234
DynamicNodeManager operator to merge trees using a CSG union or intersection.
Definition: Merge.h:193
T & x()
Reference to the component, e.g. v.x() = 4.5f;.
Definition: Vec4.h:78
TriangleMeshEdgeConnectivity(const std::vector< Vec3T > &coords, const std::vector< Vec3I > &cells)
Constructs the TriangleMeshEdgeConnectivity object with given coordinates and cell data...
Definition: LevelSetDilatedMeshImpl.h:986
Definition: Coord.h:590
OutGridT XformOp & op
Definition: ValueTransformer.h:139
Index32 Index
Definition: Types.h:54
void operator()(const tbb::blocked_range< size_t > &rng)
Definition: LevelSetDilatedMeshImpl.h:1260
T & z()
Definition: Vec3.h:88
bool isApproxZero(const Type &x)
Return true if x is equal to zero to within the default floating-point comparison tolerance...
Definition: Math.h:349
openvdb::GridBase::Ptr GridPtr
Definition: Utils.h:44
void operator()(const math::Vec3< ScalarType > &pt1, const math::Vec3< ScalarType > &pt2, const math::Vec3< ScalarType > &pt3, const ScalarType &radius)
Create an open prism.
Definition: LevelSetDilatedMeshImpl.h:100
Spatially partitions points using a parallel radix-based sorting algorithm.
Defined various multi-threaded utility functions for trees.
Base class used to generate the narrow-band level set of a convex region.
Definition: Exceptions.h:65
Index64 cellSize() const
Definition: LevelSetDilatedMeshImpl.h:1286
void foreachTopDown(const NodeOp &op, bool threaded=true, size_t leafGrainSize=1, size_t nonLeafGrainSize=1)
Threaded method that applies a user-supplied functor to all the nodes in the tree.
Definition: NodeManager.h:977
Base class for interrupters.
Definition: NullInterrupter.h:25
const Vec3T & getCoord(const T &i) const
Retrieves the 3D coordinate at a given index.
Definition: LevelSetDilatedMeshImpl.h:1045
T & x()
Reference to the component, e.g. v.x() = 4.5f;.
Definition: Vec3.h:86
Base class used to generate a grid of type GridType containing a narrow-band level set representation...
Definition: ConvexVoxelizer.h:170
std::vector< Vec3T > getPrimitive(const T &i) const
Retrieves the 3D coordinates of the vertices forming a primitive (triangle) at a given cell index...
Definition: LevelSetDilatedMeshImpl.h:1072
Tag dispatch class that distinguishes constructors that steal.
Definition: Types.h:758
Vec3< T > cross(const Vec3< T > &v) const
Return the cross product of "this" vector and v;.
Definition: Vec3.h:221
static const int size
Definition: Tuple.h:36
OutGridT XformOp bool threaded
Definition: ValueTransformer.h:140
Index64 coordCount() const
Retrieves the total number of coordinates in the mesh.
Definition: LevelSetDilatedMeshImpl.h:1098
Class representing the connectivity of edges in a triangle mesh, where each edge is associated with t...
Definition: LevelSetDilatedMeshImpl.h:973
Class used to generate a grid of type GridType containing a narrow-band level set representation of a...
Definition: LevelSetDilatedMeshImpl.h:1206
OpenCapsuleWedgeVoxelizer(GridPtr &grid, const bool &threaded=false, InterruptT *interrupter=nullptr)
Constructor.
Definition: LevelSetDilatedMeshImpl.h:391
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
OpenTriangularPrismVoxelizer(GridPtr &grid, const bool &threaded=false, InterruptT *interrupter=nullptr)
Constructor.
Definition: LevelSetDilatedMeshImpl.h:85
Tolerance for floating-point comparison.
Definition: Math.h:148
Definition: Exceptions.h:13
Definition: Mat.h:165
Class used to generate a grid of type GridType containing a narrow-band level set representation of a...
Definition: LevelSetDilatedMeshImpl.h:48
T & y()
Definition: Vec4.h:79
bool operator<(const Tuple< SIZE, T0 > &t0, const Tuple< SIZE, T1 > &t1)
Definition: Tuple.h:175
Vec3< T > unitSafe() const
return normalized this, or (1, 0, 0) if this is null vector
Definition: Vec3.h:392
Generate a narrow-band level set of a capsule, tapered capsule, and tube complex. ...
bool wasInterrupted(T *i, int percent=-1)
Definition: NullInterrupter.h:49
GridType
List of types that are currently supported by NanoVDB.
Definition: NanoVDB.h:220
Vec3T getNormal(const T &i) const
Retrieves the unit normal vector of a cell (triangle) at a given index.
Definition: LevelSetDilatedMeshImpl.h:1087
math::Vec3< Index32 > Vec3I
Definition: Types.h:73
Internal class used by derived ConvexVoxelizer classes that make use of PointPartitioner.
Definition: ConvexVoxelizer.h:41
Index64 cellCount() const
Retrieves the total number of cells (triangles) in the mesh.
Definition: LevelSetDilatedMeshImpl.h:1107
Index64 bucketSize() const
Definition: LevelSetDilatedMeshImpl.h:1284
bool getAdjacentCells(const Index &v1, const Index &v2, std::vector< Index > &cellIds) const
Retrieves the IDs of cells adjacent to an edge formed by two vertices.
Definition: LevelSetDilatedMeshImpl.h:1028
const Type & Min(const Type &a, const Type &b)
Return the minimum of two values.
Definition: Math.h:656
Definition: PointPartitioner.h:77
Definition: Vec2.h:23
Definition: Mat4.h:24
void operator()(const math::Vec3< ScalarType > &pt1, const math::Vec3< ScalarType > &pt2, const ScalarType &radius, const math::Vec3< ScalarType > &nrml1, const math::Vec3< ScalarType > &nrml2)
Create an open wedge.
Definition: LevelSetDilatedMeshImpl.h:409
Class used to generate a grid of type GridType containing a narrow-band level set representation of a...
Definition: LevelSetTubesImpl.h:48
DilatedMeshVoxelizer(DilatedMeshVoxelizer &other, tbb::split)
Definition: LevelSetDilatedMeshImpl.h:1249
int Sign(const Type &x)
Return the sign of the given value as an integer (either -1, 0 or 1).
Definition: Math.h:736
GridType::Ptr createLevelSetDilatedMesh(const std::vector< math::Vec3< ScalarType >> &vertices, const std::vector< Vec3I > &triangles, ScalarType radius, float voxelSize, float halfWidth=float(LEVEL_SET_HALF_WIDTH), InterruptT *interrupter=nullptr)
Return a grid of type GridType containing a narrow-band level set representation of a dilated triangl...
Definition: LevelSetDilatedMeshImpl.h:1520
const Type & Max(const Type &a, const Type &b)
Return the maximum of two values.
Definition: Math.h:595
Class used to generate a grid of type GridType containing a narrow-band level set representation of a...
Definition: LevelSetDilatedMeshImpl.h:353
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition: version.h.in:121
void split(ContainerT &out, const std::string &in, const char delim)
Definition: Name.h:43
T dot(const Vec3< T > &v) const
Dot product.
Definition: Vec3.h:192
const std::enable_if<!VecTraits< T >::IsVec, T >::type & max(const T &a, const T &b)
Definition: Composite.h:110
void pruneLevelSet(TreeT &tree, bool threaded=true, size_t grainSize=1)
Reduce the memory footprint of a tree by replacing nodes whose values are all inactive with inactive ...
Definition: Prune.h:390
#define OPENVDB_USE_VERSION_NAMESPACE
Definition: version.h.in:218