Merge branch 'development' into Atom/guthadam/atomtools_support_ly_set_gem_variant_to_load
This commit is contained in:
@@ -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="$<TARGET_FILE_BASE_NAME:Camera.Editor>"
|
||||
)
|
||||
|
||||
ly_add_googletest(
|
||||
NAME Legacy::EditorLib.Camera.Tests
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <EditorModularViewportCameraComposer.h>
|
||||
|
||||
#include <AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzFramework/Render/IntersectorInterface.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportMessages.h>
|
||||
@@ -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
|
||||
|
||||
@@ -10,13 +10,16 @@
|
||||
|
||||
#include <AtomToolsFramework/Viewport/ModularViewportCameraController.h>
|
||||
#include <AzFramework/Viewport/CameraInput.h>
|
||||
#include <AzToolsFramework/API/EditorCameraBus.h>
|
||||
#include <EditorModularViewportCameraComposerBus.h>
|
||||
#include <SandboxAPI.h>
|
||||
|
||||
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<AzFramework::RotateCameraInput> m_firstPersonRotateCamera;
|
||||
AZStd::shared_ptr<AzFramework::PanCameraInput> m_firstPersonPanCamera;
|
||||
AZStd::shared_ptr<AzFramework::TranslateCameraInput> m_firstPersonTranslateCamera;
|
||||
|
||||
@@ -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
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
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 <AZTestShared/Math/MathTestHelpers.h>
|
||||
#include <AtomToolsFramework/Viewport/ModularViewportCameraController.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/Viewport/ViewportControllerList.h>
|
||||
#include <AzTest/GemTestEnvironment.h>
|
||||
#include <AzToolsFramework/API/EditorCameraBus.h>
|
||||
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
||||
#include <EditorModularViewportCameraComposer.h>
|
||||
|
||||
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<SandboxEditor::EditorModularViewportCameraComposer> m_editorModularViewportCameraComposer;
|
||||
AZStd::unique_ptr<AZ::DynamicModuleHandle> m_editorLibHandle;
|
||||
AzFramework::ViewportControllerListPtr m_controllerList;
|
||||
AZStd::unique_ptr<AZ::Entity> 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<AzFramework::ViewportControllerList>();
|
||||
m_controllerList->RegisterViewportContext(TestViewportId);
|
||||
|
||||
m_entity = AZStd::make_unique<AZ::Entity>();
|
||||
m_entity->Init();
|
||||
m_entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
m_entity->Activate();
|
||||
|
||||
m_editorModularViewportCameraComposer = AZStd::make_unique<SandboxEditor::EditorModularViewportCameraComposer>(TestViewportId);
|
||||
|
||||
auto controller = m_editorModularViewportCameraComposer->CreateModularViewportCameraController();
|
||||
// set some overrides for the test
|
||||
controller->SetCameraViewportContextBuilderCallback(
|
||||
[this](AZStd::unique_ptr<AtomToolsFramework::ModularCameraViewportContext>& cameraViewportContext) mutable
|
||||
{
|
||||
cameraViewportContext = AZStd::make_unique<AtomToolsFramework::PlaceholderModularCameraViewportContextImpl>();
|
||||
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();
|
||||
@@ -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<AtomToolsFramework::ModularCameraViewportContext>& cameraViewportContext)
|
||||
{
|
||||
cameraViewportContext = AZStd::make_unique<TestModularCameraViewportContextImpl>();
|
||||
cameraViewportContext = AZStd::make_unique<AtomToolsFramework::PlaceholderModularCameraViewportContextImpl>();
|
||||
m_cameraViewportContextView = cameraViewportContext.get();
|
||||
});
|
||||
|
||||
|
||||
+4
-4
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+23
@@ -116,11 +116,18 @@ namespace AtomToolsFramework
|
||||
// ModularViewportCameraControllerRequestBus overrides ...
|
||||
void InterpolateToTransform(const AZ::Transform& worldFromLocal, float lookAtDistance) override;
|
||||
AZStd::optional<AZ::Vector3> 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<AzFramework::Camera> 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<AZ::Vector3> 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<ModularCameraViewportContext> 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
|
||||
|
||||
+10
@@ -35,6 +35,16 @@ namespace AtomToolsFramework
|
||||
//! Look at point after an interpolation has finished and no translation has occurred.
|
||||
virtual AZStd::optional<AZ::Vector3> 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;
|
||||
};
|
||||
|
||||
+83
-7
@@ -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
|
||||
|
||||
@@ -57,6 +57,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
PRIVATE
|
||||
AZ::AzToolsFramework
|
||||
Gem::Camera.Static
|
||||
Gem::AtomToolsFramework.Static
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::EditorCommon
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<AnimGraphMotionNode*>(m_object);
|
||||
AZ_Assert(motionNode, "Unique data linked to incorrect node type.");
|
||||
|
||||
|
||||
@@ -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<UniqueData*>(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<UniqueData*>(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<UniqueData*>(FindOrCreateUniqueNodeData(animGraphInstance));
|
||||
|
||||
if (!IsTransitioning(uniqueData))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/EBus/Event.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
|
||||
|
||||
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<AzFramework::EntitySpawnTicket> RequestNetSpawnableInstantiation(
|
||||
const AZ::Data::Asset<AzFramework::Spawnable>& 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
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <Source/MultiplayerGem.h>
|
||||
#include <Source/MultiplayerSystemComponent.h>
|
||||
#include <Source/AutoGen/AutoComponentTypes.h>
|
||||
#include <Source/Pipeline/NetBindMarkerComponent.h>
|
||||
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
|
||||
#include <Multiplayer/Components/NetBindComponent.h>
|
||||
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
|
||||
@@ -23,7 +22,6 @@ namespace Multiplayer
|
||||
AzNetworking::NetworkingSystemComponent::CreateDescriptor(),
|
||||
MultiplayerSystemComponent::CreateDescriptor(),
|
||||
NetBindComponent::CreateDescriptor(),
|
||||
NetBindMarkerComponent::CreateDescriptor(),
|
||||
NetworkSpawnableHolderComponent::CreateDescriptor(),
|
||||
});
|
||||
|
||||
|
||||
@@ -465,8 +465,60 @@ namespace Multiplayer
|
||||
return netEntityId;
|
||||
}
|
||||
|
||||
void NetworkEntityManager::OnRootSpawnableAssigned(
|
||||
[[maybe_unused]] AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation)
|
||||
AZStd::unique_ptr<AzFramework::EntitySpawnTicket> NetworkEntityManager::RequestNetSpawnableInstantiation(
|
||||
const AZ::Data::Asset<AzFramework::Spawnable>& 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<INetworkSpawnableLibrary>::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<uint32_t>(entities.size());
|
||||
netEntityIndex < entitiesSize; ++netEntityIndex)
|
||||
{
|
||||
AZ::Entity* netEntity = *(entities.begin() + netEntityIndex);
|
||||
|
||||
if (shouldUpdateTransform)
|
||||
{
|
||||
AzFramework::TransformComponent* netEntityTransform =
|
||||
netEntity->FindComponent<AzFramework::TransformComponent>();
|
||||
|
||||
AZ::Transform worldTm = netEntityTransform->GetWorldTM();
|
||||
worldTm = rootTransform * worldTm;
|
||||
netEntityTransform->SetWorldTM(worldTm);
|
||||
}
|
||||
|
||||
PrefabEntityId prefabEntityId;
|
||||
prefabEntityId.m_prefabName = netSpawnableName;
|
||||
prefabEntityId.m_entityOffset = netEntityIndex;
|
||||
AZ::Interface<INetworkEntityManager>::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<AzFramework::EntitySpawnTicket>(netSpawnable);
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*ticket, AZStd::move(optionalArgs));
|
||||
return ticket;
|
||||
}
|
||||
|
||||
void NetworkEntityManager::OnRootSpawnableAssigned(AZ::Data::Asset<AzFramework::Spawnable> 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();
|
||||
|
||||
|
||||
@@ -60,6 +60,9 @@ namespace Multiplayer
|
||||
const AZ::Transform& transform
|
||||
) override;
|
||||
|
||||
AZStd::unique_ptr<AzFramework::EntitySpawnTicket> RequestNetSpawnableInstantiation(
|
||||
const AZ::Data::Asset<AzFramework::Spawnable>& netSpawnable, const AZ::Transform& transform) override;
|
||||
|
||||
void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override;
|
||||
|
||||
uint32_t GetEntityCount() const override;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 <Source/Pipeline/NetBindMarkerComponent.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <Multiplayer/IMultiplayer.h>
|
||||
#include <Multiplayer/INetworkSpawnableLibrary.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
void NetBindMarkerComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<NetBindMarkerComponent, AZ::Component>()
|
||||
->Version(1)
|
||||
->Field("NetEntityIndex", &NetBindMarkerComponent::m_netEntityIndex)
|
||||
->Field("NetSpawnableAsset", &NetBindMarkerComponent::m_networkSpawnableAsset);
|
||||
}
|
||||
}
|
||||
|
||||
AzFramework::Spawnable* GetSpawnableFromAsset(AZ::Data::Asset<AzFramework::Spawnable>& asset)
|
||||
{
|
||||
AzFramework::Spawnable* spawnable = asset.GetAs<AzFramework::Spawnable>();
|
||||
if (!spawnable)
|
||||
{
|
||||
asset =
|
||||
AZ::Data::AssetManager::Instance().GetAsset<AzFramework::Spawnable>(asset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad);
|
||||
AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(asset);
|
||||
|
||||
spawnable = asset.GetAs<AzFramework::Spawnable>();
|
||||
}
|
||||
|
||||
return spawnable;
|
||||
}
|
||||
|
||||
|
||||
void NetBindMarkerComponent::Activate()
|
||||
{
|
||||
const auto agentType = AZ::Interface<IMultiplayer>::Get()->GetAgentType();
|
||||
const bool spawnImmediately =
|
||||
(agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer);
|
||||
|
||||
if (spawnImmediately && m_networkSpawnableAsset.GetId().IsValid())
|
||||
{
|
||||
AZ::Transform worldTm = GetEntity()->FindComponent<AzFramework::TransformComponent>()->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<AzFramework::TransformComponent>();
|
||||
transformComponent->SetWorldTM(worldTm);
|
||||
|
||||
AZ::Name spawnableName = AZ::Interface<INetworkSpawnableLibrary>::Get()->GetSpawnableNameFromAssetId(spawnableAssetId);
|
||||
PrefabEntityId prefabEntityId;
|
||||
prefabEntityId.m_prefabName = spawnableName;
|
||||
prefabEntityId.m_entityOffset = static_cast<uint32_t>(netEntityIndex);
|
||||
AZ::Interface<INetworkEntityManager>::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<AzFramework::Spawnable> networkSpawnableAsset)
|
||||
{
|
||||
m_networkSpawnableAsset = networkSpawnableAsset;
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AzFramework::Spawnable> NetBindMarkerComponent::GetNetworkSpawnableAsset() const
|
||||
{
|
||||
return m_networkSpawnableAsset;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <AzCore/Component/Component.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
|
||||
|
||||
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<AzFramework::Spawnable> networkSpawnableAsset);
|
||||
AZ::Data::Asset<AzFramework::Spawnable> GetNetworkSpawnableAsset() const;
|
||||
|
||||
private:
|
||||
AZ::Data::Asset<AzFramework::Spawnable> m_networkSpawnableAsset{AZ::Data::AssetLoadBehavior::PreLoad};
|
||||
size_t m_netEntityIndex = 0;
|
||||
AzFramework::EntitySpawnTicket m_netSpawnTicket;
|
||||
};
|
||||
} // namespace Multiplayer
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <Multiplayer/IMultiplayerTools.h>
|
||||
#include <Multiplayer/Components/NetBindComponent.h>
|
||||
#include <Pipeline/NetBindMarkerComponent.h>
|
||||
#include <Pipeline/NetworkPrefabProcessor.h>
|
||||
#include <Pipeline/NetworkSpawnableHolderComponent.h>
|
||||
|
||||
@@ -46,7 +45,7 @@ namespace Multiplayer
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
|
||||
{
|
||||
serializeContext->Class<NetworkPrefabProcessor, PrefabProcessor>()->Version(1);
|
||||
serializeContext->Class<NetworkPrefabProcessor, PrefabProcessor>()->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>();
|
||||
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<AzFramework::TransformComponent>();
|
||||
breadcrumbEntity->CreateComponent<AzFramework::TransformComponent>(*transformComponent);
|
||||
|
||||
instance->AddEntity(*breadcrumbEntity);
|
||||
|
||||
netEntitiesIndexCounter++;
|
||||
}
|
||||
|
||||
// Add net spawnable asset holder to the prefab root
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
#include <Multiplayer/IMultiplayer.h>
|
||||
|
||||
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<AzFramework::TransformComponent>();
|
||||
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<AzFramework::Spawnable> networkSpawnableAsset)
|
||||
@@ -39,7 +63,7 @@ namespace Multiplayer
|
||||
m_networkSpawnableAsset = networkSpawnableAsset;
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AzFramework::Spawnable> NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset()
|
||||
AZ::Data::Asset<AzFramework::Spawnable> NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset() const
|
||||
{
|
||||
return m_networkSpawnableAsset;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
@@ -33,9 +34,10 @@ namespace Multiplayer
|
||||
//! @}
|
||||
|
||||
void SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset);
|
||||
AZ::Data::Asset<AzFramework::Spawnable> GetNetworkSpawnableAsset();
|
||||
AZ::Data::Asset<AzFramework::Spawnable> GetNetworkSpawnableAsset() const;
|
||||
|
||||
private:
|
||||
AZ::Data::Asset<AzFramework::Spawnable> m_networkSpawnableAsset{ AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
AZStd::unique_ptr<AzFramework::EntitySpawnTicket> m_netSpawnableTicket;
|
||||
};
|
||||
} // namespace Multiplayer
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <AzTest/GemTestEnvironment.h>
|
||||
#include <QApplication>
|
||||
#include <Multiplayer/Components/NetBindComponent.h>
|
||||
#include <Source/Pipeline/NetBindMarkerComponent.h>
|
||||
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
|
||||
#include <UnitTest/ToolsTestApplication.h>
|
||||
|
||||
@@ -30,7 +29,6 @@ namespace Multiplayer
|
||||
{
|
||||
AZStd::vector<AZ::ComponentDescriptor*> descriptors({
|
||||
NetBindComponent::CreateDescriptor(),
|
||||
NetBindMarkerComponent::CreateDescriptor(),
|
||||
NetworkSpawnableHolderComponent::CreateDescriptor()
|
||||
});
|
||||
|
||||
|
||||
@@ -110,8 +110,6 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user