载入中...
搜索中...
未找到
TerrainImageAdapter.cpp
浏览该文件的文档.
2#include <simplesquirrel/simplesquirrel.hpp>
3#include <algorithm>
4#include <cmath>
6#include "image/ImageData.h"
11
12namespace eve::procgen {
21namespace {
22bool validImage(const image::ImageData& value, bool writable = false) {
23 if (value.getWidth() <= 0 || value.getHeight() <= 0 || !value.getData() || !value.getPixelGetFunction() ||
24 (writable && !value.getPixelSetFunction())) return false;
25 const std::size_t pixelSize = value.getPixelSize();
26 return pixelSize > 0 && pixelSize <= sizeof(image::ImageData::Pixel) &&
27 std::size_t(value.getWidth()) * value.getHeight() <= value.getSize() / pixelSize;
28}
29image::ImageData::Colorf repeatBilinear(const image::ImageData& image, double u, double v) {
30 u -= std::floor(u); v -= std::floor(v);
31 const double px = u * image.getWidth() - 0.5, py = v * image.getHeight() - 0.5;
32 const int x0 = static_cast<int>(std::floor(px)), y0 = static_cast<int>(std::floor(py));
33 const float tx = static_cast<float>(px - x0), ty = static_cast<float>(py - y0);
34 auto sample = [&](int x, int y) {
35 x = (x % image.getWidth() + image.getWidth()) % image.getWidth();
36 y = (y % image.getHeight() + image.getHeight()) % image.getHeight();
37 return image.getPixel(x, y);
38 };
39 const auto a = sample(x0, y0), b = sample(x0 + 1, y0), c = sample(x0, y0 + 1), d = sample(x0 + 1, y0 + 1);
40 auto blend = [&](float av, float bv, float cv, float dv) {
41 return (av + (bv - av) * tx) * (1 - ty) + (cv + (dv - cv) * tx) * ty;
42 };
43 return {blend(a.r,b.r,c.r,d.r), blend(a.g,b.g,c.g,d.g), blend(a.b,b.b,c.b,d.b), blend(a.a,b.a,c.a,d.a)};
44}
45float smoothStep(float low, float high, float value) {
46 if (low == high) return value >= high ? 1.0F : 0.0F;
47 const float t = std::clamp((value - low) / (high - low), 0.0F, 1.0F);
48 return t * t * (3 - 2 * t);
49}
50struct Vec3 { float x, y, z; };
51Vec3 normalize(Vec3 value) {
52 const float length = std::sqrt(value.x * value.x + value.y * value.y + value.z * value.z);
53 return length > 0 ? Vec3{value.x / length, value.y / length, value.z / length} : Vec3{0, 1, 0};
54}
55Vec3 cross(Vec3 a, Vec3 b) {
56 return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
57}
58float dot(Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
59void terrainFrame(Vec3 terrain, Vec3& tangent, Vec3& bitangent) {
60 tangent = normalize(cross(terrain, {0, 0, 1}));
61 bitangent = normalize(cross(tangent, terrain));
62}
63Vec3 blendWorldNormal(Vec3 terrain, Vec3 surface) {
64 const Vec3 tangent = cross(terrain, {0, 0, 1});
65 const Vec3 bitangent = cross(tangent, terrain);
66 return {surface.x * tangent.x + surface.y * bitangent.x + surface.z * terrain.x,
67 surface.x * tangent.y + surface.y * bitangent.y + surface.z * terrain.y,
68 surface.x * tangent.z + surface.y * bitangent.z + surface.z * terrain.z};
69}
70Vec3 terrainNormal(const Heightmap& heights, int x, int z, double spacingX, double spacingZ) {
71 const int xl = std::max(0, x - 1), xr = std::min(heights.getWidth() - 1, x + 1);
72 const int zb = std::max(0, z - 1), zf = std::min(heights.getHeight() - 1, z + 1);
73 const double dx = (heights.height(xr, z) - heights.height(xl, z)) / ((xr - xl) * spacingX + (xr == xl));
74 const double dz = (heights.height(x, zf) - heights.height(x, zb)) / ((zf - zb) * spacingZ + (zf == zb));
75 return normalize({static_cast<float>(-dx), 1, static_cast<float>(-dz)});
76}
77double fraction(double value) { return value - std::floor(value); }
78std::array<double, 2> gtsHash(double x, double y) {
79 return {fraction(std::sin(127.1 * x + 311.7 * y) * 43758.5453),
80 fraction(std::sin(269.5 * x + 183.3 * y) * 43758.5453)};
81}
82struct TriangleSample { float weights[3]; std::array<double, 2> vertices[3]; };
83TriangleSample triangleGrid(double u, double v) {
84 u *= 3.464; v *= 3.464;
85 const double sx = u - 0.57735027 * v, sy = 1.15470054 * v;
86 const double bx = std::floor(sx), by = std::floor(sy), fx = fraction(sx), fy = fraction(sy);
87 const double third = 1 - fx - fy;
88 TriangleSample result{};
89 if (third > 0) {
90 result.weights[0]=static_cast<float>(third);result.weights[1]=static_cast<float>(fy);
91 result.weights[2]=static_cast<float>(fx);result.vertices[0]={bx,by};
92 result.vertices[1]={bx,by+1};result.vertices[2]={bx+1,by};
93 } else {
94 result.weights[0]=static_cast<float>(-third);result.weights[1]=static_cast<float>(1-fy);
95 result.weights[2]=static_cast<float>(1-fx);result.vertices[0]={bx+1,by+1};
96 result.vertices[1]={bx+1,by};result.vertices[2]={bx,by+1};
97 }
98 return result;
99}
100image::ImageData::Colorf stochasticSample(const image::ImageData& image, double u, double v) {
101 const auto grid = triangleGrid(u, v);
102 image::ImageData::Colorf result{0, 0, 0, 0};
103 for (int i = 0; i < 3; ++i) {
104 const auto offset = gtsHash(grid.vertices[i][0], grid.vertices[i][1]);
105 const auto value = repeatBilinear(image, u + offset[0], v + offset[1]);
106 result.r += grid.weights[i] * value.r; result.g += grid.weights[i] * value.g;
107 result.b += grid.weights[i] * value.b; result.a += grid.weights[i] * value.a;
108 }
109 return result;
110}
111image::ImageData::Colorf projectedSample(const image::ImageData& image,
112 const GtsPackedLayerSettings& settings,
113 Vec3 position, Vec3 normal, double planarU, double planarV) {
114 if (!settings.triPlanar)
115 return settings.stochastic ? stochasticSample(image, planarU, planarV)
116 : repeatBilinear(image, planarU, planarV);
117 float wx = std::pow(std::abs(normal.x), 4.0F), wy = std::pow(std::abs(normal.y), 15.0F);
118 float wz = std::pow(std::abs(normal.z), 4.0F), total = std::max(wx + wy + wz, 0.0001F);
119 wx /= total; wy /= total; wz /= total;
120 const double sizeX = settings.triPlanarSizeX * 10.0, sizeZ = settings.triPlanarSizeZ * 10.0;
121 const double signX = normal.x < 0 ? -1 : 1, signY = normal.y < 0 ? -1 : 1;
122 const double signZ = normal.z < 0 ? -1 : 1;
123 const auto sampleX = repeatBilinear(image, position.z / sizeX * signX, position.y / sizeZ);
124 const auto sampleY = repeatBilinear(image, (position.x / sizeX + 0.33) * signY,
125 position.z / sizeZ + 0.33);
126 const auto sampleZ = repeatBilinear(image, -(position.x / sizeX + 0.67) * signZ,
127 position.y / sizeZ + 0.67);
128 return {sampleX.r*wx+sampleY.r*wy+sampleZ.r*wz, sampleX.g*wx+sampleY.g*wy+sampleZ.g*wz,
129 sampleX.b*wx+sampleY.b*wy+sampleZ.b*wz, sampleX.a*wx+sampleY.a*wy+sampleZ.a*wz};
130}
131} // namespace
132Result<int> TerrainSplatPalette::addColor(float red, float green, float blue, float alpha) {
133 const auto valid = [](float value) { return std::isfinite(value) && value >= 0 && value <= 1; };
134 if (!valid(red) || !valid(green) || !valid(blue) || !valid(alpha))
135 return raster_detail::invalid("terrain.splatPalette: normalized finite color required");
136 colors_.push_back({red, green, blue, alpha});
137 return Result<int>::success(static_cast<int>(colors_.size()));
138}
139GtsPackedLayerSet::GtsPackedLayerSet() : impl_(std::make_unique<Impl>()) {}
142GtsPackedLayerSet& GtsPackedLayerSet::operator=(GtsPackedLayerSet&&) noexcept = default;
143Result<int> GtsPackedLayerSet::addLayer(const image::ImageData& albedoHeight,
144 const image::ImageData& normalMask,
146 using namespace raster_detail;
147 const float values[] = {settings.tileSizeX, settings.tileSizeZ, settings.offsetX, settings.offsetZ,
148 settings.triPlanarSizeX, settings.triPlanarSizeZ,
149 settings.tintR, settings.tintG, settings.tintB, settings.normalStrength,
150 settings.aoMin, settings.smoothnessMin, settings.aoMax, settings.smoothnessMax,
151 settings.geoAmount, settings.detailAmount, settings.heightContrast,
152 settings.heightBrightness, settings.heightIncrease, settings.displacementContrast,
153 settings.displacementBrightness, settings.displacementIncrease,
154 settings.tessellationAmount};
155 if (!validImage(albedoHeight) || !validImage(normalMask) ||
156 !std::all_of(std::begin(values), std::end(values), [](float v) { return std::isfinite(v); }) ||
157 settings.tileSizeX <= 0 || settings.tileSizeZ <= 0 || settings.triPlanarSizeX <= 0 ||
158 settings.triPlanarSizeZ <= 0 || settings.normalStrength < 0 ||
159 settings.tintR < 0 || settings.tintR > 1 || settings.tintG < 0 || settings.tintG > 1 ||
160 settings.tintB < 0 || settings.tintB > 1 || settings.geoAmount < 0 || settings.geoAmount > 1 ||
161 settings.detailAmount < 0 || settings.detailAmount > 1 || settings.heightContrast < 0 ||
162 settings.displacementContrast < 0)
163 return invalid("terrain.gtsPackedLayers: readable textures and finite normalized settings required");
164 if (!impl_) impl_ = std::make_unique<Impl>();
165 if (impl_->layers.size() >= 8) return invalid("terrain.gtsPackedLayers: at most eight layers supported");
166 impl_->layers.push_back({albedoHeight, normalMask, settings});
167 return Result<int>::success(static_cast<int>(impl_->layers.size()));
168}
169
171 const Heightmap& heights, const TerrainSplatmap& splatmap,
172 const GtsPackedLayerSet& layers, double cameraX, double cameraY,
173 double cameraZ, double tessellationMultiplier, double originX,
174 double originY, double originZ, double spacingX, double spacingZ) {
175 using namespace raster_detail;
176 const int width = splatmap.getWidth(), height = splatmap.getHeight(), count = splatmap.getLayerCount();
177 auto matches = [&](const Heightmap& map) {
178 return validRaster(map) && map.getWidth() == width && map.getHeight() == height;
179 };
180 const double values[]{cameraX, cameraY, cameraZ, tessellationMultiplier, originX, originY, originZ,
182 if (!layers.impl_ || layers.impl_->layers.size() != static_cast<std::size_t>(count) || count < 1 ||
183 count > 8 || !matches(displacement) || !matches(tessellation) || !matches(heights) ||
184 !std::all_of(std::begin(values), std::end(values), [](double value) { return std::isfinite(value); }) ||
185 spacingX <= 0 || spacingZ <= 0)
186 return invalid("terrain.gtsLayerDisplacement: matching splat, layers, heights and finite settings required");
187 Heightmap nextDisplacement(displacement), nextTessellation(tessellation);
188 int changed = 0;
189 for (int z = 0; z < height; ++z) for (int x = 0; x < width; ++x) {
190 const double worldX = originX + x * spacingX, worldY = originY + heights.height(x, z);
191 const double worldZ = originZ + z * spacingZ;
192 const double dx = cameraX - worldX, dy = cameraY - worldY, dz = cameraZ - worldZ;
193 float displaced = 0, tessellated = 0;
194 if (dx * dx + dy * dy + dz * dz < 100000.0) {
195 std::array<std::pair<float, int>, 8> ranked{};
196 for (int layer = 0; layer < count; ++layer)
197 ranked[static_cast<std::size_t>(layer)] = {splatmap.sample(layer, x, z).value(), layer};
198 std::stable_sort(ranked.begin(), ranked.begin() + count,
199 [](const auto& a, const auto& b) { return a.first > b.first; });
200 for (int rank = 0; rank < std::min(4, count); ++rank) {
201 const auto [weight, layer] = ranked[static_cast<std::size_t>(rank)];
202 if (weight <= 0) continue;
203 const auto& source = layers.impl_->layers[static_cast<std::size_t>(layer)];
204 const auto& settings = source.settings;
205 const double u = x * spacingX / settings.tileSizeX + settings.offsetX;
206 const double v = z * spacingZ / settings.tileSizeZ + settings.offsetZ;
207 const Vec3 position{static_cast<float>(worldX), static_cast<float>(worldY),
208 static_cast<float>(worldZ)};
209 const Vec3 normal = terrainNormal(heights, x, z, spacingX, spacingZ);
210 const float rawHeight = projectedSample(source.albedoHeight, settings, position, normal, u, v).a;
211 const float layerDisplacement = std::pow(std::abs(rawHeight), settings.displacementContrast) *
212 settings.displacementBrightness +
213 settings.displacementIncrease;
214 displaced += layerDisplacement * weight;
215 tessellated += settings.tessellationAmount * weight;
216 }
217 }
218 tessellated *= static_cast<float>(tessellationMultiplier);
219 if (!std::isfinite(displaced) || !std::isfinite(tessellated))
220 return invalid("terrain.gtsLayerDisplacement: profile produced a nonfinite output");
221 changed += displacement.height(x, z) != displaced;
222 changed += tessellation.height(x, z) != tessellated;
223 nextDisplacement.setHeight(x, z, displaced);
224 nextTessellation.setHeight(x, z, tessellated);
225 }
226 displacement.data().swap(nextDisplacement.data());
227 tessellation.data().swap(nextTessellation.data());
228 return Result<int>::success(changed);
229}
230int GtsPackedLayerSet::getLayerCount() const noexcept { return impl_ ? static_cast<int>(impl_->layers.size()) : 0; }
231
233 Heightmap& geoStrength, Heightmap& detailStrength,
234 const Heightmap& heights, const TerrainSplatmap& splatmap,
235 const GtsPackedLayerSet& layers, double originX, double originY,
236 double originZ, double spacingX, double spacingZ) {
237 using namespace raster_detail;
238 const int width = splatmap.getWidth(), height = splatmap.getHeight(), count = splatmap.getLayerCount();
239 auto rasterMatches = [&](const Heightmap& map) { return validRaster(map) && map.getWidth() == width && map.getHeight() == height; };
240 auto imageMatches = [&](const image::ImageData& image) { return validImage(image, true) && image.getWidth() == width && image.getHeight() == height; };
241 if (!layers.impl_ || layers.impl_->layers.size() != static_cast<std::size_t>(count) || count < 1 || count > 8 ||
242 !imageMatches(albedo) || !imageMatches(packedNormal) || !rasterMatches(geoStrength) ||
243 !rasterMatches(detailStrength) || !rasterMatches(heights) || !std::isfinite(originX) ||
244 !std::isfinite(originY) || !std::isfinite(originZ) ||
245 !std::isfinite(spacingX) || spacingX <= 0 || !std::isfinite(spacingZ) || spacingZ <= 0)
246 return invalid("terrain.gtsPackedLayers: matching splat, layers and outputs required");
247 image::ImageData nextAlbedo(albedo), nextNormal(packedNormal);
248 Heightmap nextGeo(geoStrength), nextDetail(detailStrength);
249 int changed = 0;
250 for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) {
251 float r=0,g=0,b=0,nx=0,ny=0,ao=0,smooth=0,geo=0,detail=0;
252 const Vec3 position{static_cast<float>(originX + x * spacingX),
253 static_cast<float>(originY + heights.height(x, y)),
254 static_cast<float>(originZ + y * spacingZ)};
255 const Vec3 surfaceNormal = terrainNormal(heights, x, y, spacingX, spacingZ);
256 for (int layer = 0; layer < count; ++layer) {
257 const float weight = splatmap.sample(layer, x, y).value();
258 const auto& source = layers.impl_->layers[static_cast<std::size_t>(layer)];
259 const auto& s = source.settings;
260 const double u = x * spacingX / s.tileSizeX + s.offsetX;
261 const double v = y * spacingZ / s.tileSizeZ + s.offsetZ;
262 const auto color = projectedSample(source.albedoHeight, s, position, surfaceNormal, u, v);
263 const auto normal = projectedSample(source.normalMask, s, position, surfaceNormal, u, v);
264 r += color.r*s.tintR*weight; g += color.g*s.tintG*weight; b += color.b*s.tintB*weight;
265 nx += (normal.r*2-1)*s.normalStrength*weight; ny += (normal.g*2-1)*s.normalStrength*weight;
266 ao += (s.aoMin+(s.aoMax-s.aoMin)*normal.b)*weight;
267 smooth += (s.smoothnessMin+(s.smoothnessMax-s.smoothnessMin)*normal.a)*weight;
268 geo += s.geoAmount*weight; detail += s.detailAmount*weight;
269 }
270 const float nz = std::sqrt(1-std::clamp(nx*nx+ny*ny,0.0F,1.0F));
271 const float len = std::sqrt(nx*nx+ny*ny+nz*nz); nx/=len; ny/=len;
272 const auto oldA=albedo.getPixel(x,y), oldN=packedNormal.getPixel(x,y);
273 image::ImageData::Colorf outA{r,g,b,1}, outN{nx*0.5F+0.5F,ny*0.5F+0.5F,ao,smooth};
274 changed += oldA.r!=r||oldA.g!=g||oldA.b!=b; changed += oldN.r!=outN.r||oldN.g!=outN.g||oldN.b!=ao||oldN.a!=smooth;
275 changed += geoStrength.height(x,y)!=geo; changed += detailStrength.height(x,y)!=detail;
276 nextAlbedo.setPixel(x,y,outA); nextNormal.setPixel(x,y,outN); nextGeo.setHeight(x,y,geo); nextDetail.setHeight(x,y,detail);
277 }
278 albedo.adopt(nextAlbedo); packedNormal.adopt(nextNormal); geoStrength.data().swap(nextGeo.data()); detailStrength.data().swap(nextDetail.data());
279 return Result<int>::success(changed);
280}
281
284 if (splatmap.getWidth() <= 0 || splatmap.getHeight() <= 0 ||
285 output.getWidth() != splatmap.getWidth() || output.getHeight() != splatmap.getHeight() ||
286 !output.getPixelSetFunction() || palette.colors_.size() != static_cast<std::size_t>(splatmap.getLayerCount()))
287 return raster_detail::invalid("terrain.splatAlbedo: matching writable image, splatmap and palette required");
289 for (int y = 0; y < splatmap.getHeight(); ++y) for (int x = 0; x < splatmap.getWidth(); ++x) {
291 for (int layer = 0; layer < splatmap.getLayerCount(); ++layer) {
292 auto weight = splatmap.sample(layer, x, y);
293 if (!weight.ok()) return Result<int>::failure(weight.status());
294 const auto& source = palette.colors_[static_cast<std::size_t>(layer)];
295 color.r += source[0] * weight.value(); color.g += source[1] * weight.value();
296 color.b += source[2] * weight.value(); color.a += source[3] * weight.value();
297 }
298 next.setPixel(x, y, color);
299 }
300 output.adopt(next);
301 return Result<int>::success(splatmap.getWidth() * splatmap.getHeight());
302}
303
305 double cameraY, double cameraZ, double blendDistance,
306 double blendRange, double originX, double originY, double originZ,
307 double spacingX, double spacingZ) {
308 using namespace raster_detail;
309 if (!validRaster(output) || !validRaster(heights) || output.getWidth() != heights.getWidth() ||
310 output.getHeight() != heights.getHeight() || !std::isfinite(cameraX) || !std::isfinite(cameraY) ||
311 !std::isfinite(cameraZ) || !std::isfinite(blendDistance) || blendDistance < 0 ||
312 !std::isfinite(blendRange) || blendRange <= 0 || !std::isfinite(originX) ||
313 !std::isfinite(originY) || !std::isfinite(originZ) || !std::isfinite(spacingX) || spacingX <= 0 ||
314 !std::isfinite(spacingZ) || spacingZ <= 0)
315 return invalid("terrain.gtsGlobalBlend: matching rasters and finite camera/profile settings required");
316 Heightmap next(output);
317 int changed = 0;
318 for (int z = 0; z < heights.getHeight(); ++z) for (int x = 0; x < heights.getWidth(); ++x) {
319 const double dx = cameraX - (originX + x * spacingX);
320 const double dy = cameraY - (originY + heights.height(x, z));
321 const double dz = cameraZ - (originZ + z * spacingZ);
322 const double scaled = std::clamp((dx * dx + dy * dy + dz * dz) * blendDistance / 10000.0, 0.0, 1.0);
323 const float value = static_cast<float>(std::clamp(std::pow(scaled, blendRange), 0.0, 1.0));
324 changed += next.height(x, z) != value;
325 next.setHeight(x, z, value);
326 }
327 output.data().swap(next.data());
328 return Result<int>::success(changed);
329}
330
332 const Heightmap& globalBlendDistance, const GtsColorMapSettings& settings) {
333 using namespace raster_detail;
334 const auto unit = [](float value) { return std::isfinite(value) && value >= 0 && value <= 1; };
335 if (!validImage(output, true) || !validImage(colorMap) || !validRaster(globalBlendDistance) ||
336 output.getWidth() != colorMap.getWidth() || output.getHeight() != colorMap.getHeight() ||
337 output.getWidth() != globalBlendDistance.getWidth() || output.getHeight() != globalBlendDistance.getHeight() ||
338 !std::isfinite(settings.alphaIntensity) || settings.alphaIntensity < 0 ||
339 !std::isfinite(settings.colorIntensity) || settings.colorIntensity < 0 ||
340 !unit(settings.nearIntensity) || !unit(settings.farIntensity))
341 return invalid("terrain.gtsColorMap: matching images, normalized blend and finite settings required");
343 int changed = 0;
344 for (int y = 0; y < output.getHeight(); ++y) for (int x = 0; x < output.getWidth(); ++x) {
345 const float distance = globalBlendDistance.height(x, y);
346 if (!unit(distance)) return invalid("terrain.gtsColorMap: normalized blend raster required");
347 const auto base = output.getPixel(x, y), map = colorMap.getPixel(x, y);
348 const float alpha = std::clamp(map.a * settings.alphaIntensity, 0.0F, 1.0F);
349 const float distanceIntensity = settings.nearIntensity +
350 (settings.farIntensity - settings.nearIntensity) * distance;
351 const float blend = alpha * distanceIntensity;
352 auto color = base;
353 color.r += (std::clamp(map.r * settings.colorIntensity, 0.0F, 1.0F) - color.r) * blend;
354 color.g += (std::clamp(map.g * settings.colorIntensity, 0.0F, 1.0F) - color.g) * blend;
355 color.b += (std::clamp(map.b * settings.colorIntensity, 0.0F, 1.0F) - color.b) * blend;
356 changed += color.r != base.r || color.g != base.g || color.b != base.b;
357 next.setPixel(x, y, color);
358 }
359 output.adopt(next);
360 return Result<int>::success(changed);
361}
362
364 const GtsMacroVariationSettings& settings, double originX,
365 double originZ, double spacingX, double spacingZ) {
366 using namespace raster_detail;
367 if (!validImage(output, true) || !validImage(variationMap) || !std::isfinite(settings.sizeA) ||
368 !std::isfinite(settings.sizeB) || !std::isfinite(settings.sizeC) || settings.sizeA <= 0 ||
369 settings.sizeB <= 0 || settings.sizeC <= 0 || !std::isfinite(settings.intensity) ||
370 settings.intensity < 0 || settings.intensity > 1 || !std::isfinite(originX) ||
371 !std::isfinite(originZ) || !std::isfinite(spacingX) || spacingX <= 0 ||
372 !std::isfinite(spacingZ) || spacingZ <= 0)
373 return invalid("terrain.gtsMacroVariation: readable images and finite profile settings required");
375 int changed = 0;
376 for (int y = 0; y < output.getHeight(); ++y) for (int x = 0; x < output.getWidth(); ++x) {
377 const double px = (settings.objectSpace ? 0.0 : originX) + x * spacingX;
378 const double pz = (settings.objectSpace ? 0.0 : originZ) + y * spacingZ;
379 const float a = repeatBilinear(variationMap, px * settings.sizeA / 1000.0, pz * settings.sizeA / 1000.0).r;
380 const float b = repeatBilinear(variationMap, px * settings.sizeB / 10000.0, pz * settings.sizeB / 10000.0).r;
381 const float c = repeatBilinear(variationMap, px * settings.sizeC / 100000.0, pz * settings.sizeC / 100000.0).r;
382 const float variation = std::clamp((a + 0.5F) * (b + 0.5F) * (c + 0.5F), 0.0F, 1.0F);
383 const float multiplier = settings.intensity + (1 - settings.intensity) * variation;
384 const auto base = output.getPixel(x, y);
385 auto color = base; color.r *= multiplier; color.g *= multiplier; color.b *= multiplier;
386 changed += color.r != base.r || color.g != base.g || color.b != base.b;
387 next.setPixel(x, y, color);
388 }
389 output.adopt(next);
390 return Result<int>::success(changed);
391}
392
394 const Heightmap& heights, const Heightmap& layerStrength,
395 const Heightmap& globalBlendDistance, const image::ImageData& geoAlbedo,
396 const image::ImageData& geoNormal, const GtsGeologicalSettings& settings,
397 double originY) {
398 using namespace raster_detail;
399 const int width = heights.getWidth(), height = heights.getHeight();
400 const auto sameRaster = [&](const Heightmap& value) {
401 return validRaster(value) && value.getWidth() == width && value.getHeight() == height;
402 };
403 const auto sameImage = [&](const image::ImageData& value, bool writable) {
404 return validImage(value, writable) && value.getWidth() == width && value.getHeight() == height;
405 };
406 const auto unit = [](float value) { return std::isfinite(value) && value >= 0 && value <= 1; };
407 if (!validRaster(heights) || !sameRaster(layerStrength) || !sameRaster(globalBlendDistance) ||
408 !sameImage(albedo, true) || !sameImage(packedNormal, true) || !validImage(geoAlbedo) ||
409 !validImage(geoNormal) || !std::isfinite(originY) || !unit(settings.nearStrength) ||
410 !unit(settings.farStrength) || !std::isfinite(settings.nearNormalStrength) ||
411 settings.nearNormalStrength < 0 || !std::isfinite(settings.farNormalStrength) ||
412 settings.farNormalStrength < 0 || !std::isfinite(settings.nearScale) || settings.nearScale <= 0 ||
413 !std::isfinite(settings.farScale) || settings.farScale <= 0 ||
414 !std::isfinite(settings.nearOffset) || !std::isfinite(settings.farOffset))
415 return invalid("terrain.gtsGeological: matching outputs, rasters, textures and finite settings required");
416 image::ImageData nextAlbedo(albedo), nextNormal(packedNormal);
417 int changed = 0;
418 for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) {
419 const float strength = layerStrength.height(x, y), distance = globalBlendDistance.height(x, y);
420 if (!unit(strength) || !unit(distance))
421 return invalid("terrain.gtsGeological: normalized strength and blend rasters required");
422 if (!settings.enabled) continue;
423 const double vertical = heights.height(x, y) + (settings.objectSpace ? 0.0 : originY);
424 const auto nearColor = repeatBilinear(geoAlbedo, 0, vertical / settings.nearScale + settings.nearOffset);
425 const auto farColor = repeatBilinear(geoAlbedo, 0, vertical / settings.farScale + settings.farOffset);
426 const auto nearNormal = repeatBilinear(geoNormal, 0, vertical / settings.nearScale + settings.nearOffset);
427 const auto farNormal = repeatBilinear(geoNormal, 0, vertical / settings.farScale + settings.farOffset);
428 const auto baseColor = albedo.getPixel(x, y);
429 auto color = baseColor;
430 const auto overlay = [&](float nearValue, float farValue) {
431 const float a = (nearValue * 2 - 0.3F) * settings.nearStrength;
432 const float b = (farValue * 2 - 0.3F) * settings.farStrength;
433 return (a + (b - a) * distance) * strength;
434 };
435 color.r = std::clamp(color.r + overlay(nearColor.r, farColor.r), 0.0F, 1.0F);
436 color.g = std::clamp(color.g + overlay(nearColor.g, farColor.g), 0.0F, 1.0F);
437 color.b = std::clamp(color.b + overlay(nearColor.b, farColor.b), 0.0F, 1.0F);
438 const auto basePacked = packedNormal.getPixel(x, y);
439 const float nearNx = (nearNormal.a * 2 - 1) * settings.nearNormalStrength;
440 const float nearNy = (nearNormal.g * 2 - 1) * settings.nearNormalStrength;
441 const float farNx = (farNormal.a * 2 - 1) * settings.farNormalStrength;
442 const float farNy = (farNormal.g * 2 - 1) * settings.farNormalStrength;
443 float nx = basePacked.r * 2 - 1 + (nearNx + (farNx - nearNx) * distance) * strength;
444 float ny = basePacked.g * 2 - 1 + (nearNy + (farNy - nearNy) * distance) * strength;
445 const float nz = std::sqrt(1 - std::clamp(nx * nx + ny * ny, 0.0F, 1.0F));
446 const float length = std::sqrt(nx * nx + ny * ny + nz * nz);
447 nx /= length; ny /= length;
448 auto normal = basePacked; normal.r = nx * 0.5F + 0.5F; normal.g = ny * 0.5F + 0.5F;
449 changed += color.r != baseColor.r || color.g != baseColor.g || color.b != baseColor.b;
450 changed += normal.r != basePacked.r || normal.g != basePacked.g;
451 nextAlbedo.setPixel(x, y, color); nextNormal.setPixel(x, y, normal);
452 }
453 albedo.adopt(nextAlbedo); packedNormal.adopt(nextNormal);
454 return Result<int>::success(changed);
455}
456
458 Heightmap& detailGreyscale, const Heightmap& layerStrength,
459 const Heightmap& globalBlendDistance, const image::ImageData& detailNormal,
460 const GtsDetailNormalSettings& settings, double originX, double originZ,
461 double spacingX, double spacingZ) {
462 using namespace raster_detail;
463 const int width = albedo.getWidth(), height = albedo.getHeight();
464 const auto rasterMatches = [&](const Heightmap& value) {
465 return validRaster(value) && value.getWidth() == width && value.getHeight() == height;
466 };
467 const auto unit = [](float value) { return std::isfinite(value) && value >= 0 && value <= 1; };
468 if (!validImage(albedo, true) || !validImage(packedNormal, true) || packedNormal.getWidth() != width ||
469 packedNormal.getHeight() != height || !rasterMatches(detailGreyscale) || !rasterMatches(layerStrength) ||
470 !rasterMatches(globalBlendDistance) || !validImage(detailNormal) || !std::isfinite(settings.nearTiling) ||
471 settings.nearTiling <= 0 || !std::isfinite(settings.farTiling) || settings.farTiling <= 0 ||
472 !std::isfinite(settings.nearStrength) || settings.nearStrength < 0 ||
473 !std::isfinite(settings.farStrength) || settings.farStrength < 0 || !std::isfinite(originX) ||
474 !std::isfinite(originZ) || !std::isfinite(spacingX) || spacingX <= 0 ||
475 !std::isfinite(spacingZ) || spacingZ <= 0)
476 return invalid("terrain.gtsDetail: matching outputs, rasters, texture and finite settings required");
477 image::ImageData nextAlbedo(albedo), nextNormal(packedNormal);
478 Heightmap nextGreyscale(detailGreyscale);
479 int changed = 0;
480 for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) {
481 const float strength = layerStrength.height(x, y), distance = globalBlendDistance.height(x, y);
482 if (!unit(strength) || !unit(distance))
483 return invalid("terrain.gtsDetail: normalized strength and blend rasters required");
484 const double px = (settings.objectSpace ? 0.0 : originX) + x * spacingX;
485 const double pz = (settings.objectSpace ? 0.0 : originZ) + y * spacingZ;
486 float nx = 0, ny = 0, greyscale = 0;
487 if (settings.enabled) {
488 const auto nearSample = repeatBilinear(detailNormal, px / settings.nearTiling, pz / settings.nearTiling);
489 const auto farSample = repeatBilinear(detailNormal, px / settings.farTiling, pz / settings.farTiling);
490 const float nearX = (nearSample.r * 2 - 1) * settings.nearStrength;
491 const float nearY = (nearSample.g * 2 - 1) * settings.nearStrength;
492 const float farX = (farSample.r * 2 - 1) * settings.farStrength;
493 const float farY = (farSample.g * 2 - 1) * settings.farStrength;
494 nx = nearX + (farX - nearX) * distance;
495 ny = nearY + (farY - nearY) * distance;
496 greyscale = std::clamp(nx * nx + ny * ny, 0.0F, 1.0F);
497 }
498 const auto baseColor = albedo.getPixel(x, y), basePacked = packedNormal.getPixel(x, y);
499 auto color = baseColor;
500 const float shade = 1 - 0.5F * greyscale * strength;
501 color.r *= shade; color.g *= shade; color.b *= shade;
502 float combinedX = basePacked.r * 2 - 1 + nx * strength;
503 float combinedY = basePacked.g * 2 - 1 + ny * strength;
504 const float combinedZ = std::sqrt(1 - std::clamp(combinedX * combinedX + combinedY * combinedY, 0.0F, 1.0F));
505 const float length = std::sqrt(combinedX * combinedX + combinedY * combinedY + combinedZ * combinedZ);
506 combinedX /= length; combinedY /= length;
507 auto normal = basePacked;
508 normal.r = combinedX * 0.5F + 0.5F; normal.g = combinedY * 0.5F + 0.5F;
509 changed += color.r != baseColor.r || color.g != baseColor.g || color.b != baseColor.b;
510 changed += normal.r != basePacked.r || normal.g != basePacked.g;
511 changed += detailGreyscale.height(x, y) != greyscale;
512 nextAlbedo.setPixel(x, y, color); nextNormal.setPixel(x, y, normal);
513 nextGreyscale.setHeight(x, y, greyscale);
514 }
515 albedo.adopt(nextAlbedo); packedNormal.adopt(nextNormal); detailGreyscale.data().swap(nextGreyscale.data());
516 return Result<int>::success(changed);
517}
518
520 const image::ImageData& snowAlbedo, const image::ImageData& snowMask,
521 const GtsSnowSurfaceSettings& snow, const GtsRainSurfaceSettings& rain,
522 double originX, double originZ, double spacingX, double spacingZ,
523 const Heightmap* detailGreyscale) {
524 using namespace raster_detail;
525 auto unit = [](float value) { return std::isfinite(value) && value >= 0 && value <= 1; };
526 if (!validImage(output, true) || !validImage(snowAlbedo) || !validImage(snowMask) || !validRaster(heights) ||
527 output.getWidth() != heights.getWidth() || output.getHeight() != heights.getHeight() ||
528 !std::isfinite(originX) || !std::isfinite(originZ) || !std::isfinite(spacingX) || spacingX <= 0 ||
529 !std::isfinite(spacingZ) || spacingZ <= 0 || !unit(snow.power) || !unit(snow.age) ||
530 !std::isfinite(snow.minimumHeight) || !std::isfinite(snow.blendRange) || snow.blendRange < 0 ||
531 !std::isfinite(snow.slopeBlend) || snow.slopeBlend < 0 || !std::isfinite(snow.scale) || snow.scale <= 0 ||
532 !unit(snow.colorR) || !unit(snow.colorG) || !unit(snow.colorB) || !unit(rain.power) ||
533 (detailGreyscale && (!validRaster(*detailGreyscale) || detailGreyscale->getWidth() != output.getWidth() ||
534 detailGreyscale->getHeight() != output.getHeight())) ||
535 !std::isfinite(rain.minimumHeight) || !std::isfinite(rain.maximumHeight) ||
536 rain.maximumHeight < rain.minimumHeight || !unit(rain.darkness))
537 return invalid("terrain.gtsWeather: finite matching images, heights and profile settings required");
539 int changed = 0;
540 for (int z = 0; z < heights.getHeight(); ++z) for (int x = 0; x < heights.getWidth(); ++x) {
541 const float h = heights.height(x, z);
542 const float normalY = terrainNormal(heights, x, z, spacingX, spacingZ).y;
543 auto color = output.getPixel(x, z);
544 if (snow.enabled) {
545 const auto sampled = repeatBilinear(snowAlbedo, (originX + x * spacingX) / snow.scale,
546 (originZ + z * spacingZ) / snow.scale);
547 const float detail = detailGreyscale ? detailGreyscale->height(x, z) : 0;
548 if (!unit(detail)) return invalid("terrain.gtsWeather: normalized detail greyscale required");
549 const float snowDetail = 1 - 0.8F * detail;
550 const float start = smoothStep(-snow.blendRange, snow.blendRange, h - snow.minimumHeight);
551 const float slopeAge = 1 + 9 * snow.age;
552 const float slope = std::clamp(std::pow(std::abs(normalY), snow.slopeBlend * slopeAge) * 4, 0.0F, 1.0F);
553 const float mask = start * slope * (1 - snow.age) * snow.power;
554 color.r += (sampled.r * snowDetail * snow.colorR - color.r) * mask;
555 color.g += (sampled.g * snowDetail * snow.colorG - color.g) * mask;
556 color.b += (sampled.b * snowDetail * snow.colorB - color.b) * mask;
557 }
558 if (rain.enabled) {
559 const float start = smoothStep(-30, 30, h - rain.minimumHeight);
560 const float end = smoothStep(-30, 30, h - rain.maximumHeight);
561 const float mask = std::min(rain.power, 0.9F) * start * (1 - end);
562 const float dark = 1 - rain.darkness;
563 color.r += (color.r * dark - color.r) * mask;
564 color.g += (color.g * dark - color.g) * mask;
565 color.b += (color.b * dark - color.b) * mask;
566 }
567 const auto old = output.getPixel(x, z);
568 changed += old.r != color.r || old.g != color.g || old.b != color.b || old.a != color.a;
569 next.setPixel(x, z, color);
570 }
571 output.adopt(next);
572 return Result<int>::success(changed);
573}
574
576 Heightmap& displacement, Heightmap& tessellation, const Heightmap& heights,
577 const image::ImageData& snowNormal, const image::ImageData& snowMask,
578 const image::ImageData& rainData, const GtsSnowSurfaceSettings& snow,
579 const GtsRainSurfaceSettings& rain, double timeSeconds, double originX,
580 double originZ, double spacingX, double spacingZ) {
581 using namespace raster_detail;
582 const int width = heights.getWidth(), height = heights.getHeight();
583 auto matches = [&](const image::ImageData& image) {
584 return validImage(image, true) && image.getWidth() == width && image.getHeight() == height;
585 };
586 auto finiteArray = [](const auto& values) {
587 return std::all_of(values.begin(), values.end(), [](float value) { return std::isfinite(value); });
588 };
589 if (!validRaster(heights) || !validRaster(displacement) || !validRaster(tessellation) ||
590 displacement.getWidth() != width || displacement.getHeight() != height ||
591 tessellation.getWidth() != width || tessellation.getHeight() != height || !matches(normalOutput) ||
592 !matches(maskOutput) || !validImage(snowNormal) || !validImage(snowMask) || !validImage(rainData) ||
593 !std::isfinite(timeSeconds) || !std::isfinite(originX) || !std::isfinite(originZ) ||
594 !std::isfinite(spacingX) || spacingX <= 0 || !std::isfinite(spacingZ) || spacingZ <= 0 ||
595 !std::isfinite(snow.normalStrength) || snow.normalStrength < 0 || !finiteArray(snow.maskRemapMin) ||
596 !finiteArray(snow.maskRemapMax) || !std::isfinite(snow.heightContrast) || snow.heightContrast < 0 ||
597 !std::isfinite(snow.heightBrightness) || !std::isfinite(snow.heightIncrease) ||
598 !std::isfinite(snow.displacementContrast) || snow.displacementContrast < 0 ||
599 !std::isfinite(snow.displacementBrightness) || !std::isfinite(snow.displacementIncrease) ||
600 !std::isfinite(snow.tessellationAmount) || !std::isfinite(rain.speed) ||
601 !std::isfinite(rain.smoothness) || rain.smoothness < 0 || rain.smoothness > 1 ||
602 !std::isfinite(rain.scale) || rain.scale <= 0)
603 return invalid("terrain.gtsWeatherPbr: finite matching outputs, textures and profile settings required");
604 GtsSnowSurfaceSettings albedoSnow = snow;
605 GtsRainSurfaceSettings albedoRain = rain;
606 if (!std::isfinite(albedoSnow.scale) || albedoSnow.scale <= 0 || !std::isfinite(albedoSnow.minimumHeight) ||
607 !std::isfinite(albedoSnow.blendRange) || albedoSnow.blendRange < 0 ||
608 !std::isfinite(albedoSnow.slopeBlend) || albedoSnow.slopeBlend < 0 || albedoSnow.power < 0 ||
609 albedoSnow.power > 1 || albedoSnow.age < 0 || albedoSnow.age > 1 || albedoRain.power < 0 ||
610 albedoRain.power > 1 || !std::isfinite(albedoRain.minimumHeight) ||
611 !std::isfinite(albedoRain.maximumHeight) || albedoRain.maximumHeight < albedoRain.minimumHeight)
612 return invalid("terrain.gtsWeatherPbr: invalid snow or rain range");
613
614 image::ImageData nextNormal(normalOutput), nextMask(maskOutput);
615 Heightmap nextDisplacement(displacement), nextTessellation(tessellation);
616 int changed = 0;
617 auto rainSample = [&](double u, double v) {
618 const auto points = repeatBilinear(rainData, u, v);
619 const float phase[4]{points.r, points.g, points.b, points.a};
620 const float offsets[4]{0, 0.5F, 0.75F, 0.25F};
621 float sum = 0;
622 for (int i = 0; i < 4; ++i) {
623 const double cycle = timeSeconds * rain.speed + offsets[i];
624 const float t = static_cast<float>(cycle - std::floor(cycle));
625 sum += std::clamp(std::sin(-20 * t * phase[i]) * std::sin(t * 3.1415F), 0.0F, 1.0F);
626 }
627 return std::clamp(sum, 0.0F, 1.0F);
628 };
629 for (int z = 0; z < height; ++z) for (int x = 0; x < width; ++x) {
630 const float h = heights.height(x, z);
631 const double worldX = originX + x * spacingX, worldZ = originZ + z * spacingZ;
632 const Vec3 terrain = terrainNormal(heights, x, z, spacingX, spacingZ);
633 const auto encoded = normalOutput.getPixel(x, z);
634 Vec3 tangent, bitangent;
635 terrainFrame(terrain, tangent, bitangent);
636 const float baseX = encoded.r * 2 - 1, baseY = encoded.g * 2 - 1;
637 const Vec3 baseTangent = normalize({baseX, baseY,
638 std::sqrt(1 - std::clamp(baseX * baseX + baseY * baseY, 0.0F, 1.0F))});
639 Vec3 normal = normalize({baseTangent.x * tangent.x + baseTangent.y * bitangent.x + baseTangent.z * terrain.x,
640 baseTangent.x * tangent.y + baseTangent.y * bitangent.y + baseTangent.z * terrain.y,
641 baseTangent.x * tangent.z + baseTangent.y * bitangent.z + baseTangent.z * terrain.z});
642 auto packed = maskOutput.getPixel(x, z);
643 float displaced = displacement.height(x, z), tessellated = tessellation.height(x, z);
644 if (snow.enabled) {
645 const double u = worldX / snow.scale, v = worldZ / snow.scale;
646 auto sampledNormal = repeatBilinear(snowNormal, u, v);
647 const float nx = (sampledNormal.r * sampledNormal.a) * 2 - 1;
648 const float ny = sampledNormal.g * 2 - 1;
649 Vec3 snowN{nx * snow.normalStrength, ny * snow.normalStrength,
650 std::sqrt(1 - std::clamp(nx * nx + ny * ny, 0.0F, 1.0F))};
651 auto sampledMask = repeatBilinear(snowMask, u, v);
652 float channels[4]{sampledMask.r, sampledMask.g, sampledMask.b, sampledMask.a};
653 for (int i = 0; i < 4; ++i)
654 channels[i] = snow.maskRemapMin[i] + channels[i] * (snow.maskRemapMax[i] - snow.maskRemapMin[i]);
655 channels[2] = std::pow(std::abs(channels[2]), snow.heightContrast) * snow.heightBrightness +
656 snow.heightIncrease;
657 const float start = smoothStep(-snow.blendRange, snow.blendRange, h - snow.minimumHeight);
658 const float slopeAge = 1 + 9 * snow.age;
659 const float slope = std::clamp(std::pow(std::abs(terrain.y), snow.slopeBlend * slopeAge) * 4, 0.0F, 1.0F);
660 const float snowBlend = start * slope * (1 - snow.age) * snow.power;
661 packed.r += (channels[0] - packed.r) * snowBlend;
662 packed.g += (channels[1] - packed.g) * snowBlend;
663 packed.b += (channels[2] - packed.b) * snowBlend;
664 packed.a += (channels[3] - packed.a) * snowBlend;
665 snowN = blendWorldNormal(terrain, snowN);
666 normal = {normal.x + (snowN.x - normal.x) * snowBlend,
667 normal.y + (snowN.y - normal.y) * snowBlend,
668 normal.z + (snowN.z - normal.z) * snowBlend};
669 const float slopeLod = std::clamp(std::pow(std::abs(terrain.y), snow.slopeBlend) * 4, 0.0F, 1.0F);
670 const float lodBlend = start * slopeLod * (1 - snow.age) * snow.power;
671 const float snowDisplacement = std::pow(std::abs(channels[2]), snow.displacementContrast) *
673 displaced += (snowDisplacement * snow.power - displaced) * lodBlend;
674 tessellated += (snow.tessellationAmount * snow.power - tessellated) * lodBlend;
675 }
676 if (rain.enabled) {
677 const float start = smoothStep(-30, 30, h - rain.minimumHeight);
678 const float end = smoothStep(-30, 30, h - rain.maximumHeight);
679 const float rainMask = std::min(rain.power, 0.9F) * start * (1 - end);
680 const float normalY2 = terrain.y * terrain.y;
681 const float topMask = std::clamp(normalY2 * normalY2, 0.0F, 1.0F);
682 const double u = worldX / rain.scale, v = worldZ / rain.scale;
683 const float center = rainSample(u, v), xd = center - rainSample(u + 0.001, v);
684 const float yd = center - rainSample(u, v + 0.001), strength = 0.6F * rainMask;
685 Vec3 rainN{yd * strength, -xd * yd * strength, 1 + (xd - 1) * std::clamp(strength, 0.0F, 1.0F)};
686 const Vec3 rainBlend = blendWorldNormal(normal, rainN);
687 const float amount = rainMask * topMask;
688 normal = {normal.x + (rainBlend.x - normal.x) * amount,
689 normal.y + (rainBlend.y - normal.y) * amount,
690 normal.z + (rainBlend.z - normal.z) * amount};
691 const float rainSmoothness = 0.4F + (rain.smoothness - 0.4F) * topMask;
692 packed.a += (rainSmoothness - packed.a) * rainMask;
693 }
694 const Vec3 finalWorld = normalize(normal);
695 const Vec3 finalTangent{dot(finalWorld, tangent), dot(finalWorld, bitangent), dot(finalWorld, terrain)};
696 const image::ImageData::Colorf nextEncoded{finalTangent.x * 0.5F + 0.5F,
697 finalTangent.y * 0.5F + 0.5F,
698 packed.g, packed.a};
699 if (!std::isfinite(nextEncoded.r) || !std::isfinite(nextEncoded.g) || !std::isfinite(nextEncoded.b) ||
700 !std::isfinite(packed.r) || !std::isfinite(packed.g) || !std::isfinite(packed.b) ||
701 !std::isfinite(packed.a) || !std::isfinite(displaced) || !std::isfinite(tessellated))
702 return invalid("terrain.gtsWeatherPbr: profile produced a nonfinite output");
703 const auto oldMask = maskOutput.getPixel(x, z);
704 changed += encoded.r != nextEncoded.r || encoded.g != nextEncoded.g || encoded.b != nextEncoded.b;
705 changed += oldMask.r != packed.r || oldMask.g != packed.g || oldMask.b != packed.b || oldMask.a != packed.a;
706 changed += displacement.height(x, z) != displaced;
707 changed += tessellation.height(x, z) != tessellated;
708 nextNormal.setPixel(x, z, nextEncoded); nextMask.setPixel(x, z, packed);
709 nextDisplacement.setHeight(x, z, displaced); nextTessellation.setHeight(x, z, tessellated);
710 }
711 normalOutput.adopt(nextNormal); maskOutput.adopt(nextMask);
712 displacement = std::move(nextDisplacement); tessellation = std::move(nextTessellation);
713 return Result<int>::success(changed);
714}
715
718 TerrainMaskBlend mode) {
719 using namespace raster_detail;
720 const int width = image.getWidth(), height = image.getHeight();
721 if (!validRaster(target) || !validRaster(input) || !validRaster(curve) || target.getWidth() != input.getWidth() ||
722 target.getHeight() != input.getHeight() || curve.getHeight() != 1 || width <= 0 || height <= 0 ||
723 width > INT_MAX / height || !image.getData() || !image.getPixelGetFunction())
724 return invalid("terrain.imageAdapter: finite masks and readable image storage required");
725 const size_t pixelSize = image.getPixelSize();
726 if (pixelSize == 0 || pixelSize > sizeof(image::ImageData::Pixel) ||
727 size_t(width) * height > image.getSize() / pixelSize)
728 return invalid("terrain.imageAdapter: incomplete or unsupported image storage");
729 Heightmap red(width, height), green(width, height), blue(width, height), alpha(width, height);
730 for (int z = 0; z < height; ++z)
731 for (int x = 0; x < width; ++x) {
732 const auto color = image.getPixel(x, z);
733 if (!std::isfinite(color.r) || !std::isfinite(color.g) || !std::isfinite(color.b) ||
734 !std::isfinite(color.a))
735 return invalid("terrain.imageAdapter: non-finite image pixel");
736 const size_t i = size_t(z) * width + x;
737 red.data()[i] = color.r;
738 green.data()[i] = color.g;
739 blue.data()[i] = color.b;
740 alpha.data()[i] = color.a;
741 }
742 return generateTerrainImageMask(target, input, red, green, blue, alpha, curve, settings, mode);
743}
745 image::ImageData& output, const image::ImageData& red, const image::ImageData& green,
746 const image::ImageData& blue, const image::ImageData& alpha, std::uint32_t activeChannels) {
747 if (activeChannels > 15 || !validImage(output, true) || !validImage(red) || !validImage(green) ||
748 !validImage(blue) || !validImage(alpha))
749 return raster_detail::invalid("terrain.maskMapExport: readable RGBA channel images required");
750 const int width = output.getWidth(), height = output.getHeight();
751 const image::ImageData* sources[] = {&red, &green, &blue, &alpha};
752 for (const auto* source : sources)
753 if (source->getWidth() != width || source->getHeight() != height)
754 return raster_detail::invalid("terrain.maskMapExport: channel dimensions must match output");
756 int changed = 0;
757 for (int y = 0; y < height; ++y)
758 for (int x = 0; x < width; ++x) {
759 image::ImageData::Colorf combined{0, 0, 0, 0};
760 float* channels[] = {&combined.r, &combined.g, &combined.b, &combined.a};
761 for (std::uint32_t channel = 0; channel < 4; ++channel)
762 if (activeChannels & (1u << channel))
763 *channels[channel] = std::clamp(sources[channel]->getPixel(x, y).r, 0.0F, 1.0F);
764 const auto before = output.getPixel(x, y);
765 changed += before.r != combined.r || before.g != combined.g ||
766 before.b != combined.b || before.a != combined.a;
767 next.setPixel(x, y, combined);
768 }
769 output.adopt(next);
770 return Result<int>::success(changed);
771}
772
774 image::ImageData& output, const image::ImageData& tile, int destinationX, int destinationY,
775 int destinationWidth, int destinationHeight) {
776 if (!validImage(output, true) || !validImage(tile) || destinationX < 0 || destinationY < 0 ||
777 destinationWidth <= 0 || destinationHeight <= 0 ||
778 destinationX > output.getWidth() - destinationWidth ||
779 destinationY > output.getHeight() - destinationHeight)
780 return raster_detail::invalid("terrain.maskMapExport: tile rectangle is invalid");
781 auto sample = [&](double u, double v) {
782 const double px = u * tile.getWidth() - 0.5;
783 const double py = v * tile.getHeight() - 0.5;
784 const int x0 = std::clamp(static_cast<int>(std::floor(px)), 0, tile.getWidth() - 1);
785 const int y0 = std::clamp(static_cast<int>(std::floor(py)), 0, tile.getHeight() - 1);
786 const int x1 = std::min(x0 + 1, tile.getWidth() - 1);
787 const int y1 = std::min(y0 + 1, tile.getHeight() - 1);
788 const float tx = std::clamp(static_cast<float>(px - std::floor(px)), 0.0F, 1.0F);
789 const float ty = std::clamp(static_cast<float>(py - std::floor(py)), 0.0F, 1.0F);
790 const auto a = tile.getPixel(x0, y0), b = tile.getPixel(x1, y0);
791 const auto c = tile.getPixel(x0, y1), d = tile.getPixel(x1, y1);
792 auto mix = [&](float av, float bv, float cv, float dv) {
793 return (av + (bv - av) * tx) * (1 - ty) + (cv + (dv - cv) * tx) * ty;
794 };
795 return image::ImageData::Colorf{mix(a.r, b.r, c.r, d.r), mix(a.g, b.g, c.g, d.g),
796 mix(a.b, b.b, c.b, d.b), mix(a.a, b.a, c.a, d.a)};
797 };
799 int changed = 0;
800 for (int y = 0; y < destinationHeight; ++y)
801 for (int x = 0; x < destinationWidth; ++x) {
802 const auto color = sample((x + 0.5) / destinationWidth, (y + 0.5) / destinationHeight);
803 const int atlasX = destinationX + x, atlasY = destinationY + y;
804 const auto before = output.getPixel(atlasX, atlasY);
805 changed += before.r != color.r || before.g != color.g ||
806 before.b != color.b || before.a != color.a;
807 next.setPixel(atlasX, atlasY, color);
808 }
809 output.adopt(next);
810 return Result<int>::success(changed);
811}
812
813void exposeTerrainImageAdapter(ssq::Table& table) {
814 auto snow = table.addClass("GtsSnowSurfaceSettings", ssq::Class::Ctor<GtsSnowSurfaceSettings()>());
815 snow.addVar("enabled", &GtsSnowSurfaceSettings::enabled);
816 snow.addVar("power", &GtsSnowSurfaceSettings::power);
817 snow.addVar("minimumHeight", &GtsSnowSurfaceSettings::minimumHeight);
818 snow.addVar("blendRange", &GtsSnowSurfaceSettings::blendRange);
819 snow.addVar("slopeBlend", &GtsSnowSurfaceSettings::slopeBlend);
820 snow.addVar("age", &GtsSnowSurfaceSettings::age);
821 snow.addVar("scale", &GtsSnowSurfaceSettings::scale);
822 snow.addVar("colorR", &GtsSnowSurfaceSettings::colorR);
823 snow.addVar("colorG", &GtsSnowSurfaceSettings::colorG);
824 snow.addVar("colorB", &GtsSnowSurfaceSettings::colorB);
825 snow.addVar("normalStrength", &GtsSnowSurfaceSettings::normalStrength);
826 snow.addVar("heightContrast", &GtsSnowSurfaceSettings::heightContrast);
827 snow.addVar("heightBrightness", &GtsSnowSurfaceSettings::heightBrightness);
828 snow.addVar("heightIncrease", &GtsSnowSurfaceSettings::heightIncrease);
829 snow.addVar("displacementContrast", &GtsSnowSurfaceSettings::displacementContrast);
830 snow.addVar("displacementBrightness", &GtsSnowSurfaceSettings::displacementBrightness);
831 snow.addVar("displacementIncrease", &GtsSnowSurfaceSettings::displacementIncrease);
832 snow.addVar("tessellationAmount", &GtsSnowSurfaceSettings::tessellationAmount);
833 snow.addFunc("setMaskRemapMin", &GtsSnowSurfaceSettings::setMaskRemapMin);
834 snow.addFunc("setMaskRemapMax", &GtsSnowSurfaceSettings::setMaskRemapMax);
835 auto rain = table.addClass("GtsRainSurfaceSettings", ssq::Class::Ctor<GtsRainSurfaceSettings()>());
836 rain.addVar("enabled", &GtsRainSurfaceSettings::enabled);
837 rain.addVar("power", &GtsRainSurfaceSettings::power);
838 rain.addVar("minimumHeight", &GtsRainSurfaceSettings::minimumHeight);
839 rain.addVar("maximumHeight", &GtsRainSurfaceSettings::maximumHeight);
840 rain.addVar("darkness", &GtsRainSurfaceSettings::darkness);
841 rain.addVar("speed", &GtsRainSurfaceSettings::speed);
842 rain.addVar("smoothness", &GtsRainSurfaceSettings::smoothness);
843 rain.addVar("scale", &GtsRainSurfaceSettings::scale);
844 auto colorMap = table.addClass("GtsColorMapSettings", ssq::Class::Ctor<GtsColorMapSettings()>());
845 colorMap.addVar("alphaIntensity", &GtsColorMapSettings::alphaIntensity);
846 colorMap.addVar("colorIntensity", &GtsColorMapSettings::colorIntensity);
847 colorMap.addVar("nearIntensity", &GtsColorMapSettings::nearIntensity);
848 colorMap.addVar("farIntensity", &GtsColorMapSettings::farIntensity);
849 auto variation = table.addClass("GtsMacroVariationSettings", ssq::Class::Ctor<GtsMacroVariationSettings()>());
850 variation.addVar("sizeA", &GtsMacroVariationSettings::sizeA);
851 variation.addVar("sizeB", &GtsMacroVariationSettings::sizeB);
852 variation.addVar("sizeC", &GtsMacroVariationSettings::sizeC);
853 variation.addVar("intensity", &GtsMacroVariationSettings::intensity);
854 variation.addVar("objectSpace", &GtsMacroVariationSettings::objectSpace);
855 auto geological = table.addClass("GtsGeologicalSettings", ssq::Class::Ctor<GtsGeologicalSettings()>());
856 geological.addVar("enabled", &GtsGeologicalSettings::enabled);
857 geological.addVar("objectSpace", &GtsGeologicalSettings::objectSpace);
858 geological.addVar("nearStrength", &GtsGeologicalSettings::nearStrength);
859 geological.addVar("nearNormalStrength", &GtsGeologicalSettings::nearNormalStrength);
860 geological.addVar("nearScale", &GtsGeologicalSettings::nearScale);
861 geological.addVar("nearOffset", &GtsGeologicalSettings::nearOffset);
862 geological.addVar("farStrength", &GtsGeologicalSettings::farStrength);
863 geological.addVar("farNormalStrength", &GtsGeologicalSettings::farNormalStrength);
864 geological.addVar("farScale", &GtsGeologicalSettings::farScale);
865 geological.addVar("farOffset", &GtsGeologicalSettings::farOffset);
866 auto detail = table.addClass("GtsDetailNormalSettings", ssq::Class::Ctor<GtsDetailNormalSettings()>());
867 detail.addVar("enabled", &GtsDetailNormalSettings::enabled);
868 detail.addVar("objectSpace", &GtsDetailNormalSettings::objectSpace);
869 detail.addVar("nearTiling", &GtsDetailNormalSettings::nearTiling);
870 detail.addVar("nearStrength", &GtsDetailNormalSettings::nearStrength);
871 detail.addVar("farTiling", &GtsDetailNormalSettings::farTiling);
872 detail.addVar("farStrength", &GtsDetailNormalSettings::farStrength);
873 auto palette = table.addClass("TerrainSplatPalette", ssq::Class::Ctor<TerrainSplatPalette()>());
874 palette.addFunc("addColor", [vm = table.getHandle()](TerrainSplatPalette* self, float r, float g, float b,
875 float a) {
876 return eve::script::projectResult(vm, self->addColor(r, g, b, a), [](int n) { return eve::Value(n); });
877 });
878 palette.addFunc("getColorCount", [](const TerrainSplatPalette* self) { return self->getColorCount(); });
879 auto packedSettings = table.addClass("GtsPackedLayerSettings", ssq::Class::Ctor<GtsPackedLayerSettings()>());
880 packedSettings.addVar("triPlanar", &GtsPackedLayerSettings::triPlanar);
881 packedSettings.addVar("stochastic", &GtsPackedLayerSettings::stochastic);
882 packedSettings.addVar("tileSizeX", &GtsPackedLayerSettings::tileSizeX); packedSettings.addVar("tileSizeZ", &GtsPackedLayerSettings::tileSizeZ);
883 packedSettings.addVar("offsetX", &GtsPackedLayerSettings::offsetX); packedSettings.addVar("offsetZ", &GtsPackedLayerSettings::offsetZ);
884 packedSettings.addVar("triPlanarSizeX", &GtsPackedLayerSettings::triPlanarSizeX);
885 packedSettings.addVar("triPlanarSizeZ", &GtsPackedLayerSettings::triPlanarSizeZ);
886 packedSettings.addVar("tintR", &GtsPackedLayerSettings::tintR); packedSettings.addVar("tintG", &GtsPackedLayerSettings::tintG);
887 packedSettings.addVar("tintB", &GtsPackedLayerSettings::tintB); packedSettings.addVar("normalStrength", &GtsPackedLayerSettings::normalStrength);
888 packedSettings.addVar("aoMin", &GtsPackedLayerSettings::aoMin); packedSettings.addVar("smoothnessMin", &GtsPackedLayerSettings::smoothnessMin);
889 packedSettings.addVar("aoMax", &GtsPackedLayerSettings::aoMax); packedSettings.addVar("smoothnessMax", &GtsPackedLayerSettings::smoothnessMax);
890 packedSettings.addVar("geoAmount", &GtsPackedLayerSettings::geoAmount); packedSettings.addVar("detailAmount", &GtsPackedLayerSettings::detailAmount);
891 packedSettings.addVar("heightContrast", &GtsPackedLayerSettings::heightContrast);
892 packedSettings.addVar("heightBrightness", &GtsPackedLayerSettings::heightBrightness);
893 packedSettings.addVar("heightIncrease", &GtsPackedLayerSettings::heightIncrease);
894 packedSettings.addVar("displacementContrast", &GtsPackedLayerSettings::displacementContrast);
895 packedSettings.addVar("displacementBrightness", &GtsPackedLayerSettings::displacementBrightness);
896 packedSettings.addVar("displacementIncrease", &GtsPackedLayerSettings::displacementIncrease);
897 packedSettings.addVar("tessellationAmount", &GtsPackedLayerSettings::tessellationAmount);
898 auto packedLayers = table.addClass("GtsPackedLayerSet", ssq::Class::Ctor<GtsPackedLayerSet()>());
899 packedLayers.addFunc("addLayer", [vm = table.getHandle()](GtsPackedLayerSet* self, const image::ImageData* albedo,
900 const image::ImageData* normal, const GtsPackedLayerSettings* settings) {
901 auto result = albedo && normal && settings ? self->addLayer(*albedo, *normal, *settings)
902 : raster_detail::invalid("terrain.gtsPackedLayers: missing input");
903 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
904 });
905 packedLayers.addFunc("getLayerCount", [](const GtsPackedLayerSet* self) { return self->getLayerCount(); });
906 table.addFunc("bakeTerrainSplatAlbedo",
907 [vm = table.getHandle()](image::ImageData* output, const TerrainSplatmap* splatmap,
908 const TerrainSplatPalette* paletteValue) {
909 auto result = output && splatmap && paletteValue ? bakeTerrainSplatAlbedo(*output, *splatmap, *paletteValue)
910 : raster_detail::invalid("terrain.splatAlbedo: missing input");
911 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
912 });
913 table.addFunc("bakeGtsPackedLayers",
914 [vm = table.getHandle()](image::ImageData* albedo, image::ImageData* normal,
915 Heightmap* geo, Heightmap* detail, const Heightmap* heights,
916 const TerrainSplatmap* splat, const GtsPackedLayerSet* layers,
917 float originX, float originY, float originZ, float spacingX,
918 float spacingZ) {
919 auto result = albedo && normal && geo && detail && heights && splat && layers
920 ? bakeGtsPackedLayers(*albedo,*normal,*geo,*detail,*heights,*splat,*layers,
921 originX,originY,originZ,spacingX,spacingZ)
922 : raster_detail::invalid("terrain.gtsPackedLayers: missing input");
923 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
924 });
925 table.addFunc("bakeGtsPackedLayerDisplacement",
926 [vm = table.getHandle()](Heightmap* displacement, Heightmap* tessellation,
927 const Heightmap* heights, const TerrainSplatmap* splat,
928 const GtsPackedLayerSet* layers, float cameraX, float cameraY,
929 float cameraZ, float multiplier, float originX, float originY,
930 float originZ, float spacingX, float spacingZ) {
931 auto result = displacement && tessellation && heights && splat && layers
932 ? bakeGtsPackedLayerDisplacement(*displacement, *tessellation, *heights, *splat,
933 *layers, cameraX, cameraY, cameraZ, multiplier,
934 originX, originY, originZ, spacingX, spacingZ)
935 : raster_detail::invalid("terrain.gtsLayerDisplacement: missing input");
936 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
937 });
938 table.addFunc("generateGtsGlobalBlendDistance",
939 [vm = table.getHandle()](Heightmap* output, const Heightmap* heights, float cameraX,
940 float cameraY, float cameraZ, float blendDistance, float blendRange,
941 float originX, float originY, float originZ, float spacingX,
942 float spacingZ) {
943 auto result = output && heights
944 ? generateGtsGlobalBlendDistance(*output, *heights, cameraX, cameraY, cameraZ,
945 blendDistance, blendRange, originX, originY, originZ,
947 : raster_detail::invalid("terrain.gtsGlobalBlend: missing input");
948 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
949 });
950 table.addFunc("placePcgMaskMapTileInto",
951 [vm = table.getHandle()](image::ImageData* output, const image::ImageData* tile,
952 int x, int y, int width, int height) {
953 auto result = output && tile
955 : raster_detail::invalid("terrain.maskMapExport: missing tile input");
956 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
957 });
958 table.addFunc("combinePcgMaskMapChannels",
959 [vm = table.getHandle()](image::ImageData* output, const image::ImageData* red,
960 const image::ImageData* green, const image::ImageData* blue,
961 const image::ImageData* alpha, int activeChannels) {
962 auto result = output && red && green && blue && alpha
963 ? combinePcgMaskMapChannels(*output, *red, *green, *blue, *alpha,
964 static_cast<std::uint32_t>(activeChannels))
965 : raster_detail::invalid("terrain.maskMapExport: missing input");
966 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
967 });
968 table.addFunc("bakeGtsWeatherAlbedo",
969 [vm = table.getHandle()](image::ImageData* output, const Heightmap* heights,
970 const image::ImageData* snowAlbedo,
971 const image::ImageData* snowMask,
972 const GtsSnowSurfaceSettings* snowSettings,
973 const GtsRainSurfaceSettings* rainSettings,
974 float originX, float originZ, float spacingX, float spacingZ) {
975 auto result = output && heights && snowAlbedo && snowMask && snowSettings && rainSettings
976 ? bakeGtsWeatherAlbedo(*output, *heights, *snowAlbedo, *snowMask, *snowSettings,
977 *rainSettings, originX, originZ, spacingX, spacingZ)
978 : raster_detail::invalid("terrain.gtsWeather: missing input");
979 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
980 });
981 table.addFunc("bakeGtsColorMapAlbedo",
982 [vm = table.getHandle()](image::ImageData* output, const image::ImageData* map,
983 const Heightmap* blend, const GtsColorMapSettings* settings) {
984 auto result = output && map && blend && settings
986 : raster_detail::invalid("terrain.gtsColorMap: missing input");
987 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
988 });
989 table.addFunc("bakeGtsMacroVariationAlbedo",
990 [vm = table.getHandle()](image::ImageData* output, const image::ImageData* map,
991 const GtsMacroVariationSettings* settings, float originX,
992 float originZ, float spacingX, float spacingZ) {
993 auto result = output && map && settings
994 ? bakeGtsMacroVariationAlbedo(*output, *map, *settings, originX, originZ, spacingX, spacingZ)
995 : raster_detail::invalid("terrain.gtsMacroVariation: missing input");
996 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
997 });
998 table.addFunc("bakeGtsGeologicalSurface",
999 [vm = table.getHandle()](image::ImageData* albedo, image::ImageData* normal,
1000 const Heightmap* heights, const Heightmap* strength,
1001 const Heightmap* blend, const image::ImageData* geoAlbedo,
1002 const image::ImageData* geoNormal,
1003 const GtsGeologicalSettings* settings, float originY) {
1004 auto result = albedo && normal && heights && strength && blend && geoAlbedo && geoNormal && settings
1006 *geoAlbedo, *geoNormal, *settings, originY)
1007 : raster_detail::invalid("terrain.gtsGeological: missing input");
1008 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
1009 });
1010 table.addFunc("bakeGtsDetailSurface",
1011 [vm = table.getHandle()](image::ImageData* albedo, image::ImageData* normal,
1012 Heightmap* greyscale, const Heightmap* strength,
1013 const Heightmap* blend, const image::ImageData* detailNormal,
1014 const GtsDetailNormalSettings* settings, float originX,
1015 float originZ, float spacingX, float spacingZ) {
1016 auto result = albedo && normal && greyscale && strength && blend && detailNormal && settings
1017 ? bakeGtsDetailSurface(*albedo, *normal, *greyscale, *strength, *blend,
1018 *detailNormal, *settings, originX, originZ, spacingX, spacingZ)
1019 : raster_detail::invalid("terrain.gtsDetail: missing input");
1020 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
1021 });
1022 table.addFunc("bakeGtsWeatherAlbedoDetailed",
1023 [vm = table.getHandle()](image::ImageData* output, const Heightmap* heights,
1024 const image::ImageData* snowAlbedo, const image::ImageData* snowMask,
1025 const GtsSnowSurfaceSettings* snowSettings,
1026 const GtsRainSurfaceSettings* rainSettings,
1027 const Heightmap* detailGreyscale, float originX, float originZ,
1028 float spacingX, float spacingZ) {
1029 auto result = output && heights && snowAlbedo && snowMask && snowSettings && rainSettings && detailGreyscale
1030 ? bakeGtsWeatherAlbedo(*output, *heights, *snowAlbedo, *snowMask, *snowSettings,
1031 *rainSettings, originX, originZ, spacingX, spacingZ, detailGreyscale)
1032 : raster_detail::invalid("terrain.gtsWeather: missing detailed input");
1033 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
1034 });
1035 table.addFunc("bakeGtsWeatherPbr",
1036 [vm = table.getHandle()](image::ImageData* normalOutput, image::ImageData* maskOutput,
1037 Heightmap* displacement, Heightmap* tessellation,
1038 const Heightmap* heights, const image::ImageData* snowNormal,
1039 const image::ImageData* snowMask, const image::ImageData* rainData,
1040 const GtsSnowSurfaceSettings* snowSettings,
1041 const GtsRainSurfaceSettings* rainSettings, float timeSeconds,
1042 float originX, float originZ, float spacingX, float spacingZ) {
1043 auto result = normalOutput && maskOutput && displacement && tessellation && heights && snowNormal &&
1044 snowMask && rainData && snowSettings && rainSettings
1045 ? bakeGtsWeatherPbr(*normalOutput, *maskOutput, *displacement, *tessellation, *heights,
1046 *snowNormal, *snowMask, *rainData, *snowSettings, *rainSettings,
1047 timeSeconds, originX, originZ, spacingX, spacingZ)
1048 : raster_detail::invalid("terrain.gtsWeatherPbr: missing input");
1049 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
1050 });
1051 table.addFunc("generateTerrainImageMaskFromImage",
1052 [vm = table.getHandle()](Heightmap* target, const Heightmap* input, const image::ImageData* image,
1053 const Heightmap* curve, const TerrainImageMaskSettings* settings, int filter,
1054 int mode) {
1055 auto result = [&]() -> Result<int> {
1056 if (!target || !input || !image || !curve || !settings)
1057 return raster_detail::invalid("terrain.imageAdapter: missing input");
1058 auto copy = *settings;
1059 copy.filter = static_cast<TerrainImageFilter>(filter);
1060 return generateTerrainImageMaskFromImage(*target, *input, *image, *curve, copy,
1061 static_cast<TerrainMaskBlend>(mode));
1062 }();
1063 return eve::script::projectResult(vm, std::move(result), [](int n) { return eve::Value(n); });
1064 });
1065}
1066} // namespace eve::procgen
LogicalId target
double value
Duration start
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::map< std::string, std::vector< Key >, std::less<> > channels
std::string output
std::string terrain
const std::string & s
int mask
float phase
Definition CaveMesh.cpp:58
Vec3 tangent
Definition CaveMesh.cpp:80
int bx
Definition CaveMesh.cpp:114
float length
Definition CaveMesh.cpp:94
int by
Definition CaveMesh.cpp:114
float py
float nx
float nz
float ny
float pz
graphics::Texture * albedo
HSQUIRRELVM vm
Definition ECS.cpp:20
std::map< std::string, Var > values
EvpackChunkInput input
Definition Evpack.cpp:170
int pixelSize
std::string palette
tensor::Graph g
Definition GpuGraph.cpp:7
vk::UniqueImage image
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
double r
float v
std::int32_t c
float blend
int h
std::vector< Colorf > px
std::uint32_t height
std::uint32_t width
size_t offset
std::array< float, 3 > position
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
Texture * normal
TileLayer * layer
std::vector< Point > vertices
std::shared_ptr< const std::vector< glm::vec2 > > points
float d
int detail
float t
std::string filter
std::uint16_t third
float dz
float dy
float dx
std::uint32_t count
The single Squirrel projection for common Result, Status and Value.
TacticalUnit * unit
Heightmap curve
TerrainThermalSettings settings
float weights[3]
double spacingZ
double spacingX
const UnitySourceAsset & source
float wz
float wx
float wy
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
Represents raw pixel data.
Definition ImageData.h:38
void getPixel(int x, int y, Colorf &c) const
Gets the pixel at location (x,y).
void setPixel(int x, int y, const Colorf &p)
Sets the pixel at location (x,y).
medialoader::Colorf Colorf
Definition ImageData.h:41
void adopt(eve::Resource &replacement) override
Replace this instance's pixels with replacement's (cache reload). The replacement is drained; its des...
Definition ImageData.cpp:62
Owning ordered GTS packed albedo/height and normal/AO/smoothness texture layers.
int getLayerCount() const noexcept
Return the number of owned packed layers.
~GtsPackedLayerSet()
Gts packed layer set.
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
float height(int x, int y) const
Height.
Definition Heightmap.cpp:28
void setHeight(int x, int y, float h)
Sets the height.
Definition Heightmap.cpp:23
int getWidth() const
Returns the width.
Definition Heightmap.cpp:16
Ordered linear RGBA colors used to visualize terrain splat layers.
Result< int > addColor(float red, float green, float blue, float alpha=1)
Append one finite normalized layer color and return the palette size.
Owning normalized terrain texture-layer weights with atomic mutation. Layers share one finite width/h...
int getHeight() const noexcept
Current splat height, or zero before initialization.
int getLayerCount() const noexcept
Current layer count, or zero before initialization.
Result< float > sample(int layer, int x, int y) const
Return one layer weight, or a structured range/precondition failure.
int getWidth() const noexcept
Current splat width, or zero before initialization.
float smoothStep(float value) noexcept
Smooth step.
double sample(const Heightmap &map, double u, double v)
Sample.
bool validRaster(const Heightmap &map)
Valid raster.
Result< int > invalid(std::string message)
Invalid.
Vec2 normalize(const Vec2 &a)
Normalize.
Definition UrbanTypes.h:44
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
Result< int > bakeGtsColorMapAlbedo(image::ImageData &output, const image::ImageData &colorMap, const Heightmap &globalBlendDistance, const GtsColorMapSettings &settings)
Blend the GTS terrain colormap into an existing albedo before weather layers.
Result< int > placePcgMaskMapTileInto(image::ImageData &output, const image::ImageData &tile, int destinationX, int destinationY, int destinationWidth, int destinationHeight)
Scale and copy one Pcg local mask-map result into a combined atlas rectangle.
Result< int > generateGtsGlobalBlendDistance(Heightmap &output, const Heightmap &heights, double cameraX, double cameraY, double cameraZ, double blendDistance, double blendRange, double originX, double originY, double originZ, double spacingX, double spacingZ)
Generate GTS camera-distance near/far blend values for a terrain raster.
Result< int > combinePcgMaskMapChannels(image::ImageData &output, const image::ImageData &red, const image::ImageData &green, const image::ImageData &blue, const image::ImageData &alpha, std::uint32_t activeChannels)
Combine Pcg Mask Map Export channel rasters into one RGBA image.
Result< int > generateTerrainImageMask(Heightmap &target, const Heightmap &input, const Heightmap &red, const Heightmap &green, const Heightmap &blue, const Heightmap &alpha, const Heightmap &curve, const TerrainImageMaskSettings &s, TerrainMaskBlend mode)
Generate the reference image-mask filter from borrowed RGBA scalar planes.
TerrainMaskBlend
Ordered scalar-mask arithmetic; intermediate values are not clamped.
Definition TerrainStamp.h:9
Result< int > bakeGtsGeologicalSurface(image::ImageData &albedo, image::ImageData &packedNormal, const Heightmap &heights, const Heightmap &layerStrength, const Heightmap &globalBlendDistance, const image::ImageData &geoAlbedo, const image::ImageData &geoNormal, const GtsGeologicalSettings &settings, double originY)
Bake the GTS near/far height-axis geological color and tangent normal overlay atomically.
Result< int > bakeGtsWeatherAlbedo(image::ImageData &output, const Heightmap &heights, const image::ImageData &snowAlbedo, const image::ImageData &snowMask, const GtsSnowSurfaceSettings &snow, const GtsRainSurfaceSettings &rain, double originX, double originZ, double spacingX, double spacingZ, const Heightmap *detailGreyscale)
Bake the GTS snow and rain albedo branches over an existing terrain image.
Result< int > generateTerrainImageMaskFromImage(Heightmap &target, const Heightmap &input, const image::ImageData &image, const Heightmap &curve, const TerrainImageMaskSettings &settings, TerrainMaskBlend mode)
Read a borrowed ImageData into scalar RGBA planes and apply the image-mask kernel atomically.
Result< int > bakeGtsPackedLayerDisplacement(Heightmap &displacement, Heightmap &tessellation, const Heightmap &heights, const TerrainSplatmap &splatmap, const GtsPackedLayerSet &layers, double cameraX, double cameraY, double cameraZ, double tessellationMultiplier, double originX, double originY, double originZ, double spacingX, double spacingZ)
Bake GTS planar top-four layer displacement and tessellation with its camera cutoff.
Result< int > bakeGtsPackedLayers(image::ImageData &albedo, image::ImageData &packedNormal, Heightmap &geoStrength, Heightmap &detailStrength, const Heightmap &heights, const TerrainSplatmap &splatmap, const GtsPackedLayerSet &layers, double originX, double originY, double originZ, double spacingX, double spacingZ)
Bake planar, stochastic or triplanar GTS packed layers using one normalized splatmap.
void exposeTerrainImageAdapter(ssq::Table &table)
Register the full-host ImageData adapter; VM-thread only, no retained table reference.
Result< int > bakeGtsWeatherPbr(image::ImageData &normalOutput, image::ImageData &maskOutput, Heightmap &displacement, Heightmap &tessellation, const Heightmap &heights, const image::ImageData &snowNormal, const image::ImageData &snowMask, const image::ImageData &rainData, const GtsSnowSurfaceSettings &snow, const GtsRainSurfaceSettings &rain, double timeSeconds, double originX, double originZ, double spacingX, double spacingZ)
Bake GTS snow/rain world normals, packed mask, displacement and tessellation atomically.
Result< int > bakeTerrainSplatAlbedo(image::ImageData &output, const TerrainSplatmap &splatmap, const TerrainSplatPalette &palette)
Blend every splat layer color into a borrowed ImageData atomically.
Result< int > bakeGtsDetailSurface(image::ImageData &albedo, image::ImageData &packedNormal, Heightmap &detailGreyscale, const Heightmap &layerStrength, const Heightmap &globalBlendDistance, const image::ImageData &detailNormal, const GtsDetailNormalSettings &settings, double originX, double originZ, double spacingX, double spacingZ)
Bake GTS near/far detail normals, albedo micro-shadowing and snow-detail influence atomically.
TerrainImageFilter
Reference image selection modes; MaximumRgb is not luminance.
Result< int > bakeGtsMacroVariationAlbedo(image::ImageData &output, const image::ImageData &variationMap, const GtsMacroVariationSettings &settings, double originX, double originZ, double spacingX, double spacingZ)
Apply GTS three-scale repeating macro variation after weather layers.
std::unordered_map< std::string, SkillDefinition > & table()
Definition Skill.cpp:65
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
WidgetDesc grid(int columns, std::vector< WidgetDesc > children, std::string id)
Fixed-column grid; children are placed in source order.
Definition Widget.cpp:687
GTS terrain colormap controls from GTSColorMapSettings.
GTS near/far detail-normal controls from GTSDetailSettings.
GTS height-axis geological overlay controls from GTSGeoSettings.
GTS three-scale macro color variation controls from GTSVariationSettings.
One GTS packed texture-array layer's planar sampling and material controls.
GTS rain albedo controls copied from GTSRainSettings.
GTS snow albedo controls copied from GTSSnowSettings.
void setMaskRemapMin(float r, float g, float b, float a)
Set finite packed snow-mask remap minima in RGBA order.
void setMaskRemapMax(float r, float g, float b, float a)
Set finite packed snow-mask remap maxima in RGBA order.
Small owning image product; raw pixels stay out of JSON metadata.
Image UV transform and color-selection settings, rotation in radians.
Pixel public API.
Definition ImageData.h:46
glm::vec4 color