OpenVDB  12.1.0
LevelSetTubesImpl.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 LevelSetTubesImpl.h
7 ///
8 /// @brief Generate a narrow-band level set of a capsule, tapered capsule, and tube complex.
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_LEVELSETTUBESIMPL_HAS_BEEN_INCLUDED
15 #define OPENVDB_TOOLS_LEVELSETTUBESIMPL_HAS_BEEN_INCLUDED
16 
17 #include "ConvexVoxelizer.h"
18 
20 #include <openvdb/tools/Prune.h>
21 
22 #include <openvdb/Grid.h>
23 #include <openvdb/Types.h>
24 #include <openvdb/math/Math.h>
26 
27 #include <tbb/parallel_sort.h>
28 #include <tbb/parallel_for.h>
29 #include <tbb/parallel_reduce.h>
30 
31 #include <cmath>
32 #include <vector>
33 #include <type_traits>
34 
35 
36 namespace openvdb {
38 namespace OPENVDB_VERSION_NAME {
39 namespace tools {
40 
41 namespace lvlset {
42 
43 /// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set
44 /// representation of a capsule.
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  CapsuleVoxelizer<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  using Vec2T = typename BaseT::Vec2T;
68 
69 public:
70 
71  friend class ConvexVoxelizer<
72  GridType,
73  CapsuleVoxelizer<GridType, InterruptT>,
74  InterruptT
75  >;
76 
77  /// @brief Constructor
78  ///
79  /// @param grid scalar grid to populate the level set in
80  /// @param threaded center of the sphere in world units
81  /// @param interrupter pointer to optional interrupter. Use template
82  /// argument util::NullInterrupter if no interruption is desired.
83  ///
84  /// @note The voxel size and half width are determined from the input grid,
85  /// meaning the voxel size and background value need to be set prior to voxelization
86  CapsuleVoxelizer(GridPtr& grid, const bool& threaded = true,
87  InterruptT* interrupter = nullptr)
88  : BaseT(grid, threaded, interrupter)
89  {
90  }
91 
92  /// @brief Create a capsule
93  ///
94  /// @param pt1 first endpoint of the capsule in world units
95  /// @param pt2 second endpoint of the capsule in world units
96  /// @param radius radius of the capsule in world units
97  template<typename ScalarType>
98  void
100  const math::Vec3<ScalarType>& pt2, const ScalarType& r)
101  {
102  static_assert(std::is_floating_point<ScalarType>::value);
103 
104  initialize(pt1, pt2, r);
105 
106  BaseT::iterate();
107  }
108 
109 private:
110 
111  // setXYRangeData inputs:
112  // step: step size in index space (default is 1)
113  //
114  // setXYRangeData class member inputs:
115  // mPt1, mPt2, mORad: a tube with points p1, p2 and radius r (padded for halfwidth)
116  //
117  // setXYRangeData class member outputs:
118  // mXs: list of x ordinates to scan over
119  // mYbs: list of bottom y ordinates to start scanning with, for each x
120  // mYts: list of top y ordinates to stop scanning at, for each x
121  //
122  //
123  // This routine projects the tube on the xy-plane, giving a stadium shape.
124  // It detemines the x-scan range, and the y-range for each x-value.
125  // The x-range is divided into intervals depending on if each y endpoint hits a circle or line.
126  //
127  // The x-range is partitioned by ordinates a0, a1, a2, a3, a4, a5
128  // and based on some cases listed below, some ai will be permuted.
129  //
130  // The x-range intervals have a few flavors depending on
131  // * if the stadium points right-down or right-up
132  // * the bottom circle ends before the top circle starts, or not
133  //
134  // 2D projection of capsule onto xy-plane, giving a stadium:
135  //
136  // ∧ y ********
137  // | *** ***
138  // | * *
139  // | / | *
140  // | / | *
141  // | / | *
142  // | / | p2 *
143  // | / | / \ r *
144  // | / | / \ *|
145  // | / | / \ *|
146  // | / |/ * |
147  // | / /| / | |
148  // | / / | / | |
149  // | / / | / | |
150  // | * / | / | |
151  // | *| / | / | |
152  // | *| / | / | |
153  // | * | / | / | |
154  // | * | p1 | | |
155  // | * | / | | |
156  // | |*| / | | |
157  // | |*| / | | |
158  // | | | * | | |
159  // | | |*** *** | | | |
160  // | | | ******** | | | |
161  // | | | | | | |
162  // | | | | | | |
163  // | a0 a1 a2 a3 a4 a5
164  // | x
165  // └----------------------------------------------------------->
166  //
167  // In this schematic, we have a0 < a1 < a2 < a3 < a4 < a5,
168  // but, for examples, it's possible for a2 and a3 to swap if the stadium is more vertical
169 
170  inline void
171  setXYRangeData(const Index& step = 1)
172  {
173  const ValueT stepv = ValueT(step);
174 
175  // degenerate
176  if (mX1 - mORad > mX2 + mORad) {
177  mXYData.clear();
178  return;
179  }
180 
181  // short circuit a vertical cylinder
182  if (mIsVertical) {
183  mXYData.reset(mX1 - mORad, mX1 + mORad, step);
184 
185  for (ValueT x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += stepv)
186  mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x));
187  return;
188  }
189 
190  const ValueT v = math::Min(mORad, mORad * math::Abs(mYdiff)/mXYNorm);
191 
192  const ValueT a0 = mX1 - mORad,
193  a1 = mX1 - v,
194  a2 = mX1 + v,
195  a3 = mX2 - v,
196  a4 = mX2 + v,
197  a5 = mX2 + mORad;
198 
199  const ValueT tc0 = tileCeil(a0, step),
200  tc1 = tileCeil(a1, step),
201  tc2 = tileCeil(a2, step),
202  tc3 = tileCeil(a3, step),
203  tc4 = tileCeil(a4, step);
204 
205  mXYData.reset(a0, a5, step);
206 
207  for (ValueT x = tc0; x <= a1; x += stepv)
208  mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x));
209 
210  if (!math::isApproxZero(mXdiff)) {
211  if (mY1 > mY2) {
212  for (ValueT x = tc1; x <= math::Min(a2, a3); x += stepv)
213  mXYData.expandYRange(x, lineBottom(x), circle1Top(x));
214  } else {
215  for (ValueT x = tc1; x <= math::Min(a2, a3); x += stepv)
216  mXYData.expandYRange(x, circle1Bottom(x), lineTop(x));
217  }
218  }
219 
220  if (a2 < a3) {
221  for (ValueT x = tc2; x <= a3; x += stepv)
222  mXYData.expandYRange(x, lineBottom(x), lineTop(x));
223  } else {
224  if (mY2 <= mY1) {
225  for (ValueT x = tc3; x <= a2; x += stepv)
226  mXYData.expandYRange(x, circle2Bottom(x), circle1Top(x));
227  } else {
228  for (ValueT x = tc3; x <= a2; x += stepv)
229  mXYData.expandYRange(x, circle1Bottom(x), circle2Top(x));
230  }
231  }
232 
233  if (!math::isApproxZero(mXdiff)) {
234  if (mY1 > mY2) {
235  for (ValueT x = math::Max(tc2, tc3); x <= a4; x += stepv)
236  mXYData.expandYRange(x, circle2Bottom(x), lineTop(x));
237  } else {
238  for (ValueT x = math::Max(tc2, tc3); x <= a4; x += stepv)
239  mXYData.expandYRange(x, lineBottom(x), circle2Top(x));
240  }
241  }
242 
243  for (ValueT x = tc4; x <= a5; x += stepv)
244  mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x));
245 
246  mXYData.trim();
247  }
248 
249  // distance in index space
250  inline ValueT
251  signedDistance(const Vec3T& p) const
252  {
253  const Vec3T w = p - mPt1;
254  const ValueT dot = w.dot(mV);
255 
256  // carefully short circuit with a fuzzy tolerance, which avoids division by small mVLenSqr
257  if (dot <= math::Tolerance<ValueT>::value())
258  return w.length() - mRad;
259 
260  if (dot >= mVLenSqr)
261  return (p - mPt2).length() - mRad;
262 
263  const ValueT t = w.dot(mV)/mVLenSqr;
264 
265  return (w - t * mV).length() - mRad;
266  }
267 
268  inline bool
269  tileCanFit(const Index& dim) const
270  {
271  return mRad >= BaseT::halfWidth() + ValueT(0.70711) * (ValueT(dim)-ValueT(1));
272  }
273 
274  // vertical capsule
275  // for a given x,y pair, find the z-range of a tube
276  // z-range is bottom sphere cap to the top sphere cap in vertical case
277  std::function<bool(ValueT&, ValueT&, const ValueT&, const ValueT&)> capsuleBottomTopVertical =
278  [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y)
279  {
280  zb = BaseT::sphereBottom(mX1, mY1, math::Min(mZ1, mZ2), mORad, x, y);
281  zt = BaseT::sphereTop(mX2, mY2, math::Max(mZ1, mZ2), mORad, x, y);
282 
283  return std::isfinite(zb) && std::isfinite(zt);
284  };
285 
286  // non vertical capsule
287  // for a given x,y pair, find the z-range of a tube
288  // first find the z-range as if its an infinite cylinder
289  // then for each z-range endpoint, determine if it should be on a sphere cap
290  std::function<bool(ValueT&, ValueT&, const ValueT&, const ValueT&)> capsuleBottomTop =
291  [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y)
292  {
293  ValueT cylptb, cylptt;
294  if (!infiniteCylinderBottomTop(cylptb, cylptt, x, y))
295  return false;
296 
297  const ValueT dotb = (Vec3T(x, y, cylptb) - mPt1).dot(mV);
298  const ValueT dott = (Vec3T(x, y, cylptt) - mPt1).dot(mV);
299 
300  if (dotb < 0)
301  zb = sphere1Bottom(x, y);
302  else if (dotb > mVLenSqr)
303  zb = sphere2Bottom(x, y);
304  else
305  zb = cylptb;
306 
307  if (dott < 0)
308  zt = sphere1Top(x, y);
309  else if (dott > mVLenSqr)
310  zt = sphere2Top(x, y);
311  else
312  zt = cylptt;
313 
314  return std::isfinite(zb) && std::isfinite(zt);
315  };
316 
317  // assumes capsule is not vertical!
318  inline bool
319  infiniteCylinderBottomTop(ValueT& cylptb, ValueT& cylptt, const ValueT& x, const ValueT& y) const
320  {
321  const Vec2T q(x, y);
322 
323  const Vec2T qproj = mPt12d + mV2d*((q - mPt12d).dot(mV2d))/mXYNorm2;
324 
325  const ValueT t = mX1 != mX2 ? (qproj[0] - mX1)/mXdiff : (qproj[1] - mY1)/mYdiff;
326 
327  const Vec3T qproj3D = mPt1 + t * mV;
328 
329  const ValueT d2 = (q - qproj).lengthSqr();
330 
331  // outside of cylinder's 2D projection
332  if (mORad2 < d2)
333  return false;
334 
335  const ValueT h = math::Sqrt((mORad2 - d2) * mVLenSqr/mXYNorm2);
336 
337  cylptb = qproj3D[2] - h;
338  cylptt = qproj3D[2] + h;
339 
340  return true;
341  }
342 
343  inline ValueT
344  lineBottom(const ValueT& x) const
345  {
346  return mY1 + (mYdiff*(x-mX1) - mORad * mXYNorm)/mXdiff;
347  }
348 
349  inline ValueT
350  lineTop(const ValueT& x) const
351  {
352  return mY1 + (mYdiff*(x-mX1) + mORad * mXYNorm)/mXdiff;
353  }
354 
355  inline ValueT
356  circle1Bottom(const ValueT& x) const
357  {
358  return BaseT::circleBottom(mX1, mY1, mORad, x);
359  }
360 
361  inline ValueT
362  circle1Top(const ValueT& x) const
363  {
364  return BaseT::circleTop(mX1, mY1, mORad, x);
365  }
366 
367  inline ValueT
368  circle2Bottom(const ValueT& x) const
369  {
370  return BaseT::circleBottom(mX2, mY2, mORad, x);
371  }
372 
373  inline ValueT
374  circle2Top(const ValueT& x) const
375  {
376  return BaseT::circleTop(mX2, mY2, mORad, x);
377  }
378 
379  inline ValueT
380  sphere1Bottom(const ValueT& x, const ValueT& y) const
381  {
382  return BaseT::sphereBottom(mX1, mY1, mZ1, mORad, x, y);
383  }
384 
385  inline ValueT
386  sphere1Top(const ValueT& x, const ValueT& y) const
387  {
388  return BaseT::sphereTop(mX1, mY1, mZ1, mORad, x, y);
389  }
390 
391  inline ValueT
392  sphere2Bottom(const ValueT& x, const ValueT& y) const
393  {
394  return BaseT::sphereBottom(mX2, mY2, mZ2, mORad, x, y);
395  }
396 
397  inline ValueT
398  sphere2Top(const ValueT& x, const ValueT& y) const
399  {
400  return BaseT::sphereTop(mX2, mY2, mZ2, mORad, x, y);
401  }
402 
403  // world space points and radius inputs
404  // initializes class members in index space
405  template<typename ScalarType>
406  inline void
408  const math::Vec3<ScalarType>& pt2, const ScalarType& r)
409  {
410  const ValueT vx = BaseT::voxelSize(),
411  hw = BaseT::halfWidth();
412 
413  if (pt1[0] <= pt2[0]) {
414  mPt1 = Vec3T(pt1)/vx;
415  mPt2 = Vec3T(pt2)/vx;
416  } else {
417  mPt1 = Vec3T(pt2)/vx;
418  mPt2 = Vec3T(pt1)/vx;
419  }
420 
421  mRad = ValueT(r)/vx;
422 
423  // padded radius used to populate the outer halfwidth of the sdf
424  mORad = mRad + hw;
425  mORad2 = mORad * mORad;
426 
427  mV = mPt2 - mPt1;
428  mVLenSqr = mV.lengthSqr();
429 
430  mX1 = mPt1[0]; mY1 = mPt1[1]; mZ1 = mPt1[2];
431  mX2 = mPt2[0]; mY2 = mPt2[1]; mZ2 = mPt2[2];
432 
433  mXdiff = mX2 - mX1;
434  mYdiff = mY2 - mY1;
435  mZdiff = mZ2 - mZ1;
436 
437  mPt12d = Vec2T(mX1, mY1);
438  mPt22d = Vec2T(mX2, mY2);
439  mV2d = mPt22d - mPt12d;
440 
441  mXYNorm2 = math::Pow2(mXdiff) + math::Pow2(mYdiff);
442  mXYNorm = math::Sqrt(mXYNorm2);
443  mIsVertical = math::isApproxZero(mXYNorm);
444 
445  BaseT::bottomTop = mIsVertical ? capsuleBottomTopVertical : capsuleBottomTop;
446  }
447 
448  // ------------ private members ------------
449 
450  // tube data -- populated via initialize()
451 
452  Vec3T mPt1, mPt2, mV;
453 
454  Vec2T mPt12d, mPt22d, mV2d;
455 
456  ValueT mORad, mORad2, mRad, mVLenSqr, mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2;
457 
458  ValueT mX1, mY1, mZ1, mX2, mY2, mZ2;
459 
460  bool mIsVertical;
461 
462 }; // class CapsuleVoxelizer
463 
464 
465 /// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set
466 /// representation of a tapered capsule.
467 ///
468 /// @note @c GridType::ValueType must be a floating-point scalar.
469 template <typename GridType, typename InterruptT = util::NullInterrupter>
471  : public ConvexVoxelizer<
472  GridType,
473  TaperedCapsuleVoxelizer<GridType, InterruptT>,
474  InterruptT>
475 {
476  using GridPtr = typename GridType::Ptr;
477 
478  using BaseT = ConvexVoxelizer<
479  GridType,
481  InterruptT
482  >;
483 
484  using BaseT::mXYData;
485  using BaseT::tileCeil;
486 
487  using ValueT = typename BaseT::ValueT;
488  using Vec3T = typename BaseT::Vec3T;
489  using Vec2T = typename BaseT::Vec2T;
490 
491 public:
492 
493  friend class ConvexVoxelizer<
494  GridType,
495  TaperedCapsuleVoxelizer<GridType, InterruptT>,
496  InterruptT
497  >;
498 
499  /// @brief Constructor
500  ///
501  /// @param grid scalar grid to populate the level set in
502  /// @param threaded center of the sphere in world units
503  /// @param interrupter pointer to optional interrupter. Use template
504  /// argument util::NullInterrupter if no interruption is desired.
505  ///
506  /// @note The voxel size and half width are determined from the input grid,
507  /// meaning the voxel size and background value need to be set prior to voxelization
508  TaperedCapsuleVoxelizer(GridPtr& grid, const bool& threaded = true,
509  InterruptT* interrupter = nullptr)
510  : BaseT(grid, threaded, interrupter)
511  {
512  }
513 
514  /// @brief Create a tapered capsule
515  ///
516  /// @param pt1 first endpoint of the tapered capsule in world units
517  /// @param pt2 second endpoint of the tapered capsule in world units
518  /// @param radius1 radius of the tapered capsule at @c pt1 in world units
519  /// @param radius2 radius of the tapered capsule at @c pt2 in world units
520  template<typename ScalarType>
521  void
523  const ScalarType& radius1, const ScalarType& radius2)
524  {
525  static_assert(std::is_floating_point<ScalarType>::value);
526 
527  // fail on degenerate inputs for now
528 
529  // ball
530  if ((pt1 - pt2).lengthSqr() <= math::Pow2(radius1 - radius2)) {
532  "The tapered capsule is degenerate, in this case it is a ball. Consider using the CapsuleVoxelizer class instead.");
533  }
534 
535  // capsule
536  if (math::Abs(radius1 - radius2) < 0.001f*BaseT::voxelSize()) {
538  "The tapered capsule is degenerate, in this case it is a capsule. Consider using the CapsuleVoxelizer class instead.");
539  }
540 
541  if (initialize(pt1, pt2, radius1, radius2))
542  BaseT::iterate();
543  }
544 
545 private:
546 
547  inline void
548  setXYRangeData(const Index& step = 1)
549  {
550  const ValueT stepv = ValueT(step);
551 
552  // short circuit when one circle is in the other
553  if (mXYNorm2 <= mRdiff2) {
554  if (mX1 - mORad1 <= mX2 - mORad2) {
555  mXYData.reset(mX1 - mORad1, mX1 + mORad1, step);
556  for (ValueT x = tileCeil(mX1 - mORad1, step); x <= mX1 + mORad1; x += stepv)
557  mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x));
558  } else {
559  mXYData.reset(mX2 - mORad2, mX2 + mORad2, step);
560  for (ValueT x = tileCeil(mX2 - mORad2, step); x <= mX2 + mORad2; x += stepv)
561  mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x));
562  }
563  return;
564  }
565 
566  mXYData.reset(
567  math::Min(mX1 - mORad1, mX2 - mORad2),
568  math::Max(mX1 + mORad1, mX2 + mORad2),
569  step
570  );
571 
572  Vec2T p1t, p2t, p1b, p2b;
573  const bool success = pullyPoints(p1t, p2t, p1b, p2b);
574 
575  if (success) {
576  setLineXYData(p1t, p2t, stepv);
577  setLineXYData(p1b, p2b, stepv);
578 
579  setCircleXYData(p1t, p1b, stepv, true); // mPt1
580  setCircleXYData(p2t, p2b, stepv, false); // mPt2
581  }
582 
583  mXYData.trim();
584  }
585 
586  // https://en.wikipedia.org/wiki/Belt_problem#Pulley_problem
587  inline bool
588  pullyPoints(Vec2T& p1t, Vec2T& p2t, Vec2T& p1b, Vec2T& p2b) const
589  {
590  const ValueT diff = mXYNorm2 - mRdiff2;
591  if (diff < 0)
592  return false;
593 
594  const ValueT alpha = std::atan2(mYdiff, mXdiff),
595  theta = std::atan2(math::Sqrt(diff), mRdiff);
596 
597  const ValueT sin1 = math::Sin(theta + alpha), sin2 = math::Sin(theta - alpha),
598  cos1 = math::Cos(theta + alpha), cos2 = math::Cos(theta - alpha);
599 
600  p1t.x() = mX1 + mORad1*cos1; p1t.y() = mY1 + mORad1*sin1;
601  p2t.x() = mX2 + mORad2*cos1; p2t.y() = mY2 + mORad2*sin1;
602  p2b.x() = mX2 + mORad2*cos2; p2b.y() = mY2 - mORad2*sin2;
603  p1b.x() = mX1 + mORad1*cos2; p1b.y() = mY1 - mORad1*sin2;
604 
605  return true;
606  }
607 
608  inline void
609  setLineXYData(const Vec2T& q1, const Vec2T& q2, const ValueT& step)
610  {
611  if (math::Abs(q1.x() - q2.x()) < math::Tolerance<ValueT>::value()) {
612  ValueT x = tileCeil(q1.x(), step);
613  if (q1.x() == x) {
614  mXYData.expandYRange(x, q1.y());
615  mXYData.expandYRange(x, q2.y());
616  }
617  } else {
618  const bool q1_left = q1.x() < q2.x();
619  const ValueT &x1 = q1_left ? q1.x() : q2.x(),
620  &y1 = q1_left ? q1.y() : q2.y(),
621  &x2 = q1_left ? q2.x() : q1.x(),
622  &y2 = q1_left ? q2.y() : q1.y();
623 
624  ValueT m = (y2 - y1)/(x2 - x1),
625  x = tileCeil(x1, step),
626  y = y1 + m * (x-x1),
627  delta = m * step;
628  for (; x <= x2; x += step, y += delta)
629  mXYData.expandYRange(x, y);
630  }
631  }
632 
633  inline void
634  setCircleXYData(const Vec2T& q1, const Vec2T& q2,
635  const ValueT& step, const bool is_pt1)
636  {
637  const Vec3T &p1 = is_pt1 ? mPt1 : mPt2;
638  const ValueT &r1 = is_pt1 ? mORad1 : mORad2;
639 
640  const std::vector<ValueT> xs = {
641  tileCeil(p1.x() - r1, step),
642  tileCeil(math::Min(q1.x(), q2.x()), step),
643  tileCeil(math::Max(q1.x(), q2.x()), step),
644  tileCeil(p1.x() + r1, step)
645  };
646 
647  for (int i = 0; i < int(xs.size()-1); ++i) {
648  setCircleHiXYData(xs[i], xs[i+1], step, is_pt1);
649  setCircleLoXYData(xs[i], xs[i+1], step, is_pt1);
650  }
651  }
652 
653  inline void
654  setCircleHiXYData(const ValueT& x1, const ValueT& x2,
655  const ValueT& step, const bool& is_pt1)
656  {
657  const ValueT x_test = static_cast<ValueT>(math::Floor(ValueT(0.5)*(x1+x2)));
658 
659  if (is_pt1) {
660  // if |x2-x1| is small, our test point might be too close to the pulley point
661  if (math::Abs(x2-x1) < 5 || mXYData.getYMax(x_test) <= circle1Top(x_test)) {
662  for (ValueT x = x1; x < x2; x += step)
663  mXYData.expandYMax(x, circle1Top(x));
664  }
665  } else {
666  if (math::Abs(x2-x1) < 5 || mXYData.getYMax(x_test) <= circle2Top(x_test)) {
667  for (ValueT x = x1; x < x2; x += step)
668  mXYData.expandYMax(x, circle2Top(x));
669  }
670  }
671  }
672 
673  inline void
674  setCircleLoXYData(const ValueT& x1, const ValueT& x2,
675  const ValueT& step, const bool& is_pt1)
676  {
677  const ValueT x_test = static_cast<ValueT>(math::Floor(ValueT(0.5)*(x1+x2)));
678 
679  if (is_pt1) {
680  // if |x2-x1| is small, our test point might be too close to the pulley point
681  if (math::Abs(x2-x1) < 5 || mXYData.getYMin(x_test) >= circle1Bottom(x_test)) {
682  for (ValueT x = x1; x < x2; x += step)
683  mXYData.expandYMin(x, circle1Bottom(x));
684  }
685  } else {
686  if (math::Abs(x2-x1) < 5 || mXYData.getYMin(x_test) >= circle2Bottom(x_test)) {
687  for (ValueT x = x1; x < x2; x += step)
688  mXYData.expandYMin(x, circle2Bottom(x));
689  }
690  }
691  }
692 
693  // Round Cone: https://iquilezles.org/articles/distfunctions/
694  // distance in index space
695  inline ValueT
696  signedDistance(const Vec3T& p) const
697  {
698  const Vec3T w = p - mPt1;
699  const ValueT y = w.dot(mV),
700  z = y - mVLenSqr,
701  x2 = (w*mVLenSqr - mV*y).lengthSqr(),
702  y2 = y*y*mVLenSqr,
703  z2 = z*z*mVLenSqr,
704  k = mRdiff2*x2; // should multiply by sgn(mRdiff), but it's always positive
705 
706  if (ValueT(math::Sign(z))*mA2*z2 >= k)
707  return math::Sqrt(x2 + z2)*mInvVLenSqr - mRad2;
708 
709  if (ValueT(math::Sign(y))*mA2*y2 <= k)
710  return math::Sqrt(x2 + y2)*mInvVLenSqr - mRad1;
711 
712  return (math::Sqrt(x2*mA2*mInvVLenSqr) + y*mRdiff)*mInvVLenSqr - mRad1;
713  }
714 
715  inline bool
716  tileCanFit(const Index& dim) const
717  {
718  // we know mRad1 >= mRad2
719  return mRad1 >= BaseT::halfWidth() + ValueT(0.70711) * (ValueT(dim)-ValueT(1));
720  }
721 
722  std::function<bool(ValueT&, ValueT&, const ValueT&, const ValueT&)> taperedCapsuleBottomTop =
723  [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y)
724  {
725  const Vec2T q(x, y);
726 
727  const bool in_ball1 = (q - mPt12d).lengthSqr() <= mORad1Sqr,
728  in_ball2 = (q - mPt22d).lengthSqr() <= mORad2Sqr;
729 
730  if (in_ball1) {
731  zt = sphere1Top(x, y);
732  zb = 2.0f*mZ1 - zt;
733  }
734 
735  if (in_ball2) {
736  if (in_ball1) {
737  const ValueT zt2 = sphere2Top(x, y),
738  zb2 = 2.0f*mZ2 - zt2;
739 
740  zt = math::Max(zt, zt2);
741  zb = math::Min(zb, zb2);
742  } else {
743  zt = sphere2Top(x, y);
744  zb = 2.0f*mZ2 - zt;
745  }
746  }
747 
748  // attempt to short circuit when top and bottom hits are on sphere caps
749  if (in_ball1 || in_ball2) {
750  const double ht = mConeD.dot(Vec3d(x,y,zt) - mConeV);
751  // top point is in one of the half spaces pointing away from the cone
752  if (mH1 > ht || ht > mH2) {
753  const double hb = mConeD.dot(Vec3d(x,y,zb) - mConeV);
754  // bottom point is in one of the half spaces pointing away from the cone
755  if (mH1 > hb || hb > mH2)
756  return true;
757  }
758  }
759 
760  ValueT conezb = 0.0f, conezt = 0.0f;
761  int cint_cnt;
762  openConeFrustumBottomTop(conezb, conezt, cint_cnt, x, y);
763 
764  if (in_ball1 && in_ball2) {
765  if (cint_cnt == 2) {
766  zb = math::Min(zb, conezb);
767  zt = math::Max(zt, conezt);
768  } else if (cint_cnt == 1) {
769  zb = math::Min(zb, conezb);
770  zt = math::Max(zt, conezb);
771  }
772 
773  // cint_cnt == 0 implies zb and zt are already set correctly
774  return true;
775  }
776 
777  if (cint_cnt == 2 || (!in_ball1 && !in_ball2)) {
778  zb = conezb; zt = conezt;
779  return cint_cnt == 2;
780  }
781 
782  // we know at this point that in_ball1 ^ in_ball2
783 
784  // zt and zb have been assigned values already based on the ball their in
785  if (cint_cnt == 0)
786  return true;
787 
788  // cint_cnt == 1 and we're only in one ball
789 
790  zt = math::Max(zt, conezb);
791  zb = math::Min(zb, conezb);
792 
793  return true;
794  };
795 
796  // https://www.geometrictools.com/Documentation/IntersectionLineCone.pdf
797  // works in double precision in case the cone tapers very slowly (r1 ~ r2)
798  inline void
799  openConeFrustumBottomTop(ValueT& conezb, ValueT& conezt, int& cint_cnt,
800  const ValueT& x, const ValueT& y) const
801  {
802  cint_cnt = 0;
803  const Vec3d p(double(x), double(y), mRayZ);
804  const Vec3d diff = p - mConeV;
805 
806  const double ddotdiff = mConeD.dot(diff);
807 
808  const double c1 = mGamma * diff.z() - mConeD.z() * ddotdiff;
809  const double c0 = ddotdiff * ddotdiff - mGamma * diff.lengthSqr();
810 
811  if (mC2 != 0.0f) {
812  const double delta = c1*c1 - c0*mC2;
813  if (delta >= 0.0f) {
814  const double sqrt = math::Sqrt(delta);
815  const double t1 = mC2Inv*(-c1 + sqrt);
816  if (validFrustumRange(t1, ddotdiff)) {
817  cint_cnt++;
818  conezb = ValueT(mRayZ - t1);
819  }
820  const double t2 = mC2Inv*(-c1 - sqrt);
821  if (validFrustumRange(t2, ddotdiff)) {
822  cint_cnt++;
823  if (cint_cnt == 2 && t1 > t2)
824  conezt = ValueT(mRayZ - t2);
825  else {
826  conezt = conezb;
827  conezb = ValueT(mRayZ - t2);
828  }
829  }
830  }
831  } else if (c1 != 0.0f) {
832  const double t = -c0/(2.0f*c1);
833  if (validFrustumRange(t, ddotdiff)) {
834  cint_cnt = 1;
835  conezb = ValueT(mRayZ - t);
836  }
837  }
838 
839  // ignore the c2 == c1 == 0 case, where the ray is on the boundary of the cone
840  }
841 
842  inline bool
843  validFrustumRange(const double& t, const double& ddotdiff) const
844  {
845  const double h = ddotdiff - t * mConeD.z();
846 
847  return mH1 <= h && h <= mH2;
848  }
849 
850  inline ValueT
851  circle1Bottom(const ValueT& x) const
852  {
853  return BaseT::circleBottom(mX1, mY1, mORad1, x);
854  }
855 
856  inline ValueT
857  circle1Top(const ValueT& x) const
858  {
859  return BaseT::circleTop(mX1, mY1, mORad1, x);
860  }
861 
862  inline ValueT
863  circle2Bottom(const ValueT& x) const
864  {
865  return BaseT::circleBottom(mX2, mY2, mORad2, x);
866  }
867 
868  inline ValueT
869  circle2Top(const ValueT& x) const
870  {
871  return BaseT::circleTop(mX2, mY2, mORad2, x);
872  }
873 
874  inline ValueT
875  sphere1Bottom(const ValueT& x, const ValueT& y) const
876  {
877  return BaseT::sphereBottom(mX1, mY1, mZ1, mORad1, x, y);
878  }
879 
880  inline ValueT
881  sphere1Top(const ValueT& x, const ValueT& y) const
882  {
883  return BaseT::sphereTop(mX1, mY1, mZ1, mORad1, x, y);
884  }
885 
886  inline ValueT
887  sphere2Bottom(const ValueT& x, const ValueT& y) const
888  {
889  return BaseT::sphereBottom(mX2, mY2, mZ2, mORad2, x, y);
890  }
891 
892  inline ValueT
893  sphere2Top(const ValueT& x, const ValueT& y) const
894  {
895  return BaseT::sphereTop(mX2, mY2, mZ2, mORad2, x, y);
896  }
897 
898  // world space points and radius inputs
899  // initializes class members in index space
900  template<typename ScalarType>
901  inline bool
903  const ScalarType& r1, const ScalarType& r2)
904  {
905  const ValueT vx = BaseT::voxelSize(),
906  hw = BaseT::halfWidth();
907 
908  // enforce mRad1 > mRad2
909  if (r2 <= r1) {
910  mPt1 = Vec3T(pt1)/vx;
911  mPt2 = Vec3T(pt2)/vx;
912  mRad1 = ValueT(r1)/vx;
913  mRad2 = ValueT(r2)/vx;
914  } else {
915  mPt1 = Vec3T(pt2)/vx;
916  mPt2 = Vec3T(pt1)/vx;
917  mRad1 = ValueT(r2)/vx;
918  mRad2 = ValueT(r1)/vx;
919  }
920 
921  // padded radii used to populate the outer halfwidth of the sdf
922  mORad1 = mRad1 + hw;
923  mORad2 = mRad2 + hw;
924  mORad1Sqr = mORad1 * mORad1;
925  mORad2Sqr = mORad2 * mORad2;
926 
927  // all voxels outside the narrow band -- nothing to populate
928  if (mORad1 <= ScalarType(0)) // this implies mORad2 <= 0 too
929  return false;
930 
931  // at this point we know mORad1 > 0
932  // but if mORad2 < 0, truncate the line segment to where radius equals 0
933  // this way things like pullyPoints make geometric sense.
934  if (mORad2 < ScalarType(0)){
935  const ScalarType t = (r2 <= r1 ? r2/(r2 - r1) : r1/(r1 - r2));
936 
937  const math::Vec3<ScalarType> pt_0 = r2 <= r1 ? pt1 : pt2;
938  const ScalarType r_0 = r2 <= r1 ? r1 : r2;
939 
940  const math::Vec3<ScalarType> pt_mid = t*pt1 + (ScalarType(1)-t)*pt2;
941  const ScalarType r_mid = ScalarType(0);
942 
943  return initialize(pt_0, pt_mid, r_0, r_mid);
944  }
945 
946  mV = mPt2 - mPt1;
947  mVLenSqr = mV.lengthSqr();
948  mInvVLenSqr = mVLenSqr != ValueT(0) ? ValueT(1)/mVLenSqr : ValueT(1);
949 
950  mX1 = mPt1[0]; mY1 = mPt1[1]; mZ1 = mPt1[2];
951  mX2 = mPt2[0]; mY2 = mPt2[1]; mZ2 = mPt2[2];
952 
953  mXdiff = mX2 - mX1;
954  mYdiff = mY2 - mY1;
955  mZdiff = mZ2 - mZ1;
956 
957  mPt12d = Vec2T(mX1, mY1);
958  mPt22d = Vec2T(mX2, mY2);
959  mV2d = mPt22d - mPt12d;
960 
961  mXYNorm2 = math::Pow2(mXdiff) + math::Pow2(mYdiff);
962  mXYNorm = math::Sqrt(mXYNorm2);
963  mIXYNorm2 = mXYNorm2 != ValueT(0) ? ValueT(1)/mXYNorm2 : ValueT(1);
964 
965  // mRdiff is non negative
966  mRdiff = mRad1 - mRad2;
967  mRdiff2 = mRdiff * mRdiff;
968  mA2 = mVLenSqr - mRdiff2;
969 
970  // we assume
971  // alpha is solid angle of cone
972  // r1 != r2, since the object is not a capsule
973  // P > abs(r1-r2), since one ball is not contained in the other
974  // we work in double precision in case csc is large
975  const double P = mV.length(),
976  csc = P/mRdiff, // csc(alpha/2)
977  sin = mRdiff/P; // sin(alpha/2)
978  mGamma = 1.0 - mRdiff2/(P*P); // cos(alpha/2)^2
979  mH1 = mORad2*(csc-sin);
980  mH2 = mORad1*(csc-sin);
981 
982  mConeD = -((Vec3d)mV).unitSafe();
983  mConeV = (Vec3d)mPt1 - (double)mORad1 * csc * mConeD;
984 
985  mRayZ = math::Max(mZ1 + mORad1, mZ2 + mORad2) + 2.0;
986  mC2 = math::Pow2(mConeD.z()) - mGamma;
987  mC2Inv = mC2 != 0.0 ? 1.0/mC2 : 1.0;
988 
989  BaseT::bottomTop = taperedCapsuleBottomTop;
990 
991  return true;
992  }
993 
994  // ------------ private members ------------
995 
996  // tapered capsule data -- populated via initialize()
997 
998  Vec3T mPt1, mPt2, mV;
999 
1000  Vec2T mPt12d, mPt22d, mV2d;
1001 
1002  ValueT mORad1, mORad2, mORad1Sqr, mORad2Sqr, mRad1, mRad2, mVLenSqr, mInvVLenSqr,
1003  mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2, mIXYNorm2, mRdiff, mRdiff2, mA2;
1004 
1005  ValueT mX1, mY1, mZ1, mX2, mY2, mZ2;
1006 
1007  // some members are stored explicitly as double because when
1008  // the cone tapers very slowly (r1 ~ r2), then csc(cone_angle) can be very large
1009 
1010  Vec3d mConeV, mConeD;
1011 
1012  double mRayZ, mGamma, mC2, mC2Inv, mH1, mH2;
1013 
1014 }; // class TaperedCapsuleVoxelizer
1015 
1016 
1017 /// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set
1018 /// representation of a tube complex.
1019 ///
1020 /// @note @c GridType::ValueType must be a floating-point scalar.
1021 /// @note @c ScalarType represents the capsule complex vertex and radius type
1022 /// and must be a floating-point scalar.
1023 /// @note Setting @c PerSegmentRadii to @c true gives a complex of capsules and a complex of tapered capsules otherwise.
1024 template <typename GridType, typename ScalarType = float,
1025  typename InterruptT = util::NullInterrupter, bool PerSegmentRadii = true>
1027 
1028  using GridPtr = typename GridType::Ptr;
1029  using TreeT = typename GridType::TreeType;
1030  using LeafT = typename TreeT::LeafNodeType;
1031 
1033 
1034  using Vec3T = math::Vec3<ScalarType>;
1035 
1036  static_assert(std::is_floating_point<ScalarType>::value);
1037 
1038 public:
1039 
1040  /// @brief Constructor for constant radius
1041  ///
1042  /// @param vertices endpoint vertices in the tube complex in world units
1043  /// @param segments segment indices in the tube complex
1044  /// @param radius radius of all tubes in world units
1045  /// @param voxelSize voxel size in world units
1046  /// @param background background value in voxel units
1047  /// @param interrupter pointer to optional interrupter. Use template
1048  /// argument util::NullInterrupter if no interruption is desired.
1049  TubeComplexVoxelizer(const std::vector<Vec3T>& vertices, const std::vector<Vec2I>& segments,
1050  ScalarType radius, float voxelSize, float halfWidth, InterruptT* interrupter)
1051  : mVox(voxelSize), mHw(halfWidth), mRad(radius)
1052  , mCoords(vertices), mCells(segments), mRadii(getEmptyVector())
1053  , mInterrupter(interrupter)
1054  {
1055  initializeGrid();
1056  initializePartitioner();
1057  }
1058 
1059  /// @brief Constructor for varying radii
1060  ///
1061  /// @param vertices endpoint vertices in the tube complex in world units
1062  /// @param segments segment indices in the tube complex
1063  /// @param radii radii specification for all tubes in world units
1064  /// @param voxelSize voxel size in world units
1065  /// @param halfWidth half-width value in voxel units
1066  /// @param interrupter pointer to optional interrupter. Use template
1067  /// argument util::NullInterrupter if no interruption is desired.
1068  ///
1069  /// @note If @c PerSegmentRadii is set to @c true then @c segments and @c radii must have
1070  /// the same size. If @c PerSegmentRadii is set to @c false then @c vertices and @c radii
1071  /// must have the same size.
1072  TubeComplexVoxelizer(const std::vector<Vec3T>& vertices, const std::vector<Vec2I>& segments,
1073  const std::vector<ScalarType>& radii, float voxelSize, float halfWidth,
1074  InterruptT* interrupter)
1075  : mVox(voxelSize), mHw(halfWidth), mRad(0.0)
1076  , mCoords(vertices), mCells(segments), mRadii(radii)
1077  , mInterrupter(interrupter)
1078  {
1079  if constexpr (PerSegmentRadii) {
1080  if (mCells.size() != mRadii.size())
1082  "TubeComplexVoxelizer needs the same number of segments and radii");
1083  } else {
1084  if (mCoords.size() != mRadii.size())
1086  "TubeComplexVoxelizer needs the same number of coordinates and radii");
1087  }
1088 
1089  initializeGrid();
1090  initializePartitioner();
1091  }
1092 
1094  : mVox(other.mVox), mHw(other.mHw), mRad(other.mRad)
1095  , mCoords(other.mCoords), mCells(other.mCells), mRadii(other.mRadii)
1096  , mPtPartitioner(other.mPtPartitioner), mInterrupter(other.mInterrupter)
1097  {
1098  initializeGrid();
1099  }
1100 
1101  template<bool PSR = PerSegmentRadii>
1102  inline typename std::enable_if_t<PSR, void>
1103  operator()(const tbb::blocked_range<size_t>& rng)
1104  {
1105  if (!checkInterrupter())
1106  return;
1107 
1108  if (mRadii.size() == 0)
1109  constantRadiusVoxelize(rng);
1110  else
1111  perSegmentRadiusVoxelize(rng);
1112  }
1113 
1114  template<bool PSR = PerSegmentRadii>
1115  inline typename std::enable_if_t<!PSR, void>
1116  operator()(const tbb::blocked_range<size_t>& rng)
1117  {
1118  if (!checkInterrupter())
1119  return;
1120 
1121  if (mRadii.size() == 0)
1122  constantRadiusVoxelize(rng);
1123  else
1124  perVertexRadiusVoxelize(rng);
1125  }
1126 
1128  {
1129  tools::CsgUnionOp<TreeT> op(other.mGrid->tree(), Steal());
1130  tree::DynamicNodeManager<TreeT> nodeManager(mGrid->tree());
1131  nodeManager.foreachTopDown(op, true);
1132 
1133  other.mGrid = nullptr;
1134  }
1135 
1136  inline Index64 bucketSize() const { return mPtPartitioner->size(); }
1137 
1138  inline GridPtr getGrid() const { return mGrid; }
1139 
1140 private:
1141 
1142  /// TODO increase performance by not creating parts of caps that overlap with other tubes:
1143  ///
1144  /// * Determine segment adjacency
1145  /// * Create _open_ cylinders and conical frustums
1146  /// * Create _open_ & _partial_ caps
1147  ///
1148  /// This should help speed up creation of complexes that contain
1149  /// a bunch of short segments that approximate a smooth curve.
1150  ///
1151  /// Idea is similar to _open_ prisms and _open_ wedges speeding up
1152  /// creation of dilated mesh level sets from finely triangulated meshes.
1153 
1154  inline void
1155  constantRadiusVoxelize(const tbb::blocked_range<size_t>& rng)
1156  {
1157  CapsuleVoxelizer<GridType, InterruptT> voxelizer(mGrid, false);
1158 
1159  for (size_t i = rng.begin(); i < rng.end(); ++i) {
1160  for (auto it = mPtPartitioner->indices(i); it; ++it) {
1161  const Index k = *it;
1162  const Vec2I& cell = mCells[k];
1163  voxelizer(mCoords[cell[0]], mCoords[cell[1]], mRad);
1164  }
1165  }
1166  }
1167 
1168  inline void
1169  perSegmentRadiusVoxelize(const tbb::blocked_range<size_t>& rng)
1170  {
1171  CapsuleVoxelizer<GridType, InterruptT> voxelizer(mGrid, false);
1172 
1173  for (size_t i = rng.begin(); i < rng.end(); ++i) {
1174  for (auto it = mPtPartitioner->indices(i); it; ++it) {
1175  const Index k = *it;
1176  const Vec2I& cell = mCells[k];
1177  voxelizer(mCoords[cell[0]], mCoords[cell[1]], mRadii[k]);
1178  }
1179  }
1180  }
1181 
1182  inline void
1183  perVertexRadiusVoxelize(const tbb::blocked_range<size_t>& rng)
1184  {
1185  TaperedCapsuleVoxelizer<GridType, InterruptT> tc_voxelizer(mGrid, false);
1186 
1187  CapsuleVoxelizer<GridType, InterruptT> c_voxelizer(mGrid, false);
1188 
1189  for (size_t i = rng.begin(); i < rng.end(); ++i) {
1190  for (auto it = mPtPartitioner->indices(i); it; ++it) {
1191  const Index k = *it;
1192  const Vec2I& cell = mCells[k];
1193  const Index32 &i = cell.x(), &j = cell.y();
1194 
1195  const Vec3T &pt1 = mCoords[i], &pt2 = mCoords[j];
1196  const ScalarType &r1 = mRadii[i], &r2 = mRadii[j];
1197 
1198  if ((pt1 - pt2).lengthSqr() <= math::Pow2(r1-r2)) { // ball
1199  if (r1 >= r2)
1200  c_voxelizer(pt1, pt1, r1);
1201  else
1202  c_voxelizer(pt2, pt2, r2);
1203  } else if (math::Abs(r1-r2) < 0.001*mVox) { // capsule
1204  c_voxelizer(pt1, pt2, r1);
1205  } else {
1206  tc_voxelizer(pt1, pt2, r1, r2);
1207  }
1208  }
1209  }
1210  }
1211 
1212  inline void
1213  computeCentroids(std::vector<Vec3T>& centroids)
1214  {
1215  const Index n = Index(mCoords.size());
1216 
1217  centroids.resize(mCells.size());
1218 
1219  tbb::parallel_for(tbb::blocked_range<size_t>(0, centroids.size()),
1220  [&](const tbb::blocked_range<size_t>& r) {
1221  for (size_t i = r.begin(); i != r.end(); ++i) {
1222  const Vec2I &cell = mCells[i];
1223 
1224  if (cell[0] >= n || cell[1] >= n)
1225  OPENVDB_THROW(ValueError, "out of bounds index");
1226 
1227  centroids[i] = ScalarType(0.5) * (mCoords[cell[0]] + mCoords[cell[1]]);
1228  }
1229  });
1230  }
1231 
1232  inline void
1233  initializeGrid()
1234  {
1235  mGrid = createLevelSet<GridType>(mVox, mHw);
1236  }
1237 
1238  inline void
1239  initializePartitioner()
1240  {
1241  std::vector<Vec3T> centroids;
1242  computeCentroids(centroids);
1243 
1244  lvlset::PointArray<Vec3T> points(centroids);
1245 
1246  mPtPartitioner = std::make_shared<PartitionerT>();
1247  mPtPartitioner->construct(points, mGrid->transform());
1248  }
1249 
1250  inline bool
1251  checkInterrupter()
1252  {
1253  if (util::wasInterrupted(mInterrupter)) {
1254  openvdb::thread::cancelGroupExecution();
1255  return false;
1256  }
1257  return true;
1258  }
1259 
1260  static const std::vector<ScalarType>&
1261  getEmptyVector() {
1262  static const std::vector<ScalarType> empty;
1263  return empty;
1264  }
1265 
1266  // ------------ private members ------------
1267 
1268  const float mVox, mHw;
1269 
1270  const ScalarType mRad;
1271 
1272  const std::vector<Vec3T>& mCoords;
1273  const std::vector<Vec2I>& mCells;
1274  const std::vector<ScalarType>& mRadii;
1275 
1276  std::shared_ptr<PartitionerT> mPtPartitioner;
1277 
1278  InterruptT* mInterrupter;
1279 
1280  GridPtr mGrid;
1281 
1282 }; // class TubeComplexVoxelizer
1283 
1284 } // namespace lvlset
1285 
1286 
1287 // ------------ createLevelSetTubeComplex ------------- //
1288 
1289 // constant radius
1290 
1291 template <typename GridType, typename ScalarType, typename InterruptT>
1292 typename GridType::Ptr
1294  const std::vector<Vec2I>& segments, ScalarType radius,
1295  float voxelSize, float halfWidth, InterruptT* interrupter)
1296 {
1297  static_assert(std::is_floating_point<ScalarType>::value);
1298 
1299  using GridPtr = typename GridType::Ptr;
1300  using ValueT = typename GridType::ValueType;
1301 
1302  using ComplexVoxelizer = typename lvlset::TubeComplexVoxelizer<GridType, ScalarType, InterruptT>;
1303 
1304  static_assert(std::is_floating_point<ValueT>::value,
1305  "createLevelSetTubeComplex must return a scalar grid");
1306 
1307  if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive");
1308  if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive");
1309 
1310  ComplexVoxelizer op(vertices, segments, radius, voxelSize, halfWidth, interrupter);
1311 
1312  const tbb::blocked_range<size_t> segmentRange(0, op.bucketSize());
1313  tbb::parallel_reduce(segmentRange, op);
1314 
1315  GridPtr tubegrid = op.getGrid();
1316  tools::pruneLevelSet(tubegrid->tree());
1317 
1318  return tubegrid;
1319 }
1320 
1321 // varying radii
1322 
1323 template <typename GridType, typename ScalarType, typename InterruptT>
1324 typename GridType::Ptr
1326  const std::vector<Vec2I>& segments, const std::vector<ScalarType>& radii,
1327  float voxelSize, float halfWidth, TubeRadiiPolicy radii_policy, InterruptT* interrupter)
1328 {
1329  static_assert(std::is_floating_point<ScalarType>::value);
1330 
1331  using GridPtr = typename GridType::Ptr;
1332  using ValueT = typename GridType::ValueType;
1333 
1334  using CapsuleComplexVoxelizer = typename lvlset::TubeComplexVoxelizer<GridType, ScalarType, InterruptT, true>;
1335  using TaperedCapsuleComplexVoxelizer = typename lvlset::TubeComplexVoxelizer<GridType, ScalarType, InterruptT, false>;
1336 
1337  static_assert(std::is_floating_point<ValueT>::value,
1338  "createLevelSetTubeComplex must return a scalar grid");
1339 
1340  if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive");
1341  if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive");
1342 
1343  if (radii_policy == TUBE_AUTOMATIC) {
1344  if (vertices.size() != radii.size() && segments.size() != radii.size())
1346  "createLevelSetTubeComplex requires equal number of vertices and radii, or segments and radii, with automatic radii policy.");
1347  }
1348 
1349  TubeRadiiPolicy resolved_radii_policy = radii_policy == TUBE_AUTOMATIC
1350  ? (vertices.size() == radii.size() ? TUBE_VERTEX_RADII : TUBE_SEGMENT_RADII)
1351  : radii_policy;
1352 
1353  GridPtr tubegrid;
1354 
1355  switch(resolved_radii_policy) {
1356  case TUBE_VERTEX_RADII : {
1357  if (vertices.size() != radii.size())
1359  "createLevelSetTubeComplex requires equal number of vertices and radii with per-vertex radii policy.");
1360 
1361  TaperedCapsuleComplexVoxelizer op(vertices, segments, radii,
1362  voxelSize, halfWidth, interrupter);
1363 
1364  const tbb::blocked_range<size_t> segmentRange(0, op.bucketSize());
1365  tbb::parallel_reduce(segmentRange, op);
1366 
1367  tubegrid = op.getGrid();
1368  break;
1369  }
1370  case TUBE_SEGMENT_RADII : {
1371  if (segments.size() != radii.size())
1373  "createLevelSetTubeComplex requires equal number of segments and radii with per-segment radii policy.");
1374 
1375  CapsuleComplexVoxelizer op(vertices, segments, radii,
1376  voxelSize, halfWidth, interrupter);
1377 
1378  const tbb::blocked_range<size_t> segmentRange(0, op.bucketSize());
1379  tbb::parallel_reduce(segmentRange, op);
1380 
1381  tubegrid = op.getGrid();
1382  break;
1383  }
1384  default:
1385  OPENVDB_THROW(ValueError, "Invalid tube radii policy.");
1386  }
1387 
1388  tools::pruneLevelSet(tubegrid->tree());
1389 
1390  return tubegrid;
1391 }
1392 
1393 
1394 // ------------ createLevelSetCapsule ------------- //
1395 
1396 template <typename GridType, typename ScalarType, typename InterruptT>
1397 typename GridType::Ptr
1399  ScalarType radius, float voxelSize, float halfWidth,
1400  InterruptT* interrupter, bool threaded)
1401 {
1402  static_assert(std::is_floating_point<ScalarType>::value);
1403 
1404  using GridPtr = typename GridType::Ptr;
1405  using ValueT = typename GridType::ValueType;
1406 
1407  using CapsuleVoxelizer = typename lvlset::CapsuleVoxelizer<GridType, InterruptT>;
1408 
1409  static_assert(std::is_floating_point<ValueT>::value,
1410  "createLevelSetCapsule must return a scalar grid");
1411 
1412  if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive");
1413  if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive");
1414 
1415  GridPtr grid = createLevelSet<GridType>(voxelSize, halfWidth);
1416 
1417  CapsuleVoxelizer voxelizer(grid, threaded, interrupter);
1418  voxelizer(pt1, pt2, radius);
1419 
1420  return grid;
1421 }
1422 
1423 template <typename GridType, typename ScalarType>
1424 typename GridType::Ptr
1426  ScalarType radius, float voxelSize, float halfWidth, bool threaded)
1427 {
1428  return createLevelSetCapsule<GridType, ScalarType, util::NullInterrupter>(
1429  pt1, pt2, radius, voxelSize, halfWidth, nullptr, threaded);
1430 }
1431 
1432 
1433 // ------------ createLevelSetTaperedCapsule ------------- //
1434 
1435 template <typename GridType, typename ScalarType, typename InterruptT>
1436 typename GridType::Ptr
1438  ScalarType radius1, ScalarType radius2,
1439  float voxelSize, float halfWidth, InterruptT* interrupter, bool threaded)
1440 {
1441  static_assert(std::is_floating_point<ScalarType>::value);
1442 
1443  using GridPtr = typename GridType::Ptr;
1444  using ValueT = typename GridType::ValueType;
1445 
1446  using CapsuleVoxelizer = typename lvlset::CapsuleVoxelizer<GridType, InterruptT>;
1447  using TaperedCapsuleVoxelizer = typename lvlset::TaperedCapsuleVoxelizer<GridType, InterruptT>;
1448 
1449  static_assert(std::is_floating_point<ValueT>::value,
1450  "createLevelSetTaperedCapsule must return a scalar grid");
1451 
1452  if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive");
1453  if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive");
1454 
1455  GridPtr grid = createLevelSet<GridType>(voxelSize, halfWidth);
1456 
1457  if ((pt1 - pt2).lengthSqr() <= math::Pow2(radius1 - radius2)) { // ball
1458 
1459  CapsuleVoxelizer voxelizer(grid, threaded, interrupter);
1460  if (radius1 >= radius2)
1461  voxelizer(pt1, pt1, radius1);
1462  else
1463  voxelizer(pt2, pt2, radius2);
1464 
1465  } else if (math::Abs(radius1 - radius2) < 0.001*voxelSize) { // capsule
1466 
1467  CapsuleVoxelizer voxelizer(grid, threaded, interrupter);
1468  voxelizer(pt1, pt2, radius1);
1469 
1470  } else { // tapered capsule
1471 
1472  TaperedCapsuleVoxelizer voxelizer(grid, threaded, interrupter);
1473  voxelizer(pt1, pt2, radius1, radius2);
1474  }
1475 
1476  return grid;
1477 }
1478 
1479 template <typename GridType, typename ScalarType>
1480 typename GridType::Ptr
1482  ScalarType radius1, ScalarType radius2, float voxelSize, float halfWidth, bool threaded)
1483 {
1484  return createLevelSetTaperedCapsule<GridType, ScalarType, util::NullInterrupter>(
1485  pt1, pt2, radius1, radius2, voxelSize, halfWidth, nullptr, threaded);
1486 }
1487 
1488 } // namespace tools
1489 } // namespace OPENVDB_VERSION_NAME
1490 } // namespace openvdb
1491 
1492 #endif // OPENVDB_TOOLS_LEVELSETTUBESIMPL_HAS_BEEN_INCLUDED
TubeComplexVoxelizer(TubeComplexVoxelizer &other, tbb::split)
Definition: LevelSetTubesImpl.h:1093
TubeRadiiPolicy
Different policies when creating a tube complex with varying radii.
Definition: LevelSetTubes.h:130
TubeComplexVoxelizer(const std::vector< Vec3T > &vertices, const std::vector< Vec2I > &segments, ScalarType radius, float voxelSize, float halfWidth, InterruptT *interrupter)
Constructor for constant radius.
Definition: LevelSetTubesImpl.h:1049
Definition: NodeManager.h:37
typename GridType::ValueType ValueT
Definition: ConvexVoxelizer.h:184
#define OPENVDB_THROW(exception, message)
Definition: Exceptions.h:74
uint64_t Index64
Definition: Types.h:53
Class used to generate a grid of type GridType containing a narrow-band level set representation of a...
Definition: LevelSetTubesImpl.h:470
int Floor(float x)
Return the floor of x.
Definition: Math.h:848
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
DynamicNodeManager operator to merge trees using a CSG union or intersection.
Definition: Merge.h:193
void join(TubeComplexVoxelizer &other)
Definition: LevelSetTubesImpl.h:1127
Index64 bucketSize() const
Definition: LevelSetTubesImpl.h:1136
GridType::Ptr createLevelSetCapsule(const math::Vec3< ScalarType > &pt1, const math::Vec3< ScalarType > &pt2, ScalarType radius, float voxelSize, float halfWidth=float(LEVEL_SET_HALF_WIDTH), InterruptT *interrupter=nullptr, bool threaded=true)
Return a grid of type GridType containing a narrow-band level set representation of a capsule (tube w...
Definition: LevelSetTubesImpl.h:1398
float Cos(const float &x)
Return cos x.
Definition: Math.h:725
Class used to generate a grid of type GridType containing a narrow-band level set representation of a...
Definition: LevelSetTubesImpl.h:1026
std::enable_if_t<!PSR, void > operator()(const tbb::blocked_range< size_t > &rng)
Definition: LevelSetTubesImpl.h:1116
OutGridT XformOp & op
Definition: ValueTransformer.h:139
Index32 Index
Definition: Types.h:54
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
GridType::Ptr createLevelSetTubeComplex(const std::vector< math::Vec3< ScalarType >> &vertices, const std::vector< Vec2I > &segments, 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 tube complex (a...
Definition: LevelSetTubesImpl.h:1293
void operator()(const math::Vec3< ScalarType > &pt1, const math::Vec3< ScalarType > &pt2, const ScalarType &r)
Create a capsule.
Definition: LevelSetTubesImpl.h:99
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
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
GridType::Ptr createLevelSetTaperedCapsule(const math::Vec3< ScalarType > &pt1, const math::Vec3< ScalarType > &pt2, ScalarType radius1, ScalarType radius2, float voxelSize, float halfWidth=float(LEVEL_SET_HALF_WIDTH), InterruptT *interrupter=nullptr, bool threaded=true)
Return a grid of type GridType containing a narrow-band level set representation of a tapered capsule...
Definition: LevelSetTubesImpl.h:1437
Definition: LevelSetTubes.h:130
Base class used to generate a grid of type GridType containing a narrow-band level set representation...
Definition: ConvexVoxelizer.h:170
Tag dispatch class that distinguishes constructors that steal.
Definition: Types.h:758
std::enable_if_t< PSR, void > operator()(const tbb::blocked_range< size_t > &rng)
Definition: LevelSetTubesImpl.h:1103
OutGridT XformOp bool threaded
Definition: ValueTransformer.h:140
uint32_t Index32
Definition: Types.h:52
Definition: Exceptions.h:63
Type Pow2(Type x)
Return x2.
Definition: Math.h:548
Tolerance for floating-point comparison.
Definition: Math.h:148
float Sin(const float &x)
Return sin x.
Definition: Math.h:716
bool empty(const char *str)
tests if a c-string str is empty, that is its first value is &#39;\0&#39;
Definition: Util.h:144
Definition: Exceptions.h:13
Definition: Mat.h:165
TubeComplexVoxelizer(const std::vector< Vec3T > &vertices, const std::vector< Vec2I > &segments, const std::vector< ScalarType > &radii, float voxelSize, float halfWidth, InterruptT *interrupter)
Constructor for varying radii.
Definition: LevelSetTubesImpl.h:1072
Definition: LevelSetTubes.h:130
bool wasInterrupted(T *i, int percent=-1)
Definition: NullInterrupter.h:49
Vec3< double > Vec3d
Definition: Vec3.h:665
GridType
List of types that are currently supported by NanoVDB.
Definition: NanoVDB.h:220
Internal class used by derived ConvexVoxelizer classes that make use of PointPartitioner.
Definition: ConvexVoxelizer.h:41
const Type & Min(const Type &a, const Type &b)
Return the minimum of two values.
Definition: Math.h:656
GridPtr getGrid() const
Definition: LevelSetTubesImpl.h:1138
TaperedCapsuleVoxelizer(GridPtr &grid, const bool &threaded=true, InterruptT *interrupter=nullptr)
Constructor.
Definition: LevelSetTubesImpl.h:508
Definition: PointPartitioner.h:77
T & y()
Definition: Vec2.h:72
Definition: Vec2.h:23
Definition: LevelSetTubes.h:130
void operator()(const math::Vec3< ScalarType > &pt1, const math::Vec3< ScalarType > &pt2, const ScalarType &radius1, const ScalarType &radius2)
Create a tapered capsule.
Definition: LevelSetTubesImpl.h:522
CapsuleVoxelizer(GridPtr &grid, const bool &threaded=true, InterruptT *interrupter=nullptr)
Constructor.
Definition: LevelSetTubesImpl.h:86
Class used to generate a grid of type GridType containing a narrow-band level set representation of a...
Definition: LevelSetTubesImpl.h:48
int Sign(const Type &x)
Return the sign of the given value as an integer (either -1, 0 or 1).
Definition: Math.h:736
const Type & Max(const Type &a, const Type &b)
Return the maximum of two values.
Definition: Math.h:595
#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 & x()
Reference to the component, e.g. v.x() = 4.5f;.
Definition: Vec2.h:71
T dot(const Vec3< T > &v) const
Dot product.
Definition: Vec3.h:192
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