Line | Branch | Exec | Source |
---|---|---|---|
1 | // Copyright Contributors to the OpenVDB Project | ||
2 | // SPDX-License-Identifier: MPL-2.0 | ||
3 | // | ||
4 | /////////////////////////////////////////////////////////////////////////// | ||
5 | // | ||
6 | /// @author Ken Museth | ||
7 | /// | ||
8 | /// @file tools/VolumeAdvect.h | ||
9 | /// | ||
10 | /// @brief Sparse hyperbolic advection of volumes, e.g. a density or | ||
11 | /// velocity (vs a level set interface). | ||
12 | |||
13 | #ifndef OPENVDB_TOOLS_VOLUME_ADVECT_HAS_BEEN_INCLUDED | ||
14 | #define OPENVDB_TOOLS_VOLUME_ADVECT_HAS_BEEN_INCLUDED | ||
15 | |||
16 | #include "openvdb/Types.h" | ||
17 | #include "openvdb/math/Math.h" | ||
18 | #include "openvdb/util/NullInterrupter.h" | ||
19 | #include "openvdb/thread/Threading.h" | ||
20 | #include "Interpolation.h"// for Sampler | ||
21 | #include "VelocityFields.h" // for VelocityIntegrator | ||
22 | #include "Morphology.h"//for dilateActiveValues | ||
23 | #include "Prune.h"// for prune | ||
24 | #include "Statistics.h" // for extrema | ||
25 | |||
26 | #include <tbb/parallel_for.h> | ||
27 | |||
28 | #include <functional> | ||
29 | |||
30 | |||
31 | namespace openvdb { | ||
32 | OPENVDB_USE_VERSION_NAMESPACE | ||
33 | namespace OPENVDB_VERSION_NAME { | ||
34 | namespace tools { | ||
35 | |||
36 | namespace Scheme { | ||
37 | /// @brief Numerical advections schemes. | ||
38 | enum SemiLagrangian { SEMI, MID, RK3, RK4, MAC, BFECC }; | ||
39 | /// @brief Flux-limiters employed to stabilize the second-order | ||
40 | /// advection schemes MacCormack and BFECC. | ||
41 | enum Limiter { NO_LIMITER, CLAMP, REVERT }; | ||
42 | } | ||
43 | |||
44 | /// @brief Performs advections of an arbitrary type of volume in a | ||
45 | /// static velocity field. The advections are performed by means | ||
46 | /// of various derivatives of Semi-Lagrangian integration, i.e. | ||
47 | /// backwards tracking along the hyperbolic characteristics | ||
48 | /// followed by interpolation. | ||
49 | /// | ||
50 | /// @note Optionally a limiter can be combined with the higher-order | ||
51 | /// integration schemes MacCormack and BFECC. There are two | ||
52 | /// types of limiters (CLAMP and REVERT) that suppress | ||
53 | /// non-physical oscillations by means of either claminging or | ||
54 | /// reverting to a first-order schemes when the function is not | ||
55 | /// bounded by the cell values used for tri-linear interpolation. | ||
56 | /// | ||
57 | /// @verbatim The supported integrations schemes: | ||
58 | /// | ||
59 | /// ================================================================ | ||
60 | /// | Lable | Accuracy | Integration Scheme | Interpolations | | ||
61 | /// | |Time/Space| | velocity/volume | | ||
62 | /// ================================================================ | ||
63 | /// | SEMI | 1/1 | Semi-Lagrangian | 1/1 | | ||
64 | /// | MID | 2/1 | Mid-Point | 2/1 | | ||
65 | /// | RK3 | 3/1 | 3rd Order Runge-Kutta | 3/1 | | ||
66 | /// | RK4 | 4/1 | 4th Order Runge-Kutta | 4/1 | | ||
67 | /// | MAC | 2/2 | MacCormack | 2/2 | | ||
68 | /// | BFECC | 2/2 | BFECC | 3/2 | | ||
69 | /// ================================================================ | ||
70 | /// @endverbatim | ||
71 | |||
72 | template<typename VelocityGridT = Vec3fGrid, | ||
73 | bool StaggeredVelocity = false, | ||
74 | typename InterrupterType = util::NullInterrupter> | ||
75 | class VolumeAdvection | ||
76 | { | ||
77 | public: | ||
78 | |||
79 | /// @brief Constructor | ||
80 | /// | ||
81 | /// @param velGrid Velocity grid responsible for the (passive) advection. | ||
82 | /// @param interrupter Optional interrupter used to prematurely end computations. | ||
83 | /// | ||
84 | /// @note The velocity field is assumed to be constant for the duration of the | ||
85 | /// advection. | ||
86 | 10 | VolumeAdvection(const VelocityGridT& velGrid, InterrupterType* interrupter = nullptr) | |
87 | : mVelGrid(velGrid) | ||
88 | , mInterrupter(interrupter) | ||
89 | , mIntegrator( Scheme::SEMI ) | ||
90 | , mLimiter( Scheme::CLAMP ) | ||
91 | , mGrainSize( 128 ) | ||
92 | 10 | , mSubSteps( 1 ) | |
93 | { | ||
94 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
10 | math::Extrema e = extrema(velGrid.cbeginValueAll(), /*threading*/true); |
95 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
10 | e.add(velGrid.background().length()); |
96 | 10 | mMaxVelocity = e.max(); | |
97 | } | ||
98 | |||
99 | ✗ | virtual ~VolumeAdvection() | |
100 | { | ||
101 |
4/14✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
|
4 | } |
102 | |||
103 | /// @brief Return the spatial order of accuracy of the advection scheme | ||
104 | /// | ||
105 | /// @note This is the optimal order in smooth regions. In | ||
106 | /// non-smooth regions the flux-limiter will drop the order of | ||
107 | /// accuracy to add numerical dissipation. | ||
108 | 2170 | int spatialOrder() const { return (mIntegrator == Scheme::MAC || | |
109 |
9/18✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 1 times.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
|
9 | mIntegrator == Scheme::BFECC) ? 2 : 1; } |
110 | |||
111 | /// @brief Return the temporal order of accuracy of the advection scheme | ||
112 | /// | ||
113 | /// @note This is the optimal order in smooth regions. In | ||
114 | /// non-smooth regions the flux-limiter will drop the order of | ||
115 | /// accuracy to add numerical dissipation. | ||
116 | ✗ | int temporalOrder() const { | |
117 |
8/56✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 1 times.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✓ Branch 22 taken 1 times.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✓ Branch 30 taken 1 times.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
✓ Branch 39 taken 1 times.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
✓ Branch 45 taken 1 times.
✗ Branch 46 not taken.
✗ Branch 47 not taken.
✗ Branch 48 not taken.
✗ Branch 49 not taken.
✗ Branch 50 not taken.
✗ Branch 51 not taken.
✓ Branch 52 taken 1 times.
✗ Branch 53 not taken.
✗ Branch 54 not taken.
✗ Branch 55 not taken.
|
8 | switch (mIntegrator) { |
118 | case Scheme::SEMI: return 1; | ||
119 | 1 | case Scheme::MID: return 2; | |
120 | 1 | case Scheme::RK3: return 3; | |
121 | 1 | case Scheme::RK4: return 4; | |
122 | 2 | case Scheme::BFECC:return 2; | |
123 | 1 | case Scheme::MAC: return 2; | |
124 | } | ||
125 | ✗ | return 0;//should never reach this point | |
126 | } | ||
127 | |||
128 | /// @brief Set the integrator (see details in the table above) | ||
129 |
6/12✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
|
8 | void setIntegrator(Scheme::SemiLagrangian integrator) { mIntegrator = integrator; } |
130 | |||
131 | /// @brief Return the integrator (see details in the table above) | ||
132 | ✗ | Scheme::SemiLagrangian getIntegrator() const { return mIntegrator; } | |
133 | |||
134 | /// @brief Set the limiter (see details above) | ||
135 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | void setLimiter(Scheme::Limiter limiter) { mLimiter = limiter; } |
136 | |||
137 | /// @brief Retrun the limiter (see details above) | ||
138 | ✗ | Scheme::Limiter getLimiter() const { return mLimiter; } | |
139 | |||
140 | /// @brief Return @c true if a limiter will be applied based on | ||
141 | /// the current settings. | ||
142 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
483 | bool isLimiterOn() const { return this->spatialOrder()>1 && |
143 |
4/48✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 480 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✓ Branch 19 taken 1 times.
✗ Branch 20 not taken.
✓ Branch 21 taken 1 times.
✓ Branch 22 taken 1 times.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
✗ Branch 45 not taken.
✗ Branch 46 not taken.
✗ Branch 47 not taken.
|
483 | mLimiter != Scheme::NO_LIMITER; } |
144 | |||
145 | /// @return the grain-size used for multi-threading | ||
146 | /// @note A grainsize of 0 implies serial execution | ||
147 | ✗ | size_t getGrainSize() const { return mGrainSize; } | |
148 | |||
149 | /// @brief Set the grain-size used for multi-threading | ||
150 | /// @note A grainsize of 0 disables multi-threading | ||
151 | /// @warning A small grainsize can degrade performance, | ||
152 | /// both in terms of time and memory footprint! | ||
153 | 1 | void setGrainSize(size_t grainsize) { mGrainSize = grainsize; } | |
154 | |||
155 | /// @return the number of sub-steps per integration (always larger | ||
156 | /// than or equal to 1). | ||
157 | ✗ | int getSubSteps() const { return mSubSteps; } | |
158 | |||
159 | /// @brief Set the number of sub-steps per integration. | ||
160 | /// @note The only reason to increase the sub-step above its | ||
161 | /// default value of one is to reduce the memory footprint | ||
162 | /// due to significant dilation. Values smaller than 1 will | ||
163 | /// be clamped to 1! | ||
164 | ✗ | void setSubSteps(int substeps) { mSubSteps = math::Max(1, substeps); } | |
165 | |||
166 | /// @brief Return the maximum magnitude of the velocity in the | ||
167 | /// advection velocity field defined during construction. | ||
168 | ✗ | double getMaxVelocity() const { return mMaxVelocity; } | |
169 | |||
170 | /// @return Returns the maximum distance in voxel units of @a inGrid | ||
171 | /// that a particle can travel in the time-step @a dt when advected | ||
172 | /// in the velocity field defined during construction. | ||
173 | /// | ||
174 | /// @details This method is useful when dilating sparse volume | ||
175 | /// grids to pad boundary regions. Excessive dilation can be | ||
176 | /// computationally expensive so use this method to prevent | ||
177 | /// or warn against run-away computation. | ||
178 | /// | ||
179 | /// @throw RuntimeError if @a inGrid does not have uniform voxels. | ||
180 | template<typename VolumeGridT> | ||
181 | 721 | int getMaxDistance(const VolumeGridT& inGrid, double dt) const | |
182 | { | ||
183 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 720 times.
|
721 | if (!inGrid.hasUniformVoxels()) { |
184 |
2/6✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
|
4 | OPENVDB_THROW(RuntimeError, "Volume grid does not have uniform voxels!"); |
185 | } | ||
186 | 720 | const double d = mMaxVelocity*math::Abs(dt)/inGrid.voxelSize()[0]; | |
187 | 720 | return static_cast<int>( math::RoundUp(d) ); | |
188 | } | ||
189 | |||
190 | /// @return Returns a new grid that is the result of passive advection | ||
191 | /// of all the active values the input grid by @a timeStep. | ||
192 | /// | ||
193 | /// @param inGrid The input grid to be advected (unmodified) | ||
194 | /// @param timeStep Time-step of the Runge-Kutta integrator. | ||
195 | /// | ||
196 | /// @details This method will advect all of the active values in | ||
197 | /// the input @a inGrid. To achieve this a | ||
198 | /// deep-copy is dilated to account for the material | ||
199 | /// transport. This dilation step can be slow for large | ||
200 | /// time steps @a dt or a velocity field with large magnitudes. | ||
201 | /// | ||
202 | /// @warning If the VolumeSamplerT is of higher order than one | ||
203 | /// (i.e. tri-linear interpolation) instabilities are | ||
204 | /// known to occur. To suppress those monotonicity | ||
205 | /// constrains or flux-limiters need to be applies. | ||
206 | /// | ||
207 | /// @throw RuntimeError if @a inGrid does not have uniform voxels. | ||
208 | template<typename VolumeGridT, | ||
209 | typename VolumeSamplerT>//only C++11 allows for a default argument | ||
210 | 962 | typename VolumeGridT::Ptr advect(const VolumeGridT& inGrid, double timeStep) | |
211 | { | ||
212 | 962 | typename VolumeGridT::Ptr outGrid = inGrid.deepCopy(); | |
213 | 962 | const double dt = timeStep/mSubSteps; | |
214 |
2/2✓ Branch 1 taken 480 times.
✓ Branch 2 taken 1 times.
|
962 | const int n = this->getMaxDistance(inGrid, dt); |
215 |
1/2✓ Branch 1 taken 480 times.
✗ Branch 2 not taken.
|
960 | dilateActiveValues( outGrid->tree(), n, NN_FACE, EXPAND_TILES); |
216 |
1/2✓ Branch 1 taken 480 times.
✗ Branch 2 not taken.
|
960 | this->template cook<VolumeGridT, VolumeSamplerT>(*outGrid, inGrid, dt); |
217 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 480 times.
|
960 | for (int step = 1; step < mSubSteps; ++step) { |
218 | ✗ | typename VolumeGridT::Ptr tmpGrid = outGrid->deepCopy(); | |
219 | ✗ | dilateActiveValues( tmpGrid->tree(), n, NN_FACE, EXPAND_TILES); | |
220 | ✗ | this->template cook<VolumeGridT, VolumeSamplerT>(*tmpGrid, *outGrid, dt); | |
221 | outGrid.swap( tmpGrid ); | ||
222 | } | ||
223 | |||
224 | 960 | return outGrid; | |
225 | } | ||
226 | |||
227 | /// @return Returns a new grid that is the result of | ||
228 | /// passive advection of the active values in @a inGrid | ||
229 | /// that intersect the active values in @c mask. The time | ||
230 | /// of the output grid is incremented by @a timeStep. | ||
231 | /// | ||
232 | /// @param inGrid The input grid to be advected (unmodified). | ||
233 | /// @param mask The mask of active values defining the active voxels | ||
234 | /// in @c inGrid on which to perform advection. Only | ||
235 | /// if a value is active in both grids will it be modified. | ||
236 | /// @param timeStep Time-step for a single Runge-Kutta integration step. | ||
237 | /// | ||
238 | /// | ||
239 | /// @details This method will advect all of the active values in | ||
240 | /// the input @a inGrid that intersects with the | ||
241 | /// active values in @a mask. To achieve this a | ||
242 | /// deep-copy is dilated to account for the material | ||
243 | /// transport and finally cropped to the intersection | ||
244 | /// with @a mask. The dilation step can be slow for large | ||
245 | /// time steps @a dt or fast moving velocity fields. | ||
246 | /// | ||
247 | /// @warning If the VolumeSamplerT is of higher order the one | ||
248 | /// (i.e. tri-linear interpolation) instabilities are | ||
249 | /// known to occur. To suppress those monotonicity | ||
250 | /// constrains or flux-limiters need to be applies. | ||
251 | /// | ||
252 | /// @throw RuntimeError if @a inGrid is not aligned with @a mask | ||
253 | /// or if its voxels are not uniform. | ||
254 | template<typename VolumeGridT, | ||
255 | typename MaskGridT, | ||
256 | typename VolumeSamplerT>//only C++11 allows for a default argument | ||
257 | 240 | typename VolumeGridT::Ptr advect(const VolumeGridT& inGrid, const MaskGridT& mask, double timeStep) | |
258 | { | ||
259 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
|
240 | if (inGrid.transform() != mask.transform()) { |
260 | ✗ | OPENVDB_THROW(RuntimeError, "Volume grid and mask grid are misaligned! Consider " | |
261 | "resampling either of the two grids into the index space of the other."); | ||
262 | } | ||
263 | 240 | typename VolumeGridT::Ptr outGrid = inGrid.deepCopy(); | |
264 | 240 | const double dt = timeStep/mSubSteps; | |
265 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
240 | const int n = this->getMaxDistance(inGrid, dt); |
266 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
240 | dilateActiveValues( outGrid->tree(), n, NN_FACE, EXPAND_TILES); |
267 | outGrid->topologyIntersection( mask ); | ||
268 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
240 | pruneInactive( outGrid->tree(), mGrainSize>0, mGrainSize ); |
269 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
240 | this->template cook<VolumeGridT, VolumeSamplerT>(*outGrid, inGrid, dt); |
270 | outGrid->topologyUnion( inGrid ); | ||
271 | |||
272 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
|
240 | for (int step = 1; step < mSubSteps; ++step) { |
273 | ✗ | typename VolumeGridT::Ptr tmpGrid = outGrid->deepCopy(); | |
274 | ✗ | dilateActiveValues( tmpGrid->tree(), n, NN_FACE, EXPAND_TILES); | |
275 | tmpGrid->topologyIntersection( mask ); | ||
276 | ✗ | pruneInactive( tmpGrid->tree(), mGrainSize>0, mGrainSize ); | |
277 | ✗ | this->template cook<VolumeGridT, VolumeSamplerT>(*tmpGrid, *outGrid, dt); | |
278 | tmpGrid->topologyUnion( inGrid ); | ||
279 | outGrid.swap( tmpGrid ); | ||
280 | } | ||
281 | 240 | return outGrid; | |
282 | } | ||
283 | |||
284 | private: | ||
285 | // disallow copy construction and copy by assignment! | ||
286 | VolumeAdvection(const VolumeAdvection&);// not implemented | ||
287 | VolumeAdvection& operator=(const VolumeAdvection&);// not implemented | ||
288 | |||
289 | ✗ | void start(const char* str) const | |
290 | { | ||
291 | ✗ | if (mInterrupter) mInterrupter->start(str); | |
292 | } | ||
293 | ✗ | void stop() const | |
294 | { | ||
295 | ✗ | if (mInterrupter) mInterrupter->end(); | |
296 | } | ||
297 | 4800 | bool interrupt() const | |
298 | { | ||
299 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2400 times.
|
4800 | if (mInterrupter && util::wasInterrupted(mInterrupter)) { |
300 | ✗ | thread::cancelGroupExecution(); | |
301 | ✗ | return true; | |
302 | } | ||
303 | return false; | ||
304 | } | ||
305 | |||
306 | template<typename VolumeGridT, typename VolumeSamplerT> | ||
307 | 720 | void cook(VolumeGridT& outGrid, const VolumeGridT& inGrid, double dt) | |
308 | { | ||
309 |
3/7✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 240 times.
✓ Branch 4 taken 240 times.
✓ Branch 5 taken 240 times.
✗ Branch 6 not taken.
|
720 | switch (mIntegrator) { |
310 | ✗ | case Scheme::SEMI: { | |
311 | ✗ | Advect<VolumeGridT, 1, VolumeSamplerT> adv(inGrid, *this); | |
312 | ✗ | adv.cook(outGrid, dt); | |
313 | break; | ||
314 | } | ||
315 | ✗ | case Scheme::MID: { | |
316 | ✗ | Advect<VolumeGridT, 2, VolumeSamplerT> adv(inGrid, *this); | |
317 | ✗ | adv.cook(outGrid, dt); | |
318 | break; | ||
319 | } | ||
320 | ✗ | case Scheme::RK3: { | |
321 | ✗ | Advect<VolumeGridT, 3, VolumeSamplerT> adv(inGrid, *this); | |
322 | ✗ | adv.cook(outGrid, dt); | |
323 | break; | ||
324 | } | ||
325 | 240 | case Scheme::RK4: { | |
326 | 480 | Advect<VolumeGridT, 4, VolumeSamplerT> adv(inGrid, *this); | |
327 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
240 | adv.cook(outGrid, dt); |
328 | break; | ||
329 | } | ||
330 | 240 | case Scheme::BFECC: { | |
331 | 480 | Advect<VolumeGridT, 1, VolumeSamplerT> adv(inGrid, *this); | |
332 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
240 | adv.cook(outGrid, dt); |
333 | break; | ||
334 | } | ||
335 | 240 | case Scheme::MAC: { | |
336 | 480 | Advect<VolumeGridT, 1, VolumeSamplerT> adv(inGrid, *this); | |
337 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
240 | adv.cook(outGrid, dt); |
338 | break; | ||
339 | } | ||
340 | default: | ||
341 | ✗ | OPENVDB_THROW(ValueError, "Spatial difference scheme not supported!"); | |
342 | } | ||
343 | 720 | pruneInactive(outGrid.tree(), mGrainSize>0, mGrainSize); | |
344 | 720 | } | |
345 | |||
346 | // Private class that implements the multi-threaded advection | ||
347 | template<typename VolumeGridT, size_t OrderRK, typename SamplerT> struct Advect; | ||
348 | |||
349 | // Private member data of VolumeAdvection | ||
350 | const VelocityGridT& mVelGrid; | ||
351 | double mMaxVelocity; | ||
352 | InterrupterType* mInterrupter; | ||
353 | Scheme::SemiLagrangian mIntegrator; | ||
354 | Scheme::Limiter mLimiter; | ||
355 | size_t mGrainSize; | ||
356 | int mSubSteps; | ||
357 | };//end of VolumeAdvection class | ||
358 | |||
359 | // Private class that implements the multi-threaded advection | ||
360 | template<typename VelocityGridT, bool StaggeredVelocity, typename InterrupterType> | ||
361 | template<typename VolumeGridT, size_t OrderRK, typename SamplerT> | ||
362 | struct VolumeAdvection<VelocityGridT, StaggeredVelocity, InterrupterType>::Advect | ||
363 | { | ||
364 | using TreeT = typename VolumeGridT::TreeType; | ||
365 | using AccT = typename VolumeGridT::ConstAccessor; | ||
366 | using ValueT = typename TreeT::ValueType; | ||
367 | using LeafManagerT = typename tree::LeafManager<TreeT>; | ||
368 | using LeafNodeT = typename LeafManagerT::LeafNodeType; | ||
369 | using LeafRangeT = typename LeafManagerT::LeafRange; | ||
370 | using VelocityIntegratorT = VelocityIntegrator<VelocityGridT, StaggeredVelocity>; | ||
371 | using RealT = typename VelocityIntegratorT::ElementType; | ||
372 | using VoxelIterT = typename TreeT::LeafNodeType::ValueOnIter; | ||
373 | |||
374 |
1/2✓ Branch 1 taken 720 times.
✗ Branch 2 not taken.
|
1440 | Advect(const VolumeGridT& inGrid, const VolumeAdvection& parent) |
375 | : mTask(nullptr) | ||
376 | , mInGrid(&inGrid) | ||
377 | , mVelocityInt(parent.mVelGrid) | ||
378 |
1/2✓ Branch 1 taken 720 times.
✗ Branch 2 not taken.
|
1440 | , mParent(&parent) |
379 | { | ||
380 | } | ||
381 | 5280 | inline void cook(const LeafRangeT& range) | |
382 | { | ||
383 |
2/2✓ Branch 0 taken 1680 times.
✓ Branch 1 taken 960 times.
|
5280 | if (mParent->mGrainSize > 0) { |
384 | 3360 | tbb::parallel_for(range, *this); | |
385 | } else { | ||
386 | 1920 | (*this)(range); | |
387 | } | ||
388 | } | ||
389 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2640 times.
|
5280 | void operator()(const LeafRangeT& range) const |
390 | { | ||
391 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2640 times.
|
5280 | assert(mTask); |
392 | 5280 | mTask(const_cast<Advect*>(this), range); | |
393 | } | ||
394 | 1440 | void cook(VolumeGridT& outGrid, double time_step) | |
395 | { | ||
396 | namespace ph = std::placeholders; | ||
397 | |||
398 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 720 times.
|
1440 | mParent->start("Advecting volume"); |
399 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 480 times.
|
3360 | LeafManagerT manager(outGrid.tree(), mParent->spatialOrder()==2 ? 1 : 0); |
400 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 480 times.
|
1440 | const LeafRangeT range = manager.leafRange(mParent->mGrainSize); |
401 | 1440 | const RealT dt = static_cast<RealT>(-time_step);//method of characteristics backtracks | |
402 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 480 times.
|
1440 | if (mParent->mIntegrator == Scheme::MAC) { |
403 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | mTask = std::bind(&Advect::rk, ph::_1, ph::_2, dt, 0, mInGrid);//out[0]=forward |
404 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | this->cook(range); |
405 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | mTask = std::bind(&Advect::rk, ph::_1, ph::_2,-dt, 1, &outGrid);//out[1]=backward |
406 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | this->cook(range); |
407 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | mTask = std::bind(&Advect::mac, ph::_1, ph::_2);//out[0] = out[0] + (in[0] - out[1])/2 |
408 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | this->cook(range); |
409 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 240 times.
|
960 | } else if (mParent->mIntegrator == Scheme::BFECC) { |
410 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | mTask = std::bind(&Advect::rk, ph::_1, ph::_2, dt, 0, mInGrid);//out[0]=forward |
411 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | this->cook(range); |
412 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | mTask = std::bind(&Advect::rk, ph::_1, ph::_2,-dt, 1, &outGrid);//out[1]=backward |
413 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | this->cook(range); |
414 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | mTask = std::bind(&Advect::bfecc, ph::_1, ph::_2);//out[0] = (3*in[0] - out[1])/2 |
415 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | this->cook(range); |
416 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | mTask = std::bind(&Advect::rk, ph::_1, ph::_2, dt, 1, &outGrid);//out[1]=forward |
417 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | this->cook(range); |
418 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | manager.swapLeafBuffer(1);// out[0] = out[1] |
419 | } else {// SEMI, MID, RK3 and RK4 | ||
420 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | mTask = std::bind(&Advect::rk, ph::_1, ph::_2, dt, 0, mInGrid);//forward |
421 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
480 | this->cook(range); |
422 | } | ||
423 | |||
424 |
2/2✓ Branch 0 taken 480 times.
✓ Branch 1 taken 240 times.
|
1440 | if (mParent->spatialOrder()==2) manager.removeAuxBuffers(); |
425 | |||
426 |
1/2✓ Branch 1 taken 720 times.
✗ Branch 2 not taken.
|
1440 | mTask = std::bind(&Advect::limiter, ph::_1, ph::_2, dt);// out[0] = limiter( out[0] ) |
427 |
1/2✓ Branch 1 taken 720 times.
✗ Branch 2 not taken.
|
1440 | this->cook(range); |
428 | |||
429 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 720 times.
|
1440 | mParent->stop(); |
430 | } | ||
431 | // Last step of the MacCormack scheme: out[0] = out[0] + (in[0] - out[1])/2 | ||
432 | 480 | void mac(const LeafRangeT& range) const | |
433 | { | ||
434 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 240 times.
|
480 | if (mParent->interrupt()) return; |
435 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
|
480 | assert( mParent->mIntegrator == Scheme::MAC ); |
436 | 480 | AccT acc = mInGrid->getAccessor(); | |
437 |
2/2✓ Branch 1 taken 839 times.
✓ Branch 2 taken 240 times.
|
2158 | for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) { |
438 |
1/2✓ Branch 1 taken 839 times.
✗ Branch 2 not taken.
|
1678 | ValueT* out0 = leafIter.buffer( 0 ).data();// forward |
439 |
1/2✓ Branch 1 taken 839 times.
✗ Branch 2 not taken.
|
1678 | const ValueT* out1 = leafIter.buffer( 1 ).data();// backward |
440 | const LeafNodeT* leaf = acc.probeConstLeaf( leafIter->origin() ); | ||
441 |
2/2✓ Branch 0 taken 824 times.
✓ Branch 1 taken 15 times.
|
1678 | if (leaf != nullptr) { |
442 |
1/2✓ Branch 1 taken 824 times.
✗ Branch 2 not taken.
|
1648 | const ValueT* in0 = leaf->buffer().data(); |
443 |
2/2✓ Branch 0 taken 322714 times.
✓ Branch 1 taken 824 times.
|
647076 | for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { |
444 | const Index i = voxelIter.pos(); | ||
445 | 645428 | out0[i] += RealT(0.5) * ( in0[i] - out1[i] ); | |
446 | } | ||
447 | } else { | ||
448 |
2/2✓ Branch 0 taken 735 times.
✓ Branch 1 taken 15 times.
|
1500 | for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { |
449 | const Index i = voxelIter.pos(); | ||
450 |
2/6✓ Branch 1 taken 735 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 735 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
|
1470 | out0[i] += RealT(0.5) * ( acc.getValue(voxelIter.getCoord()) - out1[i] ); |
451 | }//loop over active voxels | ||
452 | } | ||
453 | }//loop over leaf nodes | ||
454 | } | ||
455 | // Intermediate step in the BFECC scheme: out[0] = (3*in[0] - out[1])/2 | ||
456 | 480 | void bfecc(const LeafRangeT& range) const | |
457 | { | ||
458 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 240 times.
|
480 | if (mParent->interrupt()) return; |
459 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
|
480 | assert( mParent->mIntegrator == Scheme::BFECC ); |
460 | 480 | AccT acc = mInGrid->getAccessor(); | |
461 |
2/2✓ Branch 1 taken 2137 times.
✓ Branch 2 taken 240 times.
|
4754 | for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) { |
462 |
1/2✓ Branch 1 taken 2137 times.
✗ Branch 2 not taken.
|
4274 | ValueT* out0 = leafIter.buffer( 0 ).data();// forward |
463 |
1/2✓ Branch 1 taken 2137 times.
✗ Branch 2 not taken.
|
4274 | const ValueT* out1 = leafIter.buffer( 1 ).data();// backward |
464 | const LeafNodeT* leaf = acc.probeConstLeaf(leafIter->origin()); | ||
465 |
2/2✓ Branch 0 taken 688 times.
✓ Branch 1 taken 1449 times.
|
4274 | if (leaf != nullptr) { |
466 |
1/2✓ Branch 1 taken 688 times.
✗ Branch 2 not taken.
|
1376 | const ValueT* in0 = leaf->buffer().data(); |
467 |
2/2✓ Branch 0 taken 270305 times.
✓ Branch 1 taken 688 times.
|
541986 | for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { |
468 | const Index i = voxelIter.pos(); | ||
469 | 540610 | out0[i] = RealT(0.5)*( RealT(3)*in0[i] - out1[i] ); | |
470 | }//loop over active voxels | ||
471 | } else { | ||
472 |
2/2✓ Branch 0 taken 59213 times.
✓ Branch 1 taken 1449 times.
|
121324 | for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { |
473 | const Index i = voxelIter.pos(); | ||
474 |
2/6✓ Branch 1 taken 59213 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 59213 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
|
118426 | out0[i] = RealT(0.5)*( RealT(3)*acc.getValue(voxelIter.getCoord()) - out1[i] ); |
475 | }//loop over active voxels | ||
476 | } | ||
477 | }//loop over leaf nodes | ||
478 | } | ||
479 | // Semi-Lagrangian integration with Runge-Kutta of various orders (1->4) | ||
480 | 2880 | void rk(const LeafRangeT& range, RealT dt, size_t n, const VolumeGridT* grid) const | |
481 | { | ||
482 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1440 times.
|
2880 | if (mParent->interrupt()) return; |
483 | 2880 | const math::Transform& xform = mInGrid->transform(); | |
484 | AccT acc = grid->getAccessor(); | ||
485 |
2/2✓ Branch 1 taken 11438 times.
✓ Branch 2 taken 1440 times.
|
25756 | for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) { |
486 |
1/2✓ Branch 1 taken 11438 times.
✗ Branch 2 not taken.
|
22876 | ValueT* phi = leafIter.buffer( n ).data(); |
487 |
2/2✓ Branch 0 taken 2217684 times.
✓ Branch 1 taken 11438 times.
|
4458244 | for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { |
488 | 4435368 | ValueT& value = phi[voxelIter.pos()]; | |
489 |
1/2✓ Branch 1 taken 2217684 times.
✗ Branch 2 not taken.
|
4435368 | Vec3d wPos = xform.indexToWorld(voxelIter.getCoord()); |
490 |
1/2✓ Branch 1 taken 2217684 times.
✗ Branch 2 not taken.
|
4435368 | mVelocityInt.template rungeKutta<OrderRK, Vec3d>(dt, wPos); |
491 |
1/4✓ Branch 1 taken 2217684 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
4435368 | value = SamplerT::sample(acc, xform.worldToIndex(wPos)); |
492 | }//loop over active voxels | ||
493 | }//loop over leaf nodes | ||
494 | } | ||
495 | 720 | void limiter(const LeafRangeT& range, RealT dt) const | |
496 | { | ||
497 | 720 | if (mParent->interrupt()) return; | |
498 | 720 | const bool doLimiter = mParent->isLimiterOn(); | |
499 | 480 | const bool doClamp = mParent->mLimiter == Scheme::CLAMP; | |
500 | ✗ | ValueT data[2][2][2], vMin, vMax; | |
501 | 720 | const math::Transform& xform = mInGrid->transform(); | |
502 | AccT acc = mInGrid->getAccessor(); | ||
503 | 720 | const ValueT backg = mInGrid->background(); | |
504 | 7045 | for (typename LeafRangeT::Iterator leafIter = range.begin(); leafIter; ++leafIter) { | |
505 | 6325 | ValueT* phi = leafIter.buffer( 0 ).data(); | |
506 | 1241524 | for (VoxelIterT voxelIter = leafIter->beginValueOn(); voxelIter; ++voxelIter) { | |
507 | 1235199 | ValueT& value = phi[voxelIter.pos()]; | |
508 | |||
509 | 1235199 | if ( doLimiter ) { | |
510 | ✗ | assert(OrderRK == 1); | |
511 | 652967 | Vec3d wPos = xform.indexToWorld(voxelIter.getCoord()); | |
512 | 652967 | mVelocityInt.template rungeKutta<1, Vec3d>(dt, wPos);// Explicit Euler | |
513 | Vec3d iPos = xform.worldToIndex(wPos); | ||
514 | 652967 | Coord ijk = Coord::floor( iPos ); | |
515 | 652967 | BoxSampler::getValues(data, acc, ijk); | |
516 | 652967 | BoxSampler::extrema(data, vMin, vMax); | |
517 | 652967 | if ( doClamp ) { | |
518 | 1305934 | value = math::Clamp( value, vMin, vMax); | |
519 | ✗ | } else if (value < vMin || value > vMax ) { | |
520 | ✗ | iPos -= Vec3R(ijk[0], ijk[1], ijk[2]);//unit coordinates | |
521 | ✗ | value = BoxSampler::trilinearInterpolation( data, iPos ); | |
522 | } | ||
523 | } | ||
524 | |||
525 | 1235199 | if (math::isApproxEqual(value, backg, math::Delta<ValueT>::value())) { | |
526 | 435421 | value = backg; | |
527 | 435421 | leafIter->setValueOff( voxelIter.pos() ); | |
528 | } | ||
529 | }//loop over active voxels | ||
530 | }//loop over leaf nodes | ||
531 | } | ||
532 | // Public member data of the private Advect class | ||
533 | |||
534 | typename std::function<void (Advect*, const LeafRangeT&)> mTask; | ||
535 | const VolumeGridT* mInGrid; | ||
536 | const VelocityIntegratorT mVelocityInt;// lightweight! | ||
537 | const VolumeAdvection* mParent; | ||
538 | };// end of private member class Advect | ||
539 | |||
540 | |||
541 | //////////////////////////////////////// | ||
542 | |||
543 | |||
544 | // Explicit Template Instantiation | ||
545 | |||
546 | #ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION | ||
547 | |||
548 | #ifdef OPENVDB_INSTANTIATE_VOLUMEADVECT | ||
549 | #include <openvdb/util/ExplicitInstantiation.h> | ||
550 | #endif | ||
551 | |||
552 | OPENVDB_INSTANTIATE_CLASS VolumeAdvection<Vec3SGrid, true, util::NullInterrupter>; | ||
553 | OPENVDB_INSTANTIATE_CLASS VolumeAdvection<Vec3SGrid, false, util::NullInterrupter>; | ||
554 | |||
555 | OPENVDB_INSTANTIATE FloatGrid::Ptr VolumeAdvection<Vec3SGrid, true, util::NullInterrupter>::advect<FloatGrid, Sampler<1, false>>(const FloatGrid&, double); | ||
556 | OPENVDB_INSTANTIATE DoubleGrid::Ptr VolumeAdvection<Vec3SGrid, true, util::NullInterrupter>::advect<DoubleGrid, Sampler<1, false>>(const DoubleGrid&, double); | ||
557 | OPENVDB_INSTANTIATE Vec3SGrid::Ptr VolumeAdvection<Vec3SGrid, true, util::NullInterrupter>::advect<Vec3SGrid, Sampler<1, false>>(const Vec3SGrid&, double); | ||
558 | |||
559 | OPENVDB_INSTANTIATE FloatGrid::Ptr VolumeAdvection<Vec3SGrid, false, util::NullInterrupter>::advect<FloatGrid, Sampler<1, false>>(const FloatGrid&, double); | ||
560 | OPENVDB_INSTANTIATE DoubleGrid::Ptr VolumeAdvection<Vec3SGrid, false, util::NullInterrupter>::advect<DoubleGrid, Sampler<1, false>>(const DoubleGrid&, double); | ||
561 | OPENVDB_INSTANTIATE Vec3SGrid::Ptr VolumeAdvection<Vec3SGrid, false, util::NullInterrupter>::advect<Vec3SGrid, Sampler<1, false>>(const Vec3SGrid&, double); | ||
562 | |||
563 | #endif // OPENVDB_USE_EXPLICIT_INSTANTIATION | ||
564 | |||
565 | |||
566 | } // namespace tools | ||
567 | } // namespace OPENVDB_VERSION_NAME | ||
568 | } // namespace openvdb | ||
569 | |||
570 | #endif // OPENVDB_TOOLS_VOLUME_ADVECT_HAS_BEEN_INCLUDED | ||
571 |