Files
o3de/Gems/EMotionFX/Code/Tests/Integration/PoseComparisonTests.cpp
T
lumberyard-employee-dm f648cb1fd8 Update the FileIO Aliases (#4186)
* Update the FileIOAlias naming to make the cache, project root and engine
root paths more clear

The alias of `@root@`, `@assets@`, and `@projectplatformcache@` has been
collapsed to `@projectproductassets@`

The alias of `@devroot@` and `@engroot@` has been collapsed to
`@engroot@`

The alias of `@devassets@` and `@projectroot@` has been collapsed to
`@projectroot@`

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated use of devassets and devroot properties in python

Those properties now use projectroot and engroot

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updating the alias @engroot@ alias path comment in each platform specific LocalFileIO_*.cpp file

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed hardcoded size of 9 for the product asset alias.

The ResolvePath function now just appends the @projectproductassets@
alias with the input path

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Remove duplicate @projectproductassets@ check in ProcessFileTreeRequest

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fix for typos in Hydra python test

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated LocalFileIO::Copy call on Windows to use the Unicode aware CopyFileW API

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the AWSMetreicsGemAllocatorFixture to properly suppress asset
cache write errors for Test file creation.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed unneeded call to set the @projectproductasstes@ alias at the bottom of the AssetSeedManagerTest SetUp

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Added a deprecated alias map to the FileIO System

When a deprecated alias is accessed, the FileIO System logs an AZ_Error and indicates the alias that should be used

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated python test scripts to use the projectroot binding

Retrieving the AutomatedTesting project path based on "<devroot>/AutomatedTesting" has been removed.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated references to devroot and devgame within the codebase

The GetAbsoluteDevGameFolderPath functions has been replaced with direct call to AZ::Utils::GetProjectPath
The GetAbsoluteDevRootFolderPath functions has been replaced with direct calls to AZ::Utils::GetEnginePath

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated <engroot>/AutomatedTesting references to projectroot


Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Replaced references that assumes the project path is <engroot>/AutomatedTesting with <projectroot> in the AutomatedTesting python test

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Correct casing in emfxworkspace file


Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed newly added AppendPathParts function
Removed the Path constructors which accepts a PathIterable instance

The PathIterable isn't safe to return to a user of the Path class as it might be referencing temporary paths supplied via PathView arguments

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed unused parameter warning

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Undid change to the LexicallyProximate function to set the path result to the base path.

It needs to return the *this path if the pathResult is empty

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Moved the LocalFileIO ConvertToAbsolutePath implementations to AZ::Utils

Fixed the ConvertToAbsolutePath implementation for Unix platforms to use a buffer that is size PATH_MAX(4096 on all our supported Unix platforms).
Because the buffer before was AZ::IO::MaxPathLength which as a size of 1024, this was resulting in the Unix `realpath` function smashing the stack when it converts a path to an absolute path that is longer than 1024 characters

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the EditorCore.Tests to attach the AZ Environment to the EditorCore shared library that is statically loaded on launch.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed for DeprecatedAlaisesKeyVisitor Visit function causing the non string_view overloads being hidden causing a hidden base class function warning

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Changed the AWSMetricsGemMock to use a temporary for writing test assets

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the LocalFileIO::ResolvePath function to use HasRootPath to determine if a path starts with a root path such as "D:", "D:/" or "/"

IsAbsolute was not the corect check as the path of "D:" is a relative
path.
To be absolute according to the Windows the path must have a root
directory. i.e "D:/" or "D:\"

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed absolute path comment from LocalFile_UnixLike.cpp and LocalFile_Android.cpp FindFiles implementations
Updated the ConvertToAlias to supply an AZ::IO::FixedMaxPath

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Replaced usage of the @projectproductassets@ alias with @engroot@ when referring to the LmbrCentral source folder in the CopyDependencyBuilderTest and the SeedBuilderTests

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the ScriptCanvas Upgrade Tool to output backed up files to the
Project User Path instead of the engine root

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed whitespacing issues in Application.cpp

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Remove unnecessary creation of a FixedMaxPath in the UpgradeTool.cpp

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Modified testSeedFolder variable in the SeedBuilderTests to use the
@engroot@ alias instead of @projectproductassets@/.. alias when
referring to the LmbrCentral Gem source path

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated references to the Project Asset Cache in the PythonTests.

Those tests no longer use the logic `azlmbr.paths.projectroot / "Cache" / "pc"` to retrieve a path to the cache root but instead the `azlmbr.paths.projectproductassets` constant

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed the FileIO Deprecated Alias test on Windows

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removing @projectsourceassets@ alias, as it is only used once.

Updated the PhysX EditorSystemComponent.cpp to query the ProjectPath
form the SettingsRegistry.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Replaced @projectproductassets@ alias with @products@

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Rollback changes to the PhysX EditorSystemComponent.cpp

The changes to use the ProjectPath from the SettingsRegistry has been implemented in PR #4497

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2021-10-05 15:12:53 -05:00

325 lines
14 KiB
C++

/*
* 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 INTEG_PoseComparisonFixture::SetUp()
{
SystemComponentFixture::SetUp();
LoadAssets();
}
void INTEG_PoseComparisonFixture::TearDown()
{
m_actorInstance->Destroy();
m_actor.reset();
delete m_motionSet;
m_motionSet = nullptr;
delete m_animGraph;
m_animGraph = nullptr;
SystemComponentFixture::TearDown();
}
void INTEG_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(INTEG_PoseComparisonFixture, Integ_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(INTEG_TestPoseComparisonFixture, Integ_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(Integ_TestPoses, INTEG_PoseComparisonFixture,
::testing::Values(
PoseComparisonFixtureParams (
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.actor",
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.animgraph",
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.motionset",
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.emfxrecording"
),
PoseComparisonFixtureParams (
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.actor",
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.animgraph",
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.motionset",
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Pendulum/pendulum.emfxrecording"
)
)
);
INSTANTIATE_TEST_CASE_P(Integ_TestPoseComparison, INTEG_TestPoseComparisonFixture,
::testing::Values(
PoseComparisonFixtureParams (
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.actor",
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.animgraph",
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.motionset",
"@products@/Test.Assets/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.emfxrecording"
)
)
);
}; // namespace EMotionFX