You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
375 lines
16 KiB
C++
375 lines
16 KiB
C++
/*
|
|
* 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 <PrefabBuilderTests.h>
|
|
#include <PrefabGroup/IPrefabGroup.h>
|
|
#include <PrefabGroup/PrefabGroup.h>
|
|
#include <PrefabGroup/PrefabGroupBehavior.h>
|
|
#include <AzTest/Utils.h>
|
|
#include <Tests/AssetSystemMocks.h>
|
|
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzCore/std/smart_ptr/make_shared.h>
|
|
#include <AzToolsFramework/Asset/AssetSystemComponent.h>
|
|
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
|
|
#include <SceneAPI/SceneCore/Containers/Scene.h>
|
|
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
|
|
#include <SceneAPI/SceneCore/DataTypes/GraphData/ITransform.h>
|
|
#include <SceneAPI/SceneCore/DataTypes/Groups/IMeshGroup.h>
|
|
#include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
|
|
#include <SceneAPI/SceneCore/Events/CallProcessorBus.h>
|
|
#include <SceneAPI/SceneCore/Events/ExportEventContext.h>
|
|
#include <SceneAPI/SceneCore/Events/ExportProductList.h>
|
|
#include <SceneAPI/SceneCore/Mocks/DataTypes/MockIGraphObject.h>
|
|
#include <SceneAPI/SceneCore/SceneCoreStandaloneAllocator.h>
|
|
#include <SceneAPI/SceneData/GraphData/MeshData.h>
|
|
#include <SceneAPI/SceneData/SceneDataStandaloneAllocator.h>
|
|
|
|
#include <PrefabGroup/PrefabBehaviorTests.inl>
|
|
|
|
// a mock AZ::Render::EditorMeshComponent
|
|
namespace AZ::Render
|
|
{
|
|
struct EditorMeshComponent
|
|
: public AZ::Component
|
|
{
|
|
AZ_COMPONENT(EditorMeshComponent, "{DCE68F6E-2E16-4CB4-A834-B6C2F900A7E9}");
|
|
void Activate() override {}
|
|
void Deactivate() override {}
|
|
|
|
static void Reflect(AZ::ReflectContext* context)
|
|
{
|
|
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
|
{
|
|
serializeContext->Class<AZ::Render::EditorMeshComponent>();
|
|
}
|
|
|
|
if (BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
|
{
|
|
behaviorContext->Class<EditorMeshComponent>("AZ::Render::EditorMeshComponent");
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
struct EditorMeshComponentHelper
|
|
: public AZ::ComponentDescriptorHelper<EditorMeshComponent>
|
|
{
|
|
AZ_CLASS_ALLOCATOR(EditorMeshComponentHelper, AZ::SystemAllocator, 0);
|
|
|
|
void Reflect(AZ::ReflectContext* reflection) const override
|
|
{
|
|
AZ::Render::EditorMeshComponent::Reflect(reflection);
|
|
}
|
|
};
|
|
}
|
|
|
|
namespace UnitTest
|
|
{
|
|
class PrefabBehaviorTests
|
|
: public PrefabBuilderTests
|
|
{
|
|
public:
|
|
static void SetUpTestCase()
|
|
{
|
|
// Allocator needed by SceneCore
|
|
if (!AZ::AllocatorInstance<AZ::SystemAllocator>().IsReady())
|
|
{
|
|
AZ::AllocatorInstance<AZ::SystemAllocator>().Create();
|
|
}
|
|
AZ::SceneAPI::SceneCoreStandaloneAllocator::Initialize(AZ::Environment::GetInstance());
|
|
AZ::SceneAPI::SceneDataStandaloneAllocator::Initialize(AZ::Environment::GetInstance());
|
|
}
|
|
|
|
static void TearDownTestCase()
|
|
{
|
|
AZ::SceneAPI::SceneDataStandaloneAllocator::TearDown();
|
|
AZ::SceneAPI::SceneCoreStandaloneAllocator::TearDown();
|
|
AZ::AllocatorInstance<AZ::SystemAllocator>().Destroy();
|
|
}
|
|
|
|
void SetUp() override
|
|
{
|
|
PrefabBuilderTests::SetUp();
|
|
m_prefabGroupBehavior = AZStd::make_unique<AZ::SceneAPI::Behaviors::PrefabGroupBehavior>();
|
|
m_prefabGroupBehavior->Activate();
|
|
|
|
// Mocking the asset system replacing the AssetSystem::AssetSystemComponent
|
|
AZ::Entity* systemEntity = m_app.FindEntity(AZ::SystemEntityId);
|
|
systemEntity->FindComponent<AzToolsFramework::AssetSystem::AssetSystemComponent>()->Deactivate();
|
|
using namespace testing;
|
|
ON_CALL(m_assetSystemRequestMock, GetSourceInfoBySourcePath(_, _, _)).WillByDefault([](auto* path, auto& info, auto&)
|
|
{
|
|
return PrefabBehaviorTests::OnGetSourceInfoBySourcePath(path, info);
|
|
});
|
|
m_assetSystemRequestMock.BusConnect();
|
|
|
|
m_componentDescriptorHelperEditorMeshComponent = AZStd::make_unique<AZ::Render::EditorMeshComponentHelper>();
|
|
m_componentDescriptorHelperEditorMeshComponent->Reflect(m_app.GetSerializeContext());
|
|
m_componentDescriptorHelperEditorMeshComponent->Reflect(m_app.GetBehaviorContext());
|
|
}
|
|
|
|
void TearDown() override
|
|
{
|
|
m_componentDescriptorHelperEditorMeshComponent.reset();
|
|
|
|
m_assetSystemRequestMock.BusDisconnect();
|
|
|
|
m_prefabGroupBehavior->Deactivate();
|
|
m_prefabGroupBehavior.reset();
|
|
|
|
PrefabBuilderTests::TearDown();
|
|
}
|
|
|
|
static bool OnGetSourceInfoBySourcePath(AZStd::string_view sourcePath, AZ::Data::AssetInfo& assetInfo)
|
|
{
|
|
if (sourcePath == AZStd::string_view("mock"))
|
|
{
|
|
assetInfo.m_assetId = AZ::Uuid::CreateRandom();
|
|
assetInfo.m_assetType = azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>();
|
|
assetInfo.m_relativePath = "mock/path";
|
|
assetInfo.m_sizeBytes = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene> CreateMockScene(
|
|
const AZStd::string manifestFilename = "ManifestFilename",
|
|
const AZStd::string sourceFileName = "Source",
|
|
const AZStd::string watchFolder = "WatchFolder")
|
|
{
|
|
using namespace AZ::SceneAPI;
|
|
|
|
auto scene = AZStd::make_shared<Containers::Scene>("mock_scene");
|
|
scene->SetManifestFilename(manifestFilename);
|
|
scene->SetSource(sourceFileName, AZ::Uuid::CreateRandom());
|
|
scene->SetWatchFolder(watchFolder);
|
|
|
|
/*---------------------------------------\
|
|
Root
|
|
|
|
|
1
|
|
|
|
|
2
|
|
/ \
|
|
------3m 7
|
|
/ / / \
|
|
6 5 4t 8m-------
|
|
\ \ \
|
|
9t 10 11
|
|
\---------------------------------------*/
|
|
|
|
// Build up the graph
|
|
auto root = scene->GetGraph().GetRoot();
|
|
scene->GetGraph().SetContent(root, AZStd::make_shared<DataTypes::MockIGraphObject>(0));
|
|
auto index1 = scene->GetGraph().AddChild(root, "1", AZStd::make_shared<DataTypes::MockIGraphObject>(1));
|
|
auto index2 = scene->GetGraph().AddChild(index1, "2", AZStd::make_shared<DataTypes::MockIGraphObject>(2));
|
|
auto index3 = scene->GetGraph().AddChild(index2, "3", AZStd::make_shared<AZ::SceneData::GraphData::MeshData>());
|
|
auto index4 = scene->GetGraph().AddChild(index3, "4", AZStd::make_shared<MockTransform>());
|
|
auto index5 = scene->GetGraph().AddChild(index3, "5", AZStd::make_shared<DataTypes::MockIGraphObject>(5));
|
|
auto index6 = scene->GetGraph().AddChild(index3, "6", AZStd::make_shared<DataTypes::MockIGraphObject>(6));
|
|
auto index7 = scene->GetGraph().AddChild(index2, "7", AZStd::make_shared<DataTypes::MockIGraphObject>(7));
|
|
auto index8 = scene->GetGraph().AddChild(index7, "8", AZStd::make_shared<AZ::SceneData::GraphData::MeshData>());
|
|
auto index9 = scene->GetGraph().AddChild(index8, "9", AZStd::make_shared<MockTransform>());
|
|
auto index10 = scene->GetGraph().AddChild(index8, "10", AZStd::make_shared<DataTypes::MockIGraphObject>(10));
|
|
auto index11 = scene->GetGraph().AddChild(index8, "11", AZStd::make_shared<DataTypes::MockIGraphObject>(11));
|
|
|
|
scene->GetGraph().MakeEndPoint(index4);
|
|
scene->GetGraph().MakeEndPoint(index5);
|
|
scene->GetGraph().MakeEndPoint(index6);
|
|
scene->GetGraph().MakeEndPoint(index9);
|
|
scene->GetGraph().MakeEndPoint(index10);
|
|
scene->GetGraph().MakeEndPoint(index11);
|
|
|
|
return scene;
|
|
}
|
|
|
|
// mock classes and structures
|
|
|
|
struct MockTransform
|
|
: public AZ::SceneAPI::DataTypes::ITransform
|
|
{
|
|
AZ::Matrix3x4 m_matrix = AZ::Matrix3x4::CreateIdentity();
|
|
|
|
AZ::Matrix3x4& GetMatrix() override
|
|
{
|
|
return m_matrix;
|
|
}
|
|
|
|
const AZ::Matrix3x4& GetMatrix() const
|
|
{
|
|
return m_matrix;
|
|
}
|
|
};
|
|
|
|
struct TestPreExportEventContext
|
|
{
|
|
TestPreExportEventContext()
|
|
: m_scene("test_context")
|
|
{
|
|
using namespace AZ::SceneAPI::Events;
|
|
m_preExportEventContext = AZStd::make_unique<PreExportEventContext>(m_productList, m_outputDirectory, m_scene, "mock");
|
|
}
|
|
|
|
void SetOutputDirectory(AZStd::string outputDirectory)
|
|
{
|
|
using namespace AZ::SceneAPI::Events;
|
|
m_outputDirectory = AZStd::move(outputDirectory);
|
|
m_preExportEventContext = AZStd::make_unique<PreExportEventContext>(m_productList, m_outputDirectory, m_scene, "mock");
|
|
}
|
|
|
|
AZStd::unique_ptr<AZ::SceneAPI::Events::PreExportEventContext> m_preExportEventContext;
|
|
AZ::SceneAPI::Events::ExportProductList m_productList;
|
|
AZStd::string m_outputDirectory;
|
|
AZ::SceneAPI::Containers::Scene m_scene;
|
|
};
|
|
|
|
AZStd::unique_ptr<AZ::SceneAPI::Behaviors::PrefabGroupBehavior> m_prefabGroupBehavior;
|
|
testing::NiceMock<UnitTests::MockAssetSystemRequest> m_assetSystemRequestMock;
|
|
AZStd::unique_ptr<AZ::Render::EditorMeshComponentHelper> m_componentDescriptorHelperEditorMeshComponent;
|
|
};
|
|
|
|
TEST_F(PrefabBehaviorTests, PrefabBehavior_EmptyContextIgnored_Works)
|
|
{
|
|
auto context = TestPreExportEventContext{};
|
|
|
|
auto result = AZ::SceneAPI::Events::ProcessingResult::Failure;
|
|
AZ::SceneAPI::Events::CallProcessorBus::BroadcastResult(
|
|
result,
|
|
&AZ::SceneAPI::Events::CallProcessorBus::Events::Process,
|
|
context.m_preExportEventContext.get());
|
|
|
|
EXPECT_EQ(result, AZ::SceneAPI::Events::ProcessingResult::Ignored);
|
|
}
|
|
|
|
TEST_F(PrefabBehaviorTests, PrefabBehavior_SimplePrefab_Works)
|
|
{
|
|
auto context = TestPreExportEventContext{};
|
|
|
|
// check for the file at <temp_directory>/mock/fake_prefab.procprefab
|
|
AZ::Test::ScopedAutoTempDirectory tempDir;
|
|
context.SetOutputDirectory(tempDir.GetDirectory());
|
|
|
|
auto jsonOutcome = AZ::JsonSerializationUtils::ReadJsonString(Data::jsonPrefab);
|
|
ASSERT_TRUE(jsonOutcome);
|
|
|
|
// Register the asset to generate an AssetId in the catalog
|
|
AZ::Data::AssetId assetId;
|
|
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
|
|
assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, "fake_prefab.procprefab",
|
|
azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(), true);
|
|
|
|
auto prefabGroup = AZStd::make_shared<AZ::SceneAPI::SceneData::PrefabGroup>();
|
|
prefabGroup.get()->SetId(AZ::Uuid::CreateRandom());
|
|
prefabGroup.get()->SetName("fake_prefab");
|
|
prefabGroup.get()->SetPrefabDom(AZStd::move(jsonOutcome.GetValue()));
|
|
context.m_scene.GetManifest().AddEntry(prefabGroup);
|
|
context.m_scene.SetSource("mock", AZ::Uuid::CreateRandom());
|
|
|
|
auto result = AZ::SceneAPI::Events::ProcessingResult::Failure;
|
|
AZ::SceneAPI::Events::CallProcessorBus::BroadcastResult(
|
|
result,
|
|
&AZ::SceneAPI::Events::CallProcessorBus::Events::Process,
|
|
context.m_preExportEventContext.get());
|
|
|
|
EXPECT_EQ(result, AZ::SceneAPI::Events::ProcessingResult::Success);
|
|
|
|
AZStd::string pathStr;
|
|
AzFramework::StringFunc::Path::ConstructFull(tempDir.GetDirectory(), "mock/fake_prefab.procprefab", pathStr, true);
|
|
if (!AZ::IO::SystemFile::Exists(pathStr.c_str()))
|
|
{
|
|
AZ_Warning("testing", false, "The product asset (%s) is missing", pathStr.c_str());
|
|
}
|
|
}
|
|
|
|
TEST_F(PrefabBehaviorTests, PrefabBehavior_UpdateManifestWithEmptyScene_DoesNotFail)
|
|
{
|
|
using namespace AZ::SceneAPI;
|
|
using namespace AZ::SceneAPI::Events;
|
|
|
|
Containers::Scene scene("empty_scene");
|
|
AssetImportRequest::ManifestAction action = AssetImportRequest::ManifestAction::ConstructDefault;
|
|
AssetImportRequest::RequestingApplication requester = {};
|
|
|
|
Behaviors::PrefabGroupBehavior prefabGroupBehavior;
|
|
ProcessingResult result = ProcessingResult::Failure;
|
|
AssetImportRequestBus::BroadcastResult(result, &AssetImportRequestBus::Events::UpdateManifest, scene, action, requester);
|
|
EXPECT_NE(result, ProcessingResult::Failure);
|
|
}
|
|
|
|
TEST_F(PrefabBehaviorTests, PrefabBehavior_UpdateManifestWithEmptyScene_Ignored)
|
|
{
|
|
using namespace AZ::SceneAPI;
|
|
using namespace AZ::SceneAPI::Events;
|
|
|
|
Containers::Scene scene("empty_scene");
|
|
AssetImportRequest::ManifestAction action = AssetImportRequest::ManifestAction::Update;
|
|
AssetImportRequest::RequestingApplication requester = {};
|
|
|
|
Behaviors::PrefabGroupBehavior prefabGroupBehavior;
|
|
ProcessingResult result = ProcessingResult::Failure;
|
|
AssetImportRequestBus::BroadcastResult(result, &AssetImportRequestBus::Events::UpdateManifest, scene, action, requester);
|
|
EXPECT_EQ(result, ProcessingResult::Ignored);
|
|
}
|
|
|
|
TEST_F(PrefabBehaviorTests, PrefabBehavior_UpdateManifestMockScene_CreatesPrefab)
|
|
{
|
|
using namespace AZ::SceneAPI;
|
|
using namespace AZ::SceneAPI::Events;
|
|
|
|
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
|
|
auto scene = CreateMockScene("Manifest", "C:/o3de/watch.folder/manifest_src_file.xml", "C:/o3de/watch.folder");
|
|
#else
|
|
auto scene = CreateMockScene("Manifest", "//o3de/watch.folder/manifest_src_file.xml", "//o3de/watch.folder");
|
|
#endif
|
|
AssetImportRequest::ManifestAction action = AssetImportRequest::ManifestAction::ConstructDefault;
|
|
AssetImportRequest::RequestingApplication requester = {};
|
|
|
|
Behaviors::PrefabGroupBehavior prefabGroupBehavior;
|
|
ProcessingResult result = ProcessingResult::Failure;
|
|
AssetImportRequestBus::BroadcastResult(result, &AssetImportRequestBus::Events::UpdateManifest, *scene, action, requester);
|
|
|
|
EXPECT_EQ(result, ProcessingResult::Success);
|
|
EXPECT_EQ(scene->GetManifest().GetEntryCount(), 3);
|
|
|
|
EXPECT_TRUE(azrtti_istypeof<AZ::SceneAPI::DataTypes::IMeshGroup>(scene->GetManifest().GetValue(0).get()));
|
|
EXPECT_TRUE(azrtti_istypeof<AZ::SceneAPI::DataTypes::IMeshGroup>(scene->GetManifest().GetValue(1).get()));
|
|
EXPECT_TRUE(azrtti_istypeof<AZ::SceneAPI::DataTypes::IPrefabGroup>(scene->GetManifest().GetValue(2).get()));
|
|
|
|
// The mesh group names are expected to be just the file name relative to the watch folder and not any absolute path
|
|
for (size_t i = 0; i < scene->GetManifest().GetEntryCount(); i++)
|
|
{
|
|
if (azrtti_istypeof<AZ::SceneAPI::DataTypes::IMeshGroup>(scene->GetManifest().GetValue(i).get()))
|
|
{
|
|
AZ::SceneAPI::DataTypes::IMeshGroup* meshGroup = reinterpret_cast<AZ::SceneAPI::DataTypes::IMeshGroup*>(scene->GetManifest().GetValue(i).get());
|
|
AZStd::string groupName = meshGroup->GetName();
|
|
EXPECT_TRUE(groupName.starts_with("default_mock_"));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(PrefabBehaviorTests, PrefabBehavior_UpdateManifest_ToggleWorks)
|
|
{
|
|
using namespace AZ::SceneAPI;
|
|
using namespace AZ::SceneAPI::Events;
|
|
|
|
ASSERT_TRUE(AZ::SettingsRegistry::Get());
|
|
AZ::SettingsRegistry::Get()->Set("/O3DE/Preferences/Prefabs/CreateDefaults", false);
|
|
|
|
auto scene = CreateMockScene();
|
|
AssetImportRequest::ManifestAction action = AssetImportRequest::ManifestAction::ConstructDefault;
|
|
AssetImportRequest::RequestingApplication requester = {};
|
|
|
|
Behaviors::PrefabGroupBehavior prefabGroupBehavior;
|
|
ProcessingResult result = ProcessingResult::Failure;
|
|
AssetImportRequestBus::BroadcastResult(result, &AssetImportRequestBus::Events::UpdateManifest, *scene, action, requester);
|
|
EXPECT_EQ(result, ProcessingResult::Ignored);
|
|
EXPECT_EQ(scene->GetManifest().GetEntryCount(), 0);
|
|
}
|
|
}
|