载入中...
搜索中...
未找到
TerrainImporter.cpp
浏览该文件的文档.
2
4
6
7#include <bit>
8#include <cmath>
9#include <limits>
10#include <set>
11
12namespace eve::asset_import {
13namespace {
14
15void put32(std::vector<std::uint8_t>& bytes, std::uint32_t value) {
16 for (unsigned shift = 0; shift != 32; shift += 8) bytes.push_back(static_cast<std::uint8_t>(value >> shift));
17}
18
19void putFloat(std::vector<std::uint8_t>& bytes, float value) { put32(bytes, std::bit_cast<std::uint32_t>(value)); }
20
21void putString(std::vector<std::uint8_t>& bytes, std::string_view value) {
22 put32(bytes, static_cast<std::uint32_t>(value.size()));
23 bytes.insert(bytes.end(), value.begin(), value.end());
24}
25
26Result<void> addAsset(PreparedAssetImport& output, PersistentId id, std::string type, Value::Object definition,
27 std::vector<std::uint8_t> blob, std::string blobName, std::vector<std::string> tags,
28 SchemaVersion schemaVersion = SchemaVersion(1)) {
29 auto reference = detail::assetRef(id);
30 if (!reference) return Result<void>::failure(reference.status());
31 const std::string root = "assets/" + id.format() + "/";
32 const std::string definitionPath = root + "asset.json";
33 if (!blob.empty()) definition["blob"] = Value(root + blobName);
34 auto encoded = Value(std::move(definition)).toJson();
35 if (!encoded) return Result<void>::failure(encoded.status());
36 std::string text = std::move(encoded).takeValue();
37 output.manifest.assets.push_back(
38 {std::move(reference).takeValue(), std::move(type), schemaVersion, definitionPath,
39 detail::sha256(std::span<const std::uint8_t>(reinterpret_cast<const std::uint8_t*>(text.data()), text.size())),
40 std::move(tags)});
41 output.entries.push_back({definitionPath, {text.begin(), text.end()}});
42 if (!blob.empty()) output.entries.push_back({root + blobName, std::move(blob)});
43 return Result<void>::success();
44}
45
46} // namespace
47
50 std::string_view importer, const AssetImportLimits& limits) {
51 if (terrain.width < 2 || terrain.height < 2 || !std::isfinite(terrain.spacingX) ||
52 !std::isfinite(terrain.spacingZ) || terrain.spacingX <= 0 || terrain.spacingZ <= 0 ||
53 terrain.heightsMeters.size() != std::uint64_t(terrain.width) * terrain.height)
55 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain dimensions, spacing or height count is invalid",
56 {}, {}, "asset.import"));
57 const std::uint64_t heightBytes = std::uint64_t(terrain.heightsMeters.size()) * sizeof(float) + 24;
58 if (heightBytes > limits.maximumDecodedBytes || terrain.layers.size() > limits.maximumAssets ||
59 terrain.scatterRules.size() > limits.maximumAssets || terrain.detailPrototypes.size() > limits.maximumAssets ||
60 terrain.instances.size() > limits.maximumAssets)
61 return Result<PreparedAssetImport>::failure(Diagnostic::error(
62 DiagnosticCode::InvalidArgument, "terrain import budget is exceeded", {}, {}, "asset.import"));
63 for (const float height : terrain.heightsMeters)
64 if (!std::isfinite(height))
65 return Result<PreparedAssetImport>::failure(Diagnostic::error(
66 DiagnosticCode::ParseError, "terrain height contains a non-finite value", {}, {}, "asset.import"));
67 auto manifest = detail::baseManifest(package, importer);
68 if (!manifest) return Result<PreparedAssetImport>::failure(manifest.status());
70 output.manifest = std::move(manifest).takeValue();
71 output.manifest.provenance["sourceTransform"] = Value(terrain.sourceTransform);
72
73 const PersistentId terrainId = package.packageId.child("terrain:root");
74 const PersistentId materialId = package.packageId.child("terrain:material");
75 const PersistentId pcgId = package.packageId.child("terrain:pcg");
76 const PersistentId instancesId = package.packageId.child("terrain:instances");
77
78 std::vector<std::uint8_t> heights;
79 heights.insert(heights.end(), {'E', 'V', 'T', 'R', 'N', 0, 1, 0});
80 put32(heights, terrain.width);
81 put32(heights, terrain.height);
82 putFloat(heights, terrain.spacingX);
83 putFloat(heights, terrain.spacingZ);
84 for (float height : terrain.heightsMeters) putFloat(heights, height);
85 Value::Object terrainDefinition;
86 terrainDefinition["schema"] = Value("eve.terrain");
87 terrainDefinition["schemaVersion"] = Value(std::int64_t(1));
88 terrainDefinition["width"] = Value(static_cast<std::int64_t>(terrain.width));
89 terrainDefinition["height"] = Value(static_cast<std::int64_t>(terrain.height));
90 terrainDefinition["spacingX"] = Value(double(terrain.spacingX));
91 terrainDefinition["spacingZ"] = Value(double(terrain.spacingZ));
92 terrainDefinition["coordinateSystem"] = Value("right-handed-x-right-y-up-minus-z-forward");
93 terrainDefinition["heightUnit"] = Value("meter");
94 if (!terrain.layers.empty()) terrainDefinition["material"] = Value("asset://" + materialId.format());
95 if (!terrain.scatterRules.empty()) terrainDefinition["pcg"] = Value("asset://" + pcgId.format());
96 if (!terrain.instances.empty()) terrainDefinition["instances"] = Value("asset://" + instancesId.format());
97 auto addedTerrain = addAsset(output, terrainId, "eve.terrain", std::move(terrainDefinition), std::move(heights),
98 "heightfield.bin", {"terrain"});
99 if (!addedTerrain) return Result<PreparedAssetImport>::failure(addedTerrain.status());
100
101 if (!terrain.layers.empty()) {
102 Value::Array layers;
103 for (const auto& layer : terrain.layers) {
104 if (layer.name.empty() || !std::isfinite(layer.tileSizeMeters) || layer.tileSizeMeters <= 0 ||
105 (layer.normalConvention != "opengl" && layer.normalConvention != "directx") ||
106 !std::isfinite(layer.normalScale) || layer.normalScale < -8 || layer.normalScale > 8 ||
107 !std::isfinite(layer.metallic) || layer.metallic < 0 || layer.metallic > 1 ||
108 !std::isfinite(layer.smoothness) || layer.smoothness < 0 || layer.smoothness > 1)
109 return Result<PreparedAssetImport>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,
110 "terrain layer metadata is invalid",
111 layer.name, {}, "asset.import"));
112 auto vector = [](const auto& source) {
113 Value::Array result;
114 for (float value : source) {
115 if (!std::isfinite(value)) throw std::invalid_argument("non-finite terrain layer vector");
116 result.emplace_back(double(value));
117 }
118 return result;
119 };
120 if (layer.tileScaleMeters[0] <= 0 || layer.tileScaleMeters[1] <= 0)
121 return Result<PreparedAssetImport>::failure(Diagnostic::error(
122 DiagnosticCode::InvalidArgument, "terrain layer scale is invalid", layer.name, {}, "asset.import"));
123 for (const auto* image : {&layer.diffuseAsset, &layer.normalAsset, &layer.weightAsset, &layer.maskAsset})
124 if (!image->empty() && !AssetRef::parse(*image))
125 return Result<PreparedAssetImport>::failure(Diagnostic::error(DiagnosticCode::ParseError,
126 "terrain image AssetRef is invalid",
127 layer.name, {}, "asset.import"));
129 value["name"] = Value(layer.name);
130 value["diffuseSource"] = Value(layer.diffuseSource);
131 value["normalSource"] = Value(layer.normalSource);
132 value["weightSource"] = Value(layer.weightSource);
133 value["maskSource"] = Value(layer.maskSource);
134 value["diffuseAsset"] = Value(layer.diffuseAsset);
135 value["normalAsset"] = Value(layer.normalAsset);
136 value["weightAsset"] = Value(layer.weightAsset);
137 value["maskAsset"] = Value(layer.maskAsset);
138 value["normalConvention"] = Value(layer.normalConvention);
139 value["tileSizeMeters"] = Value(double(layer.tileSizeMeters));
140 try {
141 value["tileScaleMeters"] = Value(vector(layer.tileScaleMeters));
142 value["tileOffsetMeters"] = Value(vector(layer.tileOffsetMeters));
143 value["maskRemapMinimum"] = Value(vector(layer.maskRemapMinimum));
144 value["maskRemapMaximum"] = Value(vector(layer.maskRemapMaximum));
145 value["specular"] = Value(vector(layer.specular));
146 } catch (const std::invalid_argument&) {
147 return Result<PreparedAssetImport>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,
148 "terrain layer vector is non-finite",
149 layer.name, {}, "asset.import"));
150 }
151 value["metallic"] = Value(double(layer.metallic));
152 value["normalScale"] = Value(double(layer.normalScale));
153 value["smoothness"] = Value(double(layer.smoothness));
154 layers.emplace_back(std::move(value));
155 }
157 definition["schema"] = Value("eve.terrain-material");
158 definition["schemaVersion"] = Value(std::int64_t(3));
159 definition["layers"] = Value(std::move(layers));
160 if (!terrain.holesAsset.empty() && !AssetRef::parse(terrain.holesAsset))
161 return Result<PreparedAssetImport>::failure(Diagnostic::error(
162 DiagnosticCode::ParseError, "terrain holes AssetRef is invalid", {}, {}, "asset.import"));
163 definition["holesSource"] = Value(terrain.holesSource);
164 definition["holesAsset"] = Value(terrain.holesAsset);
165 Value::Array controls;
166 Value::Array controlAssets;
167 for (std::size_t index = 0; index < terrain.controlSources.size(); ++index) {
168 controls.emplace_back(terrain.controlSources[index]);
169 if (!terrain.controlAssets[index].empty() && !AssetRef::parse(terrain.controlAssets[index]))
171 Diagnostic::error(DiagnosticCode::ParseError, "terrain control AssetRef is invalid",
172 std::to_string(index), {}, "asset.import"));
173 controlAssets.emplace_back(terrain.controlAssets[index]);
174 }
175 definition["controlSources"] = Value(std::move(controls));
176 definition["controlAssets"] = Value(std::move(controlAssets));
177 if (!std::isfinite(terrain.boundsMultiplier) || terrain.boundsMultiplier <= 0)
178 return Result<PreparedAssetImport>::failure(Diagnostic::error(
179 DiagnosticCode::InvalidArgument, "terrain bounds multiplier is invalid", {}, {}, "asset.import"));
180 definition["boundsMultiplier"] = Value(double(terrain.boundsMultiplier));
181 auto added = addAsset(output, materialId, "eve.terrain-material", std::move(definition), {}, {},
182 {"terrain", "material"}, SchemaVersion(3));
183 if (!added) return Result<PreparedAssetImport>::failure(added.status());
184 }
185
186 if (!terrain.scatterRules.empty()) {
187 Value::Array rules;
188 std::set<std::string> ids;
189 for (const auto& rule : terrain.scatterRules) {
190 if (rule.id.empty() || rule.prototype.empty() || !ids.emplace(rule.id).second ||
191 !std::isfinite(rule.densityPerSquareMeter) || rule.densityPerSquareMeter < 0 ||
192 !std::isfinite(rule.minimumSlopeRadians) || !std::isfinite(rule.maximumSlopeRadians) ||
193 rule.minimumSlopeRadians > rule.maximumSlopeRadians)
194 return Result<PreparedAssetImport>::failure(Diagnostic::error(
195 DiagnosticCode::InvalidArgument, "terrain scatter rule is invalid", rule.id, {}, "asset.import"));
197 value["id"] = Value(rule.id);
198 value["prototype"] = Value(rule.prototype);
199 value["layer"] = Value(rule.layer);
200 value["densityPerSquareMeter"] = Value(double(rule.densityPerSquareMeter));
201 value["minimumSlopeRadians"] = Value(double(rule.minimumSlopeRadians));
202 value["maximumSlopeRadians"] = Value(double(rule.maximumSlopeRadians));
203 value["seed"] = Value(static_cast<std::int64_t>(rule.seed & 0x7fffffffffffffffULL));
204 rules.emplace_back(std::move(value));
205 }
207 definition["schema"] = Value("eve.pcg-graph");
208 definition["schemaVersion"] = Value(std::int64_t(1));
209 definition["algorithm"] = Value("terrain-layer-scatter-v1");
210 definition["rules"] = Value(std::move(rules));
211 auto added = addAsset(output, pcgId, "eve.pcg-graph", std::move(definition), {}, {}, {"terrain", "pcg"});
212 if (!added) return Result<PreparedAssetImport>::failure(added.status());
213 }
214
215 if (!terrain.instances.empty()) {
216 std::vector<std::uint8_t> instances;
217 instances.insert(instances.end(), {'E', 'V', 'I', 'N', 'S', 'T', 0, 1});
218 put32(instances, static_cast<std::uint32_t>(terrain.instances.size()));
219 put32(instances, 0);
220 for (const auto& instance : terrain.instances) {
221 if (instance.prototype.empty() || instance.prototype.size() > limits.maximumStringBytes ||
222 !isValidUtf8(instance.prototype, Utf8NullPolicy::Reject))
223 return Result<PreparedAssetImport>::failure(Diagnostic::error(
224 DiagnosticCode::InvalidArgument, "terrain instance prototype is invalid", {}, {}, "asset.import"));
225 const float rotationLength =
226 std::sqrt(instance.rotation[0] * instance.rotation[0] + instance.rotation[1] * instance.rotation[1] +
227 instance.rotation[2] * instance.rotation[2] + instance.rotation[3] * instance.rotation[3]);
228 if (!std::isfinite(rotationLength) || rotationLength < 0.999f || rotationLength > 1.001f ||
229 instance.scale[0] == 0.f || instance.scale[1] == 0.f || instance.scale[2] == 0.f)
231 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain instance rotation or scale is invalid",
232 instance.prototype, {}, "asset.import"));
233 putString(instances, instance.prototype);
234 for (float value : instance.position) {
235 if (!std::isfinite(value))
236 return Result<PreparedAssetImport>::failure(Diagnostic::error(
237 DiagnosticCode::ParseError, "instance position is non-finite", {}, {}, "asset.import"));
239 }
240 for (float value : instance.rotation) {
241 if (!std::isfinite(value))
242 return Result<PreparedAssetImport>::failure(Diagnostic::error(
243 DiagnosticCode::ParseError, "instance rotation is non-finite", {}, {}, "asset.import"));
245 }
246 for (float value : instance.scale) {
247 if (!std::isfinite(value))
248 return Result<PreparedAssetImport>::failure(Diagnostic::error(
249 DiagnosticCode::ParseError, "instance scale is non-finite", {}, {}, "asset.import"));
251 }
252 }
253 if (instances.size() > limits.maximumDecodedBytes)
254 return Result<PreparedAssetImport>::failure(Diagnostic::error(
255 DiagnosticCode::InvalidArgument, "terrain instance blob exceeds budget", {}, {}, "asset.import"));
257 definition["schema"] = Value("eve.instance-set");
258 definition["schemaVersion"] = Value(std::int64_t(5));
259 definition["count"] = Value(static_cast<std::int64_t>(terrain.instances.size()));
260 definition["partition"] = Value("single-cell");
261 const bool validWavingGrass = std::isfinite(terrain.wavingGrassAmount) && terrain.wavingGrassAmount >= 0.f &&
262 std::isfinite(terrain.wavingGrassSpeed) && terrain.wavingGrassSpeed >= 0.f &&
263 std::isfinite(terrain.wavingGrassStrength) && terrain.wavingGrassStrength >= 0.f &&
264 std::all_of(terrain.wavingGrassTint.begin(), terrain.wavingGrassTint.end(),
265 [](float value) { return std::isfinite(value) && value >= 0.f && value <= 1.f; });
266 if (!validWavingGrass)
267 return Result<PreparedAssetImport>::failure(Diagnostic::error(
268 DiagnosticCode::InvalidArgument, "terrain waving grass settings are invalid", {}, {}, "asset.import"));
269 definition["wavingGrass"] = Value(Value::Object{
270 {"amount", Value(double(terrain.wavingGrassAmount))},
271 {"speed", Value(double(terrain.wavingGrassSpeed))},
272 {"strength", Value(double(terrain.wavingGrassStrength))},
273 {"tint", Value(Value::Array{Value(double(terrain.wavingGrassTint[0])),
274 Value(double(terrain.wavingGrassTint[1])),
275 Value(double(terrain.wavingGrassTint[2])),
276 Value(double(terrain.wavingGrassTint[3]))})}});
277 Value::Array detailPrototypes;
278 std::set<std::string> detailPrototypeIds;
279 for (const auto& prototype : terrain.detailPrototypes) {
280 const bool validMode = prototype.renderMode == "GrassBillboard" || prototype.renderMode == "Grass" ||
281 prototype.renderMode == "VertexLit";
282 const bool finite = std::isfinite(prototype.minWidth) && std::isfinite(prototype.maxWidth) &&
283 std::isfinite(prototype.minHeight) && std::isfinite(prototype.maxHeight) &&
284 std::isfinite(prototype.noiseSpread) && std::isfinite(prototype.density) &&
285 std::isfinite(prototype.alignToGround) && std::isfinite(prototype.positionJitter) &&
286 std::isfinite(prototype.bendFactor) && std::isfinite(prototype.holeEdgePadding) &&
287 std::all_of(prototype.healthyColor.begin(), prototype.healthyColor.end(),
288 [](float value) { return std::isfinite(value) && value >= 0.f && value <= 1.f; }) &&
289 std::all_of(prototype.dryColor.begin(), prototype.dryColor.end(),
290 [](float value) { return std::isfinite(value) && value >= 0.f && value <= 1.f; });
291 if (prototype.prototype.empty() || prototype.prototype.size() > limits.maximumStringBytes ||
292 !isValidUtf8(prototype.prototype, Utf8NullPolicy::Reject) ||
293 !detailPrototypeIds.emplace(prototype.prototype).second || !validMode || !finite ||
294 prototype.minWidth <= 0 || prototype.maxWidth < prototype.minWidth || prototype.minHeight <= 0 ||
295 prototype.maxHeight < prototype.minHeight || prototype.noiseSpread < 0 || prototype.density < 0 ||
296 prototype.alignToGround < 0 || prototype.alignToGround > 1 || prototype.positionJitter < 0 ||
297 prototype.positionJitter > 1 || prototype.bendFactor < 0 || prototype.bendFactor > 1 ||
298 prototype.holeEdgePadding < 0 || prototype.holeEdgePadding > 1 || prototype.resourceAsset.empty() ||
299 !AssetRef::parse(prototype.resourceAsset) ||
300 (prototype.usePrototypeMesh != prototype.prototype.starts_with("unity-guid:")))
301 return Result<PreparedAssetImport>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,
302 "terrain detail prototype is invalid",
303 prototype.prototype, {}, "asset.import"));
304 detailPrototypes.emplace_back(Value::Object{{"prototype", Value(prototype.prototype)},
305 {"renderMode", Value(prototype.renderMode)},
306 {"usePrototypeMesh", Value(prototype.usePrototypeMesh)},
307 {"useInstancing", Value(prototype.useInstancing)},
308 {"minWidth", Value(double(prototype.minWidth))},
309 {"maxWidth", Value(double(prototype.maxWidth))},
310 {"minHeight", Value(double(prototype.minHeight))},
311 {"maxHeight", Value(double(prototype.maxHeight))},
312 {"noiseSeed", Value(prototype.noiseSeed)},
313 {"noiseSpread", Value(double(prototype.noiseSpread))},
314 {"density", Value(double(prototype.density))},
315 {"alignToGround", Value(double(prototype.alignToGround))},
316 {"positionJitter", Value(double(prototype.positionJitter))},
317 {"healthyColor", Value(Value::Array{Value(double(prototype.healthyColor[0])), Value(double(prototype.healthyColor[1])), Value(double(prototype.healthyColor[2])), Value(double(prototype.healthyColor[3]))})},
318 {"dryColor", Value(Value::Array{Value(double(prototype.dryColor[0])), Value(double(prototype.dryColor[1])), Value(double(prototype.dryColor[2])), Value(double(prototype.dryColor[3]))})},
319 {"bendFactor", Value(double(prototype.bendFactor))},
320 {"holeEdgePadding", Value(double(prototype.holeEdgePadding))},
321 {"useDensityScaling", Value(prototype.useDensityScaling)},
322 {"resourceAsset", Value(prototype.resourceAsset)}});
323 }
324 for (const auto& instance : terrain.instances)
325 if ((instance.prototype.starts_with("unity-texture-guid:") ||
326 (instance.prototype.starts_with("unity-guid:") && !terrain.detailPrototypes.empty())) &&
327 !detailPrototypeIds.contains(instance.prototype))
329 Diagnostic::error(DiagnosticCode::NotFound, "terrain detail instance prototype is undeclared",
330 instance.prototype, {}, "asset.import"));
331 definition["prototypes"] = Value(std::move(detailPrototypes));
332 auto added = addAsset(output, instancesId, "eve.instance-set", std::move(definition), std::move(instances),
333 "instances.bin", {"terrain", "instances"}, SchemaVersion(5));
334 if (!added) return Result<PreparedAssetImport>::failure(added.status());
335 }
336
337 auto terrainRef = detail::assetRef(terrainId);
338 if (!terrainRef) return Result<PreparedAssetImport>::failure(terrainRef.status());
339 output.manifest.entrypoints.emplace("default", std::move(terrainRef).takeValue());
340 auto rootRef = detail::assetRef(terrainId);
341 if (!rootRef) return Result<PreparedAssetImport>::failure(rootRef.status());
342 if (!terrain.layers.empty()) {
343 auto target = detail::assetRef(materialId);
345 output.manifest.dependencies.push_back({rootRef.value(),
346 std::move(target).takeValue(),
347 asset::EvaDependencyKind::RuntimeRequired,
348 "material",
349 {},
350 "eve.terrain-material/3"});
351 }
352 if (!terrain.scatterRules.empty()) {
353 auto target = detail::assetRef(pcgId);
355 output.manifest.dependencies.push_back(
356 {rootRef.value(),
357 std::move(target).takeValue(),
358 asset::EvaDependencyKind::RuntimeOptional,
359 "pcg",
360 {},
361 "eve.pcg-graph/1",
362 {{"behavior", Value("omit-feature")}, {"observableCode", Value("terrain.pcg-unavailable")}}});
363 }
364 if (!terrain.instances.empty()) {
365 auto target = detail::assetRef(instancesId);
367 output.manifest.dependencies.push_back(
368 {std::move(rootRef).takeValue(),
369 std::move(target).takeValue(),
370 asset::EvaDependencyKind::RuntimeOptional,
371 "instances",
372 {},
373 "eve.instance-set/5",
374 {{"behavior", Value("omit-feature")}, {"observableCode", Value("terrain.instances-unavailable")}}});
375 }
377}
378
379} // namespace eve::asset_import
LogicalId target
double value
int root
Definition AnimSmr.cpp:119
std::string output
std::string terrain
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
std::vector< BuildingInstanceSnapshot > instances
std::vector< std::uint8_t > blob
vk::UniqueImage image
glm::uvec4 ids
std::uint32_t height
std::string text
std::uint64_t bytes
bool finite
TileLayer * layer
LogicalId definition
Shared canonical terrain inputs used by Unreal and Unity adapters.
uint32_t index
const UnitySourceAsset & source
const AssetImportLimits & limits
Strict allocation-free UTF-8 validation.
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
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
Id128 public API.
Definition Identity.h:112
std::string format() const
Formats the ID in lower-case canonical UUID text.
Definition Identity.h:173
Id128 child(std::string_view role) const noexcept
Derives a deterministic child UUID for a hierarchical identity.
Definition Identity.h:192
std::string sha256(std::span< const std::uint8_t > bytes)
Sha 256.
Result< AssetRef > assetRef(PersistentId id)
Asset ref.
void put32(std::vector< std::uint8_t > &bytes, std::uint32_t value)
Append little-endian uint32 to owned storage.
void putFloat(std::vector< std::uint8_t > &bytes, float value)
Append IEEE FLOAT to owned storage.
Result< PreparedAssetImport > prepareCanonicalTerrainImport(const ImportPackageIdentity &package, const CanonicalTerrainInput &terrain, std::string_view importer, const AssetImportLimits &limits)
Build eve.terrain, terrain material, PCG and instance assets from canonical input.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
StrongUuid< detail::PersistentIdTag > PersistentId
Stable instance identity for persistence, networking and process boundaries.
Definition Identity.h:254
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
bool isValidUtf8(std::string_view text, Utf8NullPolicy nullPolicy) noexcept
Validate shortest-form UTF-8, Unicode scalar range and optional NUL exclusion.
Value::Object provenance
Definition EvaManifest.h:72
Parser and allocation budgets applied to untrusted importer inputs.
Fully parsed source terrain ready for canonical archive construction.
Common stable package metadata supplied by an import transaction.
Owning, validated source archive candidate not yet published to disk/database.