| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <AzCore/IO/FileIO.h>
- #include <Tests/Integration/PoseComparisonFixture.h>
- #include <EMotionFX/Source/Actor.h>
- #include <EMotionFX/Source/AnimGraph.h>
- #include <EMotionFX/Source/MotionSet.h>
- #include <EMotionFX/Source/Node.h>
- #include <EMotionFX/Source/Recorder.h>
- #include <EMotionFX/Source/Skeleton.h>
- #include <EMotionFX/Source/KeyTrackLinearDynamic.h>
- #include <EMotionFX/Source/Importer/Importer.h>
- #include <Tests/Printers.h>
- namespace EMotionFX
- {
- void PrintTo(const Recorder::ActorInstanceData& actorInstanceData, ::std::ostream* os)
- {
- *os << actorInstanceData.m_actorInstance->GetActor()->GetName();
- }
- template<class ReturnType, class StorageType = ReturnType>
- void PrintTo(const KeyFrame<ReturnType, StorageType>* keyFrame, ::std::ostream* os)
- {
- *os << "(Time: " << keyFrame->GetTime() << ", Value: ";
- PrintTo(keyFrame->GetValue(), os);
- *os << ")";
- }
- template<class T>
- void PrintTo(const KeyTrackLinearDynamic<T>& keyTrack, ::std::ostream* os)
- {
- *os << "KeyTrackLinearDynamic<" << AZ::AzTypeInfo<T>::Name() << "> with " << keyTrack.GetNumKeys() << " keyframes";
- }
- void PrintTo(const Recorder::TransformTracks& tracks, ::std::ostream* os)
- {
- PrintTo(tracks.m_positions, os);
- PrintTo(tracks.m_rotations, os);
- }
- AZ_PUSH_DISABLE_WARNING(4100, "-Wmissing-declarations") // 'result_listener': unreferenced formal parameter
- MATCHER(FloatEq, "Test if two floats are close to each other")
- {
- return ::testing::ExplainMatchResult(::testing::FloatEq(::testing::get<1>(arg)), ::testing::get<0>(arg), result_listener);
- }
- MATCHER_P2(AZIsClose, expected, tolerance, "")
- {
- return expected.IsClose(arg, tolerance);
- }
- MATCHER_P(KeyIsClose, tolerance, "")
- {
- using LhsType = typename ::testing::tuple_element<0, arg_type>::type;
- using RhsType = typename ::testing::tuple_element<1, arg_type>::type;
- using ::testing::get;
- LhsType got = get<0>(arg);
- RhsType expected = get<1>(arg);
- return ::testing::ExplainMatchResult(::testing::FloatEq(expected->GetTime()), got->GetTime(), result_listener)
- && ::testing::ExplainMatchResult(AZIsClose(expected->GetValue(), tolerance), got->GetValue(), result_listener);
- }
- AZ_POP_DISABLE_WARNING
- // This class is modeled after the built-in testing::Pointwise fixture. It
- // doesn't work for our use case because the KeyTrack class does not have
- // an STL-like interface, and it is overly verbose for large containers.
- // This matcher will ensure that the key tracks have the same number of
- // keys, and that each key is "close".
- template<class T>
- class KeyTrackMatcher
- : public ::testing::MatcherInterface<const KeyTrackLinearDynamic<T>&>
- {
- public:
- using InnerMatcherArg = ::testing::tuple<const KeyFrame<T>*, const KeyFrame<T>*>;
- KeyTrackMatcher(const KeyTrackLinearDynamic<T>& expected, const char* nodeName)
- : m_expected(expected)
- , m_nodeName(nodeName)
- {
- }
- bool MatchAndExplain(const KeyTrackLinearDynamic<T>& got, ::testing::MatchResultListener* result_listener) const override
- {
- const size_t gotSize = got.GetNumKeys();
- const size_t expectedSize = m_expected.GetNumKeys();
- const size_t commonSize = AZStd::min(gotSize, expectedSize);
- for (size_t i = 0; i != commonSize; ++i)
- {
- const KeyFrame<T>* gotKey = got.GetKey(i);
- const KeyFrame<T>* expectedKey = m_expected.GetKey(i);
- const auto innerMatcher = ::testing::SafeMatcherCast<InnerMatcherArg>(KeyIsClose(0.01f));
- if (!innerMatcher.MatchAndExplain(::testing::make_tuple(gotKey, expectedKey), result_listener))
- {
- *result_listener << "where the value pair at index #" << i << " don't match\n";
- const uint32 numContextLines = 2;
- const size_t beginContextLines = i > numContextLines ? i - numContextLines : 0;
- const size_t endContextLines = i > commonSize - numContextLines - 1 ? commonSize : i + numContextLines + 1;
- for (size_t contextIndex = beginContextLines; contextIndex < endContextLines; ++contextIndex)
- {
- const bool contextLineMatches = ::testing::Matches(innerMatcher)(::testing::make_tuple(got.GetKey(contextIndex), m_expected.GetKey(contextIndex)));
- if (!contextLineMatches)
- {
- *result_listener << "\033[0;31m"; // red
- }
- *result_listener << contextIndex << ": Expected: ";
- PrintTo(m_expected.GetKey(contextIndex), result_listener->stream());
- *result_listener << "\n" << contextIndex << ": Actual: ";
- PrintTo(got.GetKey(contextIndex), result_listener->stream());
- if (!contextLineMatches)
- {
- *result_listener << "\033[0;m";
- }
- if (contextIndex != endContextLines-1)
- {
- *result_listener << "\n";
- }
- }
- return false;
- }
- }
- return gotSize == expectedSize;
- }
- void DescribeTo(::std::ostream* os) const override
- {
- PrintTo(m_expected, os);
- *os << " for node " << m_nodeName;
- }
- void DescribeNegationTo(::std::ostream* os) const override
- {
- PrintTo(m_expected, os);
- *os << " for node " << m_nodeName << " shouldn't match";
- }
- private:
- const KeyTrackLinearDynamic<T>& m_expected;
- const char* m_nodeName;
- };
- template<class T>
- inline ::testing::Matcher<const KeyTrackLinearDynamic<T>&> MatchesKeyTrack(const KeyTrackLinearDynamic<T>& expected, const char* nodeName) {
- return MakeMatcher(new KeyTrackMatcher<T>(expected, nodeName));
- }
- void PoseComparisonFixture::SetUp()
- {
- SystemComponentFixture::SetUp();
- LoadAssets();
- }
- void PoseComparisonFixture::TearDown()
- {
- m_actorInstance->Destroy();
- m_actor.reset();
- delete m_motionSet;
- m_motionSet = nullptr;
- delete m_animGraph;
- m_animGraph = nullptr;
- SystemComponentFixture::TearDown();
- }
- void PoseComparisonFixture::LoadAssets()
- {
- const AZStd::string actorPath = ResolvePath(GetParam().m_actorFile);
- m_actor = EMotionFX::GetImporter().LoadActor(actorPath);
- ASSERT_TRUE(m_actor) << "Failed to load actor";
- const AZStd::string animGraphPath = ResolvePath(GetParam().m_animGraphFile);
- m_animGraph = EMotionFX::GetImporter().LoadAnimGraph(animGraphPath);
- ASSERT_TRUE(m_animGraph) << "Failed to load anim graph";
- const AZStd::string motionSetPath = ResolvePath(GetParam().m_motionSetFile);
- m_motionSet = EMotionFX::GetImporter().LoadMotionSet(motionSetPath);
- ASSERT_TRUE(m_motionSet) << "Failed to load motion set";
- m_motionSet->Preload();
- m_actorInstance = ActorInstance::Create(m_actor.get());
- m_actorInstance->SetAnimGraphInstance(AnimGraphInstance::Create(m_animGraph, m_actorInstance, m_motionSet));
- }
- TEST_P(PoseComparisonFixture, TestPoses)
- {
- const AZStd::string recordingPath = ResolvePath(GetParam().m_recordingFile);
- Recorder* recording = EMotionFX::Recorder::LoadFromFile(recordingPath.c_str());
- const EMotionFX::Recorder::ActorInstanceData& expectedActorInstanceData = recording->GetActorInstanceData(0);
- EMotionFX::GetRecorder().StartRecording(recording->GetRecordSettings());
- for (const float timeDelta : recording->GetTimeDeltas())
- {
- EXPECT_GE(timeDelta, 0) << "Expected a positive time delta";
- EMotionFX::GetEMotionFX().Update(timeDelta);
- }
- EMotionFX::Recorder::ActorInstanceData& gotActorInstanceData = EMotionFX::GetRecorder().GetActorInstanceData(0);
- // Make sure that the captured times match the expected times
- EXPECT_THAT(GetRecorder().GetTimeDeltas(), ::testing::Pointwise(FloatEq(), recording->GetTimeDeltas()));
- const AZStd::vector<Recorder::TransformTracks>& gotTracks = gotActorInstanceData.m_transformTracks;
- const AZStd::vector<Recorder::TransformTracks>& expectedTracks = expectedActorInstanceData.m_transformTracks;
- EXPECT_EQ(gotTracks.size(), expectedTracks.size()) << "recording has a different number of transform tracks";
- const size_t numberOfItemsInCommon = AZStd::min(gotTracks.size(), expectedTracks.size());
- for (size_t trackNum = 0; trackNum < numberOfItemsInCommon; ++trackNum)
- {
- const Recorder::TransformTracks& gotTrack = gotTracks[trackNum];
- const Recorder::TransformTracks& expectedTrack = expectedTracks[trackNum];
- const char* nodeName = gotActorInstanceData.m_actorInstance->GetActor()->GetSkeleton()->GetNode(trackNum)->GetName();
- EXPECT_THAT(gotTrack.m_positions, MatchesKeyTrack(expectedTrack.m_positions, nodeName));
- EXPECT_THAT(gotTrack.m_rotations, MatchesKeyTrack(expectedTrack.m_rotations, nodeName));
- }
- recording->Destroy();
- }
- TEST_P(TestPoseComparisonFixture, TestRecording)
- {
- // Make one recording, 10 seconds at 60 fps
- Recorder::RecordSettings settings;
- settings.m_fps = 1000000;
- settings.m_recordTransforms = true;
- settings.m_recordAnimGraphStates = false;
- settings.m_recordNodeHistory = false;
- settings.m_recordScale = false;
- settings.m_initialAnimGraphAnimBytes = 4 * 1024 * 1024; // 4 mb
- settings.m_historyStatesOnly = false;
- settings.m_recordEvents = false;
- EMotionFX::GetRecorder().StartRecording(settings);
- const float fps = 60.0f;
- const float fixedTimeDelta = 1.0f / fps;
- for (uint32 keyID = 0; keyID < fps * 10.0f; ++keyID)
- {
- EMotionFX::GetEMotionFX().Update(fixedTimeDelta);
- }
- AZStd::vector<AZ::u8> buffer;
- AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8>> stream(&buffer);
- const bool serializeSuccess = AZ::Utils::SaveObjectToStream(stream, AZ::ObjectStream::ST_BINARY, &EMotionFX::GetRecorder());
- ASSERT_TRUE(serializeSuccess);
- stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
- Recorder* recording = AZ::Utils::LoadObjectFromStream<Recorder>(stream);
- m_actorInstance->Destroy();
- m_actorInstance = ActorInstance::Create(m_actor.get());
- m_actorInstance->SetAnimGraphInstance(AnimGraphInstance::Create(m_animGraph, m_actorInstance, m_motionSet));
- EMotionFX::GetRecorder().StartRecording(settings);
- for (const float timeDelta : recording->GetTimeDeltas())
- {
- EMotionFX::GetEMotionFX().Update(timeDelta);
- }
- const EMotionFX::Recorder::ActorInstanceData& expectedActorInstanceData = recording->GetActorInstanceData(0);
- const EMotionFX::Recorder::ActorInstanceData& gotActorInstanceData = EMotionFX::GetRecorder().GetActorInstanceData(0);
- // Make sure that the captured times match the expected times
- EXPECT_THAT(GetRecorder().GetTimeDeltas(), ::testing::Pointwise(FloatEq(), recording->GetTimeDeltas()));
- const AZStd::vector<Recorder::TransformTracks>& gotTracks = gotActorInstanceData.m_transformTracks;
- const AZStd::vector<Recorder::TransformTracks>& expectedTracks = expectedActorInstanceData.m_transformTracks;
- EXPECT_EQ(gotTracks.size(), expectedTracks.size()) << "recording has a different number of transform tracks";
- const size_t numberOfItemsInCommon = AZStd::min(gotTracks.size(), expectedTracks.size());
- for (size_t trackNum = 0; trackNum < numberOfItemsInCommon; ++trackNum)
- {
- const Recorder::TransformTracks& gotTrack = gotTracks[trackNum];
- const Recorder::TransformTracks& expectedTrack = expectedTracks[trackNum];
- const char* nodeName = gotActorInstanceData.m_actorInstance->GetActor()->GetSkeleton()->GetNode(trackNum)->GetName();
- EXPECT_THAT(gotTrack.m_positions, MatchesKeyTrack(expectedTrack.m_positions, nodeName));
- EXPECT_THAT(gotTrack.m_rotations, MatchesKeyTrack(expectedTrack.m_rotations, nodeName));
- }
- recording->Destroy();
- }
- INSTANTIATE_TEST_CASE_P(DISABLED_TestPoses, PoseComparisonFixture,
- ::testing::Values(
- PoseComparisonFixtureParams (
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.actor",
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.animgraph",
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.motionset",
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.emfxrecording"
- ),
- PoseComparisonFixtureParams (
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.actor",
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.animgraph",
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.motionset",
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.emfxrecording"
- )
- )
- );
- INSTANTIATE_TEST_CASE_P(DISABLED_TestPoseComparison, TestPoseComparisonFixture,
- ::testing::Values(
- PoseComparisonFixtureParams (
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.actor",
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.animgraph",
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.motionset",
- "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.emfxrecording"
- )
- )
- );
- }; // namespace EMotionFX
|