Line |
Branch |
Exec |
Source |
1 |
|
|
// Copyright Contributors to the OpenVDB Project |
2 |
|
|
// SPDX-License-Identifier: MPL-2.0 |
3 |
|
|
// |
4 |
|
|
/// @file tools/LevelSetFracture.h |
5 |
|
|
/// |
6 |
|
|
/// @brief Divide volumes represented by level set grids into multiple, |
7 |
|
|
/// disjoint pieces by intersecting them with one or more "cutter" volumes, |
8 |
|
|
/// also represented by level sets. |
9 |
|
|
|
10 |
|
|
#ifndef OPENVDB_TOOLS_LEVELSETFRACTURE_HAS_BEEN_INCLUDED |
11 |
|
|
#define OPENVDB_TOOLS_LEVELSETFRACTURE_HAS_BEEN_INCLUDED |
12 |
|
|
|
13 |
|
|
#include <openvdb/Grid.h> |
14 |
|
|
#include <openvdb/math/Quat.h> |
15 |
|
|
#include <openvdb/util/NullInterrupter.h> |
16 |
|
|
|
17 |
|
|
#include "Composite.h" // for csgIntersectionCopy() and csgDifferenceCopy() |
18 |
|
|
#include "GridTransformer.h" // for resampleToMatch() |
19 |
|
|
#include "LevelSetUtil.h" // for sdfSegmentation() |
20 |
|
|
|
21 |
|
|
#include <algorithm> // for std::max(), std::min() |
22 |
|
|
#include <limits> |
23 |
|
|
#include <list> |
24 |
|
|
#include <vector> |
25 |
|
|
|
26 |
|
|
#include <tbb/blocked_range.h> |
27 |
|
|
#include <tbb/parallel_reduce.h> |
28 |
|
|
|
29 |
|
|
|
30 |
|
|
namespace openvdb { |
31 |
|
|
OPENVDB_USE_VERSION_NAMESPACE |
32 |
|
|
namespace OPENVDB_VERSION_NAME { |
33 |
|
|
namespace tools { |
34 |
|
|
|
35 |
|
|
/// @brief Level set fracturing |
36 |
|
|
template<class GridType, class InterruptType = util::NullInterrupter> |
37 |
|
|
class LevelSetFracture |
38 |
|
|
{ |
39 |
|
|
public: |
40 |
|
|
using Vec3sList = std::vector<Vec3s>; |
41 |
|
|
using QuatsList = std::vector<math::Quats>; |
42 |
|
|
using GridPtrList = std::list<typename GridType::Ptr>; |
43 |
|
|
using GridPtrListIter = typename GridPtrList::iterator; |
44 |
|
|
|
45 |
|
|
|
46 |
|
|
/// @brief Default constructor |
47 |
|
|
/// |
48 |
|
|
/// @param interrupter optional interrupter object |
49 |
|
|
explicit LevelSetFracture(InterruptType* interrupter = nullptr); |
50 |
|
|
|
51 |
|
|
/// @brief Divide volumes represented by level set grids into multiple, |
52 |
|
|
/// disjoint pieces by intersecting them with one or more "cutter" volumes, |
53 |
|
|
/// also represented by level sets. |
54 |
|
|
/// @details If desired, the process can be applied iteratively, so that |
55 |
|
|
/// fragments created with one cutter are subdivided by other cutters. |
56 |
|
|
/// |
57 |
|
|
/// @note The incoming @a grids and the @a cutter are required to have matching |
58 |
|
|
/// transforms and narrow band widths! |
59 |
|
|
/// |
60 |
|
|
/// @param grids list of grids to fracture. The residuals of the |
61 |
|
|
/// fractured grids will remain in this list |
62 |
|
|
/// @param cutter a level set grid to use as the cutter object |
63 |
|
|
/// @param segment toggle to split disjoint fragments into their own grids |
64 |
|
|
/// @param points optional list of world space points at which to instance the |
65 |
|
|
/// cutter object (if null, use the cutter's current position only) |
66 |
|
|
/// @param rotations optional list of custom rotations for each cutter instance |
67 |
|
|
/// @param cutterOverlap toggle to allow consecutive cutter instances to fracture |
68 |
|
|
/// previously generated fragments |
69 |
|
|
void fracture(GridPtrList& grids, const GridType& cutter, bool segment = false, |
70 |
|
|
const Vec3sList* points = nullptr, const QuatsList* rotations = nullptr, |
71 |
|
|
bool cutterOverlap = true); |
72 |
|
|
|
73 |
|
|
/// Return a list of new fragments, not including the residuals from the input grids. |
74 |
|
✗ |
GridPtrList& fragments() { return mFragments; } |
75 |
|
|
|
76 |
|
|
/// Remove all elements from the fragment list. |
77 |
|
✗ |
void clear() { mFragments.clear(); } |
78 |
|
|
|
79 |
|
|
private: |
80 |
|
|
// disallow copy by assignment |
81 |
|
✗ |
void operator=(const LevelSetFracture&) {} |
82 |
|
|
|
83 |
|
✗ |
bool wasInterrupted(int percent = -1) const { |
84 |
|
✗ |
return mInterrupter && mInterrupter->wasInterrupted(percent); |
85 |
|
|
} |
86 |
|
|
|
87 |
|
|
bool isValidFragment(GridType&) const; |
88 |
|
|
void segmentFragments(GridPtrList&) const; |
89 |
|
|
void process(GridPtrList&, const GridType& cutter); |
90 |
|
|
|
91 |
|
|
InterruptType* mInterrupter; |
92 |
|
|
GridPtrList mFragments; |
93 |
|
|
}; |
94 |
|
|
|
95 |
|
|
|
96 |
|
|
//////////////////////////////////////// |
97 |
|
|
|
98 |
|
|
/// @cond OPENVDB_DOCS_INTERNAL |
99 |
|
|
|
100 |
|
|
// Internal utility objects and implementation details |
101 |
|
|
|
102 |
|
|
namespace level_set_fracture_internal { |
103 |
|
|
|
104 |
|
|
|
105 |
|
|
template<typename LeafNodeType> |
106 |
|
|
struct FindMinMaxVoxelValue { |
107 |
|
|
|
108 |
|
|
using ValueType = typename LeafNodeType::ValueType; |
109 |
|
|
|
110 |
|
✗ |
FindMinMaxVoxelValue(const std::vector<const LeafNodeType*>& nodes) |
111 |
|
|
: minValue(std::numeric_limits<ValueType>::max()) |
112 |
|
|
, maxValue(-minValue) |
113 |
|
✗ |
, mNodes(nodes.empty() ? nullptr : &nodes.front()) |
114 |
|
|
{ |
115 |
|
|
} |
116 |
|
|
|
117 |
|
✗ |
FindMinMaxVoxelValue(FindMinMaxVoxelValue& rhs, tbb::split) |
118 |
|
|
: minValue(std::numeric_limits<ValueType>::max()) |
119 |
|
|
, maxValue(-minValue) |
120 |
|
✗ |
, mNodes(rhs.mNodes) |
121 |
|
|
{ |
122 |
|
|
} |
123 |
|
|
|
124 |
|
✗ |
void operator()(const tbb::blocked_range<size_t>& range) { |
125 |
|
✗ |
for (size_t n = range.begin(), N = range.end(); n < N; ++n) { |
126 |
|
✗ |
const ValueType* data = mNodes[n]->buffer().data(); |
127 |
|
✗ |
for (Index i = 0; i < LeafNodeType::SIZE; ++i) { |
128 |
|
✗ |
minValue = std::min(minValue, data[i]); |
129 |
|
✗ |
maxValue = std::max(maxValue, data[i]); |
130 |
|
|
} |
131 |
|
|
} |
132 |
|
|
} |
133 |
|
|
|
134 |
|
|
void join(FindMinMaxVoxelValue& rhs) { |
135 |
|
✗ |
minValue = std::min(minValue, rhs.minValue); |
136 |
|
✗ |
maxValue = std::max(maxValue, rhs.maxValue); |
137 |
|
|
} |
138 |
|
|
|
139 |
|
|
ValueType minValue, maxValue; |
140 |
|
|
|
141 |
|
|
LeafNodeType const * const * const mNodes; |
142 |
|
|
}; // struct FindMinMaxVoxelValue |
143 |
|
|
|
144 |
|
|
|
145 |
|
|
} // namespace level_set_fracture_internal |
146 |
|
|
|
147 |
|
|
/// @endcond |
148 |
|
|
|
149 |
|
|
//////////////////////////////////////// |
150 |
|
|
|
151 |
|
|
|
152 |
|
|
template<class GridType, class InterruptType> |
153 |
|
✗ |
LevelSetFracture<GridType, InterruptType>::LevelSetFracture(InterruptType* interrupter) |
154 |
|
|
: mInterrupter(interrupter) |
155 |
|
✗ |
, mFragments() |
156 |
|
|
{ |
157 |
|
|
} |
158 |
|
|
|
159 |
|
|
|
160 |
|
|
template<class GridType, class InterruptType> |
161 |
|
|
void |
162 |
|
✗ |
LevelSetFracture<GridType, InterruptType>::fracture(GridPtrList& grids, const GridType& cutter, |
163 |
|
|
bool segmentation, const Vec3sList* points, const QuatsList* rotations, bool cutterOverlap) |
164 |
|
|
{ |
165 |
|
|
// We can process all incoming grids with the same cutter instance, |
166 |
|
|
// this optimization is enabled by the requirement of having matching |
167 |
|
|
// transforms between all incoming grids and the cutter object. |
168 |
|
✗ |
if (points && points->size() != 0) { |
169 |
|
|
|
170 |
|
|
|
171 |
|
✗ |
math::Transform::Ptr originalCutterTransform = cutter.transform().copy(); |
172 |
|
✗ |
GridType cutterGrid(*const_cast<GridType*>(&cutter), ShallowCopy()); |
173 |
|
|
|
174 |
|
|
const bool hasInstanceRotations = |
175 |
|
✗ |
points && rotations && points->size() == rotations->size(); |
176 |
|
|
|
177 |
|
|
// for each instance point.. |
178 |
|
✗ |
for (size_t p = 0, P = points->size(); p < P; ++p) { |
179 |
|
✗ |
int percent = int((float(p) / float(P)) * 100.0); |
180 |
|
|
if (wasInterrupted(percent)) break; |
181 |
|
|
|
182 |
|
✗ |
GridType instCutterGrid; |
183 |
|
✗ |
instCutterGrid.setTransform(originalCutterTransform->copy()); |
184 |
|
✗ |
math::Transform::Ptr xform = originalCutterTransform->copy(); |
185 |
|
|
|
186 |
|
✗ |
if (hasInstanceRotations) { |
187 |
|
✗ |
const Vec3s& rot = (*rotations)[p].eulerAngles(math::XYZ_ROTATION); |
188 |
|
✗ |
xform->preRotate(rot[0], math::X_AXIS); |
189 |
|
✗ |
xform->preRotate(rot[1], math::Y_AXIS); |
190 |
|
✗ |
xform->preRotate(rot[2], math::Z_AXIS); |
191 |
|
✗ |
xform->postTranslate((*points)[p]); |
192 |
|
|
} else { |
193 |
|
✗ |
xform->postTranslate((*points)[p]); |
194 |
|
|
} |
195 |
|
|
|
196 |
|
✗ |
cutterGrid.setTransform(xform); |
197 |
|
|
|
198 |
|
|
// Since there is no scaling, use the generic resampler instead of |
199 |
|
|
// the more expensive level set rebuild tool. |
200 |
|
✗ |
if (mInterrupter != nullptr) { |
201 |
|
|
|
202 |
|
✗ |
if (hasInstanceRotations) { |
203 |
|
✗ |
doResampleToMatch<BoxSampler>(cutterGrid, instCutterGrid, *mInterrupter); |
204 |
|
|
} else { |
205 |
|
✗ |
doResampleToMatch<PointSampler>(cutterGrid, instCutterGrid, *mInterrupter); |
206 |
|
|
} |
207 |
|
|
} else { |
208 |
|
✗ |
util::NullInterrupter interrupter; |
209 |
|
✗ |
if (hasInstanceRotations) { |
210 |
|
✗ |
doResampleToMatch<BoxSampler>(cutterGrid, instCutterGrid, interrupter); |
211 |
|
|
} else { |
212 |
|
✗ |
doResampleToMatch<PointSampler>(cutterGrid, instCutterGrid, interrupter); |
213 |
|
|
} |
214 |
|
|
} |
215 |
|
|
|
216 |
|
|
if (wasInterrupted(percent)) break; |
217 |
|
|
|
218 |
|
✗ |
if (cutterOverlap && !mFragments.empty()) process(mFragments, instCutterGrid); |
219 |
|
✗ |
process(grids, instCutterGrid); |
220 |
|
|
} |
221 |
|
|
|
222 |
|
|
} else { |
223 |
|
|
// use cutter in place |
224 |
|
✗ |
if (cutterOverlap && !mFragments.empty()) process(mFragments, cutter); |
225 |
|
✗ |
process(grids, cutter); |
226 |
|
|
} |
227 |
|
|
|
228 |
|
✗ |
if (segmentation) { |
229 |
|
✗ |
segmentFragments(mFragments); |
230 |
|
✗ |
segmentFragments(grids); |
231 |
|
|
} |
232 |
|
|
} |
233 |
|
|
|
234 |
|
|
|
235 |
|
|
template<class GridType, class InterruptType> |
236 |
|
|
bool |
237 |
|
✗ |
LevelSetFracture<GridType, InterruptType>::isValidFragment(GridType& grid) const |
238 |
|
|
{ |
239 |
|
|
using LeafNodeType = typename GridType::TreeType::LeafNodeType; |
240 |
|
|
|
241 |
|
✗ |
if (grid.tree().leafCount() < 9) { |
242 |
|
|
|
243 |
|
|
std::vector<const LeafNodeType*> nodes; |
244 |
|
|
grid.tree().getNodes(nodes); |
245 |
|
|
|
246 |
|
|
Index64 activeVoxelCount = 0; |
247 |
|
|
|
248 |
|
✗ |
for (size_t n = 0, N = nodes.size(); n < N; ++n) { |
249 |
|
✗ |
activeVoxelCount += nodes[n]->onVoxelCount(); |
250 |
|
|
} |
251 |
|
|
|
252 |
|
✗ |
if (activeVoxelCount < 27) return false; |
253 |
|
|
|
254 |
|
|
level_set_fracture_internal::FindMinMaxVoxelValue<LeafNodeType> op(nodes); |
255 |
|
✗ |
tbb::parallel_reduce(tbb::blocked_range<size_t>(0, nodes.size()), op); |
256 |
|
|
|
257 |
|
✗ |
if ((op.minValue < 0) == (op.maxValue < 0)) return false; |
258 |
|
|
} |
259 |
|
|
|
260 |
|
|
return true; |
261 |
|
|
} |
262 |
|
|
|
263 |
|
|
|
264 |
|
|
template<class GridType, class InterruptType> |
265 |
|
|
void |
266 |
|
✗ |
LevelSetFracture<GridType, InterruptType>::segmentFragments(GridPtrList& grids) const |
267 |
|
|
{ |
268 |
|
|
GridPtrList newFragments; |
269 |
|
|
|
270 |
|
✗ |
for (GridPtrListIter it = grids.begin(); it != grids.end(); ++it) { |
271 |
|
|
|
272 |
|
✗ |
std::vector<typename GridType::Ptr> segments; |
273 |
|
✗ |
segmentSDF(*(*it), segments); |
274 |
|
|
|
275 |
|
✗ |
for (size_t n = 0, N = segments.size(); n < N; ++n) { |
276 |
|
|
newFragments.push_back(segments[n]); |
277 |
|
|
} |
278 |
|
|
} |
279 |
|
|
|
280 |
|
|
grids.swap(newFragments); |
281 |
|
|
} |
282 |
|
|
|
283 |
|
|
|
284 |
|
|
template<class GridType, class InterruptType> |
285 |
|
|
void |
286 |
|
✗ |
LevelSetFracture<GridType, InterruptType>::process( |
287 |
|
|
GridPtrList& grids, const GridType& cutter) |
288 |
|
|
{ |
289 |
|
|
using GridPtr = typename GridType::Ptr; |
290 |
|
|
GridPtrList newFragments; |
291 |
|
|
|
292 |
|
✗ |
for (GridPtrListIter it = grids.begin(); it != grids.end(); ++it) { |
293 |
|
|
|
294 |
|
|
if (wasInterrupted()) break; |
295 |
|
|
|
296 |
|
|
GridPtr& grid = *it; |
297 |
|
|
|
298 |
|
✗ |
GridPtr fragment = csgIntersectionCopy(*grid, cutter); |
299 |
|
✗ |
if (!isValidFragment(*fragment)) continue; |
300 |
|
|
|
301 |
|
✗ |
GridPtr residual = csgDifferenceCopy(*grid, cutter); |
302 |
|
✗ |
if (!isValidFragment(*residual)) continue; |
303 |
|
|
|
304 |
|
|
newFragments.push_back(fragment); |
305 |
|
|
|
306 |
|
✗ |
grid->tree().clear(); |
307 |
|
✗ |
grid->tree().merge(residual->tree()); |
308 |
|
|
} |
309 |
|
|
|
310 |
|
✗ |
if (!newFragments.empty()) { |
311 |
|
|
mFragments.splice(mFragments.end(), newFragments); |
312 |
|
|
} |
313 |
|
|
} |
314 |
|
|
|
315 |
|
|
|
316 |
|
|
//////////////////////////////////////// |
317 |
|
|
|
318 |
|
|
|
319 |
|
|
// Explicit Template Instantiation |
320 |
|
|
|
321 |
|
|
#ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION |
322 |
|
|
|
323 |
|
|
#ifdef OPENVDB_INSTANTIATE_LEVELSETFRACTURE |
324 |
|
|
#include <openvdb/util/ExplicitInstantiation.h> |
325 |
|
|
#endif |
326 |
|
|
|
327 |
|
|
OPENVDB_INSTANTIATE_CLASS LevelSetFracture<FloatGrid, util::NullInterrupter>; |
328 |
|
|
OPENVDB_INSTANTIATE_CLASS LevelSetFracture<DoubleGrid, util::NullInterrupter>; |
329 |
|
|
|
330 |
|
|
#endif // OPENVDB_USE_EXPLICIT_INSTANTIATION |
331 |
|
|
|
332 |
|
|
|
333 |
|
|
} // namespace tools |
334 |
|
|
} // namespace OPENVDB_VERSION_NAME |
335 |
|
|
} // namespace openvdb |
336 |
|
|
|
337 |
|
|
#endif // OPENVDB_TOOLS_LEVELSETFRACTURE_HAS_BEEN_INCLUDED |
338 |
|
|
|