You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
443 lines
19 KiB
C++
443 lines
19 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
#include <Atom/Component/DebugCamera/ArcBallControllerComponent.h>
|
|
|
|
#include <AzCore/Component/TransformBus.h>
|
|
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
|
|
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
|
#include <AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h>
|
|
#include <AzFramework/Input/Devices/Touch/InputDeviceTouch.h>
|
|
#include <AzFramework/Windowing/WindowBus.h>
|
|
|
|
#include <DebugCameraUtils.h>
|
|
|
|
namespace AZ
|
|
{
|
|
namespace Debug
|
|
{
|
|
ArcBallControllerComponent::ArcBallControllerComponent()
|
|
: AzFramework::InputChannelEventListener(AzFramework::InputChannelEventListener::GetPriorityDefault())
|
|
{}
|
|
|
|
void ArcBallControllerComponent::Reflect(AZ::ReflectContext* reflection)
|
|
{
|
|
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
|
|
{
|
|
serializeContext->Class<ArcBallControllerComponent, CameraControllerComponent, AZ::Component>()
|
|
->Version(1);
|
|
}
|
|
|
|
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflection))
|
|
{
|
|
behaviorContext->EBus<ArcBallControllerRequestBus>("ArcBallControllerRequestBus")
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
|
->Attribute(AZ::Script::Attributes::Category, "Camera")
|
|
->Attribute(AZ::Script::Attributes::Module, "render")
|
|
->Event("SetCenter", &ArcBallControllerRequestBus::Events::SetCenter)
|
|
->Event("SetPan", &ArcBallControllerRequestBus::Events::SetPan)
|
|
->Event("SetDistance", &ArcBallControllerRequestBus::Events::SetDistance)
|
|
->Event("SetMinDistance", &ArcBallControllerRequestBus::Events::SetMinDistance)
|
|
->Event("SetMaxDistance", &ArcBallControllerRequestBus::Events::SetMaxDistance)
|
|
->Event("SetHeading", &ArcBallControllerRequestBus::Events::SetHeading)
|
|
->Event("SetPitch", &ArcBallControllerRequestBus::Events::SetPitch)
|
|
->Event("SetPanningSensitivity", &ArcBallControllerRequestBus::Events::SetPanningSensitivity)
|
|
->Event("SetZoomingSensitivity", &ArcBallControllerRequestBus::Events::SetZoomingSensitivity)
|
|
->Event("GetCenter", &ArcBallControllerRequestBus::Events::GetCenter)
|
|
->Event("GetPan", &ArcBallControllerRequestBus::Events::GetPan)
|
|
->Event("GetDistance", &ArcBallControllerRequestBus::Events::GetDistance)
|
|
->Event("GetMinDistance", &ArcBallControllerRequestBus::Events::GetMinDistance)
|
|
->Event("GetMaxDistance", &ArcBallControllerRequestBus::Events::GetMaxDistance)
|
|
->Event("GetHeading", &ArcBallControllerRequestBus::Events::GetHeading)
|
|
->Event("GetPitch", &ArcBallControllerRequestBus::Events::GetPitch)
|
|
->Event("GetPanningSensitivity", &ArcBallControllerRequestBus::Events::GetPanningSensitivity)
|
|
->Event("GetZoomingSensitivity", &ArcBallControllerRequestBus::Events::GetZoomingSensitivity)
|
|
;
|
|
}
|
|
}
|
|
|
|
void ArcBallControllerComponent::OnEnabled()
|
|
{
|
|
// Reset parameters with initial values
|
|
m_arcballActive = false;
|
|
m_panningActive = false;
|
|
m_center = AZ::Vector3::CreateZero();
|
|
m_panningOffset = AZ::Vector3::CreateZero();
|
|
m_panningOffsetDelta = AZ::Vector3::CreateZero();
|
|
m_distance = 5.0f;
|
|
m_minDistance = 0.1f;
|
|
m_maxDistance = 10.0f;
|
|
m_currentHeading = 0.0f;
|
|
m_currentPitch = 0.0f;
|
|
m_panningSensitivity = 1.0f;
|
|
m_zoomingSensitivity = 1.0f;
|
|
|
|
AzFramework::NativeWindowHandle windowHandle = nullptr;
|
|
AzFramework::WindowSystemRequestBus::BroadcastResult(
|
|
windowHandle,
|
|
&AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
|
|
|
|
AzFramework::WindowSize windowSize;
|
|
AzFramework::WindowRequestBus::EventResult(
|
|
windowSize,
|
|
windowHandle,
|
|
&AzFramework::WindowRequestBus::Events::GetClientAreaSize);
|
|
|
|
m_windowWidth = windowSize.m_width;
|
|
m_windowHeight = windowSize.m_height;
|
|
|
|
ArcBallControllerRequestBus::Handler::BusConnect(GetEntityId());
|
|
AzFramework::InputChannelEventListener::Connect();
|
|
AZ::TickBus::Handler::BusConnect();
|
|
}
|
|
|
|
void ArcBallControllerComponent::OnDisabled()
|
|
{
|
|
TickBus::Handler::BusDisconnect();
|
|
AzFramework::InputChannelEventListener::Disconnect();
|
|
ArcBallControllerRequestBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
void ArcBallControllerComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time)
|
|
{
|
|
AZ_UNUSED(time);
|
|
|
|
if (m_distance < m_minDistance)
|
|
{
|
|
m_distance = m_minDistance;
|
|
}
|
|
else if (m_distance > m_maxDistance)
|
|
{
|
|
m_distance = m_maxDistance;
|
|
}
|
|
|
|
// The coordinate system is right-handed and Z-up. So heading is a rotation around the Z axis.
|
|
// After that rotation we rotate around the (rotated by heading) X axis for pitch.
|
|
AZ::Quaternion orientation = AZ::Quaternion::CreateRotationZ(m_currentHeading)
|
|
* AZ::Quaternion::CreateRotationX(m_currentPitch);
|
|
|
|
m_panningOffsetDelta *= deltaTime;
|
|
m_panningOffset += (orientation.TransformVector(m_panningOffsetDelta));
|
|
AZ::Vector3 position = (m_center + m_panningOffset) + (orientation.TransformVector(AZ::Vector3(0, -m_distance, 0)));
|
|
|
|
AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation(orientation, position);
|
|
AZ::TransformBus::Event(
|
|
GetEntityId(), &AZ::TransformBus::Events::SetLocalTM, transform);
|
|
}
|
|
|
|
bool ArcBallControllerComponent::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
|
|
{
|
|
static const float PixelToDegree = 1.0 / 360.0f;
|
|
|
|
uint32_t handledChannels = ArcBallControllerChannel_None;
|
|
|
|
const AzFramework::InputChannelId& inputChannelId = inputChannel.GetInputChannelId();
|
|
switch (inputChannel.GetState())
|
|
{
|
|
case AzFramework::InputChannel::State::Began:
|
|
case AzFramework::InputChannel::State::Updated: // update the camera rotation
|
|
{
|
|
//Keyboard & Mouse
|
|
if (inputChannelId == AzFramework::InputDeviceMouse::Button::Right)
|
|
{
|
|
m_arcballActive = true;
|
|
handledChannels |= ArcBallControllerChannel_Orientation;
|
|
}
|
|
else if (inputChannelId == AzFramework::InputDeviceMouse::Button::Middle)
|
|
{
|
|
m_panningActive = true;
|
|
handledChannels |= ArcBallControllerChannel_Pan;
|
|
}
|
|
|
|
if (m_arcballActive)
|
|
{
|
|
if (inputChannelId == AzFramework::InputDeviceMouse::Movement::X)
|
|
{
|
|
// modify yaw angle
|
|
m_currentHeading -= inputChannel.GetValue() * PixelToDegree;
|
|
m_currentHeading = NormalizeAngle(m_currentHeading);
|
|
}
|
|
else if (inputChannelId == AzFramework::InputDeviceMouse::Movement::Y)
|
|
{
|
|
// modify pitch angle
|
|
m_currentPitch -= inputChannel.GetValue() * PixelToDegree;
|
|
m_currentPitch = AZStd::max(m_currentPitch, -AZ::Constants::HalfPi);
|
|
m_currentPitch = AZStd::min(m_currentPitch, AZ::Constants::HalfPi);
|
|
}
|
|
}
|
|
else if (m_panningActive)
|
|
{
|
|
if (inputChannelId == AzFramework::InputDeviceMouse::Movement::X)
|
|
{
|
|
m_panningOffsetDelta.SetX(-inputChannel.GetValue() * m_panningSensitivity);
|
|
}
|
|
else if (inputChannelId == AzFramework::InputDeviceMouse::Movement::Y)
|
|
{
|
|
m_panningOffsetDelta.SetZ(inputChannel.GetValue() * m_panningSensitivity);
|
|
}
|
|
}
|
|
|
|
if (inputChannelId == AzFramework::InputDeviceMouse::Movement::Z)
|
|
{
|
|
const float MouseWheelDeltaScale = 1.0f / 120.0f; // based on WHEEL_DELTA in WinUser.h
|
|
m_distance -= inputChannel.GetValue() * MouseWheelDeltaScale * m_zoomingSensitivity;
|
|
m_zoomingActive = true;
|
|
handledChannels |= ArcBallControllerChannel_Distance;
|
|
}
|
|
|
|
// Gamepad
|
|
if (inputChannelId == AzFramework::InputDeviceGamepad::Trigger::L2)
|
|
{
|
|
m_arcballActive = true;
|
|
handledChannels |= ArcBallControllerChannel_Orientation;
|
|
}
|
|
else if (inputChannelId == AzFramework::InputDeviceGamepad::Button::L1)
|
|
{
|
|
m_panningActive = true;
|
|
handledChannels |= ArcBallControllerChannel_Pan;
|
|
}
|
|
|
|
if (m_arcballActive)
|
|
{
|
|
if (inputChannelId == AzFramework::InputDeviceGamepad::ThumbStickAxis1D::RX)
|
|
{
|
|
// modify yaw angle
|
|
m_currentHeading -= inputChannel.GetValue() * PixelToDegree;
|
|
m_currentHeading = NormalizeAngle(m_currentHeading);
|
|
}
|
|
else if (inputChannelId == AzFramework::InputDeviceGamepad::ThumbStickAxis1D::RY)
|
|
{
|
|
// modify pitch angle
|
|
m_currentPitch += inputChannel.GetValue() * PixelToDegree;
|
|
m_currentPitch = AZStd::max(m_currentPitch, -AZ::Constants::HalfPi);
|
|
m_currentPitch = AZStd::min(m_currentPitch, AZ::Constants::HalfPi);
|
|
}
|
|
}
|
|
else if (m_panningActive)
|
|
{
|
|
if (inputChannelId == AzFramework::InputDeviceGamepad::ThumbStickAxis1D::RX)
|
|
{
|
|
m_panningOffsetDelta.SetX(-inputChannel.GetValue() * 10.0f * m_panningSensitivity);
|
|
}
|
|
else if (inputChannelId == AzFramework::InputDeviceGamepad::ThumbStickAxis1D::RY)
|
|
{
|
|
m_panningOffsetDelta.SetZ(inputChannel.GetValue() * 10.0f * m_panningSensitivity);
|
|
}
|
|
}
|
|
|
|
if (inputChannelId == AzFramework::InputDeviceGamepad::ThumbStickAxis1D::LY)
|
|
{
|
|
m_distance -= inputChannel.GetValue() * m_zoomingSensitivity;
|
|
m_zoomingActive = true;
|
|
handledChannels |= ArcBallControllerChannel_Distance;
|
|
}
|
|
|
|
// Touch controls works depending which side of the screen you start the touch event.
|
|
// Left side controls the heading and pitch. Right side controls the panning.
|
|
// Only one touch control can be active at the same time.
|
|
if (inputChannelId == AzFramework::InputDeviceTouch::Touch::Index0)
|
|
{
|
|
auto* positionData = inputChannel.GetCustomData<AzFramework::InputChannel::PositionData2D>();
|
|
AZ::Vector2 screenPos = positionData->m_normalizedPosition;
|
|
auto deltaInPixels = screenPos - m_lastTouchPosition;
|
|
deltaInPixels *= AZ::Vector2(static_cast<float>(m_windowWidth), static_cast<float>(m_windowHeight));
|
|
if (inputChannel.GetState() == AzFramework::InputChannel::State::Began)
|
|
{
|
|
m_panningActive = screenPos.GetX() > 0.5f ? true : false;
|
|
m_arcballActive = !m_panningActive;
|
|
}
|
|
else if(m_panningActive)
|
|
{
|
|
m_panningOffsetDelta.SetX(-deltaInPixels.GetX() * m_panningSensitivity);
|
|
m_panningOffsetDelta.SetZ(deltaInPixels.GetY() * m_panningSensitivity);
|
|
}
|
|
else if(m_arcballActive)
|
|
{
|
|
// modify yaw angle
|
|
m_currentHeading -= deltaInPixels.GetX() * PixelToDegree;
|
|
m_currentHeading = NormalizeAngle(m_currentHeading);
|
|
|
|
// modify pitch angle
|
|
m_currentPitch -= deltaInPixels.GetY() * PixelToDegree;
|
|
m_currentPitch = AZStd::max(m_currentPitch, -AZ::Constants::HalfPi);
|
|
m_currentPitch = AZStd::min(m_currentPitch, AZ::Constants::HalfPi);
|
|
}
|
|
|
|
m_lastTouchPosition = screenPos;
|
|
|
|
if (m_panningActive)
|
|
{
|
|
handledChannels |= ArcBallControllerChannel_Pan;
|
|
}
|
|
else if (m_arcballActive)
|
|
{
|
|
handledChannels |= ArcBallControllerChannel_Orientation;
|
|
}
|
|
}
|
|
|
|
if (handledChannels && AzFramework::InputChannel::State::Began == inputChannel.GetState())
|
|
{
|
|
CameraControllerNotificationBus::Broadcast(&CameraControllerNotifications::OnCameraMoveBegan, RTTI_GetType(), handledChannels);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case AzFramework::InputChannel::State::Ended: // update the released input state
|
|
{
|
|
uint32_t handledChannels2 = ArcBallControllerChannel_None;
|
|
|
|
if (inputChannelId == AzFramework::InputDeviceMouse::Button::Right)
|
|
{
|
|
m_arcballActive = false;
|
|
handledChannels2 |= ArcBallControllerChannel_Orientation;
|
|
}
|
|
else if (inputChannelId == AzFramework::InputDeviceMouse::Button::Middle)
|
|
{
|
|
m_panningActive = false;
|
|
handledChannels2 |= ArcBallControllerChannel_Pan;
|
|
}
|
|
else if (inputChannelId == AzFramework::InputDeviceGamepad::Trigger::L2)
|
|
{
|
|
m_arcballActive = false;
|
|
handledChannels2 |= ArcBallControllerChannel_Orientation;
|
|
}
|
|
else if (inputChannelId == AzFramework::InputDeviceGamepad::Button::L1)
|
|
{
|
|
m_panningActive = false;
|
|
handledChannels2 |= ArcBallControllerChannel_Pan;
|
|
}
|
|
else if (inputChannelId == AzFramework::InputDeviceTouch::Touch::Index0)
|
|
{
|
|
if (m_panningActive)
|
|
{
|
|
handledChannels2 |= ArcBallControllerChannel_Pan;
|
|
}
|
|
else if (m_arcballActive)
|
|
{
|
|
handledChannels2 |= ArcBallControllerChannel_Orientation;
|
|
}
|
|
|
|
m_panningActive = false;
|
|
m_arcballActive = false;
|
|
}
|
|
else if (inputChannelId == AzFramework::InputDeviceMouse::Movement::Z ||
|
|
inputChannelId == AzFramework::InputDeviceGamepad::ThumbStickAxis1D::LY)
|
|
{
|
|
m_zoomingActive = false;
|
|
handledChannels2 |= ArcBallControllerChannel_Distance;
|
|
}
|
|
|
|
if (handledChannels2)
|
|
{
|
|
CameraControllerNotificationBus::Broadcast(&CameraControllerNotifications::OnCameraMoveEnded, RTTI_GetType(), handledChannels2);
|
|
}
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ArcBallControllerComponent::SetCenter(AZ::Vector3 center)
|
|
{
|
|
m_center = center;
|
|
}
|
|
|
|
void ArcBallControllerComponent::SetPan(AZ::Vector3 pan)
|
|
{
|
|
m_panningOffset = pan;
|
|
}
|
|
|
|
void ArcBallControllerComponent::SetDistance(float distance)
|
|
{
|
|
m_distance = distance;
|
|
}
|
|
|
|
void ArcBallControllerComponent::SetMinDistance(float minDistance)
|
|
{
|
|
m_minDistance = minDistance;
|
|
m_distance = AZ::GetMax(m_distance, m_minDistance);
|
|
}
|
|
|
|
void ArcBallControllerComponent::SetMaxDistance(float maxDistance)
|
|
{
|
|
m_maxDistance = maxDistance;
|
|
m_distance = AZ::GetMin(m_distance, m_maxDistance);
|
|
}
|
|
|
|
void ArcBallControllerComponent::SetHeading(float heading)
|
|
{
|
|
m_currentHeading = heading;
|
|
}
|
|
|
|
void ArcBallControllerComponent::SetPitch(float pitch)
|
|
{
|
|
m_currentPitch = pitch;
|
|
}
|
|
|
|
void ArcBallControllerComponent::SetPanningSensitivity(float panningSensitivity)
|
|
{
|
|
m_panningSensitivity = AZ::GetMax(panningSensitivity, 0.0f);
|
|
}
|
|
|
|
void ArcBallControllerComponent::SetZoomingSensitivity(float zoomingSensitivity)
|
|
{
|
|
m_zoomingSensitivity = AZ::GetMax(zoomingSensitivity, 0.0f);
|
|
}
|
|
|
|
AZ::Vector3 ArcBallControllerComponent::GetCenter()
|
|
{
|
|
return m_center;
|
|
}
|
|
|
|
AZ::Vector3 ArcBallControllerComponent::GetPan()
|
|
{
|
|
return m_panningOffset;
|
|
}
|
|
|
|
float ArcBallControllerComponent::GetDistance()
|
|
{
|
|
return m_distance;
|
|
}
|
|
|
|
float ArcBallControllerComponent::GetMinDistance()
|
|
{
|
|
return m_minDistance;
|
|
}
|
|
|
|
float ArcBallControllerComponent::GetMaxDistance()
|
|
{
|
|
return m_maxDistance;
|
|
}
|
|
|
|
float ArcBallControllerComponent::GetHeading()
|
|
{
|
|
return m_currentHeading;
|
|
}
|
|
|
|
float ArcBallControllerComponent::GetPitch()
|
|
{
|
|
return m_currentPitch;
|
|
}
|
|
|
|
float ArcBallControllerComponent::GetPanningSensitivity()
|
|
{
|
|
return m_panningSensitivity;
|
|
}
|
|
|
|
float ArcBallControllerComponent::GetZoomingSensitivity()
|
|
{
|
|
return m_zoomingSensitivity;
|
|
}
|
|
} // namespace Debug
|
|
} // namespace AZ
|