diff --git a/Gems/RobotecSpectatorCamera/Code/Include/RobotecSpectatorCamera/RobotecSpectatorCameraBus.h b/Gems/RobotecSpectatorCamera/Code/Include/RobotecSpectatorCamera/RobotecSpectatorCameraBus.h index b319c1c8..6910247b 100644 --- a/Gems/RobotecSpectatorCamera/Code/Include/RobotecSpectatorCamera/RobotecSpectatorCameraBus.h +++ b/Gems/RobotecSpectatorCamera/Code/Include/RobotecSpectatorCamera/RobotecSpectatorCameraBus.h @@ -59,6 +59,31 @@ namespace RobotecSpectatorCamera //! This offset is used to change (in Z-Axis) the point around which the cam orbits //! @param verticalOffset new vertical offset virtual void SetVerticalOffset(const float verticalOffset) = 0; + + //! Get the third-person orbit radius (distance from the look-at target) + //! @return orbit radius + virtual float GetOrbitRadius() const = 0; + + //! Set the third-person orbit radius (distance from the look-at target) + //! The value is clamped to the supported orbit radius range + //! @param orbitRadius new orbit radius + virtual void SetOrbitRadius(const float orbitRadius) = 0; + + //! Get whether mouse look in third-person mode requires the right mouse button to be held + //! @return true if the right mouse button is required to look in third-person mode + virtual bool GetRequireRmbThirdPerson() const = 0; + + //! Set whether mouse look in third-person mode requires the right mouse button to be held + //! @param requireRmb new value of the third-person require-RMB flag + virtual void SetRequireRmbThirdPerson(const bool requireRmb) = 0; + + //! Get whether mouse look in free-flying mode requires the right mouse button to be held + //! @return true if the right mouse button is required to look in free-flying mode + virtual bool GetRequireRmbFreeFlying() const = 0; + + //! Set whether mouse look in free-flying mode requires the right mouse button to be held + //! @param requireRmb new value of the free-flying require-RMB flag + virtual void SetRequireRmbFreeFlying(const bool requireRmb) = 0; }; using RobotecSpectatorCameraRequestBus = AZ::EBus; diff --git a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraComponent.cpp b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraComponent.cpp index fb61d602..3c9fd4b2 100644 --- a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraComponent.cpp +++ b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraComponent.cpp @@ -37,6 +37,11 @@ namespace RobotecSpectatorCamera AZ::TransformBus::EventResult(m_currentTransform, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM); + if (m_configuration.m_seedOrbitFromPlacement) + { + SeedOrbitAnglesFromAuthoredTransform(); + } + auto* registry = AZ::SettingsRegistry::Get(); AZ_Assert(registry, "No Registry available"); if (registry) @@ -56,35 +61,20 @@ namespace RobotecSpectatorCamera { if (m_configuration.m_cameraMode == CameraMode::ThirdPerson) { - // initial position of the camera, 'behind' the target - AZ::Vector3 cameraLocalPosition{ -m_orbitRadius, 0.0f, 0.0f }; - // calculation of the rotations using the m_yaw and m_pitch values (which correspond to the mouse movement) - const AZ::Quaternion yawMouseRotation = AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisZ(1.0f), -m_yaw); - const AZ::Quaternion pitchMouseRotation = AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisY(1.0f), m_pitch); - const AZ::Quaternion finalRotation = yawMouseRotation * pitchMouseRotation; - // local camera position after the mouse rotations - cameraLocalPosition = finalRotation.TransformVector(cameraLocalPosition); + const AZ::Vector3 cameraLocalPosition = OrbitOffsetFromAngles(m_yaw, m_pitch, m_configuration.m_orbitRadius); + AZ::Transform targetWorldTM = AZ::Transform::CreateIdentity(); AZ::TransformBus::EventResult(targetWorldTM, m_configuration.m_lookAtTarget, &AZ::TransformBus::Events::GetWorldTM); - AZ::Vector3 currentTranslation = targetWorldTM.GetTranslation(); - // change the target's translation to apply the vertical offset - const float verticalValueWithOffset = currentTranslation.GetZ() + m_configuration.m_verticalOffset; - currentTranslation.SetZ(verticalValueWithOffset); - targetWorldTM.SetTranslation(currentTranslation); - if (m_configuration.m_followTargetRotation) - { - // calculation of the final transform that follows the target's rotation - without mouse input the camera always looks at - // the same target point - m_currentTransform = - AZ::Transform::CreateLookAt((targetWorldTM.TransformPoint(cameraLocalPosition)), targetWorldTM.GetTranslation()); - } - else - { - // calculation of the final transform that doesn't follow the target's rotation - without mouse input the camera position is - // const, but the target can change its position in relation to the camera - m_currentTransform = - AZ::Transform::CreateLookAt((targetWorldTM.GetTranslation() + cameraLocalPosition), targetWorldTM.GetTranslation()); - } + + AZ::Vector3 lookAtPoint = targetWorldTM.GetTranslation(); + lookAtPoint.SetZ(lookAtPoint.GetZ() + m_configuration.m_verticalOffset); + + // Rotate the offset with GetRotation(), never TransformPoint, so the target's scale + // cannot contaminate the orbit radius. + const AZ::Vector3 cameraWorldPosition = m_configuration.m_followTargetRotation + ? lookAtPoint + targetWorldTM.GetRotation().TransformVector(cameraLocalPosition) + : lookAtPoint + cameraLocalPosition; + m_currentTransform = AZ::Transform::CreateLookAt(cameraWorldPosition, lookAtPoint); } AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetWorldTM, m_currentTransform); } @@ -112,13 +102,11 @@ namespace RobotecSpectatorCamera if (channelId == AzFramework::InputDeviceMouse::Movement::Z && m_configuration.m_cameraMode == CameraMode::ThirdPerson) { - m_orbitRadius = AZStd::clamp( - m_orbitRadius - (inputChannel.GetValue() / scrollValueDivider), - SpectatorCameraConfiguration::OrbitRadiusMin, - SpectatorCameraConfiguration::OrbitRadiusMax); + // Scrolling up (positive value) zooms in, i.e. reduces the orbit radius. + ZoomOrbit(-(inputChannel.GetValue() / scrollValueDivider)); } - if (channelId == AzFramework::InputDeviceMouse::Button::Right && m_configuration.m_cameraMode == CameraMode::ThirdPerson) + if (channelId == AzFramework::InputDeviceMouse::Button::Right && RequiresRightMouseButton(m_configuration)) { AzFramework::SystemCursorState currentCursorState; AzFramework::InputSystemCursorRequestBus::EventResult( @@ -162,7 +150,7 @@ namespace RobotecSpectatorCamera } } - if ((m_isRightMouseButtonPressed || m_configuration.m_cameraMode == CameraMode::FreeFlying) && + if (ShouldRotateOnMouse(m_configuration, m_isRightMouseButtonPressed) && (channelId == AzFramework::InputDeviceMouse::Movement::X || channelId == AzFramework::InputDeviceMouse::Movement::Y)) { if (m_ignoreNextMovement) @@ -206,6 +194,41 @@ namespace RobotecSpectatorCamera } } + if (m_configuration.m_cameraMode == CameraMode::ThirdPerson) + { + // A/D yaw, W/S zoom, Q/E altitude on the orbit sphere. + if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericA) + { + m_yaw += orbitKeyboardYawStep; + } + if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericD) + { + m_yaw -= orbitKeyboardYawStep; + } + // Bound yaw to avoid float drift (mirrors the mouse path). + if (AZStd::abs(m_yaw) >= AZ::DegToRad(360.0f)) + { + m_yaw = 0.0f; + } + if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericW) + { + ZoomOrbit(-orbitKeyboardZoomStep); + } + if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericS) + { + ZoomOrbit(orbitKeyboardZoomStep); + } + const float pitchLimit = AZ::DegToRad(pitchDegLimit); + if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericE) + { + m_pitch = AZStd::clamp(m_pitch + orbitKeyboardPitchStep, -pitchLimit, pitchLimit); + } + if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericQ) + { + m_pitch = AZStd::clamp(m_pitch - orbitKeyboardPitchStep, -pitchLimit, pitchLimit); + } + } + if (m_configuration.m_cameraMode == CameraMode::FreeFlying) { AZ::Vector3 localCamPositionChange = AZ::Vector3::CreateZero(); @@ -295,6 +318,68 @@ namespace RobotecSpectatorCamera } } + bool SpectatorCameraComponent::RequiresRightMouseButton(const SpectatorCameraConfiguration& configuration) + { + return configuration.m_cameraMode == CameraMode::ThirdPerson ? configuration.m_requireRmbThirdPerson + : configuration.m_requireRmbFreeFlying; + } + + bool SpectatorCameraComponent::ShouldRotateOnMouse(const SpectatorCameraConfiguration& configuration, bool isRightMouseButtonPressed) + { + return !RequiresRightMouseButton(configuration) || isRightMouseButtonPressed; + } + + void SpectatorCameraComponent::ZoomOrbit(float radiusDelta) + { + m_configuration.m_orbitRadius = AZStd::clamp( + m_configuration.m_orbitRadius + radiusDelta, + SpectatorCameraConfiguration::OrbitRadiusMin, + SpectatorCameraConfiguration::OrbitRadiusMax); + } + + AZ::Vector3 SpectatorCameraComponent::OrbitOffsetFromAngles(float yaw, float pitch, float radius) + { + const AZ::Quaternion yawRotation = AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisZ(1.0f), -yaw); + const AZ::Quaternion pitchRotation = AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisY(1.0f), pitch); + return (yawRotation * pitchRotation).TransformVector(AZ::Vector3{ -radius, 0.0f, 0.0f }); + } + + void SpectatorCameraComponent::OrbitAnglesFromOffset(const AZ::Vector3& offset, float& yaw, float& pitch) + { + const float horizontalLength = AZ::Vector2(offset.GetX(), offset.GetY()).GetLength(); + if (offset.GetLength() < AZ::Constants::Tolerance) + { + return; // degenerate: camera coincides with the look-at point, keep the current angles + } + // Inverse of OrbitOffsetFromAngles: offset = (-r cosP cosY, r cosP sinY, r sinP) with P = pitch, Y = yaw. + const float pitchLimit = AZ::DegToRad(pitchDegLimit); + pitch = AZStd::clamp(AZ::Atan2(offset.GetZ(), horizontalLength), -pitchLimit, pitchLimit); + yaw = AZ::Atan2(offset.GetY(), -offset.GetX()); + } + + void SpectatorCameraComponent::SeedOrbitAnglesFromAuthoredTransform() + { + if (!m_configuration.m_lookAtTarget.IsValid()) + { + return; // no target -> keep the default orbit angles + } + + AZ::Transform targetWorldTM = AZ::Transform::CreateIdentity(); + AZ::TransformBus::EventResult(targetWorldTM, m_configuration.m_lookAtTarget, &AZ::TransformBus::Events::GetWorldTM); + + AZ::Vector3 lookAtPoint = targetWorldTM.GetTranslation(); + lookAtPoint.SetZ(lookAtPoint.GetZ() + m_configuration.m_verticalOffset); + + AZ::Vector3 offset = m_currentTransform.GetTranslation() - lookAtPoint; + + // OnTick applies the offset in the target's rotated frame when following, so decompose in that frame. + if (m_configuration.m_followTargetRotation) + { + offset = targetWorldTM.GetRotation().GetConjugate().TransformVector(offset); + } + OrbitAnglesFromOffset(offset, m_yaw, m_pitch); + } + void SpectatorCameraComponent::ToggleCameraMode() { // RMB should be handled only in the ThirdPerson mode. This line fixes problem with such combination: @@ -357,4 +442,35 @@ namespace RobotecSpectatorCamera { m_configuration.m_verticalOffset = verticalOffset; } + + float SpectatorCameraComponent::GetOrbitRadius() const + { + return m_configuration.m_orbitRadius; + } + + void SpectatorCameraComponent::SetOrbitRadius(const float orbitRadius) + { + m_configuration.m_orbitRadius = + AZStd::clamp(orbitRadius, SpectatorCameraConfiguration::OrbitRadiusMin, SpectatorCameraConfiguration::OrbitRadiusMax); + } + + bool SpectatorCameraComponent::GetRequireRmbThirdPerson() const + { + return m_configuration.m_requireRmbThirdPerson; + } + + void SpectatorCameraComponent::SetRequireRmbThirdPerson(const bool requireRmb) + { + m_configuration.m_requireRmbThirdPerson = requireRmb; + } + + bool SpectatorCameraComponent::GetRequireRmbFreeFlying() const + { + return m_configuration.m_requireRmbFreeFlying; + } + + void SpectatorCameraComponent::SetRequireRmbFreeFlying(const bool requireRmb) + { + m_configuration.m_requireRmbFreeFlying = requireRmb; + } } // namespace RobotecSpectatorCamera diff --git a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraComponent.h b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraComponent.h index a8c8124e..2b062ca3 100644 --- a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraComponent.h +++ b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraComponent.h @@ -44,20 +44,43 @@ namespace RobotecSpectatorCamera void SetFollowTargetRotation(const bool followTargetRotation) override; float GetVerticalOffset() const override; void SetVerticalOffset(const float verticalOffset) override; + float GetOrbitRadius() const override; + void SetOrbitRadius(const float orbitRadius) override; + bool GetRequireRmbThirdPerson() const override; + void SetRequireRmbThirdPerson(const bool requireRmb) override; + bool GetRequireRmbFreeFlying() const override; + void SetRequireRmbFreeFlying(const bool requireRmb) override; + + static bool ShouldRotateOnMouse(const SpectatorCameraConfiguration& configuration, bool isRightMouseButtonPressed); + + //! Camera offset relative to the look-at point for the given orbit angles and radius. + static AZ::Vector3 OrbitOffsetFromAngles(float yaw, float pitch, float radius); + + //! Inverse of OrbitOffsetFromAngles (direction only); pitch is clamped to the orbit limit. + static void OrbitAnglesFromOffset(const AZ::Vector3& offset, float& yaw, float& pitch); private: + static bool RequiresRightMouseButton(const SpectatorCameraConfiguration& configuration); + + //! Seed yaw/pitch so third-person starts along the authored camera -> look-at ray. + void SeedOrbitAnglesFromAuthoredTransform(); + void MouseEvent(const AzFramework::InputChannel& inputChannel); void KeyboardEvent(const AzFramework::InputChannel& inputChannel); AZ::Vector2 GetCurrentMousePosition() const; void RotateCameraOnMouse(const AZ::Vector2& mouseDelta); + void ZoomOrbit(float radiusDelta); void ToggleCameraMode(); static constexpr float scrollValueDivider = 1200.0f; static constexpr float pitchDegLimit = 87.0f; + // Per-input-event keyboard orbit steps: yaw/pitch in radians, zoom in meters. + static constexpr float orbitKeyboardYawStep = 0.02f; + static constexpr float orbitKeyboardPitchStep = 0.02f; + static constexpr float orbitKeyboardZoomStep = 0.1f; SpectatorCameraConfiguration m_configuration; - float m_orbitRadius{ 10.0f }; float m_pitch{ 0.5f }; float m_yaw{ 0.0f }; bool m_isRightMouseButtonPressed{ false }; diff --git a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraConfiguration.cpp b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraConfiguration.cpp index 4e59877c..63ec726c 100644 --- a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraConfiguration.cpp +++ b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraConfiguration.cpp @@ -8,12 +8,16 @@ namespace RobotecSpectatorCamera if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(0) + ->Version(1) ->Field("LookAtTarget", &SpectatorCameraConfiguration::m_lookAtTarget) ->Field("FollowTargetRotation", &SpectatorCameraConfiguration::m_followTargetRotation) ->Field("MouseSensitivity", &SpectatorCameraConfiguration::m_mouseSensitivity) ->Field("CameraSpeed", &SpectatorCameraConfiguration::m_cameraSpeed) - ->Field("VerticalOffset", &SpectatorCameraConfiguration::m_verticalOffset); + ->Field("VerticalOffset", &SpectatorCameraConfiguration::m_verticalOffset) + ->Field("OrbitRadius", &SpectatorCameraConfiguration::m_orbitRadius) + ->Field("RequireRmbThirdPerson", &SpectatorCameraConfiguration::m_requireRmbThirdPerson) + ->Field("RequireRmbFreeFlying", &SpectatorCameraConfiguration::m_requireRmbFreeFlying) + ->Field("SeedOrbitFromPlacement", &SpectatorCameraConfiguration::m_seedOrbitFromPlacement); if (auto editContext = serializeContext->GetEditContext()) { @@ -22,7 +26,7 @@ namespace RobotecSpectatorCamera ->DataElement( AZ::Edit::UIHandlers::Default, &SpectatorCameraConfiguration::m_lookAtTarget, - "Look at target entity ID", + "Look-at target", "Look at target entity ID") ->DataElement( AZ::Edit::UIHandlers::Default, @@ -46,7 +50,30 @@ namespace RobotecSpectatorCamera "Vertical offset", "Vertical offset to change the point of the target which is used to calculate LookAt transform") ->Attribute(AZ::Edit::Attributes::Min, VerticalOffsetMin) - ->Attribute(AZ::Edit::Attributes::Max, VerticalOffsetMax); + ->Attribute(AZ::Edit::Attributes::Max, VerticalOffsetMax) + ->DataElement( + AZ::Edit::UIHandlers::Slider, + &SpectatorCameraConfiguration::m_orbitRadius, + "Orbit radius", + "Initial third-person distance from the look-at target (also adjustable at runtime via scroll or W/S)") + ->Attribute(AZ::Edit::Attributes::Min, OrbitRadiusMin) + ->Attribute(AZ::Edit::Attributes::Max, OrbitRadiusMax) + ->DataElement( + AZ::Edit::UIHandlers::Default, + &SpectatorCameraConfiguration::m_requireRmbThirdPerson, + "Third-person RMB", + "When enabled, mouse look in third-person mode only applies while the right mouse button is held") + ->DataElement( + AZ::Edit::UIHandlers::Default, + &SpectatorCameraConfiguration::m_requireRmbFreeFlying, + "Free-flying RMB", + "When enabled, mouse look in free-flying mode only applies while the right mouse button is held") + ->DataElement( + AZ::Edit::UIHandlers::Default, + &SpectatorCameraConfiguration::m_seedOrbitFromPlacement, + "Start at placement", + "When enabled, the initial orbit angles derive from the entity's placed transform; otherwise the camera starts " + "behind the target"); } } } diff --git a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraConfiguration.h b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraConfiguration.h index 7e1a9da6..7de5ac89 100644 --- a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraConfiguration.h +++ b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraConfiguration.h @@ -31,7 +31,11 @@ namespace RobotecSpectatorCamera float m_mouseSensitivity{ 1.0f }; float m_cameraSpeed{ CameraSpeedMin }; float m_verticalOffset{ 0.0f }; + float m_orbitRadius{ 10.0f }; bool m_followTargetRotation{ true }; + bool m_requireRmbThirdPerson{ true }; + bool m_requireRmbFreeFlying{ false }; + bool m_seedOrbitFromPlacement{ false }; CameraMode m_cameraMode{ CameraMode::ThirdPerson }; }; } // namespace RobotecSpectatorCamera diff --git a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraEditorComponent.cpp b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraEditorComponent.cpp index be3a74bc..d324bec2 100644 --- a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraEditorComponent.cpp +++ b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraEditorComponent.cpp @@ -1,6 +1,9 @@ #include "SpectatorCameraEditorComponent.h" #include "SpectatorCameraComponent.h" +#include #include +#include +#include namespace RobotecSpectatorCamera { @@ -32,6 +35,74 @@ namespace RobotecSpectatorCamera required.push_back(AZ_CRC_CE("CameraService")); } + void SpectatorCameraEditorComponent::Activate() + { + AzToolsFramework::Components::EditorComponentBase::Activate(); + AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId()); + Camera::EditorCameraNotificationBus::Handler::BusConnect(); + } + + void SpectatorCameraEditorComponent::Deactivate() + { + Camera::EditorCameraNotificationBus::Handler::BusDisconnect(); + AZ::TransformNotificationBus::Handler::BusDisconnect(); + AzToolsFramework::Components::EditorComponentBase::Deactivate(); + } + + bool SpectatorCameraEditorComponent::IsActiveViewCamera() const + { + AZ::EntityId currentViewEntity; + Camera::EditorCameraRequests::Bus::BroadcastResult(currentViewEntity, &Camera::EditorCameraRequests::GetCurrentViewEntityId); + return currentViewEntity == GetEntityId(); + } + + void SpectatorCameraEditorComponent::OnTransformChanged( + [[maybe_unused]] const AZ::Transform& local, [[maybe_unused]] const AZ::Transform& world) + { + // Track the radius only while "Be this camera" is engaged, riding its viewport -> entity transform sync. + if (IsActiveViewCamera()) + { + UpdateOrbitRadiusFromCurrentTransform(); + } + } + + void SpectatorCameraEditorComponent::OnViewportViewEntityChanged(const AZ::EntityId& newViewId) + { + // Becoming the active view emits no transform change of its own, so refresh here too. + if (newViewId == GetEntityId()) + { + UpdateOrbitRadiusFromCurrentTransform(); + } + } + + void SpectatorCameraEditorComponent::UpdateOrbitRadiusFromCurrentTransform() + { + if (!m_configuration.m_lookAtTarget.IsValid()) + { + return; + } + + AZ::Transform cameraWorldTM = AZ::Transform::CreateIdentity(); + AZ::TransformBus::EventResult(cameraWorldTM, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM); + + AZ::Transform targetWorldTM = AZ::Transform::CreateIdentity(); + AZ::TransformBus::EventResult(targetWorldTM, m_configuration.m_lookAtTarget, &AZ::TransformBus::Events::GetWorldTM); + AZ::Vector3 lookAtPoint = targetWorldTM.GetTranslation(); + lookAtPoint.SetZ(lookAtPoint.GetZ() + m_configuration.m_verticalOffset); + + const float newRadius = AZStd::clamp( + (cameraWorldTM.GetTranslation() - lookAtPoint).GetLength(), + SpectatorCameraConfiguration::OrbitRadiusMin, + SpectatorCameraConfiguration::OrbitRadiusMax); + + if (!AZ::IsClose(newRadius, m_configuration.m_orbitRadius)) + { + m_configuration.m_orbitRadius = newRadius; + AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( + &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_Values); + } + } + void SpectatorCameraEditorComponent::BuildGameEntity(AZ::Entity* gameEntity) { AZ_Error("SpectatorCameraEditorComponent", m_configuration.m_lookAtTarget.IsValid(), "LookAtTarget's EntityId is not valid"); diff --git a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraEditorComponent.h b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraEditorComponent.h index 3198aeb9..41ccbfe2 100644 --- a/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraEditorComponent.h +++ b/Gems/RobotecSpectatorCamera/Code/Source/SpectatorCamera/SpectatorCameraEditorComponent.h @@ -1,12 +1,17 @@ #pragma once #include "SpectatorCameraConfiguration.h" +#include +#include #include #include namespace RobotecSpectatorCamera { - class SpectatorCameraEditorComponent : public AzToolsFramework::Components::EditorComponentBase + class SpectatorCameraEditorComponent + : public AzToolsFramework::Components::EditorComponentBase + , public AZ::TransformNotificationBus::Handler + , public Camera::EditorCameraNotificationBus::Handler { public: AZ_EDITOR_COMPONENT(SpectatorCameraEditorComponent, SpectatorCameraEditorComponentTypeId); @@ -15,9 +20,23 @@ namespace RobotecSpectatorCamera static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + // AzToolsFramework::Components::EditorComponentBase overrides + void Activate() override; + void Deactivate() override; + void BuildGameEntity(AZ::Entity* gameEntity) override; + // AZ::TransformNotificationBus::Handler overrides + void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override; + + // Camera::EditorCameraNotificationBus::Handler overrides + void OnViewportViewEntityChanged(const AZ::EntityId& newViewId) override; + private: + bool IsActiveViewCamera() const; + //! Recompute the orbit radius from the entity's distance to the look-at point. + void UpdateOrbitRadiusFromCurrentTransform(); + SpectatorCameraConfiguration m_configuration; }; } // namespace RobotecSpectatorCamera diff --git a/Gems/RobotecSpectatorCamera/gem.json b/Gems/RobotecSpectatorCamera/gem.json index 753a21f2..5fddc616 100644 --- a/Gems/RobotecSpectatorCamera/gem.json +++ b/Gems/RobotecSpectatorCamera/gem.json @@ -1,6 +1,6 @@ { "gem_name": "RobotecSpectatorCamera", - "version": "1.0.0", + "version": "2.0.0", "display_name": "RobotecSpectatorCamera", "license": "Apache-2.0", "license_url": "https://opensource.org/licenses/Apache-2.0", diff --git a/README.md b/README.md index a5a8bba5..00b62af3 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Note that this is not a "Canonical" part of O3DE - those gems are third-party co | [**RobotecGeoJSONSpawner**](#groboteceojsonspawner) | not verified | | [**RobotecGeoJSONSpawnerROS2**](#robotecgeojsonspawnerros2) | not verified | | [**RobotecRecordingTools**](#robotecrecordingtools) | not verified | -| [**RobotecSpectatorCamera**](#robotecspectatorcamera) | not verified | +| [**RobotecSpectatorCamera**](#robotecspectatorcamera) | compatible | | [**RobotecSplineTools**](#robotecsplinetools) | not verified | | [**RobotecWatchdogTools**](#robotecwatchdogtools) | not verified | | [**ROS2PoseControl**](#ros2posecontrol) | compatible |