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";
19struct SceneComponent {
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\})");
31 const auto body = (*it)[2].str();
32 if (!std::regex_search(
body,
script))
continue;
34 result.push_back({std::stoll((*it)[1].str()),
body});
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\})");
47 const auto body = (*it)[1].str();
48 if (std::regex_search(
body, script))
return body;
53Result<double>
number(std::string_view
body, std::string_view
name,
double fallback) {
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))
60 const auto token = match[1].str();
62 if (
end !=
token.size() || !std::isfinite(
value))
throw std::invalid_argument(
"number");
66 std::string(
name), {},
"asset.import"));
70Result<Value> array(std::string_view
body,
71 std::initializer_list<std::pair<std::string_view, double>>
fields) {
76 result.emplace_back(
value.value());
81Result<Value>
color(std::string_view
body, std::string_view
name, std::array<double, 4> fallback) {
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))
93 if (
end !=
token.size() || !std::isfinite(
value))
throw std::invalid_argument(
"color");
94 result.emplace_back(
value);
97 std::string(
name), {},
"asset.import"));
103std::string guid(std::string_view
body, std::string_view
name) {
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)
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}});
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)
130 auto highlightValue = std::move(
highlight).takeValue();
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()}}));
140Result<Value> control(std::string_view
body) {
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)
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")}}));
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}});
166 {
"noiseTextureGuid", guid(
body,
"noiseTexture")}}));
169struct SceneTransform {
176std::optional<std::int64_t>
fileId(std::string_view
body, std::string_view
name) {
177 const std::string
field =
" " + std::string(
name) +
":";
181 if (
position == std::string_view::npos)
return {};
183 if (
position == std::string_view::npos)
return {};
198std::optional<double> rotationComponent(std::string_view
line, std::string_view
name) {
199 const std::string
field = std::string(
name) +
":";
201 if (
position == std::string_view::npos)
return {};
216std::optional<std::array<double, 3>> vector3Field(std::string_view
body, std::string_view
name) {
217 const std::string
field =
" " + std::string(
name) +
":";
219 if (
start == std::string_view::npos)
return {};
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};
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 &";
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:")) {
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;
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) {
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);
276struct WorldTransform {
278 std::array<double, 4>
rotation{0, 0, 0, 1};
281std::array<double, 4> multiply(std::array<double, 4> lhs, std::array<double, 4> rhs);
283Result<std::array<double, 4>> normalized(std::array<double, 4>
value) {
290 return Result<std::array<double, 4>>::success(
value);
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]};
300Result<WorldTransform> worldTransform(
const std::map<std::int64_t, SceneTransform>& sceneTransforms,
302 auto found = std::find_if(sceneTransforms.begin(), sceneTransforms.end(),
303 [&](
const auto& entry) { return entry.second.gameObject == gameObject; });
304 if (
found == sceneTransforms.end())
307 std::vector<const SceneTransform*> chain{&
found->second};
308 std::set<std::int64_t> visited{
found->first};
311 const auto ancestor = sceneTransforms.find(
parent);
312 if (ancestor == sceneTransforms.end() || !visited.emplace(
parent).second)
315 chain.push_back(&ancestor->second);
316 parent = ancestor->second.parent;
318 WorldTransform
world;
319 for (
auto it = chain.rbegin(); it != chain.rend(); ++it) {
320 auto localRotation = normalized((*it)->rotation);
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]};
325 for (std::size_t axis = 0;
axis < 3; ++
axis) {
329 auto composed = normalized(multiply(
world.rotation, localRotation.value()));
331 world.rotation = std::move(composed).takeValue();
339std::optional<std::string_view> propertyBlock(std::string_view
body, std::string_view
name) {
340 const std::string marker =
" prop: " + std::string(
name);
342 if (
begin == std::string_view::npos)
return {};
344 if (
begin == std::string_view::npos)
return {};
346 if (
end == std::string_view::npos)
end =
body.size();
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);
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]});
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{};
369Result<Value> materialProperties(std::string_view
body) {
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|$))");
375 const auto next =
body.find(
"\n - type:",
cursor + marker.size());
378 if (!std::regex_search(block.data(), block.data() + block.size(),
name, namePattern))
382 auto vector =
color(block,
" vector", {0, 0, 0, 0});
386 "TVE element property value is malformed",
name[1].str(),
387 {},
"asset.import"));
389 {
"textureGuid", guid(block,
" texture")},
390 {
"vector", std::move(vector).takeValue()}, {
"value",
value.value()}}));
399void collectTextureGuids(
const Value&
value, std::set<std::string>&
output) {
403 collectTextureGuids(child,
output);
406 for (
const auto& child : *array) collectTextureGuids(
child,
output);
410bool importedImageAvailable(
const UnityProjectImportRequest&
request, std::string_view targetGuid) {
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;
420 if (extension ==
".png" || extension ==
".tga" || extension ==
".tif" || extension ==
".tiff")
return true;
421 if (extension !=
".asset")
return false;
423 return text.find(
"\nTexture2D:") != std::string::npos;
428Result<void> bindTextureAssets(Value&
value,
const UnityProjectImportRequest&
request) {
430 const auto guidValue =
object->find(
"textureGuid");
431 if (guidValue !=
object->end() && guidValue->second.isString()) {
433 if (!guidValue->second.asString().empty() && importedImageAvailable(
request, guidValue->second.asString())) {
439 object->emplace(
"textureAsset", std::move(asset));
443 auto bound = bindTextureAssets(child,
request);
444 if (!bound)
return bound;
447 for (
auto& child : *array) {
448 auto bound = bindTextureAssets(child,
request);
449 if (!bound)
return bound;
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}},
477 const auto sceneTransforms = transforms(
source);
479 std::size_t invalidMaterials = 0;
480 for (
const auto& component : components(
source, elementGuid)) {
484 const auto shader = guid(component.body,
" 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);
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)));
504 const auto seasonal = materialNumber(component.body,
"_ElementMode", 0) >= .5;
507 if (
kind ==
"color-tint")
508 seasons.push_back(materialVector(component.body,
"_AdditionalColor" + std::to_string(
index),
main));
510 const auto value = materialNumber(component.body,
"_AdditionalValue" + std::to_string(
index), 1);
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)
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});
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)}}));
557 std::to_string(
output.size()) +
" executable element definitions normalized"});
558 if (invalidMaterials != 0)
560 std::to_string(invalidMaterials) +
" source elements have no executable material"});
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]};
571Result<Value> motionDirection(std::string_view
source, std::string_view
body) {
572 const auto manager =
fileId(
body,
"m_GameObject").value_or(0);
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"));
583 std::set<std::int64_t> visited{
found->first};
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"));
592 parent = ancestor->second.parent;
598 "TVE motion direction rotation is invalid",
"mainDirection", {},
603 const double horizontal = std::hypot(unityX, unityZ);
604 if (!std::isfinite(horizontal) || horizontal <= 1e-12)
606 "mainDirection", {},
"asset.import"));
610Result<Value> volumeChannel(std::string_view
body, std::string_view
name) {
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)) {
615 const auto resolution =
static_cast<std::int64_t
>(std::stoll(legacy[1].str()));
620 "TVE legacy volume resolution is invalid",
621 std::string(
name), {},
"asset.import"));
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))
628 std::string(
name), {},
"asset.import"));
629 const auto nested = match[1].str();
630 auto read = [&](std::string_view
field) -> Result<double> {
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))
636 std::string(
name) +
"." + std::string(field), {},
"asset.import"));
640 const auto decoded = std::stod(
token, &
end);
641 if (
end !=
token.size() || !std::isfinite(decoded))
throw std::invalid_argument(
"number");
646 std::string(
name) +
"." + std::string(field), {},
"asset.import"));
650 const bool modeOk = bool(mode), widthOk = bool(
width), heightOk = bool(
height);
651 if (!modeOk || !widthOk || !heightOk)
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)
666 : !extrasOk ? extras.
status()
667 : !motionOk ? motion.
status()
670 {
"colors", std::move(
colors).takeValue()},
671 {
"extras", std::move(extras).takeValue()},
672 {
"motion", std::move(motion).takeValue()},
673 {
"vertex", std::move(vertex).takeValue()}}));
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)
687 if (!controlBody || !detailsBody || !motionBody || !volumeBody)
689 "TVE scene manager component set is incomplete",
691 std::vector<ImportFinding> elementFindings;
692 auto controlValue = control(*controlBody), detailsValue = details(*detailsBody), motionValue = motion(*motionBody),
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)
699 auto directionValue = motionDirection(
text, *motionBody);
701 (*motionValue.value().getIf<
Value::Object>())[
"direction"] = std::move(directionValue).takeValue();
702 std::set<std::string> textureGuids;
703 auto boundTextures = bindTextureAssets(elementsValue.value(),
request);
705 collectTextureGuids(elementsValue.value(), textureGuids);
706 for (
const auto name : {
"overlayAlbedo",
"overlayNormal",
"noiseTexture3D"}) {
708 if (!
value.empty()) textureGuids.insert(std::move(
value));
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();
723 out.
manifest = std::move(manifest).takeValue();
727 for (
const auto& textureGuid : textureGuids) {
728 if (!importedImageAvailable(
request, textureGuid))
continue;
732 "elementTexture:" + textureGuid, {},
"eve.image/3", {}});
734 const std::string
path =
"assets/" +
id.format() +
"/asset.json";
735 const std::vector<std::uint8_t>
payload(encoded.value().begin(), encoded.value().end());
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()));
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";
752 if (templateAsset == hierarchy.value().manifest.assets.end())
755 {},
"asset.import"));
756 const AssetRef templateRef = templateAsset->asset;
758 "vegetationScene", {},
"eve.vegetation-scene/1", {}});
760 for (
auto& asset : hierarchy.value().manifest.assets) out.
manifest.
assets.push_back(std::move(asset));
761 for (
auto& dependency : hierarchy.value().manifest.dependencies)
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));
AuthorityStoreHandleRef reference
std::map< std::string, Var > values
const GltfImportRequest & request
wgpu::PopErrorScopeStatus status
std::array< double, 10 > q
std::array< float, 4 > rotation
std::array< float, 3 > position
std::array< float, 3 > scale
std::unordered_map< std::string, ShaderEntry > shaders
std::vector< std::string > fields
std::vector< float > colors
Direct Unity text-serialization adapter for TerrainData and Prefab assets.
std::vector< ImportFinding > findings
const UnitySourceAsset & source
Stable asset identity backed by PersistentId.
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.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
The canonical owning dynamic value used by data-facing protocols.
std::map< std::string, Value > Object
static Value array(Array value)
Compatibility factory for an array value.
std::vector< Value > Array
static Value object(Object value)
Compatibility factory for an object value.
Id128 child(std::string_view role) const noexcept
Derives a deterministic child UUID for a hierarchical identity.
int main(int argc, char **argv)
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
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
double dot(const Vec2 &a, const Vec2 &b)
Dot.
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.
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
std::vector< EvaAssetEntry > assets
std::vector< EvaDependency > dependencies
std::map< std::string, AssetRef > entrypoints
ImportPackageIdentity package
Owning, validated source archive candidate not yet published to disk/database.
std::vector< ImportFinding > findings
std::vector< asset::EvaArchiveEntry > entries
std::vector< ImportSourceMapping > sourceMappings
asset::EvaManifest manifest
In-memory Unity project slice; keys are normalized project-relative paths.
Immutable-by-convention owning source index entry; no borrowed source addresses.