8#include <glm/common.hpp>
9#include <glm/geometric.hpp>
14[[nodiscard]]
float hash31(
int x,
int y,
int z, std::uint32_t
seed)
noexcept {
15 std::uint32_t
n =
static_cast<std::uint32_t
>(
x) * 374761393u +
16 static_cast<std::uint32_t
>(
y) * 668265263u +
17 static_cast<std::uint32_t
>(
z) * 2147483647u +
seed * 1013904223u;
18 n = (
n ^ (
n >> 13)) * 1274126177u;
20 return static_cast<float>(
n & 0x00FFFFFFu) /
static_cast<float>(0x01000000u);
32 if (!(
size.x > 0.f &&
size.y > 0.f &&
size.z > 0.f) || !std::isfinite(
size.x) ||
33 !std::isfinite(
size.y) || !std::isfinite(
size.z)) {
36 "bounds", {},
"graphics.fog"));
43 cellSize_ = glm::vec3(
size.x /
static_cast<float>(
width),
size.y /
static_cast<float>(
height),
45 const std::size_t
count =
static_cast<std::size_t
>(
width) *
static_cast<std::size_t
>(
height) *
46 static_cast<std::size_t
>(
depth);
47 density_.assign(
count, 0.f);
48 curl_.assign(
count, glm::vec3(0.f));
49 lightAssist_.assign(
count, glm::vec3(0.f));
55 std::fill(density_.begin(), density_.end(), 0.f);
56 std::fill(curl_.begin(), curl_.end(), glm::vec3(0.f));
57 std::fill(lightAssist_.begin(), lightAssist_.end(), glm::vec3(0.f));
61std::size_t FogDensityField::index(
int x,
int y,
int z)
const noexcept {
62 x = std::clamp(
x, 0, std::max(width_ - 1, 0));
63 y = std::clamp(
y, 0, std::max(height_ - 1, 0));
64 z = std::clamp(
z, 0, std::max(depth_ - 1, 0));
65 return (
static_cast<std::size_t
>(
z) *
static_cast<std::size_t
>(height_) +
66 static_cast<std::size_t
>(
y)) *
67 static_cast<std::size_t
>(width_) +
68 static_cast<std::size_t
>(
x);
71glm::vec3 FogDensityField::cellCenter(
int x,
int y,
int z)
const noexcept {
72 return bounds_.minimum +
73 glm::vec3((
static_cast<float>(
x) + 0.5f) * cellSize_.x,
74 (
static_cast<float>(
y) + 0.5f) * cellSize_.y,
75 (
static_cast<float>(
z) + 0.5f) * cellSize_.z);
79 if (density_.empty())
return;
80 density_[
index(
x,
y,
z)] = std::max(density, 0.f);
85 if (density_.empty())
return 0.f;
90 if (curl_.empty())
return;
96 if (curl_.empty())
return {};
101 if (lightAssist_.empty())
return;
102 lightAssist_[
index(
x,
y,
z)] = assist;
107 if (lightAssist_.empty())
return {};
108 return lightAssist_[
index(
x,
y,
z)];
111void FogDensityField::sampleLattice(
const glm::vec3&
world,
int& x0,
int& y0,
int& z0,
int& x1,
int& y1,
112 int& z1,
float& fx,
float& fy,
float& fz)
const noexcept {
113 const glm::vec3
local = (
world - bounds_.minimum) / cellSize_ - glm::vec3(0.5f);
114 const float lx = std::clamp(
local.x, 0.f,
static_cast<float>(std::max(width_ - 1, 0)));
115 const float ly = std::clamp(
local.y, 0.f,
static_cast<float>(std::max(height_ - 1, 0)));
116 const float lz = std::clamp(
local.z, 0.f,
static_cast<float>(std::max(depth_ - 1, 0)));
117 x0 =
static_cast<int>(std::floor(lx));
118 y0 =
static_cast<int>(std::floor(ly));
119 z0 =
static_cast<int>(std::floor(lz));
120 x1 = std::min(x0 + 1, width_ - 1);
121 y1 = std::min(y0 + 1, height_ - 1);
122 z1 = std::min(z0 + 1, depth_ - 1);
123 fx = lx -
static_cast<float>(x0);
124 fy = ly -
static_cast<float>(y0);
125 fz = lz -
static_cast<float>(z0);
129 if (density_.empty() || !bounds_.contains(
world)) {
131 if (density_.empty())
return 0.f;
132 const glm::vec3
c = glm::clamp(
world, bounds_.minimum, bounds_.maximum);
133 if (glm::length(
world -
c) > cellSize_.x)
return 0.f;
135 int x0, y0, z0, x1, y1, z1;
137 sampleLattice(
world, x0, y0, z0, x1, y1, z1, fx, fy, fz);
138 const auto lerp = [](
float a,
float b,
float t) {
return a + (
b -
a) *
t; };
139 const float c00 = lerp(densityAt(x0, y0, z0), densityAt(x1, y0, z0), fx);
140 const float c10 = lerp(densityAt(x0, y1, z0), densityAt(x1, y1, z0), fx);
141 const float c01 = lerp(densityAt(x0, y0, z1), densityAt(x1, y0, z1), fx);
142 const float c11 = lerp(densityAt(x0, y1, z1), densityAt(x1, y1, z1), fx);
143 return lerp(lerp(c00, c10, fy), lerp(c01, c11, fy), fz);
147 if (curl_.empty())
return {};
148 int x0, y0, z0, x1, y1, z1;
150 sampleLattice(
world, x0, y0, z0, x1, y1, z1, fx, fy, fz);
151 const auto lerp3 = [](
const glm::vec3&
a,
const glm::vec3&
b,
float t) {
return a + (
b -
a) *
t; };
152 const glm::vec3 c00 = lerp3(curlVelocityAt(x0, y0, z0), curlVelocityAt(x1, y0, z0), fx);
153 const glm::vec3 c10 = lerp3(curlVelocityAt(x0, y1, z0), curlVelocityAt(x1, y1, z0), fx);
154 const glm::vec3 c01 = lerp3(curlVelocityAt(x0, y0, z1), curlVelocityAt(x1, y0, z1), fx);
155 const glm::vec3 c11 = lerp3(curlVelocityAt(x0, y1, z1), curlVelocityAt(x1, y1, z1), fx);
156 return lerp3(lerp3(c00, c10, fy), lerp3(c01, c11, fy), fz);
160 if (lightAssist_.empty())
return {};
161 int x0, y0, z0, x1, y1, z1;
163 sampleLattice(
world, x0, y0, z0, x1, y1, z1, fx, fy, fz);
164 const auto lerp3 = [](
const glm::vec3&
a,
const glm::vec3&
b,
float t) {
return a + (
b -
a) *
t; };
165 const glm::vec3 c00 = lerp3(lightAssistAt(x0, y0, z0), lightAssistAt(x1, y0, z0), fx);
166 const glm::vec3 c10 = lerp3(lightAssistAt(x0, y1, z0), lightAssistAt(x1, y1, z0), fx);
167 const glm::vec3 c01 = lerp3(lightAssistAt(x0, y0, z1), lightAssistAt(x1, y0, z1), fx);
168 const glm::vec3 c11 = lerp3(lightAssistAt(x0, y1, z1), lightAssistAt(x1, y1, z1), fx);
169 return lerp3(lerp3(c00, c10, fy), lerp3(c01, c11, fy), fz);
173 float curlScale, std::uint32_t
seed) {
174 if (density_.empty()) {
176 "density field is not allocated",
"resize", {},
179 if (!std::isfinite(baseDensity) || baseDensity < 0.f || !std::isfinite(falloff) || falloff < 0.f ||
180 !std::isfinite(curlScale)) {
183 "seedHeightBand", {},
"graphics.fog"));
186 for (
int z = 0;
z < depth_; ++
z) {
187 for (
int y = 0;
y < height_; ++
y) {
188 for (
int x = 0;
x < width_; ++
x) {
189 const glm::vec3
p = cellCenter(
x,
y,
z);
190 const float h = std::max(
p.y - baseHeight, 0.f);
191 float d = baseDensity * std::exp(-falloff *
h);
192 const float n = 0.65f + 0.35f * hash31(
x,
y,
z,
seed);
197 const float a = hash31(
x + 17,
y,
z,
seed) * 6.2831853f;
198 const float b = hash31(
x,
y + 31,
z,
seed) * 6.2831853f;
199 const glm::vec3 curl(curlScale * std::sin(
a) * std::cos(
b),
200 curlScale * 0.35f * std::sin(
a +
b),
201 curlScale * std::cos(
a) * std::sin(
b));
212 return densityAt(
x,
y,
z) > threshold;
Stable, structured diagnostics shared by engine modules.
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.
const FogWorldBounds & bounds() const noexcept
float densityAt(int x, int y, int z) const noexcept
Density at.
bool isOccupied(int x, int y, int z, float threshold=1e-4f) const noexcept
Occupancy bit: true when density exceeds the threshold.
std::span< float > densitySpan() noexcept
Borrow density storage for MAC coupling (same resolution).
glm::vec3 sampleLightAssist(const glm::vec3 &world) const noexcept
Trilinear world-space light-assist sample.
glm::vec3 curlVelocityAt(int x, int y, int z) const noexcept
Curl velocity at.
glm::vec3 sampleCurlVelocity(const glm::vec3 &world) const noexcept
Trilinear world-space curl velocity sample.
void clear()
Clear density / curl / light-assist bands and bump revision.
glm::vec3 lightAssistAt(int x, int y, int z) const noexcept
Light assist at.
Result< void > seedHeightBand(float baseDensity, float baseHeight, float falloff, float curlScale, std::uint32_t seed)
Seed a height-banded density layer with optional curl noise.
int depth() const noexcept
Result< void > resize(int width, int height, int depth, const FogWorldBounds &bounds)
Allocate a regular lattice over world bounds.
void setDensity(int x, int y, int z, float density)
Write cell-centered density; indices are clamped.
void setCurlVelocity(int x, int y, int z, const glm::vec3 &velocity)
Write curl-assist velocity (m/s) at a cell center.
int height() const noexcept
int width() const noexcept
float sampleDensity(const glm::vec3 &world) const noexcept
Trilinear world-space density sample.
void setLightAssist(int x, int y, int z, const glm::vec3 &assist)
Accumulated lighting assist (pre-multiplied irradiance hint).
Axis-aligned world bounds for a fog simulation domain.