9#include <glm/common.hpp>
10#include <glm/geometric.hpp>
11#include <glm/vec3.hpp>
16[[nodiscard]]
bool finiteVec(
const glm::vec3&
v)
noexcept {
17 return std::isfinite(
v.x) && std::isfinite(
v.y) && std::isfinite(
v.z);
20[[nodiscard]] glm::vec3 safeNormalize(
const glm::vec3&
v,
const glm::vec3& fallback)
noexcept {
21 const float len2 = glm::dot(
v,
v);
22 if (len2 < 1e-12f)
return fallback;
23 return v * (1.f / std::sqrt(len2));
29 for (std::size_t i = 0; i < proxies.size(); ++i) {
30 const auto&
p = proxies[i];
31 if (!finiteVec(
p.position) || !finiteVec(
p.velocity) || !finiteVec(
p.extents) ||
32 !finiteVec(
p.axis) || !finiteVec(
p.axisX) || !finiteVec(
p.axisY) || !finiteVec(
p.axisZ)) {
37 if (
p.extents.x <= 0.f ||
p.extents.y <= 0.f ||
p.extents.z <= 0.f) {
42 if (!std::isfinite(
p.drag) ||
p.drag < 0.f ||
p.drag > 1.f || !std::isfinite(
p.wakeStrength) ||
43 p.wakeStrength < 0.f) {
46 "drag", {},
"graphics.fog"));
49 proxies_ = std::move(proxies);
54 return proxies_.at(
index);
57float FogInteractor::signedDistance(
const FogSolidProxy& proxy,
const glm::vec3&
world)
const noexcept {
58 switch (proxy.shape) {
60 return glm::length(
world - proxy.position) - proxy.extents.x;
63 const glm::vec3
a = proxy.position - proxy.axis;
64 const glm::vec3
b = proxy.position + proxy.axis;
65 const glm::vec3 pa =
world -
a;
66 const glm::vec3 ba =
b -
a;
67 const float h = std::clamp(glm::dot(pa, ba) / std::max(glm::dot(ba, ba), 1e-8f), 0.f, 1.f);
68 return glm::length(pa - ba *
h) - proxy.extents.x;
71 const glm::vec3
d =
world - proxy.position;
72 const glm::vec3
local(glm::dot(
d, proxy.axisX), glm::dot(
d, proxy.axisY),
73 glm::dot(
d, proxy.axisZ));
74 const glm::vec3
q = glm::abs(
local) - proxy.extents;
75 return glm::length(glm::max(
q, glm::vec3(0.f))) +
76 std::min(std::max(
q.x, std::max(
q.y,
q.z)), 0.f);
82glm::vec3 FogInteractor::closestPoint(
const FogSolidProxy& proxy,
const glm::vec3&
world)
const noexcept {
83 const float sd = signedDistance(proxy,
world);
85 const glm::vec3
n = surfaceNormal(proxy,
world);
88 const glm::vec3
n = surfaceNormal(proxy,
world);
92glm::vec3 FogInteractor::surfaceNormal(
const FogSolidProxy& proxy,
const glm::vec3&
world)
const noexcept {
93 constexpr float e = 1e-3f;
94 const float dx = signedDistance(proxy,
world + glm::vec3(e, 0.f, 0.f)) -
95 signedDistance(proxy,
world - glm::vec3(e, 0.f, 0.f));
96 const float dy = signedDistance(proxy,
world + glm::vec3(0.f, e, 0.f)) -
97 signedDistance(proxy,
world - glm::vec3(0.f, e, 0.f));
98 const float dz = signedDistance(proxy,
world + glm::vec3(0.f, 0.f, e)) -
99 signedDistance(proxy,
world - glm::vec3(0.f, 0.f, e));
100 return safeNormalize(glm::vec3(
dx,
dy,
dz), glm::vec3(0.f, 1.f, 0.f));
104 for (
const auto&
p : proxies_) {
105 if (!
p.enabled)
continue;
106 if (signedDistance(
p,
world) <= 0.f)
return true;
112 if (!std::isfinite(dt) || dt < 0.f) {
116 if (grid.width() <= 0) {
118 "grid", {},
"graphics.fog"));
122 const glm::vec3
cell = grid.cellSize_;
124 std::fill(grid.solid_.begin(), grid.solid_.end(), 0);
126 for (
int z = 0;
z < grid.depth_; ++
z) {
127 for (
int y = 0;
y < grid.height_; ++
y) {
128 for (
int x = 0;
x < grid.width_; ++
x) {
129 const glm::vec3
world =
131 (
z + 0.5f) *
cell.z);
133 grid.solid_[grid.cellIndex(
x,
y,
z)] = 1;
134 grid.setDensity(
x,
y,
z, 0.f);
136 for (
const auto& proxy : proxies_) {
137 if (!proxy.enabled)
continue;
138 if (signedDistance(proxy,
world) >
cell.x)
continue;
139 const glm::vec3
n = surfaceNormal(proxy,
world);
140 const float vn = glm::dot(proxy.velocity,
n);
142 const glm::vec3 tangential = proxy.velocity -
n * vn;
143 const glm::vec3 wake =
144 n * (vn * proxy.wakeStrength) + tangential * (1.f - proxy.drag);
146 if (
x + 1 < grid.width_)
147 grid.u_[grid.uIndex(
x + 1,
y,
z)] += wake.x * dt;
148 if (
x > 0) grid.u_[grid.uIndex(
x,
y,
z)] += wake.x * dt;
149 if (
y + 1 < grid.height_)
150 grid.v_[grid.vIndex(
x,
y + 1,
z)] += wake.y * dt;
151 if (
y > 0) grid.v_[grid.vIndex(
x,
y,
z)] += wake.y * dt;
152 if (
z + 1 < grid.depth_)
153 grid.w_[grid.wIndex(
x,
y,
z + 1)] += wake.z * dt;
154 if (
z > 0) grid.w_[grid.wIndex(
x,
y,
z)] += wake.z * dt;
161 for (
int z = 0;
z < grid.depth_; ++
z) {
162 for (
int y = 0;
y < grid.height_; ++
y) {
163 for (
int i = 0; i <= grid.width_; ++i) {
164 const bool leftSolid = i > 0 && grid.solid_[grid.cellIndex(i - 1,
y,
z)];
165 const bool rightSolid = i < grid.width_ && grid.solid_[grid.cellIndex(i,
y,
z)];
166 if (leftSolid || rightSolid) grid.u_[grid.uIndex(i,
y,
z)] = 0.f;
170 for (
int z = 0;
z < grid.depth_; ++
z) {
171 for (
int j = 0; j <= grid.height_; ++j) {
172 for (
int x = 0;
x < grid.width_; ++
x) {
173 const bool downSolid = j > 0 && grid.solid_[grid.cellIndex(
x, j - 1,
z)];
174 const bool upSolid = j < grid.height_ && grid.solid_[grid.cellIndex(
x, j,
z)];
175 if (downSolid || upSolid) grid.v_[grid.vIndex(
x, j,
z)] = 0.f;
179 for (
int k = 0; k <= grid.depth_; ++k) {
180 for (
int y = 0;
y < grid.height_; ++
y) {
181 for (
int x = 0;
x < grid.width_; ++
x) {
182 const bool backSolid = k > 0 && grid.solid_[grid.cellIndex(
x,
y, k - 1)];
183 const bool frontSolid = k < grid.depth_ && grid.solid_[grid.cellIndex(
x,
y, k)];
184 if (backSolid || frontSolid) grid.w_[grid.wIndex(
x,
y, k)] = 0.f;
193 const glm::vec3& cellSize)
const noexcept {
194 if (proxies_.empty())
return startLocal + deltaLocal;
196 const float len = glm::length(deltaLocal);
197 if (len < 1e-8f)
return startLocal;
199 const int steps = std::max(1,
static_cast<int>(std::ceil(len * 2.f)));
200 const glm::vec3
step = deltaLocal /
static_cast<float>(
steps);
201 glm::vec3
p = startLocal;
202 for (
int i = 0; i <
steps; ++i) {
203 const glm::vec3 next =
p +
step;
204 const glm::vec3
world =
205 bounds.minimum + glm::vec3((next.x + 0.5f) * cellSize.x, (next.y + 0.5f) * cellSize.y,
206 (next.z + 0.5f) * cellSize.z);
207 if (isSolidWorld(
world))
return p;
Stable, structured diagnostics shared by engine modules.
std::array< double, 10 > q
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.
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...
const FogSolidProxy & proxyAt(std::size_t index) const
Result< void > setProxies(std::vector< FogSolidProxy > proxies)
Replace the proxy set atomically after validation.
bool isSolidWorld(const glm::vec3 &world) const noexcept
True when a world point lies inside any enabled solid.
glm::vec3 clipAdvection(const glm::vec3 &startLocal, const glm::vec3 &deltaLocal, const FogWorldBounds &bounds, const glm::vec3 &cellSize) const noexcept
Clip a local-cell advection displacement against thin solids.
MAC (Marker-And-Cell) staggered-grid Eulerian fog fluid.
One analytic solid that carves / wakes the fog domain.
Axis-aligned world bounds for a fog simulation domain.