Line | Branch | Exec | Source |
---|---|---|---|
1 | // Copyright Contributors to the OpenVDB Project | ||
2 | // SPDX-License-Identifier: MPL-2.0 | ||
3 | |||
4 | #ifndef OPENVDB_TOOLS_LEVELSETREBUILD_HAS_BEEN_INCLUDED | ||
5 | #define OPENVDB_TOOLS_LEVELSETREBUILD_HAS_BEEN_INCLUDED | ||
6 | |||
7 | #include <openvdb/Grid.h> | ||
8 | #include <openvdb/Exceptions.h> | ||
9 | #include <openvdb/math/Math.h> | ||
10 | #include <openvdb/math/Transform.h> | ||
11 | #include <openvdb/tools/VolumeToMesh.h> | ||
12 | #include <openvdb/tools/MeshToVolume.h> | ||
13 | #include <openvdb/util/NullInterrupter.h> | ||
14 | #include <openvdb/util/Util.h> | ||
15 | #include <openvdb/openvdb.h> | ||
16 | #include <tbb/blocked_range.h> | ||
17 | #include <tbb/parallel_for.h> | ||
18 | #include <type_traits> | ||
19 | |||
20 | |||
21 | namespace openvdb { | ||
22 | OPENVDB_USE_VERSION_NAMESPACE | ||
23 | namespace OPENVDB_VERSION_NAME { | ||
24 | namespace tools { | ||
25 | |||
26 | |||
27 | /// @brief Return a new grid of type @c GridType that contains a narrow-band level set | ||
28 | /// representation of an isosurface of a given grid. | ||
29 | /// | ||
30 | /// @param grid a scalar, floating-point grid with one or more disjoint, | ||
31 | /// closed isosurfaces at the given @a isovalue | ||
32 | /// @param isovalue the isovalue that defines the implicit surface (defaults to zero, | ||
33 | /// which is typical if the input grid is already a level set or a SDF). | ||
34 | /// @param halfWidth half the width of the narrow band, in voxel units | ||
35 | /// (defaults to 3 voxels, which is required for some level set operations) | ||
36 | /// @param xform optional transform for the output grid | ||
37 | /// (if not provided, the transform of the input @a grid will be matched) | ||
38 | /// | ||
39 | /// @throw TypeError if @a grid is not scalar or not floating-point | ||
40 | /// | ||
41 | /// @note If the input grid contains overlapping isosurfaces, interior edges will be lost. | ||
42 | template<class GridType> | ||
43 | typename GridType::Ptr | ||
44 | levelSetRebuild(const GridType& grid, float isovalue = 0, | ||
45 | float halfWidth = float(LEVEL_SET_HALF_WIDTH), const math::Transform* xform = nullptr); | ||
46 | |||
47 | |||
48 | /// @brief Return a new grid of type @c GridType that contains a narrow-band level set | ||
49 | /// representation of an isosurface of a given grid. | ||
50 | /// | ||
51 | /// @param grid a scalar, floating-point grid with one or more disjoint, | ||
52 | /// closed isosurfaces at the given @a isovalue | ||
53 | /// @param isovalue the isovalue that defines the implicit surface | ||
54 | /// @param exBandWidth the exterior narrow-band width in voxel units | ||
55 | /// @param inBandWidth the interior narrow-band width in voxel units | ||
56 | /// @param xform optional transform for the output grid | ||
57 | /// (if not provided, the transform of the input @a grid will be matched) | ||
58 | /// | ||
59 | /// @throw TypeError if @a grid is not scalar or not floating-point | ||
60 | /// | ||
61 | /// @note If the input grid contains overlapping isosurfaces, interior edges will be lost. | ||
62 | template<class GridType> | ||
63 | typename GridType::Ptr | ||
64 | levelSetRebuild(const GridType& grid, float isovalue, float exBandWidth, float inBandWidth, | ||
65 | const math::Transform* xform = nullptr); | ||
66 | |||
67 | |||
68 | /// @brief Return a new grid of type @c GridType that contains a narrow-band level set | ||
69 | /// representation of an isosurface of a given grid. | ||
70 | /// | ||
71 | /// @param grid a scalar, floating-point grid with one or more disjoint, | ||
72 | /// closed isosurfaces at the given @a isovalue | ||
73 | /// @param isovalue the isovalue that defines the implicit surface | ||
74 | /// @param exBandWidth the exterior narrow-band width in voxel units | ||
75 | /// @param inBandWidth the interior narrow-band width in voxel units | ||
76 | /// @param xform optional transform for the output grid | ||
77 | /// (if not provided, the transform of the input @a grid will be matched) | ||
78 | /// @param interrupter optional interrupter object | ||
79 | /// | ||
80 | /// @throw TypeError if @a grid is not scalar or not floating-point | ||
81 | /// | ||
82 | /// @note If the input grid contains overlapping isosurfaces, interior edges will be lost. | ||
83 | template<class GridType, typename InterruptT> | ||
84 | typename GridType::Ptr | ||
85 | levelSetRebuild(const GridType& grid, float isovalue, float exBandWidth, float inBandWidth, | ||
86 | const math::Transform* xform = nullptr, InterruptT* interrupter = nullptr); | ||
87 | |||
88 | |||
89 | //////////////////////////////////////// | ||
90 | |||
91 | /// @cond OPENVDB_DOCS_INTERNAL | ||
92 | |||
93 | // Internal utility objects and implementation details | ||
94 | |||
95 | namespace internal { | ||
96 | |||
97 | class PointListTransform | ||
98 | { | ||
99 | public: | ||
100 | PointListTransform(const PointList& pointsIn, std::vector<Vec3s>& pointsOut, | ||
101 | const math::Transform& xform) | ||
102 | 5 | : mPointsIn(pointsIn) | |
103 | , mPointsOut(&pointsOut) | ||
104 | 5 | , mXform(xform) | |
105 | { | ||
106 | } | ||
107 | |||
108 | 5 | void runParallel() | |
109 | { | ||
110 | 5 | tbb::parallel_for(tbb::blocked_range<size_t>(0, mPointsOut->size()), *this); | |
111 | 5 | } | |
112 | |||
113 | void runSerial() | ||
114 | { | ||
115 | (*this)(tbb::blocked_range<size_t>(0, mPointsOut->size())); | ||
116 | } | ||
117 | |||
118 | 512 | inline void operator()(const tbb::blocked_range<size_t>& range) const | |
119 | { | ||
120 |
2/2✓ Branch 0 taken 1664 times.
✓ Branch 1 taken 512 times.
|
2176 | for (size_t n = range.begin(); n < range.end(); ++n) { |
121 | 1664 | (*mPointsOut)[n] = Vec3s(mXform.worldToIndex(mPointsIn[n])); | |
122 | } | ||
123 | 512 | } | |
124 | |||
125 | private: | ||
126 | const PointList& mPointsIn; | ||
127 | std::vector<Vec3s> * const mPointsOut; | ||
128 | const math::Transform& mXform; | ||
129 | }; | ||
130 | |||
131 | |||
132 | class PrimCpy | ||
133 | { | ||
134 | public: | ||
135 | PrimCpy(const PolygonPoolList& primsIn, const std::vector<size_t>& indexList, | ||
136 | std::vector<Vec4I>& primsOut) | ||
137 | 5 | : mPrimsIn(primsIn) | |
138 | , mIndexList(indexList) | ||
139 | 5 | , mPrimsOut(&primsOut) | |
140 | { | ||
141 | } | ||
142 | |||
143 | 5 | void runParallel() | |
144 | { | ||
145 | 5 | tbb::parallel_for(tbb::blocked_range<size_t>(0, mIndexList.size()), *this); | |
146 | 5 | } | |
147 | |||
148 | void runSerial() | ||
149 | { | ||
150 | (*this)(tbb::blocked_range<size_t>(0, mIndexList.size())); | ||
151 | } | ||
152 | |||
153 | 32 | inline void operator()(const tbb::blocked_range<size_t>& range) const | |
154 | { | ||
155 | openvdb::Vec4I quad; | ||
156 | 32 | quad[3] = openvdb::util::INVALID_IDX; | |
157 | 32 | std::vector<Vec4I>& primsOut = *mPrimsOut; | |
158 | |||
159 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 32 times.
|
64 | for (size_t n = range.begin(); n < range.end(); ++n) { |
160 | 32 | size_t index = mIndexList[n]; | |
161 | 32 | PolygonPool& polygons = mPrimsIn[n]; | |
162 | |||
163 | // Copy quads | ||
164 |
2/2✓ Branch 0 taken 1656 times.
✓ Branch 1 taken 32 times.
|
1688 | for (size_t i = 0, I = polygons.numQuads(); i < I; ++i) { |
165 | 1656 | primsOut[index++] = polygons.quad(i); | |
166 | } | ||
167 | 32 | polygons.clearQuads(); | |
168 | |||
169 | // Copy triangles (adaptive mesh) | ||
170 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | for (size_t i = 0, I = polygons.numTriangles(); i < I; ++i) { |
171 | const openvdb::Vec3I& triangle = polygons.triangle(i); | ||
172 | ✗ | quad[0] = triangle[0]; | |
173 | ✗ | quad[1] = triangle[1]; | |
174 | ✗ | quad[2] = triangle[2]; | |
175 | ✗ | primsOut[index++] = quad; | |
176 | } | ||
177 | |||
178 | 32 | polygons.clearTriangles(); | |
179 | } | ||
180 | 32 | } | |
181 | |||
182 | private: | ||
183 | const PolygonPoolList& mPrimsIn; | ||
184 | const std::vector<size_t>& mIndexList; | ||
185 | std::vector<Vec4I> * const mPrimsOut; | ||
186 | }; | ||
187 | |||
188 | } // namespace internal | ||
189 | |||
190 | /// @endcond | ||
191 | |||
192 | //////////////////////////////////////// | ||
193 | |||
194 | |||
195 | //{ | ||
196 | /// @cond OPENVDB_DOCS_INTERNAL | ||
197 | |||
198 | /// The normal entry points for level set rebuild are the levelSetRebuild() functions. | ||
199 | /// doLevelSetRebuild() is mainly for internal use, but when the isovalue and half band | ||
200 | /// widths are given in ValueType units (for example, if they are queried from | ||
201 | /// a grid), it might be more convenient to call this function directly. | ||
202 | /// | ||
203 | /// @internal This overload is enabled only for grids with a scalar, floating-point ValueType. | ||
204 | template<class GridType, typename InterruptT> | ||
205 | inline typename std::enable_if< | ||
206 | std::is_floating_point<typename GridType::ValueType>::value, typename GridType::Ptr>::type | ||
207 | 10 | doLevelSetRebuild(const GridType& grid, typename GridType::ValueType iso, | |
208 | typename GridType::ValueType exWidth, typename GridType::ValueType inWidth, | ||
209 | const math::Transform* xform, InterruptT* interrupter) | ||
210 | { | ||
211 | const float | ||
212 | ✗ | isovalue = float(iso), | |
213 | ✗ | exBandWidth = float(exWidth), | |
214 | ✗ | inBandWidth = float(inWidth); | |
215 | |||
216 | 20 | tools::VolumeToMesh mesher(isovalue); | |
217 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
10 | mesher(grid); |
218 | |||
219 |
2/6✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
10 | math::Transform::Ptr transform = (xform != nullptr) ? xform->copy() : grid.transform().copy(); |
220 | |||
221 |
2/6✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
10 | std::vector<Vec3s> points(mesher.pointListSize()); |
222 | |||
223 | { // Copy and transform (required for MeshToVolume) points to grid space. | ||
224 | internal::PointListTransform ptnXForm(mesher.pointList(), points, *transform); | ||
225 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
10 | ptnXForm.runParallel(); |
226 | mesher.pointList().reset(nullptr); | ||
227 | } | ||
228 | |||
229 | std::vector<Vec4I> primitives; | ||
230 | |||
231 | { // Copy primitives. | ||
232 | PolygonPoolList& polygonPoolList = mesher.polygonPoolList(); | ||
233 | |||
234 | size_t numPrimitives = 0; | ||
235 |
1/4✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
10 | std::vector<size_t> indexlist(mesher.polygonPoolListSize()); |
236 | |||
237 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 5 times.
|
74 | for (size_t n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { |
238 | const openvdb::tools::PolygonPool& polygons = polygonPoolList[n]; | ||
239 | 64 | indexlist[n] = numPrimitives; | |
240 | 64 | numPrimitives += polygons.numQuads(); | |
241 | 64 | numPrimitives += polygons.numTriangles(); | |
242 | } | ||
243 | |||
244 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
10 | primitives.resize(numPrimitives); |
245 | internal::PrimCpy primCpy(polygonPoolList, indexlist, primitives); | ||
246 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
10 | primCpy.runParallel(); |
247 | } | ||
248 | |||
249 | QuadAndTriangleDataAdapter<Vec3s, Vec4I> mesh(points, primitives); | ||
250 | |||
251 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
10 | if (interrupter) { |
252 | return meshToVolume<GridType>(*interrupter, mesh, *transform, exBandWidth, inBandWidth, | ||
253 | ✗ | DISABLE_RENORMALIZATION, nullptr); | |
254 | } | ||
255 | |||
256 | return meshToVolume<GridType>(mesh, *transform, exBandWidth, inBandWidth, | ||
257 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
10 | DISABLE_RENORMALIZATION, nullptr); |
258 | } | ||
259 | |||
260 | |||
261 | /// @internal This overload is enabled only for grids that do not have a scalar, | ||
262 | /// floating-point ValueType. | ||
263 | template<class GridType, typename InterruptT> | ||
264 | inline typename std::enable_if< | ||
265 | !std::is_floating_point<typename GridType::ValueType>::value, typename GridType::Ptr>::type | ||
266 | ✗ | doLevelSetRebuild(const GridType&, typename GridType::ValueType /*isovalue*/, | |
267 | typename GridType::ValueType /*exWidth*/, typename GridType::ValueType /*inWidth*/, | ||
268 | const math::Transform*, InterruptT*) | ||
269 | { | ||
270 | ✗ | OPENVDB_THROW(TypeError, | |
271 | "level set rebuild is supported only for scalar, floating-point grids"); | ||
272 | } | ||
273 | |||
274 | /// @endcond | ||
275 | //} | ||
276 | |||
277 | |||
278 | //////////////////////////////////////// | ||
279 | |||
280 | |||
281 | template<class GridType, typename InterruptT> | ||
282 | typename GridType::Ptr | ||
283 | 10 | levelSetRebuild(const GridType& grid, float iso, float exWidth, float inWidth, | |
284 | const math::Transform* xform, InterruptT* interrupter) | ||
285 | { | ||
286 | using ValueT = typename GridType::ValueType; | ||
287 | ValueT | ||
288 | 10 | isovalue(zeroVal<ValueT>() + ValueT(iso)), | |
289 | 10 | exBandWidth(zeroVal<ValueT>() + ValueT(exWidth)), | |
290 | 10 | inBandWidth(zeroVal<ValueT>() + ValueT(inWidth)); | |
291 | |||
292 | 10 | return doLevelSetRebuild(grid, isovalue, exBandWidth, inBandWidth, xform, interrupter); | |
293 | } | ||
294 | |||
295 | |||
296 | template<class GridType> | ||
297 | typename GridType::Ptr | ||
298 | ✗ | levelSetRebuild(const GridType& grid, float iso, float exWidth, float inWidth, | |
299 | const math::Transform* xform) | ||
300 | { | ||
301 | using ValueT = typename GridType::ValueType; | ||
302 | ValueT | ||
303 | ✗ | isovalue(zeroVal<ValueT>() + ValueT(iso)), | |
304 | ✗ | exBandWidth(zeroVal<ValueT>() + ValueT(exWidth)), | |
305 | ✗ | inBandWidth(zeroVal<ValueT>() + ValueT(inWidth)); | |
306 | |||
307 | return doLevelSetRebuild<GridType, util::NullInterrupter>( | ||
308 | ✗ | grid, isovalue, exBandWidth, inBandWidth, xform, nullptr); | |
309 | } | ||
310 | |||
311 | |||
312 | template<class GridType> | ||
313 | typename GridType::Ptr | ||
314 | ✗ | levelSetRebuild(const GridType& grid, float iso, float halfVal, const math::Transform* xform) | |
315 | { | ||
316 | using ValueT = typename GridType::ValueType; | ||
317 | ValueT | ||
318 | ✗ | isovalue(zeroVal<ValueT>() + ValueT(iso)), | |
319 | ✗ | halfWidth(zeroVal<ValueT>() + ValueT(halfVal)); | |
320 | |||
321 | return doLevelSetRebuild<GridType, util::NullInterrupter>( | ||
322 | ✗ | grid, isovalue, halfWidth, halfWidth, xform, nullptr); | |
323 | } | ||
324 | |||
325 | |||
326 | //////////////////////////////////////// | ||
327 | |||
328 | |||
329 | // Explicit Template Instantiation | ||
330 | |||
331 | #ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION | ||
332 | |||
333 | #ifdef OPENVDB_INSTANTIATE_LEVELSETREBUILD | ||
334 | #include <openvdb/util/ExplicitInstantiation.h> | ||
335 | #endif | ||
336 | |||
337 | #define _FUNCTION(TreeT) \ | ||
338 | Grid<TreeT>::Ptr levelSetRebuild(const Grid<TreeT>&, float, float, const math::Transform*) | ||
339 | OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) | ||
340 | #undef _FUNCTION | ||
341 | |||
342 | #define _FUNCTION(TreeT) \ | ||
343 | Grid<TreeT>::Ptr levelSetRebuild(const Grid<TreeT>&, float, float, float, const math::Transform*) | ||
344 | OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) | ||
345 | #undef _FUNCTION | ||
346 | |||
347 | #define _FUNCTION(TreeT) \ | ||
348 | Grid<TreeT>::Ptr levelSetRebuild(const Grid<TreeT>&, float, float, float, const math::Transform*, \ | ||
349 | util::NullInterrupter*) | ||
350 | OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) | ||
351 | #undef _FUNCTION | ||
352 | |||
353 | #endif // OPENVDB_USE_EXPLICIT_INSTANTIATION | ||
354 | |||
355 | |||
356 | } // namespace tools | ||
357 | } // namespace OPENVDB_VERSION_NAME | ||
358 | } // namespace openvdb | ||
359 | |||
360 | #endif // OPENVDB_TOOLS_LEVELSETREBUILD_HAS_BEEN_INCLUDED | ||
361 |