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