16 const size_t n =
static_cast<size_t>(res) *
static_cast<size_t>(res);
18 normals.assign(
n, glm::vec3(0.f, 1.f, 0.f));
26int SurfaceField::index(
int x,
int z)
const {
30bool SurfaceField::inBounds(
int x,
int z)
const {
35 if (
height.empty())
return 0.f;
37 const float fx =
uv.x *
static_cast<float>(
resolution - 1);
38 const float fz =
uv.y *
static_cast<float>(
resolution - 1);
39 const int x0 = std::clamp(
static_cast<int>(std::floor(fx)), 0,
resolution - 1);
40 const int z0 = std::clamp(
static_cast<int>(std::floor(fz)), 0,
resolution - 1);
41 const int x1 = std::min(x0 + 1,
resolution - 1);
42 const int z1 = std::min(z0 + 1,
resolution - 1);
43 const float tx = fx -
static_cast<float>(x0);
44 const float tz = fz -
static_cast<float>(z0);
45 const float h00 =
height[
static_cast<size_t>(
index(x0, z0))];
46 const float h10 =
height[
static_cast<size_t>(
index(x1, z0))];
47 const float h01 =
height[
static_cast<size_t>(
index(x0, z1))];
48 const float h11 =
height[
static_cast<size_t>(
index(x1, z1))];
49 return glm::mix(glm::mix(h00, h10, tx), glm::mix(h01, h11, tx), tz);
53 if (
normals.empty())
return glm::vec3(0.f, 1.f, 0.f);
60glm::vec4 SurfaceField::sampleLifeTexel(
float u,
float v)
const {
61 const float fx =
u *
static_cast<float>(
resolution - 1);
62 const float fz =
v *
static_cast<float>(
resolution - 1);
63 const int x0 = std::clamp(
static_cast<int>(std::floor(fx)), 0,
resolution - 1);
64 const int z0 = std::clamp(
static_cast<int>(std::floor(fz)), 0,
resolution - 1);
65 const int x1 = std::min(x0 + 1,
resolution - 1);
66 const int z1 = std::min(z0 + 1,
resolution - 1);
67 const float tx = fx -
static_cast<float>(x0);
68 const float tz = fz -
static_cast<float>(z0);
70 return glm::mix(glm::mix(
at(x0, z0),
at(x1, z0), tx), glm::mix(
at(x0, z1),
at(x1, z1), tx), tz);
74 if (
lifeField.empty())
return glm::vec4(0.f);
76 return sampleLifeTexel(std::clamp(
uv.x, 0.f, 1.f), std::clamp(
uv.y, 0.f, 1.f));
84 const std::vector<glm::vec3>& deposits,
float depositStrength) {
87 const float decay = std::exp(-decayRate * dt);
88 const float freshDecay =
89 halfLife > 1e-4f ? std::exp(-std::log(2.f) * dt / halfLife) : 0.f;
93 for (
int z = 0;
z < res; ++
z) {
94 for (
int x = 0;
x < res; ++
x) {
95 const size_t i =
static_cast<size_t>(
index(
x,
z));
99 if (diffusion > 0.f) {
102 for (
int dz = -1;
dz <= 1; ++
dz) {
103 for (
int dx = -1;
dx <= 1; ++
dx) {
104 if (
dx == 0 &&
dz == 0)
continue;
105 const int nx =
x +
dx;
106 const int nz =
z +
dz;
107 if (!inBounds(
nx,
nz))
continue;
113 const glm::vec4 avg = sum /
static_cast<float>(
count);
114 c.r = glm::mix(
c.r, avg.r, diffusion);
115 c.g = glm::mix(
c.g, avg.g, diffusion * 0.5f);
116 c.b = glm::mix(
c.b, avg.b, diffusion * 0.25f);
123 for (
const glm::vec3&
p : deposits) {
125 if (
uv.x < 0.f ||
uv.y < 0.f ||
uv.x >= 1.f ||
uv.y >= 1.f)
continue;
126 const int x = std::clamp(
static_cast<int>(
uv.x * res), 0, res - 1);
127 const int z = std::clamp(
static_cast<int>(
uv.y * res), 0, res - 1);
128 auto&
c = next[
static_cast<size_t>(
index(
x,
z))];
129 c.r = std::min(
c.r + depositStrength, 1.f);
EVEngine assertion entry point, backed by zeroerr.
#define EV_PARAM_CHECK(cond,...)
Validate a function parameter / public API precondition.
std::vector< glm::vec4 > lifeField
Life field RGBA: R=trail, G=nutrient, B=danger, A=freshness.
glm::vec3 sampleNormal(float x, float z) const
Bilinear sample of surface normal.
glm::vec3 project(const glm::vec3 &p) const
Project a world point onto the surface (Y from height).
void stepLife(float dt, float decayRate, float halfLife, float diffusion, const std::vector< glm::vec3 > &deposits, float depositStrength)
Advance trail deposit / decay / diffusion / danger for one fixed step.
float sampleHeight(float x, float z) const
Bilinear sample of height at world XZ.
std::vector< glm::vec3 > normals
Packed normals as vec3 per texel.
std::vector< float > height
Height samples, resolution^2, row-major X then Z.
void initFlat(const glm::vec3 &originMin, float size, int res, float planeY=0.f)
Allocate grids and fill a flat plane at y = planeY.
glm::vec2 worldToUv(const glm::vec3 &p) const
World XZ → texel UV in [0,1].
glm::vec4 sampleLife(float x, float z) const
Bilinear sample of life RGBA.