OpenVDB 12.1.0
Loading...
Searching...
No Matches
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
36namespace openvdb {
38namespace OPENVDB_VERSION_NAME {
39namespace tools {
40
41namespace 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.
47template <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
69public:
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
107 }
108
109private:
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
407 initialize(const math::Vec3<ScalarType>& pt1,
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.
469template <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
491public:
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))
543 }
544
545private:
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
902 initialize(const math::Vec3<ScalarType>& pt1, const math::Vec3<ScalarType>& pt2,
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.
1024template <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
1038public:
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
1140private:
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
1291template <typename GridType, typename ScalarType, typename InterruptT>
1292typename 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
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
1323template <typename GridType, typename ScalarType, typename InterruptT>
1324typename 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
1396template <typename GridType, typename ScalarType, typename InterruptT>
1397typename 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
1423template <typename GridType, typename ScalarType>
1424typename GridType::Ptr
1426 ScalarType radius, float voxelSize, float halfWidth, bool threaded)
1427{
1429 pt1, pt2, radius, voxelSize, halfWidth, nullptr, threaded);
1430}
1431
1432
1433// ------------ createLevelSetTaperedCapsule ------------- //
1434
1435template <typename GridType, typename ScalarType, typename InterruptT>
1436typename 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
1479template <typename GridType, typename ScalarType>
1480typename GridType::Ptr
1482 ScalarType radius1, ScalarType radius2, float voxelSize, float halfWidth, bool threaded)
1483{
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
Base class used to generate the narrow-band level set of a convex region.
General-purpose arithmetic and comparison routines, most of which accept arbitrary value types (or at...
Spatially partitions points using a parallel radix-based sorting algorithm.
Defined various multi-threaded utility functions for trees.
Definition Exceptions.h:63
Tag dispatch class that distinguishes constructors that steal.
Definition Types.h:758
Definition Exceptions.h:65
Definition Vec3.h:25
T dot(const Vec3< T > &v) const
Dot product.
Definition Vec3.h:192
static ValueT tileCeil(const ValueT &x, const ValueT &step)
Definition ConvexVoxelizer.h:327
ConvexVoxelizer(GridPtr &grid, const bool &threaded=false, util::NullInterrupter *interrupter=nullptr)
Definition ConvexVoxelizer.h:201
Definition PointPartitioner.h:78
Class used to generate a grid of type GridType containing a narrow-band level set representation of a...
Definition LevelSetTubesImpl.h:53
CapsuleVoxelizer(GridPtr &grid, const bool &threaded=true, InterruptT *interrupter=nullptr)
Constructor.
Definition LevelSetTubesImpl.h:86
void operator()(const math::Vec3< ScalarType > &pt1, const math::Vec3< ScalarType > &pt2, const ScalarType &r)
Create a capsule.
Definition LevelSetTubesImpl.h:99
Class used to generate a grid of type GridType containing a narrow-band level set representation of a...
Definition LevelSetTubesImpl.h:475
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
TaperedCapsuleVoxelizer(GridPtr &grid, const bool &threaded=true, InterruptT *interrupter=nullptr)
Constructor.
Definition LevelSetTubesImpl.h:508
Class used to generate a grid of type GridType containing a narrow-band level set representation of a...
Definition LevelSetTubesImpl.h:1026
GridPtr getGrid() const
Definition LevelSetTubesImpl.h:1138
Index64 bucketSize() const
Definition LevelSetTubesImpl.h:1136
std::enable_if_t< PSR, void > operator()(const tbb::blocked_range< size_t > &rng)
Definition LevelSetTubesImpl.h:1103
std::enable_if_t<!PSR, void > operator()(const tbb::blocked_range< size_t > &rng)
Definition LevelSetTubesImpl.h:1116
void join(TubeComplexVoxelizer &other)
Definition LevelSetTubesImpl.h:1127
TubeComplexVoxelizer(TubeComplexVoxelizer &other, tbb::split)
Definition LevelSetTubesImpl.h:1093
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
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 NodeManager.h:891
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
bool empty(const char *str)
tests if a c-string str is empty, that is its first value is '\0'
Definition Util.h:144
GridType
List of types that are currently supported by NanoVDB.
Definition NanoVDB.h:220
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
const Type & Max(const Type &a, const Type &b)
Return the maximum of two values.
Definition Math.h:595
Vec3< double > Vec3d
Definition Vec3.h:665
const Type & Min(const Type &a, const Type &b)
Return the minimum of two values.
Definition Math.h:656
Coord Abs(const Coord &xyz)
Definition Coord.h:518
Type Pow2(Type x)
Return x2.
Definition Math.h:548
OutGridT const XformOp bool bool
Definition ValueTransformer.h:610
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
OutGridT XformOp & op
Definition ValueTransformer.h:140
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
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
CsgUnionOrIntersectionOp< TreeT, true > CsgUnionOp
Definition Merge.h:267
TubeRadiiPolicy
Different policies when creating a tube complex with varying radii.
Definition LevelSetTubes.h:130
@ TUBE_VERTEX_RADII
Definition LevelSetTubes.h:130
@ TUBE_AUTOMATIC
Definition LevelSetTubes.h:130
@ TUBE_SEGMENT_RADII
Definition LevelSetTubes.h:130
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
OutGridT XformOp bool threaded
Definition ValueTransformer.h:140
Index32 Index
Definition Types.h:54
math::Vec2< Index32 > Vec2I
Definition Types.h:64
OPENVDB_IMPORT void initialize()
Global registration of native Grid, Transform, Metadata and Point attribute types....
uint32_t Index32
Definition Types.h:52
GridType::Ptr createLevelSet(Real voxelSize=1.0, Real halfWidth=LEVEL_SET_HALF_WIDTH)
Create a new grid of type GridType classified as a "Level Set", i.e., a narrow-band level set.
Definition Grid.h:1782
uint64_t Index64
Definition Types.h:53
openvdb::GridBase::Ptr GridPtr
Definition Utils.h:44
Definition Exceptions.h:13
#define OPENVDB_THROW(exception, message)
Definition Exceptions.h:74
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition version.h.in:121
#define OPENVDB_USE_VERSION_NAMESPACE
Definition version.h.in:218