/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "AnimGraphFixture.h" #include <AzCore/Asset/AssetManager.h> #include <EMotionFX/Source/AnimGraph.h> #include <EMotionFX/Source/AnimGraphBindPoseNode.h> #include <EMotionFX/Source/AnimGraphReferenceNode.h> #include <EMotionFX/Source/AnimGraphStateMachine.h> #include <EMotionFX/Source/BlendTree.h> #include <EMotionFX/Source/BlendTreeFinalNode.h> #include <EMotionFX/Source/BlendTreeParameterNode.h> #include <EMotionFX/Source/BlendTreeTransformNode.h> #include <EMotionFX/Source/EMotionFXManager.h> #include <EMotionFX/Source/Parameter/FloatSliderParameter.h> #include <EMotionFX/Source/Parameter/ParameterFactory.h> #include <Tests/Printers.h> namespace EMotionFX { // Add a reference node without any asset in it class AnimGraphReferenceNodeBaseTests : public AnimGraphFixture { public: void ConstructGraph() override { AnimGraphFixture::ConstructGraph(); m_blendTree = aznew BlendTree(); m_blendTree->SetName("BlendTreeInParentGraph"); m_animGraph->GetRootStateMachine()->AddChildNode(m_blendTree); m_animGraph->GetRootStateMachine()->SetEntryState(m_blendTree); m_referenceNode = aznew AnimGraphReferenceNode(); m_referenceNode->SetName("ReferenceNodeInParentGraph"); m_blendTree->AddChildNode(m_referenceNode); BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode(); finalNode->SetName("BlendTreeFinalNodeParentGraph"); m_blendTree->AddChildNode(finalNode); finalNode->AddUnitializedConnection(m_referenceNode, AnimGraphReferenceNode::PORTID_OUTPUT_POSE, BlendTreeFinalNode::PORTID_INPUT_POSE); } BlendTree* m_blendTree = nullptr; AnimGraphReferenceNode* m_referenceNode = nullptr; }; // Basic test that just evaluates the node. Since the node is not doing anything, // The pose should not be affected. TEST_F(AnimGraphReferenceNodeBaseTests, Evaluate) { Evaluate(); const Transform& outputRoot = GetOutputTransform(); Transform identity; identity.Identity(); ASSERT_EQ(identity, outputRoot); } // Add a reference node with an empty asset class AnimGraphReferenceNodeWithAssetTests : public AnimGraphReferenceNodeBaseTests { public: void ConstructGraph() override { AnimGraphReferenceNodeBaseTests::ConstructGraph(); m_referenceAnimGraph = aznew AnimGraph(); AZ::Data::Asset<Integration::AnimGraphAsset> animGraphAsset = AZ::Data::AssetManager::Instance().CreateAsset<Integration::AnimGraphAsset>(AZ::Data::AssetId("{E8FBAEF1-CBC5-43C2-83C8-9F8812857494}")); animGraphAsset.GetAs<Integration::AnimGraphAsset>()->SetData(m_referenceAnimGraph); m_referenceNode->SetAnimGraphAsset(animGraphAsset); AnimGraphStateMachine* rootStateMachine = aznew AnimGraphStateMachine(); m_referenceAnimGraph->SetRootStateMachine(rootStateMachine); ConstructReferenceGraph(); m_referenceAnimGraph->InitAfterLoading(); m_referenceNode->SetAnimGraph(m_animGraph.get()); m_referenceNode->OnAssetReady(animGraphAsset); } virtual void ConstructReferenceGraph() {} AnimGraph* m_referenceAnimGraph = nullptr; }; // Load an empty anim graph into the reference node TEST_F(AnimGraphReferenceNodeWithAssetTests, EvaluateEmptyAnimGraphAsset) { Evaluate(); const Transform& outputRoot = GetOutputTransform(); Transform identity; identity.Identity(); ASSERT_EQ(identity, outputRoot); } // Add a reference node with an asset that contains a blendtree with a transform node class AnimGraphReferenceNodeWithContentsTests : public AnimGraphReferenceNodeWithAssetTests { public: void ConstructReferenceGraph() override { AnimGraphReferenceNodeWithAssetTests::ConstructReferenceGraph(); BlendTree* blendTree = aznew BlendTree(); blendTree->SetName("BlendTreeInReferenceGraph"); m_referenceAnimGraph->GetRootStateMachine()->AddChildNode(blendTree); m_referenceAnimGraph->GetRootStateMachine()->SetEntryState(blendTree); m_transformNode = aznew BlendTreeTransformNode(); m_transformNode->SetName("BlendTreeTransformNodeInReferenceGraph"); blendTree->AddChildNode(m_transformNode); BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode(); finalNode->SetName("BlendTreeFinalNodeInReferenceGraph"); blendTree->AddChildNode(finalNode); finalNode->AddUnitializedConnection(m_transformNode, BlendTreeTransformNode::PORTID_OUTPUT_POSE, BlendTreeFinalNode::PORTID_INPUT_POSE); // Add a parameter to the reference anim graph { Parameter* parameter = ParameterFactory::Create(azrtti_typeid<FloatSliderParameter>()); m_referenceAnimGraph->AddParameter(parameter); } BlendTreeParameterNode* parameterNode = aznew BlendTreeParameterNode(); parameterNode->SetName("ParameterNodeInReferenceGraph"); blendTree->AddChildNode(parameterNode); m_transformNode->AddUnitializedConnection(parameterNode, 0, BlendTreeTransformNode::PORTID_INPUT_TRANSLATE_AMOUNT); } void ConstructGraph() override { AnimGraphReferenceNodeWithAssetTests::ConstructGraph(); { Parameter* parameter = ParameterFactory::Create(azrtti_typeid<FloatSliderParameter>()); m_animGraph->AddParameter(parameter); } BlendTreeParameterNode* parameterNode = aznew BlendTreeParameterNode(); parameterNode->SetName("ParameterNodeInParentGraph"); m_blendTree->AddChildNode(parameterNode); m_referenceNode->AddUnitializedConnection(parameterNode, 0, 0); } BlendTreeTransformNode* m_transformNode = nullptr; }; // Just evaluate the node TEST_F(AnimGraphReferenceNodeWithContentsTests, Evaluate) { Evaluate(); const Transform& outputRoot = GetOutputTransform(); Transform identity; identity.Identity(); ASSERT_EQ(identity, outputRoot); } /////////////////////////////////////////////////////////////////////////// class AnimGraphReferenceNodeDeferredInitTests : public AnimGraphFixture { public: void ConstructGraph() override { /* +-Root state machine--------------------------------------------+ | | | +------------+ +---------------+ +----------+ | | =>| BindPose |------>| ReferenceNode |------>| EndState | | | +------------+ +---------------+ +----------+ | | | +---------------------------------------------------------------+ +-Root state machine (referenceNode)----------------------------+ | | | +---------------+ +----------+ | | =>| RefBindPose |------>| endState | | | +---------------+ +----------+ | | | +---------------------------------------------------------------+ */ AnimGraphFixture::ConstructGraph(); AnimGraphBindPoseNode* entryState = aznew AnimGraphBindPoseNode(); entryState->SetName("StateA"); m_rootStateMachine->AddChildNode(entryState); m_rootStateMachine->SetEntryState(entryState); m_referenceNode = aznew AnimGraphReferenceNode(); m_referenceNode->SetName("StateB (Reference)"); m_rootStateMachine->AddChildNode(m_referenceNode); AddTransitionWithTimeCondition(entryState, m_referenceNode, 1.0f, 1.0f); AnimGraphBindPoseNode* endState = aznew AnimGraphBindPoseNode(); endState->SetName("StateC"); m_rootStateMachine->AddChildNode(endState); AddTransitionWithTimeCondition(m_referenceNode, endState, 1.0f, 1.0f); AnimGraph* referenceAnimGraph = CreateReferenceGraph(); AZ::Data::Asset<Integration::AnimGraphAsset> animGraphAsset = AZ::Data::AssetManager::Instance().CreateAsset<Integration::AnimGraphAsset>(AZ::Data::AssetId("{E8FBAEF1-CBC5-43C2-83C8-9F8812857494}")); animGraphAsset.GetAs<Integration::AnimGraphAsset>()->SetData(referenceAnimGraph); m_referenceNode->SetAnimGraphAsset(animGraphAsset); referenceAnimGraph->InitAfterLoading(); m_referenceNode->SetAnimGraph(m_animGraph.get()); m_referenceNode->OnAssetReady(animGraphAsset); } AnimGraph* CreateReferenceGraph() { AnimGraph* referenceAnimGraph = aznew AnimGraph(); AnimGraphStateMachine* referenceRootSM = aznew AnimGraphStateMachine(); referenceAnimGraph->SetRootStateMachine(referenceRootSM); AnimGraphBindPoseNode* referenceEntryState = aznew AnimGraphBindPoseNode(); referenceEntryState->SetName("RefEntryState"); referenceRootSM->AddChildNode(referenceEntryState); referenceRootSM->SetEntryState(referenceEntryState); AnimGraphBindPoseNode* referenceEndState = aznew AnimGraphBindPoseNode(); referenceEndState->SetName("RefEndState"); referenceRootSM->AddChildNode(referenceEndState); AddTransitionWithTimeCondition(referenceEntryState, referenceEndState, 1.0f, 1.0f); return referenceAnimGraph; } public: AnimGraphReferenceNode* m_referenceNode = nullptr; }; TEST_F(AnimGraphReferenceNodeDeferredInitTests, DeferredReferenceGraphTest) { const size_t numObjects = m_animGraph->GetNumObjects(); EXPECT_EQ(numObjects, m_animGraphInstance->GetNumUniqueObjectDatas()) << "There should be a unique data placeholder for each anim graph object."; EXPECT_EQ(m_animGraphInstance->CalcNumAllocatedUniqueDatas(), 0) << "Unique datas should not be allocated yet."; // Entry state active, conditions are watching. GetEMotionFX().Update(0.0f); EXPECT_EQ(m_animGraphInstance->CalcNumAllocatedUniqueDatas(), 3) << "Exactly 3 unique datas should be allocated now, the root state machine, the entry state (StateA) as well as the time condition."; // Transitioning from entry to reference state. GetEMotionFX().Update(1.5f); EXPECT_EQ(m_animGraphInstance->CalcNumAllocatedUniqueDatas(), 6) << "As we're transitioning, unique datas from the root SM, State A (entry node), the transition (A->B) + condition, State B and the new condition of B->C as the count-down timer already started as soon as B gets activated."; const AnimGraphReferenceNode::UniqueData* referenceNodeUniqueData = static_cast<AnimGraphReferenceNode::UniqueData*>(m_animGraphInstance->GetUniqueObjectData(m_referenceNode->GetObjectIndex())); EXPECT_NE(referenceNodeUniqueData, nullptr) << "Unique data for reference node should have already been allocated, as we're transitioning into the node."; const AnimGraphInstance* referenceAnimGraphInstance = referenceNodeUniqueData->m_referencedAnimGraphInstance; EXPECT_NE(referenceAnimGraphInstance, nullptr) << "Anim graph instance for reference node should be created already, as we're transitioning into the reference node."; EXPECT_EQ(referenceAnimGraphInstance->CalcNumAllocatedUniqueDatas(), 3) << "Exactly 3 unique datas should be allocated in the reference instance now, the root state machine, the entry state (RefEntryState) as well as the time condition."; // The reference node state machine transitions into the end state. GetEMotionFX().Update(1.0f); EXPECT_EQ(referenceAnimGraphInstance->CalcNumAllocatedUniqueDatas(), 5) << "The transition as well as the end state unique datas are now also allocated."; const AnimGraph* refAnimGraph = referenceAnimGraphInstance->GetAnimGraph(); EXPECT_EQ(referenceAnimGraphInstance->CalcNumAllocatedUniqueDatas(), refAnimGraph->GetNumObjects()) << "All objects should have their unique datas allocated now."; // The root state machine transitioned into the end state. GetEMotionFX().Update(1.0f); EXPECT_EQ(m_animGraphInstance->CalcNumAllocatedUniqueDatas(), 8) << "The last transition as well as the end state of the root state machine unique datas should now be allocated."; EXPECT_EQ(m_animGraphInstance->CalcNumAllocatedUniqueDatas(), numObjects) << "We should have reached all states, transitions and conditions."; } } // end namespace EMotionFX