Files
EVEngine/test/editor_skeleton_overlay.cpp
T
西风逍遥游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

109 lines
4.2 KiB
C++

#include "animation/editing/SkeletonOverlay.h"
#include "animation/AnimPose.h"
#include "animation/AnimSkeleton.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include <algorithm>
using namespace eve::animation_editing;
using namespace eve::editing;
namespace {
const GizmoPrimitive* primitive(const GizmoSnapshot& snapshot, const std::string& id) {
const auto found = std::find_if(snapshot.primitives.begin(), snapshot.primitives.end(),
[&](const auto& value) { return value.id == id; });
return found == snapshot.primitives.end() ? nullptr : &*found;
}
} // namespace
TEST_CASE("editor.animation.skeleton_overlay_builds_lines_axes_constraints_retarget_and_mask") {
SkeletonOverlayBone root;
root.id = StableId("root");
root.name = "root";
root.position = {0.0, 0.0, 0.0};
SkeletonOverlayBone spine;
spine.id = StableId("spine");
spine.parent = root.id;
spine.name = "spine";
spine.position = {0.0, 1.0, 0.0};
spine.selected = true;
spine.constrained = true;
spine.minimumAngles = {-0.5, -0.25};
spine.maximumAngles = {0.5, 0.75};
spine.retarget = SkeletonRetargetState::Matched;
SkeletonOverlayBone hand;
hand.id = StableId("hand");
hand.parent = spine.id;
hand.name = "hand";
hand.position = {1.0, 1.0, 0.0};
hand.retarget = SkeletonRetargetState::Unmatched;
hand.maskWeight = 0.0;
auto overlay = SkeletonOverlayBuilder().build("avatar", 17, {root, spine, hand});
REQUIRE_EQ(static_cast<int>(overlay.status), static_cast<int>(EditorStatus::Applied));
CHECK_EQ(overlay.targetRevision, Revision{17});
CHECK_EQ(overlay.primitives.size(), size_t{10});
REQUIRE(primitive(overlay, "spine:bone"));
CHECK_EQ(primitive(overlay, "spine:bone")->length, 1.0);
REQUIRE(primitive(overlay, "spine:axis-x"));
REQUIRE(primitive(overlay, "spine:constraint-yaw"));
REQUIRE(primitive(overlay, "hand:joint"));
CHECK(primitive(overlay, "hand:joint")->dashed);
CHECK(primitive(overlay, "hand:joint")->color[0] < 0.3);
CHECK(!primitive(overlay, "hand:axis-x"));
}
TEST_CASE("editor.animation.skeleton_overlay_rejects_cycles_missing_parents_and_budget") {
SkeletonOverlayBone first;
first.id = StableId("first");
first.parent = StableId("second");
SkeletonOverlayBone second;
second.id = StableId("second");
second.parent = StableId("first");
CHECK_EQ(static_cast<int>(SkeletonOverlayBuilder().build("cycle", 1, {first, second}).status),
static_cast<int>(EditorStatus::Failed));
first.parent = StableId("missing");
CHECK_EQ(static_cast<int>(SkeletonOverlayBuilder().build("missing", 1, {first}).status),
static_cast<int>(EditorStatus::Failed));
first.parent = {};
SkeletonOverlayOptions options;
options.maximumBones = 0;
CHECK_EQ(static_cast<int>(SkeletonOverlayBuilder().build("budget", 1, {first}, options).status),
static_cast<int>(EditorStatus::Failed));
options.maximumBones = 1;
first.maskWeight = 1.5;
CHECK_EQ(static_cast<int>(SkeletonOverlayBuilder().build("mask", 1, {first}, options).status),
static_cast<int>(EditorStatus::Failed));
}
TEST_CASE("editor.animation.runtime_skeleton_adapter_uses_world_pose_and_stable_names") {
eve::animation::AnimSkeleton skeleton;
const int root = skeleton.addBone("root");
const int hand = skeleton.addBone("hand", root);
eve::animation::AnimPose pose(skeleton.getBoneCount());
skeleton.applyBindPose(&pose);
pose.setLocalPosition(root, 2.f, 3.f, 4.f);
pose.setLocalPosition(hand, 0.f, 2.f, 0.f);
auto overlay = AnimationSkeletonOverlayAdapter().build(
&skeleton, &pose, "runtime-avatar", 9, "hand",
{{"root", "hips"}, {"hand", ""}}, {{"hand", 0.5}});
REQUIRE_EQ(static_cast<int>(overlay.status), static_cast<int>(EditorStatus::Applied));
REQUIRE(primitive(overlay, "root:joint"));
CHECK_EQ(primitive(overlay, "root:joint")->position[0], 2.0);
REQUIRE(primitive(overlay, "hand:joint"));
CHECK_EQ(primitive(overlay, "hand:joint")->position[1], 5.0);
CHECK(primitive(overlay, "hand:joint")->dashed);
REQUIRE(primitive(overlay, "hand:axis-z"));
}