载入中...
搜索中...
未找到
EvpackTerrainMaterialLoader.cpp
浏览该文件的文档.
2
5
6#include <cmath>
7#include <limits>
8
9namespace eve::asset_procgen {
10namespace {
11
12const Value* field(const Value::Object& object, std::string_view name) {
13 const auto found = object.find(std::string(name));
14 return found == object.end() ? nullptr : &found->second;
15}
16
17Result<std::string> textField(const Value::Object& object, std::string_view name,
18 const TerrainMaterialLoadLimits& limits, bool allowEmpty) {
19 const Value* value = field(object, name);
20 if (!value || !value->isString() || (!allowEmpty && value->asString().empty()) ||
21 value->asString().size() > limits.maximumStringBytes || !isValidUtf8(value->asString(), Utf8NullPolicy::Reject))
23 "terrain layer string is invalid", std::string(name), {},
24 "asset.procgen.terrain-material"));
25 return Result<std::string>::success(value->asString());
26}
27
28Result<std::optional<AssetRef>> assetField(const Value::Object& object, std::string_view name,
29 const TerrainMaterialLoadLimits& limits) {
30 auto text = textField(object, name, limits, true);
31 if (!text) return Result<std::optional<AssetRef>>::failure(text.status());
32 if (text.value().empty()) return Result<std::optional<AssetRef>>::success(std::nullopt);
33 auto parsed = AssetRef::parse(text.value());
34 if (!parsed)
35 return Result<std::optional<AssetRef>>::failure(
36 Diagnostic::error(DiagnosticCode::ParseError, "terrain image AssetRef is invalid", std::string(name), {},
37 "asset.procgen.terrain-material"));
38 return Result<std::optional<AssetRef>>::success(std::move(parsed).takeValue());
39}
40
41Result<float> numberField(const Value::Object& object, std::string_view name, float fallback) {
42 const auto* value = field(object, name);
43 if (!value) return Result<float>::success(fallback);
44 if (!value->isNumeric())
46 std::string(name), {}, "asset.procgen.terrain-material"));
47 const double number = value->isInt64() ? double(value->asInt()) : value->asDouble();
48 if (!std::isfinite(number) || std::abs(number) > std::numeric_limits<float>::max())
50 "terrain number is outside float range", std::string(name), {},
51 "asset.procgen.terrain-material"));
52 return Result<float>::success(float(number));
53}
54
55template <std::size_t N>
56Result<std::array<float, N>> vectorField(const Value::Object& object, std::string_view name,
57 std::array<float, N> fallback) {
58 const auto* value = field(object, name);
59 if (!value) return Result<std::array<float, N>>::success(fallback);
60 const auto* array = value->getIf<Value::Array>();
61 if (!array || array->size() != N)
62 return Result<std::array<float, N>>::failure(Diagnostic::error(DiagnosticCode::ParseError,
63 "terrain vector is invalid", std::string(name),
64 {}, "asset.procgen.terrain-material"));
65 for (std::size_t i = 0; i < N; ++i) {
66 Value::Object scalar{{"value", (*array)[i]}};
67 auto parsed = numberField(scalar, "value", 0);
68 if (!parsed) return Result<std::array<float, N>>::failure(parsed.status());
69 fallback[i] = parsed.value();
70 }
71 return Result<std::array<float, N>>::success(fallback);
72}
73
74} // namespace
75
77 const asset::EvpackCapabilities& capabilities,
78 const TerrainMaterialLoadLimits& limits) const {
79 auto payload = reader_.read(material, "eve.terrain-material/3", capabilities, limits.maximumDecodedBytes);
80 if (!payload && payload.error()->code() == DiagnosticCode::TypeMismatch)
81 payload = reader_.read(material, "eve.terrain-material/2", capabilities, limits.maximumDecodedBytes);
83 const asset::RuntimeAssetChunk* definition = nullptr;
84 for (const auto& chunk : payload.value().chunks) {
85 if (chunk.kind != asset::EvpackChunkKind::Definition)
87 Diagnostic::error(DiagnosticCode::TypeMismatch, "terrain material cannot contain runtime bulk", {}, {},
88 "asset.procgen.terrain-material"));
89 if (definition)
91 Diagnostic::error(DiagnosticCode::Conflict, "terrain material has duplicate definitions", {}, {},
92 "asset.procgen.terrain-material"));
93 definition = &chunk;
94 }
95 if (!definition)
97 "terrain material definition is missing", {},
98 {}, "asset.procgen.terrain-material"));
99 asset::RuntimeDefinitionLimits definitionLimits;
100 definitionLimits.maximumBytes = limits.maximumDecodedBytes;
101 definitionLimits.maximumStringBytes = limits.maximumStringBytes;
102 auto metadata = asset::decodeRuntimeDefinition(definition->bytes, definitionLimits);
103 if (!metadata) return Result<LoadedTerrainMaterial>::failure(metadata.status());
104 const auto* root = metadata.value().getIf<Value::Object>();
105 const Value* schema = root ? field(*root, "schema") : nullptr;
106 const Value* version = root ? field(*root, "schemaVersion") : nullptr;
107 const Value* layersValue = root ? field(*root, "layers") : nullptr;
108 const auto* layers = layersValue ? layersValue->getIf<Value::Array>() : nullptr;
109 if (!schema || !schema->isString() || schema->asString() != "eve.terrain-material" || !version ||
110 !version->isInt64() || (version->asInt() != 2 && version->asInt() != 3) || !layers || layers->empty() ||
111 layers->size() > limits.maximumLayers)
113 "terrain material envelope is invalid", {}, {},
114 "asset.procgen.terrain-material"));
115 std::vector<RuntimeTerrainLayer> decoded;
116 decoded.reserve(layers->size());
117 for (std::size_t index = 0; index < layers->size(); ++index) {
118 const auto* layer = (*layers)[index].getIf<Value::Object>();
119 if (!layer)
121 Diagnostic::error(DiagnosticCode::ParseError, "terrain layer must be an object", std::to_string(index),
122 {}, "asset.procgen.terrain-material"));
123 auto name = textField(*layer, "name", limits, false);
124 auto diffuse = textField(*layer, "diffuseSource", limits, true);
125 auto normal = textField(*layer, "normalSource", limits, true);
126 auto weight = textField(*layer, "weightSource", limits, true);
127 auto convention = textField(*layer, "normalConvention", limits, false);
128 const Value* tile = field(*layer, "tileSizeMeters");
129 if (!name || !diffuse || !normal || !weight || !convention || !tile || (!tile->isDouble() && !tile->isInt64()))
131 Diagnostic::error(DiagnosticCode::ParseError, "terrain layer fields are incomplete",
132 std::to_string(index), {}, "asset.procgen.terrain-material"));
133 const double tileSize = tile->isDouble() ? tile->asDouble() : double(tile->asInt());
134 if (!std::isfinite(tileSize) || tileSize <= 0 || tileSize > 1'000'000.0 ||
135 (convention.value() != "opengl" && convention.value() != "directx"))
137 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain layer physical semantics are invalid",
138 std::to_string(index), {}, "asset.procgen.terrain-material"));
139 RuntimeTerrainLayer runtime;
140 runtime.name = std::move(name).takeValue();
141 runtime.diffuseSource = std::move(diffuse).takeValue();
142 runtime.normalSource = std::move(normal).takeValue();
143 runtime.weightSource = std::move(weight).takeValue();
144 runtime.normalConvention = std::move(convention).takeValue();
145 runtime.tileSizeMeters = static_cast<float>(tileSize);
146 if (version->asInt() >= 2) {
147 auto mask = textField(*layer, "maskSource", limits, true);
148 auto tileScale = vectorField<2>(*layer, "tileScaleMeters", {1, 1});
149 auto tileOffset = vectorField<2>(*layer, "tileOffsetMeters", {});
150 auto remapMin = vectorField<4>(*layer, "maskRemapMinimum", {});
151 auto remapMax = vectorField<4>(*layer, "maskRemapMaximum", {1, 1, 1, 1});
152 auto specular = vectorField<4>(*layer, "specular", {});
153 auto metallic = numberField(*layer, "metallic", 0);
154 auto normalScale = numberField(*layer, "normalScale", 1);
155 auto smoothness = numberField(*layer, "smoothness", 0);
156 if (!mask || !tileScale || !tileOffset || !remapMin || !remapMax || !specular || !metallic ||
157 !normalScale || !smoothness)
159 Diagnostic::error(DiagnosticCode::ParseError, "terrain layer v2 fields are incomplete",
160 std::to_string(index), {}, "asset.procgen.terrain-material"));
161 if (tileScale.value()[0] <= 0 || tileScale.value()[1] <= 0 || metallic.value() < 0 ||
162 metallic.value() > 1 || normalScale.value() < -8 || normalScale.value() > 8 || smoothness.value() < 0 ||
163 smoothness.value() > 1)
165 DiagnosticCode::InvalidArgument, "terrain layer v2 physical semantics are invalid",
166 std::to_string(index), {}, "asset.procgen.terrain-material"));
167 runtime.maskSource = std::move(mask).takeValue();
168 runtime.tileScaleMeters = std::move(tileScale).takeValue();
169 runtime.tileOffsetMeters = std::move(tileOffset).takeValue();
170 runtime.maskRemapMinimum = std::move(remapMin).takeValue();
171 runtime.maskRemapMaximum = std::move(remapMax).takeValue();
172 runtime.specular = std::move(specular).takeValue();
173 runtime.metallic = metallic.value();
174 runtime.normalScale = normalScale.value();
175 runtime.smoothness = smoothness.value();
176 if (version->asInt() >= 3) {
177 auto diffuseAsset = assetField(*layer, "diffuseAsset", limits);
178 auto normalAsset = assetField(*layer, "normalAsset", limits);
179 auto weightAsset = assetField(*layer, "weightAsset", limits);
180 auto maskAsset = assetField(*layer, "maskAsset", limits);
181 if (!diffuseAsset || !normalAsset || !weightAsset || !maskAsset)
183 Diagnostic::error(DiagnosticCode::ParseError, "terrain layer v3 AssetRefs are invalid",
184 std::to_string(index), {}, "asset.procgen.terrain-material"));
185 runtime.diffuseAsset = std::move(diffuseAsset).takeValue();
186 runtime.normalAsset = std::move(normalAsset).takeValue();
187 runtime.weightAsset = std::move(weightAsset).takeValue();
188 runtime.maskAsset = std::move(maskAsset).takeValue();
189 }
190 }
191 decoded.push_back(std::move(runtime));
192 }
193 std::string holes;
194 std::array<std::string, 4> controls;
195 std::optional<AssetRef> holesAsset;
196 std::array<std::optional<AssetRef>, 4> controlAssets;
197 float boundsMultiplier = 1;
198 if (version->asInt() >= 2) {
199 auto parsedHoles = textField(*root, "holesSource", limits, true);
200 const auto* encodedControls = field(*root, "controlSources");
201 const auto* controlArray = encodedControls ? encodedControls->getIf<Value::Array>() : nullptr;
202 auto parsedBounds = numberField(*root, "boundsMultiplier", 1);
203 if (!parsedHoles || !controlArray || controlArray->size() != controls.size() || !parsedBounds)
205 "terrain material v2 fields are incomplete",
206 {}, {}, "asset.procgen.terrain-material"));
207 for (std::size_t i = 0; i < controls.size(); ++i) {
208 if (!(*controlArray)[i].isString() || (*controlArray)[i].asString().size() > limits.maximumStringBytes ||
209 !isValidUtf8((*controlArray)[i].asString(), Utf8NullPolicy::Reject))
211 Diagnostic::error(DiagnosticCode::ParseError, "terrain control source is invalid",
212 std::to_string(i), {}, "asset.procgen.terrain-material"));
213 controls[i] = (*controlArray)[i].asString();
214 }
215 if (parsedBounds.value() <= 0 || parsedBounds.value() > 1'000'000)
217 "terrain bounds multiplier is invalid", {},
218 {}, "asset.procgen.terrain-material"));
219 holes = std::move(parsedHoles).takeValue();
220 boundsMultiplier = parsedBounds.value();
221 if (version->asInt() >= 3) {
222 auto parsedHolesAsset = assetField(*root, "holesAsset", limits);
223 const auto* encodedControlAssets = field(*root, "controlAssets");
224 const auto* controlAssetArray =
225 encodedControlAssets ? encodedControlAssets->getIf<Value::Array>() : nullptr;
226 if (!parsedHolesAsset || !controlAssetArray || controlAssetArray->size() != controlAssets.size())
228 Diagnostic::error(DiagnosticCode::ParseError, "terrain material v3 AssetRefs are incomplete", {},
229 {}, "asset.procgen.terrain-material"));
230 holesAsset = std::move(parsedHolesAsset).takeValue();
231 for (std::size_t i = 0; i < controlAssets.size(); ++i) {
232 Value::Object encoded{{"asset", (*controlAssetArray)[i]}};
233 auto parsed = assetField(encoded, "asset", limits);
234 if (!parsed)
236 Diagnostic::error(DiagnosticCode::ParseError, "terrain control AssetRef is invalid",
237 std::to_string(i), {}, "asset.procgen.terrain-material"));
238 controlAssets[i] = std::move(parsed).takeValue();
239 }
240 }
241 }
243 result.layers = std::move(decoded);
244 result.holesSource = std::move(holes);
245 result.controlSources = std::move(controls);
246 result.holesAsset = std::move(holesAsset);
247 result.controlAssets = std::move(controlAssets);
248 result.boundsMultiplier = boundsMultiplier;
249 result.variant = std::move(payload).takeValue().variant;
250 return Result<LoadedTerrainMaterial>::success(std::move(result));
251}
252
253} // namespace eve::asset_procgen
double value
Value::Object payload
int root
Definition AnimSmr.cpp:119
int mask
Runtime canonical terrain-layer semantics.
std::string text
std::string name
Texture * normal
float metallic
TileLayer * layer
Material * material
double number
bool found
JSON-free versioned runtime metadata codec.
float size
Definition TreeMesh.cpp:156
uint32_t index
const AssetImportLimits & limits
Strict allocation-free UTF-8 validation.
Stable asset identity backed by PersistentId.
Definition ResourceRef.h:53
static Result< AssetRef > parse(std::string_view text)
Parse an asset://<canonical UUID> reference.
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
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
bool isInt64() const noexcept
Return true when this value is an Int64.
Definition Value.h:91
std::map< std::string, Value > Object
Definition Value.h:34
double asDouble() const
Return the Double payload; the caller must have checked the kind.
Definition Value.cpp:75
std::int64_t asInt() const
Return the Int64 payload; the caller must have checked the kind.
Definition Value.cpp:69
std::vector< Value > Array
Definition Value.h:33
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
bool isDouble() const noexcept
Return true when this value is a Double.
Definition Value.h:93
Result< RuntimeAssetPayload > read(const AssetRef &asset, std::string_view expectedType, const EvpackCapabilities &capabilities, std::uint64_t maximumDecodedBytes) const
Resolve, type-check, variant-select and decode a canonical runtime asset.
Result< LoadedTerrainMaterial > load(const AssetRef &material, const asset::EvpackCapabilities &capabilities, const TerrainMaterialLoadLimits &limits={}) const
Decode and validate canonical layer, normal and tiling semantics transactionally.
Result< Value > decodeRuntimeDefinition(std::span< const std::uint8_t > bytes, const RuntimeDefinitionLimits &limits)
Decode a bounded EVDEF\0\1 metadata tree without a JSON parser.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
const EditorValue * field(const EditorValue &value, const char *name)
@ TypeMismatch
A stable reference resolved to a different canonical domain type.
bool isValidUtf8(std::string_view text, Utf8NullPolicy nullPolicy) noexcept
Validate shortest-form UTF-8, Unicode scalar range and optional NUL exclusion.
Actual runtime device capabilities used for variant selection.
Definition Evpack.h:92
One decoded runtime chunk with its admitted semantic metadata.
Structural and allocation limits for untrusted EVDEF metadata.
Owning capability-selected terrain material candidate.
One owning canonical terrain layer independent of its source engine.
Admission budgets for terrain material definitions.