/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace EMotionFX { struct BlendTreeFloatMath1NodeTestData { std::vector m_xInputFloat; std::vector m_xInputInt; std::vector m_xInputBool; }; std::vector blendTreeFloatMath1NodeTestData { { // TODO: MCore RandF function does not handle extreme values properly // eg. MCore::Math::RandF(0, FLT_MAX) returns inf {1000.3f, -1000.3f, 0.1f, -1.2f, 1.2f}, {1000, -1000, 0, -1, 1}, {true, false} } }; class BlendTreeFloatMath1NodeFixture : public AnimGraphFixture , public ::testing::WithParamInterface { public: void ConstructGraph() override { AnimGraphFixture::ConstructGraph(); m_param = GetParam(); m_blendTreeAnimGraph = AnimGraphFactory::Create(); m_rootStateMachine = m_blendTreeAnimGraph->GetRootStateMachine(); m_blendTree = m_blendTreeAnimGraph->GetBlendTreeNode(); AddParameter("FloatParam", 0.0f); AddParameter("BoolParam", false); AddParameter("IntParam", 0); /* +------------------+ | | | bindPoseNode | | | +------------------+ +------------------+ +------------------+-->+ | | | | blend2Node +-->+ finalNode | +------------------+ +------------------+ | | | | | | | +-->+------------------+ +------------------+ | m_paramNode +-->+ m_floatMath1Node | | | | | +------------------+ +------------------+ */ BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode(); m_blendTree->AddChildNode(finalNode); AnimGraphBindPoseNode* bindPoseNode = aznew AnimGraphBindPoseNode(); m_blendTree->AddChildNode(bindPoseNode); BlendTreeBlend2Node* blend2Node = aznew BlendTreeBlend2Node(); m_blendTree->AddChildNode(blend2Node); m_floatMath1Node = aznew BlendTreeFloatMath1Node(); m_blendTree->AddChildNode(m_floatMath1Node); m_paramNode = aznew BlendTreeParameterNode(); m_blendTree->AddChildNode(m_paramNode); // Connect the nodes. blend2Node->AddConnection(bindPoseNode, AnimGraphBindPoseNode::PORTID_OUTPUT_POSE, BlendTreeBlend2Node::INPUTPORT_POSE_A); blend2Node->AddConnection(bindPoseNode, AnimGraphBindPoseNode::PORTID_OUTPUT_POSE, BlendTreeBlend2Node::INPUTPORT_POSE_B); blend2Node->AddConnection(m_floatMath1Node, BlendTreeFloatMath1Node::OUTPUTPORT_RESULT, BlendTreeBlend2Node::INPUTPORT_WEIGHT); finalNode->AddConnection(blend2Node, BlendTreeBlend2Node::PORTID_OUTPUT_POSE, BlendTreeFinalNode::PORTID_INPUT_POSE); m_blendTreeAnimGraph->InitAfterLoading(); } template void TestInput(const AZStd::string& paramName, std::vector xInputs) { BlendTreeConnection* connection = m_floatMath1Node->AddConnection(m_paramNode, m_paramNode->FindOutputPortByName(paramName)->mPortID, BlendTreeFloatMath1Node::PORTID_INPUT_X); for (inputType i : xInputs) { // Get and set parameter value to different test data inputs const AZ::Outcome parameterIndex = m_animGraphInstance->FindParameterIndex(paramName); MCore::Attribute* param = m_animGraphInstance->GetParameterValue(static_cast(parameterIndex.GetValue())); paramType* typeParam = static_cast(param); typeParam->SetValue(i); for (AZ::u8 j = 0; j < BlendTreeFloatMath1Node::MATHFUNCTION_NUMFUNCTIONS; j++) { // Test input with all 26 math functions const BlendTreeFloatMath1Node::EMathFunction eMathFunc = static_cast(j); m_floatMath1Node->SetMathFunction(eMathFunc); GetEMotionFX().Update(1.0f / 60.0f); const float actualOutput = m_floatMath1Node->GetOutputFloat(m_animGraphInstance, BlendTreeFloatMath1Node::OUTPUTPORT_RESULT)->GetValue(); const float expectedOutput = CalculateMathFunctionOutput(eMathFunc, static_cast(i)); // Special cases for random float where float equal is not suitable // If actual and expected outputs are both NaN, then they should be considered same if (eMathFunc == BlendTreeFloatMath1Node::MATHFUNCTION_RANDOMFLOAT) { EXPECT_TRUE(RandomFloatIsInRange(actualOutput, 0, static_cast(i))) << "Random float is not in range."; continue; } if (isnan(actualOutput) && isnan(expectedOutput)) { continue; } EXPECT_FLOAT_EQ(actualOutput, expectedOutput) << "Actual and expected outputs does not match."; } } m_floatMath1Node->RemoveConnection(connection); } void SetUp() override { AnimGraphFixture::SetUp(); m_animGraphInstance->Destroy(); m_animGraphInstance = m_blendTreeAnimGraph->GetAnimGraphInstance(m_actorInstance, m_motionSet); } protected: AZStd::unique_ptr m_blendTreeAnimGraph; BlendTree* m_blendTree = nullptr; BlendTreeFloatMath1Node* m_floatMath1Node = nullptr; BlendTreeFloatMath1NodeTestData m_param; BlendTreeParameterNode* m_paramNode = nullptr; private: bool RandomFloatIsInRange(float randomFloat, float bound1, float bound2) { if (bound1 > bound2) { return (randomFloat - bound2) <= (bound1 - bound2); } return (randomFloat - bound1) <= (bound2 - bound1); } template void AddParameter(const AZStd::string name, const ValueType& defaultValue) { ParameterType* parameter = aznew ParameterType(); parameter->SetName(name); parameter->SetDefaultValue(defaultValue); m_blendTreeAnimGraph->AddParameter(parameter); } float CalculateMathFunctionOutput(BlendTreeFloatMath1Node::EMathFunction mathFunction, float input) { switch (mathFunction) { case BlendTreeFloatMath1Node::MATHFUNCTION_SIN: return CalculateSin(input); case BlendTreeFloatMath1Node::MATHFUNCTION_COS: return CalculateCos(input); case BlendTreeFloatMath1Node::MATHFUNCTION_TAN: return CalculateTan(input); case BlendTreeFloatMath1Node::MATHFUNCTION_SQR: return CalculateSqr(input); case BlendTreeFloatMath1Node::MATHFUNCTION_SQRT: return CalculateSqrt(input); case BlendTreeFloatMath1Node::MATHFUNCTION_ABS: return CalculateAbs(input); case BlendTreeFloatMath1Node::MATHFUNCTION_FLOOR: return CalculateFloor(input); case BlendTreeFloatMath1Node::MATHFUNCTION_CEIL: return CalculateCeil(input); case BlendTreeFloatMath1Node::MATHFUNCTION_ONEOVERINPUT: return CalculateOneOverInput(input); case BlendTreeFloatMath1Node::MATHFUNCTION_INVSQRT: return CalculateInvSqrt(input); case BlendTreeFloatMath1Node::MATHFUNCTION_LOG: return CalculateLog(input); case BlendTreeFloatMath1Node::MATHFUNCTION_LOG10: return CalculateLog10(input); case BlendTreeFloatMath1Node::MATHFUNCTION_EXP: return CalculateExp(input); case BlendTreeFloatMath1Node::MATHFUNCTION_FRACTION: return CalculateFraction(input); case BlendTreeFloatMath1Node::MATHFUNCTION_SIGN: return CalculateSign(input); case BlendTreeFloatMath1Node::MATHFUNCTION_ISPOSITIVE: return CalculateIsPositive(input); case BlendTreeFloatMath1Node::MATHFUNCTION_ISNEGATIVE: return CalculateIsNegative(input); case BlendTreeFloatMath1Node::MATHFUNCTION_ISNEARZERO: return CalculateIsNearZero(input); case BlendTreeFloatMath1Node::MATHFUNCTION_RANDOMFLOAT: return 0.0f; case BlendTreeFloatMath1Node::MATHFUNCTION_RADTODEG: return CalculateRadToDeg(input); case BlendTreeFloatMath1Node::MATHFUNCTION_DEGTORAD: return CalculateDegToRad(input); case BlendTreeFloatMath1Node::MATHFUNCTION_SMOOTHSTEP: return CalculateSmoothStep(input); case BlendTreeFloatMath1Node::MATHFUNCTION_ACOS: return CalculateACos(input); case BlendTreeFloatMath1Node::MATHFUNCTION_ASIN: return CalculateASin(input); case BlendTreeFloatMath1Node::MATHFUNCTION_ATAN: return CalculateATan(input); case BlendTreeFloatMath1Node::MATHFUNCTION_NEGATE: return CalculateNegate(input); default: AZ_Assert(false, "EMotionFX: Math function unknown."); return 0.0f; } } //----------------------------------------------- // The math functions //----------------------------------------------- float CalculateSin(float input) { return sin(input); } float CalculateCos(float input) { return cos(input); } float CalculateTan(float input) { return tan(input); } float CalculateSqr(float input) { return (input * input); } float CalculateSqrt(float input) { if (input > AZ_FLT_EPSILON) { return sqrt(input); } return 0.0f; } float CalculateAbs(float input) { return abs(input); } float CalculateFloor(float input) { return floor(input); } float CalculateCeil(float input) { return ceil(input); } float CalculateOneOverInput(float input) { if (input > AZ_FLT_EPSILON) { return 1.0f / input; } return 0.0f; } float CalculateInvSqrt(float input) { if (input > AZ_FLT_EPSILON) { return 1.0f / sqrt(input); } return 0.0f; } float CalculateLog(float input) { if (input > AZ_FLT_EPSILON) { return log(input); } return 0.0f; } float CalculateLog10(float input) { if (input > AZ_FLT_EPSILON) { return log10f(input); } return 0.0f; } float CalculateExp(float input) { return exp(input); } float CalculateFraction(float input) { return AZ::GetMod(input, 1.0f); } float CalculateSign(float input) { if (input < 0.0f) { return -1.0f; } if (input > 0.0f) { return 1.0f; } return 0.0f; } float CalculateIsPositive(float input) { if (input >= 0.0f) { return 1.0f; } return 0.0f; } float CalculateIsNegative(float input) { if (input < 0.0f) { return 1.0f; } return 0.0f; } float CalculateIsNearZero(float input) { if ((input > -AZ_FLT_EPSILON) && (input < AZ_FLT_EPSILON)) { return 1.0f; } return 0.0f; } float CalculateRadToDeg(float input) { return AZ::RadToDeg(input); } float CalculateDegToRad(float input) { return AZ::DegToRad(input); } float CalculateSmoothStep(float input) { const float f = AZ::GetClamp(input, 0.0f, 1.0f); const float weight = (1.0f - cos(f * AZ::Constants::Pi)) * 0.5f;; return 0.0f * (1.0f - weight) + (weight * 1.0f); } float CalculateACos(float input) { return acos(input); } float CalculateASin(float input) { return asin(input); } float CalculateATan(float input) { return atan(input); } float CalculateNegate(float input) { return -input; } }; TEST_P(BlendTreeFloatMath1NodeFixture, NoInput_OutputsCorrectFloatTest) { // Testing float math1 node without input node for (AZ::u8 i = 0; i < BlendTreeFloatMath1Node::MATHFUNCTION_NUMFUNCTIONS; i++) { BlendTreeFloatMath1Node::EMathFunction eMathFunc = static_cast(i); m_floatMath1Node->SetMathFunction(eMathFunc); GetEMotionFX().Update(1.0f / 60.0f); // Default output should be 0.0f EXPECT_FLOAT_EQ(m_floatMath1Node->GetOutputFloat(m_animGraphInstance, BlendTreeFloatMath1Node::OUTPUTPORT_RESULT)->GetValue(), 0.0f) << "Expected Output: 0.0f"; } }; TEST_P(BlendTreeFloatMath1NodeFixture, FloatInput_OutputsCorrectFloatTest) { TestInput("FloatParam", m_param.m_xInputFloat); }; TEST_P(BlendTreeFloatMath1NodeFixture, IntInput_OutputsCorrectFloatTest) { TestInput("IntParam", m_param.m_xInputInt); }; TEST_P(BlendTreeFloatMath1NodeFixture, BoolInput_OutputsCorrectFloatTest) { TestInput("BoolParam", m_param.m_xInputBool); }; INSTANTIATE_TEST_CASE_P(BlendTreeFloatMath1Node_ValidOutputTests, BlendTreeFloatMath1NodeFixture, ::testing::ValuesIn(blendTreeFloatMath1NodeTestData) ); } // end namespace EMotionFX