diff --git a/Code/Editor/CMakeLists.txt b/Code/Editor/CMakeLists.txt index 5158ebc6d5..5884795413 100644 --- a/Code/Editor/CMakeLists.txt +++ b/Code/Editor/CMakeLists.txt @@ -254,4 +254,35 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_googletest( NAME Legacy::EditorLib.Tests ) + + ly_add_target( + NAME EditorLib.Camera.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Legacy + FILES_CMAKE + Lib/Tests/Camera/editor_lib_camera_test_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + . + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + AZ::AzTest + AZ::AzToolsFramework + AZ::AzTestShared + Legacy::EditorLib + Gem::Camera.Editor + Gem::AtomToolsFramework.Static + RUNTIME_DEPENDENCIES + Legacy::EditorLib + ) + + ly_add_source_properties( + SOURCES Lib/Tests/Camera/test_EditorCamera.cpp + PROPERTY COMPILE_DEFINITIONS + VALUES CAMERA_EDITOR_MODULE="$" + ) + + ly_add_googletest( + NAME Legacy::EditorLib.Camera.Tests + ) endif() diff --git a/Code/Editor/EditorModularViewportCameraComposer.cpp b/Code/Editor/EditorModularViewportCameraComposer.cpp index 498d6f3353..e375e98fb6 100644 --- a/Code/Editor/EditorModularViewportCameraComposer.cpp +++ b/Code/Editor/EditorModularViewportCameraComposer.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -34,10 +35,12 @@ namespace SandboxEditor : m_viewportId(viewportId) { EditorModularViewportCameraComposerNotificationBus::Handler::BusConnect(viewportId); + Camera::EditorCameraNotificationBus::Handler::BusConnect(); } EditorModularViewportCameraComposer::~EditorModularViewportCameraComposer() { + Camera::EditorCameraNotificationBus::Handler::BusDisconnect(); EditorModularViewportCameraComposerNotificationBus::Handler::BusDisconnect(); } @@ -283,4 +286,22 @@ namespace SandboxEditor m_orbitCamera->SetOrbitInputChannelId(SandboxEditor::CameraOrbitChannelId()); m_orbitDollyMoveCamera->SetDollyInputChannelId(SandboxEditor::CameraOrbitDollyChannelId()); } + + void EditorModularViewportCameraComposer::OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId) + { + if (viewEntityId.IsValid()) + { + AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity(); + AZ::TransformBus::EventResult(worldFromLocal, viewEntityId, &AZ::TransformBus::Events::GetWorldTM); + + AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( + m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, + worldFromLocal); + } + else + { + AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( + m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::ClearReferenceFrame); + } + } } // namespace SandboxEditor diff --git a/Code/Editor/EditorModularViewportCameraComposer.h b/Code/Editor/EditorModularViewportCameraComposer.h index cb223d39e6..e6e71c976c 100644 --- a/Code/Editor/EditorModularViewportCameraComposer.h +++ b/Code/Editor/EditorModularViewportCameraComposer.h @@ -10,13 +10,16 @@ #include #include +#include #include #include namespace SandboxEditor { //! Type responsible for building the editor's modular viewport camera controller. - class EditorModularViewportCameraComposer : private EditorModularViewportCameraComposerNotificationBus::Handler + class EditorModularViewportCameraComposer + : private EditorModularViewportCameraComposerNotificationBus::Handler + , private Camera::EditorCameraNotificationBus::Handler { public: SANDBOX_API explicit EditorModularViewportCameraComposer(AzFramework::ViewportId viewportId); @@ -32,6 +35,9 @@ namespace SandboxEditor // EditorModularViewportCameraComposerNotificationBus overrides ... void OnEditorModularViewportCameraComposerSettingsChanged() override; + // EditorCameraNotificationBus overrides ... + void OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId) override; + AZStd::shared_ptr m_firstPersonRotateCamera; AZStd::shared_ptr m_firstPersonPanCamera; AZStd::shared_ptr m_firstPersonTranslateCamera; diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Android/tool_dependencies_android.cmake b/Code/Editor/Lib/Tests/Camera/editor_lib_camera_test_files.cmake similarity index 85% rename from Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Android/tool_dependencies_android.cmake rename to Code/Editor/Lib/Tests/Camera/editor_lib_camera_test_files.cmake index 5bf4d7cb7e..69d3e37f2d 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Android/tool_dependencies_android.cmake +++ b/Code/Editor/Lib/Tests/Camera/editor_lib_camera_test_files.cmake @@ -6,5 +6,6 @@ # # -set(GEM_DEPENDENCIES +set(FILES + test_EditorCamera.cpp ) diff --git a/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp b/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp new file mode 100644 index 0000000000..affe5794cc --- /dev/null +++ b/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp @@ -0,0 +1,225 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class EditorCameraTestEnvironment : public AZ::Test::GemTestEnvironment + { + // AZ::Test::GemTestEnvironment overrides ... + void AddGemsAndComponents() override; + }; + + void EditorCameraTestEnvironment::AddGemsAndComponents() + { + AddDynamicModulePaths({ CAMERA_EDITOR_MODULE }); + AddComponentDescriptors({ AzToolsFramework::Components::TransformComponent::CreateDescriptor() }); + } + + class EditorCameraFixture : public ::testing::Test + { + public: + AtomToolsFramework::ModularCameraViewportContext* m_cameraViewportContextView = nullptr; + AZStd::unique_ptr m_editorModularViewportCameraComposer; + AZStd::unique_ptr m_editorLibHandle; + AzFramework::ViewportControllerListPtr m_controllerList; + AZStd::unique_ptr m_entity; + + static const AzFramework::ViewportId TestViewportId; + + void SetUp() override + { + m_editorLibHandle = AZ::DynamicModuleHandle::Create("EditorLib"); + [[maybe_unused]] const bool loaded = m_editorLibHandle->Load(true); + AZ_Assert(loaded, "EditorLib could not be loaded"); + + m_controllerList = AZStd::make_shared(); + m_controllerList->RegisterViewportContext(TestViewportId); + + m_entity = AZStd::make_unique(); + m_entity->Init(); + m_entity->CreateComponent(); + m_entity->Activate(); + + m_editorModularViewportCameraComposer = AZStd::make_unique(TestViewportId); + + auto controller = m_editorModularViewportCameraComposer->CreateModularViewportCameraController(); + // set some overrides for the test + controller->SetCameraViewportContextBuilderCallback( + [this](AZStd::unique_ptr& cameraViewportContext) mutable + { + cameraViewportContext = AZStd::make_unique(); + m_cameraViewportContextView = cameraViewportContext.get(); + }); + + m_controllerList->Add(controller); + } + + void TearDown() override + { + m_editorModularViewportCameraComposer.reset(); + m_cameraViewportContextView = nullptr; + m_entity.reset(); + m_editorLibHandle = {}; + } + }; + + const AzFramework::ViewportId EditorCameraFixture::TestViewportId = AzFramework::ViewportId(1337); + + TEST_F(EditorCameraFixture, ModularViewportCameraControllerReferenceFrameUpdatedWhenViewportEntityisChanged) + { + // Given + const auto entityTransform = AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(10.0f, 5.0f, -2.0f)); + AZ::TransformBus::Event(m_entity->GetId(), &AZ::TransformBus::Events::SetWorldTM, entityTransform); + + // When + // imitate viewport entity changing + Camera::EditorCameraNotificationBus::Broadcast( + &Camera::EditorCameraNotificationBus::Events::OnViewportViewEntityChanged, m_entity->GetId()); + + // ensure the viewport updates after the viewport view entity change + const float deltaTime = 1.0f / 60.0f; + m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() }); + + // retrieve updated camera transform + const AZ::Transform cameraTransform = m_cameraViewportContextView->GetCameraTransform(); + + // Then + // camera transform matches that of the entity + EXPECT_THAT(cameraTransform, IsClose(entityTransform)); + } + + TEST_F(EditorCameraFixture, ReferenceFrameRemainsIdentityAfterExternalCameraTransformChangeWhenNotSet) + { + // Given + m_cameraViewportContextView->SetCameraTransform(AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 20.0f, 30.0f))); + + // When + AZ::Transform referenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f)); + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + referenceFrame, TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame); + + // Then + // reference frame is still the identity + EXPECT_THAT(referenceFrame, IsClose(AZ::Transform::CreateIdentity())); + } + + TEST_F(EditorCameraFixture, ExternalCameraTransformChangeWhenReferenceFrameIsSetUpdatesReferenceFrame) + { + // Given + const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f)); + AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( + TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame); + + const AZ::Transform nextTransform = AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 20.0f, 30.0f)); + m_cameraViewportContextView->SetCameraTransform(nextTransform); + + // When + AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f)); + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + currentReferenceFrame, TestViewportId, + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame); + + // Then + EXPECT_THAT(currentReferenceFrame, IsClose(nextTransform)); + } + + TEST_F(EditorCameraFixture, ReferenceFrameReturnedToIdentityAfterClear) + { + // Given + const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f)); + AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( + TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame); + + // When + AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( + TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::ClearReferenceFrame); + + AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f)); + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + currentReferenceFrame, TestViewportId, + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame); + + // Then + EXPECT_THAT(currentReferenceFrame, IsClose(AZ::Transform::CreateIdentity())); + } + + TEST_F(EditorCameraFixture, InterpolateToTransform) + { + // When + AZ::Transform transformToInterpolateTo = AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationZ(AZ::DegToRad(90.0f)), AZ::Vector3(20.0f, 40.0f, 60.0f)); + AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( + TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, + transformToInterpolateTo, 0.0f); + + // simulate interpolation + m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() }); + m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() }); + + const auto finalTransform = m_cameraViewportContextView->GetCameraTransform(); + + // Then + EXPECT_THAT(finalTransform, IsClose(transformToInterpolateTo)); + } + + TEST_F(EditorCameraFixture, InterpolateToTransformWithReferenceSpaceSet) + { + // Given + const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f)); + AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( + TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame); + + AZ::Transform transformToInterpolateTo = AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationZ(AZ::DegToRad(90.0f)), AZ::Vector3(20.0f, 40.0f, 60.0f)); + + // When + AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( + TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, + transformToInterpolateTo, 0.0f); + + // simulate interpolation + m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() }); + m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() }); + + AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f)); + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + currentReferenceFrame, TestViewportId, + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame); + + const auto finalTransform = m_cameraViewportContextView->GetCameraTransform(); + + // Then + EXPECT_THAT(finalTransform, IsClose(transformToInterpolateTo)); + EXPECT_THAT(currentReferenceFrame, IsClose(AZ::Transform::CreateIdentity())); + } +} // namespace UnitTest + +// required to support running integration tests with the Camera Gem +AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv) +{ + ::testing::InitGoogleMock(&argc, argv); + AZ::Test::printUnusedParametersWarning(argc, argv); + AZ::Test::addTestEnvironments({ new UnitTest::EditorCameraTestEnvironment() }); + int result = RUN_ALL_TESTS(); + return result; +} + +IMPLEMENT_TEST_EXECUTABLE_MAIN(); diff --git a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp index ce5564c7f6..157291cfc2 100644 --- a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp +++ b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp @@ -58,28 +58,6 @@ namespace UnitTest return true; } - class TestModularCameraViewportContextImpl : public AtomToolsFramework::ModularCameraViewportContext - { - public: - AZ::Transform GetCameraTransform() const override - { - return m_cameraTransform; - } - - void SetCameraTransform(const AZ::Transform& transform) override - { - m_cameraTransform = transform; - } - - void ConnectViewMatrixChangedHandler(AZ::RPI::ViewportContext::MatrixChangedEvent::Handler&) override - { - // noop - } - - private: - AZ::Transform m_cameraTransform = AZ::Transform::CreateIdentity(); - }; - class ModularViewportCameraControllerFixture : public AllocatorsTestFixture { public: @@ -146,7 +124,7 @@ namespace UnitTest controller->SetCameraViewportContextBuilderCallback( [this](AZStd::unique_ptr& cameraViewportContext) { - cameraViewportContext = AZStd::make_unique(); + cameraViewportContext = AZStd::make_unique(); m_cameraViewportContextView = cameraViewportContext.get(); }); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp index 75d31321c2..91a916ae70 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -248,6 +249,7 @@ namespace AzToolsFramework components.insert(components.end(), { azrtti_typeid(), azrtti_typeid(), + azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp index 97f52e253b..b68d086892 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,7 @@ namespace AzToolsFramework Components::EditorSelectionAccentSystemComponent::CreateDescriptor(), EditorEntityContextComponent::CreateDescriptor(), EditorEntityFixupComponent::CreateDescriptor(), + FocusModeSystemComponent::CreateDescriptor(), SliceMetadataEntityContextComponent::CreateDescriptor(), SliceRequestComponent::CreateDescriptor(), Prefab::PrefabSystemComponent::CreateDescriptor(), diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h new file mode 100644 index 0000000000..6d3a7599de --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h @@ -0,0 +1,39 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include + +namespace AzToolsFramework +{ + //! FocusModeInterface + //! Interface to handle the Editor Focus Mode. + class FocusModeInterface + { + public: + AZ_RTTI(FocusModeInterface, "{437243B0-F86B-422F-B7B8-4A21CC000702}"); + + //! Sets the root entity the Editor should focus on. + //! The Editor will only allow the user to select entities that are descendants of the EntityId provided. + //! @param entityId The entityId that will become the new focus root. + virtual void SetFocusRoot(AZ::EntityId entityId) = 0; + + //! Clears the Editor focus, allowing the user to select the whole level again. + virtual void ClearFocusRoot() = 0; + + //! Returns the entity id of the root of the current Editor focus. + //! @return The entity id of the root of the Editor focus, or an invalid entity id if no focus is set. + virtual AZ::EntityId GetFocusRoot() = 0; + + //! Returns whether the entity id provided is part of the focused sub-tree. + virtual bool IsInFocusSubTree(AZ::EntityId entityId) = 0; + }; + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp new file mode 100644 index 0000000000..e08191032d --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp @@ -0,0 +1,92 @@ +/* + * 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 + +#include +#include + +namespace AzToolsFramework +{ + bool IsInFocusSubTree(AZ::EntityId entityId, AZ::EntityId focusRootId) + { + if (entityId == AZ::EntityId()) + { + return false; + } + + if (entityId == focusRootId) + { + return true; + } + + AZ::EntityId parentId; + AZ::TransformBus::EventResult(parentId, entityId, &AZ::TransformInterface::GetParentId); + + return IsInFocusSubTree(parentId, focusRootId); + } + + void FocusModeSystemComponent::Init() + { + } + + void FocusModeSystemComponent::Activate() + { + AZ::Interface::Register(this); + } + + void FocusModeSystemComponent::Deactivate() + { + AZ::Interface::Unregister(this); + } + + void FocusModeSystemComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context) + { + } + + void FocusModeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("EditorFocusMode")); + } + + void FocusModeSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) + { + } + + void FocusModeSystemComponent::GetIncompatibleServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + } + + void FocusModeSystemComponent::SetFocusRoot(AZ::EntityId entityId) + { + m_focusRoot = entityId; + + // TODO - If m_focusRoot != AZ::EntityId(), activate focus mode via ViewportEditorModeTrackerInterface; else, deactivate focus mode + } + + void FocusModeSystemComponent::ClearFocusRoot() + { + SetFocusRoot(AZ::EntityId()); + } + + AZ::EntityId FocusModeSystemComponent::GetFocusRoot() + { + return m_focusRoot; + } + + bool FocusModeSystemComponent::IsInFocusSubTree(AZ::EntityId entityId) + { + if (m_focusRoot == AZ::EntityId()) + { + return true; + } + + return AzToolsFramework::IsInFocusSubTree(entityId, m_focusRoot); + } + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h new file mode 100644 index 0000000000..697a4ff998 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h @@ -0,0 +1,51 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include + +#include + +namespace AzToolsFramework +{ + bool IsInFocusSubTree(AZ::EntityId entityId, AZ::EntityId focusRootId); + + //! System Component to handle the Editor Focus Mode system + class FocusModeSystemComponent final + : public AZ::Component + , private FocusModeInterface + { + public: + AZ_COMPONENT(FocusModeSystemComponent, "{6CE522FE-2057-4794-BD05-61E04BD8EA30}"); + + FocusModeSystemComponent() = default; + virtual ~FocusModeSystemComponent() = default; + + // AZ::Component overrides ... + void Init() override; + void Activate() override; + void Deactivate() override; + + static void Reflect(AZ::ReflectContext* context); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + + // FocusModeInterface overrides ... + void SetFocusRoot(AZ::EntityId entityId) override; + void ClearFocusRoot() override; + AZ::EntityId GetFocusRoot() override; + bool IsInFocusSubTree(AZ::EntityId entityId) override; + + private: + AZ::EntityId m_focusRoot; + }; + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp new file mode 100644 index 0000000000..2338c8892b --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -0,0 +1,96 @@ +/* + * 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 + +#include +#include +#include + +namespace AzToolsFramework::Prefab +{ + PrefabFocusHandler::PrefabFocusHandler() + { + m_instanceEntityMapperInterface = AZ::Interface::Get(); + AZ_Assert( + m_instanceEntityMapperInterface, + "Prefab - PrefabFocusHandler - " + "Instance Entity Mapper Interface could not be found. " + "Check that it is being correctly initialized."); + + AZ::Interface::Register(this); + } + + PrefabFocusHandler::~PrefabFocusHandler() + { + AZ::Interface::Unregister(this); + } + + PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId) + { + InstanceOptionalReference focusedInstance; + + if (entityId == AZ::EntityId()) + { + PrefabEditorEntityOwnershipInterface* prefabEditorEntityOwnershipInterface = + AZ::Interface::Get(); + + if(!prefabEditorEntityOwnershipInterface) + { + return AZ::Failure(AZStd::string("Could not focus on root prefab instance - internal error " + "(PrefabEditorEntityOwnershipInterface unavailable).")); + } + + focusedInstance = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance(); + } + else + { + focusedInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); + } + + if (!focusedInstance.has_value()) + { + return AZ::Failure(AZStd::string( + "Prefab Focus Handler: Couldn't find owning instance of entityId provided.")); + } + + m_focusedInstance = focusedInstance; + m_focusedTemplateId = focusedInstance->get().GetTemplateId(); + + FocusModeInterface* focusModeInterface = AZ::Interface::Get(); + if (focusModeInterface) + { + focusModeInterface->SetFocusRoot(focusedInstance->get().GetContainerEntityId()); + } + + return AZ::Success(); + } + + TemplateId PrefabFocusHandler::GetFocusedPrefabTemplateId() + { + return m_focusedTemplateId; + } + + InstanceOptionalReference PrefabFocusHandler::GetFocusedPrefabInstance() + { + return m_focusedInstance; + } + + bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) + { + if (entityId == AZ::EntityId()) + { + return false; + } + + InstanceOptionalReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); + + return instance.has_value() && (&instance->get() == &m_focusedInstance->get()); + } + +} // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h new file mode 100644 index 0000000000..fa7727894d --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h @@ -0,0 +1,44 @@ +/* + * 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 + * + */ + +#pragma once + +#include + +#include +#include +#include + +namespace AzToolsFramework::Prefab +{ + class InstanceEntityMapperInterface; + + //! Handles Prefab Focus mode, determining which prefab file entity changes will target. + class PrefabFocusHandler final + : private PrefabFocusInterface + { + public: + AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0); + + PrefabFocusHandler(); + ~PrefabFocusHandler(); + + // PrefabFocusInterface override ... + PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override; + TemplateId GetFocusedPrefabTemplateId() override; + InstanceOptionalReference GetFocusedPrefabInstance() override; + bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) override; + + private: + InstanceOptionalReference m_focusedInstance; + TemplateId m_focusedTemplateId; + + InstanceEntityMapperInterface* m_instanceEntityMapperInterface; + }; + +} // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h new file mode 100644 index 0000000000..833a5ef7c8 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h @@ -0,0 +1,43 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include + +#include +#include + +namespace AzToolsFramework::Prefab +{ + using PrefabFocusOperationResult = AZ::Outcome; + + //! Interface to handle operations related to the Prefab Focus system. + class PrefabFocusInterface + { + public: + AZ_RTTI(PrefabFocusInterface, "{F3CFA37B-5FD8-436A-9C30-60EB54E350E1}"); + + //! Set the focused prefab instance to the owning instance of the entityId provided. + //! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on. + virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0; + + //! Returns the template id of the instance the prefab system is focusing on. + virtual TemplateId GetFocusedPrefabTemplateId() = 0; + + //! Returns a reference to the instance the prefab system is focusing on. + virtual InstanceOptionalReference GetFocusedPrefabInstance() = 0; + + //! Returns whether the entity belongs to the instance that is being focused on, or one of its descendants. + //! @param entityId The entityId of the queried entity. + //! @return true if the entity belongs to the focused instance or one of its descendants, false otherwise. + virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) = 0; + }; + +} // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index 42b6bc1666..f3c0e67d46 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -189,6 +190,9 @@ namespace AzToolsFramework PrefabLoaderInterface* m_prefabLoaderInterface = nullptr; PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr; + // Handles the Prefab Focus API that determines what prefab is being edited. + PrefabFocusHandler m_prefabFocusHandler; + // Caches entity states for undo/redo purposes PrefabUndoCache m_prefabUndoCache; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h index d6908922c9..746c306c2b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h @@ -369,7 +369,7 @@ namespace AzToolsFramework // A counter for generating unique Link Ids. AZStd::atomic m_linkIdCounter = 0u; - // Used for finding the owning instance of an arbitrary entity + // Used for finding the owning instance of an arbitrary entity. InstanceEntityMapper m_instanceEntityMapper; // Used for finding the Instances owned by an arbitrary Template. @@ -378,16 +378,16 @@ namespace AzToolsFramework // Used for loading/saving Prefab Template files. PrefabLoader m_prefabLoader; - // Handler the public Prefab API used by UI and scripting + // Handles the public Prefab API used by UI and scripting. PrefabPublicHandler m_prefabPublicHandler; // Used for updating Instances of Prefab Template. InstanceUpdateExecutor m_instanceUpdateExecutor; - // Used for updating Templates when Instances are modified + // Used for updating Templates when Instances are modified. InstanceToTemplatePropagator m_instanceToTemplatePropagator; - // Handler of the public Prefab requests + // Handler of the public Prefab requests. PrefabPublicRequestHandler m_prefabPublicRequestHandler; }; } // namespace Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp index c75f6aa86d..bb6bdf0ebd 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp @@ -8,7 +8,6 @@ #include -#include #include #include @@ -24,14 +23,6 @@ namespace AzToolsFramework LevelRootUiHandler::LevelRootUiHandler() { - m_prefabEditInterface = AZ::Interface::Get(); - - if (m_prefabEditInterface == nullptr) - { - AZ_Assert(false, "LevelRootUiHandler - could not get PrefabEditInterface on LevelRootUiHandler construction."); - return; - } - m_prefabPublicInterface = AZ::Interface::Get(); if (m_prefabPublicInterface == nullptr) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h index 6eeccfe88d..1e485572b8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h @@ -14,7 +14,6 @@ namespace AzToolsFramework { namespace Prefab { - class PrefabEditInterface; class PrefabPublicInterface; }; @@ -36,7 +35,6 @@ namespace AzToolsFramework void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; private: - Prefab::PrefabEditInterface* m_prefabEditInterface = nullptr; Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr; static constexpr int m_levelRootBorderThickness = 1; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditInterface.h deleted file mode 100644 index 1bd4a164f8..0000000000 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditInterface.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 - * - */ - -#pragma once - -#include -#include - -namespace AzToolsFramework -{ - namespace Prefab - { - /*! - * PrefabEditInterface - * Interface to expose the API to Edit Prefabs in the Editor. - */ - class PrefabEditInterface - { - public: - AZ_RTTI(PrefabEditInterface, "{DABB1D43-3760-420E-9F1E-5104F0AFF167}"); - - /** - * Sets the prefab for the instance owning the entity provided as the prefab being edited. - * @param entityId The entity whose owning prefab should be edited. - */ - virtual void EditOwningPrefab(AZ::EntityId entityId) = 0; - - /** - * Queries the Edit Manager to know if the provided entity is part of the prefab currently being edited. - * @param entityId The entity whose prefab editing state we want to query. - * @return True if the prefab owning this entity is being edited, false otherwise. - */ - virtual bool IsOwningPrefabBeingEdited(AZ::EntityId entityId) = 0; - }; - - } // namespace Prefab -} // namespace AzToolsFramework - diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.cpp deleted file mode 100644 index 27a4c3bcaf..0000000000 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 - -#include - -namespace AzToolsFramework -{ - namespace Prefab - { - PrefabEditManager::PrefabEditManager() - { - m_prefabPublicInterface = AZ::Interface::Get(); - - if (m_prefabPublicInterface == nullptr) - { - AZ_Assert(false, "Prefab - could not get PrefabPublicInterface on PrefabEditManager construction."); - return; - } - - AZ::Interface::Register(this); - } - - PrefabEditManager::~PrefabEditManager() - { - AZ::Interface::Unregister(this); - } - - void PrefabEditManager::EditOwningPrefab(AZ::EntityId entityId) - { - m_instanceBeingEdited = m_prefabPublicInterface->GetInstanceContainerEntityId(entityId); - } - - bool PrefabEditManager::IsOwningPrefabBeingEdited(AZ::EntityId entityId) - { - AZ::EntityId containerEntity = m_prefabPublicInterface->GetInstanceContainerEntityId(entityId); - return m_instanceBeingEdited == containerEntity; - } - } -} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.h deleted file mode 100644 index d155322e3c..0000000000 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabEditManager.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 - * - */ - -#pragma once - -#include -#include - -#include -#include - -namespace AzToolsFramework -{ - namespace Prefab - { - class PrefabEditManager final - : private PrefabEditInterface - { - public: - AZ_CLASS_ALLOCATOR(PrefabEditManager, AZ::SystemAllocator, 0); - - PrefabEditManager(); - ~PrefabEditManager(); - - private: - // PrefabEditInterface... - void EditOwningPrefab(AZ::EntityId entityId) override; - bool IsOwningPrefabBeingEdited(AZ::EntityId entityId) override; - - AZ::EntityId m_instanceBeingEdited; - - PrefabPublicInterface* m_prefabPublicInterface; - }; - } -} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 0c61ccb501..9b7f2fff10 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -57,9 +58,9 @@ namespace AzToolsFramework { EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr; - PrefabPublicInterface* PrefabIntegrationManager::s_prefabPublicInterface = nullptr; - PrefabEditInterface* PrefabIntegrationManager::s_prefabEditInterface = nullptr; + PrefabFocusInterface* PrefabIntegrationManager::s_prefabFocusInterface = nullptr; PrefabLoaderInterface* PrefabIntegrationManager::s_prefabLoaderInterface = nullptr; + PrefabPublicInterface* PrefabIntegrationManager::s_prefabPublicInterface = nullptr; PrefabSystemComponentInterface* PrefabIntegrationManager::s_prefabSystemComponentInterface = nullptr; const AZStd::string PrefabIntegrationManager::s_prefabFileExtension = ".prefab"; @@ -102,13 +103,6 @@ namespace AzToolsFramework return; } - s_prefabEditInterface = AZ::Interface::Get(); - if (s_prefabEditInterface == nullptr) - { - AZ_Assert(false, "Prefab - could not get PrefabEditInterface on PrefabIntegrationManager construction."); - return; - } - s_prefabLoaderInterface = AZ::Interface::Get(); if (s_prefabLoaderInterface == nullptr) { @@ -123,6 +117,13 @@ namespace AzToolsFramework return; } + s_prefabFocusInterface = AZ::Interface::Get(); + if (s_prefabFocusInterface == nullptr) + { + AZ_Assert(false, "Prefab - could not get PrefabFocusInterface on PrefabIntegrationManager construction."); + return; + } + EditorContextMenuBus::Handler::BusConnect(); PrefabInstanceContainerNotificationBus::Handler::BusConnect(); AZ::Interface::Register(this); @@ -224,7 +225,7 @@ namespace AzToolsFramework // Edit Prefab if (prefabWipFeaturesEnabled) { - bool beingEdited = s_prefabEditInterface->IsOwningPrefabBeingEdited(selectedEntity); + bool beingEdited = s_prefabFocusInterface->IsOwningPrefabBeingFocused(selectedEntity); if (!beingEdited) { @@ -428,7 +429,7 @@ namespace AzToolsFramework void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity) { - s_prefabEditInterface->EditOwningPrefab(containerEntity); + s_prefabFocusInterface->FocusOnOwningPrefab(containerEntity); } void PrefabIntegrationManager::ContextMenu_SavePrefab(AZ::EntityId containerEntity) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h index 3e66350932..6f66b1f514 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h @@ -17,7 +17,7 @@ #include #include #include -#include + #include #include #include @@ -28,7 +28,7 @@ namespace AzToolsFramework { namespace Prefab { - + class PrefabFocusInterface; class PrefabLoaderInterface; //! Structure for saving/retrieving user settings related to prefab workflows. @@ -80,9 +80,6 @@ namespace AzToolsFramework void ExecuteSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference) override; private: - // Manages the Edit Mode UI for prefabs - PrefabEditManager m_prefabEditManager; - // Used to handle the UI for the level root LevelRootUiHandler m_levelRootUiHandler; @@ -135,13 +132,12 @@ namespace AzToolsFramework AZStd::unique_ptr ConstructSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference); void SavePrefabsInDialog(QDialog* unsavedPrefabsDialog); - static const AZStd::string s_prefabFileExtension; static EditorEntityUiInterface* s_editorEntityUiInterface; - static PrefabPublicInterface* s_prefabPublicInterface; - static PrefabEditInterface* s_prefabEditInterface; + static PrefabFocusInterface* s_prefabFocusInterface; static PrefabLoaderInterface* s_prefabLoaderInterface; + static PrefabPublicInterface* s_prefabPublicInterface; static PrefabSystemComponentInterface* s_prefabSystemComponentInterface; }; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp index fda56b79f1..c802850c4b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp @@ -8,7 +8,7 @@ #include -#include +#include #include #include @@ -26,21 +26,19 @@ namespace AzToolsFramework PrefabUiHandler::PrefabUiHandler() { - m_prefabEditInterface = AZ::Interface::Get(); - - if (m_prefabEditInterface == nullptr) - { - AZ_Assert(false, "PrefabUiHandler - could not get PrefabEditInterface on PrefabUiHandler construction."); - return; - } - m_prefabPublicInterface = AZ::Interface::Get(); - if (m_prefabPublicInterface == nullptr) { AZ_Assert(false, "PrefabUiHandler - could not get PrefabPublicInterface on PrefabUiHandler construction."); return; } + + m_prefabFocusInterface = AZ::Interface::Get(); + if (m_prefabFocusInterface == nullptr) + { + AZ_Assert(false, "PrefabUiHandler - could not get PrefabFocusInterface on PrefabUiHandler construction."); + return; + } } QString PrefabUiHandler::GenerateItemInfoString(AZ::EntityId entityId) const @@ -83,7 +81,7 @@ namespace AzToolsFramework QIcon PrefabUiHandler::GenerateItemIcon(AZ::EntityId entityId) const { - if (m_prefabEditInterface->IsOwningPrefabBeingEdited(entityId)) + if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId)) { return QIcon(m_prefabEditIconPath); } @@ -105,7 +103,7 @@ namespace AzToolsFramework const bool hasVisibleChildren = index.data(EntityOutlinerListModel::ExpandedRole).value() && index.model()->hasChildren(index); QColor backgroundColor = m_prefabCapsuleColor; - if (m_prefabEditInterface->IsOwningPrefabBeingEdited(entityId)) + if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId)) { backgroundColor = m_prefabCapsuleEditColor; } @@ -191,7 +189,7 @@ namespace AzToolsFramework const bool isLastColumn = descendantIndex.column() == EntityOutlinerListModel::ColumnLockToggle; QColor borderColor = m_prefabCapsuleColor; - if (m_prefabEditInterface->IsOwningPrefabBeingEdited(entityId)) + if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId)) { borderColor = m_prefabCapsuleEditColor; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h index d900fae427..547c100eb1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h @@ -12,9 +12,10 @@ namespace AzToolsFramework { + namespace Prefab { - class PrefabEditInterface; + class PrefabFocusInterface; class PrefabPublicInterface; }; @@ -37,7 +38,7 @@ namespace AzToolsFramework const QModelIndex& descendantIndex) const override; private: - Prefab::PrefabEditInterface* m_prefabEditInterface = nullptr; + Prefab::PrefabFocusInterface* m_prefabFocusInterface = nullptr; Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr; static bool IsLastVisibleChild(const QModelIndex& parent, const QModelIndex& child); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 27a77f9ce7..8157d369d2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -3642,7 +3642,7 @@ namespace AzToolsFramework } } - void EditorTransformComponentSelection::OnViewportViewEntityChanged(const AZ::EntityId& newViewId) + void EditorTransformComponentSelection::OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId) { AZ_PROFILE_FUNCTION(AzToolsFramework); @@ -3650,12 +3650,12 @@ namespace AzToolsFramework // match the editor camera translation/orientation), record the entity id if we have // a manipulator tracking it (entity id exists in m_entityIdManipulator lookups) // and remove it when recreating manipulators (see InitializeManipulators) - if (newViewId.IsValid()) + if (viewEntityId.IsValid()) { - const auto entityIdLookupIt = m_entityIdManipulators.m_lookups.find(newViewId); + const auto entityIdLookupIt = m_entityIdManipulators.m_lookups.find(viewEntityId); if (entityIdLookupIt != m_entityIdManipulators.m_lookups.end()) { - m_editorCameraComponentEntityId = newViewId; + m_editorCameraComponentEntityId = viewEntityId; RegenerateManipulators(); } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index 935e80951a..6cc89b2d10 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -270,7 +270,7 @@ namespace AzToolsFramework void OnTransformChanged(const AZ::Transform& localTM, const AZ::Transform& worldTM) override; // Camera::EditorCameraNotificationBus overrides ... - void OnViewportViewEntityChanged(const AZ::EntityId& newViewId) override; + void OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId) override; // EditorContextVisibilityNotificationBus overrides ... void OnEntityVisibilityChanged(bool visibility) override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index 3932190d8e..88febef8c2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -149,6 +149,9 @@ set(FILES Entity/SliceEditorEntityOwnershipServiceBus.h Fingerprinting/TypeFingerprinter.h Fingerprinting/TypeFingerprinter.cpp + FocusMode/FocusModeInterface.h + FocusMode/FocusModeSystemComponent.h + FocusMode/FocusModeSystemComponent.cpp Logger/TraceLogger.cpp Logger/TraceLogger.h Manipulators/AngularManipulator.cpp @@ -629,6 +632,9 @@ set(FILES Prefab/PrefabDomTypes.h Prefab/PrefabDomUtils.h Prefab/PrefabDomUtils.cpp + Prefab/PrefabFocusHandler.h + Prefab/PrefabFocusHandler.cpp + Prefab/PrefabFocusInterface.h Prefab/PrefabIdTypes.h Prefab/PrefabLoader.h Prefab/PrefabLoader.cpp @@ -721,9 +727,6 @@ set(FILES UI/Layer/LayerUiHandler.cpp UI/Prefab/LevelRootUiHandler.h UI/Prefab/LevelRootUiHandler.cpp - UI/Prefab/PrefabEditInterface.h - UI/Prefab/PrefabEditManager.h - UI/Prefab/PrefabEditManager.cpp UI/Prefab/PrefabIntegrationBus.h UI/Prefab/PrefabIntegrationManager.h UI/Prefab/PrefabIntegrationManager.cpp diff --git a/Code/Tools/ProjectManager/Platform/Linux/ProjectBuilderWorker_linux.cpp b/Code/Tools/ProjectManager/Platform/Linux/ProjectBuilderWorker_linux.cpp index cffac78365..2987fc4dc2 100644 --- a/Code/Tools/ProjectManager/Platform/Linux/ProjectBuilderWorker_linux.cpp +++ b/Code/Tools/ProjectManager/Platform/Linux/ProjectBuilderWorker_linux.cpp @@ -7,14 +7,69 @@ */ #include +#include +#include + +#include +#include namespace O3DE::ProjectManager { - AZ::Outcome ProjectBuilderWorker::BuildProjectForPlatform() + AZ::Outcome ProjectBuilderWorker::ConstructCmakeGenerateProjectArguments(const QString& thirdPartyPath) const { - QString error = tr("Automatic building on Linux not currently supported!"); - QStringToAZTracePrint(error); - return AZ::Failure(error); + // Attempt to use the Ninja build system if it is installed (described in the o3de documentation) if possible, + // otherwise default to the the default for Linux (Unix Makefiles) + auto whichNinjaResult = ProjectUtils::ExecuteCommandResult("which", QStringList{"ninja"}, QProcessEnvironment::systemEnvironment()); + QString cmakeGenerator = (whichNinjaResult.IsSuccess()) ? "Ninja Multi-Config" : "Unix Makefiles"; + bool compileProfileOnBuild = (whichNinjaResult.IsSuccess()); + + // On Linux the default compiler is gcc. For O3DE, it is clang, so we need to specify the version of clang that is detected + // in order to get the compiler option. + auto compilerOptionResult = ProjectUtils::FindSupportedCompilerForPlatform(); + if (!compilerOptionResult.IsSuccess()) + { + return AZ::Failure(compilerOptionResult.GetError()); + } + auto clangCompilers = compilerOptionResult.GetValue().split('|'); + AZ_Assert(clangCompilers.length()==2, "Invalid clang compiler pair specification"); + + QString clangCompilerOption = clangCompilers[0]; + QString clangPPCompilerOption = clangCompilers[1]; + QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix); + QStringList generateProjectArgs = QStringList{ProjectCMakeCommand, + "-B", ProjectBuildPathPostfix, + "-S", ".", + QString("-G%1").arg(cmakeGenerator), + QString("-DCMAKE_C_COMPILER=").append(clangCompilerOption), + QString("-DCMAKE_CXX_COMPILER=").append(clangPPCompilerOption), + QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath)}; + if (!compileProfileOnBuild) + { + generateProjectArgs.append("-DCMAKE_BUILD_TYPE=profile"); + } + return AZ::Success(generateProjectArgs); } - + + AZ::Outcome ProjectBuilderWorker::ConstructCmakeBuildCommandArguments() const + { + auto whichNinjaResult = ProjectUtils::ExecuteCommandResult("which", QStringList{"ninja"}, QProcessEnvironment::systemEnvironment()); + bool compileProfileOnBuild = (whichNinjaResult.IsSuccess()); + QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix); + QString launcherTargetName = m_projectInfo.m_projectName + ".GameLauncher"; + + QStringList buildProjectArgs = QStringList{ProjectCMakeCommand, + "--build", ProjectBuildPathPostfix, + "--target", launcherTargetName, ProjectCMakeBuildTargetEditor}; + if (compileProfileOnBuild) + { + buildProjectArgs.append(QStringList{"--config","profile"}); + } + return AZ::Success(buildProjectArgs); + } + + AZ::Outcome ProjectBuilderWorker::ConstructKillProcessCommandArguments(const QString& pidToKill) const + { + return AZ::Success(QStringList{"kill", "-9", pidToKill}); + } + } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp b/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp index 64d18ec605..56feb9f70a 100644 --- a/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp +++ b/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp @@ -6,15 +6,48 @@ */ #include +#include +#include namespace O3DE::ProjectManager { namespace ProjectUtils { - AZ::Outcome FindSupportedCompilerForPlatform() + // The list of clang C/C++ compiler command lines to validate on the host Linux system + const QStringList SupportedClangCommands = {"clang-12|clang++-12"}; + + AZ::Outcome GetCommandLineProcessEnvironment() { - // Compiler detection not supported on platform - return AZ::Success(); + return AZ::Success(QProcessEnvironment(QProcessEnvironment::systemEnvironment())); + } + + AZ::Outcome FindSupportedCompilerForPlatform() + { + // Validate that cmake is installed and is in the command line + auto whichCMakeResult = ProjectUtils::ExecuteCommandResult("which", QStringList{ProjectCMakeCommand}, QProcessEnvironment::systemEnvironment()); + if (!whichCMakeResult.IsSuccess()) + { + return AZ::Failure(QObject::tr("CMake not found. \n\n" + "Make sure that the minimum version of CMake is installed and available from the command prompt. " + "Refer to the O3DE requirements page for more information.")); + } + + // Look for the first compatible version of clang. The list below will contain the known clang compilers that have been tested for O3DE. + for (const QString& supportClangCommand : SupportedClangCommands) + { + auto clangCompilers = supportClangCommand.split('|'); + AZ_Assert(clangCompilers.length()==2, "Invalid clang compiler pair specification"); + + auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{clangCompilers[0]}, QProcessEnvironment::systemEnvironment()); + auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{clangCompilers[1]}, QProcessEnvironment::systemEnvironment()); + if (whichClangResult.IsSuccess() && whichClangPPResult.IsSuccess()) + { + return AZ::Success(supportClangCommand); + } + } + return AZ::Failure(QObject::tr("Clang not found. \n\n" + "Make sure that the clang is installed and available from the command prompt. " + "Refer to the O3DE requirements page for more information.")); } } // namespace ProjectUtils diff --git a/Code/Tools/ProjectManager/Platform/Mac/ProjectBuilderWorker_mac.cpp b/Code/Tools/ProjectManager/Platform/Mac/ProjectBuilderWorker_mac.cpp index 7f4c5eb5d0..ab412d84d8 100644 --- a/Code/Tools/ProjectManager/Platform/Mac/ProjectBuilderWorker_mac.cpp +++ b/Code/Tools/ProjectManager/Platform/Mac/ProjectBuilderWorker_mac.cpp @@ -7,14 +7,82 @@ */ #include +#include +#include + +#include +#include namespace O3DE::ProjectManager { - AZ::Outcome ProjectBuilderWorker::BuildProjectForPlatform() + namespace Internal { - QString error = tr("Automatic building on MacOS not currently supported!"); - QStringToAZTracePrint(error); - return AZ::Failure(error); + AZ::Outcome QueryInstalledCmakeFullPath() + { + auto environmentRequest = ProjectUtils::GetCommandLineProcessEnvironment(); + if (!environmentRequest.IsSuccess()) + { + return AZ::Failure(environmentRequest.GetError()); + } + auto currentEnvironment = environmentRequest.GetValue(); + + auto queryCmakeInstalled = ProjectUtils::ExecuteCommandResult("which", + QStringList{ProjectCMakeCommand}, + currentEnvironment); + if (!queryCmakeInstalled.IsSuccess()) + { + return AZ::Failure(QObject::tr("Unable to detect CMake on this host.")); + } + QString cmakeInstalledPath = queryCmakeInstalled.GetValue().split("\n")[0]; + return AZ::Success(cmakeInstalledPath); + } + } + + AZ::Outcome ProjectBuilderWorker::ConstructCmakeGenerateProjectArguments(const QString& thirdPartyPath) const + { + // For Mac, we need to resolve the full path of cmake and use that in the process request. For + // some reason, 'which' will resolve the full path, but when you just specify cmake with the same + // environment, it is unable to resolve. To work around this, we will use 'which' to resolve the + // full path and then use it as the command argument + auto cmakeInstalledPathQuery = Internal::QueryInstalledCmakeFullPath(); + if (!cmakeInstalledPathQuery.IsSuccess()) + { + return AZ::Failure(cmakeInstalledPathQuery.GetError()); + } + QString cmakeInstalledPath = cmakeInstalledPathQuery.GetValue(); + QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix); + + return AZ::Success(QStringList{cmakeInstalledPath, + "-B", targetBuildPath, + "-S", m_projectInfo.m_path, + "-GXcode"}); + } + + AZ::Outcome ProjectBuilderWorker::ConstructCmakeBuildCommandArguments() const + { + // For Mac, we need to resolve the full path of cmake and use that in the process request. For + // some reason, 'which' will resolve the full path, but when you just specify cmake with the same + // environment, it is unable to resolve. To work around this, we will use 'which' to resolve the + // full path and then use it as the command argument + auto cmakeInstalledPathQuery = Internal::QueryInstalledCmakeFullPath(); + if (!cmakeInstalledPathQuery.IsSuccess()) + { + return AZ::Failure(cmakeInstalledPathQuery.GetError()); + } + + QString cmakeInstalledPath = cmakeInstalledPathQuery.GetValue(); + QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix); + QString launcherTargetName = m_projectInfo.m_projectName + ".GameLauncher"; + + return AZ::Success(QStringList{cmakeInstalledPath, + "--build", targetBuildPath, + "--config", "profile", + "--target", launcherTargetName, ProjectCMakeBuildTargetEditor}); + } + + AZ::Outcome ProjectBuilderWorker::ConstructKillProcessCommandArguments(const QString& pidToKill) const + { + return AZ::Success(QStringList{"kill", "-9", pidToKill}); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp b/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp index 64d18ec605..0d150abc3b 100644 --- a/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp +++ b/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp @@ -7,15 +7,59 @@ #include +#include + namespace O3DE::ProjectManager { namespace ProjectUtils { - AZ::Outcome FindSupportedCompilerForPlatform() + AZ::Outcome GetCommandLineProcessEnvironment() { - // Compiler detection not supported on platform - return AZ::Success(); + // For CMake on Mac, if its installed through home-brew, then it will be installed + // under /usr/local/bin, which may not be in the system PATH environment. + // Add that path for the command line process so that it will be able to locate + // a home-brew installed version of CMake + QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment()); + QString pathValue = currentEnvironment.value("PATH"); + pathValue += ":/usr/local/bin"; + currentEnvironment.insert("PATH", pathValue); + return AZ::Success(currentEnvironment); } - + + AZ::Outcome FindSupportedCompilerForPlatform() + { + QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment()); + QString pathValue = currentEnvironment.value("PATH"); + pathValue += ":/usr/local/bin"; + currentEnvironment.insert("PATH", pathValue); + + // Validate that we have cmake installed first + auto queryCmakeInstalled = ExecuteCommandResult("which", QStringList{ProjectCMakeCommand}, currentEnvironment); + if (!queryCmakeInstalled.IsSuccess()) + { + return AZ::Failure(QObject::tr("Unable to detect CMake on this host.")); + } + QString cmakeInstalledPath = queryCmakeInstalled.GetValue().split("\n")[0]; + + // Query the version of the installed cmake + auto queryCmakeVersionQuery = ExecuteCommandResult(cmakeInstalledPath, QStringList{"-version"}, currentEnvironment); + if (!queryCmakeVersionQuery.IsSuccess()) + { + return AZ::Failure(QObject::tr("Unable to determine the version of CMake on this host.")); + } + AZ_TracePrintf("Project Manager", "Cmake version %s detected.", queryCmakeVersionQuery.GetValue().split("\n")[0].toUtf8().constData()); + + // Query for the version of xcodebuild (if installed) + auto queryXcodeBuildVersion = ExecuteCommandResult("xcodebuild", QStringList{"-version"}, currentEnvironment); + if (!queryCmakeInstalled.IsSuccess()) + { + return AZ::Failure(QObject::tr("Unable to detect XCodeBuilder on this host.")); + } + QString xcodeBuilderVersionNumber = queryXcodeBuildVersion.GetValue().split("\n")[0]; + AZ_TracePrintf("Project Manager", "XcodeBuilder version %s detected.", xcodeBuilderVersionNumber.toUtf8().constData()); + + + return AZ::Success(xcodeBuilderVersionNumber); + } } // namespace ProjectUtils } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp b/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp index 8856e2312a..075c6de774 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp @@ -8,189 +8,37 @@ #include #include -#include #include -#include -#include -#include -#include -#include +#include namespace O3DE::ProjectManager { - AZ::Outcome ProjectBuilderWorker::BuildProjectForPlatform() + AZ::Outcome ProjectBuilderWorker::ConstructCmakeGenerateProjectArguments(const QString& thirdPartyPath) const { - // Check if we are trying to cancel task - if (QThread::currentThread()->isInterruptionRequested()) - { - QStringToAZTracePrint(BuildCancelled); - return AZ::Failure(BuildCancelled); - } + QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix); - QFile logFile(GetLogFilePath()); - if (!logFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) - { - QString error = tr("Failed to open log file."); - QStringToAZTracePrint(error); - return AZ::Failure(error); - } + return AZ::Success(QStringList{ ProjectCMakeCommand, + "-B", targetBuildPath, + "-S", m_projectInfo.m_path, + QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath), + "-DLY_UNITY_BUILD=ON" } ); + } - EngineInfo engineInfo; + AZ::Outcome ProjectBuilderWorker::ConstructCmakeBuildCommandArguments() const + { + QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix); + QString launcherTargetName = m_projectInfo.m_projectName + ".GameLauncher"; - AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); - if (engineInfoResult.IsSuccess()) - { - engineInfo = engineInfoResult.GetValue(); - } - else - { - QString error = tr("Failed to get engine info."); - QStringToAZTracePrint(error); - return AZ::Failure(error); - } + return AZ::Success(QStringList{ ProjectCMakeCommand, + "--build", targetBuildPath, + "--config", "profile", + "--target", launcherTargetName, ProjectCMakeBuildTargetEditor }); + } - QTextStream logStream(&logFile); - if (QThread::currentThread()->isInterruptionRequested()) - { - logFile.close(); - QStringToAZTracePrint(BuildCancelled); - return AZ::Failure(BuildCancelled); - } - - // Show some kind of progress with very approximate estimates - UpdateProgress(++m_progressEstimate); - - QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment()); - // Append cmake path to PATH incase it is missing - QDir cmakePath(engineInfo.m_path); - cmakePath.cd("cmake/runtime/bin"); - QString pathValue = currentEnvironment.value("PATH"); - pathValue += ";" + cmakePath.path(); - currentEnvironment.insert("PATH", pathValue); - - m_configProjectProcess = new QProcess(this); - m_configProjectProcess->setProcessChannelMode(QProcess::MergedChannels); - m_configProjectProcess->setWorkingDirectory(m_projectInfo.m_path); - m_configProjectProcess->setProcessEnvironment(currentEnvironment); - - m_configProjectProcess->start( - "cmake", - QStringList - { - "-B", - QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix), - "-S", - m_projectInfo.m_path, - "-G", - "Visual Studio 16", - "-DLY_3RDPARTY_PATH=" + engineInfo.m_thirdPartyPath, - "-DLY_UNITY_BUILD=1" - }); - - if (!m_configProjectProcess->waitForStarted()) - { - QString error = tr("Configuring project failed to start."); - QStringToAZTracePrint(error); - return AZ::Failure(error); - } - bool containsGeneratingDone = false; - while (m_configProjectProcess->waitForReadyRead(MaxBuildTimeMSecs)) - { - QString configOutput = m_configProjectProcess->readAllStandardOutput(); - - if (configOutput.contains("Generating done")) - { - containsGeneratingDone = true; - } - - logStream << configOutput; - logStream.flush(); - - UpdateProgress(qMin(++m_progressEstimate, 19)); - - if (QThread::currentThread()->isInterruptionRequested()) - { - logFile.close(); - m_configProjectProcess->close(); - QStringToAZTracePrint(BuildCancelled); - return AZ::Failure(BuildCancelled); - } - } - - if (m_configProjectProcess->exitStatus() != QProcess::ExitStatus::NormalExit - || m_configProjectProcess->exitCode() != 0 - || !containsGeneratingDone) - { - QString error = tr("Configuring project failed. See log for details."); - QStringToAZTracePrint(error); - return AZ::Failure(error); - } - - UpdateProgress(++m_progressEstimate); - - m_buildProjectProcess = new QProcess(this); - m_buildProjectProcess->setProcessChannelMode(QProcess::MergedChannels); - m_buildProjectProcess->setWorkingDirectory(m_projectInfo.m_path); - m_buildProjectProcess->setProcessEnvironment(currentEnvironment); - - m_buildProjectProcess->start( - "cmake", - QStringList - { - "--build", - QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix), - "--target", - m_projectInfo.m_projectName + ".GameLauncher", - "Editor", - "--config", - "profile" - }); - - if (!m_buildProjectProcess->waitForStarted()) - { - QString error = tr("Building project failed to start."); - QStringToAZTracePrint(error); - return AZ::Failure(error); - } - - // There are a lot of steps when building so estimate around 800 more steps ((100 - 20) * 10) remaining - m_progressEstimate = 200; - while (m_buildProjectProcess->waitForReadyRead(MaxBuildTimeMSecs)) - { - logStream << m_buildProjectProcess->readAllStandardOutput(); - logStream.flush(); - - // Show 1% progress for every 10 steps completed - UpdateProgress(qMin(++m_progressEstimate / 10, 99)); - - if (QThread::currentThread()->isInterruptionRequested()) - { - // QProcess is unable to kill its child processes so we need to ask the operating system to do that for us - QProcess killBuildProcess; - killBuildProcess.setProcessChannelMode(QProcess::MergedChannels); - killBuildProcess.start( - "cmd.exe", QStringList{ "/C", "taskkill", "/pid", QString::number(m_buildProjectProcess->processId()), "/f", "/t" }); - killBuildProcess.waitForFinished(); - - logStream << "Killing Project Build."; - logStream << killBuildProcess.readAllStandardOutput(); - m_buildProjectProcess->kill(); - logFile.close(); - QStringToAZTracePrint(BuildCancelled); - return AZ::Failure(BuildCancelled); - } - } - - if (m_configProjectProcess->exitStatus() != QProcess::ExitStatus::NormalExit - || m_configProjectProcess->exitCode() != 0) - { - QString error = tr("Building project failed. See log for details."); - QStringToAZTracePrint(error); - return AZ::Failure(error); - } - - return AZ::Success(); + AZ::Outcome ProjectBuilderWorker::ConstructKillProcessCommandArguments(const QString& pidToKill) const + { + return AZ::Success(QStringList { "cmd.exe", "/C", "taskkill", "/pid", pidToKill, "/f", "/t" } ); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp b/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp index 7ca51fb9c5..d012ca8921 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp @@ -7,6 +7,8 @@ #include +#include + #include #include #include @@ -16,8 +18,44 @@ namespace O3DE::ProjectManager { namespace ProjectUtils { - AZ::Outcome FindSupportedCompilerForPlatform() + AZ::Outcome GetCommandLineProcessEnvironment() { + // Use the engine path to insert a path for cmake + auto engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); + if (!engineInfoResult.IsSuccess()) + { + return AZ::Failure(QObject::tr("Failed to get engine info")); + } + auto engineInfo = engineInfoResult.GetValue(); + + QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment()); + + // Append cmake path to PATH incase it is missing + QDir cmakePath(engineInfo.m_path); + cmakePath.cd("cmake/runtime/bin"); + QString pathValue = currentEnvironment.value("PATH"); + pathValue += ";" + cmakePath.path(); + currentEnvironment.insert("PATH", pathValue); + return AZ::Success(currentEnvironment); + } + + AZ::Outcome FindSupportedCompilerForPlatform() + { + // Validate that cmake is installed + auto cmakeProcessEnvResult = GetCommandLineProcessEnvironment(); + if (!cmakeProcessEnvResult.IsSuccess()) + { + return AZ::Failure(cmakeProcessEnvResult.GetError()); + } + auto cmakeVersionQueryResult = ExecuteCommandResult("cmake", QStringList{"--version"}, cmakeProcessEnvResult.GetValue()); + if (!cmakeVersionQueryResult.IsSuccess()) + { + return AZ::Failure(QObject::tr("CMake not found. \n\n" + "Make sure that the minimum version of CMake is installed and available from the command prompt. " + "Refer to the O3DE requirements for more information.")); + } + + // Validate that the minimal version of visual studio is installed QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); QString programFilesPath = environment.value("ProgramFiles(x86)"); QString vsWherePath = QDir(programFilesPath).filePath("Microsoft Visual Studio/Installer/vswhere.exe"); @@ -25,27 +63,31 @@ namespace O3DE::ProjectManager QFileInfo vsWhereFile(vsWherePath); if (vsWhereFile.exists() && vsWhereFile.isFile()) { - QProcess vsWhereProcess; - vsWhereProcess.setProcessChannelMode(QProcess::MergedChannels); + QStringList vsWhereBaseArguments = QStringList{"-version", + "16.9.2", + "-latest", + "-requires", + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"}; - vsWhereProcess.start( - vsWherePath, - QStringList{ - "-version", - "16.9.2", - "-latest", - "-requires", - "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", - "-property", - "isComplete" - }); + QProcess vsWhereIsCompleteProcess; + vsWhereIsCompleteProcess.setProcessChannelMode(QProcess::MergedChannels); - if (vsWhereProcess.waitForStarted() && vsWhereProcess.waitForFinished()) + vsWhereIsCompleteProcess.start(vsWherePath, vsWhereBaseArguments + QStringList{ "-property", "isComplete" }); + + if (vsWhereIsCompleteProcess.waitForStarted() && vsWhereIsCompleteProcess.waitForFinished()) { - QString vsWhereOutput(vsWhereProcess.readAllStandardOutput()); - if (vsWhereOutput.startsWith("1")) + QString vsWhereIsCompleteOutput(vsWhereIsCompleteProcess.readAllStandardOutput()); + if (vsWhereIsCompleteOutput.startsWith("1")) { - return AZ::Success(); + QProcess vsWhereCompilerVersionProcess; + vsWhereCompilerVersionProcess.setProcessChannelMode(QProcess::MergedChannels); + vsWhereCompilerVersionProcess.start(vsWherePath, vsWhereBaseArguments + QStringList{"-property", "catalog_productDisplayVersion"}); + + if (vsWhereCompilerVersionProcess.waitForStarted() && vsWhereCompilerVersionProcess.waitForFinished()) + { + QString vsWhereCompilerVersionOutput(vsWhereCompilerVersionProcess.readAllStandardOutput()); + return AZ::Success(vsWhereCompilerVersionOutput); + } } } } diff --git a/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.cpp b/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.cpp index 2fe1c6db15..c6a6b20a1d 100644 --- a/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.cpp @@ -8,8 +8,15 @@ #include #include +#include +#include #include +#include +#include +#include +#include +#include //#define MOCK_BUILD_PROJECT true @@ -67,4 +74,176 @@ namespace O3DE::ProjectManager { AZ_TracePrintf("Project Manager", error.toStdString().c_str()); } + + AZ::Outcome ProjectBuilderWorker::BuildProjectForPlatform() + { + // Check if we are trying to cancel task + if (QThread::currentThread()->isInterruptionRequested()) + { + QStringToAZTracePrint(BuildCancelled); + return AZ::Failure(BuildCancelled); + } + + QFile logFile(GetLogFilePath()); + if (!logFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + { + QString error = tr("Failed to open log file."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + + EngineInfo engineInfo; + + AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); + if (engineInfoResult.IsSuccess()) + { + engineInfo = engineInfoResult.GetValue(); + } + else + { + QString error = tr("Failed to get engine info."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + + QTextStream logStream(&logFile); + if (QThread::currentThread()->isInterruptionRequested()) + { + logFile.close(); + QStringToAZTracePrint(BuildCancelled); + return AZ::Failure(BuildCancelled); + } + + // Show some kind of progress with very approximate estimates + UpdateProgress(++m_progressEstimate); + + auto currentEnvironmentRequest = ProjectUtils::GetCommandLineProcessEnvironment(); + if (!currentEnvironmentRequest.IsSuccess()) + { + QStringToAZTracePrint(currentEnvironmentRequest.GetError()); + return AZ::Failure(currentEnvironmentRequest.GetError()); + } + QProcessEnvironment currentEnvironment = currentEnvironmentRequest.GetValue(); + + m_configProjectProcess = new QProcess(this); + m_configProjectProcess->setProcessChannelMode(QProcess::MergedChannels); + m_configProjectProcess->setWorkingDirectory(m_projectInfo.m_path); + m_configProjectProcess->setProcessEnvironment(currentEnvironment); + + auto cmakeGenerateArgumentsResult = ConstructCmakeGenerateProjectArguments(engineInfo.m_thirdPartyPath); + if (!cmakeGenerateArgumentsResult.IsSuccess()) + { + QStringToAZTracePrint(cmakeGenerateArgumentsResult.GetError()); + return AZ::Failure(cmakeGenerateArgumentsResult.GetError()); + } + auto cmakeGenerateArguments = cmakeGenerateArgumentsResult.GetValue(); + m_configProjectProcess->start(cmakeGenerateArguments.front(), cmakeGenerateArguments.mid(1)); + if (!m_configProjectProcess->waitForStarted()) + { + QString error = tr("Configuring project failed to start."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + bool containsGeneratingDone = false; + while (m_configProjectProcess->waitForReadyRead(MaxBuildTimeMSecs)) + { + QString configOutput = m_configProjectProcess->readAllStandardOutput(); + + if (configOutput.contains("Generating done")) + { + containsGeneratingDone = true; + } + + logStream << configOutput; + logStream.flush(); + + UpdateProgress(qMin(++m_progressEstimate, 19)); + + if (QThread::currentThread()->isInterruptionRequested()) + { + logFile.close(); + m_configProjectProcess->close(); + QStringToAZTracePrint(BuildCancelled); + return AZ::Failure(BuildCancelled); + } + } + + if (m_configProjectProcess->exitStatus() != QProcess::ExitStatus::NormalExit || m_configProjectProcess->exitCode() != 0 || + !containsGeneratingDone) + { + QString error = tr("Configuring project failed. See log for details."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + + UpdateProgress(++m_progressEstimate); + + m_buildProjectProcess = new QProcess(this); + m_buildProjectProcess->setProcessChannelMode(QProcess::MergedChannels); + m_buildProjectProcess->setWorkingDirectory(m_projectInfo.m_path); + m_buildProjectProcess->setProcessEnvironment(currentEnvironment); + + auto cmakeBuildArgumentsResult = ConstructCmakeBuildCommandArguments(); + if (!cmakeBuildArgumentsResult.IsSuccess()) + { + QStringToAZTracePrint(cmakeBuildArgumentsResult.GetError()); + return AZ::Failure(cmakeBuildArgumentsResult.GetError()); + } + auto cmakeBuildArguments = cmakeBuildArgumentsResult.GetValue(); + + m_buildProjectProcess->start(cmakeBuildArguments.front(), cmakeBuildArguments.mid(1)); + if (!m_buildProjectProcess->waitForStarted()) + { + QString error = tr("Building project failed to start."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + + // There are a lot of steps when building so estimate around 800 more steps ((100 - 20) * 10) remaining + m_progressEstimate = 200; + while (m_buildProjectProcess->waitForReadyRead(MaxBuildTimeMSecs)) + { + logStream << m_buildProjectProcess->readAllStandardOutput(); + logStream.flush(); + + // Show 1% progress for every 10 steps completed + UpdateProgress(qMin(++m_progressEstimate / 10, 99)); + + if (QThread::currentThread()->isInterruptionRequested()) + { + // QProcess is unable to kill its child processes so we need to ask the operating system to do that for us + auto killProcessArgumentsResult = ConstructKillProcessCommandArguments(QString::number(m_buildProjectProcess->processId())); + if (!killProcessArgumentsResult.IsSuccess()) + { + return AZ::Failure(killProcessArgumentsResult.GetError()); + } + auto killProcessArguments = killProcessArgumentsResult.GetValue(); + + + QProcess killBuildProcess; + + + killBuildProcess.setProcessChannelMode(QProcess::MergedChannels); + killBuildProcess.start(killProcessArguments.front(), killProcessArguments.mid(1)); + killBuildProcess.waitForFinished(); + + logStream << "Killing Project Build."; + logStream << killBuildProcess.readAllStandardOutput(); + m_buildProjectProcess->kill(); + logFile.close(); + QStringToAZTracePrint(BuildCancelled); + return AZ::Failure(BuildCancelled); + } + } + + if (m_buildProjectProcess->exitStatus() != QProcess::ExitStatus::NormalExit || m_buildProjectProcess->exitCode() != 0) + { + QString error = tr("Building project failed. See log for details."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + + return AZ::Success(); + } + } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.h b/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.h index f42c87f47b..94c4a6bd52 100644 --- a/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.h +++ b/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.h @@ -12,6 +12,7 @@ #include #include +#include #endif QT_FORWARD_DECLARE_CLASS(QProcess) @@ -44,6 +45,12 @@ namespace O3DE::ProjectManager AZ::Outcome BuildProjectForPlatform(); void QStringToAZTracePrint(const QString& error); + // Command line argument builders + AZ::Outcome ConstructCmakeGenerateProjectArguments(const QString& thirdPartyPath) const; + AZ::Outcome ConstructCmakeBuildCommandArguments() const; + AZ::Outcome ConstructKillProcessCommandArguments(const QString& pidToKill) const; + + QProcess* m_configProjectProcess = nullptr; QProcess* m_buildProjectProcess = nullptr; ProjectInfo m_projectInfo; diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h b/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h index 8ab5128741..264515652f 100644 --- a/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h +++ b/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h @@ -14,6 +14,7 @@ namespace O3DE::ProjectManager inline constexpr static int ProjectPreviewImageWidth = 210; inline constexpr static int ProjectPreviewImageHeight = 280; inline constexpr static int ProjectTemplateImageWidth = 92; + inline constexpr static int ProjectCommandLineTimeoutSeconds = 30; static const QString ProjectBuildDirectoryName = "build"; extern const QString ProjectBuildPathPostfix; @@ -21,4 +22,8 @@ namespace O3DE::ProjectManager static const QString ProjectBuildErrorLogName = "CMakeProjectBuildError.log"; static const QString ProjectCacheDirectoryName = "Cache"; static const QString ProjectPreviewImagePath = "preview.png"; + + static const QString ProjectCMakeCommand = "cmake"; + static const QString ProjectCMakeBuildTargetEditor = "Editor"; + } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectUtils.cpp b/Code/Tools/ProjectManager/Source/ProjectUtils.cpp index 84d731832e..81eb70391d 100644 --- a/Code/Tools/ProjectManager/Source/ProjectUtils.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectUtils.cpp @@ -22,6 +22,8 @@ #include #include +#include + namespace O3DE::ProjectManager { namespace ProjectUtils @@ -507,5 +509,33 @@ namespace O3DE::ProjectManager return ProjectManagerScreen::Invalid; } + + AZ::Outcome ExecuteCommandResult( + const QString& cmd, + const QStringList& arguments, + const QProcessEnvironment& processEnv, + int commandTimeoutSeconds /*= ProjectCommandLineTimeoutSeconds*/) + { + QProcess execProcess; + execProcess.setProcessEnvironment(processEnv); + execProcess.setProcessChannelMode(QProcess::MergedChannels); + execProcess.start(cmd, arguments); + if (!execProcess.waitForStarted()) + { + return AZ::Failure(QObject::tr("Unable to start process for command '%1'").arg(cmd)); + } + + if (!execProcess.waitForFinished(commandTimeoutSeconds * 1000 /* Milliseconds per second */)) + { + return AZ::Failure(QObject::tr("Process for command '%1' timed out at %2 seconds").arg(cmd).arg(commandTimeoutSeconds)); + } + int resultCode = execProcess.exitCode(); + if (resultCode != 0) + { + return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2").arg(cmd).arg(resultCode)); + } + QString resultOutput = execProcess.readAllStandardOutput(); + return AZ::Success(resultOutput); + } } // namespace ProjectUtils } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectUtils.h b/Code/Tools/ProjectManager/Source/ProjectUtils.h index f1050531d4..0866b57923 100644 --- a/Code/Tools/ProjectManager/Source/ProjectUtils.h +++ b/Code/Tools/ProjectManager/Source/ProjectUtils.h @@ -9,8 +9,11 @@ #include #include +#include #include +#include + #include namespace O3DE::ProjectManager @@ -28,8 +31,17 @@ namespace O3DE::ProjectManager bool ReplaceProjectFile(const QString& origFile, const QString& newFile, QWidget* parent = nullptr, bool interactive = true); bool FindSupportedCompiler(QWidget* parent = nullptr); - AZ::Outcome FindSupportedCompilerForPlatform(); + AZ::Outcome FindSupportedCompilerForPlatform(); ProjectManagerScreen GetProjectManagerScreen(const QString& screen); + + AZ::Outcome ExecuteCommandResult( + const QString& cmd, + const QStringList& arguments, + const QProcessEnvironment& processEnv, + int commandTimeoutSeconds = ProjectCommandLineTimeoutSeconds); + + AZ::Outcome GetCommandLineProcessEnvironment(); + } // namespace ProjectUtils } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 6f8ff9abce..c92ad53cd4 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -339,7 +339,7 @@ namespace O3DE::ProjectManager { for (auto engine : allEngines) { - AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); + AZ::IO::FixedMaxPath enginePath(Py_To_String(engine)); if (enginePath.Compare(m_enginePath) == 0) { return; diff --git a/Gems/AWSClientAuth/gem.json b/Gems/AWSClientAuth/gem.json index 42996e7f4f..75c07d025b 100644 --- a/Gems/AWSClientAuth/gem.json +++ b/Gems/AWSClientAuth/gem.json @@ -5,9 +5,19 @@ "origin": "Amazon Web Services, Inc.", "type": "Code", "summary": "AWS Client Auth provides client authentication and AWS authorization solution.", - "canonical_tags": ["Gem"], - "user_tags": ["AWS", "Network", "SDK"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "AWS", + "Network", + "SDK" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/aws/aws-client-auth/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/aws/aws-client-auth/", + "dependencies": [ + "AWSCore", + "HttpRequestor" + ] } diff --git a/Gems/AWSCore/gem.json b/Gems/AWSCore/gem.json index 4c7889ace4..1bb9da9192 100644 --- a/Gems/AWSCore/gem.json +++ b/Gems/AWSCore/gem.json @@ -5,9 +5,16 @@ "origin": "Amazon Web Services, Inc.", "type": "Code", "summary": "The AWS Core Gem provides basic shared AWS functionality such as AWS SDK initialization and client configuration, and is automatically added when selecting any AWS feature Gem.", - "canonical_tags": ["Gem"], - "user_tags": ["AWS", "Network", "SDK"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "AWS", + "Network", + "SDK" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/aws/aws-core/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/aws/aws-core/", + "dependencies": [] } diff --git a/Gems/AWSGameLift/gem.json b/Gems/AWSGameLift/gem.json index 7711495ee7..a0d7f62cd1 100644 --- a/Gems/AWSGameLift/gem.json +++ b/Gems/AWSGameLift/gem.json @@ -5,9 +5,18 @@ "origin": "Amazon Web Services, Inc.", "type": "Code", "summary": "The AWS GameLift Gem provides a framework to extend O3DE networking layer to work with GameLift resources via GameLift server and client SDK.", - "canonical_tags": ["Gem"], - "user_tags": ["AWS", "Framework", "Network"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "AWS", + "Framework", + "Network" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/", + "dependencies": [ + "AWSCore" + ] } diff --git a/Gems/AWSMetrics/gem.json b/Gems/AWSMetrics/gem.json index 5faeb66787..df16890012 100644 --- a/Gems/AWSMetrics/gem.json +++ b/Gems/AWSMetrics/gem.json @@ -5,9 +5,18 @@ "origin": "Amazon Web Services, Inc.", "type": "Code", "summary": "The AWS Metrics Gem provides a solution for AWS metrics submission and analytics.", - "canonical_tags": ["Gem"], - "user_tags": ["AWS", "Network", "SDK"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "AWS", + "Network", + "SDK" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/", + "dependencies": [ + "AWSCore" + ] } diff --git a/Gems/Achievements/gem.json b/Gems/Achievements/gem.json index ca2bc7f3e9..bd643f471a 100644 --- a/Gems/Achievements/gem.json +++ b/Gems/Achievements/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Achievements Gem provides a target platform agnostic interface for retrieving achievement details and unlocking achievements.", - "canonical_tags": ["Gem"], - "user_tags": ["Gameplay", "Achievements"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Gameplay", + "Achievements" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/gameplay/achievements/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/gameplay/achievements/", + "dependencies": [] } diff --git a/Gems/AssetMemoryAnalyzer/gem.json b/Gems/AssetMemoryAnalyzer/gem.json index 544e0f7948..902102fa27 100644 --- a/Gems/AssetMemoryAnalyzer/gem.json +++ b/Gems/AssetMemoryAnalyzer/gem.json @@ -5,9 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Asset Memory Analyzer Gem provides tools to profile asset memory usage in Open 3D Engine through ImGUI (Immediate Mode Graphical User Interface).", - "canonical_tags": ["Gem"], - "user_tags": ["Debug", "Utility", "Tools"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Debug", + "Utility", + "Tools" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/debug/asset-memory-analyzer/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/debug/asset-memory-analyzer/", + "dependencies": [ + "ImGui" + ] } diff --git a/Gems/AssetValidation/gem.json b/Gems/AssetValidation/gem.json index 83e37ddaf5..1e57f60dc4 100644 --- a/Gems/AssetValidation/gem.json +++ b/Gems/AssetValidation/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Asset Validation Gem provides seed-related commands to ensure assets have valid seeds for asset bundling.", - "canonical_tags": ["Gem"], - "user_tags": ["Assets", "Utility", "Scripting"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Assets", + "Utility", + "Scripting" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/asset-validation/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/asset-validation/", + "dependencies": [] } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/PixelOperation.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/PixelOperation.cpp index 3e897078fa..fa4baeefc0 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/PixelOperation.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/PixelOperation.cpp @@ -248,8 +248,8 @@ namespace ImageProcessingAtom { const uint8* data = buf; r = U8ToF32(data[0]); - g = 0.f; - b = 0.f; + g = r; + b = r; a = 1.f; } @@ -333,8 +333,8 @@ namespace ImageProcessingAtom { const uint16* data = (uint16*)(buf); r = U16ToF32(data[0]); - g = 0.f; - b = 0.f; + g = r; + b = r; a = 1.f; } @@ -418,8 +418,8 @@ namespace ImageProcessingAtom { const float* data = (float*)(buf); r = data[0]; - g = 0.f; - b = 0.f; + g = r; + b = r; a = 1.f; } @@ -485,8 +485,8 @@ namespace ImageProcessingAtom { const SHalf* data = (SHalf*)(buf); r = data[0]; - g = 0.f; - b = 0.f; + g = r; + b = r; a = 1.f; } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Albedo.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Albedo.preset index 3a5122f18e..e0d043cf20 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Albedo.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Albedo.preset @@ -39,7 +39,7 @@ "_bc", "_diffuse" ], - "PixelFormat": "ETC2", + "PixelFormat": "ASTC_6x6", "DiscardAlpha": true, "IsPowerOf2": true, "MipMapSetting": { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithCoverage.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithCoverage.preset index 692ef99b1c..fda5a9cc52 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithCoverage.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithCoverage.preset @@ -36,7 +36,7 @@ "_bc", "_diffuse" ], - "PixelFormat": "ETC2a1", + "PixelFormat": "ASTC_6x6", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithGenericAlpha.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithGenericAlpha.preset index 4ebe773f0e..8c196530e9 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithGenericAlpha.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithGenericAlpha.preset @@ -36,7 +36,7 @@ "_bc", "_diffuse" ], - "PixelFormat": "ETC2a", + "PixelFormat": "ASTC_6x6", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithOpacity.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithOpacity.preset index 6049ef5bd4..346f0f8cac 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithOpacity.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithOpacity.preset @@ -36,7 +36,7 @@ "_bc", "_diffuse" ], - "PixelFormat": "ETC2a", + "PixelFormat": "ASTC_6x6", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/AmbientOcclusion.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/AmbientOcclusion.preset index 56dec20f3e..638e31f838 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/AmbientOcclusion.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/AmbientOcclusion.preset @@ -28,7 +28,7 @@ "_amb", "_ambientocclusion" ], - "PixelFormat": "EAC_R11" + "PixelFormat": "ASTC_4x4" }, "ios": { "UUID": "{02ED0ECE-B198-49D9-85BC-CEBA6C28546C}", @@ -41,7 +41,7 @@ "_amb", "_ambientocclusion" ], - "PixelFormat": "EAC_R11" + "PixelFormat": "ASTC_4x4" }, "mac": { "UUID": "{02ED0ECE-B198-49D9-85BC-CEBA6C28546C}", diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/CloudShadows.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/CloudShadows.preset index 2280a06302..8d1105cdfc 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/CloudShadows.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/CloudShadows.preset @@ -15,14 +15,14 @@ "UUID": "{884B5F7C-44AC-4E9E-8B8A-559D098BE2C7}", "Name": "CloudShadows", "DestColor": "Linear", - "PixelFormat": "EAC_R11", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true }, "ios": { "UUID": "{884B5F7C-44AC-4E9E-8B8A-559D098BE2C7}", "Name": "CloudShadows", "DestColor": "Linear", - "PixelFormat": "EAC_R11", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true }, "mac": { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Decal_AlbedoWithOpacity.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Decal_AlbedoWithOpacity.preset index f1e43e74b1..628cd673e9 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Decal_AlbedoWithOpacity.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Decal_AlbedoWithOpacity.preset @@ -24,13 +24,13 @@ "FileMasks": [ "_decal" ], - "PixelFormat": "ETC2a", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" }, // Decal Texture Arrays need all mips available immediately for packing. - "NumberResidentMips": 255 + "NumberResidentMips": 255 }, "ios": { "UUID": "{E06B5087-2640-49B6-B9BA-D40048162B90}", diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Detail_MergedAlbedoNormalsSmoothness.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Detail_MergedAlbedoNormalsSmoothness.preset index 991692c5cc..5bfea9a376 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Detail_MergedAlbedoNormalsSmoothness.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Detail_MergedAlbedoNormalsSmoothness.preset @@ -26,7 +26,7 @@ "FileMasks": [ "_detail" ], - "PixelFormat": "ETC2a", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Displacement.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Displacement.preset index 520e4ae193..8f21ad005b 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Displacement.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Displacement.preset @@ -45,7 +45,7 @@ "_ht", "_h" ], - "PixelFormat": "EAC_R11", + "PixelFormat": "ASTC_4x4", "DiscardAlpha": true, "IsPowerOf2": true, "SizeReduceLevel": 3, @@ -70,7 +70,7 @@ "_ht", "_h" ], - "PixelFormat": "EAC_R11", + "PixelFormat": "ASTC_4x4", "DiscardAlpha": true, "IsPowerOf2": true, "MipMapSetting": { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Emissive.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Emissive.preset index ffb16482fd..8f6c846e82 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Emissive.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Emissive.preset @@ -29,7 +29,7 @@ "_em", "_emit" ], - "PixelFormat": "ETC2", + "PixelFormat": "ASTC_6x6", "DiscardAlpha": true }, "ios": { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Greyscale.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Greyscale.preset index f06682be42..c71ada1269 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Greyscale.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Greyscale.preset @@ -26,7 +26,7 @@ "FileMasks": [ "_mask" ], - "PixelFormat": "EAC_R11", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" @@ -40,7 +40,7 @@ "FileMasks": [ "_mask" ], - "PixelFormat": "EAC_R11", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/LensOptics.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/LensOptics.preset index 9f4b5bf68d..d277785151 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/LensOptics.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/LensOptics.preset @@ -12,7 +12,7 @@ "android": { "UUID": "{3000A993-0A04-4E08-A813-DFB1A47A0980}", "Name": "LensOptics", - "PixelFormat": "ETC2" + "PixelFormat": "ASTC_4x4" }, "ios": { "UUID": "{3000A993-0A04-4E08-A813-DFB1A47A0980}", diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/LightProjector.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/LightProjector.preset index ede264a78e..4de95427d6 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/LightProjector.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/LightProjector.preset @@ -18,7 +18,7 @@ "UUID": "{1DFEF41A-D97F-40FB-99D3-C142A3E5225E}", "Name": "LightProjector", "DestColor": "Linear", - "PixelFormat": "EAC_RG11", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" @@ -28,7 +28,7 @@ "UUID": "{1DFEF41A-D97F-40FB-99D3-C142A3E5225E}", "Name": "LightProjector", "DestColor": "Linear", - "PixelFormat": "EAC_RG11", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Minimap.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Minimap.preset index 9370de063d..79dda1977e 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Minimap.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Minimap.preset @@ -19,7 +19,7 @@ "UUID": "{0D2F4C31-A665-4862-9C63-9E49A58E9A37}", "Name": "Minimap", "SuppressEngineReduce": true, - "PixelFormat": "ETC2", + "PixelFormat": "ASTC_6x6", "IsPowerOf2": true, "SizeReduceLevel": 1, "MipMapSetting": { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/MuzzleFlash.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/MuzzleFlash.preset index 459cd5b1fb..b02227b454 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/MuzzleFlash.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/MuzzleFlash.preset @@ -18,7 +18,7 @@ "UUID": "{8BCC23A5-D08E-458E-B0B3-087C65FA1D31}", "Name": "MuzzleFlash", "SuppressEngineReduce": true, - "PixelFormat": "ETC2", + "PixelFormat": "ASTC_6x6", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Opacity.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Opacity.preset index bbd7fd5db9..8b66b30f28 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Opacity.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Opacity.preset @@ -44,7 +44,7 @@ "_msk", "_blend" ], - "PixelFormat": "EAC_R11", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" @@ -67,7 +67,7 @@ "_msk", "_blend" ], - "PixelFormat": "EAC_R11", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Reflectance.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Reflectance.preset index 1844e0186e..e9ea34060b 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Reflectance.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Reflectance.preset @@ -57,7 +57,7 @@ "_roughness", "_rough" ], - "PixelFormat": "ETC2", + "PixelFormat": "ASTC_6x6", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/ReflectanceWithSmoothness_Legacy.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/ReflectanceWithSmoothness_Legacy.preset index e51cc7122b..e868a44096 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/ReflectanceWithSmoothness_Legacy.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/ReflectanceWithSmoothness_Legacy.preset @@ -22,7 +22,7 @@ "FileMasks": [ "_spec" ], - "PixelFormat": "ETC2a", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Reflectance_Linear.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Reflectance_Linear.preset index 07cc39c955..e52b616f48 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Reflectance_Linear.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Reflectance_Linear.preset @@ -26,7 +26,7 @@ "_spec", "_refl" ], - "PixelFormat": "ETC2", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/SF_Image.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/SF_Image.preset index 46a32ce5d4..191425bb92 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/SF_Image.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/SF_Image.preset @@ -19,7 +19,7 @@ "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, - "PixelFormat": "ETC2", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true }, "ios": { @@ -28,7 +28,7 @@ "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, - "PixelFormat": "PVRTC4", + "PixelFormat": "ASTC_4x4", "IsPowerOf2": true }, "mac": { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/SF_Image_nonpower2.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/SF_Image_nonpower2.preset index 0ba70d2ca3..9b1a9c7c45 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/SF_Image_nonpower2.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/SF_Image_nonpower2.preset @@ -18,7 +18,7 @@ "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, - "PixelFormat": "ETC2" + "PixelFormat": "ASTC_4x4" }, "ios": { "UUID": "{C456B8AB-C360-4822-BCDD-225252D0E697}", @@ -26,7 +26,7 @@ "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, - "PixelFormat": "PVRTC4" + "PixelFormat": "ASTC_4x4" }, "mac": { "UUID": "{C456B8AB-C360-4822-BCDD-225252D0E697}", diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Terrain_Albedo.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Terrain_Albedo.preset index 84a70935f1..8b12a08465 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Terrain_Albedo.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Terrain_Albedo.preset @@ -21,7 +21,7 @@ "Name": "Terrain_Albedo", "SourceColor": "Linear", "DestColor": "Linear", - "PixelFormat": "ETC2", + "PixelFormat": "ASTC_6x6", "IsPowerOf2": true, "HighPassMip": 5, "MipMapSetting": { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Terrain_Albedo_HighPassed.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Terrain_Albedo_HighPassed.preset index 1d83737ef9..d24531858f 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Terrain_Albedo_HighPassed.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/Terrain_Albedo_HighPassed.preset @@ -20,7 +20,7 @@ "Name": "Terrain_Albedo_HighPassed", "SourceColor": "Linear", "DestColor": "Linear", - "PixelFormat": "ETC2", + "PixelFormat": "ASTC_6x6", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/UserInterface_Compressed.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/UserInterface_Compressed.preset index 6f70e8f14f..13334de700 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/UserInterface_Compressed.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/UserInterface_Compressed.preset @@ -17,7 +17,7 @@ "UUID": "{2828FBFE-BDF9-45A7-9370-F93822719CCF}", "Name": "UserInterface_Compressed", "SuppressEngineReduce": true, - "PixelFormat": "ETC2" + "PixelFormat": "ASTC_6x6" }, "ios": { "UUID": "{2828FBFE-BDF9-45A7-9370-F93822719CCF}", diff --git a/Gems/Atom/Asset/ImageProcessingAtom/gem.json b/Gems/Atom/Asset/ImageProcessingAtom/gem.json index 45d96f7168..1424841256 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/gem.json +++ b/Gems/Atom/Asset/ImageProcessingAtom/gem.json @@ -8,7 +8,11 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RPI", + "Atom_RHI", + "Atom" + ] } diff --git a/Gems/Atom/Asset/Shader/gem.json b/Gems/Atom/Asset/Shader/gem.json index eadf34acff..6d5e9f4dbe 100644 --- a/Gems/Atom/Asset/Shader/gem.json +++ b/Gems/Atom/Asset/Shader/gem.json @@ -8,7 +8,10 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RHI", + "Atom_RPI" + ] } diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp index e3749175bc..8a6b7db1ca 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp @@ -176,7 +176,10 @@ namespace AZ m_isAssetCatalogLoaded = true; - RPI::RPISystemInterface::Get()->InitializeSystemAssets(); + if (!RPI::RPISystemInterface::Get()->IsInitialized()) + { + RPI::RPISystemInterface::Get()->InitializeSystemAssets(); + } if (!RPI::RPISystemInterface::Get()->IsInitialized()) { diff --git a/Gems/Atom/Bootstrap/gem.json b/Gems/Atom/Bootstrap/gem.json index d5f287eb7f..5e98a1887d 100644 --- a/Gems/Atom/Bootstrap/gem.json +++ b/Gems/Atom/Bootstrap/gem.json @@ -8,7 +8,9 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RPI" + ] } diff --git a/Gems/Atom/Component/DebugCamera/gem.json b/Gems/Atom/Component/DebugCamera/gem.json index cb2c597b07..586cb37058 100644 --- a/Gems/Atom/Component/DebugCamera/gem.json +++ b/Gems/Atom/Component/DebugCamera/gem.json @@ -8,7 +8,9 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RPI" + ] } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli index ccaf5cf5d0..7a2d2ee428 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli @@ -40,11 +40,10 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject //! Reflection Probe (smallest probe volume that overlaps the object position) struct ReflectionProbeData { - float3 m_aabbPos; - float3 m_outerAabbMin; - float3 m_outerAabbMax; - float3 m_innerAabbMin; - float3 m_innerAabbMax; + row_major float3x4 m_modelToWorld; + row_major float3x4 m_modelToWorldInverse; // does not include extents + float3 m_outerObbHalfLengths; + float3 m_innerObbHalfLengths; float m_padding; bool m_useReflectionProbe; bool m_useParallaxCorrection; @@ -52,4 +51,32 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject ReflectionProbeData m_reflectionProbeData; TextureCube m_reflectionProbeCubeMap; + + float4x4 GetReflectionProbeWorldMatrix() + { + float4x4 modelToWorld = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorld[0] = m_reflectionProbeData.m_modelToWorld[0]; + modelToWorld[1] = m_reflectionProbeData.m_modelToWorld[1]; + modelToWorld[2] = m_reflectionProbeData.m_modelToWorld[2]; + return modelToWorld; + } + + float4x4 GetReflectionProbeWorldMatrixInverse() + { + float4x4 modelToWorldInverse = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorldInverse[0] = m_reflectionProbeData.m_modelToWorldInverse[0]; + modelToWorldInverse[1] = m_reflectionProbeData.m_modelToWorldInverse[1]; + modelToWorldInverse[2] = m_reflectionProbeData.m_modelToWorldInverse[2]; + return modelToWorldInverse; + } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LightingUtils.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LightingUtils.azsli index 503095020c..3248fc83eb 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LightingUtils.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LightingUtils.azsli @@ -50,10 +50,10 @@ float GetRoughnessMip(float roughness) return roughness * maxRoughnessMip; } -// compute parallax corrected reflection vector +// compute parallax corrected reflection vector, AABB version // we do this by finding the intersection with the volume and adjusting the reflection vector for the surface position // https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ -float3 ApplyParallaxCorrection(float3 aabbMin, float3 aabbMax, float3 aabbPos, float3 positionWS, float3 reflectDir) +float3 ApplyParallaxCorrectionAABB(float3 aabbMin, float3 aabbMax, float3 aabbPos, float3 positionWS, float3 reflectDir) { float3 rcpReflectDir = 1.0f / reflectDir; float3 intersectA = (aabbMax - positionWS) * rcpReflectDir; @@ -63,3 +63,10 @@ float3 ApplyParallaxCorrection(float3 aabbMin, float3 aabbMax, float3 aabbPos, f float3 intersectPos = reflectDir * distance + positionWS; return (intersectPos - aabbPos); } + +// compute parallax corrected reflection vector, OBB version +float3 ApplyParallaxCorrectionOBB(float4x4 obbTransformInverse, float3 obbHalfExtents, float3 positionWS, float3 reflectDir) +{ + float4 p = mul(obbTransformInverse, float4(positionWS, 1.0f)); + return ApplyParallaxCorrectionAABB(-obbHalfExtents, obbHalfExtents, float3(0.0f, 0.0f, 0.0f), p, reflectDir); +} diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli index 5a0e150dbb..d33a307dfe 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli @@ -48,10 +48,9 @@ float3 GetIblSpecular( { if (ObjectSrg::m_reflectionProbeData.m_useParallaxCorrection) { - reflectDir = ApplyParallaxCorrection( - ObjectSrg::m_reflectionProbeData.m_outerAabbMin, - ObjectSrg::m_reflectionProbeData.m_outerAabbMax, - ObjectSrg::m_reflectionProbeData.m_aabbPos, + reflectDir = ApplyParallaxCorrectionOBB( + ObjectSrg::GetReflectionProbeWorldMatrixInverse(), + ObjectSrg::m_reflectionProbeData.m_outerObbHalfLengths, position, reflectDir); } @@ -60,11 +59,10 @@ float3 GetIblSpecular( probeSpecular *= (specularF0 * brdf.x + brdf.y); // compute blend amount based on world position in the reflection probe volume - float blendAmount = ComputeLerpBetweenInnerOuterAABBs( - ObjectSrg::m_reflectionProbeData.m_innerAabbMin, - ObjectSrg::m_reflectionProbeData.m_innerAabbMax, - ObjectSrg::m_reflectionProbeData.m_outerAabbMax, - ObjectSrg::m_reflectionProbeData.m_aabbPos, + float blendAmount = ComputeLerpBetweenInnerOuterOBBs( + ObjectSrg::GetReflectionProbeWorldMatrixInverse(), + ObjectSrg::m_reflectionProbeData.m_innerObbHalfLengths, + ObjectSrg::m_reflectionProbeData.m_outerObbHalfLengths, position); outSpecular = lerp(outSpecular, probeSpecular, blendAmount); diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeBlendWeight.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeBlendWeight.azsl index 095576caaf..0fca4d03c6 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeBlendWeight.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeBlendWeight.azsl @@ -63,7 +63,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) // make sure the pixel belongs to this probe volume // this is necessary since it could have the correct stencil value but actually reside // in another volume that's in between the camera and the volume we're rendering - if (!AabbContainsPoint(ObjectSrg::m_outerAabbMin, ObjectSrg::m_outerAabbMax, positionWS)) + if (!ObbContainsPoint(ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_outerObbHalfLengths, positionWS)) { discard; } @@ -71,11 +71,15 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) // determine blend based on position with respect to the inner and outer AABBs // if it's inside the inner AABB it blends at 100%, otherwise it's the percentage of the distance between the inner/outer AABB float blendWeight = 1.0f; - if (!AabbContainsPoint(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, positionWS)) + if (!ObbContainsPoint(ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_innerObbHalfLengths, positionWS)) { // not inside the inner AABB, so it's in between the inner and outer AABBs // compute blend amount based on the distance to the outer AABB - blendWeight = ComputeLerpBetweenInnerOuterAABBs(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, ObjectSrg::m_outerAabbMax, ObjectSrg::m_aabbPos, positionWS); + blendWeight = ComputeLerpBetweenInnerOuterOBBs( + ObjectSrg::GetWorldMatrixInverse(), + ObjectSrg::m_innerObbHalfLengths, + ObjectSrg::m_outerObbHalfLengths, + positionWS); } // write the blend weight (additive) at this position for the probe volume diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderCommon.azsli b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderCommon.azsli index f172035177..ce4684b150 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderCommon.azsli +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderCommon.azsli @@ -12,12 +12,12 @@ #include // compute final probe specular using the probe cubemap and the roughness, normals, and specularF0 for the surface -bool ComputeProbeSpecular(float2 screenCoords, float3 positionWS, float3 aabbMin, float3 aabbMax, uint sampleIndex, out float3 specular) +bool ComputeProbeSpecular(float2 screenCoords, float3 positionWS, float4x4 obbTransformInverse, float3 outerObbHalfLengths, uint sampleIndex, out float3 specular) { // make sure the pixel belongs to this probe volume // this is necessary since it could have the correct stencil value but actually reside // in another volume that's in between the camera and the volume we're rendering - if (!AabbContainsPoint(aabbMin, aabbMax, positionWS)) + if (!ObbContainsPoint(obbTransformInverse, outerObbHalfLengths, positionWS)) { return false; } @@ -47,7 +47,11 @@ bool ComputeProbeSpecular(float2 screenCoords, float3 positionWS, float3 aabbMin float3 localReflectDir = reflectDir; if (ObjectSrg::m_useParallaxCorrection) { - localReflectDir = ApplyParallaxCorrection(ObjectSrg::m_outerAabbMin, ObjectSrg::m_outerAabbMax, ObjectSrg::m_aabbPos, positionWS, reflectDir); + localReflectDir = ApplyParallaxCorrectionOBB( + ObjectSrg::GetWorldMatrixInverse(), + ObjectSrg::m_outerObbHalfLengths, + positionWS, + reflectDir); } // sample reflection cubemap with the appropriate roughness mip diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl index 209605be03..a0734caf02 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl @@ -75,7 +75,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) // compute specular using the probe cubemap and the roughness, normals, and specularF0 for the surface float3 specular = float3(0.0f, 0.0f, 0.0f); - if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, sampleIndex, specular)) + if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_innerObbHalfLengths, sampleIndex, specular)) { discard; } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli index 92ecd82dee..8151ed2fd5 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli @@ -13,12 +13,9 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject { row_major float3x4 m_modelToWorld; - - float3 m_aabbPos; - float3 m_outerAabbMin; - float3 m_outerAabbMax; - float3 m_innerAabbMin; - float3 m_innerAabbMax; + row_major float3x4 m_modelToWorldInverse; // does not include extents + float3 m_outerObbHalfLengths; + float3 m_innerObbHalfLengths; bool m_useParallaxCorrection; TextureCube m_reflectionCubeMap; @@ -35,4 +32,18 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject modelToWorld[2] = ObjectSrg::m_modelToWorld[2]; return modelToWorld; } + + float4x4 GetWorldMatrixInverse() + { + float4x4 modelToWorldInverse = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorldInverse[0] = ObjectSrg::m_modelToWorldInverse[0]; + modelToWorldInverse[1] = ObjectSrg::m_modelToWorldInverse[1]; + modelToWorldInverse[2] = ObjectSrg::m_modelToWorldInverse[2]; + return modelToWorldInverse; + } } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl index be94a53588..ac97172f1f 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl @@ -77,7 +77,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) // compute specular using the probe cubemap and the roughness, normals, and specularF0 for the surface float3 specular = float3(0.0f, 0.0f, 0.0f); - if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::m_outerAabbMin, ObjectSrg::m_outerAabbMax, sampleIndex, specular)) + if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_outerObbHalfLengths, sampleIndex, specular)) { discard; } @@ -85,13 +85,17 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) // determine blend based on position with respect to the inner and outer AABBs // if it's inside the inner AABB it blends at 100%, otherwise it's the percentage of the distance between the inner/outer AABB float blendWeight = 1.0f; - if (!AabbContainsPoint(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, positionWS)) + if (!ObbContainsPoint(ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_innerObbHalfLengths, positionWS)) { // not inside the inner AABB, so it's in between the inner and outer AABBs // compute blend amount based on the distance to the outer AABB - blendWeight = ComputeLerpBetweenInnerOuterAABBs(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, ObjectSrg::m_outerAabbMax, ObjectSrg::m_aabbPos, positionWS); + blendWeight = ComputeLerpBetweenInnerOuterOBBs( + ObjectSrg::GetWorldMatrixInverse(), + ObjectSrg::m_innerObbHalfLengths, + ObjectSrg::m_outerObbHalfLengths, + positionWS); } - + // retrieve the blend weight of all probes at this location float blendWeightAllProbes = PassSrg::m_blendWeight.Load(IN.m_position.xy, sampleIndex).r; diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index fe201f9ca8..bd0d12106b 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -1111,20 +1111,17 @@ namespace AZ if (reflectionProbeFeatureProcessor && (m_descriptor.m_useForwardPassIblSpecular || m_hasForwardPassIblSpecularMaterial)) { // retrieve probe constant indices - AZ::RHI::ShaderInputConstantIndex posConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_aabbPos")); - AZ_Error("MeshDataInstance", posConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); + AZ::RHI::ShaderInputConstantIndex modelToWorldConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_modelToWorld")); + AZ_Error("MeshDataInstance", modelToWorldConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); - AZ::RHI::ShaderInputConstantIndex outerAabbMinConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_outerAabbMin")); - AZ_Error("MeshDataInstance", outerAabbMinConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); + AZ::RHI::ShaderInputConstantIndex modelToWorldInverseConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_modelToWorldInverse")); + AZ_Error("MeshDataInstance", modelToWorldInverseConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); - AZ::RHI::ShaderInputConstantIndex outerAabbMaxConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_outerAabbMax")); - AZ_Error("MeshDataInstance", outerAabbMaxConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); + AZ::RHI::ShaderInputConstantIndex outerObbHalfLengthsConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_outerObbHalfLengths")); + AZ_Error("MeshDataInstance", outerObbHalfLengthsConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); - AZ::RHI::ShaderInputConstantIndex innerAabbMinConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_innerAabbMin")); - AZ_Error("MeshDataInstance", innerAabbMinConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); - - AZ::RHI::ShaderInputConstantIndex innerAabbMaxConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_innerAabbMax")); - AZ_Error("MeshDataInstance", innerAabbMaxConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); + AZ::RHI::ShaderInputConstantIndex innerObbHalfLengthsConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_innerObbHalfLengths")); + AZ_Error("MeshDataInstance", innerObbHalfLengthsConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); AZ::RHI::ShaderInputConstantIndex useReflectionProbeConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_useReflectionProbe")); AZ_Error("MeshDataInstance", useReflectionProbeConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); @@ -1146,11 +1143,10 @@ namespace AZ if (!reflectionProbes.empty() && reflectionProbes[0]) { - m_shaderResourceGroup->SetConstant(posConstantIndex, reflectionProbes[0]->GetPosition()); - m_shaderResourceGroup->SetConstant(outerAabbMinConstantIndex, reflectionProbes[0]->GetOuterAabbWs().GetMin()); - m_shaderResourceGroup->SetConstant(outerAabbMaxConstantIndex, reflectionProbes[0]->GetOuterAabbWs().GetMax()); - m_shaderResourceGroup->SetConstant(innerAabbMinConstantIndex, reflectionProbes[0]->GetInnerAabbWs().GetMin()); - m_shaderResourceGroup->SetConstant(innerAabbMaxConstantIndex, reflectionProbes[0]->GetInnerAabbWs().GetMax()); + m_shaderResourceGroup->SetConstant(modelToWorldConstantIndex, reflectionProbes[0]->GetTransform()); + m_shaderResourceGroup->SetConstant(modelToWorldInverseConstantIndex, Matrix3x4::CreateFromTransform(reflectionProbes[0]->GetTransform()).GetInverseFull()); + m_shaderResourceGroup->SetConstant(outerObbHalfLengthsConstantIndex, reflectionProbes[0]->GetOuterObbWs().GetHalfLengths()); + m_shaderResourceGroup->SetConstant(innerObbHalfLengthsConstantIndex, reflectionProbes[0]->GetInnerObbWs().GetHalfLengths()); m_shaderResourceGroup->SetConstant(useReflectionProbeConstantIndex, true); m_shaderResourceGroup->SetConstant(useParallaxCorrectionConstantIndex, reflectionProbes[0]->GetUseParallaxCorrection()); diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp index 778f06c3be..e86d91d387 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp @@ -138,43 +138,39 @@ namespace AZ if (m_updateSrg) { // stencil Srg - // Note: the stencil pass uses a slightly reduced inner AABB to avoid seams + // Note: the stencil pass uses a slightly reduced inner OBB to avoid seams Vector3 innerExtentsReduced = m_innerExtents - Vector3(0.1f, 0.1f, 0.1f); - Matrix3x4 modelToWorldStencil = Matrix3x4::CreateFromMatrix3x3AndTranslation(Matrix3x3::CreateIdentity(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(innerExtentsReduced); + Matrix3x4 modelToWorldStencil = Matrix3x4::CreateFromQuaternionAndTranslation(m_transform.GetRotation(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(innerExtentsReduced); m_stencilSrg->SetConstant(m_reflectionRenderData->m_modelToWorldStencilConstantIndex, modelToWorldStencil); m_stencilSrg->Compile(); + Matrix3x4 modelToWorldInverse = Matrix3x4::CreateFromTransform(m_transform).GetInverseFull(); + // blend weight Srg - Matrix3x4 modelToWorldOuter = Matrix3x4::CreateFromMatrix3x3AndTranslation(Matrix3x3::CreateIdentity(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_outerExtents); + Matrix3x4 modelToWorldOuter = Matrix3x4::CreateFromQuaternionAndTranslation(m_transform.GetRotation(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_outerExtents); m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_modelToWorldRenderConstantIndex, modelToWorldOuter); - m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_aabbPosRenderConstantIndex, m_outerAabbWs.GetCenter()); - m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_outerAabbMinRenderConstantIndex, m_outerAabbWs.GetMin()); - m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_outerAabbMaxRenderConstantIndex, m_outerAabbWs.GetMax()); - m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_innerAabbMinRenderConstantIndex, m_innerAabbWs.GetMin()); - m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_innerAabbMaxRenderConstantIndex, m_innerAabbWs.GetMax()); + m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_modelToWorldInverseRenderConstantIndex, modelToWorldInverse); + m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_outerObbHalfLengthsRenderConstantIndex, m_outerObbWs.GetHalfLengths()); + m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_innerObbHalfLengthsRenderConstantIndex, m_innerObbWs.GetHalfLengths()); m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_useParallaxCorrectionRenderConstantIndex, m_useParallaxCorrection); m_blendWeightSrg->SetImage(m_reflectionRenderData->m_reflectionCubeMapRenderImageIndex, m_cubeMapImage); m_blendWeightSrg->Compile(); // render outer Srg m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_modelToWorldRenderConstantIndex, modelToWorldOuter); - m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_aabbPosRenderConstantIndex, m_outerAabbWs.GetCenter()); - m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_outerAabbMinRenderConstantIndex, m_outerAabbWs.GetMin()); - m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_outerAabbMaxRenderConstantIndex, m_outerAabbWs.GetMax()); - m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_innerAabbMinRenderConstantIndex, m_innerAabbWs.GetMin()); - m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_innerAabbMaxRenderConstantIndex, m_innerAabbWs.GetMax()); + m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_modelToWorldInverseRenderConstantIndex, modelToWorldInverse); + m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_outerObbHalfLengthsRenderConstantIndex, m_outerObbWs.GetHalfLengths()); + m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_innerObbHalfLengthsRenderConstantIndex, m_innerObbWs.GetHalfLengths()); m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_useParallaxCorrectionRenderConstantIndex, m_useParallaxCorrection); m_renderOuterSrg->SetImage(m_reflectionRenderData->m_reflectionCubeMapRenderImageIndex, m_cubeMapImage); m_renderOuterSrg->Compile(); // render inner Srg - Matrix3x4 modelToWorldInner = Matrix3x4::CreateFromMatrix3x3AndTranslation(Matrix3x3::CreateIdentity(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_innerExtents); + Matrix3x4 modelToWorldInner = Matrix3x4::CreateFromQuaternionAndTranslation(m_transform.GetRotation(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_innerExtents); m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_modelToWorldRenderConstantIndex, modelToWorldInner); - m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_aabbPosRenderConstantIndex, m_outerAabbWs.GetCenter()); - m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_outerAabbMinRenderConstantIndex, m_outerAabbWs.GetMin()); - m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_outerAabbMaxRenderConstantIndex, m_outerAabbWs.GetMax()); - m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_innerAabbMinRenderConstantIndex, m_innerAabbWs.GetMin()); - m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_innerAabbMaxRenderConstantIndex, m_innerAabbWs.GetMax()); + m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_modelToWorldInverseRenderConstantIndex, modelToWorldInverse); + m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_outerObbHalfLengthsRenderConstantIndex, m_outerObbWs.GetHalfLengths()); + m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_innerObbHalfLengthsRenderConstantIndex, m_innerObbWs.GetHalfLengths()); m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_useParallaxCorrectionRenderConstantIndex, m_useParallaxCorrection); m_renderInnerSrg->SetImage(m_reflectionRenderData->m_reflectionCubeMapRenderImageIndex, m_cubeMapImage); m_renderInnerSrg->Compile(); @@ -244,22 +240,22 @@ namespace AZ m_outerExtents *= m_transform.GetUniformScale(); m_innerExtents *= m_transform.GetUniformScale(); - m_outerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_outerExtents / 2.0f); - m_innerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_innerExtents / 2.0f); + m_outerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_outerExtents / 2.0f); + m_innerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_innerExtents / 2.0f); m_updateSrg = true; } void ReflectionProbe::SetOuterExtents(const AZ::Vector3& outerExtents) { m_outerExtents = outerExtents * m_transform.GetUniformScale(); - m_outerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_outerExtents / 2.0f); + m_outerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_outerExtents / 2.0f); m_updateSrg = true; } void ReflectionProbe::SetInnerExtents(const AZ::Vector3& innerExtents) { m_innerExtents = innerExtents * m_transform.GetUniformScale(); - m_innerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_innerExtents / 2.0f); + m_innerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_innerExtents / 2.0f); m_updateSrg = true; } @@ -410,13 +406,14 @@ namespace AZ lod.m_screenCoverageMax = 1.0f; // update cullable bounds + Aabb outerAabb = Aabb::CreateFromObb(m_outerObbWs); Vector3 center; float radius; - m_outerAabbWs.GetAsSphere(center, radius); + outerAabb.GetAsSphere(center, radius); m_cullable.m_cullData.m_boundingSphere = Sphere(center, radius); - m_cullable.m_cullData.m_boundingObb = m_outerAabbWs.GetTransformedObb(AZ::Transform::CreateIdentity()); - m_cullable.m_cullData.m_visibilityEntry.m_boundingVolume = m_outerAabbWs; + m_cullable.m_cullData.m_boundingObb = m_outerObbWs; + m_cullable.m_cullData.m_visibilityEntry.m_boundingVolume = outerAabb; m_cullable.m_cullData.m_visibilityEntry.m_userData = &m_cullable; m_cullable.m_cullData.m_visibilityEntry.m_typeFlags = AzFramework::VisibilityEntry::TYPE_RPI_Cullable; diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h index bb30ce9470..5b56b70ec8 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h @@ -55,15 +55,13 @@ namespace AZ RHI::DrawListTag m_renderOuterDrawListTag; RHI::DrawListTag m_renderInnerDrawListTag; - RHI::ShaderInputConstantIndex m_modelToWorldStencilConstantIndex; - RHI::ShaderInputConstantIndex m_modelToWorldRenderConstantIndex; - RHI::ShaderInputConstantIndex m_aabbPosRenderConstantIndex; - RHI::ShaderInputConstantIndex m_outerAabbMinRenderConstantIndex; - RHI::ShaderInputConstantIndex m_outerAabbMaxRenderConstantIndex; - RHI::ShaderInputConstantIndex m_innerAabbMinRenderConstantIndex; - RHI::ShaderInputConstantIndex m_innerAabbMaxRenderConstantIndex; - RHI::ShaderInputConstantIndex m_useParallaxCorrectionRenderConstantIndex; - RHI::ShaderInputImageIndex m_reflectionCubeMapRenderImageIndex; + RHI::ShaderInputNameIndex m_modelToWorldStencilConstantIndex = "m_modelToWorld"; + RHI::ShaderInputNameIndex m_modelToWorldRenderConstantIndex = "m_modelToWorld"; + RHI::ShaderInputNameIndex m_modelToWorldInverseRenderConstantIndex = "m_modelToWorldInverse"; + RHI::ShaderInputNameIndex m_outerObbHalfLengthsRenderConstantIndex = "m_outerObbHalfLengths"; + RHI::ShaderInputNameIndex m_innerObbHalfLengthsRenderConstantIndex = "m_innerObbHalfLengths"; + RHI::ShaderInputNameIndex m_useParallaxCorrectionRenderConstantIndex = "m_useParallaxCorrection"; + RHI::ShaderInputNameIndex m_reflectionCubeMapRenderImageIndex = "m_reflectionCubeMap"; }; // ReflectionProbe manages all aspects of a single probe, including rendering, visualization, and cubemap generation @@ -78,6 +76,7 @@ namespace AZ void Simulate(uint32_t probeIndex); const Vector3& GetPosition() const { return m_transform.GetTranslation(); } + const AZ::Transform& GetTransform() const { return m_transform; } void SetTransform(const AZ::Transform& transform); const AZ::Vector3& GetOuterExtents() const { return m_outerExtents; } @@ -86,8 +85,8 @@ namespace AZ const AZ::Vector3& GetInnerExtents() const { return m_innerExtents; } void SetInnerExtents(const AZ::Vector3& innerExtents); - const Aabb& GetOuterAabbWs() const { return m_outerAabbWs; } - const Aabb& GetInnerAabbWs() const { return m_innerAabbWs; } + const Obb& GetOuterObbWs() const { return m_outerObbWs; } + const Obb& GetInnerObbWs() const { return m_innerObbWs; } const Data::Instance& GetCubeMapImage() const { return m_cubeMapImage; } void SetCubeMapImage(const Data::Instance& cubeMapImage, const AZStd::string& relativePath); @@ -133,9 +132,9 @@ namespace AZ AZ::Vector3 m_outerExtents = AZ::Vector3(0.0f, 0.0f, 0.0f); AZ::Vector3 m_innerExtents = AZ::Vector3(0.0f, 0.0f, 0.0f); - // probe volume AABBs (world space), built from position and extents - Aabb m_outerAabbWs; - Aabb m_innerAabbWs; + // probe volume OBBs (world space), built from position and extents + Obb m_outerObbWs; + Obb m_innerObbWs; // cubemap Data::Instance m_cubeMapImage; diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp index 91c72013dd..9fecbc19fc 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp @@ -77,61 +77,6 @@ namespace AZ m_reflectionRenderData.m_renderInnerSrgLayout, m_reflectionRenderData.m_renderInnerDrawListTag); - // create ShaderResourceGroups here so we can get the layout and cache the indices - // Note: the SRGs are not needed beyond this method since each probe creates its own SRGs, we are just interested in the indices - - // cache probe stencil shader indices - Data::Instance stencilSrg = RPI::ShaderResourceGroup::Create( - m_reflectionRenderData.m_stencilShader->GetAsset(), - m_reflectionRenderData.m_stencilShader->GetSupervariantIndex(), - m_reflectionRenderData.m_stencilSrgLayout->GetName()); - AZ_Error("ReflectionProbeFeatureProcessor", stencilSrg.get(), "Failed to create stencil back face shader resource group"); - - const RHI::ShaderResourceGroupLayout* stencilSrgLayout = stencilSrg->GetLayout(); - Name modelToWorldConstantName = Name("m_modelToWorld"); - m_reflectionRenderData.m_modelToWorldStencilConstantIndex = stencilSrgLayout->FindShaderInputConstantIndex(modelToWorldConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_modelToWorldStencilConstantIndex.IsValid(), "Failed to find stencil shader input constant [%s]", modelToWorldConstantName.GetCStr()); - - // cache probe render shader indices - // Note: the outer and inner render shaders use the same Srg - Data::Instance renderReflectionSrg = RPI::ShaderResourceGroup::Create( - m_reflectionRenderData.m_renderOuterShader->GetAsset(), - m_reflectionRenderData.m_renderOuterShader->GetSupervariantIndex(), - m_reflectionRenderData.m_renderOuterSrgLayout->GetName()); - AZ_Error("ReflectionProbeFeatureProcessor", renderReflectionSrg.get(), "Failed to create render reflection shader resource group"); - - const RHI::ShaderResourceGroupLayout* renderReflectionSrgLayout = renderReflectionSrg->GetLayout(); - m_reflectionRenderData.m_modelToWorldRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(modelToWorldConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_modelToWorldRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", modelToWorldConstantName.GetCStr()); - - Name aabbPosConstantName = Name("m_aabbPos"); - m_reflectionRenderData.m_aabbPosRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(aabbPosConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_aabbPosRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", aabbPosConstantName.GetCStr()); - - Name outerAabbMinConstantName = Name("m_outerAabbMin"); - m_reflectionRenderData.m_outerAabbMinRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(outerAabbMinConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_outerAabbMinRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", outerAabbMinConstantName.GetCStr()); - - Name outerAabbMaxConstantName = Name("m_outerAabbMax"); - m_reflectionRenderData.m_outerAabbMaxRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(outerAabbMaxConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_outerAabbMaxRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", outerAabbMaxConstantName.GetCStr()); - - Name innerAabbMinConstantName = Name("m_innerAabbMin"); - m_reflectionRenderData.m_innerAabbMinRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(innerAabbMinConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_innerAabbMinRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", innerAabbMinConstantName.GetCStr()); - - Name innerAabbMaxConstantName = Name("m_innerAabbMax"); - m_reflectionRenderData.m_innerAabbMaxRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(innerAabbMaxConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_innerAabbMaxRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", innerAabbMaxConstantName.GetCStr()); - - Name useParallaxCorrectionConstantName = Name("m_useParallaxCorrection"); - m_reflectionRenderData.m_useParallaxCorrectionRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(useParallaxCorrectionConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_useParallaxCorrectionRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", useParallaxCorrectionConstantName.GetCStr()); - - Name reflectionCubeMapImageName = Name("m_reflectionCubeMap"); - m_reflectionRenderData.m_reflectionCubeMapRenderImageIndex = renderReflectionSrgLayout->FindShaderInputImageIndex(reflectionCubeMapImageName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_reflectionCubeMapRenderImageIndex.IsValid(), "Failed to find render shader input image [%s]", reflectionCubeMapImageName.GetCStr()); - EnableSceneNotification(); } @@ -197,10 +142,11 @@ namespace AZ // sort the probes by descending inner volume size, so the smallest volumes are rendered last auto sortFn = [](AZStd::shared_ptr const& probe1, AZStd::shared_ptr const& probe2) -> bool { - const Aabb& aabb1 = probe1->GetInnerAabbWs(); - const Aabb& aabb2 = probe2->GetInnerAabbWs(); - float size1 = aabb1.GetXExtent() * aabb1.GetZExtent() * aabb1.GetYExtent(); - float size2 = aabb2.GetXExtent() * aabb2.GetZExtent() * aabb2.GetYExtent(); + const Obb& obb1 = probe1->GetInnerObbWs(); + const Obb& obb2 = probe2->GetInnerObbWs(); + float size1 = obb1.GetHalfLengthX() * obb1.GetHalfLengthZ() * obb1.GetHalfLengthY(); + float size2 = obb2.GetHalfLengthX() * obb2.GetHalfLengthZ() * obb2.GetHalfLengthY(); + return (size1 > size2); }; @@ -345,7 +291,7 @@ namespace AZ // simple AABB check to find the reflection probes that contain the position for (auto& reflectionProbe : m_reflectionProbes) { - if (reflectionProbe->GetOuterAabbWs().Contains(position) + if (reflectionProbe->GetOuterObbWs().Contains(position) && reflectionProbe->GetCubeMapImage() && reflectionProbe->GetCubeMapImage()->IsInitialized()) { diff --git a/Gems/Atom/Feature/Common/gem.json b/Gems/Atom/Feature/Common/gem.json index cbaa56e0d7..36a61fbbbb 100644 --- a/Gems/Atom/Feature/Common/gem.json +++ b/Gems/Atom/Feature/Common/gem.json @@ -8,7 +8,12 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RPI", + "Atom", + "ImGui", + "Atom_RHI" + ] } diff --git a/Gems/Atom/RHI/DX12/gem.json b/Gems/Atom/RHI/DX12/gem.json index df0ae12cc4..eb876a1b9f 100644 --- a/Gems/Atom/RHI/DX12/gem.json +++ b/Gems/Atom/RHI/DX12/gem.json @@ -8,7 +8,9 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RHI" + ] } diff --git a/Gems/Atom/RHI/Metal/gem.json b/Gems/Atom/RHI/Metal/gem.json index 497e0149b6..8da6bfabec 100644 --- a/Gems/Atom/RHI/Metal/gem.json +++ b/Gems/Atom/RHI/Metal/gem.json @@ -8,7 +8,9 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RHI" + ] } diff --git a/Gems/Atom/RHI/Null/gem.json b/Gems/Atom/RHI/Null/gem.json index aa2e08501a..0870efaae7 100644 --- a/Gems/Atom/RHI/Null/gem.json +++ b/Gems/Atom/RHI/Null/gem.json @@ -8,7 +8,9 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RHI" + ] } diff --git a/Gems/Atom/RHI/Vulkan/gem.json b/Gems/Atom/RHI/Vulkan/gem.json index 2b43de4ab1..81e8dd22ea 100644 --- a/Gems/Atom/RHI/Vulkan/gem.json +++ b/Gems/Atom/RHI/Vulkan/gem.json @@ -8,7 +8,9 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RHI" + ] } diff --git a/Gems/Atom/RHI/gem.json b/Gems/Atom/RHI/gem.json index 015ee64994..5f916e5224 100644 --- a/Gems/Atom/RHI/gem.json +++ b/Gems/Atom/RHI/gem.json @@ -8,7 +8,13 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RHI_DX12", + "Atom_RHI_Metal", + "Atom_RHI_Vulkan", + "Atom_RHI_Null", + "Atom_Feature_Common" + ] } diff --git a/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli b/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli index a94e01e11b..b004623c4e 100644 --- a/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli +++ b/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli @@ -116,6 +116,22 @@ float ComputeLerpBetweenInnerOuterAABBs(float3 innerAabbMin, float3 innerAabbMax return totalDistance > 0.0f ? saturate(shortestDistance / totalDistance) : 1.0f; } +// returns true if the Obb contains the specified point +bool ObbContainsPoint(float4x4 obbTransformInverse, float3 obbHalfExtents, float3 testPoint) +{ + // get the position in Obb local space, force to positive quadrant with abs() + float4 p = abs(mul(obbTransformInverse, float4(testPoint, 1.0f))); + return AabbContainsPoint(-obbHalfExtents, obbHalfExtents, p); +} + +// computes [0..1] percentage of a point that's in between the inner and outer OBBs +float ComputeLerpBetweenInnerOuterOBBs(float3x4 obbTransformInverse, float3 innerObbHalfExtents, float3 outerObbHalfExtents, float3 position) +{ + // get the position in Obb local space, force to positive quadrant with abs() + float3 p = abs(mul(obbTransformInverse, float4(position, 1.0f))); + return ComputeLerpBetweenInnerOuterAABBs(-innerObbHalfExtents, innerObbHalfExtents, outerObbHalfExtents, float3(0.0f, 0.0f, 0.0f), p); +} + // ---------- Normal Encoding ----------- // Encode/Decode functions for Signed Octahedron normals diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp index 5ddeb08ef8..ef0678046c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp @@ -30,6 +30,7 @@ #include #include #include +#include namespace AZ { @@ -46,7 +47,7 @@ namespace AZ { AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor; materialBuilderDescriptor.m_name = JobKey; - materialBuilderDescriptor.m_version = 107; // ATOM-14918 + materialBuilderDescriptor.m_version = 108; // Set materialtype dependency to OrderOnce materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.material", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.materialtype", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); materialBuilderDescriptor.m_busId = azrtti_typeid(); @@ -66,21 +67,19 @@ namespace AZ //! Adds all relevant dependencies for a referenced source file, considering that the path might be relative to the original file location or a full asset path. //! This will usually include multiple source dependencies and a single job dependency, but will include only source dependencies if the file is not found. //! Note the AssetBuilderSDK::JobDependency::m_platformIdentifier will not be set by this function. The calling code must set this value before passing back - //! to the AssetBuilderSDK::CreateJobsResponse. - void AddPossibleDependencies( - AZStd::string_view currentFilePath, AZStd::string_view referencedParentPath, - AZStd::vector& sourceFileDependencies, - const char* jobKey, AZStd::vector& jobDependencies) + //! to the AssetBuilderSDK::CreateJobsResponse. If isOrderedOnceForMaterialTypes is true and the dependency is a materialtype file, the job dependency type + //! will be set to JobDependencyType::OrderOnce. + void AddPossibleDependencies(AZStd::string_view currentFilePath, + AZStd::string_view referencedParentPath, + const char* jobKey, + AZStd::vector& jobDependencies, + bool isOrderedOnceForMaterialTypes = false) { bool dependencyFileFound = false; AZStd::vector possibleDependencies = RPI::AssetUtils::GetPossibleDepenencyPaths(currentFilePath, referencedParentPath); for (auto& file : possibleDependencies) { - AssetBuilderSDK::SourceFileDependency sourceFileDependency; - sourceFileDependency.m_sourceFileDependencyPath = file; - sourceFileDependencies.push_back(sourceFileDependency); - // The first path found is the highest priority, and will have a job dependency, as this is the one // the builder will actually use if (!dependencyFileFound) @@ -93,8 +92,11 @@ namespace AZ { AssetBuilderSDK::JobDependency jobDependency; jobDependency.m_jobKey = jobKey; - jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; jobDependency.m_sourceFile.m_sourceFileDependencyPath = file; + + const bool isMaterialTypeFile = AzFramework::StringFunc::Path::IsExtension(file.c_str(), MaterialTypeSourceData::Extension); + jobDependency.m_type = (isMaterialTypeFile && isOrderedOnceForMaterialTypes) ? AssetBuilderSDK::JobDependencyType::OrderOnce : AssetBuilderSDK::JobDependencyType::Order; + jobDependencies.push_back(jobDependency); } } @@ -173,8 +175,9 @@ namespace AZ for (auto& shader : materialTypeSourceData.GetValue().m_shaderCollection) { - AddPossibleDependencies(request.m_sourceFile, shader.m_shaderFilePath, - response.m_sourceFileDependencyList, "Shader Asset", + AddPossibleDependencies(request.m_sourceFile, + shader.m_shaderFilePath, + "Shader Asset", outputJobDescriptor.m_jobDependencyList); } @@ -184,9 +187,10 @@ namespace AZ for (const MaterialFunctorSourceData::AssetDependency& dependency : dependencies) { - AddPossibleDependencies(request.m_sourceFile, dependency.m_sourceFilePath, - response.m_sourceFileDependencyList, - dependency.m_jobKey.c_str(), outputJobDescriptor.m_jobDependencyList); + AddPossibleDependencies(request.m_sourceFile, + dependency.m_sourceFilePath, + dependency.m_jobKey.c_str(), + outputJobDescriptor.m_jobDependencyList); } } } @@ -219,11 +223,24 @@ namespace AZ parentMaterialPath = materialTypePath; } + // If includeMaterialPropertyNames is false, then a job dependency is needed so the material builder can validate MaterialAsset properties + // against the MaterialTypeAsset at asset build time. + // If includeMaterialPropertyNames is true, the material properties will be validated at runtime when the material is loaded, so the job dependency + // is needed only for first-time processing to set up the initial MaterialAsset. This speeds up AP processing time when a materialtype file + // is edited (e.g. 10s when editing StandardPBR.materialtype on AtomTest project from 45s). + bool includeMaterialPropertyNames = true; + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + settingsRegistry->Get(includeMaterialPropertyNames, "/O3DE/Atom/RPI/MaterialBuilder/IncludeMaterialPropertyNames"); + } + // Register dependency on the parent material source file so we can load it and use it's data to build this variant material. // Note, we don't need a direct dependency on the material type because the parent material will depend on it. - AddPossibleDependencies(request.m_sourceFile, parentMaterialPath, - response.m_sourceFileDependencyList, - JobKey, outputJobDescriptor.m_jobDependencyList); + AddPossibleDependencies(request.m_sourceFile, + parentMaterialPath, + JobKey, + outputJobDescriptor.m_jobDependencyList, + includeMaterialPropertyNames); } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp index cf7e53c596..c83761cf8e 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp @@ -44,7 +44,7 @@ namespace AZ if (auto* serialize = azrtti_cast(context)) { serialize->Class() - ->Version(4) + ->Version(5) // Set materialtype dependency to OrderOnce ->Attribute(Edit::Attributes::SystemComponentTags, AZStd::vector({ AssetBuilderSDK::ComponentTags::AssetBuilder })); } } @@ -78,10 +78,7 @@ namespace AZ AZStd::string materialTypePath; RPI::MaterialConverterBus::BroadcastResult(materialTypePath, &RPI::MaterialConverterBus::Events::GetMaterialTypePath); - bool includeMaterialPropertyNames = true; - RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames); - // TODO: Use includeMaterialPropertyNames to break materialtype dependency on fbx files. Materialasset's dependency on materialtypeasset will need to be decoupled first - if (conversionEnabled && !materialTypePath.empty() /*&& !includeMaterialPropertyNames*/) + if (conversionEnabled && !materialTypePath.empty()) { AssetBuilderSDK::SourceFileDependency materialTypeSource; materialTypeSource.m_sourceFileDependencyPath = materialTypePath; @@ -90,7 +87,15 @@ namespace AZ jobDependency.m_jobKey = "Atom Material Builder"; jobDependency.m_sourceFile = materialTypeSource; jobDependency.m_platformIdentifier = platformIdentifier; - jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; + + // If includeMaterialPropertyNames is false, then a job dependency is needed so the material builder can validate + // MaterialAsset properties against the MaterialTypeAsset at asset build time. If includeMaterialPropertyNames is true, the + // material properties will be validated at runtime when the material is loaded, so the job dependency is needed only for + // first-time processing to set up the initial MaterialAsset. This speeds up AP processing time when a materialtype file is + // edited (e.g. 10s when editing StandardPBR.materialtype on AtomTest project from 45s). + bool includeMaterialPropertyNames = true; + RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames); + jobDependency.m_type = includeMaterialPropertyNames ? AssetBuilderSDK::JobDependencyType::OrderOnce : AssetBuilderSDK::JobDependencyType::Order; jobDependencyList.push_back(jobDependency); } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Buffer/BufferAssetCreator.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Buffer/BufferAssetCreator.cpp index bb80a1b8d2..feeeff36cf 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Buffer/BufferAssetCreator.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Buffer/BufferAssetCreator.cpp @@ -57,7 +57,7 @@ namespace AZ // Only allocate buffer if initial data is not empty if (initialData != nullptr && initialDataSize > 0) { - bufferAsset->m_buffer.resize(descriptor.m_byteCount); + bufferAsset->m_buffer.resize_no_construct(descriptor.m_byteCount); memcpy(bufferAsset->m_buffer.data(), initialData, initialDataSize); } diff --git a/Gems/Atom/RPI/gem.json b/Gems/Atom/RPI/gem.json index f30a1c3201..b5a6fd5a1a 100644 --- a/Gems/Atom/RPI/gem.json +++ b/Gems/Atom/RPI/gem.json @@ -8,7 +8,9 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RHI" + ] } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h index 42fe9a01c9..4956fbc1dc 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h @@ -116,11 +116,18 @@ namespace AtomToolsFramework // ModularViewportCameraControllerRequestBus overrides ... void InterpolateToTransform(const AZ::Transform& worldFromLocal, float lookAtDistance) override; AZStd::optional LookAtAfterInterpolation() const override; + AZ::Transform GetReferenceFrame() const override; + void SetReferenceFrame(const AZ::Transform& worldFromLocal) override; + void ClearReferenceFrame() override; private: // AzFramework::ViewportDebugDisplayEventBus overrides ... void DisplayViewport(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) override; + //! Update the reference frame after a change has been made to the camera + //! view without updating the internal camera via user input. + void RefreshReferenceFrame(); + //! The current mode the camera controller is in. enum class CameraMode { @@ -139,6 +146,8 @@ namespace AtomToolsFramework AzFramework::Camera m_camera; //!< The current camera state (pitch/yaw/position/look-distance). AzFramework::Camera m_targetCamera; //!< The target (next) camera state that m_camera is catching up to. + AzFramework::Camera m_previousCamera; //!< The state of the camera from the previous frame. + AZStd::optional m_storedCamera; //!< A potentially stored camera for when a custom reference frame is set. AzFramework::CameraSystem m_cameraSystem; //!< The camera system responsible for managing all CameraInputs. AzFramework::CameraProps m_cameraProps; //!< Camera properties to control rotate and translate smoothness. CameraControllerPriorityFn m_priorityFn; //!< Controls at what priority the camera controller should respond to events. @@ -147,6 +156,7 @@ namespace AtomToolsFramework CameraMode m_cameraMode = CameraMode::Control; //!< The current mode the camera is operating in. AZStd::optional m_lookAtAfterInterpolation; //!< The look at point after an interpolation has finished. //!< Will be cleared when the view changes (camera looks away). + AZ::Transform m_referenceFrameOverride = AZ::Transform::CreateIdentity(); //!< //! Flag to prevent circular updates of the camera transform (while the viewport transform is being updated internally). bool m_updatingTransformInternally = false; //! Listen for camera view changes outside of the camera controller. @@ -154,4 +164,17 @@ namespace AtomToolsFramework //! The current instance of the modular camera viewport context. AZStd::unique_ptr m_modularCameraViewportContext; }; + + //! Placeholder implementation for ModularCameraViewportContext (useful for verifying the interface). + class PlaceholderModularCameraViewportContextImpl : public AtomToolsFramework::ModularCameraViewportContext + { + public: + AZ::Transform GetCameraTransform() const override; + void SetCameraTransform(const AZ::Transform& transform) override; + void ConnectViewMatrixChangedHandler(AZ::RPI::ViewportContext::MatrixChangedEvent::Handler& handler) override; + + private: + AZ::Transform m_cameraTransform = AZ::Transform::CreateIdentity(); + AZ::RPI::ViewportContext::MatrixChangedEvent m_viewMatrixChangedEvent; + }; } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h index ae4dc5dd25..388d24164a 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h @@ -35,6 +35,16 @@ namespace AtomToolsFramework //! Look at point after an interpolation has finished and no translation has occurred. virtual AZStd::optional LookAtAfterInterpolation() const = 0; + //! Return the current reference frame. + //! @note If a reference frame has not been set or a frame has been cleared, this is just the identity. + virtual AZ::Transform GetReferenceFrame() const = 0; + + //! Set a new reference frame other than the identity for the camera controller. + virtual void SetReferenceFrame(const AZ::Transform& worldFromLocal) = 0; + + //! Clear the current reference frame to restore the identity. + virtual void ClearReferenceFrame() = 0; + protected: ~ModularViewportCameraControllerRequests() = default; }; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp index 36e45bb7ca..13b9a2ba27 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp @@ -87,6 +87,7 @@ namespace AtomToolsFramework AtomToolsApplication ::~AtomToolsApplication() { + m_styleManager.reset(); AtomToolsMainWindowNotificationBus::Handler::BusDisconnect(); AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect(); AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect(); @@ -174,12 +175,14 @@ namespace AtomToolsFramework AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@assets@/assetcatalog.xml"); - AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets(); + if (!AZ::RPI::RPISystemInterface::Get()->IsInitialized()) + { + AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets(); + } LoadSettings(); AtomToolsMainWindowNotificationBus::Handler::BusConnect(); - AtomToolsMainWindowFactoryRequestBus::Broadcast(&AtomToolsMainWindowFactoryRequestBus::Handler::CreateMainWindow); auto editorPythonEventsInterface = AZ::Interface::Get(); @@ -206,6 +209,7 @@ namespace AtomToolsFramework { // before modules are unloaded, destroy UI to free up any assets it cached AtomToolsMainWindowFactoryRequestBus::Broadcast(&AtomToolsMainWindowFactoryRequestBus::Handler::DestroyMainWindow); + m_styleManager.reset(); AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect(); AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect(); @@ -461,6 +465,7 @@ namespace AtomToolsFramework void AtomToolsApplication::Stop() { AtomToolsMainWindowFactoryRequestBus::Broadcast(&AtomToolsMainWindowFactoryRequestBus::Handler::DestroyMainWindow); + m_styleManager.reset(); UnloadSettings(); Base::Stop(); @@ -468,7 +473,7 @@ namespace AtomToolsFramework void AtomToolsApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const { - appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Game; + appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Tool; } void AtomToolsApplication::OnTraceMessage([[maybe_unused]] AZStd::string_view message) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index 4419e0c49f..bcc9a08f77 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -30,6 +30,18 @@ namespace AtomToolsFramework ""); AZ_CVAR(float, ed_cameraSystemOrbitPointSize, 0.1f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); + AZ::Transform TransformFromMatrix4x4(const AZ::Matrix4x4& matrix) + { + const auto rotation = AZ::Matrix3x3::CreateFromMatrix4x4(matrix); + const auto translation = matrix.GetTranslation(); + return AZ::Transform::CreateFromMatrix3x3AndTranslation(rotation, translation); + } + + AZ::Matrix4x4 Matrix4x4FromTransform(const AZ::Transform& transform) + { + return AZ::Matrix4x4::CreateFromQuaternionAndTranslation(transform.GetRotation(), transform.GetTranslation()); + } + // debug void DrawPreviewAxis(AzFramework::DebugDisplayRequests& display, const AZ::Transform& transform, const float axisLength) { @@ -167,11 +179,19 @@ namespace AtomToolsFramework controller->SetupCameraControllerPriority(m_priorityFn); controller->SetupCameraControllerViewportContext(m_modularCameraViewportContext); - auto handleCameraChange = [this](const AZ::Matrix4x4&) + auto handleCameraChange = [this]([[maybe_unused]] const AZ::Matrix4x4& cameraView) { // ignore these updates if the camera is being updated internally if (!m_updatingTransformInternally) { + if (m_storedCamera.has_value()) + { + // if an external change occurs ensure we update the stored reference frame if one is set + RefreshReferenceFrame(); + return; + } + + m_previousCamera = m_targetCamera; UpdateCameraFromTransform(m_targetCamera, m_modularCameraViewportContext->GetCameraTransform()); m_camera = m_targetCamera; } @@ -231,7 +251,7 @@ namespace AtomToolsFramework } } - m_modularCameraViewportContext->SetCameraTransform(m_camera.Transform()); + m_modularCameraViewportContext->SetCameraTransform(m_referenceFrameOverride * m_camera.Transform()); } else if (m_cameraMode == CameraMode::Animation) { @@ -240,6 +260,8 @@ namespace AtomToolsFramework return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); }; + m_cameraAnimation.m_time = AZ::GetClamp(m_cameraAnimation.m_time + event.m_deltaTime.count(), 0.0f, 1.0f); + const auto& [transformStart, transformEnd, animationTime] = m_cameraAnimation; const float transitionTime = smootherStepFn(animationTime); @@ -253,14 +275,13 @@ namespace AtomToolsFramework m_camera.m_lookAt = current.GetTranslation(); m_targetCamera = m_camera; + m_modularCameraViewportContext->SetCameraTransform(current); + if (animationTime >= 1.0f) { m_cameraMode = CameraMode::Control; + RefreshReferenceFrame(); } - - m_cameraAnimation.m_time = AZ::GetClamp(animationTime + event.m_deltaTime.count(), 0.0f, 1.0f); - - m_modularCameraViewportContext->SetCameraTransform(current); } m_updatingTransformInternally = false; @@ -280,7 +301,7 @@ namespace AtomToolsFramework void ModularViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal, const float lookAtDistance) { m_cameraMode = CameraMode::Animation; - m_cameraAnimation = CameraAnimation{ m_camera.Transform(), worldFromLocal, 0.0f }; + m_cameraAnimation = CameraAnimation{ m_referenceFrameOverride * m_camera.Transform(), worldFromLocal, 0.0f }; m_lookAtAfterInterpolation = worldFromLocal.GetTranslation() + worldFromLocal.GetBasisY() * lookAtDistance; } @@ -288,4 +309,59 @@ namespace AtomToolsFramework { return m_lookAtAfterInterpolation; } + + AZ::Transform ModularViewportCameraControllerInstance::GetReferenceFrame() const + { + return m_referenceFrameOverride; + } + + void ModularViewportCameraControllerInstance::SetReferenceFrame(const AZ::Transform& worldFromLocal) + { + if (!m_storedCamera.has_value()) + { + m_storedCamera = m_previousCamera; + } + + m_referenceFrameOverride = worldFromLocal; + m_targetCamera.m_pitch = 0.0f; + m_targetCamera.m_yaw = 0.0f; + m_targetCamera.m_lookAt = AZ::Vector3::CreateZero(); + m_targetCamera.m_lookDist = 0.0f; + m_camera = m_targetCamera; + } + + void ModularViewportCameraControllerInstance::ClearReferenceFrame() + { + m_referenceFrameOverride = AZ::Transform::CreateIdentity(); + + if (m_storedCamera.has_value()) + { + m_targetCamera = m_storedCamera.value(); + m_camera = m_targetCamera; + } + + m_storedCamera.reset(); + } + + void ModularViewportCameraControllerInstance::RefreshReferenceFrame() + { + m_referenceFrameOverride = m_modularCameraViewportContext->GetCameraTransform() * m_camera.Transform().GetInverse(); + } + + AZ::Transform PlaceholderModularCameraViewportContextImpl::GetCameraTransform() const + { + return m_cameraTransform; + } + + void PlaceholderModularCameraViewportContextImpl::SetCameraTransform(const AZ::Transform& transform) + { + m_cameraTransform = transform; + m_viewMatrixChangedEvent.Signal(AzFramework::CameraViewFromCameraTransform(Matrix4x4FromTransform(transform))); + } + + void PlaceholderModularCameraViewportContextImpl::ConnectViewMatrixChangedHandler( + AZ::RPI::ViewportContext::MatrixChangedEvent::Handler& handler) + { + handler.Connect(m_viewMatrixChangedEvent); + } } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/gem.json b/Gems/Atom/Tools/AtomToolsFramework/gem.json index 5cea71c5cc..2b0380bdae 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/gem.json +++ b/Gems/Atom/Tools/AtomToolsFramework/gem.json @@ -8,7 +8,11 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RPI", + "Atom_RHI", + "Atom_Bootstrap" + ] } diff --git a/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt b/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt index 6230217ac2..d585e81162 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt +++ b/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt @@ -93,12 +93,14 @@ ly_add_target( AUTOMOC FILES_CMAKE materialeditor_files.cmake - Source/Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + ${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + PLATFORM_INCLUDE_FILES + ${pal_source_dir}/tool_dependencies_${PAL_PLATFORM_NAME_LOWERCASE}.cmake INCLUDE_DIRECTORIES PRIVATE . Source - Source/Platform/${PAL_PLATFORM_NAME} + ${pal_source_dir} PUBLIC Include BUILD_DEPENDENCIES @@ -108,8 +110,14 @@ ly_add_target( Gem::MaterialEditor.Window Gem::MaterialEditor.Viewport Gem::MaterialEditor.Document + RUNTIME_DEPENDENCIES + Gem::AtomToolsFramework.Editor + Gem::EditorPythonBindings.Editor + Gem::ImageProcessingAtom.Editor ) +ly_set_gem_variant_to_load(TARGETS MaterialEditor VARIANTS Tools) + # Add a 'builders' alias to allow the MaterialEditor root gem path to be added to the generated # cmake_dependencies..assetprocessor.setreg to allow the asset scan folder for it to be added ly_create_alias(NAME MaterialEditor.Builders NAMESPACE Gem) @@ -118,26 +126,6 @@ ly_create_alias(NAME MaterialEditor.Builders NAMESPACE Gem) # Editor opens up the MaterialEditor ly_add_dependencies(Editor Gem::MaterialEditor) -ly_add_target_files( - TARGETS - MaterialEditor - FILES - ${CMAKE_CURRENT_LIST_DIR}/../MaterialEditor.xml - OUTPUT_SUBDIRECTORY - Gems/Atom/Tools/MaterialEditor -) - -ly_add_target_dependencies( - TARGETS - MaterialEditor - DEPENDENCIES_FILES - tool_dependencies.cmake - Source/Platform/${PAL_PLATFORM_NAME}/tool_dependencies_${PAL_PLATFORM_NAME_LOWERCASE}.cmake - # The Material Editor needs the LyShine "Tools" gem variant for the custom LyShine pass - DEPENDENT_TARGETS - Gem::LyShine.Tools -) - # Inject the project path into the MaterialEditor VS debugger command arguments if the build system being invoked # in a project centric view if(NOT PROJECT_NAME STREQUAL "O3DE") diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Linux/MaterialEditor_Linux.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Linux/MaterialEditor_Linux.cpp deleted file mode 100644 index 14b5fee1fe..0000000000 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Linux/MaterialEditor_Linux.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 -#include -#include - -namespace Platform -{ - void LoadPluginDependencies() - { - AZ_Warning("Material Editor", false, "LoadPluginDependencies() function is not implemented"); - } - - void ProcessInput(void* message) - { - AZ_Warning("Material Editor", false, "ProcessInput() function is not implemented"); - } - - AzFramework::NativeWindowHandle GetWindowHandle(WId winId) - { - AZ_Warning("Material Editor", false, "GetWindowHandle() function is not implemented"); - AZ_UNUSED(winId); - return nullptr; - } - - AzFramework::WindowSize GetClientAreaSize(AzFramework::NativeWindowHandle window) - { - AZ_Warning("Material Editor", false, "GetClientAreaSize() function is not implemented"); - AZ_UNUSED(window); - return AzFramework::WindowSize{1,1}; - } -} diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Linux/platform_linux_files.cmake b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Linux/platform_linux_files.cmake index c09abb0dd2..2417ad1b55 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Linux/platform_linux_files.cmake +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Linux/platform_linux_files.cmake @@ -9,5 +9,4 @@ set(FILES MaterialEditor_Traits_Platform.h MaterialEditor_Traits_Linux.h - MaterialEditor_Linux.cpp ) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Linux/tool_dependencies_linux.cmake b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Linux/tool_dependencies_linux.cmake index 5bf4d7cb7e..b2885100e9 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Linux/tool_dependencies_linux.cmake +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Linux/tool_dependencies_linux.cmake @@ -6,5 +6,5 @@ # # -set(GEM_DEPENDENCIES +set(LY_RUNTIME_DEPENDENCIES ) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Mac/MaterialEditor_Mac.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Mac/MaterialEditor_Mac.cpp deleted file mode 100644 index 14b5fee1fe..0000000000 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Mac/MaterialEditor_Mac.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 -#include -#include - -namespace Platform -{ - void LoadPluginDependencies() - { - AZ_Warning("Material Editor", false, "LoadPluginDependencies() function is not implemented"); - } - - void ProcessInput(void* message) - { - AZ_Warning("Material Editor", false, "ProcessInput() function is not implemented"); - } - - AzFramework::NativeWindowHandle GetWindowHandle(WId winId) - { - AZ_Warning("Material Editor", false, "GetWindowHandle() function is not implemented"); - AZ_UNUSED(winId); - return nullptr; - } - - AzFramework::WindowSize GetClientAreaSize(AzFramework::NativeWindowHandle window) - { - AZ_Warning("Material Editor", false, "GetClientAreaSize() function is not implemented"); - AZ_UNUSED(window); - return AzFramework::WindowSize{1,1}; - } -} diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Mac/platform_mac_files.cmake b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Mac/platform_mac_files.cmake index ec56cc1c4e..7275f82047 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Mac/platform_mac_files.cmake +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Mac/platform_mac_files.cmake @@ -9,5 +9,4 @@ set(FILES MaterialEditor_Traits_Platform.h MaterialEditor_Traits_Mac.h - MaterialEditor_Mac.cpp ) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Mac/tool_dependencies_mac.cmake b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Mac/tool_dependencies_mac.cmake index 5bf4d7cb7e..b2885100e9 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Mac/tool_dependencies_mac.cmake +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Mac/tool_dependencies_mac.cmake @@ -6,5 +6,5 @@ # # -set(GEM_DEPENDENCIES +set(LY_RUNTIME_DEPENDENCIES ) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Windows/MaterialEditor_Windows.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Windows/MaterialEditor_Windows.cpp deleted file mode 100644 index 65c135315b..0000000000 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Windows/MaterialEditor_Windows.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 -#include -#include -#include -#include - -namespace Platform -{ - void ProcessInput(void* message) - { - MSG* msg = (MSG*)message; - - // Ensure that the Windows WM_INPUT messages get passed through to the AzFramework input system, - // but only while in game mode so we don't accumulate raw input events before we start actually - // ticking the input devices, otherwise the queued events will get sent when entering game mode. - if (msg->message == WM_INPUT) - { - UINT rawInputSize; - const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER); - GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize); - - LPBYTE rawInputBytes = new BYTE[rawInputSize]; - GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize); - - RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes; - - AzFramework::RawInputNotificationBusWindows::Broadcast( - &AzFramework::RawInputNotificationBusWindows::Events::OnRawInputEvent, *rawInput); - } - } - - AzFramework::NativeWindowHandle GetWindowHandle(WId winId) - { - return reinterpret_cast(winId); - } - - AzFramework::WindowSize GetClientAreaSize(AzFramework::NativeWindowHandle window) - { - RECT r; - if (GetWindowRect(reinterpret_cast(window), &r)) - { - return AzFramework::WindowSize{aznumeric_cast(r.right - r.left), aznumeric_cast(r.bottom - r.top)}; - } - else - { - AZ_Assert(false, "Failed to get dimensions for window"); - return AzFramework::WindowSize{}; - } - } -} diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Windows/platform_windows_files.cmake b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Windows/platform_windows_files.cmake index c104a2b8be..608cb36a28 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Windows/platform_windows_files.cmake +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Windows/platform_windows_files.cmake @@ -9,6 +9,5 @@ set(FILES MaterialEditor_Traits_Platform.h MaterialEditor_Traits_Windows.h - MaterialEditor_Windows.cpp MaterialEditor.rc ) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Windows/tool_dependencies_windows.cmake b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Windows/tool_dependencies_windows.cmake index 374438983f..e1e811ff67 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Windows/tool_dependencies_windows.cmake +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Platform/Windows/tool_dependencies_windows.cmake @@ -6,6 +6,6 @@ # # -set(GEM_DEPENDENCIES +set(LY_RUNTIME_DEPENDENCIES Gem::QtForPython.Editor ) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp index 36498d6d08..7edff11174 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp @@ -231,12 +231,10 @@ namespace MaterialEditor MaterialViewportNotificationBus::Handler::BusConnect(); AZ::TickBus::Handler::BusConnect(); AZ::TransformNotificationBus::MultiHandler::BusConnect(m_cameraEntity->GetId()); - AzFramework::WindowSystemRequestBus::Handler::BusConnect(); } MaterialViewportRenderer::~MaterialViewportRenderer() { - AzFramework::WindowSystemRequestBus::Handler::BusDisconnect(); AZ::TransformNotificationBus::MultiHandler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect(); AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler::BusDisconnect(); @@ -287,11 +285,6 @@ namespace MaterialEditor return m_viewportController; } - AzFramework::NativeWindowHandle MaterialViewportRenderer::GetDefaultWindowHandle() - { - return (m_windowContext) ? m_windowContext->GetWindowHandle() : nullptr; - } - void MaterialViewportRenderer::OnDocumentOpened(const AZ::Uuid& documentId) { AZ::Data::Instance materialInstance; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.h index 8a3e0ca4ff..c6380ddc00 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.h @@ -16,7 +16,6 @@ #include #include #include -#include #include namespace AZ @@ -46,7 +45,6 @@ namespace MaterialEditor , public AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler , public MaterialViewportNotificationBus::Handler , public AZ::TransformNotificationBus::MultiHandler - , public AzFramework::WindowSystemRequestBus::Handler { public: AZ_CLASS_ALLOCATOR(MaterialViewportRenderer, AZ::SystemAllocator, 0); @@ -81,9 +79,6 @@ namespace MaterialEditor // AZ::TransformNotificationBus::MultiHandler overrides... void OnTransformChanged(const AZ::Transform&, const AZ::Transform&) override; - // AzFramework::WindowSystemRequestBus::Handler overrides ... - AzFramework::NativeWindowHandle GetDefaultWindowHandle() override; - using DirectionalLightHandle = AZ::Render::DirectionalLightFeatureProcessorInterface::LightHandle; AZ::Data::Instance m_swapChainPass; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.cpp index c78951ed45..0322fa194e 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.cpp @@ -13,24 +13,16 @@ #include #include -#include -#include +#include +#include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT -#include #include -#include "Source/Viewport/ui_MaterialViewportWidget.h" +#include "Viewport/ui_MaterialViewportWidget.h" AZ_POP_DISABLE_WARNING -#include #include -namespace Platform -{ - void ProcessInput(void* message); -} - - namespace MaterialEditor { @@ -40,11 +32,6 @@ namespace MaterialEditor { m_ui->setupUi(this); - if (auto dispatcher = QAbstractEventDispatcher::instance()) - { - dispatcher->installNativeEventFilter(this); - } - // The viewport context created by AtomToolsFramework::RenderViewportWidget has no name. // Systems like frame capturing and post FX expect there to be a context with DefaultViewportContextName auto viewportContextManager = AZ::Interface::Get(); @@ -54,13 +41,4 @@ namespace MaterialEditor m_renderer = AZStd::make_unique(GetViewportContext()->GetWindowContext()); GetControllerList()->Add(m_renderer->GetController()); } - - // This is a temporary fix to get input working in Qt window, otherwise it wont receive input events - // This will later be handled on the QApplication subclass level - bool MaterialViewportWidget::nativeEventFilter(const QByteArray& /*eventType*/, void* message, long* /*result*/) - { - Platform::ProcessInput(message); - - return false; - } } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.h index 438c7b9a9e..2a71dcc1df 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.h @@ -8,12 +8,10 @@ #pragma once #if !defined(Q_MOC_RUN) -#include #include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT #include -#include AZ_POP_DISABLE_WARNING #endif @@ -38,14 +36,11 @@ namespace MaterialEditor class MaterialViewportWidget : public AtomToolsFramework::RenderViewportWidget - , public QAbstractNativeEventFilter { public: MaterialViewportWidget(QWidget* parent = nullptr); QScopedPointer m_ui; AZStd::unique_ptr m_renderer; - - bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override; }; } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/materialeditor_files.cmake b/Gems/Atom/Tools/MaterialEditor/Code/materialeditor_files.cmake index 2b554a852e..d4c4364ba7 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/materialeditor_files.cmake +++ b/Gems/Atom/Tools/MaterialEditor/Code/materialeditor_files.cmake @@ -10,5 +10,4 @@ set(FILES Source/main.cpp Source/MaterialEditorApplication.cpp Source/MaterialEditorApplication.h - tool_dependencies.cmake ) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/tool_dependencies.cmake b/Gems/Atom/Tools/MaterialEditor/Code/tool_dependencies.cmake deleted file mode 100644 index 8803be3852..0000000000 --- a/Gems/Atom/Tools/MaterialEditor/Code/tool_dependencies.cmake +++ /dev/null @@ -1,22 +0,0 @@ -# -# 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 -# -# - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Null.Private - Gem::Atom_RHI_DX12.Private - Gem::Atom_RHI_Vulkan.Private - Gem::Atom_RHI.Private - Gem::Atom_Component_DebugCamera - Gem::Atom_RPI.Editor - Gem::Atom_RPI.Builders - Gem::Atom_Feature_Common.Editor - Gem::AtomToolsFramework.Editor - Gem::AtomLyIntegration_CommonFeatures.Editor - Gem::EditorPythonBindings.Editor - Gem::ImageProcessingAtom.Editor -) diff --git a/Gems/Atom/Tools/MaterialEditor/MaterialEditor.xml b/Gems/Atom/Tools/MaterialEditor/MaterialEditor.xml deleted file mode 100644 index 9f91001f66..0000000000 --- a/Gems/Atom/Tools/MaterialEditor/MaterialEditor.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Gems/Atom/Tools/MaterialEditor/gem.json b/Gems/Atom/Tools/MaterialEditor/gem.json index 83d9909a84..85ff434eab 100644 --- a/Gems/Atom/Tools/MaterialEditor/gem.json +++ b/Gems/Atom/Tools/MaterialEditor/gem.json @@ -8,7 +8,16 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "AtomToolsFramework", + "Atom_RPI", + "Atom_RHI", + "Atom_Feature_Common", + "ImageProcessingAtom", + "Atom_Component_DebugCamera", + "CommonFeaturesAtom", + "LyShine" + ] } diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/CMakeLists.txt b/Gems/Atom/Tools/ShaderManagementConsole/Code/CMakeLists.txt index d7e415a279..d384378b09 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/CMakeLists.txt +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/CMakeLists.txt @@ -43,6 +43,7 @@ ly_add_target( NAMESPACE Gem AUTOMOC AUTOUIC + AUTORCC FILES_CMAKE shadermanagementconsolewindow_files.cmake INCLUDE_DIRECTORIES @@ -64,6 +65,8 @@ ly_add_target( FILES_CMAKE shadermanagementconsole_files.cmake ${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + PLATFORM_INCLUDE_FILES + ${pal_source_dir}/tool_dependencies_${PAL_PLATFORM_NAME_LOWERCASE}.cmake INCLUDE_DIRECTORIES PRIVATE . @@ -78,27 +81,16 @@ ly_add_target( Gem::ShaderManagementConsole.Window Gem::ShaderManagementConsole.Document RUNTIME_DEPENDENCIES - Gem::Atom_RHI_DX12.Private - Gem::Atom_RHI_Vulkan.Private - Gem::Atom_RHI.Private - Gem::Atom_RPI.Private - Gem::Atom_RPI.Builders - Gem::Atom_Feature_Common.Editor + Gem::AtomToolsFramework.Editor Gem::EditorPythonBindings.Editor ) +ly_set_gem_variant_to_load(TARGETS ShaderManagementConsole VARIANTS Tools) + # Add build dependency to Editor for the ShaderManagementConsole application since # Editor opens up the ShaderManagementConsole ly_add_dependencies(Editor Gem::ShaderManagementConsole) -ly_add_target_dependencies( - TARGETS - ShaderManagementConsole - DEPENDENCIES_FILES - tool_dependencies.cmake - Source/Platform/${PAL_PLATFORM_NAME}/tool_dependencies_${PAL_PLATFORM_NAME_LOWERCASE}.cmake -) - # Inject the project path into the ShaderManagementConsole VS debugger command arguments if the build system being invoked # in a project centric view if(NOT PROJECT_NAME STREQUAL "O3DE") @@ -108,9 +100,14 @@ endif() # Adds the ShaderManagementConsole target as a C preprocessor define so that it can be used as a Settings Registry # specialization in order to look up the generated .setreg which contains the dependencies # specified for the target. -set_source_files_properties( - Source/ShaderManagementConsoleApplication.cpp - PROPERTIES - COMPILE_DEFINITIONS - LY_CMAKE_TARGET="ShaderManagementConsole" -) +if(TARGET ShaderManagementConsole) + set_source_files_properties( + Source/ShaderManagementConsoleApplication.cpp + PROPERTIES + COMPILE_DEFINITIONS + LY_CMAKE_TARGET="ShaderManagementConsole" + ) +else() + message(FATAL_ERROR "Cannot set LY_CMAKE_TARGET define to ShaderManagementConsole as the target doesn't exist anymore." + " Perhaps it has been renamed") +endif() diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/PAL_linux.cmake b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/PAL_linux.cmake index 762ac16004..b5332e2e15 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/PAL_linux.cmake +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/PAL_linux.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_ATOM_SHADER_MANAGEMENT_CONSOLE_APPLICATION_SUPPORTED FALSE) +set(PAL_TRAIT_ATOM_SHADER_MANAGEMENT_CONSOLE_APPLICATION_SUPPORTED TRUE) diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/ShaderManagementConsole_Traits_Linux.h b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/ShaderManagementConsole_Traits_Linux.h new file mode 100644 index 0000000000..2897402f04 --- /dev/null +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/ShaderManagementConsole_Traits_Linux.h @@ -0,0 +1,11 @@ +/* + * 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 + * + */ +#pragma once + +#define AZ_TRAIT_SHADER_MANAGEMENT_CONSOLE_EXT "" + diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/ShaderManagementConsole_Traits_Platform.h b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/ShaderManagementConsole_Traits_Platform.h new file mode 100644 index 0000000000..d381cc79d0 --- /dev/null +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/ShaderManagementConsole_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * 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 + * + */ +#pragma once + +#include diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/platform_linux_files.cmake b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/platform_linux_files.cmake index c2c5a11c4c..11babcdf15 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/platform_linux_files.cmake +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/platform_linux_files.cmake @@ -7,4 +7,6 @@ # set(FILES + ShaderManagementConsole_Traits_Platform.h + ShaderManagementConsole_Traits_Linux.h ) diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/tool_dependencies_linux.cmake b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/tool_dependencies_linux.cmake index 5bf4d7cb7e..b2885100e9 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/tool_dependencies_linux.cmake +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Linux/tool_dependencies_linux.cmake @@ -6,5 +6,5 @@ # # -set(GEM_DEPENDENCIES +set(LY_RUNTIME_DEPENDENCIES ) diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Mac/ShaderManagementConsole_Mac.cpp b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Mac/ShaderManagementConsole_Mac.cpp deleted file mode 100644 index 6a05b895e0..0000000000 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Mac/ShaderManagementConsole_Mac.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 -#include -#include - -namespace Platform -{ - void ProcessInput(void* message) - { - AZ_Warning("Shader Management Console", false, "ProcessInput() function is not implemented"); - } - - AzFramework::NativeWindowHandle GetWindowHandle(WId winId) - { - AZ_Warning("Shader Management Console", false, "GetWindowHandle() function is not implemented"); - AZ_UNUSED(winId); - return nullptr; - } - - AzFramework::WindowSize GetClientAreaSize(AzFramework::NativeWindowHandle window) - { - AZ_Warning("Shader Management Console", false, "GetClientAreaSize() function is not implemented"); - AZ_UNUSED(window); - return AzFramework::WindowSize{1,1}; - } -} diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Mac/platform_mac_files.cmake b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Mac/platform_mac_files.cmake index e48e836b73..6fe2859a89 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Mac/platform_mac_files.cmake +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Mac/platform_mac_files.cmake @@ -9,5 +9,4 @@ set(FILES ShaderManagementConsole_Traits_Platform.h ShaderManagementConsole_Traits_Mac.h - ShaderManagementConsole_Mac.cpp ) diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Mac/tool_dependencies_mac.cmake b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Mac/tool_dependencies_mac.cmake index 5bf4d7cb7e..b2885100e9 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Mac/tool_dependencies_mac.cmake +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Mac/tool_dependencies_mac.cmake @@ -6,5 +6,5 @@ # # -set(GEM_DEPENDENCIES +set(LY_RUNTIME_DEPENDENCIES ) diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Windows/ShaderManagementConsole_Windows.cpp b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Windows/ShaderManagementConsole_Windows.cpp deleted file mode 100644 index ff94ef1196..0000000000 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Windows/ShaderManagementConsole_Windows.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 -#include -#include - -namespace Platform -{ - void ProcessInput(void* message) - { - MSG* msg = (MSG*)message; - - // Ensure that the Windows WM_INPUT messages get passed through to the AzFramework input system, - // but only while in game mode so we don't accumulate raw input events before we start actually - // ticking the input devices, otherwise the queued events will get sent when entering game mode. - if (msg->message == WM_INPUT) - { - UINT rawInputSize; - const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER); - GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize); - - LPBYTE rawInputBytes = new BYTE[rawInputSize]; - GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize); - - RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes; - - AzFramework::RawInputNotificationBusWindows::Broadcast( - &AzFramework::RawInputNotificationBusWindows::Events::OnRawInputEvent, *rawInput); - } - } - - AzFramework::NativeWindowHandle GetWindowHandle(WId winId) - { - return reinterpret_cast(winId); - } - - AzFramework::WindowSize GetClientAreaSize(AzFramework::NativeWindowHandle window) - { - RECT r; - if (GetWindowRect(reinterpret_cast(window), &r)) - { - return AzFramework::WindowSize{aznumeric_cast(r.right - r.left), aznumeric_cast(r.bottom - r.top)}; - } - else - { - AZ_Assert(false, "Failed to get dimensions for window"); - return AzFramework::WindowSize{}; - } - } -} diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Windows/platform_windows_files.cmake b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Windows/platform_windows_files.cmake index 4ec319f402..b978cedeb4 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Windows/platform_windows_files.cmake +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Windows/platform_windows_files.cmake @@ -9,6 +9,5 @@ set(FILES ShaderManagementConsole_Traits_Platform.h ShaderManagementConsole_Traits_Windows.h - ShaderManagementConsole_Windows.cpp ShaderManagementConsole.rc ) diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Windows/tool_dependencies_windows.cmake b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Windows/tool_dependencies_windows.cmake index 374438983f..e1e811ff67 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Windows/tool_dependencies_windows.cmake +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/Windows/tool_dependencies_windows.cmake @@ -6,6 +6,6 @@ # # -set(GEM_DEPENDENCIES +set(LY_RUNTIME_DEPENDENCIES Gem::QtForPython.Editor ) diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/iOS/tool_dependencies_ios.cmake b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/iOS/tool_dependencies_ios.cmake deleted file mode 100644 index 5bf4d7cb7e..0000000000 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Platform/iOS/tool_dependencies_ios.cmake +++ /dev/null @@ -1,10 +0,0 @@ -# -# 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 -# -# - -set(GEM_DEPENDENCIES -) diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/tool_dependencies.cmake b/Gems/Atom/Tools/ShaderManagementConsole/Code/tool_dependencies.cmake deleted file mode 100644 index 8803be3852..0000000000 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/tool_dependencies.cmake +++ /dev/null @@ -1,22 +0,0 @@ -# -# 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 -# -# - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Null.Private - Gem::Atom_RHI_DX12.Private - Gem::Atom_RHI_Vulkan.Private - Gem::Atom_RHI.Private - Gem::Atom_Component_DebugCamera - Gem::Atom_RPI.Editor - Gem::Atom_RPI.Builders - Gem::Atom_Feature_Common.Editor - Gem::AtomToolsFramework.Editor - Gem::AtomLyIntegration_CommonFeatures.Editor - Gem::EditorPythonBindings.Editor - Gem::ImageProcessingAtom.Editor -) diff --git a/Gems/Atom/gem.json b/Gems/Atom/gem.json index 99ca26025a..1f5e4a37f3 100644 --- a/Gems/Atom/gem.json +++ b/Gems/Atom/gem.json @@ -5,8 +5,24 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Atom Renderer Gem provides Atom Renderer and its associated tools (such as Material Editor), utilites, libraries, and interfaces.", - "canonical_tags": ["Gem"], - "user_tags": ["Rendering", "Core"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Rendering", + "Core" + ], "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/atom/atom/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/atom/atom/", + "dependencies": [ + "Atom_Feature_Common", + "AtomShader", + "Atom_Bootstrap", + "Atom_Component_DebugCamera", + "Atom_RHI", + "Atom_RPI", + "AtomToolsFramework", + "MaterialEditor", + "Atom_AtomBridge" + ] } diff --git a/Gems/AtomContent/ReferenceMaterials/gem.json b/Gems/AtomContent/ReferenceMaterials/gem.json index 9e145c20cc..f697d50bc4 100644 --- a/Gems/AtomContent/ReferenceMaterials/gem.json +++ b/Gems/AtomContent/ReferenceMaterials/gem.json @@ -7,5 +7,6 @@ "summary": "Atom Asset Gem with a library of reference materials for StandardPBR (and others in the future)", "canonical_tags": ["Gem"], "user_tags": ["Assets", "PBR", "Materials"], - "icon_path": "preview.png" + "icon_path": "preview.png", + "dependencies": [] } diff --git a/Gems/AtomContent/Sponza/gem.json b/Gems/AtomContent/Sponza/gem.json index 3fc76927e1..64de0e5da0 100644 --- a/Gems/AtomContent/Sponza/gem.json +++ b/Gems/AtomContent/Sponza/gem.json @@ -5,7 +5,12 @@ "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "A standard test scene for Global Illumination (forked from crytek sponza scene)", - "canonical_tags": ["Gem"], - "user_tags": ["Assets"], - "requirements": "" + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Assets" + ], + "requirements": "", + "dependencies": [] } diff --git a/Gems/AtomContent/gem.json b/Gems/AtomContent/gem.json index 6fcb8903e4..1bffe7d989 100644 --- a/Gems/AtomContent/gem.json +++ b/Gems/AtomContent/gem.json @@ -5,8 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The Atom Content Gem provides assets for Atom Renderer and a modified version of the Pixar Look Development Studio.", - "canonical_tags": ["Gem"], - "user_tags": ["Rendering", "Assets", "Tools"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Rendering", + "Assets", + "Tools" + ], "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/atom/atom-content/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/atom/atom-content/", + "dependencies": [ + "ReferenceMaterials", + "Sponza" + ] } diff --git a/Gems/AtomLyIntegration/AtomBridge/gem.json b/Gems/AtomLyIntegration/AtomBridge/gem.json index 49a56d2201..1d48d8be61 100644 --- a/Gems/AtomLyIntegration/AtomBridge/gem.json +++ b/Gems/AtomLyIntegration/AtomBridge/gem.json @@ -8,7 +8,26 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RPI", + "Atom_Bootstrap", + "Atom_RHI", + "Atom_RHI_Null", + "Atom_Feature_Common", + "Atom_Component_DebugCamera", + "AtomImGuiTools", + "CommonFeaturesAtom", + "EMotionFX_Atom", + "ImguiAtom", + "AtomFont", + "AtomViewportDisplayInfo", + "Atom", + "AtomShader", + "ImageProcessingAtom", + "AtomToolsFramework", + "AtomViewportDisplayIcons", + "MaterialEditor" + ] } diff --git a/Gems/AtomLyIntegration/AtomFont/gem.json b/Gems/AtomLyIntegration/AtomFont/gem.json index 7e1a71db86..ed5b488de7 100644 --- a/Gems/AtomLyIntegration/AtomFont/gem.json +++ b/Gems/AtomLyIntegration/AtomFont/gem.json @@ -8,7 +8,11 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RHI", + "Atom_RPI", + "Atom_AtomBridge" + ] } diff --git a/Gems/AtomLyIntegration/AtomImGuiTools/gem.json b/Gems/AtomLyIntegration/AtomImGuiTools/gem.json index ff20d5a19f..564eeedee2 100644 --- a/Gems/AtomLyIntegration/AtomImGuiTools/gem.json +++ b/Gems/AtomLyIntegration/AtomImGuiTools/gem.json @@ -12,5 +12,9 @@ "Debug", "Rendering" ], - "requirements": "" + "requirements": "", + "dependencies": [ + "ImguiAtom", + "Atom" + ] } diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayIcons/Code/Source/AtomViewportDisplayIconsSystemComponent.cpp b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/Code/Source/AtomViewportDisplayIconsSystemComponent.cpp index 6ea6a8e85f..c0ca2fe993 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayIcons/Code/Source/AtomViewportDisplayIconsSystemComponent.cpp +++ b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/Code/Source/AtomViewportDisplayIconsSystemComponent.cpp @@ -82,6 +82,8 @@ namespace AZ::Render void AtomViewportDisplayIconsSystemComponent::Activate() { + m_drawContextRegistered = false; + AzToolsFramework::EditorViewportIconDisplay::Register(this); Bootstrap::NotificationBus::Handler::BusConnect(); @@ -97,9 +99,10 @@ namespace AZ::Render { return; } - if (perViewportDynamicDrawInterface) + if (perViewportDynamicDrawInterface && m_drawContextRegistered) { perViewportDynamicDrawInterface->UnregisterDynamicDrawContext(m_drawContextName); + m_drawContextRegistered = false; } AzToolsFramework::EditorViewportIconDisplay::Unregister(this); @@ -367,6 +370,8 @@ namespace AZ::Render drawContext->EndInit(); }); + m_drawContextRegistered = true; + Data::AssetBus::Handler::BusDisconnect(); } } // namespace AZ::Render diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayIcons/Code/Source/AtomViewportDisplayIconsSystemComponent.h b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/Code/Source/AtomViewportDisplayIconsSystemComponent.h index 25cc7eb8f6..a0914e7e2d 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayIcons/Code/Source/AtomViewportDisplayIconsSystemComponent.h +++ b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/Code/Source/AtomViewportDisplayIconsSystemComponent.h @@ -77,6 +77,8 @@ namespace AZ }; AZStd::unordered_map m_iconData; IconId m_currentId = 0; + + bool m_drawContextRegistered = false; }; } // namespace Render } // namespace AZ diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayIcons/gem.json b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/gem.json index 52dbb219d3..a2a7ba4b77 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayIcons/gem.json +++ b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/gem.json @@ -8,7 +8,12 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RHI", + "Atom_RPI", + "Atom_Bootstrap", + "Atom_AtomBridge" + ] } diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json index f277d6cf82..be5cc96f95 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json @@ -8,7 +8,10 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_RHI", + "Atom_RPI" + ] } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp index 4f4f21ab0f..aa68333108 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp @@ -212,6 +212,9 @@ namespace AZ AZ::Vector3 position = AZ::Vector3::CreateZero(); AZ::TransformBus::EventResult(position, GetEntityId(), &AZ::TransformBus::Events::GetWorldTranslation); + AZ::Quaternion rotationQuaternion = AZ::Quaternion::CreateIdentity(); + AZ::TransformBus::EventResult(rotationQuaternion, GetEntityId(), &AZ::TransformBus::Events::GetWorldRotationQuaternion); + AZ::Matrix3x3 rotationMatrix = AZ::Matrix3x3::CreateFromQuaternion(rotationQuaternion); float scale = 1.0f; AZ::TransformBus::EventResult(scale, GetEntityId(), &AZ::TransformBus::Events::GetLocalUniformScale); @@ -224,9 +227,7 @@ namespace AZ AZ::Vector3 innerExtents(configuration.m_innerWidth, configuration.m_innerLength, configuration.m_innerHeight); innerExtents *= scale; - AZ::Vector3 innerMin(position.GetX() - innerExtents.GetX() / 2, position.GetY() - innerExtents.GetY() / 2, position.GetZ() - innerExtents.GetZ() / 2); - AZ::Vector3 innerMax(position.GetX() + innerExtents.GetX() / 2, position.GetY() + innerExtents.GetY() / 2, position.GetZ() + innerExtents.GetZ() / 2); - debugDisplay.DrawWireBox(innerMin, innerMax); + debugDisplay.DrawWireOBB(position, rotationMatrix.GetBasisX(), rotationMatrix.GetBasisY(), rotationMatrix.GetBasisZ(), innerExtents / 2.0f); } AZ::Aabb EditorReflectionProbeComponent::GetEditorSelectionBoundsViewport([[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo) diff --git a/Gems/AtomLyIntegration/CommonFeatures/gem.json b/Gems/AtomLyIntegration/CommonFeatures/gem.json index 8eceb50932..30738ad14c 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/gem.json +++ b/Gems/AtomLyIntegration/CommonFeatures/gem.json @@ -8,7 +8,15 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "Atom_Feature_Common", + "LmbrCentral", + "GradientSignal", + "SurfaceData", + "Atom_Bootstrap", + "Atom_RPI", + "AtomToolsFramework" + ] } diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/gem.json b/Gems/AtomLyIntegration/EMotionFXAtom/gem.json index a7c3e96d2b..f921990360 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/gem.json +++ b/Gems/AtomLyIntegration/EMotionFXAtom/gem.json @@ -8,7 +8,14 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "EMotionFX", + "Atom", + "Atom_Feature_Common", + "Atom_RPI", + "Atom_RHI", + "CommonFeaturesAtom" + ] } diff --git a/Gems/AtomLyIntegration/ImguiAtom/gem.json b/Gems/AtomLyIntegration/ImguiAtom/gem.json index 918414f053..6b032275b9 100644 --- a/Gems/AtomLyIntegration/ImguiAtom/gem.json +++ b/Gems/AtomLyIntegration/ImguiAtom/gem.json @@ -8,7 +8,10 @@ "canonical_tags": [ "Gem" ], - "user_tags": [ - ], - "requirements": "" + "user_tags": [], + "requirements": "", + "dependencies": [ + "ImGui", + "Atom_Feature_Common" + ] } diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json index f978d777cf..1cee5c8298 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json @@ -10,9 +10,10 @@ ], "user_tags": [ "DCC", - "Digital", - "Content", - "Creation" + "Digital", + "Content", + "Creation" ], - "requirements": "" + "requirements": "", + "dependencies": [] } diff --git a/Gems/AtomLyIntegration/gem.json b/Gems/AtomLyIntegration/gem.json index 5af133c8e8..00b3a25f74 100644 --- a/Gems/AtomLyIntegration/gem.json +++ b/Gems/AtomLyIntegration/gem.json @@ -5,8 +5,24 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Atom O3DE Integration Gem provides components, libraries, and functionality to support and integrate Atom Renderer in Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Rendering", "Core", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Rendering", + "Core", + "Utility" + ], "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/atom/atom-ly-integration/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/atom/atom-ly-integration/", + "dependencies": [ + "Atom_AtomBridge", + "AtomFont", + "AtomImGuiTools", + "AtomViewportDisplayIcons", + "AtomViewportDisplayInfo", + "CommonFeaturesAtom", + "EMotionFX_Atom", + "ImguiAtom" + ] } diff --git a/Gems/AudioEngineWwise/gem.json b/Gems/AudioEngineWwise/gem.json index 084c924977..0588af1908 100644 --- a/Gems/AudioEngineWwise/gem.json +++ b/Gems/AudioEngineWwise/gem.json @@ -5,9 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Wwise Audio Engine Gem provides support for Audiokinetic Wave Works Interactive Sound Engine (Wwise).", - "canonical_tags": ["Gem"], - "user_tags": ["Audio", "Utility", "Tools"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Audio", + "Utility", + "Tools" + ], "icon_path": "preview.png", "requirements": "Users will need to download Wwise from the Audiokinetic Web Site.", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/audio/wwise/audio-engine-wwise/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/audio/wwise/audio-engine-wwise/", + "dependencies": [ + "AudioSystem" + ] } diff --git a/Gems/AudioSystem/gem.json b/Gems/AudioSystem/gem.json index f618103803..64d4d9af5a 100644 --- a/Gems/AudioSystem/gem.json +++ b/Gems/AudioSystem/gem.json @@ -5,9 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Audio System Gem provides the Audio Translation Layer (ATL) and Audio Controls Editor, which add support for audio in Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Audio", "Utility", "Tools"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Audio", + "Utility", + "Tools" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/audio/audio-system/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/audio/audio-system/", + "dependencies": [ + "LmbrCentral" + ] } diff --git a/Gems/BarrierInput/gem.json b/Gems/BarrierInput/gem.json index 738d644d84..7fdc58e8b3 100644 --- a/Gems/BarrierInput/gem.json +++ b/Gems/BarrierInput/gem.json @@ -5,8 +5,17 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Barrier Input Gem allows the Open 3D Engine to function as a Barrier client so that it can receive input from a remote Barrier server.", - "canonical_tags": ["Gem"], - "user_tags": ["Input", "Barrier", "Synergy"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Input", + "Barrier", + "Synergy" + ], "icon_path": "preview.png", - "requirements": "" + "requirements": "", + "dependencies": [ + "Atom_RPI" + ] } diff --git a/Gems/Blast/gem.json b/Gems/Blast/gem.json index 3b1205d6fe..d6af47f482 100644 --- a/Gems/Blast/gem.json +++ b/Gems/Blast/gem.json @@ -5,9 +5,22 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The NVIDIA Blast Gem provides tools to author fractured mesh assets in Houdini, and functionality to create realistic destruction simulations in Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Physics", "Simulation", "Animation"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Physics", + "Simulation", + "Animation" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/physics/nvidia/nvidia-blast/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/physics/nvidia/nvidia-blast/", + "dependencies": [ + "Atom_Feature_Common", + "CommonFeaturesAtom", + "PhysX", + "Atom_RPI", + "PythonAssetBuilder" + ] } diff --git a/Gems/Camera/Code/CMakeLists.txt b/Gems/Camera/Code/CMakeLists.txt index f34ac8a698..c9f0bf768f 100644 --- a/Gems/Camera/Code/CMakeLists.txt +++ b/Gems/Camera/Code/CMakeLists.txt @@ -57,6 +57,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) PRIVATE AZ::AzToolsFramework Gem::Camera.Static + Gem::AtomToolsFramework.Static RUNTIME_DEPENDENCIES Legacy::EditorCommon ) diff --git a/Gems/Camera/gem.json b/Gems/Camera/gem.json index 2fc2ea8355..9f8ea22412 100644 --- a/Gems/Camera/gem.json +++ b/Gems/Camera/gem.json @@ -5,9 +5,17 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Camera Gem provides a basic camera component that defines a frustum for runtime rendering.", - "canonical_tags": ["Gem"], - "user_tags": ["Rendering", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Rendering", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/camera/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/camera/", + "dependencies": [ + "Atom_RPI" + ] } diff --git a/Gems/CameraFramework/gem.json b/Gems/CameraFramework/gem.json index 2ab25a84b4..c5520fd5b4 100644 --- a/Gems/CameraFramework/gem.json +++ b/Gems/CameraFramework/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Camera Framework Gem provides a base for implementing more complex camera systems.", - "canonical_tags": ["Gem"], - "user_tags": ["Rendering", "Framework", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Rendering", + "Framework", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/camera-framework/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/camera-framework/", + "dependencies": [] } diff --git a/Gems/CertificateManager/gem.json b/Gems/CertificateManager/gem.json index cb49abc6b6..968da2788a 100644 --- a/Gems/CertificateManager/gem.json +++ b/Gems/CertificateManager/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Certificate Manager Gem provides access to authentication files for secure game connections from Amazon S3, files on disk, and other 3rd party sources.", - "canonical_tags": ["Gem"], - "user_tags": ["Network", "Framework"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Network", + "Framework" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/network/certificate-manager/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/network/certificate-manager/", + "dependencies": [] } diff --git a/Gems/CrashReporting/gem.json b/Gems/CrashReporting/gem.json index bff54dcf36..9b8d3a9e0a 100644 --- a/Gems/CrashReporting/gem.json +++ b/Gems/CrashReporting/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Crash Reporting Gem provides support for external crash reporting for Open 3D Engine projects.", - "canonical_tags": ["Gem"], - "user_tags": ["Debug", "Framework"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Debug", + "Framework" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/debug/crash-reporting/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/debug/crash-reporting/", + "dependencies": [] } diff --git a/Gems/CustomAssetExample/gem.json b/Gems/CustomAssetExample/gem.json index ac7c2788d7..ab5ed7002d 100644 --- a/Gems/CustomAssetExample/gem.json +++ b/Gems/CustomAssetExample/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Custom Asset Example Gem provides example code for creating a custom asset for Open 3D Engine's asset pipeline.", - "canonical_tags": ["Gem"], - "user_tags": ["Assets", "Tools"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Assets", + "Tools" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/custom-asset-example/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/custom-asset-example/", + "dependencies": [] } diff --git a/Gems/DebugDraw/gem.json b/Gems/DebugDraw/gem.json index a7939f4077..7e0da07103 100644 --- a/Gems/DebugDraw/gem.json +++ b/Gems/DebugDraw/gem.json @@ -5,9 +5,19 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Debug Draw Gem provides Editor and runtime debug visualization features for Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Debug", "Tools", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Debug", + "Tools", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/debug/debug-draw/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/debug/debug-draw/", + "dependencies": [ + "Atom_RPI", + "Atom_Bootstrap" + ] } diff --git a/Gems/DevTextures/gem.json b/Gems/DevTextures/gem.json index 81d14e78cb..8b40badbf1 100644 --- a/Gems/DevTextures/gem.json +++ b/Gems/DevTextures/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The Dev Textures Gem provides a collection of general purpose texture assets useful for prototypes and preproduction.", - "canonical_tags": ["Gem"], - "user_tags": ["Assets", "Debug", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Assets", + "Debug", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/dev-textures/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/dev-textures/", + "dependencies": [] } diff --git a/Gems/EMotionFX/Assets/Editor/Layouts/SimulatedObjects.layout b/Gems/EMotionFX/Assets/Editor/Layouts/SimulatedObjects.layout index a7879584a9..f52df642e8 100644 Binary files a/Gems/EMotionFX/Assets/Editor/Layouts/SimulatedObjects.layout and b/Gems/EMotionFX/Assets/Editor/Layouts/SimulatedObjects.layout differ diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp index 5a4ca95670..b547b92306 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp @@ -372,6 +372,8 @@ namespace EMotionFX // updates the skinning matrices of all nodes void ActorInstance::UpdateSkinningMatrices() { + AZ_PROFILE_SCOPE(Animation, "ActorInstance::UpdateSkinningMatrices"); + AZ::Matrix3x4* skinningMatrices = m_transformData->GetSkinningMatrices(); const Pose* pose = m_transformData->GetCurrentPose(); @@ -596,6 +598,8 @@ namespace EMotionFX // update the bounding volume void ActorInstance::UpdateBounds(size_t geomLODLevel, EBoundsType boundsType, uint32 itemFrequency) { + AZ_PROFILE_SCOPE(Animation, "ActorInstance::UpdateBounds"); + // depending on the bounding volume update type switch (boundsType) { diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphInstance.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphInstance.cpp index d0c77721d1..7f9e8ba6fc 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphInstance.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphInstance.cpp @@ -218,6 +218,8 @@ namespace EMotionFX // output the results into the internal pose object void AnimGraphInstance::Output(Pose* outputPose) { + AZ_PROFILE_SCOPE(Animation, "AnimGraphInstance::Output"); + // reset max used const uint32 threadIndex = m_actorInstance->GetThreadIndex(); AnimGraphPosePool& posePool = GetEMotionFX().GetThreadData(threadIndex)->GetPosePool(); @@ -854,6 +856,8 @@ namespace EMotionFX // synchronize all nodes, based on sync tracks etc void AnimGraphInstance::Update(float timePassedInSeconds) { + AZ_PROFILE_SCOPE(Animation, "AnimGraphInstance::Update"); + // pass 0: (Optional, networking only) When this instance is shared between network, restore the instance using an animgraph snapshot. if (m_snapshot) { @@ -940,6 +944,8 @@ namespace EMotionFX // reset all node pose ref counts void AnimGraphInstance::ResetPoseRefCountsForAllNodes() { + AZ_PROFILE_SCOPE(Animation, "AnimGraphInstance::ResetPoseRefCountsForAllNodes"); + const size_t numNodes = m_animGraph->GetNumNodes(); for (size_t i = 0; i < numNodes; ++i) { @@ -951,6 +957,8 @@ namespace EMotionFX // reset all node pose ref counts void AnimGraphInstance::ResetRefDataRefCountsForAllNodes() { + AZ_PROFILE_SCOPE(Animation, "AnimGraphInstance::ResetRefDataRefCountsForAllNodes"); + const size_t numNodes = m_animGraph->GetNumNodes(); for (size_t i = 0; i < numNodes; ++i) { @@ -962,6 +970,8 @@ namespace EMotionFX // reset all node flags void AnimGraphInstance::ResetFlagsForAllObjects() { + AZ_PROFILE_SCOPE(Animation, "AnimGraphInstance::ResetFlagsForAllObjects"); + MCore::MemSet(m_objectFlags.data(), 0, sizeof(uint32) * m_objectFlags.size()); for (AnimGraphInstance* childInstance : m_childAnimGraphInstances) diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphMotionNode.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphMotionNode.cpp index 6bcc69360b..c5e50ed9dd 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphMotionNode.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphMotionNode.cpp @@ -394,6 +394,8 @@ namespace EMotionFX // the main process method of the final node void AnimGraphMotionNode::Output(AnimGraphInstance* animGraphInstance) { + AZ_PROFILE_SCOPE(Animation, "AnimGraphMotionNode::Output"); + // if this motion is disabled, output the bind pose if (m_disabled) { @@ -537,6 +539,8 @@ namespace EMotionFX void AnimGraphMotionNode::UniqueData::Update() { + AZ_PROFILE_SCOPE(Animation, "AnimGraphMotionNode::Update"); + AnimGraphMotionNode* motionNode = azdynamic_cast(m_object); AZ_Assert(motionNode, "Unique data linked to incorrect node type."); diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphStateMachine.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphStateMachine.cpp index 9b4c2d07ac..0024d97a45 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphStateMachine.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphStateMachine.cpp @@ -92,6 +92,8 @@ namespace EMotionFX void AnimGraphStateMachine::Output(AnimGraphInstance* animGraphInstance) { + AZ_PROFILE_SCOPE(Animation, "AnimGraphStateMachine::Update"); + ActorInstance* actorInstance = animGraphInstance->GetActorInstance(); AnimGraphPose* outputPose = nullptr; @@ -476,6 +478,8 @@ namespace EMotionFX void AnimGraphStateMachine::Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) { + AZ_PROFILE_SCOPE(Animation, "AnimGraphStateMachine::Update"); + UniqueData* uniqueData = static_cast(FindOrCreateUniqueNodeData(animGraphInstance)); // Defer switch to entry state. @@ -622,6 +626,8 @@ namespace EMotionFX void AnimGraphStateMachine::PostUpdate(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) { + AZ_PROFILE_SCOPE(Animation, "AnimGraphStateMachine::PostUpdate"); + RequestRefDatas(animGraphInstance); UniqueData* uniqueData = static_cast(FindOrCreateUniqueNodeData(animGraphInstance)); AnimGraphRefCountedData* data = uniqueData->GetRefCountedData(); @@ -1344,6 +1350,8 @@ namespace EMotionFX void AnimGraphStateMachine::TopDownUpdate(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) { + AZ_PROFILE_SCOPE(Animation, "AnimGraphStateMachine::TopDownUpdate"); + UniqueData* uniqueData = static_cast(FindOrCreateUniqueNodeData(animGraphInstance)); if (!IsTransitioning(uniqueData)) diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/BlendSpace1DNode.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/BlendSpace1DNode.cpp index 0e823075a4..deeb593c96 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/BlendSpace1DNode.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/BlendSpace1DNode.cpp @@ -166,6 +166,8 @@ namespace EMotionFX void BlendSpace1DNode::Output(AnimGraphInstance* animGraphInstance) { + AZ_PROFILE_SCOPE(Animation, "BlendSpace1DNode::Output"); + if (!AnimGraphInstanceExists(animGraphInstance)) { return; @@ -276,6 +278,8 @@ namespace EMotionFX void BlendSpace1DNode::Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) { + AZ_PROFILE_SCOPE(Animation, "BlendSpace1DNode::Update"); + if (!m_disabled) { EMotionFX::BlendTreeConnection* paramConnection = GetInputPort(INPUTPORT_VALUE).m_connection; diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/BlendSpace2DNode.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/BlendSpace2DNode.cpp index 03a7552410..66366087f8 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/BlendSpace2DNode.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/BlendSpace2DNode.cpp @@ -282,6 +282,8 @@ namespace EMotionFX void BlendSpace2DNode::Output(AnimGraphInstance* animGraphInstance) { + AZ_PROFILE_SCOPE(Animation, "BlendSpace2DNode::Output"); + if (!AnimGraphInstanceExists(animGraphInstance)) { return; @@ -402,6 +404,8 @@ namespace EMotionFX void BlendSpace2DNode::Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) { + AZ_PROFILE_SCOPE(Animation, "BlendSpace2DNode::Update"); + if (!AnimGraphInstanceExists(animGraphInstance)) { return; diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/BlendTree.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/BlendTree.cpp index f742696275..a4109a5a60 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/BlendTree.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/BlendTree.cpp @@ -116,10 +116,11 @@ namespace EMotionFX return nullptr; } - // process the blend tree and calculate its output void BlendTree::Output(AnimGraphInstance* animGraphInstance) { + AZ_PROFILE_SCOPE(Animation, "BlendTree::Output"); + AZ_Assert(m_finalNode, "There should always be a final node. Something seems to be wrong with the blend tree creation."); // get the output pose @@ -164,6 +165,8 @@ namespace EMotionFX // post sync update void BlendTree::PostUpdate(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) { + AZ_PROFILE_SCOPE(Animation, "AnimGraphStateMachine::PostUpdate"); + // if this node is disabled, exit if (m_disabled) { @@ -212,6 +215,8 @@ namespace EMotionFX // update all nodes void BlendTree::Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) { + AZ_PROFILE_SCOPE(Animation, "BlendTree::Update"); + // if this node is disabled, output the bind pose if (m_disabled) { @@ -256,6 +261,8 @@ namespace EMotionFX // top down update void BlendTree::TopDownUpdate(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) { + AZ_PROFILE_SCOPE(Animation, "BlendTree::TopDownUpdate"); + // get the final node AnimGraphNode* finalNode = GetRealFinalNode(); diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/BlendTreeBlend2Node.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/BlendTreeBlend2Node.cpp index 06223d4b14..b14f72c7c3 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/BlendTreeBlend2Node.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/BlendTreeBlend2Node.cpp @@ -45,6 +45,8 @@ namespace EMotionFX void BlendTreeBlend2Node::Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) { + AZ_PROFILE_SCOPE(Animation, "BlendTreeBlend2Node::Update"); + if (m_disabled) { AnimGraphNodeData* uniqueData = FindOrCreateUniqueNodeData(animGraphInstance); @@ -88,9 +90,10 @@ namespace EMotionFX } } - void BlendTreeBlend2Node::Output(AnimGraphInstance* animGraphInstance) { + AZ_PROFILE_SCOPE(Animation, "BlendTreeBlend2Node::Output"); + if (m_disabled) { RequestPoses(animGraphInstance); diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/MotionSystem.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/MotionSystem.cpp index a6ddb776c2..237389312f 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/MotionSystem.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/MotionSystem.cpp @@ -147,6 +147,8 @@ namespace EMotionFX // update motion queue and instances void MotionSystem::Update(float timePassed, bool updateNodes) { + AZ_PROFILE_SCOPE(Animation, "MotionSystem::Update"); + MCORE_UNUSED(updateNodes); // update the motion queue diff --git a/Gems/EMotionFX/Code/Source/Editor/Plugins/Cloth/ClothJointWidget.cpp b/Gems/EMotionFX/Code/Source/Editor/Plugins/Cloth/ClothJointWidget.cpp index 971eb15501..0597b79d90 100644 --- a/Gems/EMotionFX/Code/Source/Editor/Plugins/Cloth/ClothJointWidget.cpp +++ b/Gems/EMotionFX/Code/Source/Editor/Plugins/Cloth/ClothJointWidget.cpp @@ -69,7 +69,7 @@ namespace EMotionFX void ClothJointWidget::InternalReinit() { - if (m_selectedModelIndices.size() == 1) + if (GetSelectedModelIndices().size() == 1) { Physics::CharacterColliderNodeConfiguration* nodeConfig = GetNodeConfig(); if (nodeConfig) @@ -94,17 +94,17 @@ namespace EMotionFX void ClothJointWidget::OnAddCollider(const AZ::TypeId& colliderType) { - ColliderHelpers::AddCollider(m_selectedModelIndices , PhysicsSetup::Cloth, colliderType); + ColliderHelpers::AddCollider(GetSelectedModelIndices(), PhysicsSetup::Cloth, colliderType); } void ClothJointWidget::OnCopyCollider(size_t colliderIndex) { - ColliderHelpers::CopyColliderToClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::Cloth); + ColliderHelpers::CopyColliderToClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::Cloth); } void ClothJointWidget::OnPasteCollider(size_t colliderIndex, bool replace) { - ColliderHelpers::PasteColliderFromClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::Cloth, replace); + ColliderHelpers::PasteColliderFromClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::Cloth, replace); } void ClothJointWidget::OnRemoveCollider(size_t colliderIndex) @@ -114,7 +114,7 @@ namespace EMotionFX Physics::CharacterColliderNodeConfiguration* ClothJointWidget::GetNodeConfig() const { - AZ_Assert(m_selectedModelIndices.size() == 1, "Get Node config function only return the config when it is single seleted"); + AZ_Assert(GetSelectedModelIndices().size() == 1, "Get Node config function only return the config when it is single seleted"); Actor* actor = GetActor(); Node* joint = GetNode(); if (!actor || !joint) diff --git a/Gems/EMotionFX/Code/Source/Editor/Plugins/HitDetection/HitDetectionJointWidget.cpp b/Gems/EMotionFX/Code/Source/Editor/Plugins/HitDetection/HitDetectionJointWidget.cpp index 17265668ab..7fc9ee36e2 100644 --- a/Gems/EMotionFX/Code/Source/Editor/Plugins/HitDetection/HitDetectionJointWidget.cpp +++ b/Gems/EMotionFX/Code/Source/Editor/Plugins/HitDetection/HitDetectionJointWidget.cpp @@ -65,7 +65,7 @@ namespace EMotionFX void HitDetectionJointWidget::InternalReinit() { - if (m_selectedModelIndices.size() == 1) + if (GetSelectedModelIndices().size() == 1) { Physics::CharacterColliderNodeConfiguration* hitDetectionNodeConfig = GetNodeConfig(); if (hitDetectionNodeConfig) @@ -90,17 +90,17 @@ namespace EMotionFX void HitDetectionJointWidget::OnAddCollider(const AZ::TypeId& colliderType) { - ColliderHelpers::AddCollider(m_selectedModelIndices, PhysicsSetup::HitDetection, colliderType); + ColliderHelpers::AddCollider(GetSelectedModelIndices(), PhysicsSetup::HitDetection, colliderType); } void HitDetectionJointWidget::OnCopyCollider(size_t colliderIndex) { - ColliderHelpers::CopyColliderToClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::HitDetection); + ColliderHelpers::CopyColliderToClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::HitDetection); } void HitDetectionJointWidget::OnPasteCollider(size_t colliderIndex, bool replace) { - ColliderHelpers::PasteColliderFromClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::HitDetection, replace); + ColliderHelpers::PasteColliderFromClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::HitDetection, replace); } void HitDetectionJointWidget::OnRemoveCollider(size_t colliderIndex) @@ -110,7 +110,7 @@ namespace EMotionFX Physics::CharacterColliderNodeConfiguration* HitDetectionJointWidget::GetNodeConfig() { - AZ_Assert(m_selectedModelIndices.size() == 1, "Get Node config function only return the config when it is single seleted"); + AZ_Assert(GetSelectedModelIndices().size() == 1, "Get Node config function only return the config when it is single seleted"); Actor* actor = GetActor(); Node* node = GetNode(); if (!actor || !node) diff --git a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeWidget.cpp b/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeWidget.cpp index 00cdb90a22..29745beb68 100644 --- a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeWidget.cpp +++ b/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeWidget.cpp @@ -116,7 +116,8 @@ namespace EMotionFX void RagdollNodeWidget::InternalReinit() { - if (m_selectedModelIndices.size() == 1) + const QModelIndexList& selectedModelIndices = GetSelectedModelIndices(); + if (selectedModelIndices.size() == 1) { m_ragdollNodeEditor->ClearInstances(false); @@ -142,7 +143,7 @@ namespace EMotionFX m_collidersWidget->Reset(); } - m_jointLimitWidget->Update(m_selectedModelIndices[0]); + m_jointLimitWidget->Update(selectedModelIndices[0]); m_ragdollNodeCard->setExpanded(true); m_ragdollNodeCard->show(); m_jointLimitWidget->show(); @@ -169,31 +170,32 @@ namespace EMotionFX void RagdollNodeWidget::OnAddRemoveRagdollNode() { + const QModelIndexList& selectedModelIndices = GetSelectedModelIndices(); if (GetRagdollNodeConfig()) { // The node is present in the ragdoll, remove it. - RagdollNodeInspectorPlugin::RemoveFromRagdoll(m_selectedModelIndices); + RagdollNodeInspectorPlugin::RemoveFromRagdoll(selectedModelIndices); } else { // The node is not part of the ragdoll, add it. - RagdollNodeInspectorPlugin::AddToRagdoll(m_selectedModelIndices); + RagdollNodeInspectorPlugin::AddToRagdoll(selectedModelIndices); } } void RagdollNodeWidget::OnAddCollider(const AZ::TypeId& colliderType) { - ColliderHelpers::AddCollider(m_selectedModelIndices, PhysicsSetup::Ragdoll, colliderType); + ColliderHelpers::AddCollider(GetSelectedModelIndices(), PhysicsSetup::Ragdoll, colliderType); } void RagdollNodeWidget::OnCopyCollider(size_t colliderIndex) { - ColliderHelpers::CopyColliderToClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::Ragdoll); + ColliderHelpers::CopyColliderToClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::Ragdoll); } void RagdollNodeWidget::OnPasteCollider(size_t colliderIndex, bool replace) { - ColliderHelpers::PasteColliderFromClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::Ragdoll, replace); + ColliderHelpers::PasteColliderFromClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::Ragdoll, replace); } void RagdollNodeWidget::OnRemoveCollider(size_t colliderIndex) diff --git a/Gems/EMotionFX/Code/Source/Editor/Plugins/SimulatedObject/SimulatedObjectColliderWidget.cpp b/Gems/EMotionFX/Code/Source/Editor/Plugins/SimulatedObject/SimulatedObjectColliderWidget.cpp index 8c6c72934d..b56cafc9be 100644 --- a/Gems/EMotionFX/Code/Source/Editor/Plugins/SimulatedObject/SimulatedObjectColliderWidget.cpp +++ b/Gems/EMotionFX/Code/Source/Editor/Plugins/SimulatedObject/SimulatedObjectColliderWidget.cpp @@ -131,7 +131,8 @@ namespace EMotionFX void SimulatedObjectColliderWidget::InternalReinit() { - if (m_selectedModelIndices.size() == 1) + const QModelIndexList& selectedModelIndices = GetSelectedModelIndices(); + if (selectedModelIndices.size() == 1) { Physics::CharacterColliderNodeConfiguration* nodeConfig = GetNodeConfig(); if (nodeConfig) @@ -172,12 +173,13 @@ namespace EMotionFX } AZStd::string labelText; + const QModelIndexList& selectedModelIndices = GetSelectedModelIndices(); const AZStd::vector& simObjs = actor->GetSimulatedObjectSetup()->GetSimulatedObjects(); for (const SimulatedObject* obj : simObjs) { - for (int i = 0; i < m_selectedModelIndices.size(); ++i) + for (int i = 0; i < selectedModelIndices.size(); ++i) { - Node* node = m_selectedModelIndices[i].data(SkeletonModel::ROLE_POINTER).value(); + Node* node = selectedModelIndices[i].data(SkeletonModel::ROLE_POINTER).value(); if (obj->FindSimulatedJointBySkeletonJointIndex(node->GetNodeIndex())) { if (!labelText.empty()) @@ -208,8 +210,9 @@ namespace EMotionFX return; } + const QModelIndexList& selectedModelIndices = GetSelectedModelIndices(); // Only show the notification when it is single selection. - if (m_selectedModelIndices.size() != 1) + if (selectedModelIndices.size() != 1) { return; } @@ -250,17 +253,18 @@ namespace EMotionFX void SimulatedObjectColliderWidget::OnAddCollider(const AZ::TypeId& colliderType) { - ColliderHelpers::AddCollider(m_selectedModelIndices, PhysicsSetup::SimulatedObjectCollider, colliderType); + ColliderHelpers::AddCollider(GetSelectedModelIndices(), PhysicsSetup::SimulatedObjectCollider, colliderType); } void SimulatedObjectColliderWidget::OnCopyCollider(size_t colliderIndex) { - ColliderHelpers::CopyColliderToClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider); + ColliderHelpers::CopyColliderToClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider); } void SimulatedObjectColliderWidget::OnPasteCollider(size_t colliderIndex, bool replace) { - ColliderHelpers::PasteColliderFromClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider, replace); + ColliderHelpers::PasteColliderFromClipboard( + GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider, replace); } void SimulatedObjectColliderWidget::OnRemoveCollider(size_t colliderIndex) @@ -270,7 +274,7 @@ namespace EMotionFX Physics::CharacterColliderNodeConfiguration* SimulatedObjectColliderWidget::GetNodeConfig() const { - AZ_Assert(m_selectedModelIndices.size() == 1, "Get Node config function only return the config when it is single seleted"); + AZ_Assert(GetSelectedModelIndices().size() == 1, "Get Node config function only return the config when it is single seleted"); Actor* actor = GetActor(); Node* joint = GetNode(); if (!actor || !joint) diff --git a/Gems/EMotionFX/Code/Source/Editor/Plugins/SkeletonOutliner/SkeletonOutlinerPlugin.cpp b/Gems/EMotionFX/Code/Source/Editor/Plugins/SkeletonOutliner/SkeletonOutlinerPlugin.cpp index f8ca291d55..ec8d1147f2 100644 --- a/Gems/EMotionFX/Code/Source/Editor/Plugins/SkeletonOutliner/SkeletonOutlinerPlugin.cpp +++ b/Gems/EMotionFX/Code/Source/Editor/Plugins/SkeletonOutliner/SkeletonOutlinerPlugin.cpp @@ -211,15 +211,15 @@ namespace EMotionFX AZ::Outcome SkeletonOutlinerPlugin::GetSelectedRowIndices() { - return AZ::Success(m_selectedRows); + return AZ::Success(m_treeView->selectionModel()->selectedRows()); } void SkeletonOutlinerPlugin::OnSelectionChanged([[maybe_unused]] const QItemSelection& selected, [[maybe_unused]] const QItemSelection& deselected) { - m_selectedRows = m_treeView->selectionModel()->selectedRows(); - if (m_selectedRows.size() == 1) + QModelIndexList selectedRows = m_treeView->selectionModel()->selectedRows(); + if (selectedRows.size() == 1) { - const QModelIndex& modelIndex = m_selectedRows[0]; + const QModelIndex& modelIndex = selectedRows[0]; Node* selectedNode = modelIndex.data(SkeletonModel::ROLE_POINTER).value(); Actor* selectedActor = modelIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value(); SkeletonOutlinerNotificationBus::Broadcast(&SkeletonOutlinerNotifications::SingleNodeSelectionChanged, selectedActor, selectedNode); diff --git a/Gems/EMotionFX/Code/Source/Editor/SkeletonModelJointWidget.cpp b/Gems/EMotionFX/Code/Source/Editor/SkeletonModelJointWidget.cpp index e9eeb7530d..9cfc763fc6 100644 --- a/Gems/EMotionFX/Code/Source/Editor/SkeletonModelJointWidget.cpp +++ b/Gems/EMotionFX/Code/Source/Editor/SkeletonModelJointWidget.cpp @@ -72,14 +72,7 @@ namespace EMotionFX setLayout(mainLayout); - AZ::Outcome selectedRowIndicesOutcome; - QModelIndexList selectedModelIndices; - SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices); - if (selectedRowIndicesOutcome.IsSuccess()) - { - selectedModelIndices = selectedRowIndicesOutcome.GetValue(); - } - Reinit(selectedModelIndices); + Reinit(); // Connect to the model. SkeletonModel* skeletonModel = nullptr; @@ -92,9 +85,9 @@ namespace EMotionFX } } - void SkeletonModelJointWidget::Reinit(const QModelIndexList& selectedModelIndices) + void SkeletonModelJointWidget::Reinit() { - m_selectedModelIndices = selectedModelIndices; + const QModelIndexList& selectedModelIndices = GetSelectedModelIndices(); if (!EMStudio::GetManager()->GetIgnoreVisibility() && !isVisible()) { @@ -103,15 +96,15 @@ namespace EMotionFX if (GetActor()) { - if (!m_selectedModelIndices.isEmpty()) + if (!selectedModelIndices.isEmpty()) { - if (m_selectedModelIndices.size() == 1) + if (selectedModelIndices.size() == 1) { m_jointNameLabel->setText(GetNode()->GetName()); } else { - m_jointNameLabel->setText(QString("%1 joints selected").arg(m_selectedModelIndices.size())); + m_jointNameLabel->setText(QString("%1 joints selected").arg(selectedModelIndices.size())); } m_noSelectionWidget->hide(); @@ -136,7 +129,7 @@ namespace EMotionFX void SkeletonModelJointWidget::showEvent(QShowEvent* event) { QWidget::showEvent(event); - Reinit(m_selectedModelIndices); + Reinit(); } void SkeletonModelJointWidget::OnSelectionChanged([[maybe_unused]] const QItemSelection& selected, [[maybe_unused]] const QItemSelection& deselected) @@ -146,36 +139,28 @@ namespace EMotionFX if (skeletonModel) { const QModelIndexList selectedRows = skeletonModel->GetSelectionModel().selectedRows(); - Reinit(selectedRows); } + Reinit(); } void SkeletonModelJointWidget::OnDataChanged([[maybe_unused]] const QModelIndex& topLeft, [[maybe_unused]] const QModelIndex& bottomRight, [[maybe_unused]] const QVector& roles) { - Reinit(m_selectedModelIndices); + Reinit(); } void SkeletonModelJointWidget::OnModelReset() { - Reinit(QModelIndexList()); + Reinit(); } Actor* SkeletonModelJointWidget::GetActor() const { Actor* actor = nullptr; - if (!m_selectedModelIndices.empty()) + SkeletonModel* skeletonModel = nullptr; + SkeletonOutlinerRequestBus::BroadcastResult(skeletonModel, &SkeletonOutlinerRequests::GetModel); + if (skeletonModel) { - actor = m_selectedModelIndices[0].data(SkeletonModel::ROLE_ACTOR_POINTER).value(); - } - - if (!actor) - { - SkeletonModel* skeletonModel = nullptr; - SkeletonOutlinerRequestBus::BroadcastResult(skeletonModel, &SkeletonOutlinerRequests::GetModel); - if (skeletonModel) - { - actor = skeletonModel->GetActor(); - } + actor = skeletonModel->GetActor(); } return actor; } @@ -183,10 +168,24 @@ namespace EMotionFX Node* SkeletonModelJointWidget::GetNode() const { Node* node = nullptr; - if (!m_selectedModelIndices.empty()) + const QModelIndexList& selectedModelIndices = GetSelectedModelIndices(); + if (!selectedModelIndices.empty()) { - node = m_selectedModelIndices[0].data(SkeletonModel::ROLE_POINTER).value(); + node = selectedModelIndices[0].data(SkeletonModel::ROLE_POINTER).value(); } return node; } + + QModelIndexList SkeletonModelJointWidget::GetSelectedModelIndices() const + { + QModelIndexList selectedModelIndices; + SkeletonModel* skeletonModel = nullptr; + SkeletonOutlinerRequestBus::BroadcastResult(skeletonModel, &SkeletonOutlinerRequests::GetModel); + if (skeletonModel) + { + selectedModelIndices = skeletonModel->GetSelectionModel().selectedRows(); + } + + return selectedModelIndices; + } } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/Source/Editor/SkeletonModelJointWidget.h b/Gems/EMotionFX/Code/Source/Editor/SkeletonModelJointWidget.h index 52178b4b55..399615c42a 100644 --- a/Gems/EMotionFX/Code/Source/Editor/SkeletonModelJointWidget.h +++ b/Gems/EMotionFX/Code/Source/Editor/SkeletonModelJointWidget.h @@ -33,13 +33,14 @@ namespace EMotionFX virtual void CreateGUI(); - void Reinit(const QModelIndexList& selectedModelIndices); + void Reinit(); void showEvent(QShowEvent* event) override; protected: Actor* GetActor() const; Node* GetNode() const; + QModelIndexList GetSelectedModelIndices() const; virtual QWidget* CreateContentWidget(QWidget* parent) = 0; virtual QWidget* CreateNoSelectionWidget(QWidget* parent) = 0; virtual void InternalReinit() = 0; @@ -50,7 +51,6 @@ namespace EMotionFX void OnModelReset(); protected: - QModelIndexList m_selectedModelIndices; QLabel* m_jointNameLabel; static int s_jointLabelSpacing; static int s_jointNameSpacing; diff --git a/Gems/EMotionFX/gem.json b/Gems/EMotionFX/gem.json index f00803ec2c..f1734d854d 100644 --- a/Gems/EMotionFX/gem.json +++ b/Gems/EMotionFX/gem.json @@ -5,9 +5,19 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The EMotion FX Animation Gem provides Open 3D Engine's animation system for rigged actors and includes Animation Editor, a tool for creating animated behaviors, simulated objects, and colliders for rigged actors.", - "canonical_tags": ["Gem"], - "user_tags": ["Animation", "Tools", "Simulation"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Animation", + "Tools", + "Simulation" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/animation/emotionfx/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/animation/emotionfx/", + "dependencies": [ + "Atom_RPI", + "LmbrCentral" + ] } diff --git a/Gems/EditorPythonBindings/Code/Source/PythonProxyBus.cpp b/Gems/EditorPythonBindings/Code/Source/PythonProxyBus.cpp index 38b29bcdf2..f9f9580c1a 100644 --- a/Gems/EditorPythonBindings/Code/Source/PythonProxyBus.cpp +++ b/Gems/EditorPythonBindings/Code/Source/PythonProxyBus.cpp @@ -195,7 +195,7 @@ namespace EditorPythonBindings if (!m_handler) { - AZ_Error("python", false, "No EBus connection deteced; missing call or failed call to connect()?"); + AZ_Error("python", false, "No EBus connection detected; missing call or failed call to connect()?"); return false; } diff --git a/Gems/EditorPythonBindings/gem.json b/Gems/EditorPythonBindings/gem.json index fae091a63f..13c5800dd7 100644 --- a/Gems/EditorPythonBindings/gem.json +++ b/Gems/EditorPythonBindings/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Editor Python Bindings Gem provides Python commands for Open 3D Engine Editor functions.", - "canonical_tags": ["Gem"], - "user_tags": ["Scripting", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Scripting", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/python/editor-python-bindings/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/python/editor-python-bindings/", + "dependencies": [] } diff --git a/Gems/ExpressionEvaluation/gem.json b/Gems/ExpressionEvaluation/gem.json index 2f555916a5..6bcc666a4d 100644 --- a/Gems/ExpressionEvaluation/gem.json +++ b/Gems/ExpressionEvaluation/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Expression Evaluation Gem provides a method for parsing and executing string expressions in Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Scripting", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Scripting", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/expression-evaluation/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/expression-evaluation/", + "dependencies": [] } diff --git a/Gems/FastNoise/gem.json b/Gems/FastNoise/gem.json index 5ee5bbaf89..ac59fe804e 100644 --- a/Gems/FastNoise/gem.json +++ b/Gems/FastNoise/gem.json @@ -5,9 +5,20 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The FastNoise Gradient Gem uses the third-party, open source FastNoise library to provide a variety of high-performance noise generation algorithms.", - "canonical_tags": ["Gem"], - "user_tags": ["Utility", "Tools", "Design"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Utility", + "Tools", + "Design" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/utility/fast-noise/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/utility/fast-noise/", + "dependencies": [ + "GradientSignal", + "LmbrCentral", + "SurfaceData" + ] } diff --git a/Gems/GameState/gem.json b/Gems/GameState/gem.json index a1f272c290..7bb3cf4214 100644 --- a/Gems/GameState/gem.json +++ b/Gems/GameState/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Game State Gem provides a generic framework to determine and manage game states and game state transitions in Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Gameplay", "Framework", "Scripting"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Gameplay", + "Framework", + "Scripting" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/gameplay/game-state/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/gameplay/game-state/", + "dependencies": [] } diff --git a/Gems/GameStateSamples/gem.json b/Gems/GameStateSamples/gem.json index f0dd82c5fa..be982b9c5b 100644 --- a/Gems/GameStateSamples/gem.json +++ b/Gems/GameStateSamples/gem.json @@ -5,9 +5,25 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Game State Samples Gem provides a set of sample game states (built on top of the Game State Gem), including primary user selection, main menu, level loading, level running, and level paused.", - "canonical_tags": ["Gem"], - "user_tags": ["Gameplay", "Sample", "Assets"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Gameplay", + "Sample", + "Assets" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/gameplay/game-state-samples/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/gameplay/game-state-samples/", + "dependencies": [ + "GameState", + "LocalUser", + "LyShine", + "SaveData", + "MessagePopup", + "LmbrCentral", + "UiBasics", + "LyShineExamples" + ] } diff --git a/Gems/Gestures/gem.json b/Gems/Gestures/gem.json index 4412e58c6d..fcc56b4704 100644 --- a/Gems/Gestures/gem.json +++ b/Gems/Gestures/gem.json @@ -5,9 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Gestures Gem provides detection for common gesture-based input actions on iOS and Android devices.", - "canonical_tags": ["Gem"], - "user_tags": ["Input", "Gameplay", "Scripting"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Input", + "Gameplay", + "Scripting" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/input/gestures/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/input/gestures/", + "dependencies": [ + "Atom_RPI" + ] } diff --git a/Gems/GradientSignal/gem.json b/Gems/GradientSignal/gem.json index 67afb1f819..e87ccfe13a 100644 --- a/Gems/GradientSignal/gem.json +++ b/Gems/GradientSignal/gem.json @@ -5,9 +5,20 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Gradient Signal Gem provides a number of components for generating, modifying, and mixing gradient signals.", - "canonical_tags": ["Gem"], - "user_tags": ["Utility", "Tools", "Design"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Utility", + "Tools", + "Design" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/utility/gradient-signal/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/utility/gradient-signal/", + "dependencies": [ + "SurfaceData", + "ImageProcessingAtom", + "LmbrCentral" + ] } diff --git a/Gems/GraphCanvas/gem.json b/Gems/GraphCanvas/gem.json index ab1e7ac77a..760bd157df 100644 --- a/Gems/GraphCanvas/gem.json +++ b/Gems/GraphCanvas/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Graph Canvas Gem provides a C++ framework for creating custom graphical node based editors for Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Framework", "Tools", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Framework", + "Tools", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/framework/graph-canvas/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/framework/graph-canvas/", + "dependencies": [] } diff --git a/Gems/GraphModel/gem.json b/Gems/GraphModel/gem.json index 62f1effb7c..256de75f6c 100644 --- a/Gems/GraphModel/gem.json +++ b/Gems/GraphModel/gem.json @@ -5,9 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Graph Model Gem provides a generic node graph data model framework for Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Framework", "Tools", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Framework", + "Tools", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/framework/graph-model/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/framework/graph-model/", + "dependencies": [ + "GraphCanvas" + ] } diff --git a/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp b/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp index 523d84f0f5..d33f37a2d7 100644 --- a/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp +++ b/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp @@ -49,7 +49,6 @@ namespace HttpRequestor { m_thread.join(); } - } void Manager::AddRequest(Parameters && httpRequestParameters) diff --git a/Gems/HttpRequestor/gem.json b/Gems/HttpRequestor/gem.json index e5eb8d6f44..eb1a112b0e 100644 --- a/Gems/HttpRequestor/gem.json +++ b/Gems/HttpRequestor/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The HTTP Requestor Gem provides functionality to make asynchronous HTTP/HTTPS requests and return data through a user-provided call back function.", - "canonical_tags": ["Gem"], - "user_tags": ["Network", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Network", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/network/http-requestor/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/network/http-requestor/", + "dependencies": [] } diff --git a/Gems/ImGui/gem.json b/Gems/ImGui/gem.json index deaac9925a..c1d89d1728 100644 --- a/Gems/ImGui/gem.json +++ b/Gems/ImGui/gem.json @@ -5,9 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Immediate Mode GUI Gem provides the 3rdParty library IMGUI which can be used to create run time immediate mode overlays for debugging and profiling information in Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Debug", "Rendering", "Framework"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Debug", + "Rendering", + "Framework" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/debug/imgui/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/debug/imgui/", + "dependencies": [ + "LmbrCentral" + ] } diff --git a/Gems/InAppPurchases/gem.json b/Gems/InAppPurchases/gem.json index ab43075896..1f1debd5fb 100644 --- a/Gems/InAppPurchases/gem.json +++ b/Gems/InAppPurchases/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The In-App Purchases Gem provides functionality for in app purchases for iOS and Android.", - "canonical_tags": ["Gem"], - "user_tags": ["SDK", "Network"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "SDK", + "Network" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/sdk/in-app-purchases/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/sdk/in-app-purchases/", + "dependencies": [] } diff --git a/Gems/LandscapeCanvas/gem.json b/Gems/LandscapeCanvas/gem.json index 63a80ec57e..ce0c64b75d 100644 --- a/Gems/LandscapeCanvas/gem.json +++ b/Gems/LandscapeCanvas/gem.json @@ -5,9 +5,23 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Landscape Canvas Gem provides the Landscape Canvas editor, a node-based graph tool for authoring workflows to populate landscape with dynamic vegetation.", - "canonical_tags": ["Gem"], - "user_tags": ["Environment", "Design", "Tools"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Environment", + "Design", + "Tools" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/environment/landscape-canvas/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/environment/landscape-canvas/", + "dependencies": [ + "GraphModel", + "GradientSignal", + "SurfaceData", + "Vegetation", + "LmbrCentral", + "GraphCanvas" + ] } diff --git a/Gems/LmbrCentral/gem.json b/Gems/LmbrCentral/gem.json index b6442bfd06..36de4c4ed3 100644 --- a/Gems/LmbrCentral/gem.json +++ b/Gems/LmbrCentral/gem.json @@ -5,10 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The O3DE Core (LmbrCentral) Gem provides required code and assets for running Open 3D Engine Editor.", - "canonical_tags": ["Gem"], - "user_tags": ["Core", "Framework", "Assets"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Core", + "Framework", + "Assets" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/core/lmbr-central/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/core/lmbr-central/", + "dependencies": [] } - diff --git a/Gems/LocalUser/gem.json b/Gems/LocalUser/gem.json index dcf4d44abb..f86e6e1bf6 100644 --- a/Gems/LocalUser/gem.json +++ b/Gems/LocalUser/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Local User Gem provides functionality for mapping local user ids to local player slots and managing local user profiles.", - "canonical_tags": ["Gem"], - "user_tags": ["Input", "Gameplay", "Scripting"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Input", + "Gameplay", + "Scripting" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/input/local-user/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/input/local-user/", + "dependencies": [] } diff --git a/Gems/LyShine/gem.json b/Gems/LyShine/gem.json index e0fdb860e5..e2da8afa4d 100644 --- a/Gems/LyShine/gem.json +++ b/Gems/LyShine/gem.json @@ -5,9 +5,24 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The LyShine Gem provides the runtime UI system and creation tools for Open 3D Engine projects.", - "canonical_tags": ["Gem"], - "user_tags": ["UI", "Tools", "Framework"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "UI", + "Tools", + "Framework" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/ui/lyshine/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/ui/lyshine/", + "dependencies": [ + "LmbrCentral", + "Atom_RPI", + "Atom", + "Atom_Bootstrap", + "AtomFont", + "TextureAtlas", + "AtomToolsFramework" + ] } diff --git a/Gems/LyShineExamples/gem.json b/Gems/LyShineExamples/gem.json index a411bfb363..122273f6d9 100644 --- a/Gems/LyShineExamples/gem.json +++ b/Gems/LyShineExamples/gem.json @@ -5,9 +5,19 @@ "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The LyShine Examples Gem provides example code and assets for LyShine, the runtime UI system and editor for Open 3D Engine projects.", - "canonical_tags": ["Gem"], - "user_tags": ["UI", "Sample", "Assets"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "UI", + "Sample", + "Assets" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/ui/lyshine-examples/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/ui/lyshine-examples/", + "dependencies": [ + "LmbrCentral", + "LyShine" + ] } diff --git a/Gems/Maestro/gem.json b/Gems/Maestro/gem.json index 6ee989aa55..5149df7c14 100644 --- a/Gems/Maestro/gem.json +++ b/Gems/Maestro/gem.json @@ -5,10 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Maestro Cinematics Gem provides Track View, Open 3D Engine's animated sequence and cinematics editor.", - "canonical_tags": ["Gem"], - "user_tags": ["Animation", "Tools", "Scripting"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Animation", + "Tools", + "Scripting" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/animation/maestro/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/animation/maestro/", + "dependencies": [ + "LmbrCentral" + ] } - diff --git a/Gems/MessagePopup/gem.json b/Gems/MessagePopup/gem.json index 2560b8869a..05d36bc2df 100644 --- a/Gems/MessagePopup/gem.json +++ b/Gems/MessagePopup/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Message Popup Gem provides an example implementation of popup messages using LyShine in Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Gameplay", "Sample"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Gameplay", + "Sample" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/ui/message-popup/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/ui/message-popup/", + "dependencies": [] } diff --git a/Gems/Metastream/gem.json b/Gems/Metastream/gem.json index 429d174cf5..862b17dd1d 100644 --- a/Gems/Metastream/gem.json +++ b/Gems/Metastream/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Metastream Gem provides functionality for an HTTP server that allows broadcasters to customize game streams with overlays of statistics and event data from a game session.", - "canonical_tags": ["Gem"], - "user_tags": ["Gameplay", "Network", "Framework"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Gameplay", + "Network", + "Framework" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/network/twitch/metastream/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/network/twitch/metastream/", + "dependencies": [] } diff --git a/Gems/Microphone/gem.json b/Gems/Microphone/gem.json index cc47cbcd7e..68492ea786 100644 --- a/Gems/Microphone/gem.json +++ b/Gems/Microphone/gem.json @@ -5,9 +5,17 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Microphone Gem provides support for audio input through microphones.", - "canonical_tags": ["Gem"], - "user_tags": ["Audio", "Input"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Audio", + "Input" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/audio/microphone/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/audio/microphone/", + "dependencies": [ + "AudioSystem" + ] } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index d09de1ad0d..743315e8bc 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -199,6 +199,8 @@ namespace Multiplayer friend class NetworkEntityManager; friend class EntityReplicationManager; + + friend class HierarchyTests; }; bool NetworkRoleHasController(NetEntityRole networkRole); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h new file mode 100644 index 0000000000..45119a89e3 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h @@ -0,0 +1,48 @@ +/* + * 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 + * + */ + +#pragma once + +#include + +namespace Multiplayer +{ + using NetworkHierarchyChangedEvent = AZ::Event; + using NetworkHierarchyLeaveEvent = AZ::Event<>; + + class NetworkHierarchyRequests + : public AZ::ComponentBus + { + public: + //! @returns true if the entity a hierarchical component attached should be considered for inclusion in a hierarchy + //! this should return false when an entity is deactivating + virtual bool IsHierarchyEnabled() const = 0; + + //! @returns hierarchical entities, the first element is the top level root + virtual AZStd::vector GetHierarchicalEntities() const = 0; + + //! @returns the top level root of a hierarchy, or nullptr if this entity is not in a hierarchy + virtual AZ::Entity* GetHierarchicalRoot() const = 0; + + //! @return true if this entity is a child entity within a hierarchy + virtual bool IsHierarchicalChild() const = 0; + + //! @return true if this entity is the top level root of a hierarchy + virtual bool IsHierarchicalRoot() const = 0; + + //! Binds the provided NetworkHierarchyChangedEvent handler to a Network Hierarchy component. + //! @param handler the handler to invoke when the entity's network hierarchy has been modified. + virtual void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) = 0; + + //! Binds the provided NetworkHierarchyLeaveEvent handler to a Network Hierarchy component. + //! @param handler the handler to invoke when the entity left its network hierarchy. + virtual void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) = 0; + }; + + typedef AZ::EBus NetworkHierarchyRequestBus; +} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h new file mode 100644 index 0000000000..544fb3d6cb --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h @@ -0,0 +1,86 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace Multiplayer +{ + class NetworkHierarchyRootComponent; + + //! @class NetworkHierarchyChildComponent + //! @brief Component that declares network dependency on the parent of this entity + /* + * The parent of this entity should have @NetworkHierarchyChildComponent (or @NetworkHierarchyRootComponent). + * A network hierarchy is a collection of entities with one @NetworkHierarchyRootComponent at the top parent + * and one or more @NetworkHierarchyChildComponent on its child entities. + */ + class NetworkHierarchyChildComponent final + : public NetworkHierarchyChildComponentBase + , public NetworkHierarchyRequestBus::Handler + { + friend class NetworkHierarchyRootComponent; + + public: + AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyChildComponent, s_networkHierarchyChildComponentConcreteUuid, Multiplayer::NetworkHierarchyChildComponentBase); + + static void Reflect(AZ::ReflectContext* context); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + + NetworkHierarchyChildComponent(); + + //! NetworkHierarchyChildComponentBase overrides. + //! @{ + void OnInit() override; + void OnActivate(EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(EntityIsMigrating entityIsMigrating) override; + //! @} + + //! NetworkHierarchyRequestBus overrides. + //! @{ + bool IsHierarchyEnabled() const override; + bool IsHierarchicalChild() const override; + bool IsHierarchicalRoot() const override { return false; } + AZ::Entity* GetHierarchicalRoot() const override; + AZStd::vector GetHierarchicalEntities() const override; + void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) override; + void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override; + //! @} + + protected: + //! Used by @NetworkHierarchyRootComponent + void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot); + + private: + AZ::ChildChangedEvent::Handler m_childChangedHandler; + AZ::ParentChangedEvent::Handler m_parentChangedHandler; + + void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child); + void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId parent); + + //! Points to the top level root. + AZ::Entity* m_rootEntity = nullptr; + + AZ::Event::Handler m_hierarchyRootNetIdChanged; + void OnHierarchyRootNetIdChanged(NetEntityId rootNetId); + + NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent; + NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent; + + //! Set to false when deactivating or otherwise not to be included in hierarchy considerations. + bool m_isHierarchyEnabled = true; + + void NotifyChildrenHierarchyDisbanded(); + }; +} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h new file mode 100644 index 0000000000..4c9f94c004 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h @@ -0,0 +1,100 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace Multiplayer +{ + //! @class NetworkHierarchyRootComponent + //! @brief Component that declares the top level entity of a network hierarchy. + /* + * Call @GetHierarchicalEntities to get the list of hierarchical entities. + * A network hierarchy is meant to be a small group of entities. You can control the maximum supported size of + * a network hierarchy by modifying CVar @bg_hierarchyEntityMaxLimit. + * + * A root component marks either a top most root of a hierarchy, or an inner root of an attach hierarchy. + */ + class NetworkHierarchyRootComponent final + : public NetworkHierarchyRootComponentBase + , public NetworkHierarchyRequestBus::Handler + { + friend class NetworkHierarchyChildComponent; + public: + AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyRootComponent, s_networkHierarchyRootComponentConcreteUuid, Multiplayer::NetworkHierarchyRootComponentBase); + + static void Reflect(AZ::ReflectContext* context); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + + NetworkHierarchyRootComponent(); + + //! NetworkHierarchyRootComponentBase overrides. + //! @{ + void OnInit() override; + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + //! @} + + //! NetworkHierarchyRequestBus overrides. + //! @{ + bool IsHierarchyEnabled() const override; + bool IsHierarchicalRoot() const override; + bool IsHierarchicalChild() const override; + AZStd::vector GetHierarchicalEntities() const override; + AZ::Entity* GetHierarchicalRoot() const override; + void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) override; + void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override; + //! @} + + protected: + void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot); + + private: + AZ::ChildChangedEvent::Handler m_childChangedHandler; + AZ::ParentChangedEvent::Handler m_parentChangedHandler; + + void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child); + void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId parent); + + NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent; + NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent; + + //! Points to the top level root, if this root is an inner root in this hierarchy. + AZ::Entity* m_rootEntity = nullptr; + + AZStd::vector m_hierarchicalEntities; + + //! Rebuilds hierarchy starting from this root component's entity. + void RebuildHierarchy(); + + //! @param underEntity Walk the child entities that belong to @underEntity and consider adding them to the hierarchy + //! @param currentEntityCount The total number of entities in the hierarchy prior to calling this method, + //! used to avoid adding too many entities to the hierarchy while walking recursively the relevant entities. + //! @currentEntityCount will be modified to reflect the total entity count upon completion of this method. + //! @returns false if an attempt was made to go beyond the maximum supported hierarchy size, true otherwise + bool RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount); + + //! @param entity Add the child entity and any of its relevant children to the hierarchy + //! @param currentEntityCount The total number of entities in the hierarchy prior to calling this method, + //! used to avoid adding too many entities to the hierarchy while walking recursively the relevant entities. + //! @currentEntityCount will be modified to reflect the total entity count upon completion of this method. + //! @returns false if an attempt was made to go beyond the maximum supported hierarchy size, true otherwise + bool RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount); + + void SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity); + + //! Set to false when deactivating or otherwise not to be included in hierarchy considerations. + bool m_isHierarchyEnabled = true; + }; +} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h index a018279a79..35bf6e9f50 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h @@ -31,9 +31,11 @@ namespace Multiplayer private: void OnPreRender(float deltaTime); void OnCorrection(); - + void OnParentChanged(NetEntityId parentId); + EntityPreRenderEvent::Handler m_entityPreRenderEventHandler; EntityCorrectionEvent::Handler m_entityCorrectionEventHandler; + AZ::Event::Handler m_parentChangedEventHandler; Multiplayer::HostFrameId m_targetHostFrameId = HostFrameId(0); }; @@ -49,7 +51,9 @@ namespace Multiplayer private: void OnTransformChangedEvent(const AZ::Transform& worldTm); + void OnParentIdChangedEvent(AZ::EntityId oldParent, AZ::EntityId newParent); AZ::TransformChangedEvent::Handler m_transformChangedHandler; + AZ::ParentChangedEvent::Handler m_parentIdChangedHandler; }; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h index 8a5fec869c..5bcf038eff 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace Multiplayer { @@ -75,6 +76,15 @@ namespace Multiplayer const AZ::Transform& transform ) = 0; + //! Requests a network spawnable to instantiate at a given transform + //! This is an async function. The instantiated entities are not available immediately but will be constructed by the spawnable system + //! The spawnable ticket has to be kept for the whole lifetime of the entities + //! @param netSpawnable the network spawnable to spawn + //! @param transform the transform where the spawnable should be spawned + //! @return the ticket for managing the spawned entities + [[nodiscard]] virtual AZStd::unique_ptr RequestNetSpawnableInstantiation( + const AZ::Data::Asset& netSpawnable, const AZ::Transform& transform) = 0; + //! Configures new networked entity //! @param netEntity the entity to setup //! @param prefabEntryId the name of the spawnable the entity originated from diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.xml new file mode 100644 index 0000000000..46523d3724 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml new file mode 100644 index 0000000000..0f33e1f642 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp new file mode 100644 index 0000000000..3124eccaa8 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp @@ -0,0 +1,222 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + void NetworkHierarchyChildComponent::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class() + ->Version(1); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class( + "Network Hierarchy Child", "Declares a network dependency on the root of this hierarchy.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Category, "Multiplayer") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) + ; + } + } + NetworkHierarchyChildComponentBase::Reflect(context); + } + + void NetworkHierarchyChildComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("NetworkTransformComponent")); + } + + void NetworkHierarchyChildComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent")); + } + + void NetworkHierarchyChildComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent")); + incompatible.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent")); + } + + NetworkHierarchyChildComponent::NetworkHierarchyChildComponent() + : m_childChangedHandler([this](AZ::ChildChangeType type, AZ::EntityId child) { OnChildChanged(type, child); }) + , m_parentChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId parent) { OnParentChanged(oldParent, parent); }) + , m_hierarchyRootNetIdChanged([this](NetEntityId rootNetId) {OnHierarchyRootNetIdChanged(rootNetId); }) + { + + } + + void NetworkHierarchyChildComponent::OnInit() + { + } + + void NetworkHierarchyChildComponent::OnActivate([[maybe_unused]] EntityIsMigrating entityIsMigrating) + { + m_isHierarchyEnabled = true; + + HierarchyRootAddEvent(m_hierarchyRootNetIdChanged); + NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId()); + + if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent()) + { + transformComponent->BindChildChangedEventHandler(m_childChangedHandler); + transformComponent->BindParentChangedEventHandler(m_parentChangedHandler); + } + } + + void NetworkHierarchyChildComponent::OnDeactivate([[maybe_unused]] EntityIsMigrating entityIsMigrating) + { + m_isHierarchyEnabled = false; + + if (m_rootEntity) + { + if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent()) + { + root->RebuildHierarchy(); + } + } + + NotifyChildrenHierarchyDisbanded(); + + NetworkHierarchyRequestBus::Handler::BusDisconnect(); + } + + bool NetworkHierarchyChildComponent::IsHierarchyEnabled() const + { + return m_isHierarchyEnabled; + } + + bool NetworkHierarchyChildComponent::IsHierarchicalChild() const + { + return GetHierarchyRoot() != InvalidNetEntityId; + } + + AZ::Entity* NetworkHierarchyChildComponent::GetHierarchicalRoot() const + { + return m_rootEntity; + } + + AZStd::vector NetworkHierarchyChildComponent::GetHierarchicalEntities() const + { + if (m_rootEntity) + { + return m_rootEntity->FindComponent()->GetHierarchicalEntities(); + } + + return {}; + } + + void NetworkHierarchyChildComponent::BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyChangedEvent); + } + + void NetworkHierarchyChildComponent::BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyLeaveEvent); + } + + void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot) + { + m_rootEntity = hierarchyRoot; + if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority) + { + NetworkHierarchyChildComponentController* controller = static_cast(GetController()); + if (m_rootEntity) + { + const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(m_rootEntity->GetId()); + controller->SetHierarchyRoot(netRootId); + + m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId()); + } + else + { + controller->SetHierarchyRoot(InvalidNetEntityId); + + m_networkHierarchyLeaveEvent.Signal(); + } + } + + if (m_rootEntity == nullptr) + { + NotifyChildrenHierarchyDisbanded(); + } + } + + void NetworkHierarchyChildComponent::OnChildChanged([[maybe_unused]] AZ::ChildChangeType type, [[maybe_unused]] AZ::EntityId child) + { + if (m_rootEntity) + { + if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent()) + { + root->RebuildHierarchy(); + } + } + } + + void NetworkHierarchyChildComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, [[maybe_unused]] AZ::EntityId parent) + { + if (m_rootEntity) + { + if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent()) + { + root->RebuildHierarchy(); + } + } + } + + void NetworkHierarchyChildComponent::OnHierarchyRootNetIdChanged(NetEntityId rootNetId) + { + ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(rootNetId); + if (rootHandle.Exists()) + { + AZ::Entity* newRoot = rootHandle.GetEntity(); + if (m_rootEntity != newRoot) + { + m_rootEntity = newRoot; + m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId()); + } + } + else + { + m_isHierarchyEnabled = false; + m_rootEntity = nullptr; + m_networkHierarchyLeaveEvent.Signal(); + } + } + + void NetworkHierarchyChildComponent::NotifyChildrenHierarchyDisbanded() + { + AZStd::vector allChildren; + AZ::TransformBus::EventResult(allChildren, GetEntityId(), &AZ::TransformBus::Events::GetChildren); + for (const AZ::EntityId& childEntityId : allChildren) + { + if (const AZ::Entity* childEntity = AZ::Interface::Get()->FindEntity(childEntityId)) + { + if (auto* hierarchyChildComponent = childEntity->FindComponent()) + { + hierarchyChildComponent->SetTopLevelHierarchyRootEntity(nullptr); + } + else if (auto* hierarchyRootComponent = childEntity->FindComponent()) + { + hierarchyRootComponent->SetTopLevelHierarchyRootEntity(nullptr); + } + } + } + } +} diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp new file mode 100644 index 0000000000..4a5e9ce8d2 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -0,0 +1,329 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +AZ_CVAR(uint32_t, bg_hierarchyEntityMaxLimit, 16, nullptr, AZ::ConsoleFunctorFlags::Null, + "Maximum allowed size of network entity hierarchies, including top level entity."); + +namespace Multiplayer +{ + void NetworkHierarchyRootComponent::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class() + ->Version(1); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class( + "Network Hierarchy Root", "Marks the entity as the root of an entity hierarchy.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Category, "Multiplayer") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) + ; + } + } + NetworkHierarchyRootComponentBase::Reflect(context); + } + + void NetworkHierarchyRootComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("NetworkTransformComponent")); + } + + void NetworkHierarchyRootComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent")); + } + + void NetworkHierarchyRootComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent")); + incompatible.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent")); + } + + NetworkHierarchyRootComponent::NetworkHierarchyRootComponent() + : m_childChangedHandler([this](AZ::ChildChangeType type, AZ::EntityId child) { OnChildChanged(type, child); }) + , m_parentChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId parent) { OnParentChanged(oldParent, parent); }) + { + } + + void NetworkHierarchyRootComponent::OnInit() + { + } + + void NetworkHierarchyRootComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + m_isHierarchyEnabled = true; + m_hierarchicalEntities.push_back(GetEntity()); + + NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId()); + + if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent()) + { + transformComponent->BindChildChangedEventHandler(m_childChangedHandler); + transformComponent->BindParentChangedEventHandler(m_parentChangedHandler); + } + } + + void NetworkHierarchyRootComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + m_isHierarchyEnabled = false; + + if (m_rootEntity) + { + // Tell parent to re-build the hierarchy + if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent()) + { + root->RebuildHierarchy(); + } + } + else + { + // Notify children that the hierarchy is disbanding + AZStd::vector allChildren; + AZ::TransformBus::EventResult(allChildren, GetEntityId(), &AZ::TransformBus::Events::GetChildren); + + for (const AZ::EntityId& childEntityId : allChildren) + { + if (const AZ::Entity* childEntity = AZ::Interface::Get()->FindEntity(childEntityId)) + { + SetRootForEntity(nullptr, childEntity); + } + } + } + + m_childChangedHandler.Disconnect(); + m_parentChangedHandler.Disconnect(); + + NetworkHierarchyRequestBus::Handler::BusDisconnect(); + + m_hierarchicalEntities.clear(); + m_rootEntity = nullptr; + } + + bool NetworkHierarchyRootComponent::IsHierarchyEnabled() const + { + return m_isHierarchyEnabled; + } + + bool NetworkHierarchyRootComponent::IsHierarchicalRoot() const + { + return GetHierarchyRoot() == InvalidNetEntityId; + } + + bool NetworkHierarchyRootComponent::IsHierarchicalChild() const + { + return !IsHierarchicalRoot(); + } + + AZStd::vector NetworkHierarchyRootComponent::GetHierarchicalEntities() const + { + return m_hierarchicalEntities; + } + + AZ::Entity* NetworkHierarchyRootComponent::GetHierarchicalRoot() const + { + if (m_rootEntity) + { + return m_rootEntity; + } + + return GetEntity(); + } + + void NetworkHierarchyRootComponent::BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyChangedEvent); + } + + void NetworkHierarchyRootComponent::BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyLeaveEvent); + } + + void NetworkHierarchyRootComponent::OnChildChanged([[maybe_unused]] AZ::ChildChangeType type, [[maybe_unused]] AZ::EntityId child) + { + if (IsHierarchicalRoot()) + { + // Parent-child notifications are not reliable enough to avoid duplicate notifications, + // so we will rebuild from scratch to avoid duplicate entries in @m_hierarchicalEntities. + RebuildHierarchy(); + } + else if (NetworkHierarchyRootComponent* root = GetHierarchicalRoot()->FindComponent()) + { + root->RebuildHierarchy(); + } + } + + void NetworkHierarchyRootComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent) + { + // If the parent is part of a hierarchy, it will detect this entity as a new child and rebuild hierarchy. + // Thus, we only need to take care of a case when the parent is not part of a hierarchy, + // in which case, this entity will be a new root of a new hierarchy. + + if (AZ::Entity* parentEntity = AZ::Interface::Get()->FindEntity(newParent)) + { + if (parentEntity->FindComponent() == nullptr && + parentEntity->FindComponent() == nullptr) + { + RebuildHierarchy(); + } + else + { + m_hierarchicalEntities.clear(); + } + } + else + { + // Detached from parent + RebuildHierarchy(); + } + } + + void NetworkHierarchyRootComponent::RebuildHierarchy() + { + AZStd::vector previousEntities; + m_hierarchicalEntities.swap(previousEntities); + + m_hierarchicalEntities.push_back(GetEntity()); // Add the root. + + uint32_t currentEntityCount = aznumeric_cast(m_hierarchicalEntities.size()); + RecursiveAttachHierarchicalEntities(GetEntityId(), currentEntityCount); + + bool hierarchyChanged = false; + + // Send out join and leave events. + for (AZ::Entity* currentEntity : m_hierarchicalEntities) + { + const auto prevEntityIterator = AZStd::find(previousEntities.begin(), previousEntities.end(), currentEntity); + if (prevEntityIterator != previousEntities.end()) + { + // This entity was here before the build of the hierarchy. + previousEntities.erase(prevEntityIterator); + } + else + { + // This is a newly added entity to the network hierarchy. + hierarchyChanged = true; + SetRootForEntity(GetEntity(), currentEntity); + } + } + + // These entities were removed since last rebuild. + for (const AZ::Entity* previousEntity : previousEntities) + { + SetRootForEntity(nullptr, previousEntity); + } + + if (!previousEntities.empty()) + { + hierarchyChanged = true; + } + + if (hierarchyChanged) + { + m_networkHierarchyChangedEvent.Signal(GetEntityId()); + } + } + + void NetworkHierarchyRootComponent::SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity) + { + if (auto* hierarchyChildComponent = childEntity->FindComponent()) + { + hierarchyChildComponent->SetTopLevelHierarchyRootEntity(root); + } + else if (auto* hierarchyRootComponent = childEntity->FindComponent()) + { + hierarchyRootComponent->SetTopLevelHierarchyRootEntity(root); + } + } + + bool NetworkHierarchyRootComponent::RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount) + { + AZStd::vector allChildren; + AZ::TransformBus::EventResult(allChildren, underEntity, &AZ::TransformBus::Events::GetChildren); + + for (const AZ::EntityId& newChildId : allChildren) + { + if (!RecursiveAttachHierarchicalChild(newChildId, currentEntityCount)) + { + return false; + } + } + + return true; + } + + bool NetworkHierarchyRootComponent::RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount) + { + if (currentEntityCount >= bg_hierarchyEntityMaxLimit) + { + AZLOG_WARN("Entity %s is trying to build a network hierarchy that is too large. bg_hierarchyEntityMaxLimit is currently set to (%u)", + GetEntity()->GetName().c_str(), static_cast(bg_hierarchyEntityMaxLimit)); + return false; + } + + if (AZ::Entity* childEntity = AZ::Interface::Get()->FindEntity(entity)) + { + auto* hierarchyChildComponent = childEntity->FindComponent(); + auto* hierarchyRootComponent = childEntity->FindComponent(); + + if ((hierarchyChildComponent && hierarchyChildComponent->IsHierarchyEnabled()) || + (hierarchyRootComponent && hierarchyRootComponent->IsHierarchyEnabled())) + { + m_hierarchicalEntities.push_back(childEntity); + ++currentEntityCount; + + if (!RecursiveAttachHierarchicalEntities(entity, currentEntityCount)) + { + return false; + } + } + } + + return true; + } + + void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot) + { + m_rootEntity = hierarchyRoot; + + if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority) + { + NetworkHierarchyChildComponentController* controller = static_cast(GetController()); + if (hierarchyRoot) + { + const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetId()); + controller->SetHierarchyRoot(netRootId); + } + else + { + controller->SetHierarchyRoot(InvalidNetEntityId); + } + } + + if (m_rootEntity == nullptr) + { + // We lost the parent hierarchical entity, so as a root we need to re-build our own hierarchy. + RebuildHierarchy(); + } + } +} diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index 1d9fee16e0..d284f100ff 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -28,6 +28,7 @@ namespace Multiplayer NetworkTransformComponent::NetworkTransformComponent() : m_entityPreRenderEventHandler([this](float deltaTime) { OnPreRender(deltaTime); }) , m_entityCorrectionEventHandler([this]() { OnCorrection(); }) + , m_parentChangedEventHandler([this](NetEntityId parentId) { OnParentChanged(parentId); }) { ; } @@ -41,6 +42,7 @@ namespace Multiplayer { GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler); GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler); + ParentEntityIdAddEvent(m_parentChangedEventHandler); } void NetworkTransformComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) @@ -97,10 +99,26 @@ namespace Multiplayer } } + void NetworkTransformComponent::OnParentChanged(NetEntityId parentId) + { + const ConstNetworkEntityHandle parentEntityHandle = GetNetworkEntityManager()->GetEntity(parentId); + if (parentEntityHandle.Exists()) + { + if (const AZ::Entity* parentEntity = parentEntityHandle.GetEntity()) + { + GetEntity()->GetTransform()->SetParent(parentEntity->GetId()); + } + } + else + { + GetEntity()->GetTransform()->SetParent(AZ::EntityId()); + } + } NetworkTransformComponentController::NetworkTransformComponentController(NetworkTransformComponent& parent) : NetworkTransformComponentControllerBase(parent) , m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform& worldTm) { OnTransformChangedEvent(worldTm); }) + , m_parentIdChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId newParent) { OnParentIdChangedEvent(oldParent, newParent); }) { ; } @@ -109,6 +127,9 @@ namespace Multiplayer { GetParent().GetTransformComponent()->BindTransformChangedEventHandler(m_transformChangedHandler); OnTransformChangedEvent(GetParent().GetTransformComponent()->GetWorldTM()); + + GetParent().GetTransformComponent()->BindParentChangedEventHandler(m_parentIdChangedHandler); + OnParentIdChangedEvent(AZ::EntityId(), GetParent().GetTransformComponent()->GetParentId()); } void NetworkTransformComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) @@ -122,4 +143,14 @@ namespace Multiplayer SetTranslation(worldTm.GetTranslation()); SetScale(worldTm.GetUniformScale()); } + + void NetworkTransformComponentController::OnParentIdChangedEvent([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent) + { + AZ::Entity* parentEntity = AZ::Interface::Get()->FindEntity(newParent); + if (parentEntity) + { + const ConstNetworkEntityHandle parentHandle(parentEntity, GetNetworkEntityTracker()); + SetParentEntityId(parentHandle.GetNetEntityId()); + } + } } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp index 1ad7be1a0a..030964d81c 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp @@ -6,13 +6,14 @@ * */ +#include +#include +#include +#include #include #include #include -#include #include -#include -#include namespace Multiplayer { @@ -23,7 +24,6 @@ namespace Multiplayer AzNetworking::NetworkingSystemComponent::CreateDescriptor(), MultiplayerSystemComponent::CreateDescriptor(), NetBindComponent::CreateDescriptor(), - NetBindMarkerComponent::CreateDescriptor(), NetworkSpawnableHolderComponent::CreateDescriptor(), }); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index 482d3a1ee8..b45c05cda7 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -75,6 +75,8 @@ namespace Multiplayer void EntityReplicationManager::ActivatePendingEntities() { + AZStd::vector notReadyEntities; + const AZ::TimeMs endTimeMs = AZ::GetElapsedTimeMs() + m_entityActivationTimeSliceMs; while (!m_entitiesPendingActivation.empty()) { @@ -83,7 +85,14 @@ namespace Multiplayer EntityReplicator* entityReplicator = GetEntityReplicator(entityId); if (entityReplicator && !entityReplicator->IsMarkedForRemoval()) { - entityReplicator->ActivateNetworkEntity(); + if (entityReplicator->IsReadyToActivate()) + { + entityReplicator->ActivateNetworkEntity(); + } + else + { + notReadyEntities.push_back(entityId); + } } if (m_entityActivationTimeSliceMs > AZ::TimeMs{ 0 } && AZ::GetElapsedTimeMs() > endTimeMs) { @@ -91,6 +100,11 @@ namespace Multiplayer break; } } + + for (NetEntityId netEntityId : notReadyEntities) + { + m_entitiesPendingActivation.push_back(netEntityId); + } } void EntityReplicationManager::SendUpdates(AZ::TimeMs hostTimeMs) @@ -249,15 +263,15 @@ namespace Multiplayer void EntityReplicationManager::SendEntityUpdates(AZ::TimeMs hostTimeMs) { EntityReplicatorList toSendList = GenerateEntityUpdateList(); - + AZLOG(NET_ReplicationInfo, "Sending %zd updates from %d to %d", toSendList.size(), (uint8_t)GetNetworkEntityManager()->GetHostId(), (uint8_t)GetRemoteHostId()); - + // prep a replication record for send, at this point, everything needs to be sent for (EntityReplicator* replicator : toSendList) { replicator->GetPropertyPublisher()->PrepareSerialization(); } - + // While our to send list is not empty, build up another packet to send do { @@ -524,7 +538,7 @@ namespace Multiplayer bool EntityReplicationManager::HandlePropertyChangeMessage ( - AzNetworking::IConnection* invokingConnection, + AzNetworking::IConnection* invokingConnection, EntityReplicator* entityReplicator, AzNetworking::PacketId packetId, NetEntityId netEntityId, @@ -1137,7 +1151,7 @@ namespace Multiplayer AzNetworking::TrackChangedSerializer outputSerializer(message.m_propertyUpdateData.GetBuffer(), static_cast(message.m_propertyUpdateData.GetSize())); if (!HandlePropertyChangeMessage ( - invokingConnection, + invokingConnection, replicator, AzNetworking::InvalidPacketId, message.m_entityId, diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index 14df1bb028..b684473ea3 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -6,23 +6,25 @@ * */ -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include #include #include @@ -48,7 +50,7 @@ namespace Multiplayer , m_onForwardRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); }) , m_onSendAutonomousRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); }) , m_onForwardAutonomousRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); }) - , m_onEntityStopHandler([this](const ConstNetworkEntityHandle &) { OnEntityRemovedEvent(); }) + , m_onEntityStopHandler([this](const ConstNetworkEntityHandle&) { OnEntityRemovedEvent(); }) , m_proxyRemovalEvent([this] { OnProxyRemovalTimedEvent(); }, AZ::Name("ProxyRemovalTimedEvent")) { if (auto localEnt = m_entityHandle.GetEntity()) @@ -119,12 +121,12 @@ namespace Multiplayer { m_replicationManager.AddReplicatorToPendingSend(*this); m_propertyPublisher = AZStd::make_unique - ( - GetRemoteNetworkRole(), - !RemoteManagerOwnsEntityLifetime() ? PropertyPublisher::OwnsLifetime::True : PropertyPublisher::OwnsLifetime::False, - m_netBindComponent, - *m_connection - ); + ( + GetRemoteNetworkRole(), + !RemoteManagerOwnsEntityLifetime() ? PropertyPublisher::OwnsLifetime::True : PropertyPublisher::OwnsLifetime::False, + m_netBindComponent, + *m_connection + ); m_netBindComponent->AddEntityDirtiedEventHandler(m_onEntityDirtiedHandler); } else @@ -279,7 +281,7 @@ namespace Multiplayer AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) - && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole()); + && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole()); bool isClient = GetRemoteNetworkRole() == NetEntityRole::Client; bool isAutonomous = GetBoundLocalNetworkRole() == NetEntityRole::Autonomous; if (isAuthority || isClient || isAutonomous) @@ -306,9 +308,9 @@ namespace Multiplayer bool EntityReplicator::RemoteManagerOwnsEntityLifetime() const { bool isServer = (GetBoundLocalNetworkRole() == NetEntityRole::Server) - && (GetRemoteNetworkRole() == NetEntityRole::Authority); + && (GetRemoteNetworkRole() == NetEntityRole::Authority); bool isClient = (GetBoundLocalNetworkRole() == NetEntityRole::Client) - || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous); + || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous); return isServer || isClient; } @@ -405,6 +407,62 @@ namespace Multiplayer return m_replicationManager.GetResendTimeoutTimeMs(); } + bool EntityReplicator::IsReadyToActivate() const + { + const AZ::Entity* entity = m_entityHandle.GetEntity(); + AZ_Assert(entity, "Entity replicator entity unexpectedly missing"); + + const NetworkHierarchyChildComponent* hierarchyChildComponent = entity->FindComponent(); + const NetworkHierarchyRootComponent* hierarchyRootComponent = nullptr; + + if (hierarchyChildComponent == nullptr) + { + // Child and root hierarchy components are mutually exclusive + hierarchyRootComponent = entity->FindComponent(); + } + + if ((hierarchyChildComponent && hierarchyChildComponent->IsHierarchicalChild()) + || (hierarchyRootComponent && hierarchyRootComponent->IsHierarchicalChild())) + { + // If hierarchy is enabled for the entity, check if the parent is available + if (const NetworkTransformComponent* networkTransform = entity->FindComponent()) + { + const NetEntityId parentId = networkTransform->GetParentEntityId(); + /* + * For root entities attached to a level, a network parent won't be set. + * In this case, this entity is the root entity of the hierarchy and it will be activated first. + */ + if (parentId != InvalidNetEntityId) + { + ConstNetworkEntityHandle parentHandle = GetNetworkEntityManager()->GetEntity(parentId); + + const AZ::Entity* parentEntity = parentHandle.GetEntity(); + if (parentEntity && parentEntity->GetState() == AZ::Entity::State::Active) + { + AZLOG + ( + NET_HierarchyActivationInfo, + "Hierchical entity %s asking for activation - granted", + entity->GetName().c_str() + ); + return true; + } + + AZLOG + ( + NET_HierarchyActivationInfo, + "Hierchical entity %s asking for activation - waiting on the parent %u", + entity->GetName().c_str(), + aznumeric_cast(parentId) + ); + return false; + } + } + } + + return true; + } + NetworkEntityUpdateMessage EntityReplicator::GenerateUpdatePacket() { if (IsMarkedForRemoval() && OwnsReplicatorLifetime()) // TODO: clean this up diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h index ec4bd8c4f5..e4dc62bc26 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h @@ -36,7 +36,7 @@ namespace Multiplayer { public: EntityReplicator(EntityReplicationManager& replicationManager, AzNetworking::IConnection* connection, NetEntityRole remoteNetworkRole, const ConstNetworkEntityHandle& entityHandle); - virtual ~EntityReplicator(); + ~EntityReplicator() override; NetEntityRole GetBoundLocalNetworkRole() const; NetEntityRole GetRemoteNetworkRole() const; @@ -62,6 +62,8 @@ namespace Multiplayer bool IsDeletionAcknowledged() const; bool WasMigrated() const; void SetWasMigrated(bool wasMigrated); + // If an entity is part of a network hierarchy then it is only ready to activate when its direct parent entity is active. + bool IsReadyToActivate() const; NetworkEntityUpdateMessage GenerateUpdatePacket(); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index f981966ae1..bcd4c9aad8 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -465,8 +465,60 @@ namespace Multiplayer return netEntityId; } - void NetworkEntityManager::OnRootSpawnableAssigned( - [[maybe_unused]] AZ::Data::Asset rootSpawnable, [[maybe_unused]] uint32_t generation) + AZStd::unique_ptr NetworkEntityManager::RequestNetSpawnableInstantiation( + const AZ::Data::Asset& netSpawnable, const AZ::Transform& transform) + { + // Prepare the parameters for the spawning process + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; + optionalArgs.m_priority = AzFramework::SpawnablePriority_High; + + const AZ::Name netSpawnableName = + AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnable.GetId()); + + if (netSpawnableName.IsEmpty()) + { + AZ_Error("NetworkEntityManager", false, + "RequestNetSpawnableInstantiation: Requested spawnable %s doesn't exist in the NetworkSpawnableLibrary. Please make sure it is a network spawnable", + netSpawnable.GetHint().c_str()); + return nullptr; + } + + // Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene + optionalArgs.m_preInsertionCallback = [netSpawnableName, rootTransform = transform] + (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) + { + bool shouldUpdateTransform = (rootTransform.IsClose(AZ::Transform::Identity()) == false); + + for (uint32_t netEntityIndex = 0, entitiesSize = aznumeric_cast(entities.size()); + netEntityIndex < entitiesSize; ++netEntityIndex) + { + AZ::Entity* netEntity = *(entities.begin() + netEntityIndex); + + if (shouldUpdateTransform) + { + AzFramework::TransformComponent* netEntityTransform = + netEntity->FindComponent(); + + AZ::Transform worldTm = netEntityTransform->GetWorldTM(); + worldTm = rootTransform * worldTm; + netEntityTransform->SetWorldTM(worldTm); + } + + PrefabEntityId prefabEntityId; + prefabEntityId.m_prefabName = netSpawnableName; + prefabEntityId.m_entityOffset = netEntityIndex; + AZ::Interface::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority); + } + }; + + // Spawn with the newly created ticket. This allows the calling code to manage the lifetime of the constructed entities + auto ticket = AZStd::make_unique(netSpawnable); + AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*ticket, AZStd::move(optionalArgs)); + return ticket; + } + + void NetworkEntityManager::OnRootSpawnableAssigned(AZ::Data::Asset rootSpawnable, + [[maybe_unused]] uint32_t generation) { auto* multiplayer = GetMultiplayer(); const auto agentType = multiplayer->GetAgentType(); @@ -479,7 +531,6 @@ namespace Multiplayer void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) { - // TODO: Do we need to clear all entities here? auto* multiplayer = GetMultiplayer(); const auto agentType = multiplayer->GetAgentType(); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 9ccc576447..50e6beedad 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -60,6 +60,9 @@ namespace Multiplayer const AZ::Transform& transform ) override; + AZStd::unique_ptr RequestNetSpawnableInstantiation( + const AZ::Data::Asset& netSpawnable, const AZ::Transform& transform) override; + void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override; uint32_t GetEntityCount() const override; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h index 09238acaee..0e632dc7b4 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h @@ -37,6 +37,7 @@ namespace Multiplayer NetworkEntityHandle Get(NetEntityId netEntityId); ConstNetworkEntityHandle Get(NetEntityId netEntityId) const; + //! Returns Net Entity ID for a given AZ Entity ID. NetEntityId Get(const AZ::EntityId& entityId) const; //! Returns true if the netEntityId exists. diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp deleted file mode 100644 index c8abac25cb..0000000000 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include - -namespace Multiplayer -{ - void NetBindMarkerComponent::Reflect(AZ::ReflectContext* context) - { - AZ::SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) - { - serializeContext->Class() - ->Version(1) - ->Field("NetEntityIndex", &NetBindMarkerComponent::m_netEntityIndex) - ->Field("NetSpawnableAsset", &NetBindMarkerComponent::m_networkSpawnableAsset); - } - } - - AzFramework::Spawnable* GetSpawnableFromAsset(AZ::Data::Asset& asset) - { - AzFramework::Spawnable* spawnable = asset.GetAs(); - if (!spawnable) - { - asset = - AZ::Data::AssetManager::Instance().GetAsset(asset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad); - AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(asset); - - spawnable = asset.GetAs(); - } - - return spawnable; - } - - - void NetBindMarkerComponent::Activate() - { - const auto agentType = AZ::Interface::Get()->GetAgentType(); - const bool spawnImmediately = - (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); - - if (spawnImmediately && m_networkSpawnableAsset.GetId().IsValid()) - { - AZ::Transform worldTm = GetEntity()->FindComponent()->GetWorldTM(); - auto preInsertionCallback = - [worldTm = AZStd::move(worldTm), netEntityIndex = m_netEntityIndex, spawnableAssetId = m_networkSpawnableAsset.GetId()] - (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) - { - if (entities.size() == 1) - { - AZ::Entity* netEntity = *entities.begin(); - - auto* transformComponent = netEntity->FindComponent(); - transformComponent->SetWorldTM(worldTm); - - AZ::Name spawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(spawnableAssetId); - PrefabEntityId prefabEntityId; - prefabEntityId.m_prefabName = spawnableName; - prefabEntityId.m_entityOffset = static_cast(netEntityIndex); - AZ::Interface::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority); - } - else - { - AZ_Error("NetBindMarkerComponent", false, "Requested to spawn 1 entity, but received %d", entities.size()); - } - }; - - m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset); - AzFramework::SpawnEntitiesOptionalArgs optionalArgs; - optionalArgs.m_preInsertionCallback = AZStd::move(preInsertionCallback); - AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities( - m_netSpawnTicket, { m_netEntityIndex }, AZStd::move(optionalArgs)); - } - } - - void NetBindMarkerComponent::Deactivate() - { - if(m_netSpawnTicket.IsValid()) - { - AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket); - } - } - - size_t NetBindMarkerComponent::GetNetEntityIndex() const - { - return m_netEntityIndex; - } - - void NetBindMarkerComponent::SetNetEntityIndex(size_t netEntityIndex) - { - m_netEntityIndex = netEntityIndex; - } - - void NetBindMarkerComponent::SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset) - { - m_networkSpawnableAsset = networkSpawnableAsset; - } - - AZ::Data::Asset NetBindMarkerComponent::GetNetworkSpawnableAsset() const - { - return m_networkSpawnableAsset; - } - -} diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h deleted file mode 100644 index dce3252200..0000000000 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 - * - */ - -#pragma once - -#include -#include -#include -#include - -namespace Multiplayer -{ - //! @class NetBindMarkerComponent - //! @brief Component for tracking net entities in the original non-networked spawnable. - class NetBindMarkerComponent final : public AZ::Component - { - public: - AZ_COMPONENT(NetBindMarkerComponent, "{40612C1B-427D-45C6-A2F0-04E16DF5B718}"); - - static void Reflect(AZ::ReflectContext* context); - - NetBindMarkerComponent() = default; - ~NetBindMarkerComponent() override = default; - - //! AZ::Component overrides. - //! @{ - void Activate() override; - void Deactivate() override; - //! @} - - size_t GetNetEntityIndex() const; - void SetNetEntityIndex(size_t val); - - void SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset); - AZ::Data::Asset GetNetworkSpawnableAsset() const; - - private: - AZ::Data::Asset m_networkSpawnableAsset{AZ::Data::AssetLoadBehavior::PreLoad}; - size_t m_netEntityIndex = 0; - AzFramework::EntitySpawnTicket m_netSpawnTicket; - }; -} // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index acfec5eb38..e0990aa785 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -46,7 +45,7 @@ namespace Multiplayer { if (auto* serializeContext = azrtti_cast(context); serializeContext != nullptr) { - serializeContext->Class()->Version(1); + serializeContext->Class()->Version(2); } } @@ -137,8 +136,6 @@ namespace Multiplayer networkSpawnableAsset.Create(networkSpawnable->GetId()); networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad); - size_t netEntitiesIndexCounter = 0; - for (auto* prefabEntity : prefabNetEntities) { Instance* instance = netEntityToInstanceMap[prefabEntity]; @@ -148,30 +145,11 @@ namespace Multiplayer AZ_Assert(netEntity, "Unable to detach entity %s [%s] from the source prefab instance", prefabEntity->GetName().c_str(), entityId.ToString().c_str()); - // Net entity will need a new ID to avoid IDs collision - netEntity->SetId(AZ::Entity::MakeId()); netEntity->InvalidateDependencies(); netEntity->EvaluateDependencies(); // Insert the entity into the target net spawnable netSpawnableEntities.emplace_back(netEntity); - - // Use the old ID for the breadcrumb entity to keep parent-child relationship in the original spawnable - AZ::Entity* breadcrumbEntity = aznew AZ::Entity(entityId, netEntity->GetName()); - breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault()); - - // Marker component is responsible to spawning entities based on the index. - NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent(); - netBindMarkerComponent->SetNetEntityIndex(netEntitiesIndexCounter); - netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset); - - // Copy the transform component from the original entity to have the correct transform and parent-child relationship - AzFramework::TransformComponent* transformComponent = netEntity->FindComponent(); - breadcrumbEntity->CreateComponent(*transformComponent); - - instance->AddEntity(*breadcrumbEntity); - - netEntitiesIndexCounter++; } // Add net spawnable asset holder to the prefab root diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp index c40cb3677b..d33c3274c2 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include namespace Multiplayer { @@ -28,10 +30,32 @@ namespace Multiplayer void NetworkSpawnableHolderComponent::Activate() { + const auto agentType = GetMultiplayer()->GetAgentType(); + const bool shouldSpawnNetEntities = + (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); + + if(shouldSpawnNetEntities) + { + AZ::Transform rootEntityTransform = AZ::Transform::CreateIdentity(); + + AzFramework::TransformComponent* rootEntityTransformComponent = + GetEntity()->FindComponent(); + if (rootEntityTransformComponent) + { + rootEntityTransform = rootEntityTransformComponent->GetWorldTM(); + } + + INetworkEntityManager* networkEntityManager = GetNetworkEntityManager(); + AZ_Assert(networkEntityManager != nullptr, + "Network Entity Manager must be initialized before NetworkSpawnableHolderComponent is activated"); + + m_netSpawnableTicket = networkEntityManager->RequestNetSpawnableInstantiation(m_networkSpawnableAsset, rootEntityTransform); + } } void NetworkSpawnableHolderComponent::Deactivate() { + m_netSpawnableTicket.reset(); } void NetworkSpawnableHolderComponent::SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset) @@ -39,7 +63,7 @@ namespace Multiplayer m_networkSpawnableAsset = networkSpawnableAsset; } - AZ::Data::Asset NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset() + AZ::Data::Asset NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset() const { return m_networkSpawnableAsset; } diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h index d369bbefd3..3836212c48 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace Multiplayer { @@ -33,9 +34,10 @@ namespace Multiplayer //! @} void SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset); - AZ::Data::Asset GetNetworkSpawnableAsset(); + AZ::Data::Asset GetNetworkSpawnableAsset() const; private: AZ::Data::Asset m_networkSpawnableAsset{ AZ::Data::AssetLoadBehavior::PreLoad }; + AZStd::unique_ptr m_netSpawnableTicket; }; } // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp new file mode 100644 index 0000000000..38ea451c85 --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp @@ -0,0 +1,390 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + using namespace testing; + using namespace ::UnitTest; + + /* + * Test NetBindComponent activation. This must work before more complicated tests. + */ + TEST_F(HierarchyTests, On_Client_NetBindComponent_Activate) + { + AZStd::unique_ptr entity = AZStd::make_unique(); + entity->CreateComponent(); + SetupEntity(entity, NetEntityId{ 1 }, NetEntityRole::Client); + entity->Activate(); + + StopEntity(entity); + + entity->Deactivate(); + } + + /* + * Hierarchy test - a child entity on a client delaying activation until its hierarchical parent has been activated + */ + TEST_F(HierarchyTests, On_Client_EntityReplicator_DontActivate_BeforeParent) + { + // Create a child entity that will be tested for activation inside a hierarchy + AZStd::unique_ptr childEntity = AZStd::make_unique(); + CreateEntityWithChildHierarchy(childEntity); + SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client); + // child entity is not activated on purpose here, we are about to test conditional activation check + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(childEntity, NetEntityId{ 1 }); + SetHierarchyRootFieldOnNetworkHierarchyChild(childEntity, NetEntityId{ 1 }); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(childEntity.get(), m_networkEntityTracker.get()); + EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); + entityReplicator.Initialize(childHandle); + + // Entity replicator should not be ready to activate the entity because its parent does not exist + EXPECT_EQ(entityReplicator.IsReadyToActivate(), false); + } + + TEST_F(HierarchyTests, On_Client_EntityReplicator_DontActivate_Inner_Root_Before_Top_Root) + { + // Create a child entity that will be tested for activation inside a hierarchy + AZStd::unique_ptr innerRootEntity = AZStd::make_unique(); + CreateEntityWithRootHierarchy(innerRootEntity); + SetupEntity(innerRootEntity, NetEntityId{ 2 }, NetEntityRole::Client); + // child entity is not activated on purpose here, we are about to test conditional activation check + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(innerRootEntity, NetEntityId{ 1 }); + SetHierarchyRootFieldOnNetworkHierarchyChild(innerRootEntity, NetEntityId{ 1 }); + + // Create an entity replicator for the child entity + const NetworkEntityHandle innerRootHandle(innerRootEntity.get(), m_networkEntityTracker.get()); + EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, innerRootHandle); + entityReplicator.Initialize(innerRootHandle); + + // Entity replicator should not be ready to activate the entity because its parent does not exist + EXPECT_EQ(entityReplicator.IsReadyToActivate(), false); + } + + TEST_F(HierarchyTests, On_Client_Not_In_Hierarchy_EntityReplicator_Ignores_Parent) + { + // Create a child entity that will be tested for activation inside a hierarchy + AZStd::unique_ptr childEntity = AZStd::make_unique(); + CreateEntityWithChildHierarchy(childEntity); + SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client); + // child entity is not activated on purpose here, we are about to test conditional activation check + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(childEntity, NetEntityId{ 1 }); + SetHierarchyRootFieldOnNetworkHierarchyChild(childEntity, InvalidNetEntityId); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(childEntity.get(), m_networkEntityTracker.get()); + EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); + entityReplicator.Initialize(childHandle); + + // Entity replicator should not be ready to activate the entity because its parent does not exist + EXPECT_EQ(entityReplicator.IsReadyToActivate(), true); + } + + /* + * Hierarchy test - a child entity on a client allowing activation when its hierarchical parent is active + */ + TEST_F(HierarchyTests, On_Client_EntityReplicator_Activates_AfterParent) + { + AZStd::unique_ptr childEntity = AZStd::make_unique(); + CreateEntityWithChildHierarchy(childEntity); + SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client); + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(childEntity, NetEntityId{ 1 }); + SetHierarchyRootFieldOnNetworkHierarchyChild(childEntity, NetEntityId{ 1 }); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(childEntity.get(), m_networkEntityTracker.get()); + EntityReplicator childEntityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); + childEntityReplicator.Initialize(childHandle); + + // Now let's create a parent entity and activate it + AZStd::unique_ptr parentEntity = AZStd::make_unique(); + CreateEntityWithRootHierarchy(parentEntity); + SetupEntity(parentEntity, NetEntityId{ 1 }, NetEntityRole::Client); + + // Create an entity replicator for the parent entity + const NetworkEntityHandle parentHandle(parentEntity.get(), m_networkEntityTracker.get()); + ON_CALL(*m_mockNetworkEntityManager, GetEntity(_)).WillByDefault(Return(parentHandle)); + EntityReplicator parentEntityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, parentHandle); + parentEntityReplicator.Initialize(parentHandle); + + parentEntity->Activate(); + + // The child should be ready to be activated + EXPECT_EQ(childEntityReplicator.IsReadyToActivate(), true); + + StopEntity(parentEntity); + + parentEntity->Deactivate(); + } + + /* + * Parent -> Child + */ + class ClientSimpleHierarchyTests : public HierarchyTests + { + public: + const NetEntityId RootNetEntityId = NetEntityId{ 1 }; + const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; + + void SetUp() override + { + HierarchyTests::SetUp(); + + m_root = AZStd::make_unique(1, "root", RootNetEntityId, EntityInfo::Role::Root); + m_child = AZStd::make_unique(2, "child", ChildNetEntityId, EntityInfo::Role::Child); + + CreateSimpleHierarchy(*m_root, *m_child); + + m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + // now the two entities are under one hierarchy + } + + void TearDown() override + { + m_child.reset(); + m_root.reset(); + + HierarchyTests::TearDown(); + } + + void CreateSimpleHierarchy(EntityInfo& root, EntityInfo& child) + { + PopulateHierarchicalEntity(root); + SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Client); + + PopulateHierarchicalEntity(child); + SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Client); + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(child.m_entity, root.m_netId); + SetHierarchyRootFieldOnNetworkHierarchyChild(child.m_entity, root.m_netId); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get()); + child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); + child.m_replicator->Initialize(childHandle); + + // Create an entity replicator for the root entity + const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get()); + root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, rootHandle); + root.m_replicator->Initialize(rootHandle); + + root.m_entity->Activate(); + child.m_entity->Activate(); + } + + void SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(const AZStd::unique_ptr& entity, NetEntityId value) + { + /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ + constexpr int totalBits = 1 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::Count*/; + constexpr int inHierarchyBit = 0 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::hierarchyRoot_DirtyFlag*/; + + ReplicationRecord currentRecord(NetEntityRole::Client); + currentRecord.m_authorityToClient.AddBits(totalBits); + currentRecord.m_authorityToClient.SetBit(inHierarchyBit, true); + + constexpr uint32_t bufferSize = 100; + AZStd::array buffer = {}; + NetworkInputSerializer inSerializer(buffer.begin(), bufferSize); + inSerializer.Serialize(reinterpret_cast(value), + "hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ + AZStd::numeric_limits::min(), AZStd::numeric_limits::max()); + + NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); + + ReplicationRecord notifyRecord = currentRecord; + + entity->FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); + entity->FindComponent()->NotifyStateDeltaChanges(notifyRecord); + } + + AZStd::unique_ptr m_root; + AZStd::unique_ptr m_child; + }; + + TEST_F(ClientSimpleHierarchyTests, Client_Activates_Hierarchy_From_Network_Fields) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchicalRoot(), + m_root->m_entity.get() + ); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 2) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child->m_entity.get() + ); + } + } + + TEST_F(ClientSimpleHierarchyTests, Client_Detaches_Child_When_Server_Detaches) + { + // simulate server detaching child entity + SetParentIdOnNetworkTransform(m_child->m_entity, InvalidNetEntityId); + SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId); + + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchicalRoot(), + nullptr + ); + } + + TEST_F(ClientSimpleHierarchyTests, Client_Sends_NetworkHierarchy_Updated_Event_On_Child_Detached_On_Server) + { + MockNetworkHierarchyCallbackHandler mock; + EXPECT_CALL(mock, OnNetworkHierarchyUpdated(m_root->m_entity->GetId())); + + m_root->m_entity->FindComponent()->BindNetworkHierarchyChangedEventHandler(mock.m_changedHandler); + + // simulate server detaching a child entity + SetParentIdOnNetworkTransform(m_child->m_entity, InvalidNetEntityId); + SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId); + } + + TEST_F(ClientSimpleHierarchyTests, Client_Sends_NetworkHierarchy_Leave_Event_On_Child_Detached_On_Server) + { + MockNetworkHierarchyCallbackHandler mock; + EXPECT_CALL(mock, OnNetworkHierarchyLeave); + + m_child->m_entity->FindComponent()->BindNetworkHierarchyLeaveEventHandler(mock.m_leaveHandler); + + // simulate server detaching a child entity + SetParentIdOnNetworkTransform(m_child->m_entity, InvalidNetEntityId); + SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId); + } + + /* + * Parent -> Child -> ChildOfChild + */ + class ClientDeepHierarchyTests : public ClientSimpleHierarchyTests + { + public: + static const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 }; + + void SetUp() override + { + ClientSimpleHierarchyTests::SetUp(); + + m_childOfChild = AZStd::make_unique((3), "child of child", ChildOfChildNetEntityId, EntityInfo::Role::Child); + + CreateDeepHierarchyOnClient(*m_childOfChild); + + m_childOfChild->m_entity->FindComponent()->SetParent(m_child->m_entity->GetId()); + } + + void TearDown() override + { + m_childOfChild.reset(); + + ClientSimpleHierarchyTests::TearDown(); + } + + void CreateDeepHierarchyOnClient(EntityInfo& childOfChild) + { + PopulateHierarchicalEntity(childOfChild); + SetupEntity(childOfChild.m_entity, childOfChild.m_netId, NetEntityRole::Client); + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(childOfChild.m_entity, m_childOfChild->m_netId); + SetHierarchyRootFieldOnNetworkHierarchyChild(childOfChild.m_entity, m_root->m_netId); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childOfChildHandle(childOfChild.m_entity.get(), m_networkEntityTracker.get()); + childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childOfChildHandle); + childOfChild.m_replicator->Initialize(childOfChildHandle); + + childOfChild.m_entity->Activate(); + } + + AZStd::unique_ptr m_childOfChild; + }; + + TEST_F(ClientDeepHierarchyTests, Client_Activates_Hierarchy_From_Network_Fields) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + EXPECT_EQ( + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchicalRoot(), + m_root->m_entity.get() + ); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild->m_entity.get() + ); + } + } +} diff --git a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h new file mode 100644 index 0000000000..2deac1aa27 --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h @@ -0,0 +1,414 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + using namespace testing; + using namespace ::UnitTest; + + class NetworkHierarchyCallbacks + { + public: + virtual ~NetworkHierarchyCallbacks() = default; + virtual void OnNetworkHierarchyLeave() = 0; + virtual void OnNetworkHierarchyUpdated(const AZ::EntityId& hierarchyRootId) = 0; + }; + + class MockNetworkHierarchyCallbackHandler : public NetworkHierarchyCallbacks + { + public: + MockNetworkHierarchyCallbackHandler() + : m_leaveHandler([this]() { OnNetworkHierarchyLeave(); }) + , m_changedHandler([this](const AZ::EntityId& rootId) { OnNetworkHierarchyUpdated(rootId); }) + { + } + + NetworkHierarchyLeaveEvent::Handler m_leaveHandler; + NetworkHierarchyChangedEvent::Handler m_changedHandler; + + MOCK_METHOD0(OnNetworkHierarchyLeave, void()); + MOCK_METHOD1(OnNetworkHierarchyUpdated, void(const AZ::EntityId&)); + }; + + class HierarchyTests + : public AllocatorsFixture + { + public: + void SetUp() override + { + SetupAllocator(); + AZ::NameDictionary::Create(); + + m_mockComponentApplicationRequests = AZStd::make_unique>(); + AZ::Interface::Register(m_mockComponentApplicationRequests.get()); + + ON_CALL(*m_mockComponentApplicationRequests, AddEntity(_)).WillByDefault(Invoke(this, &HierarchyTests::AddEntity)); + ON_CALL(*m_mockComponentApplicationRequests, FindEntity(_)).WillByDefault(Invoke(this, &HierarchyTests::FindEntity)); + + // register components involved in testing + m_serializeContext = AZStd::make_unique(); + + m_transformDescriptor.reset(AzFramework::TransformComponent::CreateDescriptor()); + m_transformDescriptor->Reflect(m_serializeContext.get()); + + m_netBindDescriptor.reset(NetBindComponent::CreateDescriptor()); + m_netBindDescriptor->Reflect(m_serializeContext.get()); + + m_hierarchyRootDescriptor.reset(NetworkHierarchyRootComponent::CreateDescriptor()); + m_hierarchyRootDescriptor->Reflect(m_serializeContext.get()); + + m_hierarchyChildDescriptor.reset(NetworkHierarchyChildComponent::CreateDescriptor()); + m_hierarchyChildDescriptor->Reflect(m_serializeContext.get()); + + m_netTransformDescriptor.reset(NetworkTransformComponent::CreateDescriptor()); + m_netTransformDescriptor->Reflect(m_serializeContext.get()); + + m_mockMultiplayer = AZStd::make_unique>(); + AZ::Interface::Register(m_mockMultiplayer.get()); + + EXPECT_NE(AZ::Interface::Get(), nullptr); + + // Create space for replication stats + // Without Multiplayer::RegisterMultiplayerComponents() the stats go to invalid id, which is fine for unit tests + GetMultiplayer()->GetStats().ReserveComponentStats(Multiplayer::InvalidNetComponentId, 50, 0); + + m_mockNetworkEntityManager = AZStd::make_unique>(); + + ON_CALL(*m_mockNetworkEntityManager, AddEntityToEntityMap(_, _)).WillByDefault(Invoke(this, &HierarchyTests::AddEntityToEntityMap)); + ON_CALL(*m_mockNetworkEntityManager, GetEntity(_)).WillByDefault(Invoke(this, &HierarchyTests::GetEntity)); + ON_CALL(*m_mockNetworkEntityManager, GetNetEntityIdById(_)).WillByDefault(Invoke(this, &HierarchyTests::GetNetEntityIdById)); + + m_mockTime = AZStd::make_unique>(); + AZ::Interface::Register(m_mockTime.get()); + + m_mockNetworkTime = AZStd::make_unique>(); + AZ::Interface::Register(m_mockNetworkTime.get()); + + ON_CALL(*m_mockMultiplayer, GetNetworkEntityManager()).WillByDefault(Return(m_mockNetworkEntityManager.get())); + EXPECT_NE(AZ::Interface::Get()->GetNetworkEntityManager(), nullptr); + + const IpAddress address("localhost", 1, ProtocolType::Udp); + m_mockConnection = AZStd::make_unique>(ConnectionId{ 1 }, address, ConnectionRole::Connector); + m_mockConnectionListener = AZStd::make_unique(); + + m_networkEntityTracker = AZStd::make_unique(); + ON_CALL(*m_mockNetworkEntityManager, GetNetworkEntityTracker()).WillByDefault(Return(m_networkEntityTracker.get())); + + m_networkEntityAuthorityTracker = AZStd::make_unique(*m_mockNetworkEntityManager); + ON_CALL(*m_mockNetworkEntityManager, GetNetworkEntityAuthorityTracker()).WillByDefault(Return(m_networkEntityAuthorityTracker.get())); + + m_entityReplicationManager = AZStd::make_unique(*m_mockConnection, *m_mockConnectionListener, EntityReplicationManager::Mode::LocalClientToRemoteServer); + + m_console.reset(aznew AZ::Console()); + AZ::Interface::Register(m_console.get()); + m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead()); + + m_multiplayerComponentRegistry = AZStd::make_unique(); + ON_CALL(*m_mockNetworkEntityManager, GetMultiplayerComponentRegistry()).WillByDefault(Return(m_multiplayerComponentRegistry.get())); + RegisterMultiplayerComponents(); + } + + void TearDown() override + { + m_multiplayerComponentRegistry.reset(); + + AZ::Interface::Unregister(m_console.get()); + m_console.reset(); + + m_networkEntityMap.clear(); + m_entities.clear(); + + m_entityReplicationManager.reset(); + + m_mockConnection.reset(); + m_mockConnectionListener.reset(); + m_networkEntityTracker.reset(); + m_networkEntityAuthorityTracker.reset(); + + AZ::Interface::Unregister(m_mockNetworkTime.get()); + AZ::Interface::Unregister(m_mockTime.get()); + AZ::Interface::Unregister(m_mockMultiplayer.get()); + AZ::Interface::Unregister(m_mockComponentApplicationRequests.get()); + + m_mockTime.reset(); + + m_mockNetworkEntityManager.reset(); + m_mockMultiplayer.reset(); + + m_transformDescriptor.reset(); + m_netTransformDescriptor.reset(); + m_hierarchyRootDescriptor.reset(); + m_hierarchyChildDescriptor.reset(); + m_netBindDescriptor.reset(); + m_serializeContext.reset(); + m_mockComponentApplicationRequests.reset(); + + AZ::NameDictionary::Destroy(); + TeardownAllocator(); + } + + AZStd::unique_ptr m_console; + + AZStd::unique_ptr> m_mockComponentApplicationRequests; + AZStd::unique_ptr m_serializeContext; + AZStd::unique_ptr m_transformDescriptor; + AZStd::unique_ptr m_netBindDescriptor; + AZStd::unique_ptr m_hierarchyRootDescriptor; + AZStd::unique_ptr m_hierarchyChildDescriptor; + AZStd::unique_ptr m_netTransformDescriptor; + + AZStd::unique_ptr> m_mockMultiplayer; + AZStd::unique_ptr m_mockNetworkEntityManager; + AZStd::unique_ptr> m_mockTime; + AZStd::unique_ptr> m_mockNetworkTime; + + AZStd::unique_ptr> m_mockConnection; + AZStd::unique_ptr m_mockConnectionListener; + AZStd::unique_ptr m_networkEntityTracker; + AZStd::unique_ptr m_networkEntityAuthorityTracker; + + AZStd::unique_ptr m_entityReplicationManager; + + AZStd::unique_ptr m_multiplayerComponentRegistry;; + + mutable AZStd::map m_networkEntityMap; + + NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) + { + m_networkEntityMap[netEntityId] = entity; + return NetworkEntityHandle(entity, netEntityId, m_networkEntityTracker.get()); + } + + ConstNetworkEntityHandle GetEntity(NetEntityId netEntityId) const + { + AZ::Entity* entity = m_networkEntityMap[netEntityId]; + return ConstNetworkEntityHandle(entity, m_networkEntityTracker.get()); + } + + NetEntityId GetNetEntityIdById(const AZ::EntityId& entityId) const + { + for (const auto& pair : m_networkEntityMap) + { + if (pair.second->GetId() == entityId) + { + return pair.first; + } + } + + return InvalidNetEntityId; + } + + AZStd::map m_entities; + + bool AddEntity(AZ::Entity* entity) + { + m_entities[entity->GetId()] = entity; + return true; + } + + AZ::Entity* FindEntity(AZ::EntityId entityId) + { + const auto iterator = m_entities.find(entityId); + if (iterator != m_entities.end()) + { + return iterator->second; + } + + return nullptr; + } + + void SetupEntity(const AZStd::unique_ptr& entity, NetEntityId netId, NetEntityRole role) + { + const auto netBindComponent = entity->FindComponent(); + EXPECT_NE(netBindComponent, nullptr); + netBindComponent->PreInit(entity.get(), PrefabEntityId{ AZ::Name("test"), 1 }, netId, role); + entity->Init(); + } + + static void StopEntity(const AZStd::unique_ptr& entity) + { + const auto netBindComponent = entity->FindComponent(); + EXPECT_NE(netBindComponent, nullptr); + netBindComponent->StopEntity(); + } + + static void StopAndDeactivateEntity(AZStd::unique_ptr& entity) + { + if (entity) + { + StopEntity(entity); + entity->Deactivate(); + entity.reset(); + } + } + + void CreateEntityWithRootHierarchy(AZStd::unique_ptr& rootEntity) + { + rootEntity->CreateComponent(); + rootEntity->CreateComponent(); + rootEntity->CreateComponent(); + rootEntity->CreateComponent(); + } + + void CreateEntityWithChildHierarchy(AZStd::unique_ptr& childEntity) + { + childEntity->CreateComponent(); + childEntity->CreateComponent(); + childEntity->CreateComponent(); + childEntity->CreateComponent(); + } + + void SetParentIdOnNetworkTransform(const AZStd::unique_ptr& entity, NetEntityId netParentId) + { + /* Derived from NetworkTransformComponent.AutoComponent.xml */ + constexpr int totalBits = 6 /*NetworkTransformComponentInternal::AuthorityToClientDirtyEnum::Count*/; + constexpr int parentIdBit = 4 /*NetworkTransformComponentInternal::AuthorityToClientDirtyEnum::parentEntityId_DirtyFlag*/; + + ReplicationRecord currentRecord; + currentRecord.m_authorityToClient.AddBits(totalBits); + currentRecord.m_authorityToClient.SetBit(parentIdBit, true); + + constexpr uint32_t bufferSize = 100; + AZStd::array buffer = {}; + NetworkInputSerializer inSerializer(buffer.begin(), bufferSize); + inSerializer.Serialize(reinterpret_cast(netParentId), + "parentEntityId", /* Derived from NetworkTransformComponent.AutoComponent.xml */ + AZStd::numeric_limits::min(), AZStd::numeric_limits::max()); + + NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); + + ReplicationRecord notifyRecord = currentRecord; + entity->FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); + entity->FindComponent()->NotifyStateDeltaChanges(notifyRecord); + } + + template + void SetHierarchyRootFieldOnNetworkHierarchyChild(const AZStd::unique_ptr& entity, NetEntityId value) + { + /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ + constexpr int totalBits = 1 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::Count*/; + constexpr int inHierarchyBit = 0 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::hierarchyRoot_DirtyFlag*/; + + ReplicationRecord currentRecord; + currentRecord.m_authorityToClient.AddBits(totalBits); + currentRecord.m_authorityToClient.SetBit(inHierarchyBit, true); + + constexpr uint32_t bufferSize = 100; + AZStd::array buffer = {}; + NetworkInputSerializer inSerializer(buffer.begin(), bufferSize); + inSerializer.Serialize(reinterpret_cast(value), + "hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ + AZStd::numeric_limits::min(), AZStd::numeric_limits::max()); + + NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); + + ReplicationRecord notifyRecord = currentRecord; + entity->FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); + entity->FindComponent()->NotifyStateDeltaChanges(notifyRecord); + } + + struct EntityInfo + { + enum class Role + { + Root, + Child, + None + }; + + EntityInfo(AZ::u64 entityId, const char* entityName, NetEntityId netId, Role role) + : m_entity(AZStd::make_unique(AZ::EntityId(entityId), entityName)) + , m_netId(netId) + , m_role(role) + { + } + + ~EntityInfo() + { + StopAndDeactivateEntity(m_entity); + } + + AZStd::unique_ptr m_entity; + NetEntityId m_netId; + AZStd::unique_ptr m_replicator; + Role m_role = Role::None; + }; + + void PopulateHierarchicalEntity(const EntityInfo& entityInfo) + { + entityInfo.m_entity->CreateComponent(); + entityInfo.m_entity->CreateComponent(); + entityInfo.m_entity->CreateComponent(); + switch (entityInfo.m_role) + { + case EntityInfo::Role::Root: + entityInfo.m_entity->CreateComponent(); + break; + case EntityInfo::Role::Child: + entityInfo.m_entity->CreateComponent(); + break; + case EntityInfo::Role::None: + break; + } + } + + void CreateDeepHierarchy(EntityInfo& root, EntityInfo& child, EntityInfo& childOfChild) + { + PopulateHierarchicalEntity(root); + PopulateHierarchicalEntity(child); + PopulateHierarchicalEntity(childOfChild); + + SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Authority); + SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Authority); + SetupEntity(childOfChild.m_entity, childOfChild.m_netId, NetEntityRole::Authority); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childOfChildHandle(childOfChild.m_entity.get(), m_networkEntityTracker.get()); + childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChildHandle); + childOfChild.m_replicator->Initialize(childOfChildHandle); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get()); + child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); + child.m_replicator->Initialize(childHandle); + + // Create an entity replicator for the root entity + const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get()); + root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle); + root.m_replicator->Initialize(rootHandle); + + root.m_entity->Activate(); + child.m_entity->Activate(); + childOfChild.m_entity->Activate(); + } + }; +} diff --git a/Gems/Multiplayer/Code/Tests/MainTools.cpp b/Gems/Multiplayer/Code/Tests/MainTools.cpp index 89b2492bc6..53dfad62d1 100644 --- a/Gems/Multiplayer/Code/Tests/MainTools.cpp +++ b/Gems/Multiplayer/Code/Tests/MainTools.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -30,7 +29,6 @@ namespace Multiplayer { AZStd::vector descriptors({ NetBindComponent::CreateDescriptor(), - NetBindMarkerComponent::CreateDescriptor(), NetworkSpawnableHolderComponent::CreateDescriptor() }); diff --git a/Gems/Multiplayer/Code/Tests/MockInterfaces.h b/Gems/Multiplayer/Code/Tests/MockInterfaces.h new file mode 100644 index 0000000000..42e034d234 --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/MockInterfaces.h @@ -0,0 +1,169 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class MockMultiplayer : public Multiplayer::IMultiplayer + { + public: + MOCK_CONST_METHOD0(GetCurrentBlendFactor, float ()); + MOCK_CONST_METHOD0(GetAgentType, Multiplayer::MultiplayerAgentType()); + MOCK_METHOD1(InitializeMultiplayer, void(Multiplayer::MultiplayerAgentType)); + MOCK_METHOD2(StartHosting, bool(uint16_t, bool)); + MOCK_METHOD2(Connect, bool(AZStd::string, uint16_t)); + MOCK_METHOD1(Terminate, void(AzNetworking::DisconnectReason)); + MOCK_METHOD1(AddClientDisconnectedHandler, void(AZ::Event<>::Handler&)); + MOCK_METHOD1(AddConnectionAcquiredHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(AddSessionInitHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(AddSessionShutdownHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(SendReadyForEntityUpdates, void(bool)); + MOCK_CONST_METHOD0(GetCurrentHostTimeMs, AZ::TimeMs()); + MOCK_METHOD0(GetNetworkTime, Multiplayer::INetworkTime* ()); + MOCK_METHOD0(GetNetworkEntityManager, Multiplayer::INetworkEntityManager* ()); + MOCK_METHOD1(SetFilterEntityManager, void(Multiplayer::IFilterEntityManager*)); + MOCK_METHOD0(GetFilterEntityManager, Multiplayer::IFilterEntityManager* ()); + }; + + class MockNetworkEntityManager : public Multiplayer::INetworkEntityManager + { + public: + MOCK_METHOD2(RequestNetSpawnableInstantiation, AZStd::unique_ptr (const AZ::Data::Asset&, const AZ::Transform&)); + MOCK_METHOD4( + CreateEntitiesImmediate, + EntityList (const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ::Transform&, Multiplayer::AutoActivate)); + MOCK_CONST_METHOD1(GetNetEntityIdById, Multiplayer::NetEntityId (const AZ::EntityId&)); + MOCK_METHOD0(GetNetworkEntityTracker, Multiplayer::NetworkEntityTracker* ()); + MOCK_METHOD0(GetNetworkEntityAuthorityTracker, Multiplayer::NetworkEntityAuthorityTracker* ()); + MOCK_METHOD0(GetMultiplayerComponentRegistry, Multiplayer::MultiplayerComponentRegistry* ()); + MOCK_CONST_METHOD0(GetHostId, Multiplayer::HostId()); + MOCK_METHOD3(CreateEntitiesImmediate, EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ:: + Transform&)); + MOCK_METHOD5(CreateEntitiesImmediate, EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityId, Multiplayer:: + NetEntityRole, Multiplayer::AutoActivate, const AZ::Transform&)); + MOCK_METHOD3(SetupNetEntity, void(AZ::Entity*, Multiplayer::PrefabEntityId, Multiplayer::NetEntityRole)); + MOCK_CONST_METHOD1(GetEntity, Multiplayer::ConstNetworkEntityHandle(Multiplayer::NetEntityId)); + MOCK_CONST_METHOD0(GetEntityCount, uint32_t()); + MOCK_METHOD2(AddEntityToEntityMap, Multiplayer::NetworkEntityHandle(Multiplayer::NetEntityId, AZ::Entity*)); + MOCK_METHOD1(MarkForRemoval, void(const Multiplayer::ConstNetworkEntityHandle&)); + MOCK_CONST_METHOD1(IsMarkedForRemoval, bool(const Multiplayer::ConstNetworkEntityHandle&)); + MOCK_METHOD1(ClearEntityFromRemovalList, void(const Multiplayer::ConstNetworkEntityHandle&)); + MOCK_METHOD0(ClearAllEntities, void()); + MOCK_METHOD1(AddEntityMarkedDirtyHandler, void(AZ::Event<>::Handler&)); + MOCK_METHOD1(AddEntityNotifyChangesHandler, void(AZ::Event<>::Handler&)); + MOCK_METHOD1(AddEntityExitDomainHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(AddControllersActivatedHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(AddControllersDeactivatedHandler, void(AZ::Event::Handler&)); + MOCK_METHOD0(NotifyEntitiesDirtied, void()); + MOCK_METHOD0(NotifyEntitiesChanged, void()); + MOCK_METHOD2(NotifyControllersActivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating)); + MOCK_METHOD2(NotifyControllersDeactivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating)); + MOCK_METHOD1(HandleLocalRpcMessage, void(Multiplayer::NetworkEntityRpcMessage&)); + }; + + class MockConnectionListener : public AzNetworking::IConnectionListener + { + public: + MOCK_METHOD3(ValidateConnect, ConnectResult(const IpAddress&, const IPacketHeader&, ISerializer&)); + MOCK_METHOD1(OnConnect, void(IConnection*)); + MOCK_METHOD3(OnPacketReceived, PacketDispatchResult (IConnection*, const IPacketHeader&, ISerializer&)); + MOCK_METHOD2(OnPacketLost, void(IConnection*, PacketId)); + MOCK_METHOD3(OnDisconnect, void(IConnection*, DisconnectReason, TerminationEndpoint)); + }; + + class MockTime : public AZ::ITime + { + public: + MOCK_CONST_METHOD0(GetElapsedTimeMs, AZ::TimeMs()); + }; + + class MockNetworkTime : public Multiplayer::INetworkTime + { + public: + MOCK_METHOD2(ForceSetTime, void (Multiplayer::HostFrameId, AZ::TimeMs)); + MOCK_CONST_METHOD0(GetHostBlendFactor, float ()); + MOCK_METHOD1(AlterBlendFactor, void (float)); + MOCK_CONST_METHOD0(IsTimeRewound, bool()); + MOCK_CONST_METHOD0(GetHostFrameId, Multiplayer::HostFrameId()); + MOCK_CONST_METHOD0(GetUnalteredHostFrameId, Multiplayer::HostFrameId()); + MOCK_METHOD0(IncrementHostFrameId, void()); + MOCK_CONST_METHOD0(GetHostTimeMs, AZ::TimeMs()); + MOCK_CONST_METHOD0(GetRewindingConnectionId, AzNetworking::ConnectionId()); + MOCK_CONST_METHOD1(GetHostFrameIdForRewindingConnection, Multiplayer::HostFrameId(AzNetworking::ConnectionId)); + MOCK_METHOD4(AlterTime, void (Multiplayer::HostFrameId, AZ::TimeMs, float, AzNetworking::ConnectionId)); + MOCK_METHOD1(SyncEntitiesToRewindState, void(const AZ::Aabb&)); + MOCK_METHOD0(ClearRewoundEntities, void()); + }; + + class MockComponentApplicationRequests : public AZ::ComponentApplicationRequests + { + public: + MOCK_METHOD1(RegisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); + MOCK_METHOD1(UnregisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); + MOCK_METHOD0(GetApplication, AZ::ComponentApplication* ()); + MOCK_METHOD1(RegisterEntityAddedEventHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(RegisterEntityRemovedEventHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(RegisterEntityActivatedEventHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(RegisterEntityDeactivatedEventHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(SignalEntityActivated, void(AZ::Entity*)); + MOCK_METHOD1(SignalEntityDeactivated, void(AZ::Entity*)); + MOCK_METHOD1(AddEntity, bool(AZ::Entity*)); + MOCK_METHOD1(RemoveEntity, bool(AZ::Entity*)); + MOCK_METHOD1(DeleteEntity, bool(const AZ::EntityId&)); + MOCK_METHOD1(FindEntity, AZ::Entity* (const AZ::EntityId&)); + MOCK_METHOD1(GetEntityName, AZStd::string(const AZ::EntityId&)); + MOCK_METHOD1(EnumerateEntities, void(const EntityCallback&)); + MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ()); + MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ()); + MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ()); + MOCK_CONST_METHOD0(GetAppRoot, const char* ()); + MOCK_CONST_METHOD0(GetEngineRoot, const char* ()); + MOCK_CONST_METHOD0(GetExecutableFolder, const char* ()); + MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ()); + MOCK_METHOD1(ResolveModulePath, void(AZ::OSString&)); + MOCK_METHOD0(GetAzCommandLine, AZ::CommandLine* ()); + MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&)); + }; + + class MockSerializer : public ISerializer + { + public: + MOCK_CONST_METHOD0(IsValid, bool ()); + MOCK_CONST_METHOD0(GetSerializerMode, SerializerMode ()); + MOCK_METHOD2(Serialize, bool (bool&, const char*)); + MOCK_METHOD4(Serialize, bool (char&, const char*, char, char)); + MOCK_METHOD4(Serialize, bool (int8_t&, const char*, int8_t, int8_t)); + MOCK_METHOD4(Serialize, bool (int16_t&, const char*, int16_t, int16_t)); + MOCK_METHOD4(Serialize, bool (int32_t&, const char*, int32_t, int32_t)); + MOCK_METHOD4(Serialize, bool (int64_t&, const char*, int64_t, int64_t)); + MOCK_METHOD4(Serialize, bool (uint8_t&, const char*, uint8_t, uint8_t)); + MOCK_METHOD4(Serialize, bool (uint16_t&, const char*, uint16_t, uint16_t)); + MOCK_METHOD4(Serialize, bool (uint32_t&, const char*, uint32_t, uint32_t)); + MOCK_METHOD4(Serialize, bool (uint64_t&, const char*, uint64_t, uint64_t)); + MOCK_METHOD4(Serialize, bool (float&, const char*, float, float)); + MOCK_METHOD4(Serialize, bool (double&, const char*, double, double)); + MOCK_METHOD5(SerializeBytes, bool (uint8_t*, uint32_t, bool, uint32_t&, const char*)); + MOCK_METHOD2(BeginObject, bool (const char*, const char*)); + MOCK_METHOD2(EndObject, bool (const char*, const char*)); + MOCK_CONST_METHOD0(GetBuffer, const uint8_t* ()); + MOCK_CONST_METHOD0(GetCapacity, uint32_t ()); + MOCK_CONST_METHOD0(GetSize, uint32_t ()); + MOCK_METHOD0(ClearTrackedChangesFlag, void ()); + MOCK_CONST_METHOD0(GetTrackedChangesFlag, bool ()); + }; +} + diff --git a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp new file mode 100644 index 0000000000..70859909b3 --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp @@ -0,0 +1,1233 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + using namespace testing; + using namespace ::UnitTest; + + /* + * Parent -> Child + */ + class ServerSimpleHierarchyTests : public HierarchyTests + { + public: + void SetUp() override + { + HierarchyTests::SetUp(); + + m_root = AZStd::make_unique(1, "root", NetEntityId{ 1 }, EntityInfo::Role::Root); + m_child = AZStd::make_unique(2, "child", NetEntityId{ 2 }, EntityInfo::Role::Child); + + CreateSimpleHierarchy(*m_root, *m_child); + + m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + // now the two entities are under one hierarchy + } + + void TearDown() override + { + m_child.reset(); + m_root.reset(); + + HierarchyTests::TearDown(); + } + + void CreateSimpleHierarchy(EntityInfo& root, EntityInfo& child) + { + PopulateHierarchicalEntity(root); + SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Authority); + + PopulateHierarchicalEntity(child); + SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Authority); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get()); + child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); + child.m_replicator->Initialize(childHandle); + + // Create an entity replicator for the root entity + const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get()); + root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle); + root.m_replicator->Initialize(rootHandle); + + root.m_entity->Activate(); + child.m_entity->Activate(); + } + + AZStd::unique_ptr m_root; + AZStd::unique_ptr m_child; + }; + + TEST_F(ServerSimpleHierarchyTests, Server_Sets_Appropriate_Network_Fields_For_Clients) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchyRoot(), + NetEntityId{ 1 } + ); + } + + TEST_F(ServerSimpleHierarchyTests, Root_Is_Top_Level_Root) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->IsHierarchicalChild(), + false + ); + } + + TEST_F(ServerSimpleHierarchyTests, Child_Has_Root_Set) + { + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchyRoot(), + NetEntityId{ 1 } + ); + } + + TEST_F(ServerSimpleHierarchyTests, Child_Has_Root_Cleared_On_Detach) + { + // now detach the child + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + } + + TEST_F(ServerSimpleHierarchyTests, Root_Has_Child_Reference) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + TEST_F(ServerSimpleHierarchyTests, Root_Has_Child_References_Removed_On_Detach) + { + // now detach the child + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 1 + ); + } + + TEST_F(ServerSimpleHierarchyTests, Root_Deactivates_Child_Has_No_References_To_Root) + { + StopEntity(m_root->m_entity); + m_root->m_entity->Deactivate(); + m_root->m_entity.reset(); + + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + } + + TEST_F(ServerSimpleHierarchyTests, Child_Deactivates_Root_Has_No_References_To_Child) + { + m_child.reset(); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 1 + ); + } + + TEST_F(ServerSimpleHierarchyTests, Root_Deactivates_IsHierarchyEnabled_Is_False) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->IsHierarchyEnabled(), + true + ); + + StopEntity(m_root->m_entity); + m_root->m_entity->Deactivate(); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->IsHierarchyEnabled(), + false + ); + + m_root->m_entity.reset(); + } + + TEST_F(ServerSimpleHierarchyTests, Child_Deactivates_IsHierarchyEnabled_Is_False) + { + EXPECT_EQ( + m_child->m_entity->FindComponent()->IsHierarchyEnabled(), + true + ); + + StopEntity(m_child->m_entity); + m_child->m_entity->Deactivate(); + + EXPECT_EQ( + m_child->m_entity->FindComponent()->IsHierarchyEnabled(), + false + ); + + m_child->m_entity.reset(); + } + + /* + * Parent -> Child -> ChildOfChild + */ + class ServerDeepHierarchyTests : public HierarchyTests + { + public: + const NetEntityId RootNetEntityId = NetEntityId{ 1 }; + const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; + const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 }; + + void SetUp() override + { + HierarchyTests::SetUp(); + + m_root = AZStd::make_unique((1), "root", RootNetEntityId, EntityInfo::Role::Root); + m_child = AZStd::make_unique((2), "child", ChildNetEntityId, EntityInfo::Role::Child); + m_childOfChild = AZStd::make_unique((3), "child of child", ChildOfChildNetEntityId, EntityInfo::Role::Child); + + CreateDeepHierarchy(*m_root, *m_child, *m_childOfChild); + + m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + m_childOfChild->m_entity->FindComponent()->SetParent(m_child->m_entity->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_childOfChild.reset(); + m_child.reset(); + m_root.reset(); + + HierarchyTests::TearDown(); + } + + AZStd::unique_ptr m_root; + AZStd::unique_ptr m_child; + AZStd::unique_ptr m_childOfChild; + }; + + TEST_F(ServerDeepHierarchyTests, Root_Is_Top_Level_Root) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->IsHierarchicalChild(), + false + ); + } + + TEST_F(ServerDeepHierarchyTests, Root_Has_Child_References) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild->m_entity.get() + ); + } + } + + TEST_F(ServerDeepHierarchyTests, Root_Has_Child_Of_Child_Reference_Removed_On_Detach) + { + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_Removed_On_Detach_Of_Mid_Child) + { + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 1 + ); + } + + TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_If_Mid_Child_Added_With_Child) + { + m_root->m_entity->FindComponent()->SetParent(AZ::EntityId()); + // reconnect + m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_If_Child_Of_Child_Added) + { + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); + // reconnect + m_childOfChild->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Points_To_Root_After_Attach) + { + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); + // reconnect + m_childOfChild->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + } + + TEST_F(ServerDeepHierarchyTests, All_New_Children_Point_To_Root_If_Mid_Child_Added_With_Child) + { + m_root->m_entity->FindComponent()->SetParent(AZ::EntityId()); + // reconnect + m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + EXPECT_EQ( + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + } + + TEST_F(ServerDeepHierarchyTests, Children_Clear_Reference_To_Root_After_Mid_Child_Detached) + { + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + EXPECT_EQ( + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + } + + TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Clears_Reference_To_Root_After_Detached) + { + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + } + + TEST_F(ServerDeepHierarchyTests, Root_Deactivates_Children_Have_No_References_To_Root) + { + m_root.reset(); + + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + + EXPECT_EQ( + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + } + + TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Deactivates_Root_Removes_References_To_It) + { + m_childOfChild.reset(); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + TEST_F(ServerDeepHierarchyTests, Testing_Limiting_Hierarchy_Maximum_Size) + { + uint32_t currentMaxLimit = 0; + m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); + m_console->PerformCommand("bg_hierarchyEntityMaxLimit 2"); + + // remake the hierarchy + m_root->m_entity->FindComponent()->SetParent(AZ::EntityId()); + m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + + m_console->PerformCommand((AZStd::string("bg_hierarchyEntityMaxLimit ") + AZStd::to_string(currentMaxLimit)).c_str()); + m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); + } + + /* + * Parent -> Child -> Child Of Child + * -> Child2 -> Child Of Child2 + * -> Child2 Of Child2 + */ + class ServerBranchedHierarchyTests : public HierarchyTests + { + public: + static const NetEntityId RootNetEntityId = NetEntityId{ 1 }; + static const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; + static const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 }; + static const NetEntityId Child2NetEntityId = NetEntityId{ 4 }; + static const NetEntityId ChildOfChild2NetEntityId = NetEntityId{ 5 }; + static const NetEntityId Child2OfChild2NetEntityId = NetEntityId{ 6 }; + + void SetUp() override + { + HierarchyTests::SetUp(); + + m_root = AZStd::make_unique((1), "root", RootNetEntityId, EntityInfo::Role::Root); + m_child = AZStd::make_unique((2), "child", ChildNetEntityId, EntityInfo::Role::Child); + m_childOfChild = AZStd::make_unique((3), "child of child", ChildOfChildNetEntityId, EntityInfo::Role::Child); + m_child2 = AZStd::make_unique((4), "child2", Child2NetEntityId, EntityInfo::Role::Child); + m_childOfChild2 = AZStd::make_unique((5), "child of child2", ChildOfChild2NetEntityId, EntityInfo::Role::Child); + m_child2OfChild2 = AZStd::make_unique((6), "child2 of child2", Child2OfChild2NetEntityId, EntityInfo::Role::Child); + + CreateBranchedHierarchy(*m_root, *m_child, *m_childOfChild, + *m_child2, *m_childOfChild2, *m_child2OfChild2); + + m_child2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + m_childOfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); + m_child2OfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); + m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + m_childOfChild->m_entity->FindComponent()->SetParent(m_child->m_entity->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_child2OfChild2.reset(); + m_childOfChild2.reset(); + m_child2.reset(); + m_childOfChild.reset(); + m_child.reset(); + m_root.reset(); + + HierarchyTests::TearDown(); + } + + + void CreateBranchedHierarchy(EntityInfo& root, EntityInfo& child, EntityInfo& childOfChild, + EntityInfo& child2, EntityInfo& childOfChild2, EntityInfo& child2OfChild2) + { + PopulateHierarchicalEntity(root); + PopulateHierarchicalEntity(child); + PopulateHierarchicalEntity(childOfChild); + PopulateHierarchicalEntity(child2); + PopulateHierarchicalEntity(childOfChild2); + PopulateHierarchicalEntity(child2OfChild2); + + SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Authority); + SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Authority); + SetupEntity(childOfChild.m_entity, childOfChild.m_netId, NetEntityRole::Authority); + SetupEntity(child2.m_entity, child2.m_netId, NetEntityRole::Authority); + SetupEntity(childOfChild2.m_entity, childOfChild2.m_netId, NetEntityRole::Authority); + SetupEntity(child2OfChild2.m_entity, child2OfChild2.m_netId, NetEntityRole::Authority); + + // Create entity replicators + const NetworkEntityHandle childOfChild2Handle(childOfChild2.m_entity.get(), m_networkEntityTracker.get()); + childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChild2Handle); + childOfChild.m_replicator->Initialize(childOfChild2Handle); + + const NetworkEntityHandle child2OfChild2Handle(child2OfChild2.m_entity.get(), m_networkEntityTracker.get()); + childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, child2OfChild2Handle); + childOfChild.m_replicator->Initialize(child2OfChild2Handle); + + const NetworkEntityHandle child2Handle(child2.m_entity.get(), m_networkEntityTracker.get()); + child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, child2Handle); + child.m_replicator->Initialize(child2Handle); + + const NetworkEntityHandle childOfChildHandle(childOfChild.m_entity.get(), m_networkEntityTracker.get()); + childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChildHandle); + childOfChild.m_replicator->Initialize(childOfChildHandle); + + const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get()); + child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); + child.m_replicator->Initialize(childHandle); + + const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get()); + root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle); + root.m_replicator->Initialize(rootHandle); + + root.m_entity->Activate(); + child.m_entity->Activate(); + childOfChild.m_entity->Activate(); + child2.m_entity->Activate(); + childOfChild2.m_entity->Activate(); + child2OfChild2.m_entity->Activate(); + } + + AZStd::unique_ptr m_root; + AZStd::unique_ptr m_child; + AZStd::unique_ptr m_childOfChild; + AZStd::unique_ptr m_child2; + AZStd::unique_ptr m_childOfChild2; + AZStd::unique_ptr m_child2OfChild2; + }; + + TEST_F(ServerBranchedHierarchyTests, Sanity_Check) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 6) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[3], + m_child2->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[4], + m_child2OfChild2->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[5], + m_childOfChild2->m_entity.get() + ); + } + } + + TEST_F(ServerBranchedHierarchyTests, Detach_Child_While_Child2_Remains_Attached) + { + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 4 + ); + + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 4) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child2->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_child2OfChild2->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[3], + m_childOfChild2->m_entity.get() + ); + } + + EXPECT_EQ( + m_child2->m_entity->FindComponent()->GetHierarchicalRoot(), + m_root->m_entity.get() + ); + EXPECT_EQ( + m_child->m_entity->FindComponent()->GetHierarchicalRoot(), + nullptr + ); + EXPECT_EQ( + m_childOfChild->m_entity->FindComponent()->GetHierarchicalRoot(), + nullptr + ); + } + + TEST_F(ServerBranchedHierarchyTests, Detach_Child_Then_Attach_To_Child2) + { + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); + m_child->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + /* + * Sets up 2 deep hierarchies. + */ + class ServerHierarchyOfHierarchyTests : public ServerDeepHierarchyTests + { + public: + static const NetEntityId Root2NetEntityId = NetEntityId{ 4 }; + static const NetEntityId Child2NetEntityId = NetEntityId{ 5 }; + static const NetEntityId ChildOfChild2NetEntityId = NetEntityId{ 6 }; + + void SetUp() override + { + ServerDeepHierarchyTests::SetUp(); + + m_root2 = AZStd::make_unique((4), "root 2", Root2NetEntityId, EntityInfo::Role::Root); + m_child2 = AZStd::make_unique((5), "child 2", Child2NetEntityId, EntityInfo::Role::Child); + m_childOfChild2 = AZStd::make_unique((6), "child of child 2", ChildOfChild2NetEntityId, EntityInfo::Role::Child); + + CreateDeepHierarchy(*m_root2, *m_child2, *m_childOfChild2); + + m_child2->m_entity->FindComponent()->SetParent(m_root2->m_entity->GetId()); + m_childOfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_childOfChild2.reset(); + m_child2.reset(); + m_root2.reset(); + + ServerDeepHierarchyTests::TearDown(); + } + + AZStd::unique_ptr m_root2; + AZStd::unique_ptr m_child2; + AZStd::unique_ptr m_childOfChild2; + }; + + TEST_F(ServerHierarchyOfHierarchyTests, Hierarchies_Are_Not_Related) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child->m_entity.get() + ); + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild->m_entity.get() + ); + } + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + + if (m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) + { + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root2->m_entity.get() + ); + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child2->m_entity.get() + ); + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild2->m_entity.get() + ); + } + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Is_Not_Top_Level_Root) + { + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->IsHierarchicalChild(), + false + ); + EXPECT_EQ( + m_root2->m_entity->FindComponent()->IsHierarchicalChild(), + true + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Root) + { + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Child) + { + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Child_Of_Child) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Root) + { + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->IsHierarchicalChild(), + true + ); + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Child) + { + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->IsHierarchicalChild(), + true + ); + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Child_Of_Child) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->IsHierarchicalChild(), + true + ); + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Doesnt_Keep_Child_References) + { + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 0 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Detachment_From_Top_Root) + { + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + // detach + m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + if (m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) + { + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root2->m_entity.get() + ); + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child2->m_entity.get() + ); + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild2->m_entity.get() + ); + } + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Detachment_From_Child_Of_Child) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + // detach + m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Top_Root_Deactivates) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + m_root.reset(); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Child_Of_Top_Root_Deactivates) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + m_child.reset(); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Child_Of_Child_Deactivates) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + m_childOfChild.reset(); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Stress_Test_Inner_Root_Has_Child_References_After_Detachment_From_Child_Of_Child) + { + for (int i = 0; i < 100; ++i) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + // detach + m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); + } + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Detachment_Of_Child_Of_Child_In_Inner_Hierarchy) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + // detach + m_childOfChild2->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 5 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Attachment_Of_Child_Of_Child_In_Inner_Hierarchy) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + // detach + m_childOfChild2->m_entity->FindComponent()->SetParent(AZ::EntityId()); + // re-connect + m_childOfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Child_Of_Child_Changed_Hierarchies) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + // detach + m_childOfChild2->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + // connect to a different hierarchy + m_childOfChild2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Detachment_Of_Child_In_Inner_Hierarchy) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + // detach + m_child2->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 4 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Child_Changed_Hierarchies) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + // detach + m_child2->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + // connect to a different hierarchy + m_child2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_No_Child_References_After_All_Children_Moved_To_Another_Hierarchy) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + m_child2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + // detach + m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 1 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Child_Deactivated_Top_Root_Has_No_Child_Reference_To_It) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + m_child2.reset(); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 4 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Testing_Limiting_Hierarchy_Maximum_Size) + { + uint32_t currentMaxLimit = 0; + m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); + m_console->PerformCommand("bg_hierarchyEntityMaxLimit 2"); + + // remake the top level hierarchy + m_root->m_entity->FindComponent()->SetParent(AZ::EntityId()); + m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + + m_console->PerformCommand((AZStd::string("bg_hierarchyEntityMaxLimit ") + AZStd::to_string(currentMaxLimit)).c_str()); + m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); + } + + /* + * Parent -> Child -> ChildOfChild (not marked as in a hierarchy) + */ + class ServerMixedDeepHierarchyTests : public HierarchyTests + { + public: + void SetUp() override + { + HierarchyTests::SetUp(); + + m_root = AZStd::make_unique((1), "root", NetEntityId{ 1 }, EntityInfo::Role::Root); + m_child = AZStd::make_unique((2), "child", NetEntityId{ 2 }, EntityInfo::Role::Child); + m_childOfChild = AZStd::make_unique((3), "child of child", NetEntityId{ 3 }, EntityInfo::Role::None); + + CreateDeepHierarchy(*m_root, *m_child, *m_childOfChild); + + m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + m_childOfChild->m_entity->FindComponent()->SetParent(m_child->m_entity->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_childOfChild.reset(); + m_child.reset(); + m_root.reset(); + + HierarchyTests::TearDown(); + } + + AZStd::unique_ptr m_root; + AZStd::unique_ptr m_child; + AZStd::unique_ptr m_childOfChild; + }; + + TEST_F(ServerMixedDeepHierarchyTests, Top_Root_Ignores_Non_Hierarchical_Entities) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + TEST_F(ServerMixedDeepHierarchyTests, Detaching_Non_Hierarchical_Entity_Has_No_Effect_On_Top_Root) + { + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + TEST_F(ServerMixedDeepHierarchyTests, Attaching_Non_Hierarchical_Entity_Has_No_Effect_On_Top_Root) + { + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); + m_childOfChild->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + /* + * 1st hierarchy: Parent -> Child -> ChildOfChild (not marked as in a hierarchy) + * 2nd hierarchy: Parent2 -> Child2 (not marked as in a hierarchy) -> ChildOfChild2 + */ + class ServerMixedHierarchyOfHierarchyTests : public ServerMixedDeepHierarchyTests + { + public: + void SetUp() override + { + ServerMixedDeepHierarchyTests::SetUp(); + + m_root2 = AZStd::make_unique((4), "root 2", NetEntityId{ 4 }, EntityInfo::Role::Root); + m_child2 = AZStd::make_unique((5), "child 2", NetEntityId{ 5 }, EntityInfo::Role::None); + m_childOfChild2 = AZStd::make_unique((6), "child of child 2", NetEntityId{ 6 }, EntityInfo::Role::Child); + + CreateDeepHierarchy(*m_root2, *m_child2, *m_childOfChild2); + + m_child2->m_entity->FindComponent()->SetParent(m_root2->m_entity->GetId()); + m_childOfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_childOfChild2.reset(); + m_child2.reset(); + m_root2.reset(); + + ServerMixedDeepHierarchyTests::TearDown(); + } + + AZStd::unique_ptr m_root2; + AZStd::unique_ptr m_child2; + AZStd::unique_ptr m_childOfChild2; + }; + + TEST_F(ServerMixedHierarchyOfHierarchyTests, Sanity_Check_Ingore_Children_Without_Hierarchy_Components) + { + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 1 + ); + } + + TEST_F(ServerMixedHierarchyOfHierarchyTests, Adding_Mixed_Hierarchy_Ingores_Children_Without_Hierarchy_Components) + { + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + + EXPECT_EQ( + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerMixedHierarchyOfHierarchyTests, Attaching_Hierarchy_To_Non_Hierarchical_Entity_Does_Not_Merge_Hierarchies) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->IsHierarchicalChild(), + false + ); + } + + /* + * Sets up a hierarchy with 3 roots, 2 of them being inner roots. + */ + class ServerHierarchyWithThreeRoots : public ServerHierarchyOfHierarchyTests + { + public: + static const NetEntityId Root3NetEntityId = NetEntityId{ 7 }; + static const NetEntityId Child3NetEntityId = NetEntityId{ 8 }; + static const NetEntityId ChildOfChild3NetEntityId = NetEntityId{ 9 }; + + void SetUp() override + { + ServerHierarchyOfHierarchyTests::SetUp(); + + m_root3 = AZStd::make_unique((7), "root 3", Root3NetEntityId, EntityInfo::Role::Root); + m_child3 = AZStd::make_unique((8), "child 3", Child3NetEntityId, EntityInfo::Role::Child); + m_childOfChild3 = AZStd::make_unique((9), "child of child 3", ChildOfChild3NetEntityId, EntityInfo::Role::Child); + + CreateDeepHierarchy(*m_root3, *m_child3, *m_childOfChild3); + + m_child3->m_entity->FindComponent()->SetParent(m_root3->m_entity->GetId()); + m_childOfChild3->m_entity->FindComponent()->SetParent(m_child3->m_entity->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_childOfChild3.reset(); + m_child3.reset(); + m_root3.reset(); + + ServerHierarchyOfHierarchyTests::TearDown(); + } + + AZStd::unique_ptr m_root3; + AZStd::unique_ptr m_child3; + AZStd::unique_ptr m_childOfChild3; + }; + + TEST_F(ServerHierarchyWithThreeRoots, Top_Root_Active_Then_Inner_Roots_Have_No_Child_References) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 0 + ); + EXPECT_EQ( + m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 0 + ); + } + + TEST_F(ServerHierarchyWithThreeRoots, Top_Root_Deactivates_Inner_Roots_Have_Child_References) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + m_root.reset(); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + EXPECT_EQ( + m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyWithThreeRoots, Child_Of_Top_Root_Deactivates_Inner_Roots_Have_Child_References) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + m_child.reset(); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + EXPECT_EQ( + m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyWithThreeRoots, Child_Of_Child_Of_Top_Root_Deactivates_Inner_Roots_Have_Child_References) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + m_childOfChild.reset(); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + EXPECT_EQ( + m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } +} diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 54311a0abc..3516255c7d 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -15,23 +15,31 @@ set(FILES Include/Multiplayer/MultiplayerTypes.h Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h Include/Multiplayer/Components/MultiplayerComponent.h - Include/Multiplayer/Components/MultiplayerController.h Include/Multiplayer/Components/MultiplayerComponentRegistry.h + Include/Multiplayer/Components/MultiplayerController.h Include/Multiplayer/Components/NetBindComponent.h + Include/Multiplayer/Components/NetworkHierarchyChildComponent.h + Include/Multiplayer/Components/NetworkHierarchyRootComponent.h + Include/Multiplayer/Components/NetworkHierarchyBus.h Include/Multiplayer/Components/NetworkCharacterComponent.h Include/Multiplayer/Components/NetworkHitVolumesComponent.h Include/Multiplayer/Components/NetworkRigidBodyComponent.h Include/Multiplayer/Components/NetworkTransformComponent.h Include/Multiplayer/ConnectionData/IConnectionData.h Include/Multiplayer/EntityDomains/IEntityDomain.h - Include/Multiplayer/NetworkEntity/INetworkEntityManager.h + Include/Multiplayer/IMultiplayer.h + Include/Multiplayer/IMultiplayerTools.h Include/Multiplayer/INetworkSpawnableLibrary.h + Include/Multiplayer/MultiplayerConstants.h + Include/Multiplayer/MultiplayerStats.h + Include/Multiplayer/MultiplayerTypes.h + Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h Include/Multiplayer/NetworkEntity/IFilterEntityManager.h - Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h - Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h + Include/Multiplayer/NetworkEntity/INetworkEntityManager.h Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h Include/Multiplayer/NetworkEntity/NetworkEntityHandle.inl - Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h + Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h + Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h Include/Multiplayer/NetworkInput/NetworkInput.h Include/Multiplayer/NetworkTime/INetworkTime.h @@ -43,14 +51,11 @@ set(FILES Include/Multiplayer/NetworkTime/RewindableObject.inl Include/Multiplayer/Physics/PhysicsUtils.h Include/Multiplayer/ReplicationWindows/IReplicationWindow.h - Source/MultiplayerSystemComponent.cpp - Source/MultiplayerSystemComponent.h - Source/MultiplayerStats.cpp - Source/AutoGen/AutoComponent_Header.jinja - Source/AutoGen/AutoComponent_Source.jinja - Source/AutoGen/AutoComponent_Common.jinja Source/AutoGen/AutoComponentTypes_Header.jinja Source/AutoGen/AutoComponentTypes_Source.jinja + Source/AutoGen/AutoComponent_Common.jinja + Source/AutoGen/AutoComponent_Header.jinja + Source/AutoGen/AutoComponent_Source.jinja Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml Source/AutoGen/Multiplayer.AutoPackets.xml Source/AutoGen/MultiplayerEditor.AutoPackets.xml @@ -58,11 +63,15 @@ set(FILES Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml Source/AutoGen/NetworkTransformComponent.AutoComponent.xml + Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.xml + Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml Source/Components/LocalPredictionPlayerInputComponent.cpp Source/Components/MultiplayerComponent.cpp - Source/Components/MultiplayerController.cpp Source/Components/MultiplayerComponentRegistry.cpp + Source/Components/MultiplayerController.cpp Source/Components/NetBindComponent.cpp + Source/Components/NetworkHierarchyChildComponent.cpp + Source/Components/NetworkHierarchyRootComponent.cpp Source/Components/NetworkCharacterComponent.cpp Source/Components/NetworkHitVolumesComponent.cpp Source/Components/NetworkRigidBodyComponent.cpp @@ -77,6 +86,9 @@ set(FILES Source/Editor/MultiplayerEditorConnection.h Source/EntityDomains/FullOwnershipEntityDomain.cpp Source/EntityDomains/FullOwnershipEntityDomain.h + Source/MultiplayerStats.cpp + Source/MultiplayerSystemComponent.cpp + Source/MultiplayerSystemComponent.h Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp Source/NetworkEntity/EntityReplication/EntityReplicationManager.h Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -92,13 +104,13 @@ set(FILES Source/NetworkEntity/NetworkEntityHandle.cpp Source/NetworkEntity/NetworkEntityManager.cpp Source/NetworkEntity/NetworkEntityManager.h - Source/NetworkEntity/NetworkSpawnableLibrary.cpp - Source/NetworkEntity/NetworkSpawnableLibrary.h Source/NetworkEntity/NetworkEntityRpcMessage.cpp Source/NetworkEntity/NetworkEntityTracker.cpp Source/NetworkEntity/NetworkEntityTracker.h Source/NetworkEntity/NetworkEntityTracker.inl Source/NetworkEntity/NetworkEntityUpdateMessage.cpp + Source/NetworkEntity/NetworkSpawnableLibrary.cpp + Source/NetworkEntity/NetworkSpawnableLibrary.h Source/NetworkInput/NetworkInput.cpp Source/NetworkInput/NetworkInputArray.cpp Source/NetworkInput/NetworkInputArray.h @@ -110,11 +122,8 @@ set(FILES Source/NetworkInput/NetworkInputMigrationVector.h Source/NetworkTime/NetworkTime.cpp Source/NetworkTime/NetworkTime.h - Source/Pipeline/NetBindMarkerComponent.cpp - Source/Pipeline/NetBindMarkerComponent.h Source/Pipeline/NetworkSpawnableHolderComponent.cpp Source/Pipeline/NetworkSpawnableHolderComponent.h - Source/Physics/PhysicsUtils.cpp Source/ReplicationWindows/NullReplicationWindow.cpp Source/ReplicationWindows/NullReplicationWindow.h Source/ReplicationWindows/ServerToClientReplicationWindow.cpp diff --git a/Gems/Multiplayer/Code/multiplayer_tests_files.cmake b/Gems/Multiplayer/Code/multiplayer_tests_files.cmake index f385a21600..3f4fcc9efa 100644 --- a/Gems/Multiplayer/Code/multiplayer_tests_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_tests_files.cmake @@ -8,6 +8,10 @@ set(FILES Tests/Main.cpp + Tests/MockInterfaces.h + Tests/ClientHierarchyTests.cpp + Tests/ServerHierarchyTests.cpp + Tests/CommonHierarchySetup.h Tests/IMultiplayerConnectionMock.h Tests/MultiplayerSystemTests.cpp Tests/RewindableContainerTests.cpp diff --git a/Gems/Multiplayer/gem.json b/Gems/Multiplayer/gem.json index 8abbddea5f..47dbddfcbd 100644 --- a/Gems/Multiplayer/gem.json +++ b/Gems/Multiplayer/gem.json @@ -5,8 +5,19 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Multiplayer Gem provides a public API for multiplayer functionality such as connecting and hosting.", - "canonical_tags": ["Gem"], - "user_tags": ["Multiplayer", "Network", "Framework"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Multiplayer", + "Network", + "Framework" + ], "icon_path": "preview.png", - "requirements": "" + "requirements": "", + "dependencies": [ + "CertificateManager", + "Atom_Feature_Common", + "ImGui" + ] } diff --git a/Gems/MultiplayerCompression/gem.json b/Gems/MultiplayerCompression/gem.json index 178cca0ffe..7dd31476e3 100644 --- a/Gems/MultiplayerCompression/gem.json +++ b/Gems/MultiplayerCompression/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Multiplayer Compression Gem provides an open source Compressor for use with AzNetworking's transport layer.", - "canonical_tags": ["Gem"], - "user_tags": ["Multiplayer", "Network", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Multiplayer", + "Network", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/multiplayer/multiplayer-compression/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/multiplayer/multiplayer-compression/", + "dependencies": [] } diff --git a/Gems/NvCloth/gem.json b/Gems/NvCloth/gem.json index 49ccbf32f1..019ce23742 100644 --- a/Gems/NvCloth/gem.json +++ b/Gems/NvCloth/gem.json @@ -5,9 +5,19 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The NVIDIA Cloth Gem provides functionality to create fast, realistic cloth simulation with the NVIDIA Cloth library.", - "canonical_tags": ["Gem"], - "user_tags": ["Physics", "Simulation", "SDK"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Physics", + "Simulation", + "SDK" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/physics/nvidia/nvidia-cloth/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/physics/nvidia/nvidia-cloth/", + "dependencies": [ + "CommonFeaturesAtom", + "EMotionFX" + ] } diff --git a/Gems/PBSreferenceMaterials/gem.json b/Gems/PBSreferenceMaterials/gem.json index 563dd31a38..feddc1e465 100644 --- a/Gems/PBSreferenceMaterials/gem.json +++ b/Gems/PBSreferenceMaterials/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The PBS Reference Materials Gem provides physically based reference materials for Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Rendering", "Sample", "Assets"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Rendering", + "Sample", + "Assets" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/pbs-reference-materials/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/pbs-reference-materials/", + "dependencies": [] } diff --git a/Gems/PhysX/gem.json b/Gems/PhysX/gem.json index 0fbd3e44f9..bacbcf2dee 100644 --- a/Gems/PhysX/gem.json +++ b/Gems/PhysX/gem.json @@ -5,9 +5,19 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The PhysX Gem provides physics simulation with NVIDIA PhysX including static and dynamic rigid body simulation, force regions, ragdolls, and dynamic PhysX joints.", - "canonical_tags": ["Gem"], - "user_tags": ["Physics", "Simulation", "SDK"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Physics", + "Simulation", + "SDK" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/physics/nvidia/physx/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/physics/nvidia/physx/", + "dependencies": [ + "LmbrCentral", + "CommonFeaturesAtom" + ] } diff --git a/Gems/PhysXDebug/gem.json b/Gems/PhysXDebug/gem.json index f7877cabdd..ece0774210 100644 --- a/Gems/PhysXDebug/gem.json +++ b/Gems/PhysXDebug/gem.json @@ -5,9 +5,19 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The PhysX Debug Gem provides debugging functionality and visualizations for NVIDIA PhysX in Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Physics", "Simulation", "Debug"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Physics", + "Simulation", + "Debug" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/physics/nvidia/physx-debug/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/physics/nvidia/physx-debug/", + "dependencies": [ + "PhysX", + "ImGui" + ] } diff --git a/Gems/PhysXSamples/gem.json b/Gems/PhysXSamples/gem.json index e48dc6991b..0c84a29f47 100644 --- a/Gems/PhysXSamples/gem.json +++ b/Gems/PhysXSamples/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The PhysX Samples Gem provides sample assets and scripts that demonstrate PhysX Gem features in Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Physics", "Simulation", "Sample"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Physics", + "Simulation", + "Sample" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/physics/nvidia/physx-samples/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/physics/nvidia/physx-samples/", + "dependencies": [] } diff --git a/Gems/Prefab/PrefabBuilder/gem.json b/Gems/Prefab/PrefabBuilder/gem.json index ad6062ae3b..ba78f96358 100644 --- a/Gems/Prefab/PrefabBuilder/gem.json +++ b/Gems/Prefab/PrefabBuilder/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Prefab Builder Gem provides an Asset Processor module for prefabs, which are complex assets built by combining smaller entities.", - "canonical_tags": ["Gem"], - "user_tags": ["Assets", "Utility", "Core"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Assets", + "Utility", + "Core" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/prefab/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/prefab/", + "dependencies": [] } diff --git a/Gems/Presence/gem.json b/Gems/Presence/gem.json index 49d937946d..a70953ae4e 100644 --- a/Gems/Presence/gem.json +++ b/Gems/Presence/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Presence Gem provides a target platform agnostic interface for Presence services.", - "canonical_tags": ["Gem"], - "user_tags": ["Network", "Gameplay", "Framework"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Network", + "Gameplay", + "Framework" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/network/presence/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/network/presence/", + "dependencies": [] } diff --git a/Gems/PrimitiveAssets/gem.json b/Gems/PrimitiveAssets/gem.json index d1789d46fe..4ad3cb62ad 100644 --- a/Gems/PrimitiveAssets/gem.json +++ b/Gems/PrimitiveAssets/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The Primitive Assets Gem provides primitive shape mesh assets with physics enabled.", - "canonical_tags": ["Gem"], - "user_tags": ["Assets", "Sample", "Debug"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Assets", + "Sample", + "Debug" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/primitive-assets/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/primitive-assets/", + "dependencies": [] } diff --git a/Gems/PythonAssetBuilder/gem.json b/Gems/PythonAssetBuilder/gem.json index 1a76d7d2c2..ce30ba9e82 100644 --- a/Gems/PythonAssetBuilder/gem.json +++ b/Gems/PythonAssetBuilder/gem.json @@ -5,9 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Python Asset Builder Gem provides functionality to implement custom asset builders in Python for Asset Processor.", - "canonical_tags": ["Gem"], - "user_tags": ["Scripting", "Assets", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Scripting", + "Assets", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/python/python-asset-builder/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/python/python-asset-builder/", + "dependencies": [ + "EditorPythonBindings" + ] } diff --git a/Gems/QtForPython/gem.json b/Gems/QtForPython/gem.json index 1519d46c52..f83be43342 100644 --- a/Gems/QtForPython/gem.json +++ b/Gems/QtForPython/gem.json @@ -5,9 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Qt for Python Gem provides the PySide2 Python libraries to manage Qt widgets.", - "canonical_tags": ["Gem"], - "user_tags": ["Scripting", "UI", "Framework"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Scripting", + "UI", + "Framework" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/python/qt-for-python/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/python/qt-for-python/", + "dependencies": [ + "EditorPythonBindings" + ] } diff --git a/Gems/SaveData/gem.json b/Gems/SaveData/gem.json index 3892cc80c2..333b4682d2 100644 --- a/Gems/SaveData/gem.json +++ b/Gems/SaveData/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Save Data Gem provides a platform independent API to save and load persistent user data in Open 3D Engine projects.", - "canonical_tags": ["Gem"], - "user_tags": ["Utility", "Gameplay"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Utility", + "Gameplay" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/utility/save-data/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/utility/save-data/", + "dependencies": [] } diff --git a/Gems/SceneLoggingExample/gem.json b/Gems/SceneLoggingExample/gem.json index ff7def1b32..16961b9c5b 100644 --- a/Gems/SceneLoggingExample/gem.json +++ b/Gems/SceneLoggingExample/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The Scene Logging Example Gem demonstrates the basics of extending the Open 3D Engine Scene API by adding additional logging to the pipeline.", - "canonical_tags": ["Gem"], - "user_tags": ["Debug", "Sample"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Debug", + "Sample" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/utility/scene-logging-example/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/utility/scene-logging-example/", + "dependencies": [] } diff --git a/Gems/SceneProcessing/gem.json b/Gems/SceneProcessing/gem.json index a6c2cefbd6..de1dfeb23f 100644 --- a/Gems/SceneProcessing/gem.json +++ b/Gems/SceneProcessing/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Scene Processing Gem provides Scene Settings, a tool you can use to specify the default settings for processing asset files for actors, meshes, motions, and PhysX.", - "canonical_tags": ["Gem"], - "user_tags": ["Assets", "Tools", "Core"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Assets", + "Tools", + "Core" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/scene-processing/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/scene-processing/", + "dependencies": [] } diff --git a/Gems/ScriptCanvas/gem.json b/Gems/ScriptCanvas/gem.json index b6b5522d7d..621ea42961 100644 --- a/Gems/ScriptCanvas/gem.json +++ b/Gems/ScriptCanvas/gem.json @@ -5,9 +5,20 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Script Canvas Gem provides Open 3D Engine's visual scripting environment, Script Canvas.", - "canonical_tags": ["Gem"], - "user_tags": ["Scripting", "Tools", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Scripting", + "Tools", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/script-canvas/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/script-canvas/", + "dependencies": [ + "ScriptEvents", + "ExpressionEvaluation", + "GraphCanvas" + ] } diff --git a/Gems/ScriptCanvasDeveloper/gem.json b/Gems/ScriptCanvasDeveloper/gem.json index d7b0aefad3..ee1bfcec84 100644 --- a/Gems/ScriptCanvasDeveloper/gem.json +++ b/Gems/ScriptCanvasDeveloper/gem.json @@ -5,9 +5,19 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Script Canvas Developer Gem provides a suite of utility features for the development and debugging of Script Canvas systems.", - "canonical_tags": ["Gem"], - "user_tags": ["Scripting", "Utility", "Debug"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Scripting", + "Utility", + "Debug" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/script-canvas-developer/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/script-canvas-developer/", + "dependencies": [ + "ScriptCanvas", + "GraphCanvas" + ] } diff --git a/Gems/ScriptCanvasPhysics/gem.json b/Gems/ScriptCanvasPhysics/gem.json index 46e2ef1194..417fa6893a 100644 --- a/Gems/ScriptCanvasPhysics/gem.json +++ b/Gems/ScriptCanvasPhysics/gem.json @@ -5,9 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Script Canvas Physics Gem provides Script Canvas nodes for physics scene queries such as raycasts.", - "canonical_tags": ["Gem"], - "user_tags": ["Scripting", "Physics", "Simulation"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Scripting", + "Physics", + "Simulation" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/script-canvas-physics/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/script-canvas-physics/", + "dependencies": [ + "ScriptCanvas" + ] } diff --git a/Gems/ScriptCanvasTesting/gem.json b/Gems/ScriptCanvasTesting/gem.json index 303eab8c7e..c45d2ac165 100644 --- a/Gems/ScriptCanvasTesting/gem.json +++ b/Gems/ScriptCanvasTesting/gem.json @@ -5,9 +5,20 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Script Canvas Testing Gem provides a framework for testing for and with Script Canvas.", - "canonical_tags": ["Gem"], - "user_tags": ["Scripting", "Debug", "Framework"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Scripting", + "Debug", + "Framework" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/script-canvas-testing/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/script-canvas-testing/", + "dependencies": [ + "ScriptCanvas", + "GraphCanvas", + "ScriptEvents" + ] } diff --git a/Gems/ScriptEvents/gem.json b/Gems/ScriptEvents/gem.json index 2b7d74ef3f..386d9b5614 100644 --- a/Gems/ScriptEvents/gem.json +++ b/Gems/ScriptEvents/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Script Events Gem provides a framework for creating event assets usable from any scripting solution in Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Scripting", "Framework", "Gameplay"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Scripting", + "Framework", + "Gameplay" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/script-events/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/script-events/", + "dependencies": [] } diff --git a/Gems/ScriptedEntityTweener/gem.json b/Gems/ScriptedEntityTweener/gem.json index a0f558356b..c51f05df9a 100644 --- a/Gems/ScriptedEntityTweener/gem.json +++ b/Gems/ScriptedEntityTweener/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Scripted Entity Tweener Gem provides a script driven animation system for Open 3D Engine projects.", - "canonical_tags": ["Gem"], - "user_tags": ["Scripting", "UI", "Animation"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Scripting", + "UI", + "Animation" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/scripted-entity-tweener/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/script/scripted-entity-tweener/", + "dependencies": [] } diff --git a/Gems/SliceFavorites/gem.json b/Gems/SliceFavorites/gem.json index 3ae37e2145..84f42fc1a2 100644 --- a/Gems/SliceFavorites/gem.json +++ b/Gems/SliceFavorites/gem.json @@ -5,7 +5,11 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "Add the ability to favorite a slice to allow easy access and instantiation", - "user_tags": ["Editor", "Slices"], + "user_tags": [ + "Editor", + "Slices" + ], "icon_path": "preview.png", - "requirements": "" + "requirements": "", + "dependencies": [] } diff --git a/Gems/StartingPointCamera/gem.json b/Gems/StartingPointCamera/gem.json index 5a9aa0df9b..613eb76267 100644 --- a/Gems/StartingPointCamera/gem.json +++ b/Gems/StartingPointCamera/gem.json @@ -5,9 +5,19 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Starting Point Camera Gem provides the behaviors used with the Camera Framework Gem to define a camera rig.", - "canonical_tags": ["Gem"], - "user_tags": ["Rendering", "Gameplay", "Scripting"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Rendering", + "Gameplay", + "Scripting" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/starting-point-camera/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/starting-point-camera/", + "dependencies": [ + "CameraFramework", + "LmbrCentral" + ] } diff --git a/Gems/StartingPointInput/gem.json b/Gems/StartingPointInput/gem.json index e65f271fe5..d2641ea27b 100644 --- a/Gems/StartingPointInput/gem.json +++ b/Gems/StartingPointInput/gem.json @@ -5,9 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Starting Point Input Gem provides functionality to map low-level input events to high-level actions.", - "canonical_tags": ["Gem"], - "user_tags": ["Input", "Gameplay", "Scripting"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Input", + "Gameplay", + "Scripting" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/input/starting-point-input/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/input/starting-point-input/", + "dependencies": [ + "ScriptCanvas" + ] } diff --git a/Gems/StartingPointMovement/gem.json b/Gems/StartingPointMovement/gem.json index 70e1d105c1..7def6da768 100644 --- a/Gems/StartingPointMovement/gem.json +++ b/Gems/StartingPointMovement/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Starting Point Movement Gem provides a series of Lua scripts that listen and respond to input events and trigger transform operations such as translation and rotation.", - "canonical_tags": ["Gem"], - "user_tags": ["Input", "Gameplay", "Scripting"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Input", + "Gameplay", + "Scripting" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/input/starting-point-movement/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/input/starting-point-movement/", + "dependencies": [] } diff --git a/Gems/SurfaceData/gem.json b/Gems/SurfaceData/gem.json index 57e4d8fb70..51a134d5df 100644 --- a/Gems/SurfaceData/gem.json +++ b/Gems/SurfaceData/gem.json @@ -5,9 +5,20 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Surface Data Gem provides functionality to emit signals or tags from surfaces such as meshes and terrain.", - "canonical_tags": ["Gem"], - "user_tags": ["Environment", "Utility", "Design"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Environment", + "Utility", + "Design" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/environment/surface-data/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/environment/surface-data/", + "dependencies": [ + "LmbrCentral", + "Atom_RPI", + "Atom_Feature_Common" + ] } diff --git a/Gems/Terrain/gem.json b/Gems/Terrain/gem.json index c01ea7e534..cd72a91708 100644 --- a/Gems/Terrain/gem.json +++ b/Gems/Terrain/gem.json @@ -4,7 +4,21 @@ "license": "Apache-2.0 Or MIT", "origin": "Open 3D Engine - o3de.org", "summary": "The Terrain Gem is an experimental terrain system. The terrain system maps height, color, and surface data to regions of the world, provides gradient-based and shape-based authoring tools and workflows, includes specialized rendering for efficient display, and integrates with physics for physical simulation.", - "canonical_tags": [ "Gem" ], - "user_tags": [ "Environment", "Tools", "Design", "Terrain" ], - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/terrain/" + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Environment", + "Tools", + "Design", + "Terrain" + ], + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/terrain/", + "dependencies": [ + "Atom_RPI", + "Atom", + "GradientSignal", + "SurfaceData", + "LmbrCentral" + ] } diff --git a/Gems/TestAssetBuilder/gem.json b/Gems/TestAssetBuilder/gem.json index 1e0b84894a..ba7568005f 100644 --- a/Gems/TestAssetBuilder/gem.json +++ b/Gems/TestAssetBuilder/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Test Asset Builder Gem is used to feature test Asset Processor.", - "canonical_tags": ["Gem"], - "user_tags": ["Assets", "Debug", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Assets", + "Debug", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/test-asset-builder/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/assets/test-asset-builder/", + "dependencies": [] } diff --git a/Gems/TextureAtlas/gem.json b/Gems/TextureAtlas/gem.json index 832dee0e62..345e085359 100644 --- a/Gems/TextureAtlas/gem.json +++ b/Gems/TextureAtlas/gem.json @@ -5,9 +5,19 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Texture Atlas Gem provides the formatting for texture atlases from 2D textures for LyShine.", - "canonical_tags": ["Gem"], - "user_tags": ["Rendering", "Assets", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Rendering", + "Assets", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/utility/texture-atlas/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/utility/texture-atlas/", + "dependencies": [ + "Atom_RPI", + "ImageProcessingAtom" + ] } diff --git a/Gems/TickBusOrderViewer/gem.json b/Gems/TickBusOrderViewer/gem.json index 5ea936960a..dc5cb6f66c 100644 --- a/Gems/TickBusOrderViewer/gem.json +++ b/Gems/TickBusOrderViewer/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Tick Bus Order Viewer Gem provides a console variable that displays the order of runtime tick events.", - "canonical_tags": ["Gem"], - "user_tags": ["Gameplay", "Simulation", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Gameplay", + "Simulation", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/gameplay/tick-bus-order-viewer/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/gameplay/tick-bus-order-viewer/", + "dependencies": [] } diff --git a/Gems/Twitch/gem.json b/Gems/Twitch/gem.json index 6f875e9171..f45433bc3f 100644 --- a/Gems/Twitch/gem.json +++ b/Gems/Twitch/gem.json @@ -5,9 +5,18 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Twitch Gem provides access to the Twitch API v5 SDK including social functions, channels, and other APIs.", - "canonical_tags": ["Gem"], - "user_tags": ["Network", "SDK", "Multiplayer"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Network", + "SDK", + "Multiplayer" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/network/twitch/twitch/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/network/twitch/twitch/", + "dependencies": [ + "HttpRequestor" + ] } diff --git a/Gems/UiBasics/gem.json b/Gems/UiBasics/gem.json index d1e06eb15a..9a1e16a462 100644 --- a/Gems/UiBasics/gem.json +++ b/Gems/UiBasics/gem.json @@ -5,9 +5,16 @@ "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The UI Basics Gem provides a collection of basic UI prefabs such as image, text, and button, that can be used with LyShine, the Open 3D Engine runtime User Interface system and editor.", - "canonical_tags": ["Gem"], - "user_tags": ["UI", "Assets", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "UI", + "Assets", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/ui/ui-basics/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/ui/ui-basics/", + "dependencies": [] } diff --git a/Gems/Vegetation/gem.json b/Gems/Vegetation/gem.json index 3119d67560..9dbcc3450b 100644 --- a/Gems/Vegetation/gem.json +++ b/Gems/Vegetation/gem.json @@ -5,9 +5,21 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Vegetation Gem provides tools to place natural-looking vegetation in Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Environment", "Tools", "Design"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Environment", + "Tools", + "Design" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/environment/vegetation/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/environment/vegetation/", + "dependencies": [ + "LmbrCentral", + "SurfaceData", + "CommonFeaturesAtom", + "GradientSignal" + ] } diff --git a/Gems/VideoPlaybackFramework/gem.json b/Gems/VideoPlaybackFramework/gem.json index 15b200c8db..9f491f47cb 100644 --- a/Gems/VideoPlaybackFramework/gem.json +++ b/Gems/VideoPlaybackFramework/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Video Playback Framework Gem provides the interface to play back video.", - "canonical_tags": ["Gem"], - "user_tags": ["Rendering", "Framework"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Rendering", + "Framework" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/video-playback-framework/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/video-playback-framework/", + "dependencies": [] } diff --git a/Gems/VirtualGamepad/gem.json b/Gems/VirtualGamepad/gem.json index 472e8b93fe..c6305f1754 100644 --- a/Gems/VirtualGamepad/gem.json +++ b/Gems/VirtualGamepad/gem.json @@ -5,9 +5,15 @@ "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Virtual Gamepad Gem provides controls that emulate a gamepad on touch screen devices.", - "canonical_tags": ["Gem"], - "user_tags": ["Input", "Gameplay"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Input", + "Gameplay" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/input/virtual-gamepad/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/input/virtual-gamepad/", + "dependencies": [] } diff --git a/Gems/WhiteBox/gem.json b/Gems/WhiteBox/gem.json index 0e44e2fe5e..81e0ad5f88 100644 --- a/Gems/WhiteBox/gem.json +++ b/Gems/WhiteBox/gem.json @@ -5,9 +5,20 @@ "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The White Box Gem provides White Box rapid design components for Open 3D Engine.", - "canonical_tags": ["Gem"], - "user_tags": ["Design", "Tools", "Utility"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Design", + "Tools", + "Utility" + ], "icon_path": "preview.png", "requirements": "", - "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/design/white-box/" + "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/design/white-box/", + "dependencies": [ + "Atom_RPI", + "Atom_Feature_Common", + "CommonFeaturesAtom" + ] } diff --git a/cmake/Platform/Mac/PAL_mac.cmake b/cmake/Platform/Mac/PAL_mac.cmake index 065b35d69c..561ce86570 100644 --- a/cmake/Platform/Mac/PAL_mac.cmake +++ b/cmake/Platform/Mac/PAL_mac.cmake @@ -37,6 +37,10 @@ endif() # Set the default asset type for deployment set(LY_ASSET_DEPLOY_ASSET_TYPE "mac" CACHE STRING "Set the asset type for deployment.") +# Set the deployment target for MacOS +set(LY_MAC_DEPLOYMENT_TARGET "11.0" CACHE STRING "Mac Deployment Target") +set(CMAKE_OSX_DEPLOYMENT_TARGET ${LY_MAC_DEPLOYMENT_TARGET}) + # Set the python cmd tool ly_set(LY_PYTHON_CMD ${CMAKE_CURRENT_SOURCE_DIR}/python/python.sh) diff --git a/cmake/Platform/iOS/Toolchain_ios.cmake b/cmake/Platform/iOS/Toolchain_ios.cmake index 49f0b23461..719c492ebb 100644 --- a/cmake/Platform/iOS/Toolchain_ios.cmake +++ b/cmake/Platform/iOS/Toolchain_ios.cmake @@ -13,7 +13,7 @@ set(CMAKE_OSX_ARCHITECTURES arm64) set(LY_IOS_CODE_SIGNING_IDENTITY "iPhone Developer" CACHE STRING "iPhone Developer") -set(LY_IOS_DEPLOYMENT_TARGET "13.0" CACHE STRING "iOS Deployment Target") +set(LY_IOS_DEPLOYMENT_TARGET "14.0" CACHE STRING "iOS Deployment Target") set(LY_IOS_DEVELOPMENT_TEAM "CF9TGN983S" CACHE STRING "The development team ID") diff --git a/scripts/build/Jenkins/Jenkinsfile b/scripts/build/Jenkins/Jenkinsfile index 5bc4b919fb..5fe19ddf46 100644 --- a/scripts/build/Jenkins/Jenkinsfile +++ b/scripts/build/Jenkins/Jenkinsfile @@ -9,6 +9,7 @@ import groovy.json.JsonOutput PIPELINE_CONFIG_FILE = 'scripts/build/Jenkins/lumberyard.json' INCREMENTAL_BUILD_SCRIPT_PATH = 'scripts/build/bootstrap/incremental_build_util.py' +PIPELINE_RETRY_ATTEMPTS = 3 EMPTY_JSON = readJSON text: '{}' @@ -502,64 +503,75 @@ def CreateTeardownStage(Map environmentVars) { def CreateSingleNode(Map pipelineConfig, def platform, def build_job, Map envVars, String branchName, String pipelineName, String repositoryName, String projectName, boolean onlyMountEBSVolume = false) { def nodeLabel = envVars['NODE_LABEL'] return { - node("${nodeLabel}") { - if(isUnix()) { // Has to happen inside a node - envVars['IS_UNIX'] = 1 - } - withEnv(GetEnvStringList(envVars)) { - def build_job_name = build_job.key - try { - CreateSetupStage(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platform.key, build_job.key, envVars, onlyMountEBSVolume).call() + def currentResult = '' + def currentException = '' + retry(PIPELINE_RETRY_ATTEMPTS) { + node("${nodeLabel}") { + if(isUnix()) { // Has to happen inside a node + envVars['IS_UNIX'] = 1 + } + withEnv(GetEnvStringList(envVars)) { + def build_job_name = build_job.key + try { + CreateSetupStage(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platform.key, build_job.key, envVars, onlyMountEBSVolume).call() - if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages - build_job.value.steps.each { build_step -> - build_job_name = build_step - envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, platform.value.build_types[build_step].PIPELINE_ENV ?: EMPTY_JSON, pipelineName) - try { - CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call() + if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages + build_job.value.steps.each { build_step -> + build_job_name = build_step + envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, platform.value.build_types[build_step].PIPELINE_ENV ?: EMPTY_JSON, pipelineName) + try { + CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call() + } + catch (Exception e) { + if (envVars['NONBLOCKING_STEP']?.toBoolean()) { + unstable(message: "Build step ${build_step} failed but it's a non-blocking step in build job ${build_job.key}") + } else { + throw e + } + } } - catch (Exception e) { - if (envVars['NONBLOCKING_STEP']?.toBoolean()) { - unstable(message: "Build step ${build_step} failed but it's a non-blocking step in build job ${build_job.key}") - } - else { - error "FAILURE: ${e}" - } + } else { + CreateBuildStage(pipelineConfig, platform.key, build_job.key, envVars).call() + } + } + catch(Exception e) { + if (e instanceof org.jenkinsci.plugins.workflow.steps.FlowInterruptedException) { + def causes = e.getCauses().toString() + if (causes.contains('RemovedNodeCause')) { + error "Node disconnected during build: ${e}" // Error raised to retry stage on a new node } } - } else { - CreateBuildStage(pipelineConfig, platform.key, build_job.key, envVars).call() + // All other errors will be raised outside the retry block + currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE' + currentException = e.toString() } - } - catch(Exception e) { - // https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/Result.java - // {SUCCESS,UNSTABLE,FAILURE,NOT_BUILT,ABORTED} - def currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE' - if (currentResult == 'FAILURE') { - currentBuild.result = 'FAILURE' - error "FAILURE: ${e}" - } else if (currentResult == 'UNSTABLE') { - currentBuild.result = 'UNSTABLE' - unstable(message: "UNSTABLE: ${e}") + finally { + def params = platform.value.build_types[build_job_name].PARAMETERS + if (env.MARS_REPO && params && params.containsKey('TEST_METRICS') && params.TEST_METRICS == 'True') { + def output_directory = params.OUTPUT_DIRECTORY + def configuration = params.CONFIGURATION + CreateTestMetricsStage(pipelineConfig, branchName, envVars, build_job_name, output_directory, configuration).call() + } + if (params && params.containsKey('TEST_RESULTS') && params.TEST_RESULTS == 'True') { + CreateExportTestResultsStage(pipelineConfig, platform.key, build_job_name, envVars, params).call() + } + if (params && params.containsKey('TEST_SCREENSHOTS') && params.TEST_SCREENSHOTS == 'True' && currentResult == 'FAILURE') { + CreateExportTestScreenshotsStage(pipelineConfig, platform.key, build_job_name, envVars, params).call() + } + CreateTeardownStage(envVars).call() } } - finally { - def params = platform.value.build_types[build_job_name].PARAMETERS - if (env.MARS_REPO && params && params.containsKey('TEST_METRICS') && params.TEST_METRICS == 'True') { - def output_directory = params.OUTPUT_DIRECTORY - def configuration = params.CONFIGURATION - CreateTestMetricsStage(pipelineConfig, branchName, envVars, build_job_name, output_directory, configuration).call() - } - if (params && params.containsKey('TEST_RESULTS') && params.TEST_RESULTS == 'True') { - CreateExportTestResultsStage(pipelineConfig, platform.key, build_job_name, envVars, params).call() - } - if (params && params.containsKey('TEST_SCREENSHOTS') && params.TEST_SCREENSHOTS == 'True' && currentResult == 'FAILURE') { - CreateExportTestScreenshotsStage(pipelineConfig, platform.key, build_job_name, envVars, params).call() - } - CreateTeardownStage(envVars).call() - } } } + // https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/Result.java + // {SUCCESS,UNSTABLE,FAILURE,NOT_BUILT,ABORTED} + if (currentResult == 'FAILURE') { + currentBuild.result = 'FAILURE' + error "FAILURE: ${currentException}" + } else if (currentResult == 'UNSTABLE') { + currentBuild.result = 'UNSTABLE' + unstable(message: "UNSTABLE: ${currentException}") + } } }