载入中...
搜索中...
未找到
UnityVegetationScene.cpp
浏览该文件的文档.
4
5#include <algorithm>
6#include <cmath>
7#include <map>
8#include <regex>
9#include <set>
10
11namespace eve::asset_import {
12namespace {
13constexpr std::string_view controlGuid = "504b8163e405ec24bab88beccbe43d42";
14constexpr std::string_view detailsGuid = "f3cf74665658f2249a2a56df92106e94";
15constexpr std::string_view motionGuid = "c879218671cebf84d9385947d79161cd";
16constexpr std::string_view volumeGuid = "eaf239e2056652741929c4183efd08b2";
17constexpr std::string_view elementGuid = "2d447c3f2fda29f41a5b7e81398c0ab0";
18
19struct SceneComponent {
20 std::int64_t fileId = 0;
21 std::string body;
22};
23
24std::vector<SceneComponent> components(std::string_view source, std::string_view scriptGuid) {
25 std::vector<SceneComponent> result;
26 const std::regex document(
27 R"((?:^|\n)--- !u!114 &(-?[0-9]+)[^\n]*\nMonoBehaviour:\s*\n([\s\S]*?)(?=\n--- !u!|$))");
28 const std::regex script("(?:^|\\n) m_Script: *\\{fileID: *11500000, guid: *" +
29 std::string(scriptGuid) + R"(, type: *3\})");
30 for (std::cregex_iterator it(source.data(), source.data() + source.size(), document), end; it != end; ++it) {
31 const auto body = (*it)[2].str();
32 if (!std::regex_search(body, script)) continue;
33 try {
34 result.push_back({std::stoll((*it)[1].str()), body});
35 } catch (...) {
36 return {};
37 }
38 }
39 return result;
40}
41
42std::optional<std::string> component(std::string_view source, std::string_view scriptGuid) {
43 const std::regex document(R"((?:^|\n)--- !u!114 &-?[0-9]+[^\n]*\nMonoBehaviour:\s*\n([\s\S]*?)(?=\n--- !u!|$))");
44 const std::regex script("(?:^|\\n) m_Script: *\\{fileID: *11500000, guid: *" +
45 std::string(scriptGuid) + R"(, type: *3\})");
46 for (std::cregex_iterator it(source.data(), source.data() + source.size(), document), end; it != end; ++it) {
47 const auto body = (*it)[1].str();
48 if (std::regex_search(body, script)) return body;
49 }
50 return {};
51}
52
53Result<double> number(std::string_view body, std::string_view name, double fallback) {
54 std::cmatch match;
55 const std::regex field("(?:^|\\n) " + std::string(name) + R"(: *([^\r\n]+))");
56 if (!std::regex_search(body.data(), body.data() + body.size(), match, field))
57 return Result<double>::success(fallback);
58 try {
59 std::size_t end = 0;
60 const auto token = match[1].str();
61 const auto value = std::stod(token, &end);
62 if (end != token.size() || !std::isfinite(value)) throw std::invalid_argument("number");
64 } catch (...) {
65 return Result<double>::failure(Diagnostic::error(DiagnosticCode::ParseError, "TVE scene number is invalid",
66 std::string(name), {}, "asset.import"));
67 }
68}
69
70Result<Value> array(std::string_view body,
71 std::initializer_list<std::pair<std::string_view, double>> fields) {
72 Value::Array result;
73 for (const auto& [name, fallback] : fields) {
74 auto value = number(body, name, fallback);
75 if (!value) return Result<Value>::failure(value.status());
76 result.emplace_back(value.value());
77 }
78 return Result<Value>::success(Value(std::move(result)));
79}
80
81Result<Value> color(std::string_view body, std::string_view name, std::array<double, 4> fallback) {
82 std::cmatch match;
83 const std::regex field("(?:^|\\n) " + std::string(name) +
84 R"(: *\{r: *([^,]+), *g: *([^,]+), *b: *([^,]+), *a: *([^}]+)\})");
85 if (!std::regex_search(body.data(), body.data() + body.size(), match, field))
86 return Result<Value>::success(Value::array({fallback[0], fallback[1], fallback[2], fallback[3]}));
87 Value::Array result;
88 for (std::size_t index = 1; index <= 4; ++index) {
89 try {
90 std::size_t end = 0;
91 const auto token = match[index].str();
92 const auto value = std::stod(token, &end);
93 if (end != token.size() || !std::isfinite(value)) throw std::invalid_argument("color");
94 result.emplace_back(value);
95 } catch (...) {
96 return Result<Value>::failure(Diagnostic::error(DiagnosticCode::ParseError, "TVE scene color is invalid",
97 std::string(name), {}, "asset.import"));
98 }
99 }
100 return Result<Value>::success(Value(std::move(result)));
101}
102
103std::string guid(std::string_view body, std::string_view name) {
104 std::cmatch match;
105 const std::regex field("(?:^|\\n) " + std::string(name) +
106 R"(: *\{fileID: *-?[0-9]+, guid: *([0-9a-fA-F]{32}), type: *[23]\})");
107 return std::regex_search(body.data(), body.data() + body.size(), match, field)
108 ? unity_detail::foldAscii(match[1].str())
109 : std::string{};
110}
111
112Result<Value> details(std::string_view body) {
113 auto layers = array(body, {{"layerColors", 0}, {"layerExtras", 0}, {"layerMotion", 0}});
114 auto global = array(body, {{"globalColor", 1}, {"globalAlpha", 1}, {"globalOverlay", 1}, {"globalWetness", 1}});
115 auto colorMask = array(body, {{"colorMaskMin", .4}, {"colorMaskMax", .6}});
116 auto overlayMask = array(body, {{"overlayMaskMin", .4}, {"overlayMaskMax", .6}});
117 auto perspective = array(body, {{"perspectivePush", 0}, {"perspectiveNoise", 0}, {"perspectiveAngle", 1}});
118 auto highlight = color(body, "motionHighlight", {1, 1, 1, 1});
119 auto bending = array(body, {{"bendingAmplitude", 1}, {"bendingSpeed", 6}, {"bendingScale", 2}});
120 auto flutter = array(body, {{"flutterAmplitude", .1}, {"flutterSpeed", 20}, {"flutterScale", 2}});
121 auto alpha = number(body, "alphaTreshold", .5), interaction = number(body, "interactionAmplitude", 1);
122 const bool layersOk = bool(layers), globalOk = bool(global), colorMaskOk = bool(colorMask),
123 overlayMaskOk = bool(overlayMask), perspectiveOk = bool(perspective), highlightOk = bool(highlight),
124 bendingOk = bool(bending), flutterOk = bool(flutter), alphaOk = bool(alpha),
125 interactionOk = bool(interaction);
126 if (!layersOk || !globalOk || !colorMaskOk || !overlayMaskOk || !perspectiveOk || !highlightOk || !bendingOk ||
127 !flutterOk || !alphaOk || !interactionOk)
129 Diagnostic::error(DiagnosticCode::ParseError, "TVE Global Details is malformed", {}, {}, "asset.import"));
130 auto highlightValue = std::move(highlight).takeValue();
131 highlightValue.getIf<Value::Array>()->pop_back();
133 {"layers", std::move(layers).takeValue()}, {"global", std::move(global).takeValue()},
134 {"colorMask", std::move(colorMask).takeValue()}, {"overlayMask", std::move(overlayMask).takeValue()},
135 {"alphaThreshold", alpha.value()}, {"perspective", std::move(perspective).takeValue()},
136 {"motionHighlight", std::move(highlightValue)}, {"bending", std::move(bending).takeValue()},
137 {"flutter", std::move(flutter).takeValue()}, {"interactionAmplitude", interaction.value()}}));
138}
139
140Result<Value> control(std::string_view body) {
141 auto tint = color(body, "globalColor", {.5, .5, .5, 0});
142 auto overlay = color(body, "overlayColor", {1, 1, 1, 1});
143 auto values = array(body, {{"seasonControl", 2}, {"globalAlpha", 1}, {"globalOverlay", 0},
144 {"globalWetness", 0}, {"globalEmissive", 1}, {"globalSubsurface", 1},
145 {"globalSizeFade", 1}, {"overlaySmoothness", .5}, {"overlayNormalScale", .5},
146 {"overlaySubsurface", .5}, {"overlayScale", 1}, {"wetnessContrast", .5},
147 {"wetnessNormalScale", .5}, {"noiseTextureTilling", 1},
148 {"proximityDistanceFade", 1}, {"sizeFadeDistanceBias", 1},
149 {"defaultConformHeight", 0}});
150 const bool tintOk = bool(tint), overlayOk = bool(overlay), valuesOk = bool(values);
151 if (!tintOk || !overlayOk || !valuesOk)
153 Diagnostic::error(DiagnosticCode::ParseError, "TVE Global Control is malformed", {}, {}, "asset.import"));
155 {"values", std::move(values).takeValue()}, {"globalColor", std::move(tint).takeValue()},
156 {"overlayColor", std::move(overlay).takeValue()}, {"overlayAlbedoGuid", guid(body, "overlayAlbedo")},
157 {"overlayNormalGuid", guid(body, "overlayNormal")}, {"noiseTextureGuid", guid(body, "noiseTexture3D")}}));
158}
159
160Result<Value> motion(std::string_view body) {
161 auto values = array(body, {{"windPower", .5}, {"noiseTilling", 1}, {"motionBending", 1},
162 {"motionBranch", 1}, {"motionFlutter", 1}, {"motionSpeed", 1},
163 {"useAnimatedMotionTime", 1}, {"motionFadeDistance", 100}});
164 if (!values) return Result<Value>::failure(values.status());
165 return Result<Value>::success(Value::object({{"values", std::move(values).takeValue()},
166 {"noiseTextureGuid", guid(body, "noiseTexture")}}));
167}
168
169struct SceneTransform {
170 std::int64_t parent = 0;
171 std::int64_t gameObject = 0;
172 std::array<double, 4> rotation{0, 0, 0, 1};
173 std::array<double, 3> position{0, 0, 0}, scale{1, 1, 1};
174};
175
176std::optional<std::int64_t> fileId(std::string_view body, std::string_view name) {
177 const std::string field = " " + std::string(name) + ":";
178 auto position = body.find(field);
179 while (position != std::string_view::npos && position != 0 && body[position - 1] != '\n')
180 position = body.find(field, position + field.size());
181 if (position == std::string_view::npos) return {};
182 position = body.find("fileID:", position + field.size());
183 if (position == std::string_view::npos) return {};
184 position += 7;
185 while (position < body.size() && body[position] == ' ') ++position;
186 const auto end = body.find_first_of(",}", position);
187 try {
188 std::size_t consumed = 0;
189 const auto token = std::string(body.substr(position, end - position));
190 const auto value = std::stoll(token, &consumed);
191 if (consumed != token.size()) return {};
192 return value;
193 } catch (...) {
194 return {};
195 }
196}
197
198std::optional<double> rotationComponent(std::string_view line, std::string_view name) {
199 const std::string field = std::string(name) + ":";
200 auto position = line.find(field);
201 if (position == std::string_view::npos) return {};
202 position += field.size();
203 while (position < line.size() && line[position] == ' ') ++position;
204 const auto end = line.find_first_of(",}", position);
205 try {
206 std::size_t consumed = 0;
207 const auto token = std::string(line.substr(position, end - position));
208 const auto value = std::stod(token, &consumed);
209 if (consumed != token.size() || !std::isfinite(value)) return {};
210 return value;
211 } catch (...) {
212 return {};
213 }
214}
215
216std::optional<std::array<double, 3>> vector3Field(std::string_view body, std::string_view name) {
217 const std::string field = " " + std::string(name) + ":";
218 const auto start = body.find(field);
219 if (start == std::string_view::npos) return {};
220 const auto end = body.find('\n', start);
221 const auto line = body.substr(start, end - start);
222 const auto x = rotationComponent(line, "x"), y = rotationComponent(line, "y"), z = rotationComponent(line, "z");
223 if (!x || !y || !z) return {};
224 return std::array<double, 3>{*x, *y, *z};
225}
226
227std::map<std::int64_t, SceneTransform> transforms(std::string_view source) {
228 std::map<std::int64_t, SceneTransform> result;
229 constexpr std::string_view marker = "--- !u!4 &";
230 std::size_t cursor = 0;
231 while ((cursor = source.find(marker, cursor)) != std::string_view::npos) {
232 if (cursor != 0 && source[cursor - 1] != '\n') {
233 cursor += marker.size();
234 continue;
235 }
236 const auto idBegin = cursor + marker.size();
237 const auto headerEnd = source.find('\n', idBegin);
238 if (headerEnd == std::string_view::npos) break;
239 const auto bodyBegin = headerEnd + 1;
240 if (!source.substr(bodyBegin).starts_with("Transform:")) {
241 cursor = bodyBegin;
242 continue;
243 }
244 const auto bodyContent = source.find('\n', bodyBegin);
245 if (bodyContent == std::string_view::npos) break;
246 const auto next = source.find("\n--- !u!", bodyContent + 1);
247 const auto body = source.substr(bodyContent + 1,
248 next == std::string_view::npos ? source.size() - bodyContent - 1
249 : next - bodyContent - 1);
250 SceneTransform transform;
251 try {
252 transform.gameObject = fileId(body, "m_GameObject").value_or(0);
253 transform.parent = fileId(body, "m_Father").value_or(0);
254 transform.position = vector3Field(body, "m_LocalPosition").value_or(std::array<double, 3>{0, 0, 0});
255 transform.scale = vector3Field(body, "m_LocalScale").value_or(std::array<double, 3>{1, 1, 1});
256 const auto rotationStart = body.find(" m_LocalRotation:");
257 if (rotationStart == std::string_view::npos) {
258 cursor = next == std::string_view::npos ? source.size() : next + 1;
259 continue;
260 }
261 const auto rotationEnd = body.find('\n', rotationStart);
262 const auto line = body.substr(rotationStart, rotationEnd - rotationStart);
263 const auto x = rotationComponent(line, "x"), y = rotationComponent(line, "y"),
264 z = rotationComponent(line, "z"), w = rotationComponent(line, "w");
265 if (!x || !y || !z || !w) return {};
266 transform.rotation = {*x, *y, *z, *w};
267 result.emplace(std::stoll(std::string(source.substr(idBegin, headerEnd - idBegin))), transform);
268 } catch (...) {
269 return {};
270 }
271 cursor = next == std::string_view::npos ? source.size() : next + 1;
272 }
273 return result;
274}
275
276struct WorldTransform {
277 std::array<double, 3> position{0, 0, 0}, scale{1, 1, 1};
278 std::array<double, 4> rotation{0, 0, 0, 1};
279};
280
281std::array<double, 4> multiply(std::array<double, 4> lhs, std::array<double, 4> rhs);
282
283Result<std::array<double, 4>> normalized(std::array<double, 4> value) {
284 const double length = std::sqrt(value[0] * value[0] + value[1] * value[1] + value[2] * value[2] +
285 value[3] * value[3]);
286 if (!std::isfinite(length) || length <= 1e-12)
287 return Result<std::array<double, 4>>::failure(Diagnostic::error(
288 DiagnosticCode::ParseError, "TVE element Transform rotation is invalid", {}, {}, "asset.import"));
289 for (auto& component : value) component /= length;
290 return Result<std::array<double, 4>>::success(value);
291}
292
293std::array<double, 3> rotate(std::array<double, 4> q, std::array<double, 3> v) {
294 const std::array<double, 4> p{v[0], v[1], v[2], 0};
295 const std::array<double, 4> conjugate{-q[0], -q[1], -q[2], q[3]};
296 const auto result = multiply(multiply(q, p), conjugate);
297 return {result[0], result[1], result[2]};
298}
299
300Result<WorldTransform> worldTransform(const std::map<std::int64_t, SceneTransform>& sceneTransforms,
301 std::int64_t gameObject) {
302 auto found = std::find_if(sceneTransforms.begin(), sceneTransforms.end(),
303 [&](const auto& entry) { return entry.second.gameObject == gameObject; });
304 if (found == sceneTransforms.end())
306 Diagnostic::error(DiagnosticCode::NotFound, "TVE element Transform is missing", {}, {}, "asset.import"));
307 std::vector<const SceneTransform*> chain{&found->second};
308 std::set<std::int64_t> visited{found->first};
309 auto parent = found->second.parent;
310 while (parent != 0) {
311 const auto ancestor = sceneTransforms.find(parent);
312 if (ancestor == sceneTransforms.end() || !visited.emplace(parent).second)
314 DiagnosticCode::ParseError, "TVE element Transform hierarchy is invalid", {}, {}, "asset.import"));
315 chain.push_back(&ancestor->second);
316 parent = ancestor->second.parent;
317 }
318 WorldTransform world;
319 for (auto it = chain.rbegin(); it != chain.rend(); ++it) {
320 auto localRotation = normalized((*it)->rotation);
321 if (!localRotation) return Result<WorldTransform>::failure(localRotation.status());
322 std::array<double, 3> local{(*it)->position[0] * world.scale[0], (*it)->position[1] * world.scale[1],
323 (*it)->position[2] * world.scale[2]};
324 local = rotate(world.rotation, local);
325 for (std::size_t axis = 0; axis < 3; ++axis) {
326 world.position[axis] += local[axis];
327 world.scale[axis] *= (*it)->scale[axis];
328 }
329 auto composed = normalized(multiply(world.rotation, localRotation.value()));
330 if (!composed) return Result<WorldTransform>::failure(composed.status());
331 world.rotation = std::move(composed).takeValue();
332 }
333 world.position[2] = -world.position[2];
334 world.rotation[0] = -world.rotation[0];
335 world.rotation[1] = -world.rotation[1];
337}
338
339std::optional<std::string_view> propertyBlock(std::string_view body, std::string_view name) {
340 const std::string marker = " prop: " + std::string(name);
341 auto begin = body.find(marker);
342 if (begin == std::string_view::npos) return {};
343 begin = body.rfind(" - type:", begin);
344 if (begin == std::string_view::npos) return {};
345 auto end = body.find("\n - type:", begin + 1);
346 if (end == std::string_view::npos) end = body.size();
347 return body.substr(begin, end - begin);
348}
349
350double materialNumber(std::string_view body, std::string_view name, double fallback) {
351 const auto block = propertyBlock(body, name);
352 if (!block) return fallback;
353 const auto value = number(*block, " value", fallback);
354 return value ? value.value() : fallback;
355}
356
357Value materialVector(std::string_view body, std::string_view name, std::array<double, 4> fallback) {
358 const auto block = propertyBlock(body, name);
359 if (!block) return Value::array({fallback[0], fallback[1], fallback[2], fallback[3]});
360 auto value = color(*block, " vector", fallback);
361 return value ? std::move(value).takeValue() : Value::array({fallback[0], fallback[1], fallback[2], fallback[3]});
362}
363
364std::string materialTexture(std::string_view body, std::string_view name) {
365 const auto block = propertyBlock(body, name);
366 return block ? guid(*block, " texture") : std::string{};
367}
368
369Result<Value> materialProperties(std::string_view body) {
371 std::size_t cursor = 0;
372 constexpr std::string_view marker = " - type:";
373 const std::regex namePattern(R"((?:^|\n) prop: *([A-Za-z_][A-Za-z0-9_]{0,127})\s*(?:\r?\n|$))");
374 while ((cursor = body.find(marker, cursor)) != std::string_view::npos) {
375 const auto next = body.find("\n - type:", cursor + marker.size());
376 const auto block = body.substr(cursor, next == std::string_view::npos ? body.size() - cursor : next - cursor);
377 std::cmatch name;
378 if (!std::regex_search(block.data(), block.data() + block.size(), name, namePattern))
380 DiagnosticCode::ParseError, "TVE element property name is malformed", {}, {}, "asset.import"));
381 auto type = number(block, " - type", -1), value = number(block, " value", 0);
382 auto vector = color(block, " vector", {0, 0, 0, 0});
383 if (!type || !value || !vector || std::floor(type.value()) != type.value() || type.value() < 0 ||
384 type.value() > 2)
386 "TVE element property value is malformed", name[1].str(),
387 {}, "asset.import"));
388 output.push_back(Value::object({{"name", name[1].str()}, {"type", std::int64_t(type.value())},
389 {"textureGuid", guid(block, " texture")},
390 {"vector", std::move(vector).takeValue()}, {"value", value.value()}}));
391 if (output.size() > 256)
393 DiagnosticCode::InvalidArgument, "TVE element property count exceeds 256", {}, {}, "asset.import"));
394 cursor = next == std::string_view::npos ? body.size() : next + 1;
395 }
396 return Result<Value>::success(Value(std::move(output)));
397}
398
399void collectTextureGuids(const Value& value, std::set<std::string>& output) {
400 if (const auto* object = value.getIf<Value::Object>()) {
401 for (const auto& [name, child] : *object) {
402 if (name == "textureGuid" && child.isString() && !child.asString().empty()) output.insert(child.asString());
403 collectTextureGuids(child, output);
404 }
405 } else if (const auto* array = value.getIf<Value::Array>()) {
406 for (const auto& child : *array) collectTextureGuids(child, output);
407 }
408}
409
410bool importedImageAvailable(const UnityProjectImportRequest& request, std::string_view targetGuid) {
411 for (const auto& [path, bytes] : request.files) {
412 if (!path.ends_with(".meta")) continue;
413 const std::string meta(bytes.begin(), bytes.end());
414 if (meta.find("guid: " + std::string(targetGuid)) == std::string::npos) continue;
415 const auto sourcePath = path.substr(0, path.size() - 5);
416 const auto source = request.files.find(sourcePath);
417 if (source == request.files.end()) return false;
418 const auto dot = sourcePath.find_last_of('.');
419 const auto extension = dot == std::string::npos ? std::string{} : unity_detail::foldAscii(sourcePath.substr(dot));
420 if (extension == ".png" || extension == ".tga" || extension == ".tif" || extension == ".tiff") return true;
421 if (extension != ".asset") return false;
422 const std::string text(source->second.begin(), source->second.end());
423 return text.find("\nTexture2D:") != std::string::npos;
424 }
425 return false;
426}
427
428Result<void> bindTextureAssets(Value& value, const UnityProjectImportRequest& request) {
429 if (auto* object = value.getIf<Value::Object>()) {
430 const auto guidValue = object->find("textureGuid");
431 if (guidValue != object->end() && guidValue->second.isString()) {
432 std::string asset;
433 if (!guidValue->second.asString().empty() && importedImageAvailable(request, guidValue->second.asString())) {
435 request.package.packageId.child("unity:" + guidValue->second.asString()).child("image:default"));
436 if (!reference) return Result<void>::failure(reference.status());
437 asset = reference.value().format();
438 }
439 object->emplace("textureAsset", std::move(asset));
440 }
441 for (auto& [name, child] : *object) {
442 (void)name;
443 auto bound = bindTextureAssets(child, request);
444 if (!bound) return bound;
445 }
446 } else if (auto* array = value.getIf<Value::Array>()) {
447 for (auto& child : *array) {
448 auto bound = bindTextureAssets(child, request);
449 if (!bound) return bound;
450 }
451 }
452 return Result<void>::success();
453}
454
455Result<Value> elements(std::string_view source, std::vector<ImportFinding>& findings, std::string_view path) {
456 const std::map<std::string_view, std::pair<std::string_view, int>> shaders{
457 {"1179777fe2a84944798f2df69a9616a5", {"color-effect", 0}},
458 {"ebee0293d9bb4564889bd81c801438b7", {"color-map", 0}},
459 {"da183c57da5cee64c9d1fd3e840427b6", {"color-noise", 0}},
460 {"853be43322d350648a82ec17b27de5cf", {"color-tint", 0}},
461 {"94b5d04d01c0973498bbb16cea8449ec", {"extras-alpha", 1}},
462 {"47fd4a0e901c8844a9f5ae6bcdef7ca4", {"extras-emissive", 1}},
463 {"b3820571eb3f04f4f855cc7161c165d7", {"extras-overlay", 1}},
464 {"3a8db8eb5154b6c44940a6c8616408d8", {"extras-wetness", 1}},
465 {"c2aed2f308142f94fa04326437ee0005", {"motion-advanced", 2}},
466 {"8aca4d3e46e8b7b458ec65a9bb11f144", {"motion-interaction", 2}},
467 {"c52053b6a008e1544bad0837b518fa94", {"motion-wind-power", 2}},
468 {"1e5171ac7b63ed441960da6cf37146a2", {"vertex-conform-model", 3}},
469 {"3f514efeb56cd5149a0f4c127eab8a8c", {"vertex-conform-simple", 3}},
470 {"4bcfd77507de42d40ad7ce2543a44cd8", {"vertex-conform-terrain", 3}},
471 {"387db0d42ba361645a8443beadc8bdd6", {"vertex-height-offset", 3}},
472 {"abb24a67f2ce88d498809438c1104783", {"vertex-height", 3}},
473 {"ab583b89d93bd5846883c513711e769a", {"vertex-orientation-model", 3}},
474 {"74b163185090aaf41b8df93640d24c50", {"vertex-orientation-terrain", 3}},
475 {"4776cc0409f0f4e4da64e5837bff4b0d", {"vertex-size", 3}},
476 };
477 const auto sceneTransforms = transforms(source);
479 std::size_t invalidMaterials = 0;
480 for (const auto& component : components(source, elementGuid)) {
481 const auto gameObject = fileId(component.body, "m_GameObject").value_or(0);
482 auto world = worldTransform(sceneTransforms, gameObject);
483 if (!world) return Result<Value>::failure(world.status());
484 const auto shader = guid(component.body, " shader");
485 const auto shaderDefinition = shaders.find(shader);
486 if (shaderDefinition == shaders.end()) { ++invalidMaterials; continue; }
487 const std::string kind(shaderDefinition->second.first);
488 const int channel = shaderDefinition->second.second;
489 std::array<double, 4> main{1, 1, 1, 1};
490 if (kind == "color-tint") main = {.5019608, .5019608, .5019608, 1};
491 double layerMask = 1;
492 if (propertyBlock(component.body, "_ElementLayerMask")) {
493 layerMask = materialNumber(component.body, "_ElementLayerMask", 1);
494 } else if (propertyBlock(component.body, "_ElementLayerValue")) {
495 layerMask = materialNumber(component.body, "_ElementLayerValue", 1);
496 } else {
497 const auto legacyLayer = materialNumber(component.body, "_ElementLayer", 0);
498 if (legacyLayer < 0 || legacyLayer > 8 || std::floor(legacyLayer) != legacyLayer)
500 "TVE legacy element layer is outside 0..8",
501 std::string(path), {}, "asset.import"));
502 layerMask = double(std::uint16_t(1u << std::uint32_t(legacyLayer)));
503 }
504 const auto seasonal = materialNumber(component.body, "_ElementMode", 0) >= .5;
505 Value::Array seasons;
506 for (int index = 1; index <= 4; ++index) {
507 if (kind == "color-tint")
508 seasons.push_back(materialVector(component.body, "_AdditionalColor" + std::to_string(index), main));
509 else {
510 const auto value = materialNumber(component.body, "_AdditionalValue" + std::to_string(index), 1);
511 seasons.push_back(Value::array({value, value, value, 1}));
512 }
513 }
514 const auto& transform = world.value();
515 auto enabledValue = number(component.body, "m_Enabled", 1);
516 auto visibilityValue = number(component.body, "customVisibility", -1);
517 auto propertiesValue = materialProperties(component.body);
518 if (!enabledValue || !visibilityValue || !propertiesValue)
519 return Result<Value>::failure(!enabledValue ? enabledValue.status()
520 : !visibilityValue ? visibilityValue.status()
521 : propertiesValue.status());
522 const double mainValue = materialNumber(component.body, "_MainValue", main[0]);
523 Value channelValue = materialVector(component.body, "_MainColor", main);
524 if (kind == "extras-overlay") channelValue = Value::array({1, 0, mainValue, 1});
525 else if (kind == "motion-interaction") channelValue = Value::array({1, 0, 0, mainValue});
526 else if (kind == "motion-wind-power") channelValue = Value::array({0, 0, mainValue, 1});
527 else if (kind == "vertex-size") channelValue = Value::array({0, 0, 0, mainValue});
528 output.push_back(Value::object({
529 {"sourceFileId", component.fileId}, {"shaderGuid", shader}, {"kind", kind}, {"channel", channel},
530 {"properties", std::move(propertiesValue).takeValue()},
531 {"enabled", enabledValue.value() >= .5},
532 {"visibility", std::int64_t(visibilityValue.value())},
533 {"position", Value::array({transform.position[0], transform.position[1], transform.position[2]})},
534 {"rotation", Value::array({transform.rotation[0], transform.rotation[1], transform.rotation[2], transform.rotation[3]})},
535 {"scale", Value::array({std::abs(transform.scale[0]), std::abs(transform.scale[1]), std::abs(transform.scale[2])})},
536 {"layers", std::int64_t(layerMask)}, {"intensity", materialNumber(component.body, "_ElementIntensity", 1)},
537 {"value", std::move(channelValue)},
538 {"seasonal", seasonal}, {"seasons", Value(std::move(seasons))},
539 {"textureGuid", materialTexture(component.body, "_MainTex")},
540 {"remap", Value::array({materialNumber(component.body, "_MainTexColorMinValue", 0),
541 materialNumber(component.body, "_MainTexColorMaxValue", 1),
542 materialNumber(component.body, "_MainTexAlphaMinValue", 0),
543 materialNumber(component.body, "_MainTexAlphaMaxValue", 1),
544 materialNumber(component.body, "_MainTexFallofMinValue", 0),
545 materialNumber(component.body, "_MainTexFallofMaxValue", 0)})},
546 {"blendRgb", std::int64_t(materialNumber(component.body, "_ElementBlendRGB", 0))},
547 {"blendAlpha", std::int64_t(materialNumber(component.body, "_ElementBlendA", 0))},
548 {"directionMode", std::int64_t(materialNumber(component.body, "_ElementDirectionMode", 20))},
549 {"invertDirection", materialNumber(component.body, "_ElementInvertMode",
550 materialNumber(component.body, "_InvertX", 0)) >= .5},
551 {"volumeFade", materialNumber(component.body, "_ElementVolumeFadeMode",
552 materialNumber(component.body, "_ElementFadeSupport", 0)) >= .5},
553 {"motionMode", std::int64_t(materialNumber(component.body, "_ElementMotionMode", 15))},
554 {"motionPower", materialNumber(component.body, "_MotionPower", 0)}}));
555 }
556 findings.push_back({std::string(path), "TVE.elements", ImportDisposition::Translated,
557 std::to_string(output.size()) + " executable element definitions normalized"});
558 if (invalidMaterials != 0)
559 findings.push_back({std::string(path), "TVE.elements.invalid-material", ImportDisposition::PreservedSource,
560 std::to_string(invalidMaterials) + " source elements have no executable material"});
561 return Result<Value>::success(Value(std::move(output)));
562}
563
564std::array<double, 4> multiply(std::array<double, 4> lhs, std::array<double, 4> rhs) {
565 return {lhs[3] * rhs[0] + lhs[0] * rhs[3] + lhs[1] * rhs[2] - lhs[2] * rhs[1],
566 lhs[3] * rhs[1] - lhs[0] * rhs[2] + lhs[1] * rhs[3] + lhs[2] * rhs[0],
567 lhs[3] * rhs[2] + lhs[0] * rhs[1] - lhs[1] * rhs[0] + lhs[2] * rhs[3],
568 lhs[3] * rhs[3] - lhs[0] * rhs[0] - lhs[1] * rhs[1] - lhs[2] * rhs[2]};
569}
570
571Result<Value> motionDirection(std::string_view source, std::string_view body) {
572 const auto manager = fileId(body, "m_GameObject").value_or(0);
573 const auto target = fileId(body, "mainDirection").value_or(0);
574 const auto gameObject = target == 0 ? manager : target;
575 const auto sceneTransforms = transforms(source);
576 auto found = std::find_if(sceneTransforms.begin(), sceneTransforms.end(),
577 [&](const auto& entry) { return entry.second.gameObject == gameObject; });
578 if (found == sceneTransforms.end())
580 "TVE motion direction Transform is missing", "mainDirection",
581 {}, "asset.import"));
582 auto rotation = found->second.rotation;
583 std::set<std::int64_t> visited{found->first};
584 auto parent = found->second.parent;
585 while (parent != 0) {
586 const auto ancestor = sceneTransforms.find(parent);
587 if (ancestor == sceneTransforms.end() || !visited.emplace(parent).second)
589 "TVE motion direction Transform hierarchy is invalid",
590 "mainDirection", {}, "asset.import"));
591 rotation = multiply(ancestor->second.rotation, rotation);
592 parent = ancestor->second.parent;
593 }
594 const double length = std::sqrt(rotation[0] * rotation[0] + rotation[1] * rotation[1] +
595 rotation[2] * rotation[2] + rotation[3] * rotation[3]);
596 if (!std::isfinite(length) || length <= 1e-12)
598 "TVE motion direction rotation is invalid", "mainDirection", {},
599 "asset.import"));
600 for (auto& value : rotation) value /= length;
601 const double unityX = 2.0 * (rotation[0] * rotation[2] + rotation[3] * rotation[1]);
602 const double unityZ = 1.0 - 2.0 * (rotation[0] * rotation[0] + rotation[1] * rotation[1]);
603 const double horizontal = std::hypot(unityX, unityZ);
604 if (!std::isfinite(horizontal) || horizontal <= 1e-12)
605 return Result<Value>::failure(Diagnostic::error(DiagnosticCode::ParseError, "TVE motion direction is vertical",
606 "mainDirection", {}, "asset.import"));
607 return Result<Value>::success(Value::array({-unityX / horizontal, -unityZ / horizontal}));
608}
609
610Result<Value> volumeChannel(std::string_view body, std::string_view name) {
611 std::cmatch legacy;
612 const std::regex legacyField("(?:^|\\n) " + std::string(name) + R"(: *(-?[0-9]+)\s*(?:\r?\n|$))");
613 if (std::regex_search(body.data(), body.data() + body.size(), legacy, legacyField)) {
614 try {
615 const auto resolution = static_cast<std::int64_t>(std::stoll(legacy[1].str()));
617 Value::array({Value(std::int64_t{10}), Value(resolution), Value(resolution)}));
618 } catch (...) {
620 "TVE legacy volume resolution is invalid",
621 std::string(name), {}, "asset.import"));
622 }
623 }
624 std::cmatch match;
625 const std::regex section("(?:^|\\n) " + std::string(name) + R"(:\s*\n([\s\S]*?)(?=\n [A-Za-z_][^\n]*:|$))");
626 if (!std::regex_search(body.data(), body.data() + body.size(), match, section))
627 return Result<Value>::failure(Diagnostic::error(DiagnosticCode::ParseError, "TVE volume channel is missing",
628 std::string(name), {}, "asset.import"));
629 const auto nested = match[1].str();
630 auto read = [&](std::string_view field) -> Result<double> {
631 std::cmatch value;
632 const std::regex pattern("(?:^|\\n) " + std::string(field) + R"(: *([^\r\n]+))");
633 if (!std::regex_search(nested.data(), nested.data() + nested.size(), value, pattern))
635 Diagnostic::error(DiagnosticCode::ParseError, "TVE volume channel field is missing",
636 std::string(name) + "." + std::string(field), {}, "asset.import"));
637 try {
638 std::size_t end = 0;
639 const auto token = value[1].str();
640 const auto decoded = std::stod(token, &end);
641 if (end != token.size() || !std::isfinite(decoded)) throw std::invalid_argument("number");
642 return Result<double>::success(decoded);
643 } catch (...) {
645 Diagnostic::error(DiagnosticCode::ParseError, "TVE volume channel field is invalid",
646 std::string(name) + "." + std::string(field), {}, "asset.import"));
647 }
648 };
649 auto mode = read("renderMode"), width = read("textureWidth"), height = read("textureHeight");
650 const bool modeOk = bool(mode), widthOk = bool(width), heightOk = bool(height);
651 if (!modeOk || !widthOk || !heightOk)
652 return Result<Value>::failure(!modeOk ? mode.status() : !widthOk ? width.status() : height.status());
653 return Result<Value>::success(Value::array({mode.value(), width.value(), height.value()}));
654}
655
656Result<Value> volume(std::string_view body) {
657 auto values = array(body, {{"renderScale", 1}, {"elementsVisibility", 0}, {"elementsSorting", 0},
658 {"elementsEdgeFade", .75}});
659 auto colors = volumeChannel(body, "renderColors"), extras = volumeChannel(body, "renderExtras"),
660 motion = volumeChannel(body, "renderMotion"), vertex = volumeChannel(body, "renderVertex");
661 const bool valuesOk = bool(values), colorsOk = bool(colors), extrasOk = bool(extras), motionOk = bool(motion),
662 vertexOk = bool(vertex);
663 if (!valuesOk || !colorsOk || !extrasOk || !motionOk || !vertexOk)
664 return Result<Value>::failure(!valuesOk ? values.status()
665 : !colorsOk ? colors.status()
666 : !extrasOk ? extras.status()
667 : !motionOk ? motion.status()
668 : vertex.status());
669 return Result<Value>::success(Value::object({{"values", std::move(values).takeValue()},
670 {"colors", std::move(colors).takeValue()},
671 {"extras", std::move(extras).takeValue()},
672 {"motion", std::move(motion).takeValue()},
673 {"vertex", std::move(vertex).takeValue()}}));
674}
675} // namespace
676
678 const UnitySourceAsset& source) {
679 const auto& bytes = request.files.at(source.path);
680 const std::string text(bytes.begin(), bytes.end());
681 const auto controlBody = component(text, controlGuid), detailsBody = component(text, detailsGuid),
682 motionBody = component(text, motionGuid), volumeBody = component(text, volumeGuid);
683 if (!controlBody && !detailsBody && !motionBody && !volumeBody)
685 Diagnostic::error(DiagnosticCode::Unsupported, "Unity scene has no TVE runtime manager components",
686 source.path, {}, "asset.import"));
687 if (!controlBody || !detailsBody || !motionBody || !volumeBody)
689 "TVE scene manager component set is incomplete",
690 source.path, {}, "asset.import"));
691 std::vector<ImportFinding> elementFindings;
692 auto controlValue = control(*controlBody), detailsValue = details(*detailsBody), motionValue = motion(*motionBody),
693 volumeValue = volume(*volumeBody), elementsValue = elements(text, elementFindings, source.path);
694 const bool controlOk = bool(controlValue), detailsOk = bool(detailsValue), motionOk = bool(motionValue),
695 volumeOk = bool(volumeValue), elementsOk = bool(elementsValue);
696 if (!controlOk || !detailsOk || !motionOk || !volumeOk || !elementsOk)
698 DiagnosticCode::ParseError, "TVE scene manager data is malformed", source.path, {}, "asset.import"));
699 auto directionValue = motionDirection(text, *motionBody);
700 if (!directionValue) return Result<PreparedAssetImport>::failure(directionValue.status());
701 (*motionValue.value().getIf<Value::Object>())["direction"] = std::move(directionValue).takeValue();
702 std::set<std::string> textureGuids;
703 auto boundTextures = bindTextureAssets(elementsValue.value(), request);
704 if (!boundTextures) return Result<PreparedAssetImport>::failure(boundTextures.status());
705 collectTextureGuids(elementsValue.value(), textureGuids);
706 for (const auto name : {"overlayAlbedo", "overlayNormal", "noiseTexture3D"}) {
707 auto value = guid(*controlBody, name);
708 if (!value.empty()) textureGuids.insert(std::move(value));
709 }
710 auto motionNoise = guid(*motionBody, "noiseTexture");
711 if (!motionNoise.empty()) textureGuids.insert(std::move(motionNoise));
712 Value definition = Value::object({{"schema", "eve.vegetation-scene"}, {"schemaVersion", 1},
713 {"sourceGuid", source.guid}, {"control", std::move(controlValue).takeValue()},
714 {"details", std::move(detailsValue).takeValue()},
715 {"motion", std::move(motionValue).takeValue()},
716 {"volume", std::move(volumeValue).takeValue()},
717 {"elements", std::move(elementsValue).takeValue()}});
718 auto encoded = definition.toJson();
719 if (!encoded) return Result<PreparedAssetImport>::failure(encoded.status());
720 auto manifest = detail::baseManifest(request.package, "eve.unity-vegetation-scene/1");
721 if (!manifest) return Result<PreparedAssetImport>::failure(manifest.status());
723 out.manifest = std::move(manifest).takeValue();
724 const auto id = request.package.packageId.child("unity:" + source.guid).child("vegetation-scene:default");
725 auto ref = detail::assetRef(id);
726 if (!ref) return Result<PreparedAssetImport>::failure(ref.status());
727 for (const auto& textureGuid : textureGuids) {
728 if (!importedImageAvailable(request, textureGuid)) continue;
729 auto image = detail::assetRef(request.package.packageId.child("unity:" + textureGuid).child("image:default"));
730 if (!image) return Result<PreparedAssetImport>::failure(image.status());
731 out.manifest.dependencies.push_back({ref.value(), image.value(), asset::EvaDependencyKind::RuntimeRequired,
732 "elementTexture:" + textureGuid, {}, "eve.image/3", {}});
733 }
734 const std::string path = "assets/" + id.format() + "/asset.json";
735 const std::vector<std::uint8_t> payload(encoded.value().begin(), encoded.value().end());
736 out.manifest.assets.push_back({ref.value(), "eve.vegetation-scene", SchemaVersion(1), path,
737 detail::sha256(payload), {"vegetation", "scene", "source:unity"}});
738 out.manifest.entrypoints.emplace("default", ref.value());
739 out.entries.push_back({path, payload});
740 out.sourceMappings.push_back({"TVE.Manager", ref.value()});
741 out.findings.push_back({source.path, "TVE.sceneManager", ImportDisposition::Translated,
742 "Global Control, Details, Motion and Volume serialized as native scene state"});
743 out.findings.insert(out.findings.end(), std::make_move_iterator(elementFindings.begin()),
744 std::make_move_iterator(elementFindings.end()));
745
746 auto hierarchy = prepareUnityPrefab(request, source.path);
747 if (!hierarchy) return Result<PreparedAssetImport>::failure(hierarchy.status());
748 const auto templateAsset = std::find_if(hierarchy.value().manifest.assets.begin(),
749 hierarchy.value().manifest.assets.end(), [](const auto& asset) {
750 return asset.type == "eve.scene-template";
751 });
752 if (templateAsset == hierarchy.value().manifest.assets.end())
754 DiagnosticCode::InvariantViolation, "Unity manager scene hierarchy produced no scene template", source.path,
755 {}, "asset.import"));
756 const AssetRef templateRef = templateAsset->asset;
757 out.manifest.dependencies.push_back({templateRef, ref.value(), asset::EvaDependencyKind::RuntimeRequired,
758 "vegetationScene", {}, "eve.vegetation-scene/1", {}});
759 out.manifest.entrypoints.emplace("scene", templateRef);
760 for (auto& asset : hierarchy.value().manifest.assets) out.manifest.assets.push_back(std::move(asset));
761 for (auto& dependency : hierarchy.value().manifest.dependencies)
762 out.manifest.dependencies.push_back(std::move(dependency));
763 for (auto& entry : hierarchy.value().entries) out.entries.push_back(std::move(entry));
764 for (auto& mapping : hierarchy.value().sourceMappings) out.sourceMappings.push_back(std::move(mapping));
765 for (auto& finding : hierarchy.value().findings) out.findings.push_back(std::move(finding));
766 return Result<PreparedAssetImport>::success(std::move(out));
767}
768} // namespace eve::asset_import
LogicalId target
double value
Value::Object payload
Duration start
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
double volume
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
float length
Definition CaveMesh.cpp:94
glm::vec4 p[6]
std::map< std::string, Var > values
std::string pattern
const GltfImportRequest & request
vk::UniqueImage image
wgpu::PopErrorScopeStatus status
glm::vec4 tint
std::array< double, 10 > q
float v
std::uint32_t height
std::uint32_t width
std::string text
TokenKind kind
std::string local
std::array< float, 4 > rotation
std::array< float, 3 > position
std::array< float, 3 > scale
std::int32_t parent
std::uint64_t bytes
std::string name
std::unordered_map< std::string, ShaderEntry > shaders
Definition OnnxGpgpu.cpp:44
World3D * world
std::string script
Definition PlayHost.cpp:116
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
float begin
Shader * shader
uint64_t token
std::vector< float > colors
double number
bool found
std::string string
std::size_t elements
std::size_t cursor
Heightmap global
std::string sourcePath
Json object
std::string body
std::int64_t fileId
Direct Unity text-serialization adapter for TerrainData and Prefab assets.
std::vector< ImportFinding > findings
uint32_t index
const UnitySourceAsset & source
std::int64_t gameObject
float highlight
Stable asset identity backed by PersistentId.
Definition ResourceRef.h:53
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
std::map< std::string, Value > Object
Definition Value.h:34
static Value array(Array value)
Compatibility factory for an array value.
Definition Value.h:112
std::vector< Value > Array
Definition Value.h:33
static Value object(Object value)
Compatibility factory for an object value.
Definition Value.h:114
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
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 > prepareUnityPrefab(const UnityProjectImportRequest &request, const std::string &path)
Prepare one prefab without copying or re-indexing the source collection.
Result< PreparedAssetImport > prepareUnityVegetationScene(const UnityProjectImportRequest &request, const UnitySourceAsset &source)
Convert a complete TVE manager component set from one Unity scene.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
const EditorValue * field(const EditorValue &value, const char *name)
int axis(int64_t a, size_t rank)
Axis.
T read(const RuntimeTensor &v, size_t i)
Reads read.
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
Definition Widget.cpp:635
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
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.
Immutable-by-convention owning source index entry; no borrowed source addresses.
Definition UnitySource.h:38
bool consumed
Definition Graphics.cpp:184
glm::vec4 color