FollowingCameraComponent.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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 "FollowingCameraComponent.h"
  9. #include <AtomBridge/FlyCameraInputBus.h>
  10. #include <AzCore/Serialization/EditContext.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
  13. #include <MathConversion.h>
  14. namespace ROS2
  15. {
  16. // Default keyboard mapping for predefined views.
  17. const AZStd::unordered_map<AzFramework::InputChannelId, int> KeysToView{
  18. { AzFramework::InputDeviceKeyboard::Key::Alphanumeric1, 0 }, { AzFramework::InputDeviceKeyboard::Key::Alphanumeric2, 1 },
  19. { AzFramework::InputDeviceKeyboard::Key::Alphanumeric3, 2 }, { AzFramework::InputDeviceKeyboard::Key::Alphanumeric4, 3 },
  20. { AzFramework::InputDeviceKeyboard::Key::Alphanumeric5, 4 }, { AzFramework::InputDeviceKeyboard::Key::Alphanumeric6, 5 },
  21. { AzFramework::InputDeviceKeyboard::Key::Alphanumeric7, 6 }, { AzFramework::InputDeviceKeyboard::Key::Alphanumeric8, 7 },
  22. { AzFramework::InputDeviceKeyboard::Key::Alphanumeric9, 8 }, { AzFramework::InputDeviceKeyboard::Key::Alphanumeric0, 9 }
  23. };
  24. FollowingCameraComponent::FollowingCameraComponent(const FollowingCameraConfiguration& configuration)
  25. : m_configuration(configuration)
  26. {
  27. }
  28. void FollowingCameraComponent::Reflect(AZ::ReflectContext* reflection)
  29. {
  30. FollowingCameraConfiguration::Reflect(reflection);
  31. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
  32. if (serializeContext)
  33. {
  34. serializeContext->Class<FollowingCameraComponent, AZ::Component>()->Version(1)->Field(
  35. "FollowingCameraConfiguration", &FollowingCameraComponent::m_configuration);
  36. AZ::EditContext* editContext = serializeContext->GetEditContext();
  37. if (editContext)
  38. {
  39. editContext->Class<FollowingCameraComponent>("Following Camera", "Camera following entity with predefined views")
  40. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  41. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game"))
  42. ->Attribute(AZ::Edit::Attributes::Category, "ROS2 Utilities")
  43. ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/Camera.svg")
  44. ->UIElement(AZ::Edit::UIHandlers::Label, "", "")
  45. ->Attribute(
  46. AZ::Edit::Attributes::ValueText,
  47. "This Component allows to switch camera view between predefined views. "
  48. "It also allows to zoom in/out and rotate around parent transformation. "
  49. "Use 0-9 keys to switch views and W, S, A, D keys to manipulate current view.")
  50. ->DataElement(
  51. AZ::Edit::UIHandlers::Default,
  52. &FollowingCameraComponent::m_configuration,
  53. "FollowingCameraConfiguration",
  54. "FollowingCameraConfiguration")
  55. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly);
  56. }
  57. }
  58. }
  59. void FollowingCameraComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  60. {
  61. provided.push_back(AZ_CRC("FollowingCameraService"));
  62. }
  63. void FollowingCameraComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  64. {
  65. incompatible.push_back(AZ_CRC("FollowingCameraService"));
  66. }
  67. void FollowingCameraComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  68. {
  69. required.push_back(AZ_CRC("TransformService"));
  70. required.push_back(AZ_CRC("CameraService"));
  71. }
  72. void FollowingCameraComponent::Activate()
  73. {
  74. if (m_configuration.m_predefinedViews.size() == 0)
  75. {
  76. AZ_Warning("FollowingCameraComponent", false, "No predefined views");
  77. return;
  78. }
  79. if (m_configuration.m_defaultView < m_configuration.m_predefinedViews.size())
  80. {
  81. m_currentView = m_configuration.m_predefinedViews[m_configuration.m_defaultView];
  82. AZ::AtomBridge::FlyCameraInputBus::EventResult(
  83. m_disableCameraMove, m_currentView, &AZ::AtomBridge::FlyCameraInputBus::Events::GetIsEnabled);
  84. }
  85. InputChannelEventListener::Connect();
  86. AZ::TickBus::Handler::BusConnect();
  87. }
  88. void FollowingCameraComponent::Deactivate()
  89. {
  90. AZ::TickBus::Handler::BusDisconnect();
  91. InputChannelEventListener::Disconnect();
  92. }
  93. AZ::Transform FollowingCameraComponent::RemoveTiltFromTransform(AZ::Transform transform)
  94. {
  95. const AZ::Vector3 axisX = transform.GetBasisX();
  96. const AZ::Vector3 axisY = transform.GetBasisY();
  97. const AZ::Matrix3x3 projectionOnXY{ AZ::Matrix3x3::CreateFromColumns(
  98. AZ::Vector3::CreateAxisX(), AZ::Vector3::CreateAxisY(), AZ::Vector3::CreateZero()) };
  99. const AZ::Vector3 newAxisZ = AZ::Vector3::CreateAxisZ(); // new axis Z points up
  100. // project axisX on the XY plane
  101. const AZ::Vector3 projectedAxisX = (projectionOnXY * axisX);
  102. const AZ::Vector3 projectedAxisY = (projectionOnXY * axisY);
  103. AZ::Vector3 newAxisX = AZ::Vector3::CreateZero();
  104. AZ::Vector3 newAxisY = AZ::Vector3::CreateZero();
  105. // get 3rd vector of the new basis from the cross product of the projected vectors.
  106. // Primarily we want to use the projectedAxisX as the newAxisX, but if it is zero-length, we use the projectedAxisY as the newAxisY.
  107. if (!projectedAxisX.IsZero())
  108. {
  109. newAxisX = projectedAxisX.GetNormalized();
  110. newAxisY = newAxisZ.Cross(newAxisX);
  111. }
  112. else
  113. {
  114. newAxisY = projectedAxisY.GetNormalized();
  115. newAxisX = newAxisY.Cross(newAxisZ);
  116. }
  117. // apply rotation using created basis
  118. transform.SetRotation(AZ::Quaternion::CreateFromBasis(newAxisX, newAxisY, newAxisZ));
  119. return transform;
  120. }
  121. void FollowingCameraComponent::CacheTransform(const AZ::Transform& transform, float deltaTime)
  122. {
  123. // update the smoothing buffer
  124. m_lastTranslationsBuffer.push_back(AZStd::make_pair(transform.GetTranslation(), deltaTime));
  125. m_lastRotationsBuffer.push_back(AZStd::make_pair(transform.GetRotation(), deltaTime));
  126. if (m_lastTranslationsBuffer.size() > m_configuration.m_smoothingBuffer)
  127. {
  128. m_lastTranslationsBuffer.pop_front();
  129. }
  130. if (m_lastRotationsBuffer.size() > m_configuration.m_smoothingBuffer)
  131. {
  132. m_lastRotationsBuffer.pop_front();
  133. }
  134. }
  135. void FollowingCameraComponent::OnTick(float deltaTime, AZ::ScriptTimePoint /*time*/)
  136. {
  137. AZ_Warning("FollowingCameraComponent", m_currentView.IsValid(), "View is not valid");
  138. if (!m_currentView.IsValid())
  139. {
  140. return;
  141. }
  142. // obtain the current view transform
  143. AZ::Transform target_local_transform;
  144. AZ::Transform target_world_transform;
  145. AZ::TransformBus::Event(m_currentView, &AZ::TransformBus::Events::GetLocalAndWorld, target_local_transform, target_world_transform);
  146. // get parent's transform
  147. const AZ::Transform parent_transform = target_world_transform * target_local_transform.GetInverse();
  148. const AZ::Transform transformToCache = m_configuration.m_lockZAxis ? RemoveTiltFromTransform(parent_transform) : parent_transform;
  149. CacheTransform(transformToCache, deltaTime);
  150. // get the averaged translation and quaternion
  151. AZ::Transform filtered_parent_transform = { SmoothTranslation(), SmoothRotation(), 1.f };
  152. auto modifiedTransformZoom = AZ::Transform::CreateIdentity();
  153. modifiedTransformZoom.SetTranslation(AZ::Vector3::CreateAxisY(m_opticalAxisTranslation));
  154. // adjust the camera's transform
  155. // - rotation is applied in the parent's frame
  156. // - translation is applied in the camera's frame
  157. AZ::Transform filteredTransformAdjusted = filtered_parent_transform *
  158. AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisZ(1.f), m_rotationOffset)) *
  159. target_local_transform * modifiedTransformZoom;
  160. // apply the transform to the camera
  161. AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetWorldTM, filteredTransformAdjusted);
  162. }
  163. AZ::Vector3 FollowingCameraComponent::AverageVector(const AZStd::deque<AZStd::pair<AZ::Vector3, float>>& buffer) const
  164. {
  165. AZ::Vector3 sum{ 0 };
  166. float normalization{ 0 };
  167. for (const auto& p : buffer)
  168. {
  169. sum += p.first * p.second;
  170. normalization += p.second;
  171. }
  172. return sum / normalization;
  173. }
  174. AZ::Vector3 FollowingCameraComponent::SmoothTranslation() const
  175. {
  176. return AverageVector(m_lastTranslationsBuffer);
  177. }
  178. AZ::Quaternion FollowingCameraComponent::SmoothRotation() const
  179. {
  180. AZ::Quaternion q = m_lastRotationsBuffer.front().first;
  181. for (int i = 1; i < m_lastRotationsBuffer.size(); i++)
  182. {
  183. q = q.Slerp(m_lastRotationsBuffer[i].first, m_lastRotationsBuffer[i].second);
  184. }
  185. return q;
  186. }
  187. bool FollowingCameraComponent::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
  188. {
  189. const AzFramework::InputDeviceId& deviceId = inputChannel.GetInputDevice().GetInputDeviceId();
  190. if (AzFramework::InputDeviceKeyboard::IsKeyboardDevice(deviceId) && inputChannel.IsStateBegan())
  191. {
  192. OnKeyboardEvent(inputChannel);
  193. }
  194. return false;
  195. }
  196. void FollowingCameraComponent::OnKeyboardEvent(const AzFramework::InputChannel& inputChannel)
  197. {
  198. const AzFramework::InputChannelId& channelId = inputChannel.GetInputChannelId();
  199. if (!m_disableCameraMove)
  200. {
  201. if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericW)
  202. {
  203. m_opticalAxisTranslation += m_configuration.m_zoomSpeed;
  204. m_opticalAxisTranslation = AZStd::min(m_opticalAxisTranslation, m_configuration.m_opticalAxisTranslationMin);
  205. return;
  206. }
  207. if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericS)
  208. {
  209. m_opticalAxisTranslation -= m_configuration.m_zoomSpeed;
  210. return;
  211. }
  212. if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericA)
  213. {
  214. m_rotationOffset -= m_configuration.m_rotationSpeed;
  215. return;
  216. }
  217. if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericD)
  218. {
  219. m_rotationOffset += m_configuration.m_rotationSpeed;
  220. return;
  221. }
  222. }
  223. // channelId is a numeric key (Key::Alphanumeric0-Key::Alphanumeric9)
  224. if (auto it = KeysToView.find(channelId); it != KeysToView.end())
  225. {
  226. if (int viewId = it->second; viewId < m_configuration.m_predefinedViews.size())
  227. {
  228. m_currentView = m_configuration.m_predefinedViews[viewId];
  229. m_lastTranslationsBuffer.clear();
  230. m_lastRotationsBuffer.clear();
  231. m_rotationOffset = 0.0f;
  232. m_opticalAxisTranslation = 0.0f;
  233. m_disableCameraMove = false; // reset value before reading
  234. AZ::AtomBridge::FlyCameraInputBus::EventResult(
  235. m_disableCameraMove, m_currentView, &AZ::AtomBridge::FlyCameraInputBus::Events::GetIsEnabled);
  236. }
  237. }
  238. }
  239. } // namespace ROS2