Overhaul of how camera orbit/pivot behavior works (#4345)
* overhaul to how camera orbit/pivot behavior works Signed-off-by: hultonha <hultonha@amazon.co.uk> * update naming from orbit to pivot Signed-off-by: hultonha <hultonha@amazon.co.uk> * fix camera unit tests Signed-off-by: hultonha <hultonha@amazon.co.uk> * add additional tests for new camera pivot behavior Signed-off-by: hultonha <hultonha@amazon.co.uk> * fix comment and add additional info for tests Signed-off-by: hultonha <hultonha@amazon.co.uk>
This commit is contained in:
@@ -26,7 +26,8 @@ namespace UnitTest
|
||||
{
|
||||
constexpr float deltaTime = 0.01666f; // 60fps
|
||||
const bool consumed = m_cameraSystem->HandleEvents(event);
|
||||
m_camera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime);
|
||||
m_targetCamera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime);
|
||||
m_camera = m_targetCamera; // no smoothing
|
||||
return consumed;
|
||||
}
|
||||
|
||||
@@ -45,20 +46,38 @@ namespace UnitTest
|
||||
m_translateCameraInputChannelIds.m_boostChannelId = AzFramework::InputChannelId("keyboard_key_modifier_shift_l");
|
||||
|
||||
m_firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Right);
|
||||
m_firstPersonTranslateCamera =
|
||||
AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::LookTranslation, m_translateCameraInputChannelIds);
|
||||
// set rotate speed to be a value that will scale motion delta (pixels moved) by a thousandth.
|
||||
m_firstPersonRotateCamera->m_rotateSpeedFn = []()
|
||||
{
|
||||
return 0.001f;
|
||||
};
|
||||
|
||||
m_orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>(m_orbitChannelId);
|
||||
auto orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Left);
|
||||
auto orbitTranslateCamera =
|
||||
AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::OrbitTranslation, m_translateCameraInputChannelIds);
|
||||
m_firstPersonTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(
|
||||
m_translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivot);
|
||||
|
||||
m_orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera);
|
||||
m_orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera);
|
||||
m_pivotCamera = AZStd::make_shared<AzFramework::PivotCameraInput>(m_pivotChannelId);
|
||||
m_pivotCamera->SetPivotFn(
|
||||
[this](const AZ::Vector3&, const AZ::Vector3&)
|
||||
{
|
||||
return m_pivot;
|
||||
});
|
||||
|
||||
auto pivotRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Left);
|
||||
// set rotate speed to be a value that will scale motion delta (pixels moved) by a thousandth.
|
||||
pivotRotateCamera->m_rotateSpeedFn = []()
|
||||
{
|
||||
return 0.001f;
|
||||
};
|
||||
|
||||
auto pivotTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(
|
||||
m_translateCameraInputChannelIds, AzFramework::PivotTranslation, AzFramework::TranslateOffset);
|
||||
|
||||
m_pivotCamera->m_pivotCameras.AddCamera(pivotRotateCamera);
|
||||
m_pivotCamera->m_pivotCameras.AddCamera(pivotTranslateCamera);
|
||||
|
||||
m_cameraSystem->m_cameras.AddCamera(m_firstPersonRotateCamera);
|
||||
m_cameraSystem->m_cameras.AddCamera(m_firstPersonTranslateCamera);
|
||||
m_cameraSystem->m_cameras.AddCamera(m_orbitCamera);
|
||||
m_cameraSystem->m_cameras.AddCamera(m_pivotCamera);
|
||||
|
||||
// these tests rely on using motion delta, not cursor positions (default is true)
|
||||
AzFramework::ed_cameraSystemUseCursor = false;
|
||||
@@ -68,7 +87,7 @@ namespace UnitTest
|
||||
{
|
||||
AzFramework::ed_cameraSystemUseCursor = true;
|
||||
|
||||
m_orbitCamera.reset();
|
||||
m_pivotCamera.reset();
|
||||
m_firstPersonRotateCamera.reset();
|
||||
m_firstPersonTranslateCamera.reset();
|
||||
|
||||
@@ -78,24 +97,29 @@ namespace UnitTest
|
||||
AllocatorsTestFixture::TearDown();
|
||||
}
|
||||
|
||||
AzFramework::InputChannelId m_orbitChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l");
|
||||
AzFramework::InputChannelId m_pivotChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l");
|
||||
AzFramework::TranslateCameraInputChannelIds m_translateCameraInputChannelIds;
|
||||
AZStd::shared_ptr<AzFramework::RotateCameraInput> m_firstPersonRotateCamera;
|
||||
AZStd::shared_ptr<AzFramework::TranslateCameraInput> m_firstPersonTranslateCamera;
|
||||
AZStd::shared_ptr<AzFramework::OrbitCameraInput> m_orbitCamera;
|
||||
AZStd::shared_ptr<AzFramework::PivotCameraInput> m_pivotCamera;
|
||||
AZ::Vector3 m_pivot = AZ::Vector3::CreateZero();
|
||||
|
||||
//! This is approximately Pi/2 * 1000 - this can be used to rotate the camera 90 degrees (pitch or yaw based
|
||||
//! on vertical or horizontal motion) as the rotate speed function is set to be 1/1000.
|
||||
inline static const int PixelMotionDelta = 1570;
|
||||
};
|
||||
|
||||
TEST_F(CameraInputFixture, BeginAndEndOrbitCameraInputConsumesCorrectEvents)
|
||||
TEST_F(CameraInputFixture, BeginAndEndPivotCameraInputConsumesCorrectEvents)
|
||||
{
|
||||
// begin orbit camera
|
||||
// begin pivot camera
|
||||
const bool consumed1 = HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceKeyboard::Key::ModifierAltL,
|
||||
AzFramework::InputChannel::State::Began });
|
||||
// begin listening for orbit rotate (click detector) - event is not consumed
|
||||
// begin listening for pivot rotate (click detector) - event is not consumed
|
||||
const bool consumed2 = HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began });
|
||||
// begin orbit rotate (mouse has moved sufficient distance to initiate)
|
||||
// begin pivot rotate (mouse has moved sufficient distance to initiate)
|
||||
const bool consumed3 = HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ 5 });
|
||||
// end orbit (mouse up) - event is not consumed
|
||||
// end pivot (mouse up) - event is not consumed
|
||||
const bool consumed4 = HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended });
|
||||
|
||||
@@ -236,29 +260,110 @@ namespace UnitTest
|
||||
EXPECT_TRUE(activationEnded);
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, OrbitCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenOrbiting)
|
||||
TEST_F(CameraInputFixture, PivotCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenPivoting)
|
||||
{
|
||||
// create pathological lookAtFn that just returns the same position as the camera
|
||||
m_orbitCamera->SetLookAtFn(
|
||||
m_pivotCamera->SetPivotFn(
|
||||
[](const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction)
|
||||
{
|
||||
return position;
|
||||
});
|
||||
|
||||
const auto expectedCameraPosition = AZ::Vector3(10.0f, 10.0f, 10.0f);
|
||||
AzFramework::UpdateCameraFromTransform(
|
||||
m_targetCamera,
|
||||
AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), AZ::Vector3(10.0f, 10.0f, 10.0f)));
|
||||
AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), expectedCameraPosition));
|
||||
|
||||
m_camera = m_targetCamera;
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began });
|
||||
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
|
||||
// verify the camera yaw has not changed and pivot point matches the expected camera position
|
||||
using ::testing::FloatNear;
|
||||
EXPECT_THAT(m_camera.m_yaw, FloatNear(AZ::DegToRad(90.0f), 0.001f));
|
||||
EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f));
|
||||
EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero()));
|
||||
EXPECT_THAT(m_camera.m_pivot, IsClose(expectedCameraPosition));
|
||||
}
|
||||
|
||||
// verify the camera yaw has not changed and the look at point
|
||||
// does not match that of the camera translation
|
||||
using ::testing::Eq;
|
||||
using ::testing::Not;
|
||||
EXPECT_THAT(m_camera.m_yaw, Eq(AZ::DegToRad(90.0f)));
|
||||
EXPECT_THAT(m_camera.m_lookAt, Not(IsClose(m_camera.Translation())));
|
||||
TEST_F(CameraInputFixture, FirstPersonRotateCameraInputRotatesYawByNinetyDegreesWithRequiredPixelDelta)
|
||||
{
|
||||
const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-10.0f);
|
||||
m_targetCamera.m_pivot = cameraStartingPosition;
|
||||
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ PixelMotionDelta });
|
||||
|
||||
const float expectedYaw = AzFramework::WrapYawRotation(-AZ::Constants::HalfPi);
|
||||
|
||||
using ::testing::FloatNear;
|
||||
EXPECT_THAT(m_camera.m_yaw, FloatNear(expectedYaw, 0.001f));
|
||||
EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f));
|
||||
EXPECT_THAT(m_camera.m_pivot, IsClose(cameraStartingPosition));
|
||||
EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero()));
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, FirstPersonRotateCameraInputRotatesPitchByNinetyDegreesWithRequiredPixelDelta)
|
||||
{
|
||||
const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-10.0f);
|
||||
m_targetCamera.m_pivot = cameraStartingPosition;
|
||||
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta });
|
||||
|
||||
const float expectedPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi);
|
||||
|
||||
using ::testing::FloatNear;
|
||||
EXPECT_THAT(m_camera.m_yaw, FloatNear(0.0f, 0.001f));
|
||||
EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f));
|
||||
EXPECT_THAT(m_camera.m_pivot, IsClose(cameraStartingPosition));
|
||||
EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero()));
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesPitchOffsetByNinetyDegreesWithRequiredPixelDelta)
|
||||
{
|
||||
const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-20.0f);
|
||||
m_targetCamera.m_pivot = cameraStartingPosition;
|
||||
|
||||
m_pivot = AZ::Vector3::CreateAxisY(-10.0f);
|
||||
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta });
|
||||
|
||||
const auto expectedCameraEndingPosition = AZ::Vector3(0.0f, -10.0f, 10.0f);
|
||||
const float expectedPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi);
|
||||
|
||||
using ::testing::FloatNear;
|
||||
EXPECT_THAT(m_camera.m_yaw, FloatNear(0.0f, 0.001f));
|
||||
EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f));
|
||||
EXPECT_THAT(m_camera.m_pivot, IsClose(m_pivot));
|
||||
EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateAxisY(-10.0f)));
|
||||
EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f));
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesYawOffsetByNinetyDegreesWithRequiredPixelDelta)
|
||||
{
|
||||
const auto cameraStartingPosition = AZ::Vector3(15.0f, -20.0f, 0.0f);
|
||||
m_targetCamera.m_pivot = cameraStartingPosition;
|
||||
|
||||
m_pivot = AZ::Vector3(10.0f, -10.0f, 0.0f);
|
||||
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ -PixelMotionDelta });
|
||||
|
||||
const auto expectedCameraEndingPosition = AZ::Vector3(20.0f, -5.0f, 0.0f);
|
||||
const float expectedYaw = AzFramework::WrapYawRotation(AZ::Constants::HalfPi);
|
||||
|
||||
using ::testing::FloatNear;
|
||||
EXPECT_THAT(m_camera.m_yaw, FloatNear(expectedYaw, 0.001f));
|
||||
EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f));
|
||||
EXPECT_THAT(m_camera.m_pivot, IsClose(m_pivot));
|
||||
EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3(5.0f, -10.0f, 0.0f)));
|
||||
EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f));
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
Reference in New Issue
Block a user