载入中...
搜索中...
未找到
MacFluidGrid.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
5
6#include <algorithm>
7#include <cmath>
8
9#include <glm/common.hpp>
10#include <glm/geometric.hpp>
11
12namespace eve::graphics::fog {
13
15 if (field.width() < 2 || field.height() < 2 || field.depth() < 2) {
17 DiagnosticCode::InvalidArgument, "density field must be allocated before MAC configure",
18 "field", {}, "graphics.fog"));
19 }
20 if (!std::isfinite(fixedDt) || fixedDt <= 0.f || fixedDt > 0.1f) {
22 DiagnosticCode::InvalidArgument, "fixedDt must be in (0, 0.1]", "fixedDt", {},
23 "graphics.fog"));
24 }
25
26 width_ = field.width();
27 height_ = field.height();
28 depth_ = field.depth();
29 bounds_ = field.bounds();
30 cellSize_ = field.cellSize();
31 fixedDt_ = fixedDt;
32 accumulator_ = 0.f;
33 stepCount_ = 0;
34
35 const std::size_t cells = static_cast<std::size_t>(width_) * height_ * depth_;
36 density_.assign(cells, 0.f);
37 pressure_.assign(cells, 0.f);
38 divergence_.assign(cells, 0.f);
39 tmpDensity_.assign(cells, 0.f);
40 solid_.assign(cells, 0);
41 u_.assign(static_cast<std::size_t>(width_ + 1) * height_ * depth_, 0.f);
42 v_.assign(static_cast<std::size_t>(width_) * (height_ + 1) * depth_, 0.f);
43 w_.assign(static_cast<std::size_t>(width_) * height_ * (depth_ + 1), 0.f);
44 tmpU_ = u_;
45 tmpV_ = v_;
46 tmpW_ = w_;
47 return Result<void>::success();
48}
49
50std::size_t MacFluidGrid::cellIndex(int x, int y, int z) const noexcept {
51 return (static_cast<std::size_t>(z) * height_ + y) * width_ + x;
52}
53std::size_t MacFluidGrid::uIndex(int i, int y, int z) const noexcept {
54 return (static_cast<std::size_t>(z) * height_ + y) * (width_ + 1) + i;
55}
56std::size_t MacFluidGrid::vIndex(int x, int j, int z) const noexcept {
57 return (static_cast<std::size_t>(z) * (height_ + 1) + j) * width_ + x;
58}
59std::size_t MacFluidGrid::wIndex(int x, int y, int k) const noexcept {
60 return (static_cast<std::size_t>(k) * height_ + y) * width_ + x;
61}
62
64 if (field.width() != width_ || field.height() != height_ || field.depth() != depth_) {
66 DiagnosticCode::Conflict, "density field resolution mismatch", "pullDensity", {},
67 "graphics.fog"));
68 }
69 const auto src = field.densitySpan();
70 std::copy(src.begin(), src.end(), density_.begin());
71 return Result<void>::success();
72}
73
75 if (field.width() != width_ || field.height() != height_ || field.depth() != depth_) {
77 DiagnosticCode::Conflict, "density field resolution mismatch", "pushDensity", {},
78 "graphics.fog"));
79 }
80 for (int z = 0; z < depth_; ++z)
81 for (int y = 0; y < height_; ++y)
82 for (int x = 0; x < width_; ++x) field.setDensity(x, y, z, densityAt(x, y, z));
83 return Result<void>::success();
84}
85
86float MacFluidGrid::densityAt(int x, int y, int z) const noexcept {
87 if (density_.empty()) return 0.f;
88 x = std::clamp(x, 0, width_ - 1);
89 y = std::clamp(y, 0, height_ - 1);
90 z = std::clamp(z, 0, depth_ - 1);
91 return density_[cellIndex(x, y, z)];
92}
93
94void MacFluidGrid::setDensity(int x, int y, int z, float density) {
95 if (density_.empty()) return;
96 x = std::clamp(x, 0, width_ - 1);
97 y = std::clamp(y, 0, height_ - 1);
98 z = std::clamp(z, 0, depth_ - 1);
99 density_[cellIndex(x, y, z)] = std::max(density, 0.f);
100}
101
102float MacFluidGrid::uAt(int i, int y, int z) const noexcept {
103 i = std::clamp(i, 0, width_);
104 y = std::clamp(y, 0, height_ - 1);
105 z = std::clamp(z, 0, depth_ - 1);
106 return u_[uIndex(i, y, z)];
107}
108float MacFluidGrid::vAt(int x, int j, int z) const noexcept {
109 x = std::clamp(x, 0, width_ - 1);
110 j = std::clamp(j, 0, height_);
111 z = std::clamp(z, 0, depth_ - 1);
112 return v_[vIndex(x, j, z)];
113}
114float MacFluidGrid::wAt(int x, int y, int k) const noexcept {
115 x = std::clamp(x, 0, width_ - 1);
116 y = std::clamp(y, 0, height_ - 1);
117 k = std::clamp(k, 0, depth_);
118 return w_[wIndex(x, y, k)];
119}
120
121glm::vec3 MacFluidGrid::velocityAtCell(int x, int y, int z) const noexcept {
122 return {0.5f * (uAt(x, y, z) + uAt(x + 1, y, z)), 0.5f * (vAt(x, y, z) + vAt(x, y + 1, z)),
123 0.5f * (wAt(x, y, z) + wAt(x, y, z + 1))};
124}
125
127 FogCflReport report;
128 report.cellSize = std::min({cellSize_.x, cellSize_.y, cellSize_.z});
129 report.dt = fixedDt_;
130 float maxSpeed = 0.f;
131 for (float s : u_) maxSpeed = std::max(maxSpeed, std::fabs(s));
132 for (float s : v_) maxSpeed = std::max(maxSpeed, std::fabs(s));
133 for (float s : w_) maxSpeed = std::max(maxSpeed, std::fabs(s));
134 report.maxSpeed = maxSpeed;
135 report.cfl = report.cellSize > 0.f ? maxSpeed * fixedDt_ / report.cellSize : 0.f;
136 report.stable = report.cfl <= 1.f;
137 return report;
138}
139
140glm::vec3 MacFluidGrid::sampleVelocity(const glm::vec3& localCell) const noexcept {
141 const float x = std::clamp(localCell.x, 0.f, static_cast<float>(width_ - 1));
142 const float y = std::clamp(localCell.y, 0.f, static_cast<float>(height_ - 1));
143 const float z = std::clamp(localCell.z, 0.f, static_cast<float>(depth_ - 1));
144 const int x0 = static_cast<int>(std::floor(x));
145 const int y0 = static_cast<int>(std::floor(y));
146 const int z0 = static_cast<int>(std::floor(z));
147 return velocityAtCell(x0, y0, z0);
148}
149
150float MacFluidGrid::sampleDensityLocal(const glm::vec3& localCell) const noexcept {
151 const float x = std::clamp(localCell.x, 0.f, static_cast<float>(width_ - 1));
152 const float y = std::clamp(localCell.y, 0.f, static_cast<float>(height_ - 1));
153 const float z = std::clamp(localCell.z, 0.f, static_cast<float>(depth_ - 1));
154 const int x0 = static_cast<int>(std::floor(x));
155 const int y0 = static_cast<int>(std::floor(y));
156 const int z0 = static_cast<int>(std::floor(z));
157 const int x1 = std::min(x0 + 1, width_ - 1);
158 const int y1 = std::min(y0 + 1, height_ - 1);
159 const int z1 = std::min(z0 + 1, depth_ - 1);
160 const float fx = x - static_cast<float>(x0);
161 const float fy = y - static_cast<float>(y0);
162 const float fz = z - static_cast<float>(z0);
163 auto lerp = [](float a, float b, float t) { return a + (b - a) * t; };
164 const float c00 = lerp(densityAt(x0, y0, z0), densityAt(x1, y0, z0), fx);
165 const float c10 = lerp(densityAt(x0, y1, z0), densityAt(x1, y1, z0), fx);
166 const float c01 = lerp(densityAt(x0, y0, z1), densityAt(x1, y0, z1), fx);
167 const float c11 = lerp(densityAt(x0, y1, z1), densityAt(x1, y1, z1), fx);
168 return lerp(lerp(c00, c10, fy), lerp(c01, c11, fy), fz);
169}
170
171glm::vec3 MacFluidGrid::traceBounded(const glm::vec3& start, const glm::vec3& delta,
172 const FogInteractor* interactor) const noexcept {
173 if (interactor) return interactor->clipAdvection(start, delta, bounds_, cellSize_);
174 return start + delta;
175}
176
177void MacFluidGrid::applyForcesAndWind(const SceneWind& wind, float dt, float simTime) {
178 for (int z = 0; z < depth_; ++z) {
179 for (int y = 0; y < height_; ++y) {
180 for (int i = 0; i <= width_; ++i) {
181 if (solid_.size() && ((i > 0 && solid_[cellIndex(i - 1, y, z)]) ||
182 (i < width_ && solid_[cellIndex(i, y, z)])))
183 continue;
184 const glm::vec3 world =
185 bounds_.minimum + glm::vec3(static_cast<float>(i) * cellSize_.x,
186 (y + 0.5f) * cellSize_.y, (z + 0.5f) * cellSize_.z);
187 u_[uIndex(i, y, z)] += wind.sample(world, simTime).x * 0.35f * dt;
188 }
189 }
190 }
191 for (int z = 0; z < depth_; ++z) {
192 for (int j = 0; j <= height_; ++j) {
193 for (int x = 0; x < width_; ++x) {
194 if (solid_.size() && ((j > 0 && solid_[cellIndex(x, j - 1, z)]) ||
195 (j < height_ && solid_[cellIndex(x, j, z)])))
196 continue;
197 const glm::vec3 world =
198 bounds_.minimum + glm::vec3((x + 0.5f) * cellSize_.x,
199 static_cast<float>(j) * cellSize_.y,
200 (z + 0.5f) * cellSize_.z);
201 v_[vIndex(x, j, z)] += wind.sample(world, simTime).y * 0.35f * dt;
202 }
203 }
204 }
205 for (int k = 0; k <= depth_; ++k) {
206 for (int y = 0; y < height_; ++y) {
207 for (int x = 0; x < width_; ++x) {
208 if (solid_.size() && ((k > 0 && solid_[cellIndex(x, y, k - 1)]) ||
209 (k < depth_ && solid_[cellIndex(x, y, k)])))
210 continue;
211 const glm::vec3 world =
212 bounds_.minimum + glm::vec3((x + 0.5f) * cellSize_.x, (y + 0.5f) * cellSize_.y,
213 static_cast<float>(k) * cellSize_.z);
214 w_[wIndex(x, y, k)] += wind.sample(world, simTime).z * 0.35f * dt;
215 }
216 }
217 }
218}
219
220void MacFluidGrid::advectVelocity(float dt) {
221 tmpU_ = u_;
222 tmpV_ = v_;
223 tmpW_ = w_;
224 const glm::vec3 invCell(1.f / cellSize_.x, 1.f / cellSize_.y, 1.f / cellSize_.z);
225
226 for (int z = 0; z < depth_; ++z) {
227 for (int y = 0; y < height_; ++y) {
228 for (int i = 1; i < width_; ++i) {
229 const glm::vec3 pos(static_cast<float>(i) - 0.5f, static_cast<float>(y),
230 static_cast<float>(z));
231 const glm::vec3 vel = sampleVelocity(pos);
232 const glm::vec3 back =
233 pos - glm::vec3(vel.x * invCell.x, vel.y * invCell.y, vel.z * invCell.z) * dt;
234 const glm::vec3 clamped(std::clamp(back.x, 0.f, static_cast<float>(width_ - 1)),
235 std::clamp(back.y, 0.f, static_cast<float>(height_ - 1)),
236 std::clamp(back.z, 0.f, static_cast<float>(depth_ - 1)));
237 tmpU_[uIndex(i, y, z)] = sampleVelocity(clamped).x;
238 }
239 }
240 }
241 for (int z = 0; z < depth_; ++z) {
242 for (int j = 1; j < height_; ++j) {
243 for (int x = 0; x < width_; ++x) {
244 const glm::vec3 pos(static_cast<float>(x), static_cast<float>(j) - 0.5f,
245 static_cast<float>(z));
246 const glm::vec3 vel = sampleVelocity(pos);
247 const glm::vec3 back =
248 pos - glm::vec3(vel.x * invCell.x, vel.y * invCell.y, vel.z * invCell.z) * dt;
249 const glm::vec3 clamped(std::clamp(back.x, 0.f, static_cast<float>(width_ - 1)),
250 std::clamp(back.y, 0.f, static_cast<float>(height_ - 1)),
251 std::clamp(back.z, 0.f, static_cast<float>(depth_ - 1)));
252 tmpV_[vIndex(x, j, z)] = sampleVelocity(clamped).y;
253 }
254 }
255 }
256 for (int k = 1; k < depth_; ++k) {
257 for (int y = 0; y < height_; ++y) {
258 for (int x = 0; x < width_; ++x) {
259 const glm::vec3 pos(static_cast<float>(x), static_cast<float>(y),
260 static_cast<float>(k) - 0.5f);
261 const glm::vec3 vel = sampleVelocity(pos);
262 const glm::vec3 back =
263 pos - glm::vec3(vel.x * invCell.x, vel.y * invCell.y, vel.z * invCell.z) * dt;
264 const glm::vec3 clamped(std::clamp(back.x, 0.f, static_cast<float>(width_ - 1)),
265 std::clamp(back.y, 0.f, static_cast<float>(height_ - 1)),
266 std::clamp(back.z, 0.f, static_cast<float>(depth_ - 1)));
267 tmpW_[wIndex(x, y, k)] = sampleVelocity(clamped).z;
268 }
269 }
270 }
271 u_.swap(tmpU_);
272 v_.swap(tmpV_);
273 w_.swap(tmpW_);
274}
275
276void MacFluidGrid::projectPressure() {
277 // Gauss–Seidel projection toward near-incompressible flow.
278 for (int z = 0; z < depth_; ++z) {
279 for (int y = 0; y < height_; ++y) {
280 for (int x = 0; x < width_; ++x) {
281 if (solid_[cellIndex(x, y, z)]) {
282 divergence_[cellIndex(x, y, z)] = 0.f;
283 continue;
284 }
285 const float du = (uAt(x + 1, y, z) - uAt(x, y, z)) / cellSize_.x;
286 const float dv = (vAt(x, y + 1, z) - vAt(x, y, z)) / cellSize_.y;
287 const float dw = (wAt(x, y, z + 1) - wAt(x, y, z)) / cellSize_.z;
288 divergence_[cellIndex(x, y, z)] = du + dv + dw;
289 pressure_[cellIndex(x, y, z)] = 0.f;
290 }
291 }
292 }
293
294 constexpr int kIters = 40;
295 for (int iter = 0; iter < kIters; ++iter) {
296 for (int z = 0; z < depth_; ++z) {
297 for (int y = 0; y < height_; ++y) {
298 for (int x = 0; x < width_; ++x) {
299 if (solid_[cellIndex(x, y, z)]) continue;
300 const float pL = x > 0 ? pressure_[cellIndex(x - 1, y, z)] : pressure_[cellIndex(x, y, z)];
301 const float pR =
302 x + 1 < width_ ? pressure_[cellIndex(x + 1, y, z)] : pressure_[cellIndex(x, y, z)];
303 const float pD = y > 0 ? pressure_[cellIndex(x, y - 1, z)] : pressure_[cellIndex(x, y, z)];
304 const float pU =
305 y + 1 < height_ ? pressure_[cellIndex(x, y + 1, z)] : pressure_[cellIndex(x, y, z)];
306 const float pB = z > 0 ? pressure_[cellIndex(x, y, z - 1)] : pressure_[cellIndex(x, y, z)];
307 const float pF =
308 z + 1 < depth_ ? pressure_[cellIndex(x, y, z + 1)] : pressure_[cellIndex(x, y, z)];
309 pressure_[cellIndex(x, y, z)] =
310 (pL + pR + pD + pU + pB + pF - divergence_[cellIndex(x, y, z)] * cellSize_.x *
311 cellSize_.x) /
312 6.f;
313 }
314 }
315 }
316 }
317
318 for (int z = 0; z < depth_; ++z) {
319 for (int y = 0; y < height_; ++y) {
320 for (int i = 1; i < width_; ++i) {
321 const float grad =
322 (pressure_[cellIndex(i, y, z)] - pressure_[cellIndex(i - 1, y, z)]) / cellSize_.x;
323 u_[uIndex(i, y, z)] -= grad;
324 }
325 }
326 }
327 for (int z = 0; z < depth_; ++z) {
328 for (int j = 1; j < height_; ++j) {
329 for (int x = 0; x < width_; ++x) {
330 const float grad =
331 (pressure_[cellIndex(x, j, z)] - pressure_[cellIndex(x, j - 1, z)]) / cellSize_.y;
332 v_[vIndex(x, j, z)] -= grad;
333 }
334 }
335 }
336 for (int k = 1; k < depth_; ++k) {
337 for (int y = 0; y < height_; ++y) {
338 for (int x = 0; x < width_; ++x) {
339 const float grad =
340 (pressure_[cellIndex(x, y, k)] - pressure_[cellIndex(x, y, k - 1)]) / cellSize_.z;
341 w_[wIndex(x, y, k)] -= grad;
342 }
343 }
344 }
345}
346
347void MacFluidGrid::advectDensity(float dt, const FogInteractor* interactor) {
348 const glm::vec3 invCell(1.f / cellSize_.x, 1.f / cellSize_.y, 1.f / cellSize_.z);
349 for (int z = 0; z < depth_; ++z) {
350 for (int y = 0; y < height_; ++y) {
351 for (int x = 0; x < width_; ++x) {
352 if (solid_[cellIndex(x, y, z)]) {
353 tmpDensity_[cellIndex(x, y, z)] = 0.f;
354 continue;
355 }
356 const glm::vec3 pos(static_cast<float>(x), static_cast<float>(y),
357 static_cast<float>(z));
358 const glm::vec3 vel = velocityAtCell(x, y, z);
359 const glm::vec3 delta =
360 -glm::vec3(vel.x * invCell.x, vel.y * invCell.y, vel.z * invCell.z) * dt;
361 const glm::vec3 back = traceBounded(pos, delta, interactor);
362 const glm::vec3 clamped(std::clamp(back.x, 0.f, static_cast<float>(width_ - 1)),
363 std::clamp(back.y, 0.f, static_cast<float>(height_ - 1)),
364 std::clamp(back.z, 0.f, static_cast<float>(depth_ - 1)));
365 tmpDensity_[cellIndex(x, y, z)] = sampleDensityLocal(clamped);
366 }
367 }
368 }
369 density_.swap(tmpDensity_);
370}
371
373 float simTime) {
374 if (width_ <= 0) {
376 DiagnosticCode::Failed, "MAC grid is not configured", "step", {}, "graphics.fog"));
377 }
378 if (!std::isfinite(dt) || dt < 0.f) {
380 DiagnosticCode::InvalidArgument, "dt must be finite and >= 0", "dt", {}, "graphics.fog"));
381 }
382
383 auto windTick = wind.tick(dt);
384 if (!windTick.ok()) return Result<FogCflReport>::failure(windTick.status());
385
386 accumulator_ = std::min(accumulator_ + dt, kMaxAccumulated);
387 float time = simTime;
388 while (accumulator_ + 1e-8f >= fixedDt_) {
389 if (interactor) {
390 auto applied = interactor->applyToGrid(*this, fixedDt_);
391 if (!applied.ok()) return Result<FogCflReport>::failure(applied.status());
392 }
393 applyForcesAndWind(wind, fixedDt_, time);
394 advectVelocity(fixedDt_);
395 projectPressure();
396 advectDensity(fixedDt_, interactor);
397 accumulator_ -= fixedDt_;
398 time += fixedDt_;
399 ++stepCount_;
400 }
402}
403
404} // namespace eve::graphics::fog
Duration start
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
const std::string & s
Stable, structured diagnostics shared by engine modules.
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
World3D * world
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
float t
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
World-space density bands, curl assist velocity, and lighting helpers.
Analytic solid proxies that carve fog and inject wake into the MAC field.
Result< void > applyToGrid(MacFluidGrid &grid, float dt) const
Rasterize solids into the MAC solid mask, clear interior density, and inject wake / drag into face ve...
Result< void > configure(const FogDensityField &field, float fixedDt=kDefaultFixedDt)
Allocate MAC arrays matching a density field lattice.
void setDensity(int x, int y, int z, float density)
glm::vec3 velocityAtCell(int x, int y, int z) const noexcept
Interpolated cell-center velocity.
float fixedDt() const noexcept
float densityAt(int x, int y, int z) const noexcept
Cell-centered concentration.
Result< void > pullDensity(const FogDensityField &field)
Copy concentration from a density field (same resolution).
static constexpr float kMaxAccumulated
float wAt(int x, int y, int k) const noexcept
float vAt(int x, int j, int z) const noexcept
FogCflReport diagnoseCfl() const noexcept
Diagnose CFL for the current velocity field and fixed dt.
float uAt(int i, int y, int z) const noexcept
Face-centered velocity samples (m/s).
Result< void > pushDensity(FogDensityField &field) const
Write concentration back into a density field.
Result< FogCflReport > step(float dt, SceneWind &wind, FogInteractor *interactor, float simTime)
Advance simulation by wall-clock dt using fixed substeps.
World-space wind provider for MAC fog transport.
Definition SceneWind.h:18
Result< void > tick(float dt)
Advance the limited-response filters by dt seconds.
Definition SceneWind.cpp:76
CFL stability snapshot for the MAC fluid solver.
Definition FogTypes.h:130