载入中...
搜索中...
未找到
UnityVegetationMaterial.cpp
浏览该文件的文档.
1#include <array>
2#include <cmath>
3#include <limits>
4#include <regex>
6#include "asset/SourcePng.h"
10
11namespace eve::asset_import {
12namespace {
13struct Invalid {};
14std::optional<std::string> match(const std::string& s, const std::string& pattern) {
15 const std::regex expression(pattern);
16 auto it = std::sregex_iterator(s.begin(), s.end(), expression);
17 if (it == std::sregex_iterator()) return {};
18 const auto result = (*it)[1].str();
19 if (++it != std::sregex_iterator()) throw Invalid{};
20 return result;
21}
22double numeric(const Value& value) {
23 if (!value.isNumeric()) throw Invalid{};
24 const double n = value.isInt64() ? double(value.asInt()) : value.asDouble();
25 if (!std::isfinite(n) || std::abs(n) > std::numeric_limits<float>::max()) throw Invalid{};
26 return n;
27}
28double scalar(const std::string& text, const std::string& name, double fallback) {
29 const auto raw = match(text, "(?:^|\\n)\\s*- " + name + ": *([^\\r\\n]+)");
30 if (!raw) return fallback;
31 auto decoded = Value::fromJson(*raw);
32 if (!decoded) throw Invalid{};
33 return numeric(decoded.value());
34}
35double importerScalar(const std::string& text, const std::string& name, double fallback) {
36 const auto raw = match(text, "(?:^|\\n)[ \\t]*" + name + ": *([^\\r\\n]+)");
37 if (!raw) return fallback;
38 auto decoded = Value::fromJson(*raw);
39 if (!decoded) throw Invalid{};
40 return numeric(decoded.value());
41}
42std::array<double, 4> vector(const std::string& text, const std::string& name, std::array<double, 4> fallback) {
43 auto raw = match(text, "(?:^|\\n)\\s*- " + name + ": *(\\{[^\\r\\n]+\\})");
44 if (!raw) return fallback;
45 auto value = Value::fromJson(std::regex_replace(*raw, std::regex("([rgba]):"), "\"$1\":"));
46 if (!value || !value.value().isObject()) throw Invalid{};
47 const auto& object = *value.value().getIf<Value::Object>();
48 unsigned i = 0;
49 for (const auto key : {"r", "g", "b", "a"}) {
50 if (!object.contains(key)) throw Invalid{};
51 fallback[i++] = numeric(object.at(key));
52 }
53 return fallback;
54}
55std::optional<std::string> texture(const std::string& text, const std::string& name) {
56 auto block = match(text, "- " + name + ":([\\s\\S]*?)(?:\\n - |\\n m_|$)");
57 if (!block) return {};
58 if (match(*block, R"(m_Texture: *\{fileID: *(0)\})")) return {};
59 auto guid = match(*block, R"(m_Texture: *\{fileID: *2800000, guid: *([0-9a-fA-F]{32}), type: *[23]\})");
60 if (!guid) throw Invalid{};
61 return unity_detail::foldAscii(*guid);
62}
63Result<std::string> texturePath(const UnityProjectImportRequest& request, const std::string& guid) {
64 std::string found;
65 for (const auto& [path, bytes] : request.files)
66 if (path.ends_with(".meta")) {
67 auto id = unity_detail::metaScalar(bytes, "guid", path);
68 if (!id) return Result<std::string>::failure(id.status());
69 if (unity_detail::foldAscii(id.value()) != guid) continue;
70 if (!found.empty())
72 Diagnostic::error(DiagnosticCode::Conflict, "duplicate texture GUID", guid, {}, "asset.import"));
73 found = path.substr(0, path.size() - 5);
74 }
75 if (found.empty() || !request.files.contains(found))
77 Diagnostic::error(DiagnosticCode::NotFound, "TVE texture source is absent", guid, {}, "asset.import"));
78 return Result<std::string>::success(std::move(found));
79}
80Result<Value::Object> textureSampler(const UnityProjectImportRequest& request, const std::string& path) {
81 const bool native = path.ends_with(".asset");
82 const auto& bytes = request.files.at(native ? path : path + ".meta");
83 const std::string settings(bytes.begin(), bytes.end());
84 const auto u = importerScalar(settings, native ? "m_WrapU" : "wrapU", -1);
85 const auto v = importerScalar(settings, native ? "m_WrapV" : "wrapV", -1);
86 for (const auto wrap : {u, v}) {
87 if (wrap != -1 && wrap != 0 && wrap != 1 && wrap != 2 && wrap != 3) throw Invalid{};
88 if (wrap == 3)
90 DiagnosticCode::Unsupported, "texture MirrorOnce addressing is not supported by the canonical sampler",
91 path, {}, "asset.import"));
92 }
93 auto convert = [](double wrap) { return wrap == 1 ? 33071 : wrap == 2 ? 33648 : 10497; };
94 auto filter = importerScalar(settings, native ? "m_FilterMode" : "filterMode", -1);
95 if (filter != -1 && filter != 0 && filter != 1 && filter != 2) throw Invalid{};
96 if (filter == -1) {
97 if (native) return Result<Value::Object>::success({{"wrapS", convert(u)}, {"wrapT", convert(v)}});
98 const auto heightNormal = importerScalar(settings, "convertToNormalMap", 0);
99 if (heightNormal != 0 && heightNormal != 1) throw Invalid{};
100 filter = heightNormal == 1 ? 2 : 1;
101 }
102 const auto mipSetting = importerScalar(settings, native ? "m_MipCount" : "enableMipMap", 1);
103 if (native ? (mipSetting < 1 || mipSetting != std::floor(mipSetting)) : (mipSetting != 0 && mipSetting != 1))
104 throw Invalid{};
105 const bool mipmaps = native ? mipSetting > 1 : mipSetting == 1;
106 const int mag = filter == 0 ? 9728 : 9729;
107 const int min = !mipmaps ? mag : filter == 0 ? 9984 : filter == 2 ? 9987 : 9985;
109 {{"wrapS", convert(u)}, {"wrapT", convert(v)}, {"minFilter", min}, {"magFilter", mag}});
110}
111Result<PreparedAssetImport> ormImage(const UnityProjectImportRequest& request, const std::string& text,
112 const PersistentId& materialId, double occlusion, double smoothness) {
113 std::vector<std::uint8_t> rgba{255, 255, 255, 255};
114 std::uint32_t width = 1, height = 1;
115 auto identity = request.package;
116 identity.packageId = materialId.child("main-orm");
117 if (auto guid = texture(text, "_MainMaskTex")) {
118 auto path = texturePath(request, *guid);
119 if (!path) return Result<PreparedAssetImport>::failure(path.status());
121 {identity, path.value(), request.files.at(path.value()), ImageColorSpace::Linear, "mask", request.limits});
122 if (!imported) return imported;
123 const auto definition = imported.value().manifest.assets.front().definition;
124 for (const auto& entry : imported.value().entries)
125 if (entry.path == definition) {
126 auto cooked = asset::cookCanonicalImageRgba8(entry.bytes, request.files.at(path.value()),
128 if (!cooked) return Result<PreparedAssetImport>::failure(cooked.status());
129 auto read32 = [&](std::size_t p) {
130 const auto& b = cooked.value().bulk;
131 return std::uint32_t(b[p]) | (std::uint32_t(b[p + 1]) << 8) | (std::uint32_t(b[p + 2]) << 16) |
132 (std::uint32_t(b[p + 3]) << 24);
133 };
134 width = read32(8);
135 height = read32(12);
136 rgba.assign(cooked.value().bulk.begin() + 28, cooked.value().bulk.end());
137 }
138 }
139 for (std::size_t p = 0; p < rgba.size(); p += 4) {
140 const auto ao = 255.0 + (double(rgba[p + 1]) - 255.0) * occlusion;
141 const auto roughness = 255.0 - double(rgba[p + 3]) * smoothness;
142 const auto mask = rgba[p + 2];
143 rgba[p] = std::uint8_t(std::lround(ao));
144 rgba[p + 1] = std::uint8_t(std::lround(roughness));
145 rgba[p + 2] = 0;
146 rgba[p + 3] = mask;
147 }
149 if (!png) return Result<PreparedAssetImport>::failure(png.status());
150 return prepareImageImport(
151 {identity, "TVE main ORM", std::move(png).takeValue(), ImageColorSpace::Linear, "orm", request.limits});
152}
153enum class NormalSource { Authored, Height };
154uint32_t normalExtent(uint32_t extent, int mode) {
155 if (mode == 0 || (extent & (extent - 1)) == 0) return extent;
156 uint32_t lower = 1;
157 while (lower <= extent / 2) lower *= 2;
158 if (lower > UINT32_MAX / 2) throw Invalid{};
159 const auto upper = lower * 2;
160 return mode == 3 || (mode == 1 && extent - lower < upper - extent) ? lower : upper;
161}
162struct NormalResizeTap {
163 uint32_t index;
164 double weight;
165};
166std::vector<NormalResizeTap> normalResizeTaps(uint32_t source, uint32_t target, uint32_t coordinate, int algorithm) {
167 if (source == target) return {{coordinate, 1}};
168 const double center = algorithm == 1 && target > source ? double(coordinate) * (source - 1) / (target - 1)
169 : (coordinate + .5) * source / target - .5;
170 std::vector<NormalResizeTap> taps;
171 auto append = [&](int64_t index, double weight) {
172 taps.push_back({uint32_t(std::clamp(index, int64_t(0), int64_t(source - 1))), weight});
173 };
174 if (algorithm == 1) {
175 const auto first = int64_t(std::floor(center));
176 append(first, 1 - (center - first));
177 append(first + 1, center - first);
178 return taps;
179 }
180 const double scale = std::max(double(source) / target, 1.0);
181 const double b = target > source ? 1.0 : 1.0 / 3.0;
182 const double c = target > source ? 0.0 : 1.0 / 3.0;
183 double sum = 0;
184 for (auto index = int64_t(std::floor(center - 2 * scale)); index <= int64_t(std::ceil(center + 2 * scale));
185 ++index) {
186 const double x = std::abs((center - index) / scale);
187 const double weight =
188 x < 1 ? ((12 - 9 * b - 6 * c) * x * x * x + (-18 + 12 * b + 6 * c) * x * x + 6 - 2 * b) / 6
189 : x < 2
190 ? ((-b - 6 * c) * x * x * x + (6 * b + 30 * c) * x * x + (-12 * b - 48 * c) * x + 8 * b + 24 * c) / 6
191 : 0;
192 append(index, weight);
193 sum += weight;
194 }
195 for (auto& tap : taps) tap.weight /= sum;
196 return taps;
197}
198std::vector<double> normalResizeRgb(const std::vector<uint8_t>& bulk, uint32_t width, uint32_t height,
199 uint32_t targetWidth, uint32_t targetHeight, int algorithm) {
200 std::vector<double> rgb(size_t(targetWidth) * targetHeight * 3);
201 for (uint32_t y = 0; y < targetHeight; ++y) {
202 const auto vertical = normalResizeTaps(height, targetHeight, y, algorithm);
203 for (uint32_t x = 0; x < targetWidth; ++x) {
204 const auto horizontal = normalResizeTaps(width, targetWidth, x, algorithm);
205 std::array<double, 3> value{};
206 for (const auto& sy : vertical)
207 for (const auto& sx : horizontal)
208 for (size_t c = 0; c < 3; ++c)
209 value[c] += bulk[28 + (size_t(sy.index) * width + sx.index) * 4 + c] * sy.weight * sx.weight;
210 for (size_t c = 0; c < 3; ++c) {
211 const auto clamped = std::clamp(value[c], 0.0, 255.0);
212 // Unity's default cubic path quantizes after both axes. Convert
213 // to float before rounding to preserve its half-integer boundary.
214 rgb[(size_t(y) * targetWidth + x) * 3 + c] =
215 algorithm == 0 ? double(std::lround(float(clamped))) / 255.0 : clamped / 255.0;
216 }
217 }
218 }
219 return rgb;
220}
221Result<PreparedAssetImport> normalImage(const UnityProjectImportRequest& request, const std::string& path,
222 const PersistentId& materialId, double strength, bool sobel, bool mipmaps,
223 bool flipGreen, NormalSource source) {
224 const bool fromHeight = source == NormalSource::Height;
225 const size_t channels = fromHeight ? 1 : 2;
226 auto identity = request.package;
227 identity.packageId = materialId.child(fromHeight ? "main-height-normal" : "main-authored-normal");
228 auto image = prepareImageImport({identity, path, request.files.at(path), ImageColorSpace::Linear,
229 fromHeight ? "height" : "normal", request.limits});
230 if (!image) return image;
231 const auto definition = image.value().manifest.assets.front().definition;
232 for (const auto& entry : image.value().entries) {
233 if (entry.path != definition) continue;
234 auto cooked =
236 if (!cooked) return Result<PreparedAssetImport>::failure(cooked.status());
237 const auto& bulk = cooked.value().bulk;
238 auto read32 = [&](std::size_t p) {
239 return std::uint32_t(bulk[p]) | (std::uint32_t(bulk[p + 1]) << 8) | (std::uint32_t(bulk[p + 2]) << 16) |
240 (std::uint32_t(bulk[p + 3]) << 24);
241 };
242 auto width = read32(8), height = read32(12);
243 const auto& metaBytes = request.files.at(path + ".meta");
244 const std::string meta(metaBytes.begin(), metaBytes.end());
245 if (mipmaps && importerScalar(meta, "mipMapMode", 0) != 0)
247 DiagnosticCode::Unsupported, "normal Kaiser mips are not implemented", path, {}, "asset.import"));
248 const auto npot = importerScalar(meta, "nPOTScale", 1);
249 if (npot < 0 || npot > 3 || npot != std::floor(npot)) throw Invalid{};
250 const auto targetWidth = normalExtent(width, int(npot)), targetHeight = normalExtent(height, int(npot));
251 std::vector<double> resized;
252 int resizeAlgorithm = 0;
253 if (targetWidth != width || targetHeight != height) {
254 const std::regex algorithms(R"((?:^|\n)[ \t]*resizeAlgorithm: *([^\r\n]+))");
255 std::optional<int> selected;
256 for (auto it = std::sregex_iterator(meta.begin(), meta.end(), algorithms); it != std::sregex_iterator();
257 ++it) {
258 auto value = Value::fromJson((*it)[1].str());
259 if (!value) throw Invalid{};
260 const auto algorithm = numeric(value.value());
261 if (algorithm != 0 && algorithm != 1) throw Invalid{};
262 if (selected && *selected != int(algorithm))
265 "platform-specific normal resize algorithms require target-specific image variants", path, {},
266 "asset.import"));
267 selected = int(algorithm);
268 }
269 resizeAlgorithm = selected.value_or(0);
270 if (uint64_t(targetWidth) * targetHeight > request.limits.maximumDecodedBytes / sizeof(double) / 3)
272 Diagnostic::error(DiagnosticCode::InvalidArgument, "resized normal RGB staging exceeds budget",
273 path, {}, "asset.import"));
274 resized = normalResizeRgb(bulk, width, height, targetWidth, targetHeight, resizeAlgorithm);
275 width = targetWidth;
276 height = targetHeight;
277 }
278 uint32_t w = width, h = height, levels = 0;
279 uint64_t total = 0;
280 do {
281 const uint64_t bytes = uint64_t(w) * h * 4;
285 DiagnosticCode::InvalidArgument, "normal mip chain exceeds budget", path, {}, "asset.import"));
286 total += bytes;
287 ++levels;
288 if (!mipmaps || (w == 1 && h == 1)) break;
289 w = std::max(w / 2, 1u);
290 h = std::max(h / 2, 1u);
291 } while (true);
292 if (total > request.limits.maximumSourceBytes ||
293 uint64_t(width) * height > request.limits.maximumDecodedBytes / sizeof(double) / channels)
295 Diagnostic::error(DiagnosticCode::InvalidArgument, "normal source or channel staging exceeds budget",
296 path, {}, "asset.import"));
297 std::vector<double> heights(size_t(width) * height * channels);
298 for (size_t i = 0; i < size_t(width) * height; ++i) {
299 if (!resized.empty()) {
300 if (fromHeight)
301 heights[i] = (resized[i * 3] + resized[i * 3 + 1] + resized[i * 3 + 2]) / 3;
302 else {
303 heights[i * 2] = resized[i * 3];
304 heights[i * 2 + 1] = resized[i * 3 + 1];
305 }
306 continue;
307 }
308 if (fromHeight)
309 heights[i] = (double(bulk[28 + i * 4]) + bulk[29 + i * 4] + bulk[30 + i * 4]) / 765.0;
310 else {
311 heights[i * 2] = double(bulk[28 + i * 4]) / 255.0;
312 heights[i * 2 + 1] = double(bulk[29 + i * 4]) / 255.0;
313 }
314 }
315 std::vector<uint8_t> rgba;
316 rgba.reserve(size_t(total));
317 w = width;
318 h = height;
319 for (uint32_t level = 0; level < levels; ++level) {
320 auto sample = [&](int64_t x, int64_t y) {
321 x = (x + w) % w;
322 y = (y + h) % h;
323 return heights[size_t(y) * w + size_t(x)];
324 };
325 const double factor = .04 * (double(w) + h) * (std::pow(10.0, strength) - 1.0);
326 for (int64_t y = 0; y < h; ++y) {
327 for (int64_t x = 0; x < w; ++x) {
328 if (!fromHeight) {
329 const auto at = (size_t(y) * w + size_t(x)) * 2;
330 const auto green = flipGreen ? 1.0 - heights[at + 1] : heights[at + 1];
331 rgba.insert(rgba.end(), {uint8_t(std::lround(heights[at] * 255)),
332 uint8_t(std::lround(green * 255)), 255, 255});
333 continue;
334 }
335 double dx = sample(x - 1, y) - sample(x + 1, y);
336 double dy = sample(x, y + 1) - sample(x, y - 1);
337 if (sobel) {
338 dx = (sample(x - 1, y - 1) - sample(x + 1, y - 1) + 2 * dx + sample(x - 1, y + 1) -
339 sample(x + 1, y + 1)) *
340 .25;
341 dy = (sample(x - 1, y + 1) - sample(x - 1, y - 1) + 2 * dy + sample(x + 1, y + 1) -
342 sample(x + 1, y - 1)) *
343 .25;
344 }
345 dx *= factor;
346 dy *= flipGreen ? -factor : factor;
347 const double length = std::sqrt(dx * dx + dy * dy + 1.0);
348 rgba.insert(rgba.end(), {uint8_t(std::lround((dx / length * .5 + .5) * 255)),
349 uint8_t(std::lround((dy / length * .5 + .5) * 255)), 255, 255});
350 }
351 }
352 if (level + 1 == levels) break;
353 const uint32_t nextW = std::max(w / 2, 1u), nextH = std::max(h / 2, 1u);
354 std::vector<double> next(size_t(nextW) * nextH * channels);
355 for (uint32_t y = 0; y < nextH; ++y) {
356 for (uint32_t x = 0; x < nextW; ++x) {
357 const double sx = (x + .5) * w / nextW - .5, sy = (y + .5) * h / nextH - .5;
358 const auto ix = uint32_t(std::floor(sx)), iy = uint32_t(std::floor(sy));
359 const double fx = sx - ix, fy = sy - iy;
360 const auto x1 = std::min(ix + 1, w - 1), y1 = std::min(iy + 1, h - 1);
361 for (size_t c = 0; c < channels; ++c)
362 next[(size_t(y) * nextW + x) * channels + c] =
363 (heights[(size_t(iy) * w + ix) * channels + c] * (1 - fx) +
364 heights[(size_t(iy) * w + x1) * channels + c] * fx) *
365 (1 - fy) +
366 (heights[(size_t(y1) * w + ix) * channels + c] * (1 - fx) +
367 heights[(size_t(y1) * w + x1) * channels + c] * fx) *
368 fy;
369 }
370 }
371 heights = std::move(next);
372 w = nextW;
373 h = nextH;
374 }
375 auto parsed = Value::fromJson(std::string(entry.bytes.begin(), entry.bytes.end()));
376 if (!parsed) return Result<PreparedAssetImport>::failure(parsed.status());
377 auto& object = *parsed.value().getIf<Value::Object>();
378 const auto oldBlob = object.at("blob").asString();
379 const auto newBlob = definition.substr(0, definition.rfind('/') + 1) + "normal.rgba8-mips";
380 object["encoding"] = Value("rgba8-mips");
381 object["blob"] = Value(newBlob);
382 object["mipCount"] = Value(int64_t(levels));
383 object["width"] = Value(int64_t(width));
384 object["height"] = Value(int64_t(height));
385 object["usage"] = Value("normal");
386 auto encoded = parsed.value().toJson();
387 if (!encoded) return Result<PreparedAssetImport>::failure(encoded.status());
388 auto result = std::move(image).takeValue();
389 for (auto& output : result.entries) {
390 if (output.path == definition)
391 output.bytes.assign(encoded.value().begin(), encoded.value().end());
392 else if (output.path == oldBlob) {
393 output.path = newBlob;
394 output.bytes = std::move(rgba);
395 }
396 }
397 auto& asset = result.manifest.assets.front();
398 asset.contentHash =
399 detail::sha256({reinterpret_cast<const uint8_t*>(encoded.value().data()), encoded.value().size()});
400 asset.tags = {"image", "usage:normal"};
401 std::erase_if(result.entries, [](const auto& output) { return output.path == "reports/import.json"; });
402 result.findings = {
403 {path, fromHeight ? "TextureImporter.HeightNormal" : "TextureImporter.AuthoredNormal",
405 fromHeight ? "height resampled and normals regenerated independently per mip"
406 : "linear source RG resampled independently per mip before green flip and quantization"}};
407 auto report = detail::finalizeImportReport(result, identity, "TVE", "12.6.0",
408 {{"fromHeight", Value(fromHeight)},
409 {"npotScale", Value(int64_t(npot))},
410 {"resizeAlgorithm", Value(int64_t(resizeAlgorithm))},
411 {"heightScale", Value(strength)},
412 {"sobel", Value(sobel)},
413 {"flipGreen", Value(flipGreen)},
414 {"mipCount", Value(int64_t(levels))}});
415 if (!report) return Result<PreparedAssetImport>::failure(report.status());
416 return Result<PreparedAssetImport>::success(std::move(result));
417 }
419 Diagnostic::error(DiagnosticCode::NotFound, "normal image definition absent", path, {}, "asset.import"));
420}
421} // namespace
422
424 const UnitySourceAsset& source) {
425 const auto& bytes = request.files.at(source.path);
426 const std::string text(bytes.begin(), bytes.end());
427 try {
428 const auto shader = match(text, R"(m_Shader: *\{fileID: *4800000, guid: *([0-9a-fA-F]{32}), type: *3\})");
429 if (!shader ||
430 (*shader != "7befaa6f41d00a6478d5f4af21d66518" && *shader != "a933075b367f9b24981408633f72ff34" &&
431 *shader != "6e6307b56f9201d40ad738f21cf03495" && *shader != "d9a724745053dee46bf301b216cdd348"))
433 DiagnosticCode::Unsupported, "shader is not an admitted TVE 12.6.0 Plant/Prop material", source.path,
434 {}, "asset.import"));
435 auto unit = [](double v) {
436 if (v < 0 || v > 1) throw Invalid{};
437 return v;
438 };
439 const auto mode = scalar(text, "_RenderMode", 0), cull = scalar(text, "_RenderCull", 0),
440 clip = scalar(text, "_RenderClip", 1), coverage = scalar(text, "_RenderCoverage", 0),
441 specular = scalar(text, "_RenderSpecular", 1);
442 if ((mode != 0 && mode != 1) || cull < 0 || cull > 2 || cull != std::floor(cull) ||
443 (clip != 0 && clip != 1) || (coverage != 0 && coverage != 1))
445 DiagnosticCode::InvalidArgument, "TVE render mode, alpha clipping, or cull mode is invalid",
446 source.path, {}, "asset.import"));
447 auto color = vector(text, "_MainColor", {1, 1, 1, 1});
448 auto second = vector(text, "_MainColorTwo", {1, 1, 1, 1});
449 const auto uv = vector(text, "_MainUVs", {1, 1, 0, 0});
450 for (unsigned c = 0; c < 4; ++c)
451 if (color[c] < 0 || second[c] < 0 || (c == 3 && (color[c] > 1 || second[c] > 1))) throw Invalid{};
452 // These source properties are [HDR] colors: Unity uploads their stored
453 // values directly, including subunit RGB, even in linear color space.
454 const auto dual = scalar(text, "_MainColorMode", 0);
455 if (dual != 0 && dual != 1) throw Invalid{};
456 const auto occlusion = unit(scalar(text, "_MainOcclusionValue", 0)),
457 smoothness = unit(scalar(text, "_MainSmoothnessValue", 0));
458 const auto albedo = unit(scalar(text, "_MainAlbedoValue", 1)),
459 cutoff = unit(scalar(text, "_AlphaClipValue", .5));
460 const auto minimum = unit(scalar(text, "_MainMaskMinValue", 0)),
461 maximum = unit(scalar(text, "_MainMaskMaxValue", 0));
462 if (float(maximum) - float(minimum) + .0001f == 0) throw Invalid{};
463 auto manifest = detail::baseManifest(request.package, "eve.unity-tve-material/1");
464 if (!manifest) return Result<PreparedAssetImport>::failure(manifest.status());
466 out.manifest = std::move(manifest).takeValue();
467 const auto id = request.package.packageId.child("unity:" + source.guid + ":2100000");
468 auto ref = detail::assetRef(id);
469 if (!ref) return Result<PreparedAssetImport>::failure(ref.status());
470 Value::Object definition{{"schema", "eve.material"},
471 {"schemaVersion", 15},
472 {"shadingModel", "pbr"},
473 {"surfaceMode", mode == 1 ? "transparent"
474 : clip == 1 ? "masked"
475 : "opaque"},
476 {"doubleSided", cull == 0},
477 {"cullMode", cull == 0 ? "none" : (cull == 1 ? "back" : "front")},
478 {"alphaToCoverage", coverage == 1 && clip == 1},
479 {"baseColor", Value::Array{color[0], color[1], color[2], color[3]}},
480 {"metallic", 0},
481 {"roughness", 1},
482 {"specularFactor", unit(specular)},
483 {"alphaCutoff", cutoff},
484 {"albedoTextureStrength", albedo}};
485 const auto extrasLayer = scalar(text, "_LayerExtrasValue", 0);
486 if (extrasLayer < 0 || extrasLayer > 8 || extrasLayer != std::floor(extrasLayer)) throw Invalid{};
487 const auto colorsLayer = scalar(text, "_LayerColorsValue", 0);
488 if (colorsLayer < 0 || colorsLayer > 8 || colorsLayer != std::floor(colorsLayer)) throw Invalid{};
489 const auto motionLayer = scalar(text, "_LayerMotionValue", 0);
490 if (motionLayer < 0 || motionLayer > 8 || motionLayer != std::floor(motionLayer)) throw Invalid{};
491 const auto vertexLayer = scalar(text, "_LayerVertexValue", 0);
492 if (vertexLayer < 0 || vertexLayer > 8 || vertexLayer != std::floor(vertexLayer)) throw Invalid{};
493 auto binary = [&](const char* name, double fallback) {
494 const auto value = scalar(text, name, fallback);
495 if (value != 0 && value != 1) throw Invalid{};
496 return value;
497 };
498 definition["vegetationAlpha"] = Value::Object{{"global", unit(scalar(text, "_GlobalAlpha", 1))},
499 {"variation", unit(scalar(text, "_AlphaVariationValue", .5))},
500 {"detailFade", binary("_DetailFadeMode", 0) == 1},
501 {"glancing", unit(scalar(text, "_FadeGlancingValue", 0))},
502 {"camera", unit(scalar(text, "_FadeCameraValue", 1))},
503 {"constant", unit(scalar(text, "_FadeConstantValue", 0))}};
504 const auto emissiveMode = binary("_EmissiveMode", 0);
505 if (emissiveMode == 1) {
506 const auto emissiveColor = vector(text, "_EmissiveColor", {0, 0, 0, 0});
507 for (size_t c = 0; c < 3; ++c)
508 if (emissiveColor[c] < 0) throw Invalid{};
509 const auto intensityMode = binary("_EmissiveIntensityMode", 0);
510 const auto authoredPower = scalar(text, "_EmissiveIntensityValue", 1);
511 const auto fallbackPower = intensityMode == 0 ? authoredPower : .125 * std::pow(2., authoredPower);
512 const auto intensity = scalar(text, "_emissive_intensity_value", fallbackPower);
513 if (intensity < 0 || intensity > 1000000) throw Invalid{};
514 const auto emissionMinimum = unit(scalar(text, "_EmissiveTexMinValue", 0));
515 const auto emissionMaximum = unit(scalar(text, "_EmissiveTexMaxValue", 1));
516 if (float(emissionMaximum) - float(emissionMinimum) + .0001f == 0) throw Invalid{};
517 definition["emissive"] = Value::Array{emissiveColor[0], emissiveColor[1], emissiveColor[2]};
518 definition["emissiveStrength"] = intensity;
519 definition["vegetationEmission"] = Value::Object{{"minimum", emissionMinimum},
520 {"maximum", emissionMaximum},
521 {"phase", unit(scalar(text, "_EmissivePhaseValue", 1))},
522 {"global", unit(scalar(text, "_GlobalEmissive", 1))}};
523 }
524 const auto gradientOne = vector(text, "_GradientColorOne", {1, 1, 1, 1});
525 const auto gradientTwo = vector(text, "_GradientColorTwo", {1, 1, 1, 1});
526 for (size_t c = 0; c < 3; ++c)
527 if (gradientOne[c] < 0 || gradientTwo[c] < 0) throw Invalid{};
528 const auto gradientMinimum = unit(scalar(text, "_GradientMinValue", 0));
529 const auto gradientMaximum = unit(scalar(text, "_GradientMaxValue", 1));
530 if (float(gradientMaximum) - float(gradientMinimum) + .0001f == 0) throw Invalid{};
531 definition["vegetationGradient"] =
532 Value::Object{{"colorOne", Value::Array{gradientOne[0], gradientOne[1], gradientOne[2]}},
533 {"colorTwo", Value::Array{gradientTwo[0], gradientTwo[1], gradientTwo[2]}},
534 {"minimum", gradientMinimum},
535 {"maximum", gradientMaximum}};
536 definition["vegetationFields"] =
537 Value::Object{{"colorsLayer", int64_t(colorsLayer)},
538 {"colorsUsePivotPosition", binary("_ColorsPositionMode", 0) == 1},
539 {"extrasLayer", int64_t(extrasLayer)},
540 {"extrasUsePivotPosition", binary("_ExtrasPositionMode", 0) == 1},
541 {"motionLayer", int64_t(motionLayer)},
542 {"vertexLayer", int64_t(vertexLayer)},
543 {"globalSize", unit(scalar(text, "_GlobalSize", 1))},
544 {"sizeFadeStart", scalar(text, "_SizeFadeStartValue", 0)},
545 {"sizeFadeEnd", scalar(text, "_SizeFadeEndValue", 100)}};
546 auto nonnegative = [&](const char* name, double fallback) {
547 const auto value = scalar(text, name, fallback);
548 if (value < 0 || value > 1000000) throw Invalid{};
549 return value;
550 };
551 definition["vegetationMotion"] =
552 Value::Object{{"dynamicMode", unit(scalar(text, "_VertexDynamicMode", 0))},
553 {"rigidity", unit(scalar(text, "_MotionPosition_10", .5))},
554 {"facing", unit(scalar(text, "_MotionFacingValue", .5))},
555 {"bending", nonnegative("_MotionAmplitude_10", .2)},
556 {"bendingSpeed", nonnegative("_MotionSpeed_10", 2)},
557 {"bendingScale", nonnegative("_MotionScale_10", 1)},
558 {"bendingVariation", nonnegative("_MotionVariation_10", 0)},
559 {"branch", nonnegative("_MotionAmplitude_20", .2)},
560 {"rolling", nonnegative("_MotionAmplitude_22", .2)},
561 {"branchSpeed", nonnegative("_MotionSpeed_20", 6)},
562 {"branchScale", nonnegative("_MotionScale_20", 3)},
563 {"branchVariation", nonnegative("_MotionVariation_20", 0)},
564 {"flutter", nonnegative("_MotionAmplitude_32", .2)},
565 {"flutterSpeed", nonnegative("_MotionSpeed_32", 20)},
566 {"flutterScale", nonnegative("_MotionScale_32", 10)},
567 {"flutterVariation", nonnegative("_MotionVariation_32", 0)},
568 {"interaction", nonnegative("_InteractionAmplitude", 1)},
569 {"interactionMask", unit(scalar(text, "_InteractionMaskValue", 1))},
570 {"perspectivePush", nonnegative("_PerspectivePushValue", 0)},
571 {"perspectiveNoise", nonnegative("_PerspectiveNoiseValue", 0)},
572 {"perspectiveAngle", nonnegative("_PerspectiveAngleValue", 1)}};
573 const auto occlusionColor = vector(text, "_VertexOcclusionColor", {1, 1, 1, .5019608});
574 for (size_t c = 0; c < 3; ++c)
575 if (occlusionColor[c] < 0) throw Invalid{};
576 const auto colorsOcclusionMode = scalar(text, "_VertexOcclusionColorsMode", 0);
577 if (colorsOcclusionMode != 0 && colorsOcclusionMode != 1) throw Invalid{};
578 const auto colorsOcclusionMinimum = unit(scalar(text, "_VertexOcclusionMinValue", 0));
579 const auto colorsOcclusionMaximum = unit(scalar(text, "_VertexOcclusionMaxValue", 1));
580 if (float(colorsOcclusionMaximum) - float(colorsOcclusionMinimum) + .0001f == 0) throw Invalid{};
581 const auto colorsIntensity = scalar(text, "_ColorsIntensityValue", 1);
582 if (colorsIntensity < 0 || colorsIntensity > 2) throw Invalid{};
583 const auto backfaceNormalMode = scalar(text, "_RenderNormals", 0);
584 if (backfaceNormalMode < 0 || backfaceNormalMode > 2 || backfaceNormalMode != std::floor(backfaceNormalMode))
585 throw Invalid{};
586 definition["vegetationSurface"] = Value::Object{
587 {"sourceFamily", "tve-12"},
588 {"overlay", unit(scalar(text, "_GlobalOverlay", 1))},
589 {"wetness", unit(scalar(text, "_GlobalWetness", 1))},
590 {"overlayVariation", unit(scalar(text, "_OverlayVariationValue", .5))},
591 {"overlayProjection", unit(scalar(text, "_OverlayProjectionValue", .5))},
592 {"vertexOcclusionAlpha", unit(occlusionColor[3])},
593 {"vertexOcclusionColor", Value::Array{occlusionColor[0], occlusionColor[1], occlusionColor[2]}},
594 {"invertVertexOcclusion", scalar(text, "_VertexOcclusionOverlayMode", 0) == 1},
595 {"colors", unit(scalar(text, "_GlobalColors", 1))},
596 {"colorsIntensity", colorsIntensity},
597 {"colorsMask", unit(scalar(text, "_ColorsMaskValue", 1))},
598 {"colorsVariation", unit(scalar(text, "_ColorsVariationValue", .5))},
599 {"vertexOcclusionMinimum", colorsOcclusionMinimum},
600 {"vertexOcclusionMaximum", colorsOcclusionMaximum},
601 {"invertVertexOcclusionColors", colorsOcclusionMode == 1},
602 {"backfaceNormalMode", int64_t(backfaceNormalMode)}};
603 const auto detailMode = scalar(text, "_DetailMode", 0);
604 if (detailMode != 0 && detailMode != 1) throw Invalid{};
605 if (detailMode == 1) {
606 const auto detailUv = vector(text, "_SecondUVs", {1, 1, 0, 0});
607 const auto detailColor = vector(text, "_SecondColor", {1, 1, 1, 1});
608 const auto detailColorTwo = vector(text, "_SecondColorTwo", {1, 1, 1, 1});
609 const auto uvMode = scalar(text, "_SecondUVsMode", 0);
610 if (uvMode < 0 || uvMode > 2 || uvMode != std::floor(uvMode)) throw Invalid{};
611 auto signedNormal = scalar(text, "_SecondNormalValue", 1);
612 if (signedNormal < -8 || signedNormal > 8 || detailUv[0] == 0 || detailUv[1] == 0) throw Invalid{};
613 definition["vegetationDetail"] = Value::Object{
614 {"value", unit(scalar(text, "_DetailValue", 1))},
615 {"uvMode", int64_t(uvMode)},
616 {"inverseUvScale", binary("_SecondUVsScaleMode", 0) == 1},
617 {"uvScale", Value::Array{detailUv[0], detailUv[1]}},
618 {"uvOffset", Value::Array{detailUv[2], 1.0 - detailUv[1] - detailUv[3]}},
619 {"color", Value::Array{detailColor[0], detailColor[1], detailColor[2], detailColor[3]}},
620 {"colorTwo", Value::Array{detailColorTwo[0], detailColorTwo[1], detailColorTwo[2], detailColorTwo[3]}},
621 {"colorMode", int64_t(binary("_SecondColorMode", 0))},
622 {"albedoValue", unit(scalar(text, "_SecondAlbedoValue", 1))},
623 {"normalValue", signedNormal},
624 {"normalBlendValue", unit(scalar(text, "_DetailNormalValue", 1))},
625 {"metallicValue", unit(scalar(text, "_SecondMetallicValue", 0))},
626 {"occlusionValue", unit(scalar(text, "_SecondOcclusionValue", 1))},
627 {"smoothnessValue", unit(scalar(text, "_SecondSmoothnessValue", 1))},
628 {"blendMode", int64_t(binary("_DetailBlendMode", 0))},
629 {"alphaMode", int64_t(binary("_DetailAlphaMode", 1))},
630 {"maskMode", int64_t(binary("_DetailMaskMode", 0))},
631 {"meshMode", int64_t(binary("_DetailMeshMode", 0))},
632 {"blendMinimum", unit(scalar(text, "_DetailBlendMinValue", 0))},
633 {"blendMaximum", unit(scalar(text, "_DetailBlendMaxValue", 1))},
634 {"maskMinimum", unit(scalar(text, "_DetailMaskMinValue", 0))},
635 {"maskMaximum", unit(scalar(text, "_DetailMaskMaxValue", 1))},
636 {"meshMinimum", unit(scalar(text, "_DetailMeshMinValue", 0))},
637 {"meshMaximum", unit(scalar(text, "_DetailMeshMaxValue", 1))}};
638 }
639 auto bindWithUv = [&](const std::string& role, const AssetRef& image, const std::array<double, 4>& transform) {
640 definition[role] = image.format();
641 definition[role + "Transform"] =
642 Value::Object{{"scale", Value::Array{transform[0], transform[1]}},
643 {"offset", Value::Array{transform[2], 1.0 - transform[1] - transform[3]}}};
644 out.manifest.dependencies.push_back(
645 {ref.value(), image, asset::EvaDependencyKind::RuntimeRequired, role, {}, "eve.image/3", {}});
646 };
647 auto bind = [&](const std::string& role, const AssetRef& image) { bindWithUv(role, image, uv); };
648 if (auto guid = texture(text, "_MainAlbedoTex")) {
649 auto path = texturePath(request, *guid);
650 if (!path) return Result<PreparedAssetImport>::failure(path.status());
651 auto image = detail::assetRef(request.package.packageId.child("unity:" + *guid).child("image:default"));
652 if (!image) return Result<PreparedAssetImport>::failure(image.status());
653 bind("baseColorTexture", image.value());
654 auto sampler = textureSampler(request, path.value());
656 definition["baseColorTextureSampler"] = std::move(sampler).takeValue();
657 }
658 if (emissiveMode == 1) {
659 auto guid = texture(text, "_EmissiveTex");
660 if (!guid) throw Invalid{};
661 auto sourcePath = texturePath(request, *guid);
663 auto image = detail::assetRef(request.package.packageId.child("unity:" + *guid).child("image:default"));
664 if (!image) return Result<PreparedAssetImport>::failure(image.status());
665 const auto emissiveUv = vector(text, "_EmissiveUVs", {1, 1, 0, 0});
666 if (emissiveUv[0] == 0 || emissiveUv[1] == 0) throw Invalid{};
667 bindWithUv("emissiveTexture", image.value(), emissiveUv);
668 auto sampler = textureSampler(request, sourcePath.value());
670 definition["emissiveTextureSampler"] = std::move(sampler).takeValue();
671 }
672 if (auto guid = texture(text, "_MainNormalTex")) {
673 auto path = texturePath(request, *guid);
674 if (!path) return Result<PreparedAssetImport>::failure(path.status());
675 const auto& bytes = request.files.at(path.value() + ".meta");
676 const std::string meta(bytes.begin(), bytes.end());
677 const auto normalType = importerScalar(meta, "textureType", 0);
678 const auto fromHeight = importerScalar(meta, "convertToNormalMap", 0);
679 const auto flipGreen = importerScalar(meta, "flipGreenChannel", 0);
680 if (normalType == 1 && (fromHeight == 0 || fromHeight == 1) && (flipGreen == 0 || flipGreen == 1)) {
681 auto sampler = textureSampler(request, path.value());
683 definition["normalTextureSampler"] = std::move(sampler).takeValue();
684 const auto strength = scalar(text, "_MainNormalValue", 1);
685 if (strength < -8 || strength > 8) throw Invalid{};
686 auto image = detail::assetRef(request.package.packageId.child("unity:" + *guid).child("image:default"));
687 if (!image) return Result<PreparedAssetImport>::failure(image.status());
688 {
689 const auto scale = importerScalar(meta, "heightScale", .25);
690 const auto filter = importerScalar(meta, "normalMapFilter", 0);
691 if (scale < 0 || scale > 1 || (filter != 0 && filter != 1)) throw Invalid{};
692 auto generated = normalImage(request, path.value(), id, scale, filter == 1,
693 importerScalar(meta, "enableMipMap", 1) == 1, flipGreen == 1,
694 fromHeight == 1 ? NormalSource::Height : NormalSource::Authored);
695 if (!generated) return generated;
696 image = Result<AssetRef>::success(generated.value().manifest.assets.front().asset);
697 for (auto& a : generated.value().manifest.assets) out.manifest.assets.push_back(std::move(a));
698 for (auto& e : generated.value().entries)
699 if (e.path.starts_with("assets/")) out.entries.push_back(std::move(e));
700 for (auto& finding : generated.value().findings) out.findings.push_back(std::move(finding));
701 }
702 bind("normalTexture", image.value());
703 definition["normalEncoding"] = "tve-rg";
704 definition["normalScale"] = strength;
705 out.findings.push_back(
706 {source.path, "Material.TVE.normal", ImportDisposition::Translated,
707 "authored or height-generated RG linked as linear data; signed strength and fixed-Z "
708 "reconstruction run after filtering"});
709 } else {
710 out.findings.push_back(
711 {source.path, "Material.TVE.normalImporter", ImportDisposition::Unsupported,
712 "normal source retained; invalid settings or non-normal importer encoding requires conversion"});
713 }
714 }
715 if (*shader == "a933075b367f9b24981408633f72ff34" || *shader == "d9a724745053dee46bf301b216cdd348") {
716 auto tint = vector(text, "_SubsurfaceColor", {1, 1, 1, 1});
717 for (size_t c = 0; c < 3; ++c) {
718 if (tint[c] < 0) throw Invalid{};
719 }
720 const auto power = scalar(text, "_SubsurfaceScatteringValue", 2);
721 const auto angle = scalar(text, "_SubsurfaceAngleValue", 8);
722 if (power < 0 || power > 16 || angle < 1 || angle > 16) throw Invalid{};
723 definition["translucency"] = Value::Object{
724 {"color", Value::Array{tint[0], tint[1], tint[2]}},
725 {"intensity", unit(scalar(text, "_SubsurfaceValue", 1))},
726 {"strength", power},
727 {"scattering", angle},
728 {"normalDistortion", unit(scalar(text, "_SubsurfaceNormalValue", 0))},
729 {"direct", unit(scalar(text, "_SubsurfaceDirectValue", 1))},
730 {"ambient", unit(scalar(text, "_SubsurfaceAmbientValue", .2))},
731 {"shadow", unit(scalar(text, "_SubsurfaceShadowValue", 1))},
732 {"maskAmount",
733 *shader == "a933075b367f9b24981408633f72ff34" ? unit(scalar(text, "_SubsurfaceMaskValue", 1)) : 0},
734 {"maskMinimum", minimum},
735 {"maskMaximum", maximum}};
736 out.findings.push_back({source.path, "Material.TVE.translucency", ImportDisposition::Translated,
737 "visible subsurface controls mapped to per-light translucency; field wetness is "
738 "composed by the vegetation runtime"});
739 }
740 if (*shader == "a933075b367f9b24981408633f72ff34" || *shader == "7befaa6f41d00a6478d5f4af21d66518") {
741 auto highlight = vector(text, "_MotionHighlightColor", {0, 0, 0, 0});
742 for (size_t c = 0; c < 3; ++c) {
743 if (highlight[c] < 0) throw Invalid{};
744 }
745 definition["motionHighlightColor"] = Value::Array{highlight[0], highlight[1], highlight[2]};
746 out.findings.push_back(
747 {source.path, "Material.TVE.motionHighlight", ImportDisposition::Translated,
748 "Plant motion highlight RGB retained; runtime requires evaluated per-vertex highlight stream"});
749 }
750 if (detailMode == 1) {
751 for (const auto& [property, role] :
752 std::array<std::pair<const char*, const char*>, 3>{{{"_SecondAlbedoTex", "detailAlbedoTexture"},
753 {"_SecondNormalTex", "detailNormalTexture"},
754 {"_SecondMaskTex", "detailMaskTexture"}}}) {
755 auto guid = texture(text, property);
756 if (!guid) continue;
757 auto path = texturePath(request, *guid);
758 if (!path) return Result<PreparedAssetImport>::failure(path.status());
759 auto image = detail::assetRef(request.package.packageId.child("unity:" + *guid).child("image:default"));
760 if (!image) return Result<PreparedAssetImport>::failure(image.status());
761 definition[role] = image.value().format();
762 auto sampler = textureSampler(request, path.value());
764 definition[std::string(role) + "Sampler"] = std::move(sampler).takeValue();
765 out.manifest.dependencies.push_back({ref.value(),
766 image.value(),
768 role,
769 {},
770 "eve.image/3",
771 {}});
772 }
773 }
774 auto orm = ormImage(request, text, id, occlusion, smoothness);
775 if (!orm) return orm;
776 const auto mask = orm.value().manifest.assets.front().asset;
777 bind("metallicRoughnessTexture", mask);
778 bind("occlusionTexture", mask);
779 if (auto guid = texture(text, "_MainMaskTex")) {
780 auto path = texturePath(request, *guid);
781 if (!path) return Result<PreparedAssetImport>::failure(path.status());
782 auto sampler = textureSampler(request, path.value());
784 definition["metallicRoughnessTextureSampler"] = sampler.value();
785 definition["occlusionTextureSampler"] = std::move(sampler).takeValue();
786 }
787 for (auto& a : orm.value().manifest.assets) out.manifest.assets.push_back(std::move(a));
788 for (auto& e : orm.value().entries)
789 if (e.path.starts_with("assets/")) out.entries.push_back(std::move(e));
790 if (dual == 1)
791 definition["colorMask"] = Value::Object{{"secondary", Value::Array{second[0], second[1], second[2]}},
792 {"minimum", minimum},
793 {"maximum", maximum}};
794 const auto path = "assets/" + id.format() + "/asset.json";
795 auto json = Value(std::move(definition)).toJson();
796 if (!json) return Result<PreparedAssetImport>::failure(json.status());
797 std::vector<std::uint8_t> encoded(json.value().begin(), json.value().end());
798 out.manifest.assets.push_back({ref.value(),
799 "eve.material",
800 SchemaVersion(15),
801 path,
802 detail::sha256(encoded),
803 {"material", "source:unity", "vegetation"}});
804 out.entries.push_back({path, std::move(encoded)});
805 out.manifest.entrypoints.emplace("default", ref.value());
806 out.sourceMappings.push_back({"2100000", ref.value()});
807 out.findings.push_back({source.path, "Material.TVE.main", ImportDisposition::Translated,
808 "TVE 12.6.0 primary RGB, albedo, UV transform, AO/smoothness and RGB mask converted"});
809 out.findings.push_back({source.path, "Material.TVE.renderState", ImportDisposition::Translated,
810 "specular, facing normal, culling and alpha-to-coverage state translated; "
811 "Direct/Ambient/Shadow and generated noise/rim declarations are inactive in the "
812 "inspected 12.6 shader calculations"});
813 return Result<PreparedAssetImport>::success(std::move(out));
814 } catch (const Invalid&) {
816 DiagnosticCode::ParseError, "invalid TVE material saved properties", source.path, {}, "asset.import"));
817 }
818}
819} // namespace eve::asset_import
LogicalId target
double value
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
std::map< std::string, std::vector< Key >, std::less<> > channels
std::string output
const std::string & s
std::unordered_map< std::string, QuestRuntime > entries
int mask
Safe source-image conversion to runtime EVIMG payloads.
float uv
float length
Definition CaveMesh.cpp:94
graphics::Texture * albedo
glm::vec4 p[6]
std::string pattern
float maximum[3]
float minimum[3]
const GltfImportRequest & request
std::uint32_t key
vk::UniqueSampler sampler
vk::UniqueImage image
wgpu::PopErrorScopeStatus status
glm::vec4 clip
glm::vec4 tint
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
float v
std::int32_t second
std::int32_t c
std::int32_t first
int h
std::uint32_t height
std::uint32_t width
std::string text
std::string imported
std::array< float, 3 > scale
std::uint64_t bytes
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float roughness
int level
std::string path
Definition PlayHost.cpp:110
Shader * shader
float power
Definition RockMesh.cpp:23
bool found
std::string filter
float dy
float dx
TacticalUnit * unit
TerrainThermalSettings settings
std::string sourcePath
Direct Unity text-serialization adapter for TerrainData and Prefab assets.
uint32_t index
const UnitySourceAsset & source
const AssetImportLimits & limits
std::size_t at
float highlight
float angle
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
Result< std::string > toJson() const
Serialize this value as deterministic compact JSON.
Definition Value.cpp:67
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
Result< std::vector< std::uint8_t > > encodeSourcePng(std::uint32_t width, std::uint32_t height, std::span< const std::uint8_t > pixels, std::uint64_t maximumBytes)
Encode top-down RGBA8 source pixels without transfer conversion.
Definition SourcePng.cpp:6
std::string sha256(std::span< const std::uint8_t > bytes)
Sha 256.
EVENGINE_API_PLATFORM Result< void > finalizeImportReport(PreparedAssetImport &prepared, const ImportPackageIdentity &package, std::string_view sourceEngine, std::string_view sourceVersion, Value::Object options={})
Append the deterministic mandatory import report and bind it from provenance.
Result< asset::EvaManifest > baseManifest(const ImportPackageIdentity &package, std::string_view importer)
Base manifest.
Result< AssetRef > assetRef(PersistentId id)
Asset ref.
std::string foldAscii(std::string value)
Fold ascii.
Result< std::string > metaScalar(std::span< const std::uint8_t > bytes, std::string_view key, std::string_view path)
Read an unindented scalar from .meta; rejects duplicate keys and binary metadata.
Result< PreparedAssetImport > prepareUnityVegetationMaterial(const UnityProjectImportRequest &request, const UnitySourceAsset &source)
Convert admitted TVE primary material data; remaining source features are reported explicitly.
EVENGINE_API_PLATFORM Result< PreparedAssetImport > prepareImageImport(const ImageImportRequest &request)
Decode image metadata and prepare one canonical eve.image source asset.
Result< CookedCanonicalImage > cookCanonicalImageRgba8(std::span< const std::uint8_t > definition, std::span< const std::uint8_t > encodedSource, std::uint64_t maximumDecodedBytes)
Decode image/2 base images or image/3 explicit linear RGBA8 mip chains into bounded EVIMG.
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 sample(const Heightmap &map, double u, double v)
Sample.
StrongUuid< detail::PersistentIdTag > PersistentId
Stable instance identity for persistence, networking and process boundaries.
Definition Identity.h:254
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
glm::vec4 color