Files
EVEngine/test/asset_import_animation.cpp
西风逍遥游andCursor 24502babef Nest satellite packages under their host modules.
Keep CMake module names and trim switches, but stop flattening editing/editor and runtime bridges as top-level src/modules directories. Parent scans skip nested facets so runtime-3d can drop authoring by directory leaf.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-06 17:56:12 +08:00

82 lines
3.6 KiB
C++

#include "asset/AssetCooker.h"
#include "asset/import/LegacyAnimationImporter.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include <bit>
#include <fstream>
#include <sstream>
using namespace eve;
using namespace eve::asset;
using namespace eve::asset_import;
namespace {
ImportPackageIdentity identity() {
auto id = PersistentId::parse("018f6f22-2490-7ad2-bf58-4f1dbca31040");
REQUIRE(id.has_value());
return {*id, "mixamo.idle", "1.0.0",
{{"license", Value(Value::Object{{"redistribution", Value("project-only")}})}}};
}
std::string fixture() {
std::ifstream input("test/assets/mixamo/Idle.anim.txt");
REQUIRE(input.good());
std::ostringstream text; text << input.rdbuf(); return text.str();
}
AssetCookProfile target(std::string os, std::string arch, std::string graphics,
std::string shader) {
AssetCookProfile result;
result.variant = {std::move(os), std::move(arch), std::move(graphics), {"rgba8"},
std::move(shader), "high", {}};
result.chunkCodec = EvpackCodec::Zstd;
return result;
}
} // namespace
TEST_CASE("asset.import.animationFixtureBecomesCanonicalEvaAndTwoRuntimeTargets") {
auto prepared = prepareLegacyAnimationImport(
{identity(), "Idle.anim.txt", fixture(), {}});
REQUIRE(prepared.ok());
REQUIRE_EQ(prepared.value().manifest.assets.size(), std::size_t(2));
REQUIRE_EQ(prepared.value().manifest.dependencies.size(), std::size_t(1));
CHECK_EQ(prepared.value().manifest.dependencies.front().expectedType,
std::string("eve.skeleton/1"));
auto archiveBytes = buildEvaArchive(prepared.value().manifest, prepared.value().entries);
REQUIRE(archiveBytes.ok());
auto archive = parseEvaArchive(archiveBytes.value());
REQUIRE(archive.ok());
const auto skeleton = std::find_if(archive.value().entries.begin(), archive.value().entries.end(),
[](const EvaArchiveEntry& entry) {
return entry.path.ends_with("skeleton.bin");
});
REQUIRE(skeleton != archive.value().entries.end());
REQUIRE(skeleton->bytes.size() > 32);
CHECK_EQ(std::string(reinterpret_cast<const char*>(skeleton->bytes.data()), 6), std::string("EVSKEL"));
// First root bind translation remains zero after centimeter -> meter conversion.
const std::uint32_t firstPositionBits = std::uint32_t(skeleton->bytes[28]) |
(std::uint32_t(skeleton->bytes[29]) << 8) | (std::uint32_t(skeleton->bytes[30]) << 16) |
(std::uint32_t(skeleton->bytes[31]) << 24);
CHECK_EQ(std::bit_cast<float>(firstPositionBits), 0.0f);
auto windows = cookEvaToEvpack(archive.value(), target("windows", "x86_64", "vulkan", "spirv-1.6"));
auto web = cookEvaToEvpack(archive.value(), target("web", "wasm32", "webgpu", "wgsl-1"));
REQUIRE(windows.ok()); REQUIRE(web.ok());
CHECK(windows.value().buildId != web.value().buildId);
REQUIRE(parseEvpack(windows.value().bytes).ok());
REQUIRE(parseEvpack(web.value().bytes).ok());
}
TEST_CASE("asset.import.animationFixtureRejectsDeclaredKeyCountMismatch") {
std::string text = fixture();
const auto track = text.find("track ");
REQUIRE(track != std::string::npos);
const auto firstCount = text.find(' ', track + 6);
REQUIRE(firstCount != std::string::npos);
text.insert(firstCount + 1, "9");
auto rejected = prepareLegacyAnimationImport({identity(), "corrupt.anim.txt", std::move(text), {}});
REQUIRE(!rejected.ok());
CHECK_EQ(rejected.error()->code(), DiagnosticCode::ParseError);
}