载入中...
搜索中...
未找到
VegetationConversionImport.cpp
浏览该文件的文档.
2
3#include "asset/SourcePng.h"
7
8#include <algorithm>
9#include <cmath>
10#include <new>
11#include <set>
12
13namespace eve::asset_import {
14namespace {
15Result<std::string> stableGuid(std::string_view seed) {
16 const auto bytes = std::span<const std::uint8_t>(reinterpret_cast<const std::uint8_t*>(seed.data()), seed.size());
17 const auto hash = detail::sha256(bytes);
18 if (hash.size() < 39)
20 "stable vegetation texture hash is unavailable", {}, {},
21 "asset.import.vegetation-preset.publication"));
22 return Result<std::string>::success(hash.substr(7, 32));
23}
24
25std::vector<std::uint8_t> bytes(std::string value) { return {value.begin(), value.end()}; }
26
27Result<std::string> texturePathForGuid(const UnityVegetationAssetImportRequest& request, std::string_view guid) {
28 std::string found;
29 for (const auto& [path, content] : request.sourceFiles) {
30 if (!path.ends_with(".meta")) continue;
31 auto parsed = unity_detail::metaScalar(content, "guid", path);
32 if (!parsed) return Result<std::string>::failure(parsed.status());
33 if (unity_detail::foldAscii(parsed.value()) != guid) continue;
34 const auto source = path.substr(0, path.size() - 5);
35 if (!request.sourceFiles.contains(source))
37 Diagnostic::error(DiagnosticCode::NotFound, "vegetation texture metadata has no source payload", source,
38 {}, "asset.import.vegetation-preset.publication"));
39 if (!found.empty())
41 Diagnostic::error(DiagnosticCode::Conflict, "duplicate vegetation texture GUID", std::string(guid), {},
42 "asset.import.vegetation-preset.publication"));
43 found = source;
44 }
45 if (found.empty())
47 Diagnostic::error(DiagnosticCode::NotFound, "referenced vegetation texture source is absent",
48 std::string(guid), {}, "asset.import.vegetation-preset.publication"));
49 return Result<std::string>::success(std::move(found));
50}
51
52Result<void> appendMesh(PreparedAssetImport& prepared, const UnityVegetationAssetImportRequest& request,
53 const VegetationConversionResult& conversion) {
54 if (!conversion.mesh) return Result<void>::success();
55 if (prepared.manifest.assets.size() >= request.limits.maximumAssets)
57 "vegetation prepared asset count exceeds budget", {}, {},
58 "asset.import.vegetation-preset.publication"));
60 *conversion.mesh, {request.limits.maximumVerticesPerPrimitive, request.limits.maximumIndicesPerPrimitive,
61 request.limits.maximumDecodedBytes});
62 if (!blob) return Result<void>::failure(blob.status());
63 const auto key = request.outputKey.empty() ? unity_detail::foldAscii(request.materialGuid) : request.outputKey;
64 const auto id = request.package.packageId.child("unity-converted:" + key + ":mesh");
65 auto ref = detail::assetRef(id);
66 if (!ref) return Result<void>::failure(ref.status());
67 std::array<float, 3> minimum{conversion.mesh->positions[0], conversion.mesh->positions[1],
68 conversion.mesh->positions[2]};
69 auto maximum = minimum;
70 for (std::size_t offset = 3; offset < conversion.mesh->positions.size(); offset += 3)
71 for (std::size_t axis = 0; axis < 3; ++axis) {
72 minimum[axis] = std::min(minimum[axis], conversion.mesh->positions[offset + axis]);
73 maximum[axis] = std::max(maximum[axis], conversion.mesh->positions[offset + axis]);
74 }
75 if (conversion.meshBoundsMinimum) minimum = *conversion.meshBoundsMinimum;
76 if (conversion.meshBoundsMaximum) maximum = *conversion.meshBoundsMaximum;
77 Value::Array uvSets;
78 for (const auto& [set, values] : conversion.mesh->texcoords) uvSets.emplace_back(std::int64_t(set));
79 Value::Array attributes;
80 for (const auto& [name, attribute] : conversion.mesh->attributes)
81 attributes.emplace_back(Value::Object{{"name", name}, {"components", std::int64_t(attribute.components)}});
82 const std::string base = "assets/" + id.format() + "/";
83 const std::string definitionPath = base + "asset.json";
84 const std::string blobPath = base + "mesh.bin";
85 auto definition = Value(Value::Object{
86 {"schema", "eve.mesh"},
87 {"schemaVersion", std::int64_t(2)},
88 {"topology", "triangles"},
89 {"vertexCount", std::int64_t(conversion.mesh->positions.size() / 3)},
90 {"indexCount", std::int64_t(conversion.mesh->indices.size())},
91 {"positions", true},
92 {"normals", !conversion.mesh->normals.empty()},
93 {"texcoordSets", std::move(uvSets)},
94 {"attributes", std::move(attributes)},
95 {"coordinateSystem", "right-handed-x-right-y-up-minus-z-forward"},
96 {"unit", "meter"},
97 {"frontFace", "counter-clockwise"},
98 {"boundsMin", Value::Array{minimum[0], minimum[1], minimum[2]}},
99 {"boundsMax", Value::Array{maximum[0], maximum[1], maximum[2]}},
100 {"cpuReadable", conversion.meshCpuReadable},
101 {"blob", blobPath},
102 })
103 .toJson();
104 if (!definition) return Result<void>::failure(definition.status());
105 auto encoded = bytes(std::move(definition).takeValue());
106 prepared.manifest.assets.push_back({ref.value(),
107 "eve.mesh",
108 SchemaVersion(2),
109 definitionPath,
110 detail::sha256(encoded),
111 {"mesh", "source:unity", "vegetation"}});
112 prepared.entries.push_back({definitionPath, std::move(encoded)});
113 prepared.entries.push_back({blobPath, std::move(blob).takeValue()});
114 prepared.manifest.entrypoints.insert_or_assign("converted-mesh", ref.value());
115 prepared.sourceMappings.push_back({"converted-mesh", ref.value()});
116 prepared.findings.push_back({request.conversion.sourcePath, "TVE.Mesh", ImportDisposition::Baked,
117 "preset vertex streams and bounds encoded as canonical EVMESH"});
118 return Result<void>::success();
119}
120} // namespace
121
123 try {
124 if (!unity_detail::validGuid(request.materialGuid))
125 return Result<PreparedAssetImport>::failure(Diagnostic::error(
126 DiagnosticCode::InvalidArgument, "vegetation material GUID must contain 32 hexadecimal digits", {}, {},
127 "asset.import.vegetation-preset.publication"));
128 const auto materialGuid = unity_detail::foldAscii(request.materialGuid);
129 const auto outputKey = request.outputKey.empty() ? materialGuid : request.outputKey;
130 auto conversion = executeUnityVegetationConversion(request.conversion);
131 if (!conversion) return Result<PreparedAssetImport>::failure(conversion.status());
132
134 project.package = request.package;
135 project.limits = request.limits;
136 auto candidate = conversion.value().candidate;
137 std::set<std::string> generatedGuids;
138 for (const auto& [property, image] : conversion.value().textures) {
139 auto imageGuid = stableGuid(outputKey + ":" + property);
140 if (!imageGuid) return Result<PreparedAssetImport>::failure(imageGuid.status());
141 auto png = asset::detail::encodeSourcePng(image.width, image.height, image.pixels,
142 request.limits.maximumDecodedBytes);
143 if (!png) return Result<PreparedAssetImport>::failure(png.status());
144 const auto path = "Assets/Generated/TVE/" + imageGuid.value() + ".png";
145 project.files[path] = std::move(png).takeValue();
146 project.files[path + ".meta"] = bytes("fileFormatVersion: 2\nguid: " + imageGuid.value() +
147 "\nTextureImporter:\n sRGBTexture: 0\n wrapU: 0\n wrapV: 0\n"
148 " filterMode: 1\n enableMipMap: 1\n");
149 candidate.materialTextures[property] = imageGuid.value();
150 generatedGuids.insert(imageGuid.value());
151 }
152 for (const auto& [property, textureGuid] : candidate.materialTextures) {
153 if (generatedGuids.contains(textureGuid)) continue;
154 auto path = texturePathForGuid(request, textureGuid);
155 if (!path) return Result<PreparedAssetImport>::failure(path.status());
156 project.files[path.value()] = request.sourceFiles.at(path.value());
157 project.files[path.value() + ".meta"] = request.sourceFiles.at(path.value() + ".meta");
158 }
159 if (conversion.value().materialOutput != VegetationMaterialOutputMode::Off) {
160 auto material = encodeUnityVegetationConversionMaterial(candidate, request.limits.maximumDecodedBytes);
162 project.files[request.conversion.sourcePath] = std::move(material).takeValue();
163 project.files[request.conversion.sourcePath + ".meta"] =
164 bytes("fileFormatVersion: 2\nguid: " + materialGuid +
165 "\nNativeFormatImporter:\n mainObjectFileID: 2100000\n");
166 }
168 conversion.value().materialOutput == VegetationMaterialOutputMode::Off ? [&]() {
169 auto manifest = detail::baseManifest(request.package, "eve.unity-vegetation-conversion/1");
170 if (!manifest) return Result<PreparedAssetImport>::failure(manifest.status());
172 value.manifest = std::move(manifest).takeValue();
174 }()
175 : prepareUnityProjectImport(project);
176 if (!prepared) return prepared;
177 std::erase_if(prepared.value().entries,
178 [](const asset::EvaArchiveEntry& entry) { return entry.path == "reports/import.json"; });
179 auto mesh = appendMesh(prepared.value(), request, conversion.value());
180 if (!mesh) return Result<PreparedAssetImport>::failure(mesh.status());
181 auto report = detail::finalizeImportReport(prepared.value(), request.package, "unity", "tve-12.6-conversion",
182 {{"preset", request.conversion.rootPreset}});
183 if (!report) return Result<PreparedAssetImport>::failure(report.status());
184 std::uint64_t totalBytes = 0;
185 for (const auto& entry : prepared.value().entries) {
186 if (entry.bytes.size() > request.limits.maximumDecodedBytes ||
187 totalBytes > request.limits.maximumDecodedBytes - entry.bytes.size())
188 return Result<PreparedAssetImport>::failure(Diagnostic::error(
189 DiagnosticCode::InvalidArgument, "vegetation prepared output exceeds aggregate byte budget", {}, {},
190 "asset.import.vegetation-preset.publication"));
191 totalBytes += entry.bytes.size();
192 }
193 return prepared;
194 } catch (const std::bad_alloc&) {
196 Diagnostic::error(DiagnosticCode::Failed, "vegetation publication allocation failed", {}, {},
197 "asset.import.vegetation-preset.publication"));
198 }
199}
200
202 try {
203 if (request.objects.empty() || request.objects.size() > request.limits.maximumAssets)
204 return Result<PreparedAssetImport>::failure(Diagnostic::error(
205 DiagnosticCode::InvalidArgument, "vegetation batch requires a bounded non-empty object set", {}, {},
206 "asset.import.vegetation-preset.batch"));
207 auto manifest = detail::baseManifest(request.package, "eve.unity-vegetation-conversion-batch/1");
208 if (!manifest) return Result<PreparedAssetImport>::failure(manifest.status());
210 batch.manifest = std::move(manifest).takeValue();
211 std::set<std::string> outputKeys;
212 std::map<std::string, std::vector<std::uint8_t>> uniqueEntries;
213 std::set<std::string> assetIds;
214 for (const auto& object : request.objects) {
215 if (object.outputKey.empty() || !outputKeys.insert(object.outputKey).second)
216 return Result<PreparedAssetImport>::failure(Diagnostic::error(
217 DiagnosticCode::Conflict, "vegetation batch output keys must be non-empty and unique",
218 object.outputKey, {}, "asset.import.vegetation-preset.batch"));
219 auto itemRequest = object;
220 itemRequest.package = request.package;
221 itemRequest.limits = request.limits;
222 auto item = prepareUnityVegetationConversionImport(itemRequest);
223 if (!item) return Result<PreparedAssetImport>::failure(item.status());
224 for (auto& asset : item.value().manifest.assets) {
225 if (!assetIds.insert(asset.asset.format()).second)
227 Diagnostic::error(DiagnosticCode::Conflict, "vegetation batch asset identity collision",
228 asset.asset.format(), {}, "asset.import.vegetation-preset.batch"));
229 batch.manifest.assets.push_back(std::move(asset));
230 }
231 for (auto& dependency : item.value().manifest.dependencies)
232 batch.manifest.dependencies.push_back(std::move(dependency));
233 for (auto& entry : item.value().entries) {
234 if (entry.path == "reports/import.json") continue;
235 auto [found, inserted] = uniqueEntries.emplace(entry.path, entry.bytes);
236 if (!inserted && found->second != entry.bytes)
238 Diagnostic::error(DiagnosticCode::Conflict, "vegetation batch entry collision", entry.path, {},
239 "asset.import.vegetation-preset.batch"));
240 }
241 for (auto& mapping : item.value().sourceMappings) {
242 mapping.sourceObject = object.outputKey + "/" + mapping.sourceObject;
243 batch.sourceMappings.push_back(std::move(mapping));
244 }
245 for (auto& finding : item.value().findings) batch.findings.push_back(std::move(finding));
246 for (const auto& [name, ref] : item.value().manifest.entrypoints) {
247 const auto key =
248 object.outputKey + (name == "default" || name == object.conversion.sourcePath ? "" : "#" + name);
249 if (!batch.manifest.entrypoints.emplace(key, ref).second)
251 Diagnostic::error(DiagnosticCode::Conflict, "vegetation batch entrypoint collision", key, {},
252 "asset.import.vegetation-preset.batch"));
253 }
254 if (batch.manifest.assets.size() > request.limits.maximumAssets)
256 Diagnostic::error(DiagnosticCode::InvalidArgument, "vegetation batch asset count exceeds budget",
257 {}, {}, "asset.import.vegetation-preset.batch"));
258 }
259 for (auto& [path, content] : uniqueEntries) batch.entries.push_back({std::move(path), std::move(content)});
260 auto report = detail::finalizeImportReport(batch, request.package, "unity", "tve-12.6-conversion-batch",
261 {{"objectCount", std::int64_t(request.objects.size())}});
262 if (!report) return Result<PreparedAssetImport>::failure(report.status());
263 std::uint64_t totalBytes = 0;
264 for (const auto& entry : batch.entries) {
265 if (entry.bytes.size() > request.limits.maximumDecodedBytes ||
266 totalBytes > request.limits.maximumDecodedBytes - entry.bytes.size())
268 Diagnostic::error(DiagnosticCode::InvalidArgument, "vegetation batch exceeds aggregate byte budget",
269 {}, {}, "asset.import.vegetation-preset.batch"));
270 totalBytes += entry.bytes.size();
271 }
272 return Result<PreparedAssetImport>::success(std::move(batch));
273 } catch (const std::bad_alloc&) {
274 return Result<PreparedAssetImport>::failure(Diagnostic::error(DiagnosticCode::Failed,
275 "vegetation batch allocation failed", {}, {},
276 "asset.import.vegetation-preset.batch"));
277 }
278}
279} // namespace eve::asset_import
double value
std::unordered_map< std::string, QuestRuntime > entries
std::map< std::string, Var > values
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
std::vector< std::uint8_t > blob
float maximum[3]
float minimum[3]
const GltfImportRequest & request
std::uint32_t key
vk::UniqueImage image
std::vector< float > texcoords
size_t offset
std::uint64_t bytes
std::string name
std::string path
Definition PlayHost.cpp:110
std::uint32_t seed
Definition PointSet.cpp:807
Mesh * mesh
Material * material
bool found
Json object
Direct Unity text-serialization adapter for TerrainData and Prefab assets.
const UnitySourceAsset & source
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
const T & value() const &
Borrow the value from a const lvalue after checking success.
Definition Result.h:308
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
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.
std::string foldAscii(std::string value)
Fold ascii.
Result< std::string > metaScalar(std::span< const std::uint8_t > bytes, std::string_view key, std::string_view path)
Read an unindented scalar from .meta; rejects duplicate keys and binary metadata.
Result< PreparedAssetImport > prepareUnityVegetationConversionImport(const UnityVegetationAssetImportRequest &request)
Convert and prepare one material, its packed images, and optional mesh as a single EVA candidate.
EVENGINE_API_PLATFORM Result< std::vector< std::uint8_t > > encodeUnityVegetationConversionMaterial(const VegetationConversionCandidate &candidate, std::uint64_t maximumBytes=16ull *1024ull *1024ull)
Encode an applied vegetation candidate as a detached Unity text Material for the canonical TVE import...
Result< PreparedAssetImport > prepareUnityVegetationConversionBatch(const UnityVegetationBatchImportRequest &request)
Prepare several vegetation objects as one all-or-nothing EVA candidate.
EVENGINE_API_PLATFORM Result< VegetationConversionResult > executeUnityVegetationConversion(const UnityVegetationConversionRequest &request)
Decode a Unity Material, evaluate one TVE preset graph, and execute every output atomically.
Result< PreparedAssetImport > prepareUnityProjectImport(const UnityProjectImportRequest &request)
Import selected Unity assets, or discover a collection when both selectors are empty.
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.
One owning regular-file entry inside an .eva archive.
Definition EvaArchive.h:31
std::vector< EvaAssetEntry > assets
Definition EvaManifest.h:69
std::vector< EvaDependency > dependencies
Definition EvaManifest.h:70
std::map< std::string, AssetRef > entrypoints
Definition EvaManifest.h:71
Owning, validated source archive candidate not yet published to disk/database.
std::vector< ImportFinding > findings
std::vector< asset::EvaArchiveEntry > entries
std::vector< ImportSourceMapping > sourceMappings
In-memory Unity project slice; keys are normalized project-relative paths.
Owning inputs for one atomically prepared Unity vegetation asset group.
Owning ordered set of independently converted vegetation objects published as one package.