载入中...
搜索中...
未找到
LegacyAnimationImporter.cpp
浏览该文件的文档.
2
4
5#include <bit>
6#include <cmath>
7#include <sstream>
8#include <set>
9
10namespace eve::asset_import {
11namespace {
12
13struct Bone {
14 std::int32_t parent = -1;
15 std::string name;
16 std::array<float, 3> position{};
17 std::array<float, 4> rotation{0, 0, 0, 1};
18 std::array<float, 3> scale{1, 1, 1};
19 bool hasBind = false;
20};
21struct VecKey { float time = 0; std::array<float, 3> value{}; };
22struct RotKey { float time = 0; std::array<float, 4> value{}; };
23struct Track {
24 std::uint32_t bone = 0;
25 std::uint32_t expectedPosition = 0, expectedRotation = 0, expectedScale = 0;
26 std::vector<VecKey> position, scale;
27 std::vector<RotKey> rotation;
28};
29struct Marker { float time = 0; std::string name; };
30struct Fixture {
31 std::vector<Bone> bones;
32 std::string clipName;
33 float duration = 0, rate = 0;
34 bool loop = true;
35 std::vector<Track> tracks;
36 std::vector<Marker> markers;
37};
38
39void put32(std::vector<std::uint8_t>& out, std::uint32_t value) {
40 for (unsigned shift = 0; shift < 32; shift += 8) out.push_back(static_cast<std::uint8_t>(value >> shift));
41}
42void putFloat(std::vector<std::uint8_t>& out, float value) { put32(out, std::bit_cast<std::uint32_t>(value)); }
43void putString(std::vector<std::uint8_t>& out, std::string_view value) {
44 put32(out, static_cast<std::uint32_t>(value.size()));
45 out.insert(out.end(), value.begin(), value.end());
46}
47std::string trim(std::string value) {
48 const auto first = value.find_first_not_of(" \t\r");
49 if (first == std::string::npos) return {};
50 const auto last = value.find_last_not_of(" \t\r");
51 return value.substr(first, last - first + 1);
52}
53bool finite(float value) { return std::isfinite(value); }
54bool complete(std::istringstream& line) { line >> std::ws; return line.eof(); }
55
56Result<Fixture> parseFixture(const LegacyAnimationImportRequest& request) {
57 if (request.text.size() > request.limits.maximumSourceBytes)
59 DiagnosticCode::InvalidArgument, "animation fixture exceeds source budget", {}, {}, "asset.import"));
60 Fixture result;
61 std::istringstream input(request.text);
62 std::string text;
63 bool magic = false, ended = false;
64 std::uint32_t declaredBones = 0;
65 Track* track = nullptr;
66 std::size_t lineNumber = 0;
67 while (std::getline(input, text)) {
68 ++lineNumber;
69 text = trim(std::move(text));
70 if (text.empty() || text.front() == '#') continue;
71 std::istringstream line(text);
72 std::string tag;
73 line >> tag;
74 const auto path = request.sourceName + ":" + std::to_string(lineNumber);
75 if (!magic) {
76 int version = 0;
77 if (tag != "EVA" || !(line >> version) || version != 1 || !complete(line))
79 "legacy animation must begin with EVA 1", path, {},
80 "asset.import"));
81 magic = true;
82 } else if (tag == "skeleton") {
83 if (!(line >> declaredBones) || declaredBones == 0 ||
84 declaredBones > request.limits.maximumAssets || !complete(line))
86 Diagnostic::error(DiagnosticCode::ParseError, "invalid skeleton count", path, {}, "asset.import"));
87 result.bones.reserve(declaredBones);
88 } else if (tag == "bone") {
89 std::uint32_t index = 0;
90 Bone bone;
91 if (!(line >> index >> bone.parent) || index != result.bones.size())
93 Diagnostic::error(DiagnosticCode::ParseError, "invalid bone index", path, {}, "asset.import"));
94 std::getline(line, bone.name); bone.name = trim(std::move(bone.name));
95 if (bone.name.empty() || bone.name.size() > request.limits.maximumStringBytes ||
96 bone.parent >= static_cast<std::int32_t>(index) || bone.parent < -1)
98 Diagnostic::error(DiagnosticCode::ParseError, "invalid bone hierarchy", path, {}, "asset.import"));
99 result.bones.push_back(std::move(bone));
100 } else if (tag == "bind") {
101 std::uint32_t index = 0;
102 if (!(line >> index) || index >= result.bones.size())
104 Diagnostic::error(DiagnosticCode::ParseError, "invalid bind index", path, {}, "asset.import"));
105 auto& bone = result.bones[index];
106 if (!(line >> bone.position[0] >> bone.position[1] >> bone.position[2] >>
107 bone.rotation[0] >> bone.rotation[1] >> bone.rotation[2] >> bone.rotation[3] >>
108 bone.scale[0] >> bone.scale[1] >> bone.scale[2]) || !complete(line))
110 Diagnostic::error(DiagnosticCode::ParseError, "invalid bind transform", path, {}, "asset.import"));
111 bone.hasBind = true;
112 } else if (tag == "clip") {
113 std::getline(line, result.clipName); result.clipName = trim(std::move(result.clipName));
114 if (result.clipName.empty() || result.clipName.size() > request.limits.maximumStringBytes)
116 Diagnostic::error(DiagnosticCode::ParseError, "invalid clip name", path, {}, "asset.import"));
117 } else if (tag == "duration") {
118 if (!(line >> result.duration) || !complete(line) || !finite(result.duration) || result.duration < 0)
120 Diagnostic::error(DiagnosticCode::ParseError, "invalid clip duration", path, {}, "asset.import"));
121 } else if (tag == "rate") {
122 if (!(line >> result.rate) || !complete(line) || !finite(result.rate) || result.rate <= 0)
124 Diagnostic::error(DiagnosticCode::ParseError, "invalid sample rate", path, {}, "asset.import"));
125 } else if (tag == "loop") {
126 int value = 0;
127 if (!(line >> value) || (value != 0 && value != 1) || !complete(line))
129 Diagnostic::error(DiagnosticCode::ParseError, "invalid loop flag", path, {}, "asset.import"));
130 result.loop = value != 0;
131 } else if (tag == "sync") {
132 Marker marker;
133 if (!(line >> marker.time) || !finite(marker.time))
135 Diagnostic::error(DiagnosticCode::ParseError, "invalid sync marker", path, {}, "asset.import"));
136 std::getline(line, marker.name); marker.name = trim(std::move(marker.name));
137 if (marker.name.empty())
139 Diagnostic::error(DiagnosticCode::ParseError, "empty sync marker", path, {}, "asset.import"));
140 result.markers.push_back(std::move(marker));
141 if (result.markers.size() > request.limits.maximumAssets)
143 "too many sync markers", path, {}, "asset.import"));
144 } else if (tag == "track") {
145 Track value;
146 if (!(line >> value.bone >> value.expectedPosition >> value.expectedRotation >> value.expectedScale) ||
147 !complete(line) || value.bone >= result.bones.size())
149 Diagnostic::error(DiagnosticCode::ParseError, "invalid track header", path, {}, "asset.import"));
150 result.tracks.push_back(std::move(value)); track = &result.tracks.back();
151 if (result.tracks.size() > request.limits.maximumAssets)
153 DiagnosticCode::InvalidArgument, "too many animation tracks", path, {}, "asset.import"));
154 } else if (tag == "p" || tag == "s") {
155 VecKey key;
156 if (!track || !(line >> key.time >> key.value[0] >> key.value[1] >> key.value[2]) ||
157 !complete(line) || !finite(key.time) || !finite(key.value[0]) || !finite(key.value[1]) || !finite(key.value[2]))
159 Diagnostic::error(DiagnosticCode::ParseError, "invalid vector key", path, {}, "asset.import"));
160 (tag == "p" ? track->position : track->scale).push_back(key);
161 } else if (tag == "r") {
162 RotKey key;
163 if (!track || !(line >> key.time >> key.value[0] >> key.value[1] >> key.value[2] >> key.value[3]) ||
164 !complete(line) || !finite(key.time) || !finite(key.value[0]) || !finite(key.value[1]) ||
165 !finite(key.value[2]) || !finite(key.value[3]))
167 Diagnostic::error(DiagnosticCode::ParseError, "invalid rotation key", path, {}, "asset.import"));
168 track->rotation.push_back(key);
169 } else if (tag == "end") {
170 if (!complete(line))
172 Diagnostic::error(DiagnosticCode::ParseError, "invalid end", path, {}, "asset.import"));
173 ended = true; break;
174 } else {
176 DiagnosticCode::ParseError, "unknown animation fixture record", path, {}, "asset.import"));
177 }
178 }
179 if (!magic || !ended || result.bones.size() != declaredBones || result.clipName.empty() || result.rate <= 0)
181 Diagnostic::error(DiagnosticCode::ParseError, "animation fixture is incomplete", {}, {}, "asset.import"));
182 for (const auto& bone : result.bones)
183 if (!bone.hasBind)
184 return Result<Fixture>::failure(
185 Diagnostic::error(DiagnosticCode::ParseError, "bone bind is missing", bone.name, {}, "asset.import"));
186 for (const auto& value : result.tracks)
189 return Result<Fixture>::failure(Diagnostic::error(
190 DiagnosticCode::ParseError, "track key count does not match declaration", {}, {}, "asset.import"));
191 std::set<std::uint32_t> trackBones;
192 std::uint64_t decodedBytes = 0;
193 const auto validTimes = [&](const auto& keys) {
194 float previous = -1.0f;
195 for (const auto& key : keys) {
196 if (key.time < 0 || key.time > result.duration || key.time < previous) return false;
197 previous = key.time;
198 }
199 return true;
200 };
201 for (const auto& value : result.tracks) {
202 if (!trackBones.emplace(value.bone).second || !validTimes(value.position) ||
203 !validTimes(value.rotation) || !validTimes(value.scale))
205 DiagnosticCode::Conflict, "tracks must be unique and key times canonical", {}, {}, "asset.import"));
206 const std::uint64_t keys = value.position.size() + value.rotation.size() + value.scale.size();
207 if (keys > request.limits.maximumDecodedBytes / 24 ||
208 decodedBytes > request.limits.maximumDecodedBytes - keys * 24)
210 DiagnosticCode::InvalidArgument, "animation key data exceeds decoded budget", {}, {}, "asset.import"));
211 decodedBytes += keys * 24;
212 }
213 return Result<Fixture>::success(std::move(result));
214}
215
216} // namespace
217
219 auto parsed = parseFixture(request);
220 if (!parsed) return Result<PreparedAssetImport>::failure(parsed.status());
221 auto fixture = std::move(parsed).takeValue();
222 auto manifest = detail::baseManifest(request.package, "eve.legacy-animation-text/1");
223 if (!manifest) return Result<PreparedAssetImport>::failure(manifest.status());
224 PreparedAssetImport output; output.manifest = std::move(manifest).takeValue();
225 const auto skeletonId = request.package.packageId.child("animation:skeleton");
226 const auto clipId = request.package.packageId.child("animation:clip:" + fixture.clipName);
227 auto skeletonRef = detail::assetRef(skeletonId); auto clipRef = detail::assetRef(clipId);
228 if (!skeletonRef) return Result<PreparedAssetImport>::failure(skeletonRef.status());
229 if (!clipRef) return Result<PreparedAssetImport>::failure(clipRef.status());
230
231 std::vector<std::uint8_t> skeletonBlob{'E','V','S','K','E','L',0,1};
232 put32(skeletonBlob, static_cast<std::uint32_t>(fixture.bones.size()));
233 for (auto bone : fixture.bones) {
234 put32(skeletonBlob, static_cast<std::uint32_t>(bone.parent)); putString(skeletonBlob, bone.name);
235 bone.position = {bone.position[0] * .01f, bone.position[1] * .01f, -bone.position[2] * .01f};
236 bone.rotation = {-bone.rotation[0], -bone.rotation[1], bone.rotation[2], bone.rotation[3]};
237 for (float value : bone.position) putFloat(skeletonBlob, value);
238 for (float value : bone.rotation) putFloat(skeletonBlob, value);
239 for (float value : bone.scale) putFloat(skeletonBlob, value);
240 }
241 std::vector<std::uint8_t> clipBlob{'E','V','A','N','I','M',0,1};
242 putFloat(clipBlob, fixture.duration); putFloat(clipBlob, fixture.rate); put32(clipBlob, fixture.loop ? 1 : 0);
243 put32(clipBlob, static_cast<std::uint32_t>(fixture.markers.size()));
244 put32(clipBlob, static_cast<std::uint32_t>(fixture.tracks.size()));
245 for (const auto& marker : fixture.markers) { putFloat(clipBlob, marker.time); putString(clipBlob, marker.name); }
246 for (auto track : fixture.tracks) {
247 put32(clipBlob, track.bone);
248 put32(clipBlob, static_cast<std::uint32_t>(track.position.size()));
249 put32(clipBlob, static_cast<std::uint32_t>(track.rotation.size()));
250 put32(clipBlob, static_cast<std::uint32_t>(track.scale.size()));
251 for (auto key : track.position) { putFloat(clipBlob, key.time); putFloat(clipBlob, key.value[0] * .01f); putFloat(clipBlob, key.value[1] * .01f); putFloat(clipBlob, -key.value[2] * .01f); }
252 for (auto key : track.rotation) { putFloat(clipBlob, key.time); putFloat(clipBlob, -key.value[0]); putFloat(clipBlob, -key.value[1]); putFloat(clipBlob, key.value[2]); putFloat(clipBlob, key.value[3]); }
253 for (auto key : track.scale) { putFloat(clipBlob, key.time); for (float value : key.value) putFloat(clipBlob, value); }
254 }
255 const std::string skeletonRoot = "assets/" + skeletonId.format() + "/";
256 const std::string clipRoot = "assets/" + clipId.format() + "/";
257 Value::Object skeletonDefinition{{"schema", Value("eve.skeleton")}, {"schemaVersion", Value(std::int64_t(1))},
258 {"boneCount", Value(static_cast<std::int64_t>(fixture.bones.size()))}, {"coordinateSystem", Value("right-handed-x-right-y-up-minus-z-forward")},
259 {"lengthUnit", Value("meter")}, {"blob", Value(skeletonRoot + "skeleton.bin")}};
260 Value::Object clipDefinition{{"schema", Value("eve.animation-clip")}, {"schemaVersion", Value(std::int64_t(1))},
261 {"name", Value(fixture.clipName)}, {"durationSeconds", Value(double(fixture.duration))},
262 {"sampleRate", Value(double(fixture.rate))}, {"loop", Value(fixture.loop)},
263 {"skeleton", Value(skeletonRef.value().format())}, {"blob", Value(clipRoot + "clip.bin")}};
264 auto skeletonJson = Value(std::move(skeletonDefinition)).toJson(); auto clipJson = Value(std::move(clipDefinition)).toJson();
265 if (!skeletonJson) return Result<PreparedAssetImport>::failure(skeletonJson.status());
266 if (!clipJson) return Result<PreparedAssetImport>::failure(clipJson.status());
267 output.manifest.assets.push_back({skeletonRef.value(), "eve.skeleton", SchemaVersion(1), skeletonRoot + "asset.json",
268 detail::sha256(std::span<const std::uint8_t>(reinterpret_cast<const std::uint8_t*>(skeletonJson.value().data()), skeletonJson.value().size())), {"animation", "skeleton"}});
269 output.manifest.assets.push_back({clipRef.value(), "eve.animation-clip", SchemaVersion(1), clipRoot + "asset.json",
270 detail::sha256(std::span<const std::uint8_t>(reinterpret_cast<const std::uint8_t*>(clipJson.value().data()), clipJson.value().size())), {"animation", "clip"}});
271 output.manifest.dependencies.push_back({clipRef.value(), skeletonRef.value(), asset::EvaDependencyKind::RuntimeRequired,
272 "skeleton", {}, "eve.skeleton/1"});
273 output.manifest.entrypoints.emplace("default", clipRef.value());
274 output.manifest.provenance["sourceName"] = Value(request.sourceName);
275 output.manifest.provenance["sourceHash"] = Value(detail::sha256(std::span<const std::uint8_t>(reinterpret_cast<const std::uint8_t*>(request.text.data()), request.text.size())));
276 output.manifest.provenance["sourceTransform"] = Value("centimeter,+Z-forward -> meter,-Z-forward");
277 output.entries = {{skeletonRoot + "asset.json", {skeletonJson.value().begin(), skeletonJson.value().end()}},
278 {skeletonRoot + "skeleton.bin", std::move(skeletonBlob)},
279 {clipRoot + "asset.json", {clipJson.value().begin(), clipJson.value().end()}},
280 {clipRoot + "clip.bin", std::move(clipBlob)}};
281 output.sourceMappings.push_back({request.sourceName + "#skeleton", skeletonRef.value()});
282 output.sourceMappings.push_back({request.sourceName + "#clip:" + fixture.clipName, clipRef.value()});
283 output.findings.push_back({request.sourceName, "EVA 1 animation fixture", ImportDisposition::Translated,
284 "legacy centimetre/+Z-forward data converted to canonical assets"});
285 auto report = detail::finalizeImportReport(output, request.package, "legacy-animation-text", "EVA 1");
286 if (!report) return Result<PreparedAssetImport>::failure(report.status());
288}
289
290} // namespace eve::asset_import
double value
float duration
std::string output
EvpackChunkInput input
Definition Evpack.cpp:170
const GltfImportRequest & request
std::uint32_t key
std::int32_t first
std::string text
std::uint32_t expectedPosition
std::vector< Marker > markers
std::vector< Track > tracks
std::vector< Bone > bones
std::array< float, 4 > rotation
std::array< float, 3 > position
std::uint32_t expectedScale
std::array< float, 3 > scale
std::string clipName
std::uint32_t bone
std::uint32_t expectedRotation
std::int32_t parent
Migration of legacy `*.anim.txt` fixtures to `.eva`.
bool ended
std::string name
const std::string * tag
graphics::Canvas * previous
bool finite
std::string error
Definition Package.cpp:60
std::string path
Definition PlayHost.cpp:110
float size
Definition TreeMesh.cpp:156
uint32_t index
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
std::map< std::string, Value > Object
Definition Value.h:34
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< asset::EvaManifest > baseManifest(const ImportPackageIdentity &package, std::string_view importer)
Base manifest.
Result< AssetRef > assetRef(PersistentId id)
Asset ref.
void put32(std::vector< std::uint8_t > &bytes, std::uint32_t value)
Append little-endian uint32 to owned storage.
void putFloat(std::vector< std::uint8_t > &bytes, float value)
Append IEEE FLOAT to owned storage.
Result< PreparedAssetImport > prepareLegacyAnimationImport(const LegacyAnimationImportRequest &request)
Convert a legacy text fixture into canonical skeleton and animation-clip assets.
eve::Diagnostic Diagnostic
eve::Result< T > Result
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
Owning request for the non-production EVA 1 animation text fixture.
Owning, validated source archive candidate not yet published to disk/database.