载入中...
搜索中...
未找到
UnityNativeMesh.cpp
浏览该文件的文档.
1#include <bit>
2#include <charconv>
3#include <cmath>
4#include <regex>
5#include <stdexcept>
9
10namespace eve::asset_import {
11namespace {
12std::string capture(const std::string& text, const std::string& pattern) {
13 std::smatch m;
14 if (!std::regex_search(text, m, std::regex(pattern))) throw std::runtime_error("missing Mesh field");
15 return m[1].str();
16}
17std::uint64_t number(const std::string& text, const std::string& key) {
18 const auto value = capture(text, "(?:^|\\n) *" + key + ": *([0-9]+)(?:\\r?\\n|$)");
19 std::uint64_t out = 0;
20 auto parsed = std::from_chars(value.data(), value.data() + value.size(), out);
21 if (parsed.ec != std::errc{} || parsed.ptr != value.data() + value.size())
22 throw std::runtime_error("Mesh integer overflow");
23 return out;
24}
25std::vector<std::uint8_t> hex(const std::string& text, std::uint64_t limit) {
26 if (text.size() % 2 || text.size() / 2 > limit) throw std::runtime_error("Mesh byte budget or hex length invalid");
27 std::vector<std::uint8_t> out;
28 for (std::size_t i = 0; i < text.size(); i += 2) {
29 unsigned value = 0;
30 auto parsed = std::from_chars(text.data() + i, text.data() + i + 2, value, 16);
31 if (parsed.ec != std::errc{} || parsed.ptr != text.data() + i + 2) throw std::runtime_error("invalid Mesh hex");
32 out.push_back(std::uint8_t(value));
33 }
34 return out;
35}
36std::uint32_t read(const std::vector<std::uint8_t>& data, std::size_t at, unsigned size) {
37 if (at > data.size() || size > data.size() - at) throw std::runtime_error("Mesh buffer truncated");
38 std::uint32_t out = 0;
39 for (unsigned i = 0; i < size; ++i) out |= std::uint32_t(data[at + i]) << (8 * i);
40 return out;
41}
42void put(std::vector<std::uint8_t>& data, std::uint32_t value) {
43 for (unsigned i = 0; i < 4; ++i) data.push_back(std::uint8_t(value >> (8 * i)));
44}
45} // namespace
47 const UnitySourceAsset& source) {
48 const auto& bytes = request.files.at(source.path);
49 std::string text(bytes.begin(), bytes.end());
50 std::erase(text, '\r');
51 if (text.find("\nMesh:") == std::string::npos && text.find("\nMesh:\r") == std::string::npos)
53 DiagnosticCode::Unsupported, "native asset is not a text Mesh", source.path, {}, "asset.import"));
54 try {
55 const auto id = capture(text, R"(--- !u!43 &(-?[0-9]+))");
56 const auto version = number(text, "serializedVersion");
57 if ((version != 9 && version != 11) || number(text, "m_MeshCompression") != 0 ||
58 text.find("m_BindPose: []") == std::string::npos)
60 Diagnostic::error(DiagnosticCode::Unsupported, "requires uncompressed static Mesh version 9 or 11",
61 source.path, {}, "asset.import"));
62 const auto shapes = text.find("m_Shapes:");
63 if (shapes != std::string::npos) {
64 const auto end = text.find("m_BindPose:", shapes);
65 const auto section = text.substr(shapes, end - shapes);
66 if (section.find("vertices: []") == std::string::npos || section.find("shapes: []") == std::string::npos)
68 Diagnostic::error(DiagnosticCode::Unsupported, "Mesh blend shapes require animation conversion",
69 source.path, {}, "asset.import"));
70 }
71 const auto count = number(text, "m_VertexCount");
73 throw std::runtime_error("Mesh vertex budget exceeded");
74 struct Channel {
75 std::uint64_t stream, offset, format, dimension;
76 };
77 std::vector<Channel> channels;
78 const std::regex channel(
79 R"(- stream: ([0-9]+)\r?\n offset: ([0-9]+)\r?\n format: ([0-9]+)\r?\n dimension: ([0-9]+))");
80 std::uint64_t stride = 0;
81 for (std::sregex_iterator it(text.begin(), text.end(), channel), end; it != end; ++it) {
82 Channel c{std::stoull((*it)[1]), std::stoull((*it)[2]), std::stoull((*it)[3]), std::stoull((*it)[4])};
83 if (c.dimension && (c.stream != 0 || c.format != 0 || c.dimension > 4 || c.offset > 256))
85 Diagnostic::error(DiagnosticCode::Unsupported, "Mesh requires interleaved float32 vertex channels",
86 source.path, {}, "asset.import"));
87 if (c.dimension) stride = std::max(stride, c.offset + c.dimension * 4);
88 channels.push_back(c);
89 }
90 if (channels.size() != 14 || channels[0].dimension != 3 || channels[1].dimension != 3 ||
91 channels[4].dimension < 2 || !stride)
92 throw std::runtime_error("Mesh position, normal or UV0 channel missing");
93 if (channels[12].dimension || channels[13].dimension)
95 Diagnostic::error(DiagnosticCode::Unsupported, "Mesh skin channels require animation conversion",
96 source.path, {}, "asset.import"));
97 for (size_t a = 0; a < channels.size(); ++a)
98 for (size_t b = a + 1; b < channels.size(); ++b)
99 if (channels[a].dimension && channels[b].dimension &&
100 channels[a].offset < channels[b].offset + channels[b].dimension * 4 &&
101 channels[b].offset < channels[a].offset + channels[a].dimension * 4)
102 throw std::runtime_error("overlapping Mesh channels");
103 auto vertices = hex(capture(text, R"(_typelessdata: ([0-9a-fA-F]*))"), request.limits.maximumDecodedBytes);
104 if (vertices.size() != number(text, "m_DataSize") || vertices.size() != stride * count)
105 throw std::runtime_error("Mesh vertex buffer size mismatch");
106 auto indices = hex(capture(text, R"(m_IndexBuffer: ([0-9a-fA-F]*))"), request.limits.maximumDecodedBytes);
107 const auto indexFormat = number(text, "m_IndexFormat");
108 if (indexFormat > 1) throw std::runtime_error("invalid Mesh index format");
109 const unsigned indexSize = indexFormat == 0 ? 2 : 4;
110 if (indices.size() % indexSize || indices.size() / indexSize > request.limits.maximumIndicesPerPrimitive)
111 throw std::runtime_error("Mesh index budget exceeded");
112 if (number(text, "size") != 0)
114 DiagnosticCode::Unsupported, "external Mesh stream unsupported", source.path, {}, "asset.import"));
115 std::vector<std::uint8_t> buffer;
116 Value::Array views, accessors, primitives;
117 auto view = [&](std::size_t start, std::uint64_t n, const char* type, int component) {
118 const auto index = std::int64_t(views.size());
119 views.emplace_back(Value::Object{{"buffer", Value(std::int64_t(0))},
120 {"byteOffset", Value(std::int64_t(start))},
121 {"byteLength", Value(std::int64_t(buffer.size() - start))}});
122 accessors.emplace_back(Value::Object{{"bufferView", Value(index)},
123 {"componentType", Value(std::int64_t(component))},
124 {"count", Value(std::int64_t(n))},
125 {"type", Value(type)}});
126 return index;
127 };
128 Value::Object attributes;
129 auto emit = [&](unsigned c, unsigned dimension, const std::string& semantic, bool raw) {
130 const auto start = buffer.size();
131 for (std::uint64_t i = 0; i < count; ++i)
132 for (unsigned j = 0; j < dimension; ++j) {
133 float value =
134 std::bit_cast<float>(read(vertices, std::size_t(i * stride + channels[c].offset + j * 4), 4));
135 if (!std::isfinite(value)) throw std::runtime_error("nonfinite Mesh vertex");
136 if (!raw && c <= 2 && j == 2) value = -value;
137 if (!raw && c == 2 && j == 3) value = -value;
138 if (!raw && c >= 4 && j == 1) value = 1.f - value;
139 put(buffer, std::bit_cast<std::uint32_t>(value));
140 }
141 const char* types[] = {"SCALAR", "SCALAR", "VEC2", "VEC3", "VEC4"};
142 attributes.emplace(semantic, Value(view(start, count, types[dimension], 5126)));
143 };
144 emit(0, 3, "POSITION", false);
145 emit(1, 3, "NORMAL", false);
146 if (channels[2].dimension) {
147 if (channels[2].dimension != 4) throw std::runtime_error("Mesh tangent must have four components");
148 emit(2, 4, "TANGENT", false);
149 }
150 if (channels[3].dimension) emit(3, unsigned(channels[3].dimension), "COLOR_0", true);
151 for (unsigned c = 4; c < 12; ++c) {
152 if (!channels[c].dimension) continue;
153 if (channels[c].dimension >= 2) emit(c, 2, "TEXCOORD_" + std::to_string(c - 4), false);
154 // Authoring coordinates retain all source bits, including packed masks and pivot axes.
155 emit(c, unsigned(channels[c].dimension), "_UNITY_UV" + std::to_string(c - 4), true);
156 }
157 const auto canonicalVertexBytes = buffer.size();
158 const std::regex sub(
159 R"( firstByte: ([0-9]+)\r?\n indexCount: ([0-9]+)\r?\n topology: ([0-9]+)\r?\n baseVertex: ([0-9]+))");
160 std::uint64_t consumed = 0;
161 std::vector<std::size_t> slots;
162 std::size_t slot = 0;
163 for (std::sregex_iterator it(text.begin(), text.end(), sub), end; it != end; ++it) {
164 const auto first = std::stoull((*it)[1]), n = std::stoull((*it)[2]), topology = std::stoull((*it)[3]),
165 base = std::stoull((*it)[4]);
166 if (topology != 0)
168 DiagnosticCode::Unsupported, "non-triangle Mesh topology", source.path, {}, "asset.import"));
169 if (n % 3 || first != consumed || first > indices.size() || n > (indices.size() - first) / indexSize ||
170 primitives.size() >= request.limits.maximumAssets)
171 throw std::runtime_error("invalid Mesh submesh range");
172 const auto currentSlot = slot++;
173 if (n == 0) continue;
174 slots.push_back(currentSlot);
175 const auto start = buffer.size();
176 for (std::uint64_t i = 0; i < n; i += 3)
177 for (unsigned j : {0u, 2u, 1u}) {
178 const auto value =
179 std::uint64_t(read(indices, std::size_t(first + (i + j) * indexSize), indexSize)) + base;
180 if (value >= count) throw std::runtime_error("Mesh index out of bounds");
181 put(buffer, std::uint32_t(value));
182 }
183 const auto accessor = view(start, n, "SCALAR", 5125);
184 primitives.emplace_back(Value::Object{{"attributes", Value(attributes)}, {"indices", Value(accessor)}});
185 consumed = first + n * indexSize;
186 }
187 if (primitives.empty() || consumed != indices.size() || buffer.size() > request.limits.maximumDecodedBytes)
188 throw std::runtime_error("Mesh submesh coverage or output budget invalid");
189 if (canonicalVertexBytes * slots.size() + indices.size() * 2 > request.limits.maximumDecodedBytes)
190 throw std::runtime_error("aggregate canonical submesh byte budget exceeded");
191 Value document(Value::Object{
192 {"asset", Value(Value::Object{{"version", Value("2.0")}})},
193 {"buffers", Value(Value::Array{Value(Value::Object{{"uri", Value("mesh.bin")},
194 {"byteLength", Value(std::int64_t(buffer.size()))}})})},
195 {"bufferViews", Value(std::move(views))},
196 {"accessors", Value(std::move(accessors))},
197 {"meshes", Value(Value::Array{Value(Value::Object{{"primitives", Value(std::move(primitives))}})})}});
198 auto json = document.toJson();
199 if (!json) return Result<PreparedAssetImport>::failure(json.status());
200 auto identity = request.package;
201 identity.packageId = identity.packageId.child("unity:" + source.guid + ":" + id);
202 auto result = prepareGltfImport({identity,
203 source.path,
204 {json.value().begin(), json.value().end()},
205 {{"mesh.bin", std::move(buffer)}},
206 request.limits});
207 if (!result) return result;
208 // Canonical identity follows the Unity slot, including empty slots, never the compacted glTF ordinal.
209 for (std::size_t i = 0; i < result.value().sourceMappings.size(); ++i) {
210 const auto sourceObject = "meshes[0].primitives[" + std::to_string(i) + "]";
211 auto mappingIt = std::find_if(result.value().sourceMappings.begin(), result.value().sourceMappings.end(),
212 [&](const auto& value) { return value.sourceObject == sourceObject; });
213 if (mappingIt == result.value().sourceMappings.end()) throw std::runtime_error("missing primitive mapping");
214 auto& mapping = *mappingIt;
215 const auto oldRef = mapping.asset;
216 auto newRef = detail::assetRef(request.package.packageId.child("unity:" + source.guid + ":" + id +
217 ":submesh:" + std::to_string(slots[i])));
218 if (!newRef) return Result<PreparedAssetImport>::failure(newRef.status());
219 const auto oldBase = "assets/" + oldRef.id().format() + "/";
220 const auto newBase = "assets/" + newRef.value().id().format() + "/";
221 auto& asset = result.value().manifest.assets.at(i);
222 for (auto& entry : result.value().entries) {
223 if (!entry.path.starts_with(oldBase)) continue;
224 if (entry.path == asset.definition) {
225 auto definition = Value::fromJson(std::string(entry.bytes.begin(), entry.bytes.end()));
226 if (!definition) return Result<PreparedAssetImport>::failure(definition.status());
227 definition.value().getIf<Value::Object>()->at("blob") = Value(newBase + "mesh.bin");
228 auto encoded = definition.value().toJson();
229 if (!encoded) return Result<PreparedAssetImport>::failure(encoded.status());
230 entry.bytes.assign(encoded.value().begin(), encoded.value().end());
231 asset.contentHash = detail::sha256(entry.bytes);
232 }
233 entry.path = newBase + entry.path.substr(oldBase.size());
234 }
235 asset.asset = newRef.value();
236 asset.definition = newBase + "asset.json";
237 for (auto& [name, ref] : result.value().manifest.entrypoints)
238 if (ref == oldRef) ref = newRef.value();
239 mapping.asset = newRef.value();
240 mapping.sourceObject = id + "/submesh/" + std::to_string(slots[i]);
241 }
242 std::erase_if(result.value().entries, [](const auto& entry) { return entry.path == "reports/import.json"; });
243 result.value().findings.push_back(
244 {source.path, "Mesh.authoringChannels", ImportDisposition::Translated,
245 "float tangent, color and complete source UV channels retained in canonical mesh attributes"});
246 auto report =
247 detail::finalizeImportReport(result.value(), identity, "unity", "mesh-v" + std::to_string(version));
248 if (!report) return Result<PreparedAssetImport>::failure(report.status());
249 return result;
250 } catch (const std::exception& e) {
252 Diagnostic::error(DiagnosticCode::ParseError, e.what(), source.path, {}, "asset.import"));
253 }
254}
255} // namespace eve::asset_import
double value
Duration start
std::map< std::string, std::vector< Key >, std::less<> > channels
std::string pattern
const GltfImportRequest & request
std::uint32_t key
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
std::vector< std::uint32_t > indices
std::int32_t c
std::int32_t first
std::string text
size_t offset
std::uint64_t bytes
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::unique_ptr< gpgpu::GpuBuffer > buffer
Definition OnnxGpgpu.cpp:26
Topology topology
std::vector< Point > vertices
float begin
glm::mat4 view
double number
LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]
std::uint32_t count
int limit
Definition TreeMesh.cpp:164
float size
Definition TreeMesh.cpp:156
Direct Unity text-serialization adapter for TerrainData and Prefab assets.
uint32_t index
const UnitySourceAsset & source
std::size_t at
float m[16]
uint32_t semantic
Definition WfcSimple.cpp:15
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 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
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< AssetRef > assetRef(PersistentId id)
Asset ref.
Result< PreparedAssetImport > prepareUnityNativeMesh(const UnityProjectImportRequest &request, const UnitySourceAsset &source)
Decode a bounded static Unity text Mesh into owning canonical submesh candidates.
EVENGINE_API_PLATFORM Result< PreparedAssetImport > prepareGltfImport(const GltfImportRequest &request)
Decode triangle primitives from glTF 2.0 into canonical typed mesh blobs.
T read(const RuntimeTensor &v, size_t i)
Reads read.
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