载入中...
搜索中...
未找到
EvpackInstanceSetLoader.cpp
浏览该文件的文档.
2
5
6#include <algorithm>
7#include <bit>
8#include <cmath>
9#include <set>
10
12namespace {
13
14std::uint32_t little32(std::span<const std::uint8_t> bytes, std::size_t offset) {
15 return std::uint32_t(bytes[offset]) | (std::uint32_t(bytes[offset + 1]) << 8) |
16 (std::uint32_t(bytes[offset + 2]) << 16) | (std::uint32_t(bytes[offset + 3]) << 24);
17}
18
19float littleFloat(std::span<const std::uint8_t> bytes, std::size_t offset) {
20 return std::bit_cast<float>(little32(bytes, offset));
21}
22
23const Value* field(const Value::Object& object, std::string_view name) {
24 const auto found = object.find(std::string(name));
25 return found == object.end() ? nullptr : &found->second;
26}
27
28} // namespace
29
31 const asset::EvpackCapabilities& capabilities,
32 const InstanceSetLoadLimits& limits) const {
33 auto payload = reader_.read(instanceRef, "eve.instance-set/5", capabilities, limits.maximumDecodedBytes);
34 if (!payload && (payload.error()->code() == DiagnosticCode::NotFound ||
35 payload.error()->code() == DiagnosticCode::TypeMismatch))
36 payload = reader_.read(instanceRef, "eve.instance-set/4", capabilities, limits.maximumDecodedBytes);
37 if (!payload && (payload.error()->code() == DiagnosticCode::NotFound ||
38 payload.error()->code() == DiagnosticCode::TypeMismatch))
39 payload = reader_.read(instanceRef, "eve.instance-set/3", capabilities, limits.maximumDecodedBytes);
40 if (!payload && (payload.error()->code() == DiagnosticCode::NotFound ||
41 payload.error()->code() == DiagnosticCode::TypeMismatch))
42 payload = reader_.read(instanceRef, "eve.instance-set/2", capabilities, limits.maximumDecodedBytes);
43 if (!payload && (payload.error()->code() == DiagnosticCode::NotFound ||
44 payload.error()->code() == DiagnosticCode::TypeMismatch))
45 payload = reader_.read(instanceRef, "eve.instance-set/1", capabilities, limits.maximumDecodedBytes);
47 const asset::RuntimeAssetChunk* definition = nullptr;
48 const asset::RuntimeAssetChunk* bulk = nullptr;
49 for (const auto& chunk : payload.value().chunks) {
50 if (chunk.kind == asset::EvpackChunkKind::Definition) {
51 if (definition)
53 "instance set has duplicate definitions",
54 {}, {}, "asset.procgen.instances"));
55 definition = &chunk;
56 } else if (chunk.kind == asset::EvpackChunkKind::Bulk) {
57 if (bulk)
59 "instance set has duplicate bulk chunks",
60 {}, {}, "asset.procgen.instances"));
61 bulk = &chunk;
62 }
63 }
64 static constexpr std::uint8_t magic[] = {'E', 'V', 'I', 'N', 'S', 'T', 0, 1};
65 if (!definition || !bulk || bulk->bytes.size() < 16 ||
66 !std::equal(std::begin(magic), std::end(magic), bulk->bytes.begin()))
68 "instance definition or EVINST payload is invalid",
69 {}, {}, "asset.procgen.instances"));
70 const std::uint32_t count = little32(bulk->bytes, 8);
71 const std::uint32_t reserved = little32(bulk->bytes, 12);
72 constexpr std::size_t minimumRecordBytes = 4 + 1 + (3 + 4 + 3) * sizeof(float);
73 if (count > limits.maximumInstances || reserved != 0 || count > (bulk->bytes.size() - 16) / minimumRecordBytes)
75 "instance count or reserved header is invalid", {},
76 {}, "asset.procgen.instances"));
77 asset::RuntimeDefinitionLimits definitionLimits;
78 definitionLimits.maximumBytes = limits.maximumDecodedBytes;
79 auto metadata = asset::decodeRuntimeDefinition(definition->bytes, definitionLimits);
80 if (!metadata) return Result<LoadedInstanceSet>::failure(metadata.status());
81 const auto* root = metadata.value().getIf<Value::Object>();
82 const Value* schema = root ? field(*root, "schema") : nullptr;
83 const Value* version = root ? field(*root, "schemaVersion") : nullptr;
84 const Value* declaredCount = root ? field(*root, "count") : nullptr;
85 const Value* partition = root ? field(*root, "partition") : nullptr;
86 if (!schema || !schema->isString() || schema->asString() != "eve.instance-set" || !version || !version->isInt64() ||
87 (version->asInt() != 1 && version->asInt() != 2 && version->asInt() != 3 && version->asInt() != 4 &&
88 version->asInt() != 5) ||
89 !declaredCount ||
90 !declaredCount->isInt64() ||
91 declaredCount->asInt() != count || !partition || !partition->isString() ||
92 partition->asString() != "single-cell")
94 "instance metadata does not match EVINST payload",
95 {}, {}, "asset.procgen.instances"));
96 LoadedInstanceSet result{instanceRef};
97 if (version->asInt() == 5) {
98 const Value* windValue = field(*root, "wavingGrass");
99 const auto* wind = windValue ? windValue->getIf<Value::Object>() : nullptr;
100 auto number = [&](std::string_view name) -> std::optional<float> {
101 const Value* value = wind ? field(*wind, name) : nullptr;
102 if (!value || !value->isNumeric()) return std::nullopt;
103 const double parsed = value->isInt64() ? double(value->asInt()) : value->asDouble();
104 if (!std::isfinite(parsed) || parsed < 0 || parsed > std::numeric_limits<float>::max()) return std::nullopt;
105 return static_cast<float>(parsed);
106 };
107 const auto amount = number("amount"), speed = number("speed"), strength = number("strength");
108 const Value* tintValue = wind ? field(*wind, "tint") : nullptr;
109 const auto* tint = tintValue ? tintValue->getIf<Value::Array>() : nullptr;
110 if (!amount || !speed || !strength || !tint || tint->size() != 4)
112 "instance waving grass settings are invalid",
113 {}, {}, "asset.procgen.instances"));
114 for (std::size_t component = 0; component < 4; ++component) {
115 if (!(*tint)[component].isNumeric())
117 "instance waving grass tint is invalid", {},
118 {}, "asset.procgen.instances"));
119 const double parsed = (*tint)[component].isInt64() ? double((*tint)[component].asInt())
120 : (*tint)[component].asDouble();
121 if (!std::isfinite(parsed) || parsed < 0 || parsed > 1)
123 "instance waving grass tint is invalid", {},
124 {}, "asset.procgen.instances"));
125 result.wavingGrassTint[component] = static_cast<float>(parsed);
126 }
127 result.wavingGrassAmount = *amount;
128 result.wavingGrassSpeed = *speed;
129 result.wavingGrassStrength = *strength;
130 }
131 std::vector<RuntimeInstancePrototype> prototypes;
132 std::set<std::string> prototypeIds;
133 if (version->asInt() >= 2) {
134 const Value* prototypeValue = field(*root, "prototypes");
135 const auto* prototypeArray = prototypeValue ? prototypeValue->getIf<Value::Array>() : nullptr;
136 if (!prototypeArray || prototypeArray->size() > limits.maximumInstances)
138 DiagnosticCode::ParseError, "instance prototype table is invalid", {}, {}, "asset.procgen.instances"));
139 prototypes.reserve(prototypeArray->size());
140 auto number = [&](const Value::Object& object, std::string_view name) -> std::optional<float> {
141 const Value* value = field(object, name);
142 if (!value || !value->isNumeric()) return std::nullopt;
143 const double parsed = value->isInt64() ? double(value->asInt()) : value->asDouble();
144 if (!std::isfinite(parsed) || parsed < -std::numeric_limits<float>::max() ||
145 parsed > std::numeric_limits<float>::max())
146 return std::nullopt;
147 return static_cast<float>(parsed);
148 };
149 for (std::size_t index = 0; index < prototypeArray->size(); ++index) {
150 const auto* object = (*prototypeArray)[index].getIf<Value::Object>();
151 const Value* id = object ? field(*object, "prototype") : nullptr;
152 const Value* mode = object ? field(*object, "renderMode") : nullptr;
153 const Value* mesh = object ? field(*object, "usePrototypeMesh") : nullptr;
154 const Value* instancing = object ? field(*object, "useInstancing") : nullptr;
155 const Value* seed = object ? field(*object, "noiseSeed") : nullptr;
156 const Value* resource = object ? field(*object, "resourceAsset") : nullptr;
157 if (!object || !id || !id->isString() || id->asString().empty() ||
158 id->asString().size() > limits.maximumStringBytes ||
159 !isValidUtf8(id->asString(), Utf8NullPolicy::Reject) || !prototypeIds.emplace(id->asString()).second ||
160 !mode || !mode->isString() ||
161 (mode->asString() != "GrassBillboard" && mode->asString() != "Grass" &&
162 mode->asString() != "VertexLit") ||
163 !mesh || !mesh->isBool() || !instancing || !instancing->isBool() || !seed || !seed->isInt64())
165 Diagnostic::error(DiagnosticCode::ParseError, "instance prototype metadata is invalid",
166 std::to_string(index), {}, "asset.procgen.instances"));
167 std::string resourceAsset;
168 if (version->asInt() >= 3) {
169 if (!resource || !resource->isString() || resource->asString().size() > limits.maximumStringBytes ||
170 (!resource->asString().empty() && !AssetRef::parse(resource->asString())))
172 Diagnostic::error(DiagnosticCode::ParseError, "instance prototype resource asset is invalid",
173 std::to_string(index), {}, "asset.procgen.instances"));
174 resourceAsset = resource->asString();
175 }
176 const auto minWidth = number(*object, "minWidth");
177 const auto maxWidth = number(*object, "maxWidth");
178 const auto minHeight = number(*object, "minHeight");
179 const auto maxHeight = number(*object, "maxHeight");
180 const auto noiseSpread = number(*object, "noiseSpread");
181 const auto density = number(*object, "density");
182 const auto align = number(*object, "alignToGround");
183 const auto jitter = number(*object, "positionJitter");
184 if (!minWidth || !maxWidth || !minHeight || !maxHeight || !noiseSpread || !density || !align || !jitter ||
185 *minWidth <= 0 || *maxWidth < *minWidth || *minHeight <= 0 || *maxHeight < *minHeight ||
186 *noiseSpread < 0 || *density < 0 || *align < 0 || *align > 1 || *jitter < 0 || *jitter > 1 ||
187 (mesh->asBool() != id->asString().starts_with("unity-guid:")))
189 Diagnostic::error(DiagnosticCode::InvalidArgument, "instance prototype values are invalid",
190 id->asString(), {}, "asset.procgen.instances"));
191 RuntimeInstancePrototype prototype{id->asString(), mode->asString(), mesh->asBool(), instancing->asBool(),
192 *minWidth, *maxWidth, *minHeight, *maxHeight, seed->asInt(),
193 *noiseSpread, *density, *align, *jitter, std::move(resourceAsset)};
194 if (version->asInt() >= 4) {
195 auto color = [&](std::string_view name, std::array<float, 4>& output) {
196 const Value* value = field(*object, name);
197 const auto* array = value ? value->getIf<Value::Array>() : nullptr;
198 if (!array || array->size() != 4) return false;
199 for (std::size_t component = 0; component < 4; ++component) {
200 if (!(*array)[component].isNumeric()) return false;
201 const double parsed = (*array)[component].isInt64() ? double((*array)[component].asInt())
202 : (*array)[component].asDouble();
203 if (!std::isfinite(parsed) || parsed < 0 || parsed > 1) return false;
204 output[component] = static_cast<float>(parsed);
205 }
206 return true;
207 };
208 const auto bend = number(*object, "bendFactor");
209 const auto padding = number(*object, "holeEdgePadding");
210 const Value* densityScaling = field(*object, "useDensityScaling");
211 if (!color("healthyColor", prototype.healthyColor) || !color("dryColor", prototype.dryColor) ||
212 !bend || *bend < 0 || *bend > 1 || !padding || *padding < 0 || *padding > 1 ||
213 !densityScaling || !densityScaling->isBool())
215 DiagnosticCode::InvalidArgument, "instance prototype extended values are invalid",
216 id->asString(), {}, "asset.procgen.instances"));
217 prototype.bendFactor = *bend;
218 prototype.holeEdgePadding = *padding;
219 prototype.useDensityScaling = densityScaling->asBool();
220 }
221 prototypes.push_back(std::move(prototype));
222 }
223 }
224 std::vector<RuntimeInstance> instances;
225 instances.reserve(count);
226 std::size_t cursor = 16;
227 for (std::uint32_t index = 0; index < count; ++index) {
228 if (bulk->bytes.size() - cursor < 4)
230 "instance prototype length is truncated", {},
231 {}, "asset.procgen.instances"));
232 const std::uint32_t stringBytes = little32(bulk->bytes, cursor);
233 cursor += 4;
234 constexpr std::size_t trsBytes = (3 + 4 + 3) * sizeof(float);
235 if (stringBytes == 0 || stringBytes > limits.maximumStringBytes || stringBytes > bulk->bytes.size() - cursor ||
236 bulk->bytes.size() - cursor - stringBytes < trsBytes)
238 "instance record exceeds payload bounds", {},
239 {}, "asset.procgen.instances"));
240 RuntimeInstance instance;
241 instance.prototype.assign(reinterpret_cast<const char*>(bulk->bytes.data() + cursor), stringBytes);
244 Diagnostic::error(DiagnosticCode::ParseError, "instance prototype is not valid UTF-8",
245 std::to_string(index), {}, "asset.procgen.instances"));
246 // Unity tree instances and mesh-backed terrain Details both use unity-guid:. A GUID absent from the
247 // Detail prototype table is therefore a tree prototype; texture-backed Details have an unambiguous prefix.
248 if (version->asInt() >= 2 && instance.prototype.starts_with("unity-texture-guid:") &&
249 !prototypeIds.contains(instance.prototype))
251 DiagnosticCode::NotFound, "instance references an undeclared terrain detail prototype",
252 instance.prototype, {}, "asset.procgen.instances"));
254 for (float& value : instance.position) {
255 value = littleFloat(bulk->bytes, cursor);
256 cursor += 4;
257 }
258 for (float& value : instance.rotation) {
259 value = littleFloat(bulk->bytes, cursor);
260 cursor += 4;
261 }
262 for (float& value : instance.scale) {
263 value = littleFloat(bulk->bytes, cursor);
264 cursor += 4;
265 }
266 const bool finite =
267 std::all_of(instance.position.begin(), instance.position.end(),
268 [](float value) { return std::isfinite(value); }) &&
269 std::all_of(instance.rotation.begin(), instance.rotation.end(),
270 [](float value) { return std::isfinite(value); }) &&
271 std::all_of(instance.scale.begin(), instance.scale.end(), [](float value) { return std::isfinite(value); });
272 const float qLength =
273 std::sqrt(instance.rotation[0] * instance.rotation[0] + instance.rotation[1] * instance.rotation[1] +
274 instance.rotation[2] * instance.rotation[2] + instance.rotation[3] * instance.rotation[3]);
275 if (!finite || qLength < 0.999f || qLength > 1.001f || instance.scale[0] == 0.f || instance.scale[1] == 0.f ||
276 instance.scale[2] == 0.f)
278 Diagnostic::error(DiagnosticCode::InvalidArgument, "instance TRS is invalid", std::to_string(index), {},
279 "asset.procgen.instances"));
280 instances.push_back(std::move(instance));
281 }
282 if (cursor != bulk->bytes.size())
284 DiagnosticCode::ParseError, "instance payload has trailing bytes", {}, {}, "asset.procgen.instances"));
285 result.prototypes = std::move(prototypes);
286 result.instances = std::move(instances);
287 result.variant = std::move(payload).takeValue().variant;
288 return Result<LoadedInstanceSet>::success(std::move(result));
289}
290
291} // namespace eve::asset_procgen
double value
Value::Object payload
int root
Definition AnimSmr.cpp:119
std::string output
std::vector< BuildingInstanceSnapshot > instances
size_t stringBytes
Runtime decoding of canonical static instances.
glm::vec4 tint
size_t offset
std::uint64_t bytes
std::string name
bool finite
std::string id
Definition PlayHost.cpp:108
std::uint32_t seed
Definition PointSet.cpp:807
Mesh * mesh
double number
bool found
JSON-free versioned runtime metadata codec.
std::string resource
std::uint32_t count
AbilityInstancingPolicy instancing
std::size_t cursor
Json object
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
const std::string & asString() const
Return the string payload; the caller must have checked the kind.
Definition Value.cpp:87
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
bool isString() const noexcept
Return true when this value is a string.
Definition Value.h:95
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
bool asBool() const
Return the boolean payload; the caller must have checked the kind.
Definition Value.cpp:81
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
bool isBool() const noexcept
Return true when this value is a boolean.
Definition Value.h:89
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< LoadedInstanceSet > load(const AssetRef &instances, const asset::EvpackCapabilities &capabilities, const InstanceSetLoadLimits &limits={}) const
Validate metadata, exact byte layout and every TRS record 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.
@ 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.
std::vector< std::uint8_t > bytes
Structural and allocation limits for untrusted EVDEF metadata.
Bounds applied before allocating untrusted instance records and strings.
Owning exact static-instance set selected for one runtime variant.
Runtime vegetation prototype metadata retained from the source terrain.
One exact canonical static instance using meters and an XYZW quaternion.
glm::vec4 color