载入中...
搜索中...
未找到
GltfImporter.cpp
浏览该文件的文档.
3
8
9#include <bit>
10#include <charconv>
11#include <cmath>
12#include <cstring>
13#include <limits>
14
15namespace eve::asset_import {
16namespace {
17
18using namespace gltf;
19
20struct PrimitiveOutput {
21 std::vector<std::uint8_t> blob;
22 std::uint32_t vertexCount = 0;
23 std::uint32_t indexCount = 0;
24 bool hasNormals = false;
25 std::vector<uint32_t> texcoordSets;
26 float minimum[3] = {};
27 float maximum[3] = {};
28};
29
30Result<float> readUv(const Accessor& uv, uint32_t vertex, uint32_t axis) {
31 if (uv.componentType == 5126) return readFloat(uv, vertex, axis);
32 const size_t offset = uv.offset + size_t(vertex) * uv.stride + axis * componentSize(uv.componentType);
33 const uint32_t value = uv.componentType == 5121
34 ? uv.buffer[offset]
35 : uint32_t(uv.buffer[offset]) | (uint32_t(uv.buffer[offset + 1]) << 8);
36 return Result<float>::success(float(value) / (uv.componentType == 5121 ? 255.f : 65535.f));
37}
38
39Result<PrimitiveOutput> decodePrimitive(const Value::Object& root, const Value::Object& primitive,
40 const std::vector<std::span<const std::uint8_t>>& buffers,
41 const AssetImportLimits& limits) {
42 auto mode = unsignedValue(member(primitive, "mode"), "primitive.mode", false, 4);
43 if (!mode) return Result<PrimitiveOutput>::failure(mode.status());
44 if (mode.value() != 4)
46 DiagnosticCode::Unsupported, "only glTF TRIANGLES primitives are supported", {}, {}, "asset.import"));
47 const auto* attributesValue = member(primitive, "attributes");
48 const auto* attributes = attributesValue ? attributesValue->getIf<Value::Object>() : nullptr;
49 if (!attributes)
51 Diagnostic::error(DiagnosticCode::ParseError, "primitive attributes are required", {}, {}, "asset.import"));
52 auto positionIndex = unsignedValue(member(*attributes, "POSITION"), "primitive.attributes.POSITION");
53 if (!positionIndex) return Result<PrimitiveOutput>::failure(positionIndex.status());
54 auto positions = accessorAt(root, buffers, positionIndex.value(), limits);
56 if (positions.value().count == 0 || positions.value().components != 3 || positions.value().componentType != 5126 ||
59 DiagnosticCode::Unsupported, "POSITION must be a bounded FLOAT VEC3 accessor", {}, {}, "asset.import"));
60 std::optional<Accessor> normals;
61 if (const Value* normal = member(*attributes, "NORMAL")) {
62 auto normalIndex = unsignedValue(normal, "primitive.attributes.NORMAL");
63 if (!normalIndex) return Result<PrimitiveOutput>::failure(normalIndex.status());
64 auto decoded = accessorAt(root, buffers, normalIndex.value(), limits);
65 if (!decoded) return Result<PrimitiveOutput>::failure(decoded.status());
66 if (decoded.value().components != 3 || decoded.value().componentType != 5126 ||
67 decoded.value().count != positions.value().count)
69 Diagnostic::error(DiagnosticCode::Unsupported, "NORMAL must match POSITION", {}, {}, "asset.import"));
70 normals = std::move(decoded).takeValue();
71 }
72 std::map<uint32_t, Accessor> texcoords;
73 for (const auto& [name, texcoord] : *attributes) {
74 if (!name.starts_with("TEXCOORD_")) continue;
75 const std::string_view suffix(name.data() + 9, name.size() - 9);
76 uint32_t set = 0;
77 const auto parsed = std::from_chars(suffix.data(), suffix.data() + suffix.size(), set);
78 if (parsed.ec != std::errc{} || parsed.ptr != suffix.data() + suffix.size() || std::to_string(set) != suffix)
80 Diagnostic::error(DiagnosticCode::ParseError, "invalid UV set semantic", {}, {}, "asset.import"));
81 auto texcoordIndex = unsignedValue(&texcoord, "primitive.attributes." + name);
82 if (!texcoordIndex) return Result<PrimitiveOutput>::failure(texcoordIndex.status());
83 auto decoded = accessorAt(root, buffers, texcoordIndex.value(), limits);
84 if (!decoded) return Result<PrimitiveOutput>::failure(decoded.status());
85 const auto& uv = decoded.value();
86 if (uv.components != 2 || uv.count != positions.value().count ||
87 !((uv.componentType == 5126 && !uv.normalized) ||
88 ((uv.componentType == 5121 || uv.componentType == 5123) && uv.normalized)))
90 DiagnosticCode::Unsupported, "UV sets must match POSITION and use FLOAT or normalized unsigned VEC2",
91 {}, {}, "asset.import"));
92 texcoords.emplace(set, std::move(decoded).takeValue());
93 }
94 std::map<std::string, Accessor> extraAttributes;
95 uint64_t extraFloats = 0;
96 for (const auto& [name, value] : *attributes) {
97 if (name != "TANGENT" && !name.starts_with("COLOR_") && !name.starts_with("_")) continue;
98 if (name.starts_with("COLOR_")) {
99 const std::string_view suffix(name.data() + 6, name.size() - 6);
100 uint32_t set = 0;
101 const auto parsed = std::from_chars(suffix.data(), suffix.data() + suffix.size(), set);
102 if (parsed.ec != std::errc{} || parsed.ptr != suffix.data() + suffix.size() ||
103 std::to_string(set) != suffix)
105 DiagnosticCode::ParseError, "invalid color set semantic", {}, {}, "asset.import"));
106 }
107 auto index = unsignedValue(&value, "primitive.attributes." + name);
108 if (!index) return Result<PrimitiveOutput>::failure(index.status());
109 auto decoded = accessorAt(root, buffers, index.value(), limits);
110 if (!decoded) return Result<PrimitiveOutput>::failure(decoded.status());
111 const auto& a = decoded.value();
112 if (a.count != positions.value().count || a.components < 1 || a.components > 4 ||
113 (name == "TANGENT" && (a.components != 4 || a.componentType != 5126)) ||
114 (name.starts_with("COLOR_") && a.components != 3 && a.components != 4) ||
115 !((a.componentType == 5126 && !a.normalized) ||
116 ((a.componentType == 5121 || a.componentType == 5123) && a.normalized)))
118 DiagnosticCode::Unsupported, "unsupported vertex attribute", name, {}, "asset.import"));
119 extraFloats += a.components;
120 extraAttributes.emplace(name, std::move(decoded).takeValue());
121 }
122 std::vector<std::uint32_t> indices;
123 if (const Value* indicesValue = member(primitive, "indices")) {
124 auto index = unsignedValue(indicesValue, "primitive.indices");
125 if (!index) return Result<PrimitiveOutput>::failure(index.status());
126 auto accessor = accessorAt(root, buffers, index.value(), limits);
127 if (!accessor) return Result<PrimitiveOutput>::failure(accessor.status());
128 if (accessor.value().count == 0 || accessor.value().count > limits.maximumIndicesPerPrimitive ||
129 accessor.value().count % 3 != 0)
131 DiagnosticCode::InvalidArgument, "triangle index count is invalid", {}, {}, "asset.import"));
132 indices.reserve(accessor.value().count);
133 for (std::uint32_t item = 0; item < accessor.value().count; ++item) {
134 auto value = readIndex(accessor.value(), item);
135 if (!value) return Result<PrimitiveOutput>::failure(value.status());
136 if (value.value() >= positions.value().count)
138 DiagnosticCode::ParseError, "mesh index exceeds vertex count", {}, {}, "asset.import"));
139 indices.push_back(value.value());
140 }
141 } else {
142 if (positions.value().count % 3 != 0)
144 DiagnosticCode::ParseError, "unindexed triangle vertex count must be divisible by three", {}, {},
145 "asset.import"));
146 indices.resize(positions.value().count);
147 for (std::uint32_t index = 0; index < indices.size(); ++index) indices[index] = index;
148 }
149 const std::uint64_t decodedSize = (extraAttributes.empty() ? 24 : 28) + uint64_t(texcoords.size()) * 4 +
150 uint64_t(extraAttributes.size()) * 72 +
151 std::uint64_t(positions.value().count) *
152 (12 + (normals ? 12 : 0) + uint64_t(texcoords.size()) * 8 + extraFloats * 4) +
153 std::uint64_t(indices.size()) * 4;
154 if (decodedSize > limits.maximumDecodedBytes || decodedSize > std::numeric_limits<std::size_t>::max())
156 DiagnosticCode::InvalidArgument, "canonical mesh exceeds decoded budget", {}, {}, "asset.import"));
157 PrimitiveOutput output;
158 output.vertexCount = positions.value().count;
159 output.indexCount = static_cast<std::uint32_t>(indices.size());
160 output.hasNormals = normals.has_value();
161 for (const auto& [set, uv] : texcoords) output.texcoordSets.push_back(set);
162 asset::CanonicalMeshData mesh;
163 mesh.indices = std::move(indices);
164 for (const auto& [name, attribute] : extraAttributes) mesh.attributes[name].components = attribute.components;
165 for (std::uint32_t vertex = 0; vertex < output.vertexCount; ++vertex) {
166 for (std::uint32_t axis = 0; axis < 3; ++axis) {
167 auto value = readFloat(positions.value(), vertex, axis);
168 if (!value) return Result<PrimitiveOutput>::failure(value.status());
169 if (vertex == 0) output.minimum[axis] = output.maximum[axis] = value.value();
170 else { output.minimum[axis] = std::min(output.minimum[axis], value.value());
171 output.maximum[axis] = std::max(output.maximum[axis], value.value()); }
172 mesh.positions.push_back(value.value());
173 }
174 if (normals) for (std::uint32_t axis = 0; axis < 3; ++axis) {
175 auto value = readFloat(*normals, vertex, axis);
176 if (!value) return Result<PrimitiveOutput>::failure(value.status());
177 mesh.normals.push_back(value.value());
178 }
179 for (const auto& [set, uv] : texcoords)
180 for (std::uint32_t axis = 0; axis < 2; ++axis) {
181 auto value = readUv(uv, vertex, axis);
182 if (!value) return Result<PrimitiveOutput>::failure(value.status());
183 mesh.texcoords[set].push_back(value.value());
184 }
185 for (const auto& [name, attribute] : extraAttributes)
186 for (uint32_t axis = 0; axis < attribute.components; ++axis) {
187 auto value = readUv(attribute, vertex, axis);
188 if (!value) return Result<PrimitiveOutput>::failure(value.status());
189 mesh.attributes[name].values.push_back(value.value());
190 }
191 }
192 auto encoded = asset::encodeCanonicalMesh(
194 if (!encoded) return Result<PrimitiveOutput>::failure(encoded.status());
195 output.blob = std::move(encoded).takeValue();
196 return Result<PrimitiveOutput>::success(std::move(output));
197}
198
199Value::Array vector3(const float values[3]) {
200 return {Value(double(values[0])), Value(double(values[1])), Value(double(values[2]))};
201}
202
203} // namespace
204
208 Diagnostic::error(DiagnosticCode::InvalidArgument, "glTF source name is invalid", {}, {}, "asset.import"));
209 auto document = parseDocument(request);
210 if (!document) return Result<PreparedAssetImport>::failure(document.status());
211 const auto* root = document.value().root.getIf<Value::Object>();
212 if (!root)
214 Diagnostic::error(DiagnosticCode::ParseError, "glTF root must be an object", {}, {}, "asset.import"));
215 if (const auto* required = member(*root, "extensionsRequired")) {
216 const auto* extensions = required->getIf<Value::Array>();
217 if (!extensions)
219 DiagnosticCode::ParseError, "extensionsRequired must be an array", {}, {}, "asset.import"));
220 for (const auto& extension : *extensions)
221 if (!extension.isString() || !gltf::supportsMaterialExtension(extension.asString()))
223 Diagnostic::error(DiagnosticCode::Unsupported, "required glTF extension is not supported",
224 "extensionsRequired", {}, "asset.import"));
225 }
226 const auto* assetValue = member(*root, "asset");
227 const auto* asset = assetValue ? assetValue->getIf<Value::Object>() : nullptr;
228 const Value* version = asset ? member(*asset, "version") : nullptr;
229 if (!version || !version->isString() || !version->asString().starts_with("2."))
231 DiagnosticCode::UnknownVersion, "only glTF 2.x is supported", "$.asset.version", {}, "asset.import"));
232 auto buffers = resolveBuffers(*root, document.value(), request);
233 if (!buffers) return Result<PreparedAssetImport>::failure(buffers.status());
234 const Value* meshesValue = member(*root, "meshes");
235 const auto* meshes = meshesValue ? meshesValue->getIf<Value::Array>() : nullptr;
236 if (!meshes || meshes->empty())
238 Diagnostic::error(DiagnosticCode::ParseError, "glTF contains no meshes", "$.meshes", {}, "asset.import"));
239 auto manifestResult = detail::baseManifest(request.package, "eve.gltf2");
240 if (!manifestResult) return Result<PreparedAssetImport>::failure(manifestResult.status());
241 PreparedAssetImport result;
242 result.manifest = std::move(manifestResult).takeValue();
243 result.manifest.provenance["importerVersion"] = Value(std::int64_t(2));
244 std::uint32_t assetCount = 0;
245 std::uint64_t decodedTotal = 0;
246 for (std::size_t meshIndex = 0; meshIndex < meshes->size(); ++meshIndex) {
247 const auto* mesh = (*meshes)[meshIndex].getIf<Value::Object>();
248 const Value* primitivesValue = mesh ? member(*mesh, "primitives") : nullptr;
249 const auto* primitives = primitivesValue ? primitivesValue->getIf<Value::Array>() : nullptr;
250 if (!primitives || primitives->empty())
252 Diagnostic::error(DiagnosticCode::ParseError, "glTF mesh has no primitives", {}, {}, "asset.import"));
253 for (std::size_t primitiveIndex = 0; primitiveIndex < primitives->size(); ++primitiveIndex) {
254 if (++assetCount > request.limits.maximumAssets)
256 Diagnostic::error(DiagnosticCode::InvalidArgument, "glTF primitive asset count exceeds limits", {},
257 {}, "asset.import"));
258 const auto* primitive = (*primitives)[primitiveIndex].getIf<Value::Object>();
259 if (!primitive)
261 DiagnosticCode::ParseError, "glTF primitive is malformed", {}, {}, "asset.import"));
262 if (member(*primitive, "targets"))
264 DiagnosticCode::Unsupported, "morph targets cannot be represented by the canonical mesh",
265 "meshes.primitives.targets", {}, "asset.import"));
266 auto decoded = decodePrimitive(*root, *primitive, buffers.value(), request.limits);
267 if (!decoded) return Result<PreparedAssetImport>::failure(decoded.status());
268 if (decoded.value().blob.size() > request.limits.maximumDecodedBytes ||
269 decodedTotal > request.limits.maximumDecodedBytes - decoded.value().blob.size())
271 "total canonical mesh budget is exceeded",
272 {}, {}, "asset.import"));
273 decodedTotal += decoded.value().blob.size();
275 "gltf:mesh:" + std::to_string(meshIndex) + ":primitive:" + std::to_string(primitiveIndex));
276 auto reference = detail::assetRef(id);
278 const std::string base = "assets/" + id.format() + "/";
279 const std::string definitionPath = base + "asset.json";
280 const std::string blobPath = base + "mesh.bin";
281 Value::Object definition;
282 definition["schema"] = Value("eve.mesh");
283 definition["schemaVersion"] = Value(std::int64_t(2));
284 definition["topology"] = Value("triangles");
285 definition["vertexCount"] = Value(static_cast<std::int64_t>(decoded.value().vertexCount));
286 definition["indexCount"] = Value(static_cast<std::int64_t>(decoded.value().indexCount));
287 definition["positions"] = Value(true); definition["normals"] = Value(decoded.value().hasNormals);
288 Value::Array uvSets;
289 for (auto set : decoded.value().texcoordSets) uvSets.emplace_back(int64_t(set));
290 definition["texcoordSets"] = Value(std::move(uvSets));
291 definition["coordinateSystem"] = Value("right-handed-x-right-y-up-minus-z-forward");
292 definition["unit"] = Value("meter"); definition["frontFace"] = Value("counter-clockwise");
293 definition["boundsMin"] = Value(vector3(decoded.value().minimum));
294 definition["boundsMax"] = Value(vector3(decoded.value().maximum));
295 definition["blob"] = Value(blobPath);
296 auto encoded = Value(std::move(definition)).toJson();
297 if (!encoded) return Result<PreparedAssetImport>::failure(encoded.status());
298 std::string definitionText = std::move(encoded).takeValue();
299 result.manifest.assets.push_back(
300 {std::move(reference).takeValue(),
301 "eve.mesh",
302 SchemaVersion(2),
303 definitionPath,
304 detail::sha256(std::span<const std::uint8_t>(
305 reinterpret_cast<const std::uint8_t*>(definitionText.data()), definitionText.size())),
306 {"mesh", "source:gltf2"}});
307 if (result.manifest.entrypoints.empty()) {
308 auto entrypoint = detail::assetRef(id);
309 if (!entrypoint) return Result<PreparedAssetImport>::failure(entrypoint.status());
310 result.manifest.entrypoints.emplace("default", std::move(entrypoint).takeValue());
311 }
312 result.entries.push_back({definitionPath, {definitionText.begin(), definitionText.end()}});
313 result.entries.push_back({blobPath, std::move(decoded).takeValue().blob});
314 auto mapping = detail::assetRef(id);
315 if (!mapping) return Result<PreparedAssetImport>::failure(mapping.status());
316 const std::string sourceObject = "meshes[" + std::to_string(meshIndex) + "].primitives[" +
317 std::to_string(primitiveIndex) + "]";
318 result.sourceMappings.push_back({sourceObject, std::move(mapping).takeValue()});
319 result.findings.push_back({request.sourceName, sourceObject,
321 "triangle primitive converted to canonical EVMESH"});
322 }
323 }
324 auto animation = gltf::appendAnimation(request, *root, buffers.value(), result);
325 if (!animation) return Result<PreparedAssetImport>::failure(animation.status());
326 auto materials = gltf::appendMaterials(request, *root, buffers.value(), result);
327 if (!materials) return Result<PreparedAssetImport>::failure(materials.status());
328 if (const auto* used = member(*root, "extensionsUsed")) {
329 const auto* extensions = used->getIf<Value::Array>();
330 if (!extensions)
332 DiagnosticCode::ParseError, "extensionsUsed must be an array", {}, {}, "asset.import"));
333 for (const auto& extension : *extensions) {
334 if (!extension.isString())
336 DiagnosticCode::ParseError, "extension name must be a string", {}, {}, "asset.import"));
337 const bool translated = gltf::supportsMaterialExtension(extension.asString());
338 result.findings.push_back({request.sourceName, extension.asString(),
340 translated ? "material extension parameters and texture bindings translated"
341 : "unknown optional extension"});
342 }
343 }
344 for (std::size_t meshIndex = 0; meshIndex < meshes->size(); ++meshIndex) {
345 const auto& mesh = *(*meshes)[meshIndex].getIf<Value::Object>();
346 const auto& primitives = *member(mesh, "primitives")->getIf<Value::Array>();
347 for (const auto& value : primitives) {
348 const auto& primitive = *value.getIf<Value::Object>();
349 const auto& attributes = *member(primitive, "attributes")->getIf<Value::Object>();
350 for (const auto& [key, unused] : attributes) {
351 (void)unused;
352 if (key == "POSITION" || key == "NORMAL" || key.starts_with("TEXCOORD_") || key == "TANGENT" ||
353 key.starts_with("COLOR_") || key.starts_with("_"))
354 continue;
355 if (key.starts_with("JOINTS_") || key.starts_with("WEIGHTS_")) {
356 if (!member(*root, "skins"))
358 Diagnostic::error(DiagnosticCode::ParseError, "joint attributes require a skin binding",
359 key, {}, "asset.import"));
360 continue;
361 }
363 "vertex attribute is not represented by canonical EVMESH/2"});
364 }
365 }
366 }
367 result.manifest.provenance["sourceName"] = Value(request.sourceName);
369 result.manifest.provenance["sourceCoordinateSystem"] = Value("gltf2-right-handed-y-up");
370 auto report = detail::finalizeImportReport(result, request.package, "gltf", "2.x",
371 {{"strict", Value(request.mode == GltfImportMode::Strict)}});
372 if (!report) return Result<PreparedAssetImport>::failure(report.status());
373 return Result<PreparedAssetImport>::success(std::move(result));
374}
375
376} // namespace eve::asset_import
double value
int root
Definition AnimSmr.cpp:119
std::string output
Tool-side image and glTF importers producing canonical .eva candidates.
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
float uv
std::map< std::string, Var > values
std::uint64_t decodedSize
std::vector< std::uint8_t > blob
std::uint32_t vertexCount
float maximum[3]
float minimum[3]
bool hasNormals
std::vector< uint32_t > texcoordSets
std::uint32_t indexCount
const GltfImportRequest & request
std::uint32_t key
std::vector< std::uint32_t > indices
std::vector< float > texcoords
std::vector< float > normals
std::vector< float > positions
size_t offset
bool required
std::string name
MeleePoint3 a
Definition MeleeHit.cpp:40
Texture * normal
Mesh * mesh
std::uint32_t count
float size
Definition TreeMesh.cpp:156
float(ui::Theme::* member)[4]
uint32_t index
const AssetImportLimits & limits
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
Result< std::string > toJson() const
Serialize this value as deterministic compact JSON.
Definition Value.cpp:67
const std::string & asString() const
Return the string payload; the caller must have checked the kind.
Definition Value.cpp:87
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::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
Id128 public API.
Definition Identity.h:112
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.
EVENGINE_API_PLATFORM Result< void > finalizeImportReport(PreparedAssetImport &prepared, const ImportPackageIdentity &package, std::string_view sourceEngine, std::string_view sourceVersion, Value::Object options={})
Append the deterministic mandatory import report and bind it from provenance.
Result< asset::EvaManifest > baseManifest(const ImportPackageIdentity &package, std::string_view importer)
Base manifest.
Result< AssetRef > assetRef(PersistentId id)
Asset ref.
Result< Accessor > accessorAt(const Value::Object &root, const std::vector< std::span< const std::uint8_t > > &buffers, std::uint64_t accessorIndex, const AssetImportLimits &limits)
Validate an accessor retaining only buffer spans borrowed for this import.
bool supportsMaterialExtension(std::string_view name)
Whether this adapter translates the named standard material/texture extension.
Result< float > readFloat(const Accessor &accessor, std::uint32_t element, std::uint32_t component)
Read one bounded finite FLOAT component.
Result< std::vector< std::span< const std::uint8_t > > > resolveBuffers(const Value::Object &root, const Document &document, const GltfImportRequest &request)
Resolve immutable spans borrowed from document/request; both must outlive their use.
Result< std::uint64_t > unsignedValue(const Value *value, std::string path, bool required, std::uint64_t fallback)
Validate an optional integer field. @ownership Borrowed nullable field owned by the input document....
Result< Document > parseDocument(const GltfImportRequest &request)
Parse a bounded document into an owning result.
Result< std::uint32_t > readIndex(const Accessor &accessor, std::uint32_t element)
Read one bounded unsigned scalar index.
Result< void > appendMaterials(const GltfImportRequest &request, const Value::Object &root, const std::vector< std::span< const uint8_t > > &buffers, PreparedAssetImport &output)
Result< void > appendAnimation(const GltfImportRequest &request, const Value::Object &root, const std::vector< std::span< const std::uint8_t > > &buffers, PreparedAssetImport &output)
Append owning canonical skeleton, skin and clip assets to an unpublished candidate.
std::uint32_t componentSize(std::uint32_t type)
Return encoded component byte size, or zero for an unknown type.
EVENGINE_API_PLATFORM Result< PreparedAssetImport > prepareGltfImport(const GltfImportRequest &request)
Decode triangle primitives from glTF 2.0 into canonical typed mesh blobs.
EVENGINE_API_FOUNDATION Result< std::vector< std::uint8_t > > encodeCanonicalMesh(const CanonicalMeshData &mesh, const CanonicalMeshLimits &limits={})
Encode an owning snapshot to EVMESH v3, or v2 when no named attributes exist. Borrows input synchrono...
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
int axis(int64_t a, size_t rank)
Axis.
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
std::vector< EvaAssetEntry > assets
Definition EvaManifest.h:69
Value::Object provenance
Definition EvaManifest.h:72
std::map< std::string, AssetRef > entrypoints
Definition EvaManifest.h:71
Untrusted .gltf/.glb input plus explicitly supplied external URI bytes.
std::vector< std::uint8_t > documentBytes
Owning, validated source archive candidate not yet published to disk/database.
std::vector< ImportFinding > findings
std::vector< asset::EvaArchiveEntry > entries
std::vector< ImportSourceMapping > sourceMappings