Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<RobotecSpectatorCameraRequests>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ namespace RobotecSpectatorCamera
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SpectatorCameraConfiguration>()
->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())
{
Expand All @@ -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,
Expand All @@ -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");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading
Loading