feat(animation): add reference-driven motion matching runtime
This commit is contained in:
1 parent
ac42525522
commit
2eef74b0fd
55 files changed
+6057
-1005
No files matched your search
@@ -0,0 +1,180 @@
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include "animation/AnimClip.h"
|
||||
#include "animation/AnimClipBinary.h"
|
||||
#include "animation/AnimParallelInternal.h"
|
||||
#include "animation/AnimPose.h"
|
||||
#include "animation/AnimSkeleton.h"
|
||||
#include "zeroerr/assert.h"
|
||||
#include "zeroerr/unittest.h"
|
||||
using namespace eve::animation;
|
||||
namespace {
|
||||
using Bytes = std::vector<std::byte>;
|
||||
void integer(Bytes& b, std::uint32_t v) {
|
||||
for (int i = 0; i < 4; ++i) b.push_back(static_cast<std::byte>((v >> (8 * i)) & 255));
|
||||
}
|
||||
void number(Bytes& b, float v) { integer(b, std::bit_cast<std::uint32_t>(v)); }
|
||||
void string(Bytes& b, const std::string& v) {
|
||||
integer(b, static_cast<std::uint32_t>(v.size()));
|
||||
for (auto c : v) b.push_back(static_cast<std::byte>(c));
|
||||
}
|
||||
Bytes fixture() {
|
||||
Bytes b;
|
||||
integer(b, 0x43415645);
|
||||
integer(b, 1);
|
||||
integer(b, 1);
|
||||
number(b, 2.f);
|
||||
number(b, 30.f);
|
||||
integer(b, 1);
|
||||
string(b, "test");
|
||||
string(b, "root");
|
||||
integer(b, 2);
|
||||
for (float t : {0.f, 2.f}) {
|
||||
number(b, t);
|
||||
number(b, 0.f);
|
||||
number(b, 0.f);
|
||||
number(b, 3.f * t);
|
||||
}
|
||||
integer(b, 1);
|
||||
number(b, 0.f);
|
||||
number(b, 0.f);
|
||||
number(b, 0.f);
|
||||
number(b, 0.f);
|
||||
number(b, 1.f);
|
||||
integer(b, 1);
|
||||
number(b, 0.f);
|
||||
number(b, 1.f);
|
||||
number(b, 1.f);
|
||||
number(b, 1.f);
|
||||
return b;
|
||||
}
|
||||
} // namespace
|
||||
TEST_CASE("animation.binary.parallelBatchMatchesSerialAndRejectsAtomically") {
|
||||
AnimSkeleton sk;
|
||||
sk.addBone("root");
|
||||
const auto bytes = fixture();
|
||||
for (int workers : {1, 2, 8}) {
|
||||
std::vector<AnimClip> clips(32);
|
||||
std::vector<AnimationTrackInput> inputs;
|
||||
for (auto& clip : clips) inputs.push_back({clip, bytes});
|
||||
auto loaded = loadAnimationTrackBatch(inputs, sk, workers);
|
||||
REQUIRE(loaded.ok());
|
||||
CHECK(loaded.value() >= 1);
|
||||
CHECK(loaded.value() <= workers);
|
||||
for (auto& clip : clips) {
|
||||
AnimPose pose;
|
||||
clip.sample(0.5f, &pose, &sk);
|
||||
CHECK(pose.getLocalPositionZ(0) == 1.5f);
|
||||
clip.setName("unchanged");
|
||||
}
|
||||
inputs[17].bytes = std::span<const std::byte>(bytes).first(12);
|
||||
auto rejected = loadAnimationTrackBatch(inputs, sk, workers);
|
||||
CHECK(!rejected.ok());
|
||||
for (auto& clip : clips) CHECK(clip.getName() == "unchanged");
|
||||
inputs[17].bytes = bytes;
|
||||
inputs[31].destination = clips[0];
|
||||
auto duplicate = loadAnimationTrackBatch(inputs, sk, workers);
|
||||
CHECK(!duplicate.ok());
|
||||
for (auto& clip : clips) CHECK(clip.getName() == "unchanged");
|
||||
}
|
||||
auto empty = loadAnimationTrackBatch({}, sk);
|
||||
REQUIRE(empty.ok());
|
||||
CHECK(empty.value() == 0);
|
||||
auto invalid = loadAnimationTrackBatch({}, sk, -1);
|
||||
CHECK(!invalid.ok());
|
||||
}
|
||||
TEST_CASE("animation.binary.parallelWorkersJoinBeforeFailureReturns") {
|
||||
std::vector<int> visited(64, 0);
|
||||
bool caught = false;
|
||||
try {
|
||||
eve::animation::detail::parallelAnimationItems(visited.size(), 8, [&](std::size_t i) {
|
||||
visited[i] = 1;
|
||||
if (i == 3 || i == 30) throw std::runtime_error(std::to_string(i));
|
||||
});
|
||||
} catch (const std::runtime_error& error) {
|
||||
caught = true;
|
||||
CHECK(std::string(error.what()) == "3");
|
||||
}
|
||||
CHECK(caught);
|
||||
for (int item : visited) CHECK(item == 1);
|
||||
}
|
||||
TEST_CASE("animation.binary.loadsTracksAndPreservesSampling") {
|
||||
AnimSkeleton sk;
|
||||
sk.addBone("root");
|
||||
sk.addBone("unanimated", 0);
|
||||
sk.setBindPosition(1, 0.f, 1.f, 0.f);
|
||||
AnimClip clip("old");
|
||||
auto loaded = loadAnimationTracks(clip, fixture(), sk);
|
||||
REQUIRE(loaded.ok());
|
||||
CHECK(clip.getName() == "test");
|
||||
CHECK(clip.getLoop());
|
||||
CHECK(clip.getDuration() == 2.f);
|
||||
AnimPose p;
|
||||
clip.sample(0.5f, &p, &sk);
|
||||
p.computeWorld(&sk);
|
||||
CHECK(std::abs(p.getWorldPositionZ(1) - 1.5f) < 1e-6f);
|
||||
CHECK(p.getWorldPositionY(1) == 1.f);
|
||||
clip.sample(2.5f, &p, &sk);
|
||||
CHECK(std::abs(p.getLocalPositionZ(0) - 1.5f) < 1e-6f);
|
||||
}
|
||||
TEST_CASE("animation.binary.rejectsEveryTruncationAtomically") {
|
||||
AnimSkeleton sk;
|
||||
sk.addBone("root");
|
||||
AnimClip clip("unchanged");
|
||||
clip.setDuration(4.f);
|
||||
const auto bytes = fixture();
|
||||
for (std::size_t size = 0; size < bytes.size(); ++size) {
|
||||
auto r = loadAnimationTracks(clip, {bytes.data(), size}, sk);
|
||||
CHECK(!r.ok());
|
||||
CHECK(clip.getName() == "unchanged");
|
||||
CHECK(clip.getDuration() == 4.f);
|
||||
}
|
||||
}
|
||||
TEST_CASE("animation.binary.rejectsVersionTrailingNamesFlagsAndNonfinite") {
|
||||
AnimSkeleton sk;
|
||||
sk.addBone("root");
|
||||
AnimClip clip("old");
|
||||
for (int bad : {0, 1, 2, 3, 4, 5, 6, 7}) {
|
||||
auto bytes = fixture();
|
||||
if (bad == 0) bytes[4] = std::byte{2};
|
||||
if (bad == 1) bytes.push_back(std::byte{0});
|
||||
if (bad == 2) bytes[36] = std::byte{'X'};
|
||||
if (bad == 3) bytes[20] = std::byte{2};
|
||||
if (bad == 4) {
|
||||
Bytes nan;
|
||||
number(nan, std::numeric_limits<float>::quiet_NaN());
|
||||
for (int i = 0; i < 4; ++i) bytes[12 + i] = nan[i];
|
||||
}
|
||||
if (bad == 5) bytes[36] = std::byte{0xc0};
|
||||
if (bad == 6)
|
||||
for (int i = 0; i < 4; ++i) bytes[60 + i] = std::byte{0};
|
||||
if (bad == 7)
|
||||
for (int i = 0; i < 4; ++i) bytes[96 + i] = std::byte{0};
|
||||
auto r = loadAnimationTracks(clip, bytes, sk);
|
||||
CHECK(!r.ok());
|
||||
CHECK(clip.getName() == "old");
|
||||
}
|
||||
}
|
||||
TEST_CASE("animation.binary.stepBoundaryAtAdjacentFloatTimes") {
|
||||
AnimClip clip("step");
|
||||
clip.setLoop(false);
|
||||
AnimSkeleton sk;
|
||||
sk.addBone("root");
|
||||
const float before = std::nextafter(1.f, 0.f);
|
||||
clip.addPositionKey(0, 0.f, 0.f, 0.f, 0.f);
|
||||
clip.addPositionKey(0, before, 0.f, 0.f, 0.f);
|
||||
clip.addPositionKey(0, 1.f, 0.f, 0.f, 2.f);
|
||||
clip.addPositionKey(0, 2.f, 0.f, 0.f, 2.f);
|
||||
AnimPose p;
|
||||
for (float t : {0.3f, before}) {
|
||||
clip.sample(t, &p, &sk);
|
||||
CHECK(p.getLocalPositionZ(0) == 0.f);
|
||||
}
|
||||
for (float t : {1.f, std::nextafter(1.f, 2.f), 1.8f}) {
|
||||
clip.sample(t, &p, &sk);
|
||||
CHECK(p.getLocalPositionZ(0) == 2.f);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user