CameraInputTests.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AZTestShared/Math/MathTestHelpers.h>
  9. #include <AzCore/UnitTest/TestTypes.h>
  10. #include <AzCore/std/smart_ptr/make_shared.h>
  11. #include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
  12. #include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
  13. #include <AzFramework/Viewport/CameraInput.h>
  14. namespace UnitTest
  15. {
  16. static AzFramework::ModifierKeyStates OrbitModifierKeyStates(const AzFramework::InputChannelId orbitChannelId, bool on = true)
  17. {
  18. AzFramework::ModifierKeyStates modifierKeyStates;
  19. modifierKeyStates.SetActive(AzFramework::GetCorrespondingModifierKeyMask(orbitChannelId), on);
  20. return modifierKeyStates;
  21. }
  22. static AzFramework::ModifierKeyStates BoostModifierKeyStates(const AzFramework::InputChannelId boostChannelId, bool on = true)
  23. {
  24. AzFramework::ModifierKeyStates modifierKeyStates;
  25. modifierKeyStates.SetActive(AzFramework::GetCorrespondingModifierKeyMask(boostChannelId), on);
  26. return modifierKeyStates;
  27. }
  28. class CameraInputFixture : public LeakDetectionFixture
  29. {
  30. public:
  31. AzFramework::Camera m_camera;
  32. AzFramework::Camera m_targetCamera;
  33. AZStd::shared_ptr<AzFramework::CameraSystem> m_cameraSystem;
  34. void Update()
  35. {
  36. constexpr float deltaTime = 0.01666f; // 60fps
  37. m_targetCamera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime);
  38. m_camera = m_targetCamera; // no smoothing
  39. }
  40. bool HandleEvent(const AzFramework::InputState& state)
  41. {
  42. return m_cameraSystem->HandleEvents(state);
  43. }
  44. bool HandleEventAndUpdate(const AzFramework::InputState& state)
  45. {
  46. const bool consumed = HandleEvent(state);
  47. Update();
  48. return consumed;
  49. }
  50. void SetUp() override
  51. {
  52. LeakDetectionFixture::SetUp();
  53. m_cameraSystem = AZStd::make_shared<AzFramework::CameraSystem>();
  54. m_translateCameraInputChannelIds.m_leftChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_A");
  55. m_translateCameraInputChannelIds.m_rightChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_D");
  56. m_translateCameraInputChannelIds.m_forwardChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_W");
  57. m_translateCameraInputChannelIds.m_backwardChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_S");
  58. m_translateCameraInputChannelIds.m_upChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_E");
  59. m_translateCameraInputChannelIds.m_downChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_Q");
  60. m_translateCameraInputChannelIds.m_boostChannelId = AzFramework::InputChannelId("keyboard_key_modifier_shift_l");
  61. m_firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Right);
  62. // set rotate speed to be a value that will scale motion delta (pixels moved) by a thousandth.
  63. m_firstPersonRotateCamera->m_rotateSpeedFn = []()
  64. {
  65. return 0.001f;
  66. };
  67. m_firstPersonTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(
  68. m_translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivotLook);
  69. m_orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>(m_orbitChannelId);
  70. m_orbitCamera->SetPivotFn(
  71. [this](const AZ::Vector3&, const AZ::Vector3&)
  72. {
  73. return m_pivot;
  74. });
  75. auto orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Left);
  76. // set rotate speed to be a value that will scale motion delta (pixels moved) by a thousandth.
  77. orbitRotateCamera->m_rotateSpeedFn = []()
  78. {
  79. return 0.001f;
  80. };
  81. auto orbitTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(
  82. m_translateCameraInputChannelIds, AzFramework::OrbitTranslation, AzFramework::TranslateOffsetOrbit);
  83. m_orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera);
  84. m_orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera);
  85. m_cameraSystem->m_cameras.AddCamera(m_firstPersonRotateCamera);
  86. m_cameraSystem->m_cameras.AddCamera(m_firstPersonTranslateCamera);
  87. m_cameraSystem->m_cameras.AddCamera(m_orbitCamera);
  88. // these tests rely on using motion delta, not cursor positions (default is true)
  89. AzFramework::ed_cameraSystemUseCursor = false;
  90. }
  91. void TearDown() override
  92. {
  93. AzFramework::ed_cameraSystemUseCursor = true;
  94. m_orbitCamera.reset();
  95. m_firstPersonRotateCamera.reset();
  96. m_firstPersonTranslateCamera.reset();
  97. m_cameraSystem->m_cameras.Clear();
  98. m_cameraSystem.reset();
  99. LeakDetectionFixture::TearDown();
  100. }
  101. AzFramework::InputChannelId m_orbitChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l");
  102. AzFramework::TranslateCameraInputChannelIds m_translateCameraInputChannelIds;
  103. AZStd::shared_ptr<AzFramework::RotateCameraInput> m_firstPersonRotateCamera;
  104. AZStd::shared_ptr<AzFramework::TranslateCameraInput> m_firstPersonTranslateCamera;
  105. AZStd::shared_ptr<AzFramework::OrbitCameraInput> m_orbitCamera;
  106. AZ::Vector3 m_pivot = AZ::Vector3::CreateZero();
  107. // this is approximately Pi/2 * 1000 - this can be used to rotate the camera 90 degrees (pitch or yaw based
  108. // on vertical or horizontal motion) as the rotate speed function is set to be 1/1000.
  109. inline static const int PixelMotionDelta90Degrees = 1570;
  110. inline static const int PixelMotionDelta135Degrees = 2356;
  111. };
  112. TEST_F(CameraInputFixture, BeginAndEndOrbitCameraInputConsumesCorrectEvents)
  113. {
  114. const AzFramework::ModifierKeyStates orbitModifierKeystate = OrbitModifierKeyStates(m_orbitChannelId);
  115. // begin orbit camera
  116. const bool consumed1 = HandleEventAndUpdate(AzFramework::InputState{
  117. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceKeyboard::Key::ModifierAltL, AzFramework::InputChannel::State::Began },
  118. orbitModifierKeystate });
  119. // begin listening for orbit rotate (click detector) - event is not consumed
  120. const bool consumed2 = HandleEventAndUpdate(AzFramework::InputState{
  121. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began },
  122. orbitModifierKeystate });
  123. // begin orbit rotate (mouse has moved sufficient distance to initiate)
  124. const bool consumed3 =
  125. HandleEventAndUpdate(AzFramework::InputState{ AzFramework::HorizontalMotionEvent{ 5 }, orbitModifierKeystate });
  126. // end orbit (mouse up) - event is not consumed
  127. const bool consumed4 = HandleEventAndUpdate(AzFramework::InputState{
  128. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended },
  129. orbitModifierKeystate });
  130. const auto allConsumed = AZStd::vector<bool>{ consumed1, consumed2, consumed3, consumed4 };
  131. using ::testing::ElementsAre;
  132. EXPECT_THAT(allConsumed, ElementsAre(true, false, true, false));
  133. }
  134. TEST_F(CameraInputFixture, BeginCameraInputNotifiesActivationBeganFnForTranslateCameraInput)
  135. {
  136. bool activationBegan = false;
  137. m_firstPersonTranslateCamera->SetActivationBeganFn(
  138. [&activationBegan]
  139. {
  140. activationBegan = true;
  141. });
  142. HandleEventAndUpdate(AzFramework::InputState{
  143. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId, AzFramework::InputChannel::State::Began },
  144. AzFramework::ModifierKeyStates{} });
  145. EXPECT_TRUE(activationBegan);
  146. }
  147. TEST_F(CameraInputFixture, BeginCameraInputNotifiesActivationBeganFnAfterDeltaForRotateCameraInput)
  148. {
  149. bool activationBegan = false;
  150. m_firstPersonRotateCamera->SetActivationBeganFn(
  151. [&activationBegan]
  152. {
  153. activationBegan = true;
  154. });
  155. HandleEventAndUpdate(AzFramework::InputState{
  156. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began },
  157. AzFramework::ModifierKeyStates{} });
  158. HandleEventAndUpdate(AzFramework::InputState{ AzFramework::HorizontalMotionEvent{ 20 },
  159. AzFramework::ModifierKeyStates{} }); // must move input device
  160. EXPECT_TRUE(activationBegan);
  161. }
  162. TEST_F(CameraInputFixture, BeginCameraInputDoesNotNotifyActivationBeganFnWithNoDeltaForRotateCameraInput)
  163. {
  164. bool activationBegan = false;
  165. m_firstPersonRotateCamera->SetActivationBeganFn(
  166. [&activationBegan]
  167. {
  168. activationBegan = true;
  169. });
  170. HandleEventAndUpdate(AzFramework::InputState{
  171. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began },
  172. AzFramework::ModifierKeyStates{} });
  173. EXPECT_FALSE(activationBegan);
  174. }
  175. TEST_F(CameraInputFixture, EndCameraInputNotifiesActivationEndFnAfterDeltaForRotateCameraInput)
  176. {
  177. bool activationEnded = false;
  178. m_firstPersonRotateCamera->SetActivationEndedFn(
  179. [&activationEnded]
  180. {
  181. activationEnded = true;
  182. });
  183. HandleEventAndUpdate(AzFramework::InputState{
  184. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began },
  185. AzFramework::ModifierKeyStates{} });
  186. HandleEventAndUpdate(AzFramework::InputState{ AzFramework::HorizontalMotionEvent{ 20 }, AzFramework::ModifierKeyStates{} });
  187. HandleEventAndUpdate(AzFramework::InputState{
  188. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Ended },
  189. AzFramework::ModifierKeyStates{} });
  190. EXPECT_TRUE(activationEnded);
  191. }
  192. TEST_F(CameraInputFixture, EndCameraInputDoesNotNotifyActivationBeganFnOrActivationBeganFnWithNoDeltaForRotateCameraInput)
  193. {
  194. bool activationBegan = false;
  195. m_firstPersonRotateCamera->SetActivationBeganFn(
  196. [&activationBegan]
  197. {
  198. activationBegan = true;
  199. });
  200. bool activationEnded = false;
  201. m_firstPersonRotateCamera->SetActivationEndedFn(
  202. [&activationEnded]
  203. {
  204. activationEnded = true;
  205. });
  206. HandleEventAndUpdate(AzFramework::InputState{
  207. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began },
  208. AzFramework::ModifierKeyStates{} });
  209. HandleEventAndUpdate(AzFramework::InputState{
  210. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Ended },
  211. AzFramework::ModifierKeyStates{} });
  212. EXPECT_FALSE(activationBegan);
  213. EXPECT_FALSE(activationEnded);
  214. }
  215. TEST_F(CameraInputFixture, End_CameraInputNotifiesActivationBeganFnOrActivationEndFnWithTranslateCamera)
  216. {
  217. bool activationBegan = false;
  218. m_firstPersonTranslateCamera->SetActivationBeganFn(
  219. [&activationBegan]
  220. {
  221. activationBegan = true;
  222. });
  223. bool activationEnded = false;
  224. m_firstPersonTranslateCamera->SetActivationEndedFn(
  225. [&activationEnded]
  226. {
  227. activationEnded = true;
  228. });
  229. HandleEventAndUpdate(AzFramework::InputState{
  230. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId, AzFramework::InputChannel::State::Began },
  231. AzFramework::ModifierKeyStates{} });
  232. HandleEventAndUpdate(AzFramework::InputState{
  233. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId, AzFramework::InputChannel::State::Ended },
  234. AzFramework::ModifierKeyStates{} });
  235. EXPECT_TRUE(activationBegan);
  236. EXPECT_TRUE(activationEnded);
  237. }
  238. TEST_F(CameraInputFixture, EndActivationCalledForCameraInputIfActiveWhenCamerasAreCleared)
  239. {
  240. bool activationEnded = false;
  241. m_firstPersonTranslateCamera->SetActivationEndedFn(
  242. [&activationEnded]
  243. {
  244. activationEnded = true;
  245. });
  246. HandleEventAndUpdate(AzFramework::InputState{
  247. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId, AzFramework::InputChannel::State::Began },
  248. AzFramework::ModifierKeyStates{} });
  249. m_cameraSystem->m_cameras.Clear();
  250. EXPECT_TRUE(activationEnded);
  251. }
  252. TEST_F(CameraInputFixture, OrbitCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenOrbiting)
  253. {
  254. // create pathological lookAtFn that just returns the same position as the camera
  255. m_orbitCamera->SetPivotFn(
  256. [](const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction)
  257. {
  258. return position;
  259. });
  260. const auto expectedCameraPosition = AZ::Vector3(10.0f, 10.0f, 10.0f);
  261. AzFramework::UpdateCameraFromTransform(
  262. m_targetCamera,
  263. AZ::Transform::CreateFromQuaternionAndTranslation(
  264. AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), expectedCameraPosition));
  265. HandleEventAndUpdate(
  266. AzFramework::InputState{ AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began },
  267. AzFramework::ModifierKeyStates{} });
  268. // verify the camera yaw has not changed and pivot point matches the expected camera
  269. // position
  270. using ::testing::FloatNear;
  271. EXPECT_THAT(m_camera.m_yaw, FloatNear(AZ::DegToRad(90.0f), 0.001f));
  272. EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f));
  273. EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero()));
  274. EXPECT_THAT(m_camera.m_pivot, IsClose(expectedCameraPosition));
  275. }
  276. TEST_F(CameraInputFixture, FirstPersonRotateCameraInputRotatesYawByNinetyDegreesWithRequiredPixelDelta)
  277. {
  278. const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-10.0f);
  279. m_targetCamera.m_pivot = cameraStartingPosition;
  280. HandleEventAndUpdate(AzFramework::InputState{
  281. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began },
  282. AzFramework::ModifierKeyStates{} });
  283. HandleEventAndUpdate(
  284. AzFramework::InputState{ AzFramework::HorizontalMotionEvent{ PixelMotionDelta90Degrees }, AzFramework::ModifierKeyStates{} });
  285. const float expectedYaw = AzFramework::WrapYawRotation(-AZ::Constants::HalfPi);
  286. using ::testing::FloatNear;
  287. EXPECT_THAT(m_camera.m_yaw, FloatNear(expectedYaw, 0.001f));
  288. EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f));
  289. EXPECT_THAT(m_camera.m_pivot, IsClose(cameraStartingPosition));
  290. EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero()));
  291. }
  292. TEST_F(CameraInputFixture, FirstPersonRotateCameraInputRotatesPitchByNinetyDegreesWithRequiredPixelDelta)
  293. {
  294. const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-10.0f);
  295. m_targetCamera.m_pivot = cameraStartingPosition;
  296. HandleEventAndUpdate(AzFramework::InputState{
  297. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began },
  298. AzFramework::ModifierKeyStates{} });
  299. HandleEventAndUpdate(
  300. AzFramework::InputState{ AzFramework::VerticalMotionEvent{ PixelMotionDelta90Degrees }, AzFramework::ModifierKeyStates{} });
  301. const float expectedPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi);
  302. using ::testing::FloatNear;
  303. EXPECT_THAT(m_camera.m_yaw, FloatNear(0.0f, 0.001f));
  304. EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f));
  305. EXPECT_THAT(m_camera.m_pivot, IsClose(cameraStartingPosition));
  306. EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero()));
  307. }
  308. TEST(CameraInput, CameraPitchIsClampedWithExpectedTolerance)
  309. {
  310. const auto [expectedMinPitch, expectedMaxPitch] = AzFramework::CameraPitchMinMaxRadiansWithTolerance();
  311. const float minPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi);
  312. const float maxPitch = AzFramework::ClampPitchRotation(AZ::Constants::HalfPi);
  313. using ::testing::FloatNear;
  314. EXPECT_THAT(minPitch, FloatNear(expectedMinPitch, AzFramework::CameraPitchTolerance));
  315. EXPECT_THAT(maxPitch, FloatNear(expectedMaxPitch, AzFramework::CameraPitchTolerance));
  316. }
  317. TEST_F(CameraInputFixture, OrbitRotateCameraInputRotatesPitchOffsetByNinetyDegreesWithRequiredPixelDelta)
  318. {
  319. const AzFramework::ModifierKeyStates orbitModifierKeystate = OrbitModifierKeyStates(m_orbitChannelId);
  320. const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-20.0f);
  321. m_targetCamera.m_pivot = cameraStartingPosition;
  322. m_pivot = AZ::Vector3::CreateAxisY(-10.0f);
  323. HandleEventAndUpdate(AzFramework::InputState{
  324. AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began }, orbitModifierKeystate });
  325. HandleEventAndUpdate(AzFramework::InputState{
  326. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began },
  327. orbitModifierKeystate });
  328. HandleEventAndUpdate(
  329. AzFramework::InputState{ AzFramework::VerticalMotionEvent{ PixelMotionDelta90Degrees }, orbitModifierKeystate });
  330. const auto expectedCameraEndingPosition = AZ::Vector3(0.0f, -10.0f, 10.0f);
  331. const float expectedPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi);
  332. using ::testing::FloatNear;
  333. EXPECT_THAT(m_camera.m_yaw, FloatNear(0.0f, 0.001f));
  334. EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f));
  335. EXPECT_THAT(m_camera.m_pivot, IsClose(m_pivot));
  336. EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateAxisY(-10.0f)));
  337. EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f));
  338. }
  339. TEST_F(CameraInputFixture, OrbitRotateCameraInputRotatesYawOffsetByNinetyDegreesWithRequiredPixelDelta)
  340. {
  341. const AzFramework::ModifierKeyStates orbitModifierKeystate = OrbitModifierKeyStates(m_orbitChannelId);
  342. const auto cameraStartingPosition = AZ::Vector3(15.0f, -20.0f, 0.0f);
  343. m_targetCamera.m_pivot = cameraStartingPosition;
  344. m_pivot = AZ::Vector3(10.0f, -10.0f, 0.0f);
  345. HandleEventAndUpdate(AzFramework::InputState{
  346. AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began }, orbitModifierKeystate });
  347. HandleEventAndUpdate(AzFramework::InputState{
  348. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began },
  349. orbitModifierKeystate });
  350. HandleEventAndUpdate(
  351. AzFramework::InputState{ AzFramework::HorizontalMotionEvent{ -PixelMotionDelta90Degrees }, orbitModifierKeystate });
  352. const auto expectedCameraEndingPosition = AZ::Vector3(20.0f, -5.0f, 0.0f);
  353. const float expectedYaw = AzFramework::WrapYawRotation(AZ::Constants::HalfPi);
  354. using ::testing::FloatNear;
  355. EXPECT_THAT(m_camera.m_yaw, FloatNear(expectedYaw, 0.001f));
  356. EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f));
  357. EXPECT_THAT(m_camera.m_pivot, IsClose(m_pivot));
  358. EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3(5.0f, -10.0f, 0.0f)));
  359. EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f));
  360. }
  361. TEST_F(CameraInputFixture, CameraPitchCanNotBeMovedPastNinetyDegreesWhenConstrained)
  362. {
  363. const auto cameraStartingPosition = AZ::Vector3(15.0f, -20.0f, 0.0f);
  364. m_targetCamera.m_pivot = cameraStartingPosition;
  365. HandleEventAndUpdate(AzFramework::InputState{
  366. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began },
  367. AzFramework::ModifierKeyStates{} });
  368. // pitch by 135.0 degrees
  369. HandleEventAndUpdate(
  370. AzFramework::InputState{ AzFramework::VerticalMotionEvent{ -PixelMotionDelta135Degrees }, AzFramework::ModifierKeyStates{} });
  371. // clamped to 90.0 degrees
  372. const float expectedPitch = AZ::DegToRad(90.0f);
  373. using ::testing::FloatNear;
  374. EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f));
  375. }
  376. TEST_F(CameraInputFixture, CameraPitchCanBeMovedPastNinetyDegreesWhenUnconstrained)
  377. {
  378. m_firstPersonRotateCamera->m_constrainPitch = []
  379. {
  380. return false;
  381. };
  382. const auto cameraStartingPosition = AZ::Vector3(15.0f, -20.0f, 0.0f);
  383. m_targetCamera.m_pivot = cameraStartingPosition;
  384. HandleEventAndUpdate(AzFramework::InputState{
  385. AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began },
  386. AzFramework::ModifierKeyStates{} });
  387. // pitch by 135.0 degrees
  388. HandleEventAndUpdate(
  389. AzFramework::InputState{ AzFramework::VerticalMotionEvent{ -PixelMotionDelta135Degrees }, AzFramework::ModifierKeyStates{} });
  390. const float expectedPitch = AZ::DegToRad(135.0f);
  391. using ::testing::FloatNear;
  392. EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f));
  393. }
  394. TEST_F(CameraInputFixture, InvalidTranslationInputKeyCannotBeginTranslateCameraInputAgain)
  395. {
  396. HandleEventAndUpdate(AzFramework::InputState{
  397. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId, AzFramework::InputChannel::State::Began },
  398. AzFramework::ModifierKeyStates{} });
  399. const bool consumed = m_cameraSystem->HandleEvents(
  400. AzFramework::InputState{ AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began },
  401. AzFramework::ModifierKeyStates{} });
  402. using ::testing::IsFalse;
  403. using ::testing::IsTrue;
  404. EXPECT_THAT(consumed, IsTrue());
  405. EXPECT_THAT(m_firstPersonTranslateCamera->Beginning(), IsFalse());
  406. EXPECT_THAT(m_firstPersonTranslateCamera->Active(), IsTrue());
  407. }
  408. TEST_F(CameraInputFixture, InvalidTranslationInputKeyDownCannotBeginTranslateCameraInputAgain)
  409. {
  410. HandleEventAndUpdate(AzFramework::InputState{
  411. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId, AzFramework::InputChannel::State::Began },
  412. AzFramework::ModifierKeyStates{} });
  413. const bool consumed = m_cameraSystem->HandleEvents(
  414. AzFramework::InputState{ AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began },
  415. AzFramework::ModifierKeyStates{} });
  416. using ::testing::IsFalse;
  417. using ::testing::IsTrue;
  418. EXPECT_THAT(consumed, IsTrue());
  419. EXPECT_THAT(m_firstPersonTranslateCamera->Beginning(), IsFalse());
  420. EXPECT_THAT(m_firstPersonTranslateCamera->Active(), IsTrue());
  421. }
  422. TEST_F(CameraInputFixture, InvalidTranslationInputKeyUpDoesNotAffectTranslateCameraInputEnd)
  423. {
  424. HandleEventAndUpdate(AzFramework::InputState{
  425. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId, AzFramework::InputChannel::State::Began },
  426. AzFramework::ModifierKeyStates{} });
  427. const bool consumed = m_cameraSystem->HandleEvents(
  428. AzFramework::InputState{ AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began },
  429. AzFramework::ModifierKeyStates{} });
  430. HandleEventAndUpdate(AzFramework::InputState{
  431. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId, AzFramework::InputChannel::State::Ended },
  432. AzFramework::ModifierKeyStates{} });
  433. using ::testing::IsFalse;
  434. using ::testing::IsTrue;
  435. EXPECT_THAT(consumed, IsTrue());
  436. EXPECT_THAT(m_firstPersonTranslateCamera->Idle(), IsTrue());
  437. }
  438. TEST_F(CameraInputFixture, OrbitCameraInputCannotBeLeftInInvalidStateIfItCannotFullyBeginAfterInputChannelBegin)
  439. {
  440. HandleEventAndUpdate(AzFramework::InputState{
  441. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId, AzFramework::InputChannel::State::Began },
  442. AzFramework::ModifierKeyStates{} });
  443. HandleEventAndUpdate(
  444. AzFramework::InputState{ AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began },
  445. AzFramework::ModifierKeyStates{} });
  446. using ::testing::IsFalse;
  447. using ::testing::IsTrue;
  448. EXPECT_THAT(m_orbitCamera->Beginning(), IsFalse());
  449. EXPECT_THAT(m_orbitCamera->Idle(), IsTrue());
  450. }
  451. TEST_F(CameraInputFixture, OrbitCameraInputCannotBeLeftInInvalidStateIfItCannotFullyBeginAfterInputChannelBeginAndEnd)
  452. {
  453. HandleEventAndUpdate(AzFramework::InputState{
  454. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId, AzFramework::InputChannel::State::Began },
  455. AzFramework::ModifierKeyStates{} });
  456. HandleEventAndUpdate(
  457. AzFramework::InputState{ AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began },
  458. AzFramework::ModifierKeyStates{} });
  459. HandleEventAndUpdate(
  460. AzFramework::InputState{ AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Ended },
  461. AzFramework::ModifierKeyStates{} });
  462. using ::testing::IsFalse;
  463. using ::testing::IsTrue;
  464. EXPECT_THAT(m_orbitCamera->Ending(), IsFalse());
  465. EXPECT_THAT(m_orbitCamera->Idle(), IsTrue());
  466. }
  467. TEST_F(CameraInputFixture, NewCameraInputCanBeAddedToCameraSystem)
  468. {
  469. auto firstPersonPanCamera = AZStd::make_shared<AzFramework::PanCameraInput>(
  470. AzFramework::InputDeviceMouse::Button::Middle, AzFramework::LookPan, AzFramework::TranslatePivotLook);
  471. const bool added =
  472. m_cameraSystem->m_cameras.AddCameras(AZStd::vector<AZStd::shared_ptr<AzFramework::CameraInput>>{ firstPersonPanCamera });
  473. EXPECT_THAT(added, ::testing::IsTrue());
  474. }
  475. TEST_F(CameraInputFixture, ExistingCameraInputCannotBeAddedToCameraSystem)
  476. {
  477. const bool added =
  478. m_cameraSystem->m_cameras.AddCameras(AZStd::vector<AZStd::shared_ptr<AzFramework::CameraInput>>{ m_firstPersonRotateCamera });
  479. EXPECT_THAT(added, ::testing::IsFalse());
  480. }
  481. TEST_F(CameraInputFixture, ExistingCameraInputCanBeRemovedFromCameraSystem)
  482. {
  483. const bool removed = m_cameraSystem->m_cameras.RemoveCameras(
  484. AZStd::vector<AZStd::shared_ptr<AzFramework::CameraInput>>{ m_firstPersonRotateCamera });
  485. EXPECT_THAT(removed, ::testing::IsTrue());
  486. }
  487. TEST_F(CameraInputFixture, NonExistentCameraInputCannotBeRemovedFromCameraSystem)
  488. {
  489. auto firstPersonPanCamera = AZStd::make_shared<AzFramework::PanCameraInput>(
  490. AzFramework::InputDeviceMouse::Button::Middle, AzFramework::LookPan, AzFramework::TranslatePivotLook);
  491. const bool removed =
  492. m_cameraSystem->m_cameras.RemoveCameras(AZStd::vector<AZStd::shared_ptr<AzFramework::CameraInput>>{ firstPersonPanCamera });
  493. EXPECT_THAT(removed, ::testing::IsFalse());
  494. }
  495. // note: this test is attempting to mimic the behavior that happens when a user presses 'ctrl-tab' to change focus from the editor
  496. // which can cause the event/update order to become irregular - this test verifies the orbit behavior does not change the position
  497. // of the camera if updates are dropped (due to the editor losing focus)
  498. TEST_F(CameraInputFixture, OrbitRotateCameraInputDoesNotResetPositionWithInconsitentEventAndUpdate)
  499. {
  500. const AzFramework::ModifierKeyStates orbitModifierKeystate = OrbitModifierKeyStates(m_orbitChannelId);
  501. const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-20.0f);
  502. m_targetCamera.m_pivot = cameraStartingPosition;
  503. m_pivot = AZ::Vector3::CreateAxisY(-10.0f);
  504. HandleEvent(AzFramework::InputState{ AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began },
  505. orbitModifierKeystate });
  506. Update();
  507. HandleEvent(
  508. AzFramework::InputState{ AzFramework::CursorEvent{ AzFramework::ScreenPoint{ 100, 100 } }, AzFramework::ModifierKeyStates{} });
  509. HandleEvent(AzFramework::InputState{ AzFramework::CursorEvent{ AzFramework::ScreenPoint{ 100, 100 } }, orbitModifierKeystate });
  510. Update();
  511. EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(cameraStartingPosition, 0.01f));
  512. }
  513. TEST_F(CameraInputFixture, TranslateCameraInputBoostDoesNotGetStuckOn)
  514. {
  515. HandleEventAndUpdate(AzFramework::InputState{
  516. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_leftChannelId, AzFramework::InputChannel::State::Began },
  517. BoostModifierKeyStates(m_translateCameraInputChannelIds.m_boostChannelId, false) });
  518. HandleEventAndUpdate(AzFramework::InputState{
  519. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_boostChannelId, AzFramework::InputChannel::State::Began },
  520. BoostModifierKeyStates(m_translateCameraInputChannelIds.m_boostChannelId, true) });
  521. EXPECT_THAT(m_firstPersonTranslateCamera->Boosting(), ::testing::IsTrue());
  522. HandleEventAndUpdate(AzFramework::InputState{
  523. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_leftChannelId, AzFramework::InputChannel::State::Ended },
  524. BoostModifierKeyStates(m_translateCameraInputChannelIds.m_boostChannelId, true) });
  525. HandleEventAndUpdate(AzFramework::InputState{
  526. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_boostChannelId, AzFramework::InputChannel::State::Ended },
  527. BoostModifierKeyStates(m_translateCameraInputChannelIds.m_boostChannelId, false) });
  528. HandleEventAndUpdate(AzFramework::InputState{
  529. AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_leftChannelId, AzFramework::InputChannel::State::Began },
  530. BoostModifierKeyStates(m_translateCameraInputChannelIds.m_boostChannelId, false) });
  531. EXPECT_THAT(m_firstPersonTranslateCamera->Boosting(), ::testing::IsFalse());
  532. }
  533. } // namespace UnitTest