Add support for camera reference frames to support camera yaw in certain situations (#4088)

* proof of concept change for fixing camera yaw rotation being ignored

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* updates to fully support camera roll

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* small fixes for PR

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* undo changes in EditorCameraComponent

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* add test to verify interaction between editor viewport view entity change and modular camera controller

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* add additional tests for updated camera behaviors

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* revert change to Camera Gem Output Name

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* move location of new files

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* fix tab/spaces issue

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* remove static from potentially unused functions

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* set camera lib name in CMakeLists.txt file

Signed-off-by: hultonha <hultonha@amazon.co.uk>

* cosmetic CMakeLists.txt change

Signed-off-by: hultonha <hultonha@amazon.co.uk>
This commit is contained in:
hultonha
2021-09-22 09:08:24 +01:00
committed by GitHub
parent 2c55ad5379
commit b865d2aec1
12 changed files with 418 additions and 36 deletions
+31
View File
@@ -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();
});
@@ -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();
}
}
@@ -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;