载入中...
搜索中...
未找到
CloudField.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5
6namespace eve::procgen {
7namespace {
8
9inline float smoothstepClamp(float e0, float e1, float x) {
10 if (e0 == e1) return x < e0 ? 0.f : 1.f;
11 const float t = std::clamp((x - e0) / (e1 - e0), 0.f, 1.f);
12 return t * t * (3.f - 2.f * t);
13}
14
15inline float fade(float t) { return t * t * (3.f - 2.f * t); }
16
17struct Lat {
20 uint32_t seed = 1;
21
22 int wrap(int i, int period) const {
23 if (period <= 0) return i;
24 int m = i % period;
25 return m < 0 ? m + period : m;
26 }
27
28 float hash01(int ix, int iy) const {
29 ix = wrap(ix, periodX);
30 iy = wrap(iy, periodY);
31 uint32_t h = seed;
32 h ^= uint32_t(ix) * 374761393u;
33 h ^= uint32_t(iy) * 668265263u;
34 h = (h ^ (h >> 13)) * 1274126177u;
35 h ^= h >> 16;
36 return float(h & 0x00FFFFFFu) / float(0x00FFFFFFu);
37 }
38
39 float valueNoise(float x, float y) const {
40 const int x0 = int(std::floor(x));
41 const int y0 = int(std::floor(y));
42 const float fx = fade(x - float(x0));
43 const float fy = fade(y - float(y0));
44 const float n00 = hash01(x0, y0);
45 const float n10 = hash01(x0 + 1, y0);
46 const float n01 = hash01(x0, y0 + 1);
47 const float n11 = hash01(x0 + 1, y0 + 1);
48 return n00 + (n10 - n00) * fx + (n01 - n00) * fy + (n00 - n10 - n01 + n11) * fx * fy;
49 }
50
51 float fbm(float x, float y, int octaves, float lacunarity, float gain) const {
52 octaves = std::max(1, octaves);
53 float sum = 0.f, amp = 1.f, norm = 0.f, freq = 1.f;
54 for (int i = 0; i < octaves; ++i) {
55 sum += valueNoise(x * freq, y * freq) * amp;
56 norm += amp;
57 amp *= gain;
58 freq *= lacunarity;
59 }
60 return norm > 0.f ? sum / norm : 0.f;
61 }
62};
63
64} // namespace
65
66void CloudField::setParams(const Params &params) { params_ = params; }
67
68void CloudField::setSeed(uint32_t seed) { params_.seed = seed; }
69void CloudField::setWorldScale(float v) { params_.worldScale = std::max(1.f, v); }
70void CloudField::setCoverage(float v) { params_.coverage = std::clamp(v, 0.f, 1.f); }
71void CloudField::setSoftness(float v) { params_.softness = std::clamp(v, 0.f, 1.f); }
72void CloudField::setDetail(float v) { params_.detail = std::clamp(v, 0.f, 1.f); }
73void CloudField::setWind(float speed, float angleRad) {
74 params_.windSpeed = std::max(0.f, speed);
75 params_.windAngle = angleRad;
76}
77void CloudField::setOctaves(int v) { params_.octaves = std::max(1, v); }
78void CloudField::setWarp(float v) { params_.warp = std::max(0.f, v); }
79void CloudField::setSeamless(bool v) { params_.seamless = v; }
80
81void CloudField::windVelocity(float &vx, float &vz) const {
82 vx = std::cos(params_.windAngle) * params_.windSpeed;
83 vz = std::sin(params_.windAngle) * params_.windSpeed;
84}
85
86float CloudField::coverageAt(float x, float z, float time) const {
87 float vx = 0.f, vz = 0.f;
88 windVelocity(vx, vz);
89
90 const float cell = params_.worldScale / float(kLattice);
91 // Drift with the wind; sample in lattice units (seamless wrap at kLattice).
92 const float px = (x - vx * time) / cell;
93 const float pz = (z - vz * time) / cell;
94
95 Lat lat;
96 lat.seed = params_.seed;
97 if (!params_.seamless) {
98 lat.periodX = 0;
99 lat.periodY = 0;
100 }
101
102 // Domain warp → organic, billowy shapes.
103 const float w = lat.fbm(px + 19.1f, pz + 7.3f, params_.octaves, params_.lacunarity,
104 params_.gain) - 0.5f;
105 const float wx = px + w * params_.warp * 0.5f;
106 const float wz = pz + (lat.fbm(px + 5.2f, pz + 31.7f, params_.octaves, params_.lacunarity,
107 params_.gain) -
108 0.5f) *
109 params_.warp * 0.5f;
110
111 float base = lat.fbm(wx, wz, params_.octaves, params_.lacunarity, params_.gain);
112 // Threshold into puffs with soft edges.
113 float c = smoothstepClamp(params_.coverage - params_.softness,
114 params_.coverage + params_.softness, base);
115
116 // High-frequency wisps erode dense regions for a textured top.
117 if (params_.detail > 0.f) {
118 const float d = lat.fbm(wx * 3.f + 11.7f, wz * 3.f + 5.3f, 2, params_.lacunarity,
119 params_.gain);
120 c = c * (1.f - params_.detail) + (c * d) * params_.detail;
121 }
122 return std::clamp(c, 0.f, 1.f);
123}
124
125void CloudField::sample(float *out, int width, int height, float time, float x0, float z0,
126 float extent) const {
127 if (!out || width <= 0 || height <= 0) return;
128 for (int y = 0; y < height; ++y) {
129 const float z = z0 + extent * (float(y) / float(height - 1));
130 for (int x = 0; x < width; ++x) {
131 const float px = x0 + extent * (float(x) / float(width - 1));
132 out[size_t(y * width + x)] = coverageAt(px, z, time);
133 }
134 }
135}
136
137} // namespace eve::procgen
int periodX
uint32_t seed
int periodY
int y
Definition Grass.cpp:135
int z
Definition Grass.cpp:135
float height
Definition Grass.cpp:235
int x
Definition Grass.cpp:135
int h
int w
std::vector< Colorf > px
uint32_t c
int width
int d
int v
float m[16]
void setSeamless(bool seamless)
void setParams(const Params &params)
void setWorldScale(float worldScale)
void setWarp(float warp)
float coverageAt(float x, float z, float time) const
Cloud coverage at world (x, z) and time t, in [0,1].
void windVelocity(float &vx, float &vz) const
Drift vector (world units/sec) implied by windSpeed + windAngle.
void setDetail(float detail)
void setSoftness(float softness)
void setOctaves(int octaves)
void setSeed(uint32_t seed)
static constexpr int kLattice
Internal lattice resolution per tile (also the noise wrap period).
Definition CloudField.h:61
void setCoverage(float coverage)
void sample(float *out, int width, int height, float time, float x0, float z0, float extent) const
const Params & params() const
Definition CloudField.h:37
void setWind(float speed, float angleRad)