载入中...
搜索中...
未找到
GtsTerrainExportSettings.cpp
浏览该文件的文档.
2
3#include "common/Value.h"
4#include "image/ImageData.h"
6
7#include <algorithm>
8#include <cmath>
9#include <iomanip>
10#include <limits>
11#include <locale>
12#include <sstream>
13#include <utility>
14
15namespace eve::procgen {
16namespace {
17bool enumRange(int value, int last) { return value >= 0 && value <= last; }
18bool textureResolutionValid(int value) {
19 for (int candidate = 32; candidate <= 8192; candidate *= 2)
20 if (value == candidate) return true;
21 return false;
22}
23Result<void> validateList(const std::vector<GtsTerrainExportLodSettings>& levels) {
24 if (levels.size() > 32)
26 "GTS terrain export supports at most 32 LOD records",
27 "procgen.gtsTerrainExportSettings"));
28 float previous = 1.0f;
29 for (const auto& level : levels) {
30 auto valid = level.validate();
31 if (!valid) return valid;
32 if (level.screenRelativeTransitionHeight >= previous)
34 "GTS terrain export transition heights must descend",
35 "procgen.gtsTerrainExportSettings"));
36 previous = level.screenRelativeTransitionHeight;
37 }
38 return Result<void>::success();
39}
40Value encodeOptions(const GtsMeshSimplificationOptions& value) {
41 return Value::Object{{"aggressiveness", value.aggressiveness},
42 {"enableSmartLink", value.enableSmartLink},
43 {"maxIterationCount", value.maxIterationCount},
44 {"preserveBorderEdges", value.preserveBorderEdges},
45 {"preserveSurfaceCurvature", value.preserveSurfaceCurvature},
46 {"preserveUvFoldoverEdges", value.preserveUvFoldoverEdges},
47 {"preserveUvSeamEdges", value.preserveUvSeamEdges},
48 {"vertexLinkDistance", value.vertexLinkDistance}};
49}
50Value encodeLod(const GtsTerrainExportLodSettings& value) {
51 return Value::Object{{"alphaChannel", int(value.alphaChannel)},
52 {"bakeLayerMask", int64_t(value.bakeLayerMask)},
53 {"bakeLighting", int(value.bakeLighting)},
54 {"bakeVertexColors", value.bakeVertexColors},
55 {"captureBaseMapTextures", value.captureBaseMapTextures},
56 {"createMaterials", value.createMaterials},
57 {"exportNormalMaps", value.exportNormalMaps},
58 {"exportSplatmaps", value.exportSplatmaps},
59 {"exportTextures", value.exportTextures},
60 {"materialShader", int(value.materialShader)},
61 {"mode", int(value.mode)},
62 {"namePrefix", value.namePrefix},
63 {"normalEdgeMode", int(value.normalEdgeMode)},
64 {"saveResolution", int(value.saveResolution)},
65 {"screenRelativeTransitionHeight", value.screenRelativeTransitionHeight},
66 {"simplification", encodeOptions(value.simplification)},
67 {"simplifyQuality", value.simplifyQuality},
68 {"textureExportMethod", int(value.textureExportMethod)},
69 {"textureExportResolution", value.textureExportResolution},
70 {"vertexColorSmoothing", value.vertexColorSmoothing}};
71}
72Value encodeWorkflow(const GtsTerrainExportWorkflow& value) {
73 return Value::Object{{"action", int(value.action)},
74 {"addMeshColliderToImpostor", value.addMeshColliderToImpostor},
75 {"addObjectColliders", value.addObjectColliders},
76 {"addTerrainCollider", value.addTerrainCollider},
77 {"addTreeColliders", value.addTreeColliders},
78 {"bakeCombinedCollisionMesh", value.bakeCombinedCollisionMesh},
79 {"colliderResolution", int(value.colliderResolution)},
80 {"colliderSimplifyQuality", value.colliderSimplifyQuality},
81 {"colliderType", int(value.colliderType)},
82 {"convertSourceTerrains", value.convertSourceTerrains},
83 {"convertTreesToObjects", value.convertTreesToObjects},
84 {"copyPcgObjects", value.copyPcgObjects},
85 {"copyPcgObjectsToImpostor", value.copyPcgObjectsToImpostor},
86 {"createColliderScenes", value.createColliderScenes},
87 {"createImpostorScenes", value.createImpostorScenes},
88 {"impostorRange", value.impostorRange},
89 {"invertExportMask", value.invertExportMask},
90 {"objFaceMode", int(value.objFaceMode)},
91 {"selection", int(value.selection)},
92 {"sourceTreatment", int(value.sourceTreatment)}};
93}
94bool exact(const Value::Object& object, std::initializer_list<const char*> names) {
95 if (object.size() != names.size()) return false;
96 for (auto* name : names)
97 if (!object.contains(name)) return false;
98 return true;
99}
100const double* number(const Value& value) { return value.getIf<double>(); }
101bool decodeOptions(const Value& encoded, GtsMeshSimplificationOptions& output) {
102 auto* object = encoded.getIf<Value::Object>();
103 if (!object || !exact(*object, {"aggressiveness", "enableSmartLink", "maxIterationCount", "preserveBorderEdges",
104 "preserveSurfaceCurvature", "preserveUvFoldoverEdges", "preserveUvSeamEdges",
105 "vertexLinkDistance"}))
106 return false;
107 auto* aggressiveness = number(object->at("aggressiveness"));
108 auto* distance = number(object->at("vertexLinkDistance"));
109 auto* iterations = object->at("maxIterationCount").getIf<int64_t>();
110 auto* smart = object->at("enableSmartLink").getIf<bool>();
111 auto* border = object->at("preserveBorderEdges").getIf<bool>();
112 auto* curvature = object->at("preserveSurfaceCurvature").getIf<bool>();
113 auto* foldover = object->at("preserveUvFoldoverEdges").getIf<bool>();
114 auto* seam = object->at("preserveUvSeamEdges").getIf<bool>();
115 if (!aggressiveness || !distance || !iterations || !smart || !border || !curvature || !foldover || !seam)
116 return false;
117 output.aggressiveness = *aggressiveness;
118 output.vertexLinkDistance = *distance;
119 output.maxIterationCount = int(*iterations);
120 output.enableSmartLink = *smart;
121 output.preserveBorderEdges = *border;
122 output.preserveSurfaceCurvature = *curvature;
123 output.preserveUvFoldoverEdges = *foldover;
124 output.preserveUvSeamEdges = *seam;
125 return true;
126}
127bool decodeLod(const Value& encoded, GtsTerrainExportLodSettings& output) {
128 auto* object = encoded.getIf<Value::Object>();
129 if (!object || !exact(*object, {"alphaChannel",
130 "bakeLayerMask",
131 "bakeLighting",
132 "bakeVertexColors",
133 "captureBaseMapTextures",
134 "createMaterials",
135 "exportNormalMaps",
136 "exportSplatmaps",
137 "exportTextures",
138 "materialShader",
139 "mode",
140 "namePrefix",
141 "normalEdgeMode",
142 "saveResolution",
143 "screenRelativeTransitionHeight",
144 "simplification",
145 "simplifyQuality",
146 "textureExportMethod",
147 "textureExportResolution",
148 "vertexColorSmoothing"}))
149 return false;
150 auto integer = [&](const char* name) { return object->at(name).getIf<int64_t>(); };
151 auto boolean = [&](const char* name) { return object->at(name).getIf<bool>(); };
152 auto* alpha = integer("alphaChannel");
153 auto* mask = integer("bakeLayerMask");
154 auto* lighting = integer("bakeLighting");
155 auto* shader = integer("materialShader");
156 auto* mode = integer("mode");
157 auto* edge = integer("normalEdgeMode");
158 auto* resolution = integer("saveResolution");
159 auto* textureMethod = integer("textureExportMethod");
160 auto* textureResolution = integer("textureExportResolution");
161 auto* smoothing = integer("vertexColorSmoothing");
162 auto* transition = number(object->at("screenRelativeTransitionHeight"));
163 auto* quality = number(object->at("simplifyQuality"));
164 auto* prefix = object->at("namePrefix").getIf<std::string>();
165 auto* vertexColors = boolean("bakeVertexColors");
166 auto* capture = boolean("captureBaseMapTextures");
167 auto* materials = boolean("createMaterials");
168 auto* normals = boolean("exportNormalMaps");
169 auto* splats = boolean("exportSplatmaps");
170 auto* textures = boolean("exportTextures");
171 if (!alpha || *alpha < 0 || *alpha > 1 || !mask || *mask < 0 || *mask > std::numeric_limits<std::uint32_t>::max() ||
172 !lighting || *lighting < 0 || *lighting > 1 || !shader || *shader < 0 || *shader > 1 || !mode || *mode < 0 ||
173 *mode > 2 || !edge || *edge < 0 || *edge > 1 || !resolution || *resolution < 0 || *resolution > 4 ||
174 !textureMethod || *textureMethod < 0 || *textureMethod > 1 || !textureResolution || *textureResolution < 32 ||
175 *textureResolution > 8192 || !smoothing || *smoothing < 0 || *smoothing > 128 || !transition || !quality ||
176 !prefix || prefix->size() > 256 || !vertexColors || !capture || !materials || !normals || !splats ||
177 !textures || !decodeOptions(object->at("simplification"), output.simplification))
178 return false;
179 output.alphaChannel = static_cast<GtsTerrainAlphaChannel>(*alpha);
180 output.bakeLayerMask = std::uint32_t(*mask);
181 output.bakeLighting = static_cast<GtsTerrainBakeLighting>(*lighting);
182 output.materialShader = static_cast<GtsTerrainExportShader>(*shader);
183 output.mode = static_cast<GtsTerrainLodMode>(*mode);
184 output.normalEdgeMode = static_cast<GtsTerrainNormalEdgeMode>(*edge);
185 output.saveResolution = static_cast<GtsTerrainSaveResolution>(*resolution);
186 output.textureExportMethod = static_cast<GtsTerrainTextureExportMethod>(*textureMethod);
187 output.textureExportResolution = int(*textureResolution);
188 output.vertexColorSmoothing = int(*smoothing);
189 output.screenRelativeTransitionHeight = float(*transition);
190 output.simplifyQuality = float(*quality);
191 output.namePrefix = *prefix;
192 output.bakeVertexColors = *vertexColors;
193 output.captureBaseMapTextures = *capture;
194 output.createMaterials = *materials;
195 output.exportNormalMaps = *normals;
196 output.exportSplatmaps = *splats;
197 output.exportTextures = *textures;
198 return output.validate().ok();
199}
200bool decodeWorkflow(const Value& encoded, GtsTerrainExportWorkflow& output, bool legacyV2 = false) {
201 auto* object = encoded.getIf<Value::Object>();
202 if (!object ||
203 (!exact(*object, {"action",
204 "addMeshColliderToImpostor",
205 "addObjectColliders",
206 "addTerrainCollider",
207 "addTreeColliders",
208 "bakeCombinedCollisionMesh",
209 "colliderResolution",
210 "colliderSimplifyQuality",
211 "colliderType",
212 "convertSourceTerrains",
213 "convertTreesToObjects",
214 "copyPcgObjects",
215 "copyPcgObjectsToImpostor",
216 "createColliderScenes",
217 "createImpostorScenes",
218 "impostorRange",
219 "invertExportMask",
220 "objFaceMode",
221 "selection",
222 "sourceTreatment"}) &&
223 !(legacyV2 &&
224 exact(*object, {"action", "addMeshColliderToImpostor", "addObjectColliders", "addTerrainCollider",
225 "addTreeColliders", "bakeCombinedCollisionMesh", "colliderResolution",
226 "colliderSimplifyQuality", "colliderType", "convertSourceTerrains", "convertTreesToObjects",
227 "copyPcgObjects", "copyPcgObjectsToImpostor", "createColliderScenes", "createImpostorScenes",
228 "impostorRange", "invertExportMask", "selection", "sourceTreatment"}))))
229 return false;
230 auto integer = [&](const char* name) { return object->at(name).getIf<int64_t>(); };
231 auto boolean = [&](const char* name) { return object->at(name).getIf<bool>(); };
232 auto *action = integer("action"), *resolution = integer("colliderResolution"), *type = integer("colliderType"),
233 *selection = integer("selection"), *treatment = integer("sourceTreatment");
234 const int64_t defaultFace = 0;
235 auto* face = legacyV2 ? &defaultFace : integer("objFaceMode");
236 auto* quality = number(object->at("colliderSimplifyQuality"));
237 auto* range = number(object->at("impostorRange"));
238 auto * addImpostor = boolean("addMeshColliderToImpostor"), *addObjects = boolean("addObjectColliders"),
239 *addTerrain = boolean("addTerrainCollider"), *addTrees = boolean("addTreeColliders"),
240 *combined = boolean("bakeCombinedCollisionMesh"), *convert = boolean("convertSourceTerrains"),
241 *convertTrees = boolean("convertTreesToObjects"), *copy = boolean("copyPcgObjects"),
242 *copyImpostor = boolean("copyPcgObjectsToImpostor"), *colliderScenes = boolean("createColliderScenes"),
243 *impostorScenes = boolean("createImpostorScenes"), *invert = boolean("invertExportMask");
244 if (!action || *action < 0 || *action > 2 || !resolution || *resolution < 0 || *resolution > 4 || !type ||
245 *type < 0 || *type > 1 || !selection || *selection < 0 || *selection > 1 || !treatment || *treatment < 0 ||
246 *treatment > 3 || !face || *face < 0 || *face > 1 || !quality || !range || !std::isfinite(*quality) ||
247 *quality < 0 || *quality > 1 || !std::isfinite(*range) || *range < 0 || !addImpostor || !addObjects ||
248 !addTerrain || !addTrees || !combined || !convert || !convertTrees || !copy || !copyImpostor ||
249 !colliderScenes || !impostorScenes || !invert)
250 return false;
251 output.action = static_cast<GtsTerrainConversionAction>(*action);
252 output.colliderResolution = static_cast<GtsTerrainSaveResolution>(*resolution);
253 output.colliderType = static_cast<GtsTerrainColliderType>(*type);
254 output.selection = static_cast<GtsTerrainExportSelection>(*selection);
255 output.objFaceMode = static_cast<GtsTerrainObjFaceMode>(*face);
256 output.sourceTreatment = static_cast<GtsSourceTerrainTreatment>(*treatment);
257 output.colliderSimplifyQuality = float(*quality);
258 output.impostorRange = *range;
259 output.addMeshColliderToImpostor = *addImpostor;
260 output.addObjectColliders = *addObjects;
261 output.addTerrainCollider = *addTerrain;
262 output.addTreeColliders = *addTrees;
263 output.bakeCombinedCollisionMesh = *combined;
264 output.convertSourceTerrains = *convert;
265 output.convertTreesToObjects = *convertTrees;
266 output.copyPcgObjects = *copy;
267 output.copyPcgObjectsToImpostor = *copyImpostor;
268 output.createColliderScenes = *colliderScenes;
269 output.createImpostorScenes = *impostorScenes;
270 output.invertExportMask = *invert;
271 return true;
272}
273} // namespace
274
276 if (!enumRange(int(saveResolution), 4) || !enumRange(int(normalEdgeMode), 1) || !enumRange(int(mode), 2) ||
277 !enumRange(int(materialShader), 1) || !enumRange(int(textureExportMethod), 1) ||
278 !enumRange(int(alphaChannel), 1) || !enumRange(int(bakeLighting), 1))
280 "GTS terrain export enum value is invalid",
281 "procgen.gtsTerrainExportSettings"));
282 if (!std::isfinite(simplifyQuality) || simplifyQuality < 0.0f || simplifyQuality > 1.0f ||
284 screenRelativeTransitionHeight >= 1.0f || vertexColorSmoothing < 0 || vertexColorSmoothing > 128 ||
285 !textureResolutionValid(textureExportResolution) || namePrefix.size() > 256 ||
290 "GTS terrain export LOD value is outside its supported range",
291 "procgen.gtsTerrainExportSettings"));
292 return Result<void>::success();
293}
301 if (level < 0)
303 Diagnostic::error(DiagnosticCode::InvalidArgument, "GTS impostor LOD index must be non-negative",
304 "procgen.gtsTerrainExportSettings"));
306 value.namePrefix = "LOD" + std::to_string(level) + "_";
310 if (level == 0) {
311 value.saveResolution = GtsTerrainSaveResolution::Half;
312 value.textureExportResolution = 2048;
313 } else if (level == 1) {
315 value.textureExportResolution = 1024;
316 } else if (level == 2) {
318 value.textureExportResolution = 512;
319 } else {
321 value.textureExportResolution = 256;
322 }
323 value.bakeVertexColors = false;
324 value.alphaChannel = GtsTerrainAlphaChannel::None;
325 value.exportSplatmaps = false;
328}
330 if (level < 0)
332 Diagnostic::error(DiagnosticCode::InvalidArgument, "GTS low-poly LOD index must be non-negative",
333 "procgen.gtsTerrainExportSettings"));
335 value.namePrefix = "LOD" + std::to_string(level) + "_";
338 value.normalEdgeMode = GtsTerrainNormalEdgeMode::Sharp;
340 value.bakeVertexColors = true;
341 value.vertexColorSmoothing = 3;
342 value.alphaChannel = GtsTerrainAlphaChannel::None;
343 value.exportNormalMaps = false;
344 value.exportSplatmaps = false;
347}
348namespace {
349Result<GtsTerrainExportLodSettings> preset(int mode, int level, float quality, float transition) {
352 : mode == int(GtsTerrainLodMode::LowPoly)
354 : Result<GtsTerrainExportLodSettings>::failure(Diagnostic::error(
355 DiagnosticCode::InvalidArgument, "GTS terrain preset mode must be Impostor or LowPoly",
356 "procgen.gtsTerrainExportSettings"));
357 if (!made) return made;
358 made.value().simplifyQuality = quality;
359 made.value().screenRelativeTransitionHeight = transition;
360 auto valid = made.value().validate();
362 return made;
363}
364} // namespace
365Result<void> GtsTerrainExportSettings::appendSourcePreset(int mode, int level, float quality, float transitionHeight) {
366 auto made = preset(mode, level, quality, transitionHeight);
367 if (!made) return Result<void>::failure(made.status());
368 auto candidate = sourceLods_;
369 candidate.push_back(std::move(made).takeValue());
370 return setSourceLods(std::move(candidate));
371}
373 float transitionHeight) {
374 auto made = preset(mode, level, quality, transitionHeight);
375 if (!made) return Result<void>::failure(made.status());
376 auto candidate = impostorLods_;
377 candidate.push_back(std::move(made).takeValue());
378 return setImpostorLods(std::move(candidate));
379}
380Result<void> GtsTerrainExportSettings::setSourceLods(std::vector<GtsTerrainExportLodSettings> levels) {
381 auto valid = validateList(levels);
382 if (!valid) return valid;
383 sourceLods_ = std::move(levels);
384 return Result<void>::success();
385}
387 if (!enumRange(int(workflow.action), 2) || !enumRange(int(workflow.sourceTreatment), 3) ||
388 !enumRange(int(workflow.selection), 1) || !enumRange(int(workflow.colliderType), 1) ||
389 !enumRange(int(workflow.objFaceMode), 1) || !enumRange(int(workflow.colliderResolution), 4) ||
390 !std::isfinite(workflow.colliderSimplifyQuality) || workflow.colliderSimplifyQuality < 0.f ||
391 workflow.colliderSimplifyQuality > 1.f || !std::isfinite(workflow.impostorRange) ||
392 workflow.impostorRange < 0.0)
394 "GTS terrain export workflow value is invalid",
395 "procgen.gtsTerrainExportSettings"));
396 workflow_ = workflow;
397 return Result<void>::success();
398}
400Result<void> GtsTerrainExportSettings::setImpostorLods(std::vector<GtsTerrainExportLodSettings> levels) {
401 auto valid = validateList(levels);
402 if (!valid) return valid;
403 impostorLods_ = std::move(levels);
404 return Result<void>::success();
405}
406int GtsTerrainExportSettings::getSourceLodCount() const noexcept { return int(sourceLods_.size()); }
407int GtsTerrainExportSettings::getImpostorLodCount() const noexcept { return int(impostorLods_.size()); }
409 return index >= 0 && index < int(sourceLods_.size()) ? &sourceLods_[std::size_t(index)] : nullptr;
410}
412 return index >= 0 && index < int(impostorLods_.size()) ? &impostorLods_[std::size_t(index)] : nullptr;
413}
415 auto valid = validateList(sourceLods_);
416 if (!valid) return Result<std::vector<GtsTerrainLodLevelSettings>>::failure(valid.status());
417 if (sourceLods_.empty())
419 Diagnostic::error(DiagnosticCode::InvalidArgument, "GTS source export requires at least one LOD",
420 "procgen.gtsTerrainExportSettings"));
421 std::vector<GtsTerrainLodLevelSettings> output;
422 output.reserve(sourceLods_.size());
423 for (const auto& source : sourceLods_) {
424 auto level = source.compileLevel();
425 if (!level) return Result<std::vector<GtsTerrainLodLevelSettings>>::failure(level.status());
426 output.push_back(std::move(level).takeValue());
427 }
428 return Result<std::vector<GtsTerrainLodLevelSettings>>::success(std::move(output));
429}
431 auto sourceValid = validateList(sourceLods_);
432 if (!sourceValid) return Result<std::string>::failure(sourceValid.status());
433 auto impostorValid = validateList(impostorLods_);
434 if (!impostorValid) return Result<std::string>::failure(impostorValid.status());
435 Value::Array source, impostor;
436 for (const auto& level : sourceLods_) source.emplace_back(encodeLod(level));
437 for (const auto& level : impostorLods_) impostor.emplace_back(encodeLod(level));
438 return Value(Value::Object{{"impostorLods", std::move(impostor)},
439 {"schema", "eve.procgen.gts-terrain-export-settings"},
440 {"sourceLods", std::move(source)},
441 {"version", 3},
442 {"workflow", encodeWorkflow(workflow_)}})
443 .toJson();
444}
446 if (json.size() > 1024U * 1024U)
448 "GTS terrain export settings JSON exceeds size limit",
449 "procgen.gtsTerrainExportSettings"));
450 auto parsed = Value::fromJson(json);
451 if (!parsed) return Result<void>::failure(parsed.status());
452 auto* root = parsed.value().getIf<Value::Object>();
453 if (!root || !root->contains("schema") || !root->contains("version") || !root->contains("sourceLods") ||
454 !root->contains("impostorLods"))
456 "GTS terrain export settings fields are invalid",
457 "procgen.gtsTerrainExportSettings"));
458 auto* schema = root->at("schema").getIf<std::string>();
459 auto* version = root->at("version").getIf<int64_t>();
460 if (!version || (*version < 1 || *version > 3) ||
461 (*version == 1 && !exact(*root, {"impostorLods", "schema", "sourceLods", "version"})) ||
462 (*version >= 2 && !exact(*root, {"impostorLods", "schema", "sourceLods", "version", "workflow"})))
464 "GTS terrain export settings fields or version are invalid",
465 "procgen.gtsTerrainExportSettings"));
466 auto* source = root->at("sourceLods").getIf<Value::Array>();
467 auto* impostor = root->at("impostorLods").getIf<Value::Array>();
468 if (!schema || *schema != "eve.procgen.gts-terrain-export-settings" || !source || !impostor ||
469 source->size() > 32 || impostor->size() > 32)
471 "GTS terrain export settings schema or counts are invalid",
472 "procgen.gtsTerrainExportSettings"));
473 GtsTerrainExportWorkflow decodedWorkflow;
474 if (*version >= 2 && !decodeWorkflow(root->at("workflow"), decodedWorkflow, *version == 2))
476 "GTS terrain export workflow is invalid",
477 "procgen.gtsTerrainExportSettings"));
478 std::vector<GtsTerrainExportLodSettings> decodedSource, decodedImpostor;
479 for (const auto& encoded : *source) {
481 if (!decodeLod(encoded, value))
483 "GTS source export LOD is invalid",
484 "procgen.gtsTerrainExportSettings"));
485 decodedSource.push_back(std::move(value));
486 }
487 for (const auto& encoded : *impostor) {
489 if (!decodeLod(encoded, value))
491 "GTS impostor export LOD is invalid",
492 "procgen.gtsTerrainExportSettings"));
493 decodedImpostor.push_back(std::move(value));
494 }
495 auto sourceValid = validateList(decodedSource);
496 if (!sourceValid) return sourceValid;
497 auto impostorValid = validateList(decodedImpostor);
498 if (!impostorValid) return impostorValid;
499 auto workflowValid = configureWorkflow(decodedWorkflow);
500 if (!workflowValid) return workflowValid;
501 sourceLods_ = std::move(decodedSource);
502 impostorLods_ = std::move(decodedImpostor);
503 return Result<void>::success();
504}
505
506
508 const GtsTerrainExportSettings& settings, float sizeX,
509 float sizeY, float sizeZ, int subTiles, GtsMeshPivot pivot) {
510 auto levels = settings.compileSourceLevels();
511 if (!levels) return Result<void>::failure(levels.status());
512 const auto* first = settings.sourceLodAt(0);
513 if (!first)
515 "GTS source export requires at least one LOD",
516 "procgen.gtsTerrainExportSettings"));
517 MeshBuild base;
518 auto built = buildGtsTerrainBaseMesh(base, heightmap, first->saveResolution, sizeX, sizeY, sizeZ);
519 if (!built) return built;
520 auto lods = buildGtsTerrainLods(base, subTiles, subTiles, pivot, levels.value());
521 if (!lods) return Result<void>::failure(lods.status());
522 output = std::move(lods).takeValue();
523 return Result<void>::success();
524}
526 const GtsTerrainExportWorkflow& workflow, float sizeX,
527 float sizeY, float sizeZ) {
528 GtsTerrainExportSettings validator;
529 auto valid = validator.configureWorkflow(workflow);
530 if (!valid) return valid;
533 "GTS workflow does not request a terrain MeshCollider",
534 "procgen.gtsTerrainExportSettings"));
535 MeshBuild base;
536 auto built = buildGtsTerrainBaseMesh(base, heightmap, workflow.colliderResolution, sizeX, sizeY, sizeZ);
537 if (!built) return built;
538 MeshBuild simplified;
539 auto reduced = simplifyGtsMesh(simplified, base, workflow.colliderSimplifyQuality);
540 if (!reduced) return Result<void>::failure(reduced.status());
541 output = std::move(simplified);
542 return Result<void>::success();
543}
544
546 float sizeY, float sizeZ, GtsTerrainObjFaceMode faceMode) {
547 if (!enumRange(int(faceMode), 1))
549 "GTS terrain OBJ face mode is invalid",
550 "procgen.gtsTerrainExportSettings"));
551 MeshBuild validated;
552 auto built = buildGtsTerrainBaseMesh(validated, heightmap, resolution, sizeX, sizeY, sizeZ);
553 if (!built) return Result<std::string>::failure(built.status());
554 const int stride = 1 << int(resolution), width = (heightmap.getWidth() - 1) / stride + 1,
555 height = (heightmap.getHeight() - 1) / stride + 1;
556 std::ostringstream stream;
557 stream.imbue(std::locale::classic());
558 stream << std::setprecision(std::numeric_limits<float>::max_digits10);
559 stream << "# Unity terrain OBJ File\n";
560 for (int y = 0; y < height; ++y)
561 for (int x = 0; x < width; ++x) {
562 const float u = float(x * stride) / float(heightmap.getWidth() - 1),
563 v = float(y * stride) / float(heightmap.getHeight() - 1);
564 stream << "v " << (-v * sizeZ) << ' ' << (heightmap.height(x * stride, y * stride) * sizeY) << ' '
565 << (u * sizeX) << '\n';
566 }
567 for (int y = 0; y < height; ++y)
568 for (int x = 0; x < width; ++x) {
569 const float u = float(x * stride) / float(heightmap.getWidth() - 1),
570 v = float(y * stride) / float(heightmap.getHeight() - 1);
571 stream << "vt " << v << ' ' << u << '\n';
572 }
573 for (int y = 0; y < height - 1; ++y)
574 for (int x = 0; x < width - 1; ++x) {
575 const int a = y * width + x + 1, b = (y + 1) * width + x + 1, c = (y + 1) * width + x + 2,
576 d = y * width + x + 2;
577 if (faceMode == GtsTerrainObjFaceMode::Triangles)
578 stream << "f " << a << '/' << a << ' ' << b << '/' << b << ' ' << d << '/' << d << "\nf " << b << '/'
579 << b << ' ' << c << '/' << c << ' ' << d << '/' << d << '\n';
580 else
581 stream << "f " << a << '/' << a << ' ' << b << '/' << b << ' ' << c << '/' << c << ' ' << d << '/' << d
582 << '\n';
583 }
584 auto text = stream.str();
585 if (text.size() > 512U * 1024U * 1024U)
587 DiagnosticCode::InvalidArgument, "GTS terrain OBJ exceeds size limit", "procgen.gtsTerrainExportSettings"));
588 return Result<std::string>::success(std::move(text));
589}
590
592 GtsTerrainSaveResolution resolution, float sizeX, float sizeY,
593 float sizeZ, GtsTerrainObjFaceMode faceMode, float threshold,
594 bool invert) {
595 if (!enumRange(int(faceMode), 1) || !std::isfinite(threshold) || threshold < 0.f || threshold > 1.f)
597 "GTS masked terrain OBJ settings are invalid",
598 "procgen.gtsTerrainExportSettings"));
599 MeshBuild validated;
600 auto built = buildGtsTerrainBaseMesh(validated, heightmap, resolution, sizeX, sizeY, sizeZ);
601 if (!built) return Result<std::string>::failure(built.status());
602 if (maskmap.getWidth() < 1 || maskmap.getHeight() < 1)
604 "GTS masked terrain OBJ mask is empty",
605 "procgen.gtsTerrainExportSettings"));
606 const int terrainResolution = heightmap.getWidth(), stride = 1 << int(resolution);
607 std::vector<int> vertexIndices(std::size_t(terrainResolution) * std::size_t(terrainResolution), -1);
608 struct Vertex {
609 float x, y, z, u, v;
610 };
611 std::vector<Vertex> vertices;
612 std::vector<int> indices;
613 auto sampleNormalized = [](const Heightmap& map, float x, float z) {
614 return map.sampleBilinear(x * float(map.getWidth()), z * float(map.getHeight()));
615 };
616 auto vertexIndex = [&](int x, int z, float height) {
617 auto& index = vertexIndices[std::size_t(z) * std::size_t(terrainResolution) + std::size_t(x)];
618 if (index < 0) {
619 index = int(vertices.size());
620 vertices.push_back({-float(x) * sizeX / float(terrainResolution - 1), height * sizeY,
621 float(z) * sizeZ / float(terrainResolution - 1), float(x) / float(terrainResolution),
622 float(z) / float(terrainResolution)});
623 }
624 return index;
625 };
626 auto addTriangle = [&](int a, int b, int c) {
627 indices.push_back(c);
628 indices.push_back(b);
629 indices.push_back(a);
630 };
631 auto addQuad = [&](int a, int b, int c, int d) {
632 indices.push_back(a);
633 indices.push_back(c);
634 indices.push_back(d);
635 indices.push_back(b);
636 };
637 for (int z = 0; z < terrainResolution - stride; z += stride) {
638 const float z0 = float(z) / float(terrainResolution), z1 = float(z + stride) / float(terrainResolution);
639 for (int x = 0; x < terrainResolution - stride; x += stride) {
640 const float x0 = float(x) / float(terrainResolution), x1 = float(x + stride) / float(terrainResolution);
641 const bool outside = sampleNormalized(maskmap, x0, z0) < threshold;
642 if (outside != invert) continue;
643 const int v1 = vertexIndex(x, z + stride, sampleNormalized(heightmap, z1, x0));
644 const int v2 = vertexIndex(x + stride, z + stride, sampleNormalized(heightmap, z1, x1));
645 const int v3 = vertexIndex(x, z, sampleNormalized(heightmap, z0, x0));
646 const int v4 = vertexIndex(x + stride, z, sampleNormalized(heightmap, z0, x1));
647 if (faceMode == GtsTerrainObjFaceMode::Triangles) {
648 addTriangle(v3, v1, v2);
649 addTriangle(v3, v2, v4);
650 } else
651 addQuad(v1, v2, v3, v4);
652 }
653 }
654 std::ostringstream stream;
655 stream.imbue(std::locale::classic());
656 stream << std::setprecision(std::numeric_limits<float>::max_digits10);
657 stream << "# Unity terrain OBJ File\n";
658 for (const auto& vertex : vertices) stream << "v " << vertex.x << ' ' << vertex.y << ' ' << vertex.z << '\n';
659 for (const auto& vertex : vertices) stream << "vt " << vertex.u << ' ' << vertex.v << '\n';
660 const int faceSize = faceMode == GtsTerrainObjFaceMode::Triangles ? 3 : 4;
661 for (std::size_t i = 0; i < indices.size(); i += std::size_t(faceSize)) {
662 stream << "f";
663 for (int j = 0; j < faceSize; ++j) {
664 const int index = indices[i + std::size_t(j)] + 1;
665 stream << ' ' << index << '/' << index;
666 }
667 stream << '\n';
668 }
669 auto text = stream.str();
670 if (text.size() > 512U * 1024U * 1024U)
672 "GTS masked terrain OBJ exceeds size limit",
673 "procgen.gtsTerrainExportSettings"));
674 return Result<std::string>::success(std::move(text));
675}
676
678 const image::ImageData& bakedTexture, GtsTerrainNormalEdgeMode edgeMode,
679 int smoothingIterations, float terrainSizeX, float terrainSizeZ,
680 bool linearize) {
681 if (!enumRange(int(edgeMode), 1) || smoothingIterations < 0 || smoothingIterations > 128 || source.empty() ||
682 source.getIndexCount() % 3 || !std::isfinite(terrainSizeX) || terrainSizeX <= 0.f ||
683 !std::isfinite(terrainSizeZ) || terrainSizeZ <= 0.f || bakedTexture.getWidth() < 1 ||
684 bakedTexture.getHeight() < 1 || !bakedTexture.getData() || !bakedTexture.getPixelGetFunction())
686 "GTS terrain vertex-color bake input is invalid",
687 "procgen.gtsTerrainExportSettings"));
688 const std::size_t pixelSize = bakedTexture.getPixelSize();
689 if (!pixelSize || std::size_t(bakedTexture.getWidth()) * std::size_t(bakedTexture.getHeight()) >
690 bakedTexture.getSize() / pixelSize)
692 "GTS terrain vertex-color texture storage is invalid",
693 "procgen.gtsTerrainExportSettings"));
695 const int width = bakedTexture.getWidth(), height = bakedTexture.getHeight();
696 std::vector<Color> colors;
697 colors.reserve(std::size_t(width) * std::size_t(height));
698 for (int y = 0; y < height; ++y)
699 for (int x = 0; x < width; ++x) colors.push_back(bakedTexture.getPixel(x, y));
700 auto safe = [&](const std::vector<Color>& values, int x, int y) {
701 x = std::clamp(x, 0, width - 1);
702 y = std::clamp(y, 0, height - 1);
703 return values[std::size_t(y) * std::size_t(width) + std::size_t(x)];
704 };
705 for (int iteration = 0; iteration < smoothingIterations; ++iteration) {
706 auto next = colors;
707 for (int y = 0; y < height; ++y)
708 for (int x = 0; x < width; ++x) {
709 const auto a = safe(colors, x, y - 1), b = safe(colors, x, y + 1), c = safe(colors, x - 1, y),
710 d = safe(colors, x + 1, y);
711 next[std::size_t(y) * std::size_t(width) + std::size_t(x)] = {
712 (a.r + b.r + c.r + d.r) * .25f, (a.g + b.g + c.g + d.g) * .25f, (a.b + b.b + c.b + d.b) * .25f,
713 (a.a + b.a + c.a + d.a) * .25f};
714 }
715 colors = std::move(next);
716 }
717 auto linear = [](float value) {
718 return value <= .04045f ? value / 12.92f : std::pow((value + .055f) / 1.055f, 2.4f);
719 };
720 auto sample = [&](float x, float z) {
721 const int tx = std::max(0, int(std::round(x / terrainSizeX * float(width))) - 1);
722 const int tz = std::max(0, int(std::round(z / terrainSizeZ * float(width))) - 1);
723 Color color = safe(colors, tx, tz);
724 if (linearize) {
725 color.r = linear(color.r);
726 color.g = linear(color.g);
727 color.b = linear(color.b);
728 }
729 return color;
730 };
731 MeshBuild candidate;
732 std::vector<float> vertexColors;
733 if (edgeMode == GtsTerrainNormalEdgeMode::Smooth) {
734 candidate = source;
735 vertexColors.reserve(std::size_t(source.getVertexCount()) * 4u);
736 for (int vertex = 0; vertex < source.getVertexCount(); ++vertex) {
737 const auto color = sample(source.getPositionX(vertex), source.getPositionZ(vertex));
738 vertexColors.insert(vertexColors.end(), {color.r, color.g, color.b, color.a});
739 }
740 } else {
741 candidate.reserve(source.getIndexCount(), source.getIndexCount());
742 vertexColors.reserve(std::size_t(source.getIndexCount()) * 4u);
743 for (int i = 0; i < source.getIndexCount(); i += 3) {
744 const int group = source.getTriangleGroup(i / 3);
745 candidate.setActiveGroup(group >= 0 ? source.getGroupName(group) : "");
746 Color triangle[3];
747 for (int corner = 0; corner < 3; ++corner) {
748 const int src = source.getIndex(i + corner);
749 triangle[corner] = sample(source.getPositionX(src), source.getPositionZ(src));
750 candidate.addVertex(source.getPositionX(src), source.getPositionY(src), source.getPositionZ(src), 0, 0,
751 0, source.getUvU(src), source.getUvV(src));
752 }
753 const Color average{(triangle[0].r + triangle[1].r + triangle[2].r) / 3.f,
754 (triangle[0].g + triangle[1].g + triangle[2].g) / 3.f,
755 (triangle[0].b + triangle[1].b + triangle[2].b) / 3.f,
756 (triangle[0].a + triangle[1].a + triangle[2].a) / 3.f};
757 for (int corner = 0; corner < 3; ++corner)
758 vertexColors.insert(vertexColors.end(), {average.r, average.g, average.b, average.a});
759 const std::uint32_t base = std::uint32_t(i);
760 candidate.addTriangle(base, base + 1, base + 2);
761 }
762 auto& normals = candidate.normals();
763 const auto& positions = candidate.positions();
764 for (int i = 0; i < candidate.getIndexCount(); i += 3) {
765 const auto a = candidate.indices()[std::size_t(i)], b = candidate.indices()[std::size_t(i + 1)],
766 c = candidate.indices()[std::size_t(i + 2)];
767 const float abx = positions[b * 3] - positions[a * 3], aby = positions[b * 3 + 1] - positions[a * 3 + 1],
768 abz = positions[b * 3 + 2] - positions[a * 3 + 2];
769 const float acx = positions[c * 3] - positions[a * 3], acy = positions[c * 3 + 1] - positions[a * 3 + 1],
770 acz = positions[c * 3 + 2] - positions[a * 3 + 2];
771 float nx = aby * acz - abz * acy, ny = abz * acx - abx * acz, nz = abx * acy - aby * acx;
772 const float length = std::sqrt(nx * nx + ny * ny + nz * nz);
773 if (!(length > 0.f) || !std::isfinite(length))
775 "GTS sharp terrain mesh contains a degenerate triangle",
776 "procgen.gtsTerrainExportSettings"));
777 nx /= length;
778 ny /= length;
779 nz /= length;
780 for (auto vertex : {a, b, c}) {
781 normals[vertex * 3] = nx;
782 normals[vertex * 3 + 1] = ny;
783 normals[vertex * 3 + 2] = nz;
784 }
785 }
786 for (const auto& [key, value] : source.metadata()) candidate.setMeta(key, value);
787 }
788 auto applied = candidate.setVertexColors(std::move(vertexColors));
789 if (!applied) return Result<int>::failure(applied.status());
790 candidate.setMeta("gts.vertexColors", edgeMode == GtsTerrainNormalEdgeMode::Sharp ? "sharp" : "smooth");
791 output = std::move(candidate);
792 return Result<int>::success(output.getVertexCount());
793}
794} // namespace eve::procgen
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
int root
Definition AnimSmr.cpp:119
std::string output
int mask
building::EdgeCurveGroup group
float length
Definition CaveMesh.cpp:94
float nx
float nz
float ny
std::map< std::string, Var > values
int pixelSize
int triangle
tensor::Graph g
Definition GpuGraph.cpp:7
std::uint32_t key
float u
Definition Grass.cpp:233
bool foldover
bool seam
bool border
std::vector< std::uint32_t > indices
std::vector< float > normals
std::vector< float > positions
float v
std::int32_t c
std::int32_t first
std::uint32_t height
std::uint32_t width
std::string text
Range range
std::string name
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
int level
graphics::Canvas * previous
std::string error
Definition Package.cpp:60
std::vector< Point > vertices
std::string action
Definition PlayHost.cpp:117
float d
Shader * shader
Lighting3DPack lighting
std::vector< float > colors
const RoadEdge * edge
double number
bool boolean
TerrainThermalSettings settings
Json object
int iterations
Definition TreeMesh.cpp:311
float size
Definition TreeMesh.cpp:156
uint32_t index
const UnitySourceAsset & source
Owning, renderer-independent dynamic values.
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
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
const T & value() const &
Borrow the value from a const lvalue after checking success.
Definition Result.h:308
const Status & status() const noexcept
Inspect the structured operation status.
Definition Result.h:269
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
Represents raw pixel data.
Definition ImageData.h:38
void getPixel(int x, int y, Colorf &c) const
Gets the pixel at location (x,y).
medialoader::Colorf Colorf
Definition ImageData.h:41
size_t getSize() const
void * getData() const
PixelGetFunction getPixelGetFunction() const
Definition ImageData.h:174
size_t getPixelSize() const
Schema-versioned source/impostor terrain export profile.
Result< void > appendSourcePreset(int mode, int level, float quality, float transitionHeight)
Append one source preset after validating the complete resulting sequence.
const GtsTerrainExportLodSettings * sourceLodAt(int index) const noexcept
Return an immutable source LOD record, or nullptr for an invalid index. @ownership The export setting...
Result< void > setImpostorLods(std::vector< GtsTerrainExportLodSettings > levels)
Replace impostor LOD records only after every record validates.
int getImpostorLodCount() const noexcept
Return the number of impostor LOD records.
Result< std::vector< GtsTerrainLodLevelSettings > > compileSourceLevels() const
Compile source records for the existing sequential LOD mesh builder.
GtsTerrainExportWorkflow getWorkflow() const noexcept
Return a value copy of the current validated workflow.
Result< void > configureWorkflow(const GtsTerrainExportWorkflow &workflow)
Atomically configure all top-level conversion and collider workflow controls.
Result< void > restoreJson(const std::string &json)
Atomically restore a strict version-1 terrain export profile.
const GtsTerrainExportLodSettings * impostorLodAt(int index) const noexcept
Return an immutable impostor LOD record, or nullptr for an invalid index. @ownership The export setti...
Result< std::string > snapshotJson() const
Serialize as strict eve.procgen.gts-terrain-export-settings version 1 JSON.
Result< void > setSourceLods(std::vector< GtsTerrainExportLodSettings > levels)
Replace source-terrain LOD records only after every record validates.
int getSourceLodCount() const noexcept
Return the number of source-terrain LOD records.
Result< void > appendImpostorPreset(int mode, int level, float quality, float transitionHeight)
Append one impostor preset after validating the complete resulting sequence.
Owned result of the GTS split-and-sequential-simplify pipeline.
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
float sampleBilinear(float x, float y) const
Bilinear height at continuous coordinate within [0, w) x [0, h).
Definition Heightmap.cpp:37
float height(int x, int y) const
Height.
Definition Heightmap.cpp:28
int getWidth() const
Returns the width.
Definition Heightmap.cpp:16
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:19
const std::vector< float > & positions() const
Positions.
Definition MeshBuild.h:125
void addVertex(float px, float py, float pz, float nx, float ny, float nz, float u, float v)
Adds vertex.
Definition MeshBuild.cpp:33
const std::vector< float > & normals() const
Normals.
Definition MeshBuild.h:127
void addTriangle(uint32_t i0, uint32_t i1, uint32_t i2)
Adds triangle.
Definition MeshBuild.cpp:46
void reserve(int vertexCount, int indexCount)
Reserve.
Definition MeshBuild.cpp:20
void setMeta(const std::string &key, const std::string &value)
Sets the meta.
int setActiveGroup(const std::string &name)
Select/create the named group assigned to subsequently added triangles.
Definition MeshBuild.cpp:53
int getIndexCount() const
Returns the index count.
eve::Result< void > setVertexColors(std::vector< float > colors)
Atomically replace the optional packed RGBA stream; empty removes it.
const std::vector< uint32_t > & indices() const
Indices.
Definition MeshBuild.h:133
double sample(const Heightmap &map, double u, double v)
Sample.
GtsTerrainSaveResolution
Pcg/GTS power-of-two sampling reduction used before terrain mesh export.
GtsTerrainAlphaChannel
Optional channel placed in the exported texture alpha channel.
Result< GtsTerrainExportLodSettings > makeGtsTerrainLowPolyLod(int level)
Construct the exact GTS low-poly preset for a zero-based LOD index.
Result< void > buildGtsTerrainBaseMesh(MeshBuild &output, const Heightmap &heightmap, GtsTerrainSaveResolution resolution, float sizeX, float sizeY, float sizeZ)
Build the local-space base mesh consumed by the GTS split and LOD pipeline.
Result< int > simplifyGtsMesh(MeshBuild &output, const MeshBuild &source, float quality, const GtsMeshSimplificationOptions &o)
Simplify a mesh with deterministic quadric edge collapse.
GtsTerrainExportShader
Native material family created for an exported terrain level.
GtsMeshPivot
Pivot translation applied independently to each GTS split mesh.
GtsTerrainConversionAction
Operation performed for the selected source terrain.
GtsTerrainColliderType
Collider representation requested by the export workflow.
GtsTerrainBakeLighting
Lighting state used while baking exported textures.
Result< void > buildGtsTerrainExportLodsFromHeightmapInto(GtsTerrainLodSet &output, const Heightmap &heightmap, const GtsTerrainExportSettings &settings, float sizeX, float sizeY, float sizeZ, int subTiles, GtsMeshPivot pivot)
Build source-terrain LODs using the first export record's SaveResolution and the complete sequential ...
GtsTerrainExportSelection
Source selection scope used by a terrain export.
GtsSourceTerrainTreatment
Treatment applied to a source terrain after a successful conversion commit.
GtsTerrainTextureExportMethod
Texture source used by a terrain export level.
GtsTerrainLodMode
Purpose assigned to one exported terrain LOD.
Result< void > buildGtsTerrainColliderMeshFromHeightmapInto(MeshBuild &output, const Heightmap &heightmap, const GtsTerrainExportWorkflow &workflow, float sizeX, float sizeY, float sizeZ)
Build the independently sampled and simplified MeshCollider geometry requested by a workflow.
Result< int > bakeGtsTerrainVertexColorsInto(MeshBuild &output, const MeshBuild &source, const image::ImageData &bakedTexture, GtsTerrainNormalEdgeMode edgeMode, int smoothingIterations, float terrainSizeX, float terrainSizeZ, bool linearize)
Apply Pcg low-poly edge processing and baked texture vertex colors atomically.
Result< std::string > encodeGtsMaskedTerrainObj(const Heightmap &heightmap, const Heightmap &maskmap, GtsTerrainSaveResolution resolution, float sizeX, float sizeY, float sizeZ, GtsTerrainObjFaceMode faceMode, float threshold, bool invert)
Encode Pcg's cell-classified masked terrain OBJ convention.
Result< std::string > encodeGtsTerrainObj(const Heightmap &heightmap, GtsTerrainSaveResolution resolution, float sizeX, float sizeY, float sizeZ, GtsTerrainObjFaceMode faceMode)
Encode the original Pcg unmasked terrain OBJ coordinate, UV and face convention.
Result< GtsTerrainLodSet > buildGtsTerrainLods(const MeshBuild &source, int xSplits, int zSplits, GtsMeshPivot pivot, const std::vector< GtsTerrainLodLevelSettings > &levels)
Split a terrain mesh and generate each tile's sequential GTS LOD chain.
GtsTerrainNormalEdgeMode
Edge-normal treatment requested for an exported terrain mesh.
Result< GtsTerrainExportLodSettings > makeGtsTerrainImpostorLod(int level)
Construct the exact GTS impostor preset for a zero-based LOD index.
GtsTerrainObjFaceMode
Polygon representation written by the Pcg terrain OBJ exporter.
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
glm::vec4 Color
Render-neutral RGBA color shared by graphics-facing modules.
Definition RenderTypes.h:8
Complete native counterpart of one GTSExportTerrainLODSettings record.
Result< void > validate() const
Validate all persisted and executable values in this record.
Result< GtsTerrainLodLevelSettings > compileLevel() const
Convert this export record to the sequential mesh-LOD contract.
Validated execution plan compiled from top-level Pcg terrain export controls.
glm::vec4 color