载入中...
搜索中...
未找到
TerrainEffect.cpp
浏览该文件的文档.
3
4namespace eve::procgen {
5namespace {
6using namespace raster_detail;
7bool isUnit(float value) { return std::isfinite(value) && value >= 0 && value <= 1; }
8bool validEffectInputs(const Heightmap& target, const Heightmap& mask) {
9 return validRaster(target) && validRaster(mask) && target.getWidth() == mask.getWidth() &&
10 target.getHeight() == mask.getHeight() && std::all_of(mask.data().begin(), mask.data().end(), isUnit);
11}
12double pixelSample(const Heightmap& map, double x, double y) {
13 const double width = map.getWidth() - 1, height = map.getHeight() - 1;
14 return sample(map, width == 0 ? 0 : std::clamp(x / width, 0.0, 1.0),
15 height == 0 ? 0 : std::clamp(y / height, 0.0, 1.0));
16}
17double roundEven(double value) {
18 // HLSL round uses nearest-even, independently of the host floating-point rounding mode.
19 const double lower = std::floor(value), fraction = value - lower;
20 if (fraction < 0.5) return lower;
21 if (fraction > 0.5) return lower + 1;
22 return std::fmod(lower, 2.0) == 0 ? lower : lower + 1;
23}
24} // namespace
25
27 using namespace raster_detail;
28 if (!validEffectInputs(target, mask) || !std::isfinite(strength) || strength < 0 || !std::isfinite(featureSize) ||
29 featureSize < 0)
30 return invalid("terrain.contrast: finite matching rasters, unit mask and nonnegative strength/size required");
31 auto tap = [&](double x, double y) { return pixelSample(target, x, y); };
32 std::vector<float> result(target.data().size());
33 for (int y = 0; y < target.getHeight(); ++y) {
34 for (int x = 0; x < target.getWidth(); ++x) {
35 const size_t i = size_t(y) * target.getWidth() + x;
36 const double original = target.data()[i], d = featureSize;
37 const double average =
38 (original + tap(x - d, y) + tap(x + d, y) + tap(x, y - d) + tap(x, y + d) +
39 0.75 * (tap(x - d, y - d) + tap(x + d, y - d) + tap(x - d, y + d) + tap(x + d, y + d))) /
40 8;
41 const double value = original + (original - average) * 0.5 * strength * mask.data()[i];
42 if (!isRepresentable(value)) return invalid("terrain.contrast: output exceeds finite float range");
43 result[i] = float(value);
44 }
45 }
46 return publish(target, std::move(result));
47}
48
50 using namespace raster_detail;
51 if (!validEffectInputs(target, mask) || !isUnit(settings.strength) || !std::isfinite(settings.radius) ||
52 settings.radius < 0 || !std::isfinite(settings.verticality) || std::abs(settings.verticality) > 1)
53 return invalid(
54 "terrain.smooth: matching unit mask, unit strength, nonnegative radius and verticality [-1,1] required");
55 constexpr double weights[] = {0.95, 0.85, 0.7, 0.4, 0.2, 0.15, 0.05};
57 std::vector<float> result(target.data().size());
58 const double centered = 1 - std::abs(double(settings.verticality));
59 const double lower = std::max(-double(settings.verticality), 0.0);
60 const double upper = std::max(double(settings.verticality), 0.0);
61 for (int pass = 0; pass < 2; ++pass) {
62 // Source shader advances vertical UV by texelSize.x as well.
63 const double radius =
64 pass == 0 ? settings.radius : double(settings.radius) * current.getHeight() / current.getWidth();
65 for (int y = 0; y < current.getHeight(); ++y) {
66 for (int x = 0; x < current.getWidth(); ++x) {
67 const size_t index = size_t(y) * current.getWidth() + x;
68 const double original = current.data()[index];
69 double average = original;
70 for (int k = 0; k < 7; ++k) {
71 const double offset = radius * (k + 1);
72 const double dx = pass == 0 ? offset : 0, dy = pass == 0 ? 0 : offset;
73 average +=
74 weights[k] * (pixelSample(current, x - dx, y - dy) + pixelSample(current, x + dx, y + dy));
75 }
76 average /= 7.6;
77 const double filtered =
78 centered * average + lower * std::min(average, original) + upper * std::max(average, original);
79 const double value = std::lerp(original, filtered, double(settings.strength) * mask.data()[index]);
80 if (!isRepresentable(value)) return invalid("terrain.smooth: output exceeds finite float range");
81 result[index] = float(value);
82 }
83 }
84 current.data().swap(result);
85 }
86 return publish(target, std::move(current.data()));
87}
88
90 using namespace raster_detail;
91 if (!validEffectInputs(target, mask) || !isUnit(settings.strength) || !isUnit(settings.mixStrength) ||
92 !std::isfinite(settings.exponent) || settings.exponent <= 0 || !std::isfinite(settings.minimum) ||
93 !std::isfinite(settings.maximum) || settings.minimum >= settings.maximum || settings.passes < 0)
94 return invalid(
95 "terrain.ridges: finite matching unit mask, unit strengths, positive exponent, ordered bounds and "
96 "nonnegative passes required");
98 std::vector<float> result(target.data().size());
99 auto powerBetween = [&](double h, double a, double b) {
100 const double low = std::min(a, b), high = std::max(a, b);
101 return h > low && h < high ? std::pow((h - low) / (high - low), settings.exponent) * (high - low) + low : h;
102 };
103 for (int pass = 0; pass < settings.passes; ++pass) {
104 for (int y = 0; y < current.getHeight(); ++y) {
105 for (int x = 0; x < current.getWidth(); ++x) {
106 const size_t index = size_t(y) * current.getWidth() + x;
107 const double original = current.data()[index];
108 const double left = current.height(std::max(x - 1, 0), y);
109 const double right = current.height(std::min(x + 1, current.getWidth() - 1), y);
110 const double top = current.height(x, std::max(y - 1, 0));
111 const double bottom = current.height(x, std::min(y + 1, current.getHeight() - 1));
112 double value = powerBetween(original, left, right);
113 value = powerBetween(value, bottom, top);
114 value = std::lerp(0.25 * (left + right + top + bottom), value, settings.mixStrength);
115 value = std::clamp(std::lerp(original, value, double(settings.strength) * mask.data()[index]),
116 double(settings.minimum), double(settings.maximum));
117 result[index] = float(value);
118 }
119 }
120 current.data().swap(result);
121 }
122 return publish(target, std::move(current.data()));
123}
124
126 using namespace raster_detail;
127 if (!validEffectInputs(target, mask) || !isUnit(settings.strength) || !isUnit(settings.bevel) ||
128 !std::isfinite(settings.count) || settings.count <= 0)
129 return invalid("terrain.terrace: finite matching unit mask, unit strength/bevel and positive count required");
130 std::vector<float> result(target.data().size());
131 for (size_t i = 0; i < result.size(); ++i) {
132 const double original = target.data()[i], scaled = original * settings.count;
133 double rounded = roundEven(scaled);
134 if (scaled - rounded > 1 - double(settings.bevel)) rounded = scaled;
135 const double value = std::lerp(original, rounded / settings.count, double(settings.strength) * mask.data()[i]);
136 if (!isRepresentable(value)) return invalid("terrain.terrace: output exceeds finite float range");
137 result[i] = float(value);
138 }
139 return publish(target, std::move(result));
140}
141
143 using namespace raster_detail;
144 if (!validEffectInputs(target, mask) || !std::isfinite(power))
145 return invalid("terrain.power: finite matching unit mask and finite power required");
146 const double exponent = 4 - double(power);
147 std::vector<float> result = target.data();
148 for (size_t i = 0; i < result.size(); ++i) {
149 if (mask.data()[i] == 0) continue;
150 const double original = target.data()[i];
151 if (original < 0 || (original == 0 && exponent <= 0))
152 return invalid("terrain.power: negative base or zero base with nonpositive exponent");
153 const double value = std::lerp(original, std::pow(original, exponent), double(mask.data()[i]));
154 if (!isRepresentable(value)) return invalid("terrain.power: output exceeds finite float range");
155 result[i] = float(value);
156 }
157 return publish(target, std::move(result));
158}
159
161 float maximum) {
162 using namespace raster_detail;
163 if (!validEffectInputs(target, mask) || !validRaster(curve) || curve.getHeight() != 1 || !std::isfinite(minimum) ||
164 !std::isfinite(maximum) || minimum >= maximum)
165 return invalid("terrain.heightCurve: matching unit mask, one-row curve and ordered finite range required");
166 std::vector<float> result = target.data();
167 for (size_t i = 0; i < result.size(); ++i) {
168 if (mask.data()[i] == 0) continue;
169 const double original = target.data()[i], range = double(maximum) - minimum;
170 double t = std::clamp((original - minimum) / range, 0.0, 1.0);
171 t = t * t * (3 - 2 * t);
172 const double value = std::lerp(original, range * curveSample(curve, t), double(mask.data()[i]));
173 if (!isRepresentable(value)) return invalid("terrain.heightCurve: output exceeds finite float range");
174 result[i] = float(value);
175 }
176 return publish(target, std::move(result));
177}
178
181 using namespace raster_detail;
182 if (!validEffectInputs(target, global) || !validRaster(local) || local.getWidth() != target.getWidth() ||
183 local.getHeight() != target.getHeight() || !std::isfinite(settings.minimum) ||
184 !std::isfinite(settings.maximum) || settings.minimum > settings.maximum || !isUnit(settings.midpoint) ||
185 !std::isfinite(settings.strength) || settings.strength < 0 || !std::isfinite(settings.clipMinimum) ||
186 !std::isfinite(settings.clipMaximum) || settings.clipMinimum >= settings.clipMaximum)
187 return invalid(
188 "terrain.heightMix: finite matching rasters, unit global/midpoint, nonnegative strength and ordered ranges "
189 "required");
190 std::vector<float> result(target.data().size());
191 for (size_t i = 0; i < result.size(); ++i) {
192 const double delta = (double(local.data()[i]) - settings.midpoint) *
193 (double(settings.maximum) - settings.minimum) * settings.strength;
194 const double value = double(target.data()[i]) + delta * global.data()[i];
195 result[i] = float(std::clamp(value, double(settings.clipMinimum), double(settings.clipMaximum)));
196 }
197 return publish(target, std::move(result));
198}
199} // namespace eve::procgen
LogicalId target
double value
SQInteger top
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
int mask
float maximum[3]
float minimum[3]
HexVec3 left
HexVec3 right
int h
std::uint32_t height
std::uint32_t width
std::string local
size_t offset
Range range
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float radius
float d
float t
float power
Definition RockMesh.cpp:23
double current
float dy
float dx
Heightmap curve
TerrainThermalSettings settings
Heightmap global
float weights[3]
uint32_t index
float bottom
Move-only operation result carrying either a value or Status.
Definition Result.h:155
In-memory terrain heightmap: a dense float grid (row-major, index = y * width + x) materialized from ...
Definition Heightmap.h:21
int getHeight() const
Returns the height.
Definition Heightmap.cpp:17
const std::vector< float > & data() const
Data.
Definition Heightmap.h:48
double curveSample(const Heightmap &curve, double value)
Curve sample.
double sample(const Heightmap &map, double u, double v)
Sample.
Result< int > publish(Heightmap &target, std::vector< float > candidate)
Publish.
bool validRaster(const Heightmap &map)
Valid raster.
Result< int > invalid(std::string message)
Invalid.
bool isRepresentable(double value)
True when representable.
Result< int > applyTerrainTerrace(Heightmap &target, const Heightmap &mask, const TerrainTerraceSettings &settings)
Terrace scalar heights using nearest-even rounding and the shader's one-sided bevel.
Result< int > applyTerrainContrast(Heightmap &target, const Heightmap &mask, float strength, float featureSize)
Apply the nine-tap Pcg contrast equation to native scalar heights.
Result< int > applyTerrainHeightMix(Heightmap &target, const Heightmap &local, const Heightmap &global, const TerrainHeightMixSettings &settings)
Mix centered local brush heights into terrain, then globally mask and clip.
Result< int > applyTerrainSmooth(Heightmap &target, const Heightmap &mask, const TerrainSmoothSettings &settings)
Apply Pcg's horizontal then vertical seven-pair weighted smooth passes.
Result< int > applyTerrainPower(Heightmap &target, const Heightmap &mask, float power)
Apply the Pcg PowerOf control: lerp(height,pow(height,4-power),mask).
Result< int > applyTerrainRidges(Heightmap &target, const Heightmap &mask, const TerrainRidgeSettings &settings)
Apply ordered horizontal/vertical ridge powers, neighbor mixing and per-pass clipping.
Result< int > applyTerrainHeightCurve(Heightmap &target, const Heightmap &mask, const Heightmap &curve, float minimum, float maximum)
Apply the height-transform shader's smoothstep and one-row height curve.
Mix-height scalar controls; delta scale is maximum-minimum, independent of clipping bounds....
Ridge controls in caller height units, with explicit clipping bounds. Defaults match the Pcg packed s...
Two-pass smoothing controls; radius in texels, verticality in [-1,1], strength in [0,...
Terrace frequency in inverse height units, interior bevel in [0,1], strength in [0,...