载入中...
搜索中...
未找到
UnityRenderBindings.cpp
浏览该文件的文档.
4
5#include <cmath>
6#include <regex>
7#include <set>
8
9namespace eve::asset_import {
10namespace {
11std::optional<std::string> match(const std::string& text, const std::string& pattern, unsigned group = 1) {
12 std::smatch result;
13 if (!std::regex_search(text, result, std::regex(pattern))) return {};
14 return result[group].str();
15}
16Result<double> property(const std::string& text, const std::string& name, double defaultValue) {
17 const auto value = match(text, "(?:^|\\n)\\s*- " + name + ": *([^\\r\\n]+)");
19 auto parsed = Value::fromJson(*value);
20 if (!parsed || !parsed.value().isNumeric())
22 Diagnostic::error(DiagnosticCode::ParseError, "invalid Unity material scalar", name, {}, "asset.import"));
23 const auto number = parsed.value().isInt64() ? double(parsed.value().asInt()) : parsed.value().asDouble();
24 if (!std::isfinite(number))
26 Diagnostic::error(DiagnosticCode::ParseError, "nonfinite material scalar", name, {}, "asset.import"));
28}
29std::string refKey(const std::string& guid, const std::string& id) {
30 return "unity:" + unity_detail::foldAscii(guid) + "/" + id;
31}
32struct Document {
33 std::string type, id, body;
34};
35std::vector<Document> documents(const std::string& text) {
36 const std::regex header(R"((?:^|\n)--- !u!([0-9]+) &(-?[0-9]+)[^\n]*\n)");
37 std::vector<Document> out;
38 std::size_t start = 0;
39 for (std::sregex_iterator it(text.begin(), text.end(), header), end; it != end; ++it) {
40 if (!out.empty()) out.back().body = text.substr(start, std::size_t(it->position()) - start);
41 out.push_back({(*it)[1].str(), (*it)[2].str(), {}});
42 start = std::size_t(it->position() + it->length());
43 }
44 if (!out.empty()) out.back().body = text.substr(start);
45 return out;
46}
47} // namespace
48
50 const UnitySourceAsset& source) {
51 const auto& bytes = request.files.at(source.path);
52 const std::string text(bytes.begin(), bytes.end());
53 if (!match(text, R"(m_Shader: *\{fileID: *(46), guid: *0000000000000000f000000000000000, type: *0\})"))
55 auto metallic = property(text, "_Metallic", 0);
57 auto smoothness = property(text, "_Glossiness", 0.5);
58 if (!smoothness) return Result<PreparedAssetImport>::failure(smoothness.status());
59 auto mode = property(text, "_Mode", 0);
60 if (!mode) return Result<PreparedAssetImport>::failure(mode.status());
61 if ((mode.value() != 0 && mode.value() != 2 && mode.value() != 3) || metallic.value() < 0 || metallic.value() > 1 ||
62 smoothness.value() < 0 || smoothness.value() > 1)
64 DiagnosticCode::Unsupported, "Standard opaque/fade/transparent materials require normalized factors",
65 source.path, {}, "asset.import"));
66 Value::Array color{Value(1.0), Value(1.0), Value(1.0), Value(1.0)};
67 if (auto value = match(text, R"(- _Color: *(\{[^\r\n]+\}))")) {
68 auto parsed = Value::fromJson(std::regex_replace(*value, std::regex(R"(([rgba]):)"), "\"$1\":"));
69 if (!parsed || !parsed.value().isObject())
71 DiagnosticCode::ParseError, "invalid material color", source.path, {}, "asset.import"));
72 unsigned i = 0;
73 for (const auto key : {"r", "g", "b", "a"}) {
74 const auto& object = *parsed.value().getIf<Value::Object>();
75 const auto entry = object.find(key);
76 if (entry == object.end() || !entry->second.isNumeric())
78 DiagnosticCode::ParseError, "invalid material color component", source.path, {}, "asset.import"));
79 color[i++] = entry->second;
80 }
81 }
82 auto manifest = detail::baseManifest(request.package, "eve.unity-material/1");
83 if (!manifest) return Result<PreparedAssetImport>::failure(manifest.status());
84 PreparedAssetImport out;
85 out.manifest = std::move(manifest).takeValue();
86 const auto id = request.package.packageId.child("unity:" + source.guid + ":2100000");
87 auto ref = detail::assetRef(id);
88 if (!ref) return Result<PreparedAssetImport>::failure(ref.status());
89 Value::Object definition{{"schema", Value("eve.material")},
90 {"schemaVersion", Value(std::int64_t(15))},
91 {"alphaToCoverage", Value(false)},
92 {"shadingModel", Value("pbr")},
93 {"surfaceMode", Value(mode.value() == 0 ? "opaque" : "transparent")},
94 {"baseColor", Value(std::move(color))},
95 {"metallic", Value(metallic.value())},
96 {"roughness", Value(1.0 - smoothness.value())}};
97 if (mode.value() != 0) definition["blendMode"] = Value(mode.value() == 3 ? "premultiplied" : "alpha");
98 if (auto main = match(text, R"(- _MainTex:([\s\S]*?)(?:\n - |\n m_Floats:))")) {
99 const auto guid = match(*main, R"(m_Texture: *\{fileID: *2800000, guid: *([0-9a-fA-F]{32}), type: *3\})");
100 if (guid) {
101 if (mode.value() == 3)
103 DiagnosticCode::Unsupported, "premultiplied Standard texture alpha requires a shader conversion",
104 source.path, {}, "asset.import"));
105 if (!match(*main, R"(m_Scale: *\{x: *(1), y: *1\})") || !match(*main, R"(m_Offset: *\{x: *(0), y: *0\})"))
107 Diagnostic::error(DiagnosticCode::Unsupported, "material texture transform is unsupported",
108 source.path, {}, "asset.import"));
109 auto image = detail::assetRef(
110 request.package.packageId.child("unity:" + unity_detail::foldAscii(*guid)).child("image:default"));
111 if (!image) return Result<PreparedAssetImport>::failure(image.status());
112 definition["baseColorTexture"] = Value(image.value().format());
113 out.manifest.dependencies.push_back({ref.value(),
114 image.value(),
116 "baseColorTexture",
117 {},
118 "eve.image/3",
119 {}});
120 }
121 }
122 const auto path = "assets/" + id.format() + "/asset.json";
123 auto json = Value(std::move(definition)).toJson();
124 if (!json) return Result<PreparedAssetImport>::failure(json.status());
125 std::vector<std::uint8_t> encoded(json.value().begin(), json.value().end());
126 out.manifest.assets.push_back(
127 {ref.value(), "eve.material", SchemaVersion(15), path, detail::sha256(encoded), {"material", "source:unity"}});
128 out.entries.push_back({path, std::move(encoded)});
129 out.manifest.entrypoints.emplace("default", ref.value());
130 out.sourceMappings.push_back({"2100000", ref.value()});
131 out.findings.push_back({source.path, "Material.Standard", ImportDisposition::Translated,
132 "metallic-roughness, base color and surface mode"});
133 out.findings.push_back({source.path, "Material.extraProperties", ImportDisposition::Unsupported,
134 "additional shader keywords, non-base texture maps and saved properties are not applied"});
135 return Result<PreparedAssetImport>::success(std::move(out));
136}
137
139 PreparedAssetImport& out) {
140 std::map<std::string, AssetRef> mappings;
141 std::map<std::string, std::size_t> nativeSlots;
142 const std::regex submeshMarker("(?:^|\\n) firstByte:");
143 for (const auto& source : index.assets) {
144 if (detail::extension(source.path) != "asset") continue;
145 const auto& bytes = request.files.at(source.path);
146 const std::string text(bytes.begin(), bytes.end());
147 if (text.find("!u!43 ") != std::string::npos)
148 nativeSlots[source.guid] = std::size_t(
149 std::distance(std::sregex_iterator(text.begin(), text.end(), submeshMarker), std::sregex_iterator()));
150 }
151 for (const auto& mapping : out.sourceMappings) mappings.emplace(mapping.sourceObject, mapping.asset);
152 for (const auto& source : index.assets) {
153 if (source.kind != UnitySourceKind::Prefab) continue;
154 const auto sceneId = detail::assetRef(request.package.packageId.child("unity-prefab:" + source.guid));
155 if (!sceneId) return Result<void>::failure(sceneId.status());
156 auto asset = std::find_if(out.manifest.assets.begin(), out.manifest.assets.end(),
157 [&](const auto& a) { return a.asset == sceneId.value(); });
158 if (asset == out.manifest.assets.end()) continue;
159 auto entry = std::find_if(out.entries.begin(), out.entries.end(),
160 [&](const auto& e) { return e.path == asset->definition; });
161 if (entry == out.entries.end())
162 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "scene definition absent",
163 source.path, {}, "asset.import"));
164 const auto& bytes = request.files.at(source.path);
165 const std::string text(bytes.begin(), bytes.end());
166 auto docs = documents(text);
167 std::map<std::string, Document> transforms, filters;
168 for (const auto& doc : docs) {
169 if (doc.type != "4" && doc.type != "33") continue;
170 const auto game = match(doc.body, R"(m_GameObject: *\{fileID: *(-?[0-9]+)\})");
171 if (!game) continue;
172 auto& target = doc.type == "4" ? transforms : filters;
173 if (!target.emplace(*game, doc).second)
175 "duplicate Transform or MeshFilter on GameObject",
176 source.path, {}, "asset.import"));
177 }
178 auto definition = Value::fromJson(std::string(entry->bytes.begin(), entry->bytes.end()));
179 if (!definition) return Result<void>::failure(definition.status());
180 auto* object = definition.value().getIf<Value::Object>();
181 if (!object)
182 return Result<void>::failure(Diagnostic::error(DiagnosticCode::ParseError, "invalid scene definition",
183 source.path, {}, "asset.import"));
184 auto* nodes = object->at("nodes").getIf<Value::Array>();
185 if (!nodes)
187 Diagnostic::error(DiagnosticCode::ParseError, "invalid scene nodes", source.path, {}, "asset.import"));
188 std::int64_t nextNode = 1;
189 for (const auto& node : *nodes)
190 nextNode = std::max(nextNode, node.getIf<Value::Object>()->at("sourceFileId").asInt());
191 Value::Array renderers;
192 std::set<std::string> resolvedComponents;
193 for (const auto& doc : docs) {
194 if (doc.type != "23") continue;
195 const auto game = match(doc.body, R"(m_GameObject: *\{fileID: *(-?[0-9]+)\})");
196 if (!game || !transforms.contains(*game) || !filters.contains(*game)) continue;
197 const auto& filter = filters.at(*game);
198 const auto meshGuid =
199 match(filter.body, R"(m_Mesh: *\{fileID: *-?[0-9]+, guid: *([0-9a-fA-F]{32}), type: *[23]\})");
200 const auto meshId = match(filter.body, R"(m_Mesh: *\{fileID: *(-?[0-9]+),)");
201 if (!meshGuid || !meshId) continue;
202 const auto key = refKey(*meshGuid, *meshId);
203 const bool native = nativeSlots.contains(unity_detail::foldAscii(*meshGuid));
204 std::size_t importedSlots = 0;
205 while (mappings.contains(key + "/submesh/" + std::to_string(importedSlots))) ++importedSlots;
206 const bool split = native || importedSlots > 0;
207 if (!split && !mappings.contains(key)) continue;
208 const auto list = match(doc.body, R"(m_Materials:([^\n]*\n(?: - [^\n]*(?:\n|$))*))");
209 if (!list) continue;
210 std::vector<std::string> materials;
211 const std::regex materialPattern(R"( - (\{[^\n]+\}))");
212 for (std::sregex_iterator it(list->begin(), list->end(), materialPattern), end; it != end; ++it) {
213 auto guid = match((*it)[1].str(), R"(fileID: *2100000, guid: *([0-9a-fA-F]{32}), type: *2)");
214 materials.push_back(guid.value_or(""));
215 }
216 std::size_t count = 1;
217 if (native)
218 count = nativeSlots.at(unity_detail::foldAscii(*meshGuid));
219 else if (importedSlots > 0)
220 count = importedSlots;
221 if (materials.size() != count) {
222 out.findings.push_back({source.path, "Prefab.materialSlots", ImportDisposition::Unsupported,
223 "material slot count does not match mesh submeshes"});
224 continue;
225 }
226 for (std::size_t slot = 0; slot < count; ++slot) {
227 if (!mappings.contains(refKey(materials[slot], "2100000")) ||
228 (split && !mappings.contains(key + "/submesh/" + std::to_string(slot))))
229 continue;
230 const auto meshRef = mappings.at(split ? key + "/submesh/" + std::to_string(slot) : key);
231 const auto materialRef = mappings.at(refKey(materials[slot], "2100000"));
232 const auto materialAsset =
233 std::find_if(out.manifest.assets.begin(), out.manifest.assets.end(), [&](const auto& candidate) {
234 return candidate.asset == materialRef && candidate.type == "eve.material";
235 });
236 if (materialAsset == out.manifest.assets.end())
238 "renderer material definition is absent",
239 source.path, {}, "asset.import"));
240 const auto materialType = "eve.material/" + std::to_string(materialAsset->schemaVersion.value());
241 auto objectId = request.package.packageId.child("unity:" + source.guid + ":" + transforms.at(*game).id);
242 if (split) {
243 if (nextNode == std::numeric_limits<std::int64_t>::max() ||
246 "submesh node budget exceeded", source.path, {},
247 "asset.import"));
248 ++nextNode;
249 objectId = objectId.child("submesh:" + std::to_string(slot));
250 nodes->emplace_back(
251 Value::Object{{"objectId", Value(objectId.format())},
252 {"sourceFileId", Value(nextNode)},
253 {"parentSourceFileId", Value(std::int64_t(std::stoll(transforms.at(*game).id)))},
254 {"name", Value("Submesh " + std::to_string(slot))},
255 {"visible", Value(true)},
256 {"position", Value(Value::Array{Value(0.0), Value(0.0), Value(0.0)})},
257 {"rotation", Value(Value::Array{Value(0.0), Value(0.0), Value(0.0), Value(1.0)})},
258 {"scale", Value(Value::Array{Value(1.0), Value(1.0), Value(1.0)})}});
259 }
260 renderers.emplace_back(
261 Value::Object{{"objectId", Value(objectId.format())},
262 {"mesh", Value(meshRef.format())},
263 {"material", Value(materialRef.format())},
264 {"enabled", Value(!match(doc.body, R"(m_Enabled: *(0)(?:\s|$))").has_value())},
265 {"castShadows",
266 Value(!match(doc.body, R"(m_CastShadows: *(0)(?:\s|$))").has_value())},
267 {"receiveShadows",
268 Value(!match(doc.body, R"(m_ReceiveShadows: *(0)(?:\s|$))").has_value())}});
269 const auto prefix = "renderers[" + std::to_string(renderers.size() - 1) + "]";
270 out.manifest.dependencies.push_back({sceneId.value(),
271 meshRef,
273 prefix + ".mesh",
274 {},
275 "eve.mesh/2",
276 {}});
277 out.manifest.dependencies.push_back({sceneId.value(),
278 materialRef,
280 prefix + ".material",
281 {},
282 materialType,
283 {}});
284 }
285 resolvedComponents.insert("Prefab.component:23:" + doc.id);
286 resolvedComponents.insert("Prefab.component:33:" + filter.id);
287 }
288 if (renderers.empty()) continue;
289 (*object)["schemaVersion"] = Value(std::int64_t(3));
290 (*object)["renderers"] = Value(std::move(renderers));
291 auto encoded = definition.value().toJson();
292 if (!encoded) return Result<void>::failure(encoded.status());
293 entry->bytes.assign(encoded.value().begin(), encoded.value().end());
294 asset->schemaVersion = SchemaVersion(3);
295 asset->contentHash = detail::sha256(entry->bytes);
296 std::erase_if(out.findings, [&](const auto& finding) {
297 return finding.sourcePath == source.path && resolvedComponents.contains(finding.feature);
298 });
299 out.findings.push_back({source.path, "Prefab.renderBindings", ImportDisposition::Translated,
300 "resolved mesh and material by GUID/fileID"});
301 out.findings.push_back({source.path, "Prefab.shadowState", ImportDisposition::Translated,
302 "renderer cast and receive shadow switches retained for runtime passes"});
303 }
304 return Result<void>::success();
305}
306} // namespace eve::asset_import
LogicalId target
double value
Duration start
building::EdgeCurveGroup group
bool split
Definition CaveMesh.cpp:123
std::string pattern
const GltfImportRequest & request
std::uint32_t key
vk::UniqueImage image
std::string text
std::uint64_t bytes
std::string name
MeleePoint3 a
Definition MeleeHit.cpp:40
std::vector< BvhNode > nodes
float metallic
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
const RoadNode * node
double number
std::string filter
std::uint32_t count
std::string body
Direct Unity text-serialization adapter for TerrainData and Prefab assets.
uint32_t index
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
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
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
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
int main(int argc, char **argv)
Definition main.cpp:46
const char * defaultValue
std::string sha256(std::span< const std::uint8_t > bytes)
Sha 256.
std::string extension(std::string_view name)
Extension.
Result< asset::EvaManifest > baseManifest(const ImportPackageIdentity &package, std::string_view importer)
Base manifest.
Result< AssetRef > assetRef(PersistentId id)
Asset ref.
std::string foldAscii(std::string value)
Fold ascii.
Result< PreparedAssetImport > prepareUnityMaterial(const UnityProjectImportRequest &request, const UnitySourceAsset &source)
Dispatch built-in Standard and admitted vegetation materials to canonical PBR data.
Result< PreparedAssetImport > prepareUnityVegetationMaterial(const UnityProjectImportRequest &request, const UnitySourceAsset &source)
Convert admitted TVE primary material data; remaining source features are reported explicitly.
Result< void > bindUnityRenderers(const UnityProjectImportRequest &request, const UnitySourceIndex &index, PreparedAssetImport &out)
Resolve renderer references after all collection assets are prepared; unpublished candidate only.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
std::string header(int localX, int localY)
Header.
Definition KernelGen.cpp:13
@ TypeMismatch
A stable reference resolved to a different canonical domain type.
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
std::vector< EvaAssetEntry > assets
Definition EvaManifest.h:69
std::vector< EvaDependency > dependencies
Definition EvaManifest.h:70
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.
Immutable-by-convention owning source index entry; no borrowed source addresses.
Definition UnitySource.h:38
Owning deterministic index sorted by path, plus missing-reference diagnostics.
Definition UnitySource.h:47
glm::vec4 color