3
0

PoseComparisonTests.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/IO/FileIO.h>
  9. #include <Tests/Integration/PoseComparisonFixture.h>
  10. #include <EMotionFX/Source/Actor.h>
  11. #include <EMotionFX/Source/AnimGraph.h>
  12. #include <EMotionFX/Source/MotionSet.h>
  13. #include <EMotionFX/Source/Node.h>
  14. #include <EMotionFX/Source/Recorder.h>
  15. #include <EMotionFX/Source/Skeleton.h>
  16. #include <EMotionFX/Source/KeyTrackLinearDynamic.h>
  17. #include <EMotionFX/Source/Importer/Importer.h>
  18. #include <Tests/Printers.h>
  19. namespace EMotionFX
  20. {
  21. void PrintTo(const Recorder::ActorInstanceData& actorInstanceData, ::std::ostream* os)
  22. {
  23. *os << actorInstanceData.m_actorInstance->GetActor()->GetName();
  24. }
  25. template<class ReturnType, class StorageType = ReturnType>
  26. void PrintTo(const KeyFrame<ReturnType, StorageType>* keyFrame, ::std::ostream* os)
  27. {
  28. *os << "(Time: " << keyFrame->GetTime() << ", Value: ";
  29. PrintTo(keyFrame->GetValue(), os);
  30. *os << ")";
  31. }
  32. template<class T>
  33. void PrintTo(const KeyTrackLinearDynamic<T>& keyTrack, ::std::ostream* os)
  34. {
  35. *os << "KeyTrackLinearDynamic<" << AZ::AzTypeInfo<T>::Name() << "> with " << keyTrack.GetNumKeys() << " keyframes";
  36. }
  37. void PrintTo(const Recorder::TransformTracks& tracks, ::std::ostream* os)
  38. {
  39. PrintTo(tracks.m_positions, os);
  40. PrintTo(tracks.m_rotations, os);
  41. }
  42. AZ_PUSH_DISABLE_WARNING(4100, "-Wmissing-declarations") // 'result_listener': unreferenced formal parameter
  43. MATCHER(FloatEq, "Test if two floats are close to each other")
  44. {
  45. return ::testing::ExplainMatchResult(::testing::FloatEq(::testing::get<1>(arg)), ::testing::get<0>(arg), result_listener);
  46. }
  47. MATCHER_P2(AZIsClose, expected, tolerance, "")
  48. {
  49. return expected.IsClose(arg, tolerance);
  50. }
  51. MATCHER_P(KeyIsClose, tolerance, "")
  52. {
  53. using LhsType = typename ::testing::tuple_element<0, arg_type>::type;
  54. using RhsType = typename ::testing::tuple_element<1, arg_type>::type;
  55. using ::testing::get;
  56. LhsType got = get<0>(arg);
  57. RhsType expected = get<1>(arg);
  58. return ::testing::ExplainMatchResult(::testing::FloatEq(expected->GetTime()), got->GetTime(), result_listener)
  59. && ::testing::ExplainMatchResult(AZIsClose(expected->GetValue(), tolerance), got->GetValue(), result_listener);
  60. }
  61. AZ_POP_DISABLE_WARNING
  62. // This class is modeled after the built-in testing::Pointwise fixture. It
  63. // doesn't work for our use case because the KeyTrack class does not have
  64. // an STL-like interface, and it is overly verbose for large containers.
  65. // This matcher will ensure that the key tracks have the same number of
  66. // keys, and that each key is "close".
  67. template<class T>
  68. class KeyTrackMatcher
  69. : public ::testing::MatcherInterface<const KeyTrackLinearDynamic<T>&>
  70. {
  71. public:
  72. using InnerMatcherArg = ::testing::tuple<const KeyFrame<T>*, const KeyFrame<T>*>;
  73. KeyTrackMatcher(const KeyTrackLinearDynamic<T>& expected, const char* nodeName)
  74. : m_expected(expected)
  75. , m_nodeName(nodeName)
  76. {
  77. }
  78. bool MatchAndExplain(const KeyTrackLinearDynamic<T>& got, ::testing::MatchResultListener* result_listener) const override
  79. {
  80. const size_t gotSize = got.GetNumKeys();
  81. const size_t expectedSize = m_expected.GetNumKeys();
  82. const size_t commonSize = AZStd::min(gotSize, expectedSize);
  83. for (size_t i = 0; i != commonSize; ++i)
  84. {
  85. const KeyFrame<T>* gotKey = got.GetKey(i);
  86. const KeyFrame<T>* expectedKey = m_expected.GetKey(i);
  87. const auto innerMatcher = ::testing::SafeMatcherCast<InnerMatcherArg>(KeyIsClose(0.01f));
  88. if (!innerMatcher.MatchAndExplain(::testing::make_tuple(gotKey, expectedKey), result_listener))
  89. {
  90. *result_listener << "where the value pair at index #" << i << " don't match\n";
  91. const uint32 numContextLines = 2;
  92. const size_t beginContextLines = i > numContextLines ? i - numContextLines : 0;
  93. const size_t endContextLines = i > commonSize - numContextLines - 1 ? commonSize : i + numContextLines + 1;
  94. for (size_t contextIndex = beginContextLines; contextIndex < endContextLines; ++contextIndex)
  95. {
  96. const bool contextLineMatches = ::testing::Matches(innerMatcher)(::testing::make_tuple(got.GetKey(contextIndex), m_expected.GetKey(contextIndex)));
  97. if (!contextLineMatches)
  98. {
  99. *result_listener << "\033[0;31m"; // red
  100. }
  101. *result_listener << contextIndex << ": Expected: ";
  102. PrintTo(m_expected.GetKey(contextIndex), result_listener->stream());
  103. *result_listener << "\n" << contextIndex << ": Actual: ";
  104. PrintTo(got.GetKey(contextIndex), result_listener->stream());
  105. if (!contextLineMatches)
  106. {
  107. *result_listener << "\033[0;m";
  108. }
  109. if (contextIndex != endContextLines-1)
  110. {
  111. *result_listener << "\n";
  112. }
  113. }
  114. return false;
  115. }
  116. }
  117. return gotSize == expectedSize;
  118. }
  119. void DescribeTo(::std::ostream* os) const override
  120. {
  121. PrintTo(m_expected, os);
  122. *os << " for node " << m_nodeName;
  123. }
  124. void DescribeNegationTo(::std::ostream* os) const override
  125. {
  126. PrintTo(m_expected, os);
  127. *os << " for node " << m_nodeName << " shouldn't match";
  128. }
  129. private:
  130. const KeyTrackLinearDynamic<T>& m_expected;
  131. const char* m_nodeName;
  132. };
  133. template<class T>
  134. inline ::testing::Matcher<const KeyTrackLinearDynamic<T>&> MatchesKeyTrack(const KeyTrackLinearDynamic<T>& expected, const char* nodeName) {
  135. return MakeMatcher(new KeyTrackMatcher<T>(expected, nodeName));
  136. }
  137. void PoseComparisonFixture::SetUp()
  138. {
  139. SystemComponentFixture::SetUp();
  140. LoadAssets();
  141. }
  142. void PoseComparisonFixture::TearDown()
  143. {
  144. m_actorInstance->Destroy();
  145. m_actor.reset();
  146. delete m_motionSet;
  147. m_motionSet = nullptr;
  148. delete m_animGraph;
  149. m_animGraph = nullptr;
  150. SystemComponentFixture::TearDown();
  151. }
  152. void PoseComparisonFixture::LoadAssets()
  153. {
  154. const AZStd::string actorPath = ResolvePath(GetParam().m_actorFile);
  155. m_actor = EMotionFX::GetImporter().LoadActor(actorPath);
  156. ASSERT_TRUE(m_actor) << "Failed to load actor";
  157. const AZStd::string animGraphPath = ResolvePath(GetParam().m_animGraphFile);
  158. m_animGraph = EMotionFX::GetImporter().LoadAnimGraph(animGraphPath);
  159. ASSERT_TRUE(m_animGraph) << "Failed to load anim graph";
  160. const AZStd::string motionSetPath = ResolvePath(GetParam().m_motionSetFile);
  161. m_motionSet = EMotionFX::GetImporter().LoadMotionSet(motionSetPath);
  162. ASSERT_TRUE(m_motionSet) << "Failed to load motion set";
  163. m_motionSet->Preload();
  164. m_actorInstance = ActorInstance::Create(m_actor.get());
  165. m_actorInstance->SetAnimGraphInstance(AnimGraphInstance::Create(m_animGraph, m_actorInstance, m_motionSet));
  166. }
  167. TEST_P(PoseComparisonFixture, TestPoses)
  168. {
  169. const AZStd::string recordingPath = ResolvePath(GetParam().m_recordingFile);
  170. Recorder* recording = EMotionFX::Recorder::LoadFromFile(recordingPath.c_str());
  171. const EMotionFX::Recorder::ActorInstanceData& expectedActorInstanceData = recording->GetActorInstanceData(0);
  172. EMotionFX::GetRecorder().StartRecording(recording->GetRecordSettings());
  173. for (const float timeDelta : recording->GetTimeDeltas())
  174. {
  175. EXPECT_GE(timeDelta, 0) << "Expected a positive time delta";
  176. EMotionFX::GetEMotionFX().Update(timeDelta);
  177. }
  178. EMotionFX::Recorder::ActorInstanceData& gotActorInstanceData = EMotionFX::GetRecorder().GetActorInstanceData(0);
  179. // Make sure that the captured times match the expected times
  180. EXPECT_THAT(GetRecorder().GetTimeDeltas(), ::testing::Pointwise(FloatEq(), recording->GetTimeDeltas()));
  181. const AZStd::vector<Recorder::TransformTracks>& gotTracks = gotActorInstanceData.m_transformTracks;
  182. const AZStd::vector<Recorder::TransformTracks>& expectedTracks = expectedActorInstanceData.m_transformTracks;
  183. EXPECT_EQ(gotTracks.size(), expectedTracks.size()) << "recording has a different number of transform tracks";
  184. const size_t numberOfItemsInCommon = AZStd::min(gotTracks.size(), expectedTracks.size());
  185. for (size_t trackNum = 0; trackNum < numberOfItemsInCommon; ++trackNum)
  186. {
  187. const Recorder::TransformTracks& gotTrack = gotTracks[trackNum];
  188. const Recorder::TransformTracks& expectedTrack = expectedTracks[trackNum];
  189. const char* nodeName = gotActorInstanceData.m_actorInstance->GetActor()->GetSkeleton()->GetNode(trackNum)->GetName();
  190. EXPECT_THAT(gotTrack.m_positions, MatchesKeyTrack(expectedTrack.m_positions, nodeName));
  191. EXPECT_THAT(gotTrack.m_rotations, MatchesKeyTrack(expectedTrack.m_rotations, nodeName));
  192. }
  193. recording->Destroy();
  194. }
  195. TEST_P(TestPoseComparisonFixture, TestRecording)
  196. {
  197. // Make one recording, 10 seconds at 60 fps
  198. Recorder::RecordSettings settings;
  199. settings.m_fps = 1000000;
  200. settings.m_recordTransforms = true;
  201. settings.m_recordAnimGraphStates = false;
  202. settings.m_recordNodeHistory = false;
  203. settings.m_recordScale = false;
  204. settings.m_initialAnimGraphAnimBytes = 4 * 1024 * 1024; // 4 mb
  205. settings.m_historyStatesOnly = false;
  206. settings.m_recordEvents = false;
  207. EMotionFX::GetRecorder().StartRecording(settings);
  208. const float fps = 60.0f;
  209. const float fixedTimeDelta = 1.0f / fps;
  210. for (uint32 keyID = 0; keyID < fps * 10.0f; ++keyID)
  211. {
  212. EMotionFX::GetEMotionFX().Update(fixedTimeDelta);
  213. }
  214. AZStd::vector<AZ::u8> buffer;
  215. AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8>> stream(&buffer);
  216. const bool serializeSuccess = AZ::Utils::SaveObjectToStream(stream, AZ::ObjectStream::ST_BINARY, &EMotionFX::GetRecorder());
  217. ASSERT_TRUE(serializeSuccess);
  218. stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
  219. Recorder* recording = AZ::Utils::LoadObjectFromStream<Recorder>(stream);
  220. m_actorInstance->Destroy();
  221. m_actorInstance = ActorInstance::Create(m_actor.get());
  222. m_actorInstance->SetAnimGraphInstance(AnimGraphInstance::Create(m_animGraph, m_actorInstance, m_motionSet));
  223. EMotionFX::GetRecorder().StartRecording(settings);
  224. for (const float timeDelta : recording->GetTimeDeltas())
  225. {
  226. EMotionFX::GetEMotionFX().Update(timeDelta);
  227. }
  228. const EMotionFX::Recorder::ActorInstanceData& expectedActorInstanceData = recording->GetActorInstanceData(0);
  229. const EMotionFX::Recorder::ActorInstanceData& gotActorInstanceData = EMotionFX::GetRecorder().GetActorInstanceData(0);
  230. // Make sure that the captured times match the expected times
  231. EXPECT_THAT(GetRecorder().GetTimeDeltas(), ::testing::Pointwise(FloatEq(), recording->GetTimeDeltas()));
  232. const AZStd::vector<Recorder::TransformTracks>& gotTracks = gotActorInstanceData.m_transformTracks;
  233. const AZStd::vector<Recorder::TransformTracks>& expectedTracks = expectedActorInstanceData.m_transformTracks;
  234. EXPECT_EQ(gotTracks.size(), expectedTracks.size()) << "recording has a different number of transform tracks";
  235. const size_t numberOfItemsInCommon = AZStd::min(gotTracks.size(), expectedTracks.size());
  236. for (size_t trackNum = 0; trackNum < numberOfItemsInCommon; ++trackNum)
  237. {
  238. const Recorder::TransformTracks& gotTrack = gotTracks[trackNum];
  239. const Recorder::TransformTracks& expectedTrack = expectedTracks[trackNum];
  240. const char* nodeName = gotActorInstanceData.m_actorInstance->GetActor()->GetSkeleton()->GetNode(trackNum)->GetName();
  241. EXPECT_THAT(gotTrack.m_positions, MatchesKeyTrack(expectedTrack.m_positions, nodeName));
  242. EXPECT_THAT(gotTrack.m_rotations, MatchesKeyTrack(expectedTrack.m_rotations, nodeName));
  243. }
  244. recording->Destroy();
  245. }
  246. INSTANTIATE_TEST_CASE_P(DISABLED_TestPoses, PoseComparisonFixture,
  247. ::testing::Values(
  248. PoseComparisonFixtureParams (
  249. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.actor",
  250. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.animgraph",
  251. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.motionset",
  252. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.emfxrecording"
  253. ),
  254. PoseComparisonFixtureParams (
  255. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.actor",
  256. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.animgraph",
  257. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.motionset",
  258. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.emfxrecording"
  259. )
  260. )
  261. );
  262. INSTANTIATE_TEST_CASE_P(DISABLED_TestPoseComparison, TestPoseComparisonFixture,
  263. ::testing::Values(
  264. PoseComparisonFixtureParams (
  265. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.actor",
  266. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.animgraph",
  267. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.motionset",
  268. "@exefolder@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.emfxrecording"
  269. )
  270. )
  271. );
  272. }; // namespace EMotionFX