| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- /*
- * 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 "InitSceneAPIFixture.h"
- #include <AzCore/std/smart_ptr/make_shared.h>
- #include <AzCore/std/smart_ptr/unique_ptr.h>
- #include <AzCore/std/string/conversions.h>
- #include <AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.h>
- #include <SceneAPI/SceneCore/Mocks/Containers/MockScene.h>
- #include <SceneAPI/SceneData/GraphData/BoneData.h>
- #include <SceneAPI/SceneData/GraphData/MeshData.h>
- #include <SceneAPI/SceneData/GraphData/BlendShapeData.h>
- #include <EMotionFX/Source/Node.h>
- #include <EMotionFX/Source/Actor.h>
- #include <EMotionFX/Source/ActorInstance.h>
- #include <EMotionFX/Source/Mesh.h>
- #include <EMotionFX/Source/MorphSetup.h>
- #include <EMotionFX/Source/MorphSetupInstance.h>
- #include <EMotionFX/Source/MorphTarget.h>
- #include <EMotionFX/Source/Node.h>
- #include <EMotionFX/Source/Skeleton.h>
- #include <EMotionFX/Pipeline/RCExt/Actor/ActorBuilder.h>
- #include <EMotionFX/Pipeline/RCExt/Actor/MorphTargetExporter.h>
- #include <EMotionFX/Pipeline/RCExt/ExportContexts.h>
- #include <EMotionFX/Pipeline/SceneAPIExt/Groups/ActorGroup.h>
- #include <EMotionFX/Pipeline/SceneAPIExt/Rules/MorphTargetRule.h>
- #include <Tests/TestAssetCode/SimpleActors.h>
- #include <Tests/TestAssetCode/ActorFactory.h>
- namespace EMotionFX
- {
- // This fixture is responsible for creating the scene description used by
- // the morph target pipeline tests
- using MorphTargetPipelineFixtureBase = InitSceneAPIFixture<
- AZ::AssetManagerComponent,
- AZ::JobManagerComponent,
- AZ::StreamerComponent,
- AzToolsFramework::Components::PropertyManagerComponent,
- EMotionFX::Integration::SystemComponent,
- EMotionFX::Pipeline::ActorBuilder,
- EMotionFX::Pipeline::MorphTargetExporter
- >;
- class MorphTargetPipelineFixture
- : public MorphTargetPipelineFixtureBase
- {
- public:
- void SetUp() override
- {
- MorphTargetPipelineFixtureBase::SetUp();
- m_actor = ActorFactory::CreateAndInit<SimpleJointChainActor>(0);
- // Set up the scene graph
- m_scene = new AZ::SceneAPI::Containers::MockScene("MockScene");
- m_scene->SetOriginalSceneOrientation(AZ::SceneAPI::Containers::Scene::SceneOrientation::ZUp);
- AZ::SceneAPI::Containers::SceneGraph& graph = m_scene->GetGraph();
- AZStd::shared_ptr<AZ::SceneData::GraphData::BoneData> boneData = AZStd::make_shared<AZ::SceneData::GraphData::BoneData>();
- graph.AddChild(graph.GetRoot(), "testRootBone", boneData);
- // Set up our base shape
- AZStd::shared_ptr<AZ::SceneData::GraphData::MeshData> meshData = AZStd::make_shared<AZ::SceneData::GraphData::MeshData>();
- AZStd::vector<AZ::Vector3> unmorphedVerticies
- {
- AZ::Vector3(0.0f, 0.0f, 0.0f),
- AZ::Vector3(1.0f, 0.0f, 0.0f),
- AZ::Vector3(0.0f, 1.0f, 0.0f)
- };
- for (const AZ::Vector3& vertex : unmorphedVerticies)
- {
- meshData->AddPosition(vertex);
- }
- meshData->AddNormal(AZ::Vector3(0.0f, 0.0f, 1.0f));
- meshData->AddNormal(AZ::Vector3(0.0f, 0.0f, 1.0f));
- meshData->AddNormal(AZ::Vector3(0.0f, 0.0f, 1.0f));
- 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);
- // Set up the morph targets
- AZStd::vector<AZStd::vector<AZ::Vector3>> morphedVertices
- {{
- {
- // Morph target 1
- AZ::Vector3(0.0f, 0.0f, 0.0f),
- AZ::Vector3(1.0f, 0.0f, 1.0f), // this one is different
- AZ::Vector3(0.0f, 1.0f, 0.0f)
- },
- {
- // Morph target 2
- AZ::Vector3(0.0f, 0.0f, 0.0f),
- AZ::Vector3(1.0f, 0.0f, 0.0f),
- AZ::Vector3(0.0f, 1.0f, 1.0f) // this one is different
- },
- }};
- const size_t morphTargetCount = morphedVertices.size();
- for (size_t morphIndex = 0; morphIndex < morphTargetCount; ++morphIndex)
- {
- AZStd::shared_ptr<AZ::SceneData::GraphData::BlendShapeData> blendShapeData = AZStd::make_shared<AZ::SceneData::GraphData::BlendShapeData>();
- const AZStd::vector<AZ::Vector3>& verticesForThisMorph = morphedVertices.at(morphIndex);
- const uint vertexCount = static_cast<uint>(verticesForThisMorph.size());
- for (uint vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
- {
- blendShapeData->AddPosition(verticesForThisMorph.at(vertexIndex));
- blendShapeData->AddNormal(AZ::Vector3::CreateAxisZ());
- blendShapeData->SetVertexIndexToControlPointIndexMap(vertexIndex, vertexIndex);
- }
- blendShapeData->AddFace({{0, 1, 2}});
- AZStd::string morphTargetName("testMorphTarget");
- morphTargetName += AZStd::to_string(static_cast<int>(morphIndex));
- graph.AddChild(meshNodeIndex, morphTargetName.c_str(), blendShapeData);
- }
- }
- AZ::SceneAPI::Events::ProcessingResult Process(EMotionFX::Pipeline::Group::ActorGroup& actorGroup)
- {
- AZ::SceneAPI::Events::ProcessingResultCombiner result;
- AZStd::vector<AZStd::string> materialReferences;
- EMotionFX::Pipeline::ActorBuilderContext actorBuilderContext(*m_scene, "tmp", actorGroup, m_actor.get(), materialReferences, AZ::RC::Phase::Construction);
- result += AZ::SceneAPI::Events::Process(actorBuilderContext);
- result += AZ::SceneAPI::Events::Process<EMotionFX::Pipeline::ActorBuilderContext>(actorBuilderContext, AZ::RC::Phase::Filling);
- result += AZ::SceneAPI::Events::Process<EMotionFX::Pipeline::ActorBuilderContext>(actorBuilderContext, AZ::RC::Phase::Finalizing);
- return result.GetResult();
- }
- void TearDown() override
- {
- m_actor.reset();
- delete m_scene;
- MorphTargetPipelineFixtureBase::TearDown();
- }
- EMotionFX::Mesh* GetMesh(const EMotionFX::Actor* actor)
- {
- Skeleton* skeleton = actor->GetSkeleton();
- EMotionFX::Mesh* mesh = nullptr;
- const size_t numNodes = skeleton->GetNumNodes();
- for (size_t nodeNum = 0; nodeNum < numNodes; ++nodeNum)
- {
- if (mesh)
- {
- // We should only have one node that has a mesh
- EXPECT_FALSE(actor->GetMesh(0, nodeNum)) << "More than one mesh found on built actor";
- }
- else
- {
- mesh = actor->GetMesh(0, nodeNum);
- AZ_Printf("EMotionFX", "%s node name", skeleton->GetNode(nodeNum)->GetName());
- }
- }
- return mesh;
- }
- AZStd::unique_ptr<Actor> m_actor;
- AZ::SceneAPI::Containers::Scene* m_scene;
- };
- class MorphTargetCreationTestFixture : public MorphTargetPipelineFixture,
- public ::testing::WithParamInterface<std::vector<std::string>>
- {
- };
- TEST_P(MorphTargetCreationTestFixture, TestMorphTargetCreation)
- {
- const std::vector<std::string>& selectedMorphTargets = GetParam();
- // 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.SetSelectedRootBone("testRootBone");
- // TODO: replace the test with atom mesh.
- // actorGroup.GetSceneNodeSelectionList().AddSelectedNode("testMesh");
- // actorGroup.GetBaseNodeSelectionList().AddSelectedNode("testMesh");
- AZStd::shared_ptr<EMotionFX::Pipeline::Rule::MorphTargetRule> morphTargetRule = AZStd::make_shared<EMotionFX::Pipeline::Rule::MorphTargetRule>();
- for (const std::string& selectedMorphTarget : selectedMorphTargets)
- {
- morphTargetRule->GetSceneNodeSelectionList().AddSelectedNode(("testMesh." + selectedMorphTarget).c_str());
- }
- actorGroup.GetRuleContainer().AddRule(morphTargetRule);
- const AZ::SceneAPI::Events::ProcessingResult result = Process(actorGroup);
- ASSERT_EQ(result, AZ::SceneAPI::Events::ProcessingResult::Success) << "Failed to build actor";
- const MorphSetup* morphSetup = m_actor->GetMorphSetup(0);
- if (selectedMorphTargets.empty())
- {
- ASSERT_FALSE(morphSetup) << "A morph setup was created when the blend shape rule specified no nodes";
- // That's all we can verify for the case where no morph targets
- // were selected for export
- return;
- }
- ASSERT_TRUE(morphSetup) << "No morph setup was created";
- const size_t expectedNumMorphTargets = selectedMorphTargets.size();
- ASSERT_EQ(morphSetup->GetNumMorphTargets(), expectedNumMorphTargets) << "Morph setup should contain " << expectedNumMorphTargets << " morph target(s)";
- EMotionFX::Integration::EMotionFXPtr<EMotionFX::ActorInstance> actorInstance = EMotionFX::Integration::EMotionFXPtr<EMotionFX::ActorInstance>::MakeFromNew(EMotionFX::ActorInstance::Create(m_actor.get()));
- const Mesh* mesh = GetMesh(m_actor.get());
- // TODO: replace the test with atom mesh.
- if (!mesh)
- {
- return;
- }
- const size_t numMorphTargets = morphSetup->GetNumMorphTargets();
- for (size_t morphTargetIndex = 0; morphTargetIndex < numMorphTargets; ++morphTargetIndex)
- {
- const MorphTarget* morphTarget = morphSetup->GetMorphTarget(morphTargetIndex);
- EXPECT_STREQ(morphTarget->GetName(), selectedMorphTargets[morphTargetIndex].c_str()) << "Morph target's name is incorrect";
- const AZ::SceneAPI::Containers::SceneGraph& graph = m_scene->GetGraph();
- // Verify that the unmorphed vertices are what we expect
- const AZ::Vector3* const positions = static_cast<AZ::Vector3*>(mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_POSITIONS));
- AZStd::vector<AZ::Vector3> gotUnmorphedVertices;
- uint32 numVertices = mesh->GetNumVertices();
- for (uint32 vertexNum = 0; vertexNum < numVertices; ++vertexNum)
- {
- gotUnmorphedVertices.emplace_back(
- positions[vertexNum].GetX(),
- positions[vertexNum].GetY(),
- positions[vertexNum].GetZ()
- );
- }
- // Get the unmorphed vertices from the scene data
- const AZStd::shared_ptr<const AZ::SceneAPI::DataTypes::IMeshData> meshData = azrtti_cast<const AZ::SceneAPI::DataTypes::IMeshData*>(graph.GetNodeContent(graph.Find("testMesh")));
- AZStd::vector<AZ::Vector3> expectedUnmorphedVertices;
- numVertices = meshData->GetVertexCount();
- for (uint vertexNum = 0; vertexNum < numVertices; ++vertexNum)
- {
- expectedUnmorphedVertices.emplace_back(meshData->GetPosition(meshData->GetControlPointIndex(vertexNum)));
- }
- EXPECT_EQ(gotUnmorphedVertices, expectedUnmorphedVertices);
- // Now apply the morph, and verify the morphed vertices against
- // what we expect
- actorInstance->GetMorphSetupInstance()->GetMorphTarget(morphTargetIndex)->SetManualMode(true);
- actorInstance->GetMorphSetupInstance()->GetMorphTarget(morphTargetIndex)->SetWeight(1.0f);
- actorInstance->UpdateTransformations(0.0f, true);
- actorInstance->UpdateMeshDeformers(0.0f);
- const AZ::Vector3* const morphedPositions = static_cast<AZ::Vector3*>(mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_POSITIONS));
- AZStd::vector<AZ::Vector3> gotMorphedVertices;
- numVertices = mesh->GetNumVertices();
- for (uint32 vertexNum = 0; vertexNum < numVertices; ++vertexNum)
- {
- gotMorphedVertices.emplace_back(
- morphedPositions[vertexNum].GetX(),
- morphedPositions[vertexNum].GetY(),
- morphedPositions[vertexNum].GetZ()
- );
- }
- // Get the morphed vertices from the scene data
- AZStd::string morphTargetSceneNodeName("testMesh.");
- morphTargetSceneNodeName += selectedMorphTargets[morphTargetIndex].c_str();
- const AZStd::shared_ptr<const AZ::SceneAPI::DataTypes::IBlendShapeData> morphTargetData = azrtti_cast<const AZ::SceneAPI::DataTypes::IBlendShapeData*>(graph.GetNodeContent(graph.Find(morphTargetSceneNodeName)));
- AZStd::vector<AZ::Vector3> expectedMorphedVertices;
- numVertices = morphTargetData->GetVertexCount();
- for (uint vertexNum = 0; vertexNum < numVertices; ++vertexNum)
- {
- expectedMorphedVertices.emplace_back(morphTargetData->GetPosition(morphTargetData->GetControlPointIndex(vertexNum)));
- }
- EXPECT_EQ(gotMorphedVertices, expectedMorphedVertices);
- // Reset the morph target weight so that the next iteration compares against the unmorphed mesh
- actorInstance->GetMorphSetupInstance()->GetMorphTarget(morphTargetIndex)->SetWeight(0.0f);
- actorInstance->UpdateTransformations(0.0f, true);
- actorInstance->UpdateMeshDeformers(0.0f);
- }
- }
- // Note that these values are instantiated before the SystemAllocator is
- // created, so we can't use AZStd::vector
- INSTANTIATE_TEST_CASE_P(TestMorphTargetCreation, MorphTargetCreationTestFixture,
- ::testing::Values(
- std::vector<std::string> {},
- std::vector<std::string> {"testMorphTarget0"},
- std::vector<std::string> {"testMorphTarget1"},
- std::vector<std::string> {"testMorphTarget0", "testMorphTarget1"}
- )
- );
- }
|