/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace EMotionFX { class BoolLogicNodeTests : public AnimGraphFixture { public: void TearDown() override { if (m_motionNodes) { delete m_motionNodes; } AnimGraphFixture::TearDown(); } void ConstructGraph() override { AnimGraphFixture::ConstructGraph(); m_blendTreeAnimGraph = AnimGraphFactory::Create(); m_rootStateMachine = m_blendTreeAnimGraph->GetRootStateMachine(); m_blendTree = m_blendTreeAnimGraph->GetBlendTreeNode(); m_blendNNode = aznew BlendTreeBlendNNode(); BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode(); m_blendTree->AddChildNode(m_blendNNode); m_blendTree->AddChildNode(finalNode); finalNode->AddConnection(m_blendNNode, BlendTreeBlendNNode::PORTID_OUTPUT_POSE, BlendTreeFinalNode::PORTID_INPUT_POSE); const int motionNodeCount = 2; for (int i = 0; i < motionNodeCount; ++i) { AnimGraphMotionNode* motionNode = aznew AnimGraphMotionNode(); m_blendTree->AddChildNode(motionNode); m_blendNNode->AddConnection(motionNode, AnimGraphMotionNode::PORTID_OUTPUT_POSE, i); m_motionNodes->push_back(motionNode); } m_blendTreeAnimGraph->InitAfterLoading(); } void SetUp() override { m_motionNodes = new AZStd::vector(); AnimGraphFixture::SetUp(); m_animGraphInstance->Destroy(); m_animGraphInstance = m_blendTreeAnimGraph->GetAnimGraphInstance(m_actorInstance, m_motionSet); for (size_t i = 0; i < m_motionNodes->size(); ++i) { // The motion set keeps track of motions by their name. Each motion // within the motion set must have a unique name. AZStd::string motionId = AZStd::string::format("testSkeletalMotion%zu", i); SkeletalMotion* motion = SkeletalMotion::Create(motionId.c_str()); motion->SetMaxTime(1.0f); MotionSet::MotionEntry* motionEntry = aznew MotionSet::MotionEntry(motion->GetName(), motion->GetName(), motion); m_motionSet->AddMotionEntry(motionEntry); (*m_motionNodes)[i]->AddMotionId(motionId.c_str()); } } void AddValueParameter(const AZ::TypeId& typeId, const AZStd::string& name) { Parameter* parameter = ParameterFactory::Create(typeId); parameter->SetName(name); m_blendTreeAnimGraph->AddParameter(parameter); m_animGraphInstance->AddMissingParameterValues(); } bool CalculateExpectedResult(BlendTreeBoolLogicNode::EFunction functionEnum, bool x, bool y, bool& error) { bool result = false; switch (functionEnum) { case BlendTreeBoolLogicNode::EFunction::FUNCTION_AND: result = x && y; break; case BlendTreeBoolLogicNode::EFunction::FUNCTION_OR: result = x || y; break; case BlendTreeBoolLogicNode::EFunction::FUNCTION_XOR: result = x ^ y; break; case BlendTreeBoolLogicNode::EFunction::FUNCTION_NAND: result = !(x && y); break; case BlendTreeBoolLogicNode::EFunction::FUNCTION_NOR: result = !(x || y); break; case BlendTreeBoolLogicNode::EFunction::FUNCTION_XNOR: result = !(x ^ y); break; case BlendTreeBoolLogicNode::EFunction::FUNCTION_NOT_X: result = !x; break; case BlendTreeBoolLogicNode::EFunction::FUNCTION_NOT_Y: result = !y; break; default: error = true; break; } return result; } AZStd::unique_ptr m_blendTreeAnimGraph; AZStd::vector* m_motionNodes = nullptr; BlendTreeBlendNNode* m_blendNNode = nullptr; BlendTree* m_blendTree = nullptr; }; TEST_F(BoolLogicNodeTests, TestBoolLogic) { bool success = true; const AZStd::string nameBoolX("parameter_bool_x_test"); const AZStd::string nameBoolY("parameter_bool_y_test"); AddValueParameter(azrtti_typeid(), nameBoolX); AddValueParameter(azrtti_typeid(), nameBoolY); BlendTreeParameterNode* parameterNode = aznew BlendTreeParameterNode(); m_blendTree->AddChildNode(parameterNode); parameterNode->InitAfterLoading(m_blendTreeAnimGraph.get()); parameterNode->InvalidateUniqueData(m_animGraphInstance); BlendTreeBoolLogicNode* boolLogicNode = aznew BlendTreeBoolLogicNode(); m_blendTree->AddChildNode(boolLogicNode); boolLogicNode->InitAfterLoading(m_blendTreeAnimGraph.get()); boolLogicNode->InvalidateUniqueData(m_animGraphInstance); const AZ::Outcome boolXParamIndexOutcome = m_animGraphInstance->FindParameterIndex(nameBoolX); const AZ::Outcome boolYParamIndexOutcome = m_animGraphInstance->FindParameterIndex(nameBoolY); success = boolXParamIndexOutcome.IsSuccess() && boolYParamIndexOutcome.IsSuccess(); uint32 boolXOutputPortIndex; uint32 boolYOutputPortIndex; const int portIndicesTosetCount = 2; int portIndicesFound = 0; const AZStd::vector& parameterNodeOutputPorts = parameterNode->GetOutputPorts(); for (const EMotionFX::AnimGraphNode::Port& port : parameterNodeOutputPorts) { uint32 paramIndex = parameterNode->GetParameterIndex(port.mPortID); if (paramIndex == boolXParamIndexOutcome.GetValue()) { boolXOutputPortIndex = port.mPortID; portIndicesFound++; } else if (paramIndex == boolYParamIndexOutcome.GetValue()) { boolYOutputPortIndex = port.mPortID; portIndicesFound++; } } success = success && (portIndicesFound == portIndicesTosetCount); bool expectedResultSuccessful = true; if (success) { boolLogicNode->AddConnection(parameterNode, static_cast(boolXOutputPortIndex), BlendTreeBoolLogicNode::INPUTPORT_X); boolLogicNode->AddConnection(parameterNode, static_cast(boolYOutputPortIndex), BlendTreeBoolLogicNode::INPUTPORT_Y); m_blendNNode->AddConnection(boolLogicNode, BlendTreeBoolLogicNode::OUTPUTPORT_BOOL, BlendTreeBlendNNode::INPUTPORT_WEIGHT); m_blendTreeAnimGraph->RecursiveReinit(); MCore::AttributeBool* testBoolXParameter = static_cast(m_animGraphInstance->FindParameter(nameBoolX)); testBoolXParameter->SetValue(false); MCore::AttributeBool* testBoolYParameter = static_cast(m_animGraphInstance->FindParameter(nameBoolY)); testBoolYParameter->SetValue(false); Evaluate(); MCore::Attribute* attribute = m_blendNNode->GetInputAttribute(m_animGraphInstance, BlendTreeBlendNNode::INPUTPORT_WEIGHT); if (!attribute) { success = false; } else { // Odds are X even are Y const bool tableOfTruthInput[8] = { false, false, false, true, true, false, true, true }; BlendTreeBoolLogicNode::EFunction functions[8] = { BlendTreeBoolLogicNode::EFunction::FUNCTION_AND, BlendTreeBoolLogicNode::EFunction::FUNCTION_OR, BlendTreeBoolLogicNode::EFunction::FUNCTION_XOR, BlendTreeBoolLogicNode::EFunction::FUNCTION_NAND, BlendTreeBoolLogicNode::EFunction::FUNCTION_NOR, BlendTreeBoolLogicNode::EFunction::FUNCTION_XNOR, BlendTreeBoolLogicNode::EFunction::FUNCTION_NOT_X, BlendTreeBoolLogicNode::EFunction::FUNCTION_NOT_Y }; for (int functionIndex = 0; functionIndex < 8; ++functionIndex) { boolLogicNode->SetFunction(functions[functionIndex]); for (int inputIndex = 0; inputIndex < 8; inputIndex += 2) { testBoolXParameter->SetValue(tableOfTruthInput[inputIndex]); testBoolYParameter->SetValue(tableOfTruthInput[inputIndex + 1]); Evaluate(); bool error = false; const bool expectedResult = CalculateExpectedResult(boolLogicNode->GetFunction(), testBoolXParameter->GetValue(), testBoolYParameter->GetValue(), error); const bool result = m_blendNNode->GetInputNumberAsBool(m_animGraphInstance, BlendTreeBlendNNode::INPUTPORT_WEIGHT); EXPECT_FALSE(error) << "Boolean logic node: CalculateExpectedResult returned error"; EXPECT_EQ(result, expectedResult) << "Boolean logic node: function " << functions[functionIndex] << " did not return the expected result"; expectedResultSuccessful = expectedResultSuccessful && !error && (result == expectedResult); } } } } ASSERT_TRUE(success && expectedResultSuccessful); } } // end namespace EMotionFX