12constexpr std::size_t kMaxJsonBytes = 256U * 1024U * 1024U, kMaxTiles = 65536, kMaxLevels = 32,
13 kMaxVertices = 8U * 1024U * 1024U;
14bool exact(
const Value::Object& o, std::initializer_list<const char*> names) {
15 if (o.size() != names.size())
return false;
17 if (!o.contains(
n)) return false;
20const double* num(
const Value&
v) {
return v.getIf<
double>(); }
23 {
"enableSmartLink", o.enableSmartLink},
24 {
"maxIterationCount", o.maxIterationCount},
25 {
"preserveBorderEdges", o.preserveBorderEdges},
26 {
"preserveSurfaceCurvature", o.preserveSurfaceCurvature},
27 {
"preserveUvFoldoverEdges", o.preserveUvFoldoverEdges},
28 {
"preserveUvSeamEdges", o.preserveUvSeamEdges},
29 {
"vertexLinkDistance", o.vertexLinkDistance}};
31bool decodeOptions(
const Value&
v, GtsMeshSimplificationOptions& o) {
33 if (!
p || !exact(*
p, {
"aggressiveness",
"enableSmartLink",
"maxIterationCount",
"preserveBorderEdges",
34 "preserveSurfaceCurvature",
"preserveUvFoldoverEdges",
"preserveUvSeamEdges",
35 "vertexLinkDistance"}))
37 auto ag = num(
p->at(
"aggressiveness")), link = num(
p->at(
"vertexLinkDistance"));
38 auto count =
p->at(
"maxIterationCount").getIf<int64_t>();
39 auto smart =
p->at(
"enableSmartLink").getIf<
bool>(),
border =
p->at(
"preserveBorderEdges").getIf<
bool>(),
40 curve =
p->at(
"preserveSurfaceCurvature").getIf<
bool>(), fold =
p->at(
"preserveUvFoldoverEdges").getIf<
bool>(),
41 seam =
p->at(
"preserveUvSeamEdges").getIf<
bool>();
42 if (!ag || !link || !
count || !smart || !
border || !
curve || !fold || !
seam || !std::isfinite(*ag) || *ag <= 0 ||
43 !std::isfinite(*link) || *link < 0 || *count <= 0 || *count > 100000)
45 o.aggressiveness = *ag;
46 o.vertexLinkDistance = *link;
47 o.maxIterationCount = int(*
count);
48 o.enableSmartLink = *smart;
49 o.preserveBorderEdges = *
border;
50 o.preserveSurfaceCurvature = *
curve;
51 o.preserveUvFoldoverEdges = *fold;
52 o.preserveUvSeamEdges = *
seam;
55Value encodeMesh(
const MeshBuild&
m) {
57 p.reserve(
m.positions().size());
58 n.reserve(
m.normals().size());
59 u.reserve(
m.uvs().size());
60 c.reserve(
m.colors().size());
61 i.reserve(
m.indices().size());
62 g.reserve(
m.getIndexCount() / 3);
65 for (
float x :
m.uvs())
u.emplace_back(
x);
67 for (
auto x :
m.
indices()) i.emplace_back(int64_t(
x));
68 for (
int t = 0;
t <
m.getIndexCount() / 3; ++
t) {
69 int group =
m.getTriangleGroup(
t);
72 return Value::Object{{
"colors", std::move(
c)}, {
"groups", std::move(
g)}, {
"indices", std::move(i)},
73 {
"normals", std::move(
n)}, {
"positions", std::move(
p)}, {
"uvs", std::move(
u)}};
75bool decodeFloatArray(
const Value&
v, std::vector<float>& out, std::size_t
max) {
77 if (!
a ||
a->size() >
max)
return false;
78 out.reserve(
a->size());
81 if (!
d || !std::isfinite(*
d) || *
d < -std::numeric_limits<float>::max() ||
82 *
d > std::numeric_limits<float>::max())
84 out.push_back(
float(*
d));
88bool decodeMesh(
const Value&
v, MeshBuild& out,
bool legacy) {
90 if (!o || !(legacy ? exact(*o, {
"groups",
"indices",
"normals",
"positions",
"uvs"})
91 : exact(*o, {
"colors",
"groups",
"indices",
"normals",
"positions",
"uvs"})))
93 std::vector<float>
p,
n,
u,
c;
94 if (!decodeFloatArray(o->at(
"positions"),
p, kMaxVertices * 3) ||
95 !decodeFloatArray(o->at(
"normals"),
n, kMaxVertices * 3) ||
96 !decodeFloatArray(o->at(
"uvs"),
u, kMaxVertices * 2) ||
97 (!legacy && !decodeFloatArray(o->at(
"colors"),
c, kMaxVertices * 4)) ||
p.size() % 3 ||
u.size() % 2 ||
98 n.size() !=
p.size() ||
u.size() / 2 !=
p.size() / 3 || (!
c.empty() &&
c.size() / 4 !=
p.size() / 3))
106 for (
size_t x = 0;
x <
p.size() / 3; ++
x)
107 m.addVertex(
p[
x * 3],
p[
x * 3 + 1],
p[
x * 3 + 2],
n[
x * 3],
n[
x * 3 + 1],
n[
x * 3 + 2],
u[
x * 2],
u[
x * 2 + 1]);
108 if (!
c.empty() && !
m.setVertexColors(std::move(
c)).
ok())
return false;
109 for (
size_t t = 0;
t <
groups->size(); ++
t) {
110 auto*
name = (*groups)[
t].getIf<std::string>();
111 if (!
name ||
name->size() > 1024)
return false;
113 for (
int corner = 0; corner < 3; ++corner) {
114 auto*
id = (*indices)[
t * 3 + corner].getIf<int64_t>();
115 if (!
id || *id < 0 || *id >= int64_t(
p.size() / 3))
return false;
116 ids[corner] = uint32_t(*
id);
119 m.setActiveGroup(*
name);
129 for (
float quality : lodQuality_) qualities.emplace_back(quality);
131 {
"lodQuality", std::move(qualities)},
132 {
"saveResolution", saveResolution_},
133 {
"schema",
"eve.procgen.gts-terrain-mesh-settings"},
134 {
"subTiles", subTiles_},
140 if (json.size() > 64U * 1024U)
142 "GTS terrain mesh settings JSON exceeds size limit",
143 "procgen.gtsTerrainLod.codec"));
147 if (!
root || !exact(*
root, {
"lodCount",
"lodQuality",
"saveResolution",
"schema",
"subTiles",
"version"}))
149 "GTS terrain mesh settings fields are invalid",
150 "procgen.gtsTerrainLod.codec"));
151 auto* schema =
root->at(
"schema").getIf<std::string>();
152 auto* version =
root->at(
"version").getIf<int64_t>();
153 auto* resolution =
root->at(
"saveResolution").getIf<int64_t>();
154 auto*
count =
root->at(
"lodCount").getIf<int64_t>();
155 auto* subTiles =
root->at(
"subTiles").getIf<int64_t>();
157 if (!schema || *schema !=
"eve.procgen.gts-terrain-mesh-settings" || !version || *version != 1 || !resolution ||
158 *resolution < 0 || *resolution > 4 || !
count || *count < 1 || *count > 4 || !subTiles || *subTiles < 0 ||
159 *subTiles > 5 || !qualities || qualities->size() != 4)
161 "GTS terrain mesh settings schema or values are invalid",
162 "procgen.gtsTerrainLod.codec"));
170 for (
int i = 0; i < 4; ++i) {
171 auto* quality = num((*qualities)[
size_t(i)]);
174 "GTS terrain mesh LOD quality is invalid",
175 "procgen.gtsTerrainLod.codec"));
177 if (!set)
return set;
179 *
this = std::move(candidate);
184 if (tiles_.size() > kMaxTiles || settings_.empty() || settings_.size() > kMaxLevels)
186 "GTS terrain LOD set exceeds codec limits",
187 "procgen.gtsTerrainLod.codec"));
189 for (
auto&
s : settings_)
191 {
"quality",
s.quality},
192 {
"transitionHeight",
s.screenRelativeTransitionHeight}});
193 for (
auto&
t : tiles_) {
195 for (
auto&
m :
t.levels) levels.emplace_back(encodeMesh(
m));
197 Value::Object{{
"levels", std::move(levels)}, {
"offsetX",
t.offsetX}, {
"offsetZ",
t.offsetZ}});
201 {
"schema",
"eve.procgen.gts-terrain-lod"},
203 {
"tiles", std::move(
tiles)},
206 if (!json)
return json;
207 if (json.value().size() > kMaxJsonBytes)
214 if (json.size() > kMaxJsonBytes)
220 if (!
root || !exact(*
root, {
"columns",
"rows",
"schema",
"settings",
"tiles",
"version"}))
223 auto* schema =
root->at(
"schema").getIf<std::string>();
224 auto* version =
root->at(
"version").getIf<int64_t>();
225 auto*
columns =
root->at(
"columns").getIf<int64_t>();
226 auto*
rows =
root->at(
"rows").getIf<int64_t>();
229 if (!schema || *schema !=
"eve.procgen.gts-terrain-lod" || !version || (*version != 1 && *version != 2) ||
234 "GTS terrain LOD schema, layout or counts are invalid",
235 "procgen.gtsTerrainLod.codec"));
237 candidate.columns_ = int(*
columns);
238 candidate.rows_ = int(*
rows);
242 if (!o || !exact(*o, {
"options",
"quality",
"transitionHeight"}))
244 "GTS terrain LOD setting fields are invalid",
245 "procgen.gtsTerrainLod.codec"));
246 auto *
q = num(o->at(
"quality")), *
h = num(o->at(
"transitionHeight"));
248 if (!
q || !
h || !std::isfinite(*
q) || *q < 0 || *q > 1 || !std::isfinite(*
h) || *h < 0 || *h >=
previous ||
249 !decodeOptions(o->at(
"options"),
s.simplification))
251 "GTS terrain LOD setting values are invalid",
252 "procgen.gtsTerrainLod.codec"));
253 s.quality = float(*
q);
254 s.screenRelativeTransitionHeight = float(*
h);
255 previous =
s.screenRelativeTransitionHeight;
256 candidate.settings_.push_back(
s);
258 for (
auto& encoded : *
tiles) {
260 if (!o || !exact(*o, {
"levels",
"offsetX",
"offsetZ"}))
262 "GTS terrain LOD tile fields are invalid",
263 "procgen.gtsTerrainLod.codec"));
264 auto *
x = num(o->at(
"offsetX")), *
z = num(o->at(
"offsetZ"));
266 if (!
x || !
z || !std::isfinite(*
x) || !std::isfinite(*
z) || !levels ||
267 levels->size() != candidate.settings_.size())
269 "GTS terrain LOD tile values are invalid",
270 "procgen.gtsTerrainLod.codec"));
274 for (
auto&
m : *levels) {
276 if (!decodeMesh(
m, decoded, *version == 1))
279 tile.
levels.push_back(std::move(decoded));
281 candidate.tiles_.push_back(std::move(tile));
283 *
this = std::move(candidate);
building::EdgeCurveGroup group
std::array< double, 10 > q
std::vector< std::uint32_t > indices
std::vector< float > normals
std::vector< float > positions
graphics::Canvas * previous
std::vector< float > colors
const SquirrelValueOptions & options
TerrainThermalSettings settings
Owning, renderer-independent dynamic values.
std::vector< WfcTile > tiles
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.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
std::map< std::string, Value > Object
std::vector< Value > Array
Owned result of the GTS split-and-sequential-simplify pipeline.
Result< std::string > snapshotJson() const
Serialize the complete owned LOD set as schema-versioned JSON.
Schema-versioned native counterpart of GTSMeshSettings.
Result< void > setLodQuality(int index, float percent)
Set one of the four simplification percentages in the range 0 through 100.
Result< void > setSaveResolution(int value)
Atomically set the SaveResolution enum value.
Result< void > setLodCount(int value)
Atomically set the active LOD count.
Result< std::string > snapshotJson() const
Serialize as strict eve.procgen.gts-terrain-mesh-settings version 1 JSON.
Result< void > setSubTiles(int value)
Atomically set the equal X/Z split-plane count.
Result< void > restoreJson(const std::string &json)
Atomically restore settings from strict schema-versioned JSON.
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Configuration for one sequential GTS terrain mesh LOD.
One split terrain tile and all of its local-space LOD meshes.
std::vector< MeshBuild > levels