/* * 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 "InitSceneAPIFixture.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace AZ::GFxFramework { class MockMaterialGroup : public MaterialGroup { public: // ActorBuilder::GetMaterialInfoForActorGroup tries to read a source file first, and if that fails // (likely because it doesn't exist), tries to read one product file, followed by another. If they all fail, // none of those files exist, so the function fails. The material read function called by // GetMaterialInfoForActorGroup is mocked to return true after a set number of false returns to mimic the // behavior we would see if each given file is on disk. MOCK_METHOD1(ReadMtlFile, bool(const char* filename)); MOCK_METHOD0(GetMaterialCount, size_t()); }; } // namespace AZ namespace EMotionFX { namespace Pipeline { class MockActorBuilder : public ActorBuilder { public: AZ_COMPONENT(MockActorBuilder, "{0C2537B5-6628-4076-BB09-CA1E57E59252}", EMotionFX::Pipeline::ActorBuilder) int m_numberReadFailsBeforeSuccess = 0; int m_materialCount = 0; protected: void InstantiateMaterialGroup() override { auto materialGroup = AZStd::make_shared(); EXPECT_CALL(*materialGroup, GetMaterialCount()) .WillRepeatedly(testing::Return(m_materialCount)); EXPECT_CALL(*materialGroup, ReadMtlFile(::testing::_)) .WillRepeatedly(testing::Return(true)); if (m_numberReadFailsBeforeSuccess) { EXPECT_CALL(*materialGroup, ReadMtlFile(::testing::_)) .Times(m_numberReadFailsBeforeSuccess) .WillRepeatedly(testing::Return(false)) .RetiresOnSaturation(); } m_materialGroup = AZStd::move(materialGroup); } }; class MockMaterialRule : public AZ::SceneAPI::DataTypes::IMaterialRule { public: bool RemoveUnusedMaterials() const override { return true; } bool UpdateMaterials() const override { return true; } }; } // namespace Pipeline // This fixture is responsible for creating the scene description used by // the actor builder pipeline tests using ActorBuilderPipelineFixtureBase = InitSceneAPIFixture< AZ::MemoryComponent, AZ::AssetManagerComponent, AZ::JobManagerComponent, AzToolsFramework::Components::PropertyManagerComponent, EMotionFX::Integration::SystemComponent, EMotionFX::Pipeline::MockActorBuilder >; class ActorBuilderPipelineFixture : public ActorBuilderPipelineFixtureBase { public: void SetUp() override { ActorBuilderPipelineFixtureBase::SetUp(); m_actor = EMotionFX::ActorFactory::CreateAndInit("testActor"); // Set up the scene graph m_scene = new AZ::SceneAPI::Containers::MockScene("MockScene"); m_scene->SetOriginalSceneOrientation(AZ::SceneAPI::Containers::Scene::SceneOrientation::ZUp); AZStd::string testSourceFile; AzFramework::StringFunc::Path::Join(GetWorkingDirectory(), "TestFile.fbx", testSourceFile); AzFramework::StringFunc::Path::Normalize(testSourceFile); m_scene->SetSource(testSourceFile, AZ::Uuid::CreateRandom()); AZ::SceneAPI::Containers::SceneGraph& graph = m_scene->GetGraph(); AZStd::shared_ptr boneData = AZStd::make_shared(); graph.AddChild(graph.GetRoot(), "testRootBone", boneData); // Set up our base shape AZStd::shared_ptr meshData = AZStd::make_shared(); AZStd::vector unmorphedVerticies { AZ::Vector3(0, 0, 0), AZ::Vector3(1, 0, 0), AZ::Vector3(0, 1, 0) }; for (const AZ::Vector3& vertex : unmorphedVerticies) { meshData->AddPosition(vertex); } meshData->AddNormal(AZ::Vector3(0, 0, 1)); meshData->AddNormal(AZ::Vector3(0, 0, 1)); meshData->AddNormal(AZ::Vector3(0, 0, 1)); meshData->SetVertexIndexToControlPointIndexMap(0, 0); meshData->SetVertexIndexToControlPointIndexMap(1, 1); meshData->SetVertexIndexToControlPointIndexMap(2, 2); meshData->AddFace(0, 1, 2); AZ::SceneAPI::Containers::SceneGraph::NodeIndex meshNodeIndex = graph.AddChild(graph.GetRoot(), "testMesh", meshData); } AZ::SceneAPI::Events::ProcessingResult Process(EMotionFX::Pipeline::Group::ActorGroup& actorGroup, AZStd::vector& materialReferences) { AZ::SceneAPI::Events::ProcessingResultCombiner result; AZStd::string workingDir = GetWorkingDirectory(); EMotionFX::Pipeline::ActorBuilderContext actorBuilderContext(*m_scene, workingDir, actorGroup, m_actor.get(), materialReferences, AZ::RC::Phase::Construction); result += AZ::SceneAPI::Events::Process(actorBuilderContext); result += AZ::SceneAPI::Events::Process(actorBuilderContext, AZ::RC::Phase::Filling); result += AZ::SceneAPI::Events::Process(actorBuilderContext, AZ::RC::Phase::Finalizing); return result.GetResult(); } void TearDown() override { delete m_scene; m_scene = nullptr; ActorBuilderPipelineFixtureBase::TearDown(); } void TestSuccessCase(const AZStd::vector& expectedMaterialReferences) { // Set up the actor group, which controls which parts of the scene graph // are used to generate the actor EMotionFX::Pipeline::Group::ActorGroup actorGroup; actorGroup.SetName("testActor"); actorGroup.SetSelectedRootBone("testRootBone"); actorGroup.GetSceneNodeSelectionList().AddSelectedNode("testMesh"); actorGroup.GetBaseNodeSelectionList().AddSelectedNode("testMesh"); // do something here to make sure there are material rules in the actor? if (!expectedMaterialReferences.empty()) { AZStd::shared_ptr materialRule = AZStd::make_shared(); actorGroup.GetRuleContainer().AddRule(materialRule); } AZStd::vector materialReferences; const AZ::SceneAPI::Events::ProcessingResult result = Process(actorGroup, materialReferences); ASSERT_EQ(result, AZ::SceneAPI::Events::ProcessingResult::Success) << "Failed to build actor"; for (auto& materialReference : materialReferences) { AzFramework::StringFunc::Path::Normalize(materialReference); } EXPECT_THAT( materialReferences, ::testing::Pointwise(StrEq(), expectedMaterialReferences) ); } AZStd::unique_ptr m_actor; AZ::SceneAPI::Containers::Scene* m_scene = nullptr; }; TEST_F(ActorBuilderPipelineFixture, ActorBuilder_MaterialReferences_NoReferences) { // Set up the actor group, which controls which parts of the scene graph // are used to generate the actor TestSuccessCase({}); } TEST_F(ActorBuilderPipelineFixture, ActorBuilder_MaterialReferences_OneSourceReference_ExpectAbsolutePath) { AZStd::string expectedMaterialReference; AzFramework::StringFunc::Path::Join(GetWorkingDirectory(), "TestFile.mtl", expectedMaterialReference); Pipeline::MockActorBuilder* actorBuilderComponent = GetSystemEntity()->FindComponent(); actorBuilderComponent->m_numberReadFailsBeforeSuccess = 0; actorBuilderComponent->m_materialCount = 1; TestSuccessCase({expectedMaterialReference}); } TEST_F(ActorBuilderPipelineFixture, ActorBuilder_MaterialReferences_OneProductReference_ExpectRelativeMaterialPath) { AZStd::string normalizedWorkingDir = GetWorkingDirectory(); AzFramework::StringFunc::Path::Normalize(normalizedWorkingDir); AZStd::string workingDirLastComponent; AzFramework::StringFunc::Path::GetComponent(normalizedWorkingDir.c_str(), workingDirLastComponent, 1, true); AZStd::string expectedMaterialReference; AzFramework::StringFunc::Path::Join(workingDirLastComponent.c_str(), "testActor.mtl", expectedMaterialReference); AZStd::to_lower(expectedMaterialReference.begin(), expectedMaterialReference.end()); Pipeline::MockActorBuilder* actorBuilderComponent = GetSystemEntity()->FindComponent(); actorBuilderComponent->m_numberReadFailsBeforeSuccess = 1; actorBuilderComponent->m_materialCount = 1; TestSuccessCase({expectedMaterialReference}); } TEST_F(ActorBuilderPipelineFixture, ActorBuilder_MaterialReferences_OneProductReference_ExpectRelativeDccPath) { AZStd::string normalizedWorkingDir = GetWorkingDirectory(); AzFramework::StringFunc::Path::Normalize(normalizedWorkingDir); AZStd::string workingDirLastComponent; AzFramework::StringFunc::Path::GetComponent(normalizedWorkingDir.c_str(), workingDirLastComponent, 1, true); AZStd::string expectedMaterialReference; AzFramework::StringFunc::Path::Join(workingDirLastComponent.c_str(), "testActor.dccmtl", expectedMaterialReference); AZStd::to_lower(expectedMaterialReference.begin(), expectedMaterialReference.end()); Pipeline::MockActorBuilder* actorBuilderComponent = GetSystemEntity()->FindComponent(); actorBuilderComponent->m_numberReadFailsBeforeSuccess = 2; actorBuilderComponent->m_materialCount = 1; TestSuccessCase({expectedMaterialReference}); } } // namespace EMotionFX