9#include <glm/common.hpp>
10#include <glm/geometric.hpp>
15 if (field.width() < 2 || field.height() < 2 || field.depth() < 2) {
18 "field", {},
"graphics.fog"));
20 if (!std::isfinite(
fixedDt) || fixedDt <= 0.f || fixedDt > 0.1f) {
26 width_ = field.width();
27 height_ = field.height();
28 depth_ = field.depth();
29 bounds_ = field.bounds();
30 cellSize_ = field.cellSize();
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);
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;
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;
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;
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;
64 if (field.width() != width_ || field.height() != height_ || field.depth() != depth_) {
69 const auto src = field.densitySpan();
70 std::copy(src.begin(), src.end(), density_.begin());
75 if (field.width() != width_ || field.height() != height_ || field.depth() != depth_) {
80 for (
int z = 0;
z < depth_; ++
z)
81 for (
int y = 0;
y < height_; ++
y)
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)];
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);
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)];
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)];
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)];
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))};
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));
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);
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);
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;
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)])))
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;
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)])))
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;
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)])))
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;
220void MacFluidGrid::advectVelocity(
float dt) {
224 const glm::vec3 invCell(1.f / cellSize_.x, 1.f / cellSize_.y, 1.f / cellSize_.z);
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;
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;
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;
276void MacFluidGrid::projectPressure() {
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;
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;
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)];
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)];
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)];
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 *
318 for (
int z = 0;
z < depth_; ++
z) {
319 for (
int y = 0;
y < height_; ++
y) {
320 for (
int i = 1; i < width_; ++i) {
322 (pressure_[cellIndex(i,
y,
z)] - pressure_[cellIndex(i - 1,
y,
z)]) / cellSize_.x;
323 u_[uIndex(i,
y,
z)] -= grad;
327 for (
int z = 0;
z < depth_; ++
z) {
328 for (
int j = 1; j < height_; ++j) {
329 for (
int x = 0;
x < width_; ++
x) {
331 (pressure_[cellIndex(
x, j,
z)] - pressure_[cellIndex(
x, j - 1,
z)]) / cellSize_.y;
332 v_[vIndex(
x, j,
z)] -= grad;
336 for (
int k = 1; k < depth_; ++k) {
337 for (
int y = 0;
y < height_; ++
y) {
338 for (
int x = 0;
x < width_; ++
x) {
340 (pressure_[cellIndex(
x,
y, k)] - pressure_[cellIndex(
x,
y, k - 1)]) / cellSize_.z;
341 w_[wIndex(
x,
y, k)] -= grad;
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;
356 const glm::vec3
pos(
static_cast<float>(
x),
static_cast<float>(
y),
357 static_cast<float>(
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);
369 density_.swap(tmpDensity_);
378 if (!std::isfinite(dt) || dt < 0.f) {
383 auto windTick = wind.
tick(dt);
387 float time = simTime;
388 while (accumulator_ + 1e-8f >= fixedDt_) {
390 auto applied = interactor->
applyToGrid(*
this, fixedDt_);
393 applyForcesAndWind(wind, fixedDt_,
time);
394 advectVelocity(fixedDt_);
396 advectDensity(fixedDt_, interactor);
397 accumulator_ -= fixedDt_;
Stable, structured diagnostics shared by engine modules.
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
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.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
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.
Result< void > tick(float dt)
Advance the limited-response filters by dt seconds.
CFL stability snapshot for the MAC fluid solver.