Line | Branch | Exec | Source |
---|---|---|---|
1 | // Copyright Contributors to the OpenVDB Project | ||
2 | // SPDX-License-Identifier: MPL-2.0 | ||
3 | |||
4 | /// @file tools/PotentialFlow.h | ||
5 | /// | ||
6 | /// @brief Tools for creating potential flow fields through solving Laplace's equation | ||
7 | /// | ||
8 | /// @authors Todd Keeler, Dan Bailey | ||
9 | |||
10 | #ifndef OPENVDB_TOOLS_POTENTIAL_FLOW_HAS_BEEN_INCLUDED | ||
11 | #define OPENVDB_TOOLS_POTENTIAL_FLOW_HAS_BEEN_INCLUDED | ||
12 | |||
13 | #include <openvdb/openvdb.h> | ||
14 | |||
15 | #include "GridOperators.h" | ||
16 | #include "GridTransformer.h" | ||
17 | #include "Mask.h" // interiorMask | ||
18 | #include "Morphology.h" // erodeActiveValues | ||
19 | #include "PoissonSolver.h" | ||
20 | #include <openvdb/openvdb.h> | ||
21 | |||
22 | |||
23 | namespace openvdb { | ||
24 | OPENVDB_USE_VERSION_NAMESPACE | ||
25 | namespace OPENVDB_VERSION_NAME { | ||
26 | namespace tools { | ||
27 | |||
28 | /// @brief Metafunction to convert a vector-valued grid type to a scalar grid type | ||
29 | template<typename VecGridT> | ||
30 | struct VectorToScalarGrid { | ||
31 | using Type = | ||
32 | typename VecGridT::template ValueConverter<typename VecGridT::ValueType::value_type>::Type; | ||
33 | using Ptr = typename Type::Ptr; | ||
34 | using ConstPtr = typename Type::ConstPtr; | ||
35 | }; | ||
36 | |||
37 | |||
38 | /// @brief Construct a mask for the Potential Flow domain. | ||
39 | /// @details For a level set, this represents a rebuilt exterior narrow band. | ||
40 | /// For any other grid it is a new region that surrounds the active voxels. | ||
41 | /// @param grid source grid to use for computing the mask | ||
42 | /// @param dilation dilation in voxels of the source grid to form the new potential flow mask | ||
43 | template<typename GridT, typename MaskT = typename GridT::template ValueConverter<ValueMask>::Type> | ||
44 | typename MaskT::Ptr | ||
45 | createPotentialFlowMask(const GridT& grid, int dilation = 5); | ||
46 | |||
47 | |||
48 | /// @brief Create a Potential Flow velocities grid for the Neumann boundary. | ||
49 | /// @param collider a level set that represents the boundary | ||
50 | /// @param domain a mask to represent the potential flow domain | ||
51 | /// @param boundaryVelocity an optional grid pointer to stores the velocities of the boundary | ||
52 | /// @param backgroundVelocity a background velocity value | ||
53 | /// @details Typically this method involves supplying a velocity grid for the | ||
54 | /// collider boundary, however it can also be used for a global wind field | ||
55 | /// around the collider by supplying an empty boundary Velocity and a | ||
56 | /// non-zero background velocity. | ||
57 | template<typename Vec3T, typename GridT, typename MaskT> | ||
58 | typename GridT::template ValueConverter<Vec3T>::Type::Ptr | ||
59 | createPotentialFlowNeumannVelocities(const GridT& collider, const MaskT& domain, | ||
60 | const typename GridT::template ValueConverter<Vec3T>::Type::ConstPtr boundaryVelocity, | ||
61 | const Vec3T& backgroundVelocity); | ||
62 | |||
63 | |||
64 | /// @brief Compute the Potential on the domain using the Neumann boundary conditions on | ||
65 | /// solid boundaries | ||
66 | /// @param domain a mask to represent the domain in which to perform the solve | ||
67 | /// @param neumann the topology of this grid defines where the solid boundaries are and grid | ||
68 | /// values give the Neumann boundaries that should be applied there | ||
69 | /// @param state the solver parameters for computing the solution | ||
70 | /// @param interrupter pointer to an optional interrupter adhering to the | ||
71 | /// util::NullInterrupter interface | ||
72 | /// @details On input, the State object should specify convergence criteria | ||
73 | /// (minimum error and maximum number of iterations); on output, it gives | ||
74 | /// the actual termination conditions. | ||
75 | template<typename Vec3GridT, typename MaskT, typename InterrupterT = util::NullInterrupter> | ||
76 | typename VectorToScalarGrid<Vec3GridT>::Ptr | ||
77 | computeScalarPotential(const MaskT& domain, const Vec3GridT& neumann, math::pcg::State& state, | ||
78 | InterrupterT* interrupter = nullptr); | ||
79 | |||
80 | |||
81 | /// @brief Compute a vector Flow Field comprising the gradient of the potential with Neumann | ||
82 | /// boundary conditions applied | ||
83 | /// @param potential scalar potential, typically computed from computeScalarPotential() | ||
84 | /// @param neumann the topology of this grid defines where the solid boundaries are and grid | ||
85 | /// values give the Neumann boundaries that should be applied there | ||
86 | /// @param backgroundVelocity a background velocity value | ||
87 | template<typename Vec3GridT> | ||
88 | typename Vec3GridT::Ptr | ||
89 | computePotentialFlow(const typename VectorToScalarGrid<Vec3GridT>::Type& potential, | ||
90 | const Vec3GridT& neumann, | ||
91 | const typename Vec3GridT::ValueType backgroundVelocity = | ||
92 | zeroVal<typename Vec3GridT::TreeType::ValueType>()); | ||
93 | |||
94 | |||
95 | ////////////////////////////////////////////////////////// | ||
96 | |||
97 | /// @cond OPENVDB_DOCS_INTERNAL | ||
98 | |||
99 | namespace potential_flow_internal { | ||
100 | |||
101 | |||
102 | /// @private | ||
103 | // helper function for retrieving a mask that comprises the outer-most layer of voxels | ||
104 | template<typename GridT> | ||
105 | typename GridT::TreeType::template ValueConverter<ValueMask>::Type::Ptr | ||
106 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
8 | extractOuterVoxelMask(GridT& inGrid) |
107 | { | ||
108 | using MaskTreeT = typename GridT::TreeType::template ValueConverter<ValueMask>::Type; | ||
109 |
3/6✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
|
16 | typename MaskTreeT::Ptr interiorMask(new MaskTreeT(inGrid.tree(), false, TopologyCopy())); |
110 |
3/8✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
16 | typename MaskTreeT::Ptr boundaryMask(new MaskTreeT(inGrid.tree(), false, TopologyCopy())); |
111 | |||
112 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
8 | tools::erodeActiveValues(*interiorMask, /*iterations=*/1, tools::NN_FACE, tools::IGNORE_TILES); |
113 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
8 | tools::pruneInactive(*interiorMask); |
114 | boundaryMask->topologyDifference(*interiorMask); | ||
115 | 8 | return boundaryMask; | |
116 | } | ||
117 | |||
118 | |||
119 | // computes Neumann velocities through sampling the gradient and velocities | ||
120 | template<typename Vec3GridT, typename GradientT> | ||
121 | struct ComputeNeumannVelocityOp | ||
122 | { | ||
123 | using ValueT = typename Vec3GridT::ValueType; | ||
124 | using VelocityAccessor = typename Vec3GridT::ConstAccessor; | ||
125 | using VelocitySamplerT = GridSampler< | ||
126 | typename Vec3GridT::ConstAccessor, BoxSampler>; | ||
127 | using GradientValueT = typename GradientT::TreeType::ValueType; | ||
128 | |||
129 | 3 | ComputeNeumannVelocityOp( const GradientT& gradient, | |
130 | const Vec3GridT& velocity, | ||
131 | const ValueT& backgroundVelocity) | ||
132 | : mGradient(gradient) | ||
133 | , mVelocity(&velocity) | ||
134 | 3 | , mBackgroundVelocity(backgroundVelocity) { } | |
135 | |||
136 | 4 | ComputeNeumannVelocityOp( const GradientT& gradient, | |
137 | const ValueT& backgroundVelocity) | ||
138 | : mGradient(gradient) | ||
139 | 4 | , mBackgroundVelocity(backgroundVelocity) { } | |
140 | |||
141 | 240 | void operator()(typename Vec3GridT::TreeType::LeafNodeType& leaf, size_t) const { | |
142 | 240 | auto gradientAccessor = mGradient.getConstAccessor(); | |
143 | |||
144 | 240 | std::unique_ptr<VelocityAccessor> velocityAccessor; | |
145 | 240 | std::unique_ptr<VelocitySamplerT> velocitySampler; | |
146 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 80 times.
|
240 | if (mVelocity) { |
147 |
2/4✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 60 times.
✗ Branch 5 not taken.
|
120 | velocityAccessor.reset(new VelocityAccessor(mVelocity->getConstAccessor())); |
148 |
1/2✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
|
120 | velocitySampler.reset(new VelocitySamplerT(*velocityAccessor, mVelocity->transform())); |
149 | } | ||
150 | |||
151 |
2/2✓ Branch 0 taken 14518 times.
✓ Branch 1 taken 140 times.
|
25128 | for (auto it = leaf.beginValueOn(); it; ++it) { |
152 |
1/2✓ Branch 1 taken 14518 times.
✗ Branch 2 not taken.
|
24888 | Coord ijk = it.getCoord(); |
153 |
1/2✓ Branch 1 taken 14518 times.
✗ Branch 2 not taken.
|
24888 | auto gradient = gradientAccessor.getValue(ijk); |
154 |
1/2✓ Branch 1 taken 14518 times.
✗ Branch 2 not taken.
|
24888 | if (gradient.normalize()) { |
155 |
1/2✓ Branch 1 taken 14518 times.
✗ Branch 2 not taken.
|
24888 | const Vec3d xyz = mGradient.transform().indexToWorld(ijk); |
156 |
3/4✓ Branch 0 taken 6222 times.
✓ Branch 1 taken 8296 times.
✓ Branch 3 taken 6222 times.
✗ Branch 4 not taken.
|
24888 | const ValueT sampledVelocity = velocitySampler ? |
157 | 4148 | velocitySampler->wsSample(xyz) : zeroVal<ValueT>(); | |
158 |
1/2✓ Branch 1 taken 14518 times.
✗ Branch 2 not taken.
|
24888 | auto velocity = sampledVelocity + mBackgroundVelocity; |
159 | 4148 | auto value = gradient.dot(velocity) * gradient; | |
160 |
1/4✓ Branch 1 taken 14518 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
24888 | it.setValue(value); |
161 | } | ||
162 | else { | ||
163 | ✗ | it.setValueOff(); | |
164 | } | ||
165 | } | ||
166 | 240 | } | |
167 | |||
168 | private: | ||
169 | const GradientT& mGradient; | ||
170 | const Vec3GridT* mVelocity = nullptr; | ||
171 | const ValueT& mBackgroundVelocity; | ||
172 | }; // struct ComputeNeumannVelocityOp | ||
173 | |||
174 | |||
175 | // initializes the boundary conditions for use in the Poisson Solver | ||
176 | template<typename Vec3GridT, typename MaskT> | ||
177 | struct SolveBoundaryOp | ||
178 | { | ||
179 |
2/6✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
|
3 | SolveBoundaryOp(const Vec3GridT& velGrid, const MaskT& domainGrid) |
180 | 3 | : mVoxelSize(domainGrid.voxelSize()[0]) | |
181 | , mVelGrid(velGrid) | ||
182 | 3 | , mDomainGrid(domainGrid) | |
183 | { } | ||
184 | |||
185 | 418032 | void operator()(const Coord& ijk, const Coord& neighbor, | |
186 | double& source, double& diagonal) const { | ||
187 | |||
188 | 418032 | typename Vec3GridT::ConstAccessor velGridAccessor = mVelGrid.getAccessor(); | |
189 | const Coord diff = (ijk - neighbor); | ||
190 | |||
191 |
2/2✓ Branch 1 taken 61308 times.
✓ Branch 2 taken 147708 times.
|
418032 | if (velGridAccessor.isValueOn(ijk)) { // Neumann |
192 |
1/2✓ Branch 1 taken 61308 times.
✗ Branch 2 not taken.
|
122616 | const typename Vec3GridT::ValueType& sampleVel = velGridAccessor.getValue(ijk); |
193 | 122616 | source += mVoxelSize*diff[0]*sampleVel[0]; | |
194 | 122616 | source += mVoxelSize*diff[1]*sampleVel[1]; | |
195 | 122616 | source += mVoxelSize*diff[2]*sampleVel[2]; | |
196 | } else { | ||
197 | 295416 | diagonal -= 1; // Zero Dirichlet | |
198 | } | ||
199 | |||
200 | } | ||
201 | |||
202 | const double mVoxelSize; | ||
203 | const Vec3GridT& mVelGrid; | ||
204 | const MaskT& mDomainGrid; | ||
205 | }; // struct SolveBoundaryOp | ||
206 | |||
207 | |||
208 | } // namespace potential_flow_internal | ||
209 | |||
210 | /// @endcond | ||
211 | |||
212 | //////////////////////////////////////////////////////////////////////////// | ||
213 | |||
214 | template<typename GridT, typename MaskT> | ||
215 | typename MaskT::Ptr | ||
216 | 9 | createPotentialFlowMask(const GridT& grid, int dilation) | |
217 | { | ||
218 | using MaskTreeT = typename MaskT::TreeType; | ||
219 | |||
220 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
|
9 | if (!grid.hasUniformVoxels()) { |
221 |
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(ValueError, "Transform must have uniform voxels for Potential Flow mask."); |
222 | } | ||
223 | |||
224 | // construct a new mask grid representing the interior region | ||
225 | 8 | auto interior = interiorMask(grid); | |
226 | |||
227 | // create a new mask grid from the interior topology | ||
228 |
2/6✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
16 | typename MaskTreeT::Ptr maskTree(new MaskTreeT(interior->tree(), false, TopologyCopy())); |
229 |
2/6✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
16 | typename MaskT::Ptr mask = MaskT::create(maskTree); |
230 |
3/8✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
|
16 | mask->setTransform(grid.transform().copy()); |
231 | |||
232 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | dilateActiveValues(*maskTree, dilation, NN_FACE_EDGE); |
233 | |||
234 | // subtract the interior region from the mask to leave just the exterior narrow band | ||
235 | mask->tree().topologyDifference(interior->tree()); | ||
236 | |||
237 | 8 | return mask; | |
238 | } | ||
239 | |||
240 | |||
241 | template<typename Vec3T, typename GridT, typename MaskT> | ||
242 | 16 | typename GridT::template ValueConverter<Vec3T>::Type::Ptr createPotentialFlowNeumannVelocities( | |
243 | const GridT& collider, | ||
244 | const MaskT& domain, | ||
245 | const typename GridT::template ValueConverter<Vec3T>::Type::ConstPtr boundaryVelocity, | ||
246 | const Vec3T& backgroundVelocity) | ||
247 | { | ||
248 | using Vec3GridT = typename GridT::template ValueConverter<Vec3T>::Type; | ||
249 | using TreeT = typename Vec3GridT::TreeType; | ||
250 | using ValueT = typename TreeT::ValueType; | ||
251 | using GradientT = typename ScalarToVectorConverter<GridT>::Type; | ||
252 | |||
253 | using potential_flow_internal::ComputeNeumannVelocityOp; | ||
254 | |||
255 | // this method requires the collider to be a level set to generate the gradient | ||
256 | // use the tools::topologyToLevelset() method if you need to convert a mask into a level set | ||
257 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 8 times.
|
16 | if (collider.getGridClass() != GRID_LEVEL_SET || |
258 | !std::is_floating_point<typename GridT::TreeType::ValueType>::value) { | ||
259 |
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.
|
8 | OPENVDB_THROW(TypeError, "Potential Flow expecting the collider to be a level set."); |
260 | } | ||
261 | |||
262 | // empty grid if there are no velocities | ||
263 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
6 | if (backgroundVelocity == zeroVal<Vec3T>() && |
264 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
|
6 | (!boundaryVelocity || boundaryVelocity->empty())) { |
265 | ✗ | auto neumann = Vec3GridT::create(); | |
266 |
3/8✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
4 | neumann->setTransform(collider.transform().copy()); |
267 | return neumann; | ||
268 | } | ||
269 | |||
270 | // extract the intersection between the collider and the domain | ||
271 | using MaskTreeT = typename GridT::TreeType::template ValueConverter<ValueMask>::Type; | ||
272 |
3/6✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
|
24 | typename MaskTreeT::Ptr boundary(new MaskTreeT(domain.tree(), false, TopologyCopy())); |
273 | boundary->topologyIntersection(collider.tree()); | ||
274 | |||
275 |
3/8✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
24 | typename TreeT::Ptr neumannTree(new TreeT(*boundary, zeroVal<ValueT>(), TopologyCopy())); |
276 | neumannTree->voxelizeActiveTiles(); | ||
277 | |||
278 | // compute the gradient from the collider | ||
279 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
12 | const typename GradientT::Ptr gradient = tools::gradient(collider); |
280 | |||
281 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
24 | typename tree::LeafManager<TreeT> leafManager(*neumannTree); |
282 | |||
283 |
5/6✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 3 times.
|
12 | if (boundaryVelocity && !boundaryVelocity->empty()) { |
284 | ComputeNeumannVelocityOp<Vec3GridT, GradientT> | ||
285 | neumannOp(*gradient, *boundaryVelocity, backgroundVelocity); | ||
286 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
6 | leafManager.foreach(neumannOp, false); |
287 | } | ||
288 | else { | ||
289 | ComputeNeumannVelocityOp<Vec3GridT, GradientT> | ||
290 | neumannOp(*gradient, backgroundVelocity); | ||
291 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
6 | leafManager.foreach(neumannOp, false); |
292 | } | ||
293 | |||
294 | // prune any inactive values | ||
295 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
12 | tools::pruneInactive(*neumannTree); |
296 | |||
297 |
2/4✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
|
24 | typename Vec3GridT::Ptr neumann(Vec3GridT::create(neumannTree)); |
298 |
3/8✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
24 | neumann->setTransform(collider.transform().copy()); |
299 | |||
300 | return neumann; | ||
301 | } | ||
302 | |||
303 | |||
304 | template<typename Vec3GridT, typename MaskT, typename InterrupterT> | ||
305 | typename VectorToScalarGrid<Vec3GridT>::Ptr | ||
306 | 6 | computeScalarPotential(const MaskT& domain, const Vec3GridT& neumann, | |
307 | math::pcg::State& state, InterrupterT* interrupter) | ||
308 | { | ||
309 | using ScalarT = typename Vec3GridT::ValueType::value_type; | ||
310 | using ScalarTreeT = typename Vec3GridT::TreeType::template ValueConverter<ScalarT>::Type; | ||
311 | using ScalarGridT = typename Vec3GridT::template ValueConverter<ScalarT>::Type; | ||
312 | |||
313 | using potential_flow_internal::SolveBoundaryOp; | ||
314 | |||
315 | // create the solution tree and activate using domain topology | ||
316 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
12 | ScalarTreeT solveTree(domain.tree(), zeroVal<ScalarT>(), TopologyCopy()); |
317 | solveTree.voxelizeActiveTiles(); | ||
318 | |||
319 | 6 | util::NullInterrupter nullInterrupt; | |
320 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
6 | if (!interrupter) interrupter = &nullInterrupt; |
321 | |||
322 | // solve for scalar potential | ||
323 | SolveBoundaryOp<Vec3GridT, MaskT> solve(neumann, domain); | ||
324 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
6 | typename ScalarTreeT::Ptr potentialTree = |
325 | poisson::solveWithBoundaryConditions(solveTree, solve, state, *interrupter, true); | ||
326 | |||
327 |
2/6✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
12 | auto potential = ScalarGridT::create(potentialTree); |
328 |
3/8✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
12 | potential->setTransform(domain.transform().copy()); |
329 | |||
330 | 6 | return potential; | |
331 | } | ||
332 | |||
333 | |||
334 | template<typename Vec3GridT> | ||
335 | typename Vec3GridT::Ptr | ||
336 | 4 | computePotentialFlow(const typename VectorToScalarGrid<Vec3GridT>::Type& potential, | |
337 | const Vec3GridT& neumann, | ||
338 | const typename Vec3GridT::ValueType backgroundVelocity) | ||
339 | { | ||
340 | using Vec3T = const typename Vec3GridT::ValueType; | ||
341 | using potential_flow_internal::extractOuterVoxelMask; | ||
342 | |||
343 | // The VDB gradient op uses the background grid value, which is zero by default, when | ||
344 | // computing the gradient at the boundary. This works at the zero-dirichlet boundaries, but | ||
345 | // give spurious values at Neumann ones as the potential should be non-zero there. To avoid | ||
346 | // the extra error, we just substitute the Neumann condition on the boundaries. | ||
347 | // Technically, we should allow for some tangential velocity, coming from the gradient of | ||
348 | // potential. However, considering the voxelized nature of our solve, a decent approximation | ||
349 | // to a tangential derivative isn't probably worth our time. Any tangential component will be | ||
350 | // found in the next interior ring of voxels. | ||
351 | |||
352 | 4 | auto gradient = tools::gradient(potential); | |
353 | |||
354 | // apply Neumann values to the gradient | ||
355 | |||
356 | 20276 | auto applyNeumann = [&gradient, &neumann] ( | |
357 | const MaskGrid::TreeType::LeafNodeType& leaf, size_t) | ||
358 | { | ||
359 | typename Vec3GridT::Accessor gradientAccessor = gradient->getAccessor(); | ||
360 | typename Vec3GridT::ConstAccessor neumannAccessor = neumann.getAccessor(); | ||
361 |
4/6✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 107552 times.
✓ Branch 3 taken 5208 times.
✓ Branch 4 taken 97488 times.
✓ Branch 5 taken 4928 times.
|
215176 | for (auto it = leaf.beginValueOn(); it; ++it) { |
362 |
2/6✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 107552 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 97488 times.
✗ Branch 8 not taken.
|
205040 | const Coord ijk = it.getCoord(); |
363 | typename Vec3GridT::ValueType value; | ||
364 |
6/12✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 6 taken 107552 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 59222 times.
✓ Branch 9 taken 48330 times.
✓ Branch 11 taken 97488 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 828 times.
✓ Branch 14 taken 96660 times.
|
205040 | if (neumannAccessor.probeValue(ijk, value)) { |
365 |
2/6✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 59222 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 828 times.
✗ Branch 8 not taken.
|
60050 | gradientAccessor.setValue(ijk, value); |
366 | } | ||
367 | } | ||
368 | }; | ||
369 | |||
370 | 4 | const MaskGrid::TreeType::Ptr boundary = extractOuterVoxelMask(*gradient); | |
371 | 8 | typename tree::LeafManager<const typename MaskGrid::TreeType> leafManager(*boundary); | |
372 | 4 | leafManager.foreach(applyNeumann); | |
373 | |||
374 | // apply the background value to the gradient if supplied | ||
375 | |||
376 | if (backgroundVelocity != zeroVal<Vec3T>()) { | ||
377 | 2465 | auto applyBackgroundVelocity = [&backgroundVelocity] ( | |
378 | typename Vec3GridT::TreeType::LeafNodeType& leaf, size_t) | ||
379 | { | ||
380 |
2/6✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1131300 times.
✓ Branch 5 taken 2464 times.
|
1133764 | for (auto it = leaf.beginValueOn(); it; ++it) { |
381 | 1131300 | it.setValue(it.getValue() - backgroundVelocity); | |
382 | } | ||
383 | }; | ||
384 | |||
385 | 2 | typename tree::LeafManager<typename Vec3GridT::TreeType> leafManager2(gradient->tree()); | |
386 | 1 | leafManager2.foreach(applyBackgroundVelocity); | |
387 | } | ||
388 | |||
389 | 4 | return gradient; | |
390 | } | ||
391 | |||
392 | |||
393 | //////////////////////////////////////// | ||
394 | |||
395 | |||
396 | // Explicit Template Instantiation | ||
397 | |||
398 | #ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION | ||
399 | |||
400 | #ifdef OPENVDB_INSTANTIATE_POTENTIALFLOW | ||
401 | #include <openvdb/util/ExplicitInstantiation.h> | ||
402 | #endif | ||
403 | |||
404 | #define _FUNCTION(TreeT) \ | ||
405 | Grid<TreeT>::Ptr createPotentialFlowNeumannVelocities(const FloatGrid&, const MaskGrid&, \ | ||
406 | const Grid<TreeT>::ConstPtr, const TreeT::ValueType&) | ||
407 | OPENVDB_VEC3_TREE_INSTANTIATE(_FUNCTION) | ||
408 | #undef _FUNCTION | ||
409 | |||
410 | #define _FUNCTION(TreeT) \ | ||
411 | VectorToScalarGrid<Grid<TreeT>>::Ptr computeScalarPotential(const MaskGrid&, const Grid<TreeT>&, \ | ||
412 | math::pcg::State&, util::NullInterrupter*) | ||
413 | OPENVDB_VEC3_TREE_INSTANTIATE(_FUNCTION) | ||
414 | #undef _FUNCTION | ||
415 | |||
416 | #define _FUNCTION(TreeT) \ | ||
417 | Grid<TreeT>::Ptr computePotentialFlow( \ | ||
418 | const VectorToScalarGrid<Grid<TreeT>>::Type&, const Grid<TreeT>&, const TreeT::ValueType) | ||
419 | OPENVDB_VEC3_TREE_INSTANTIATE(_FUNCTION) | ||
420 | #undef _FUNCTION | ||
421 | |||
422 | #endif // OPENVDB_USE_EXPLICIT_INSTANTIATION | ||
423 | |||
424 | |||
425 | } // namespace tools | ||
426 | } // namespace OPENVDB_VERSION_NAME | ||
427 | } // namespace openvdb | ||
428 | |||
429 | #endif // OPENVDB_TOOLS_POTENTIAL_FLOW_HAS_BEEN_INCLUDED | ||
430 |