/* * 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 #include #include #include #include #include #include #include #include namespace AZ { namespace Debug { const AzFramework::InputChannelId NoClipControllerComponent::TouchEvent::InvalidTouchChannelId = AzFramework::InputChannelId("InvalidChannel"); static constexpr float MaxFov = 160.0f * Constants::Pi / 180.0f; static constexpr float MinFov = 1.0f * Constants::Pi / 180.0f; static constexpr float DefaultFov = Constants::QuarterPi; void NoClipControllerProperties::Reflect(AZ::ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(2) ->Field("Mouse Sensitivity X", &NoClipControllerProperties::m_mouseSensitivityX) ->Field("Mouse Sensitivity Y", &NoClipControllerProperties::m_mouseSensitivityY) ->Field("Move Speed", &NoClipControllerProperties::m_moveSpeed) ->Field("Panning Speed", &NoClipControllerProperties::m_panningSpeed) ->Field("Touch Sensitivity", &NoClipControllerProperties::m_touchSensitivity); } } NoClipControllerComponent::NoClipControllerComponent() : AzFramework::InputChannelEventListener(AzFramework::InputChannelEventListener::GetPriorityDefault()) {} void NoClipControllerComponent::Reflect(AZ::ReflectContext* reflection) { NoClipControllerProperties::Reflect(reflection); if (auto* serializeContext = azrtti_cast(reflection)) { serializeContext->Class() ->Version(1) ->Field("Properties", &NoClipControllerComponent::m_properties); } } void NoClipControllerComponent::OnEnabled() { // Reset parameters m_mouseLookEnabled = false; m_inputStates = 0; m_currentHeading = 0.0f; m_currentPitch = 0.0f; m_currentFov = DefaultFov; m_lastForward = 0.0f; m_lastStrafe = 0.0f; m_lastAscent = 0.0f; NoClipControllerRequestBus::Handler::BusConnect(GetEntityId()); AzFramework::InputChannelEventListener::Connect(); AZ::TickBus::Handler::BusConnect(); } void NoClipControllerComponent::OnDisabled() { TickBus::Handler::BusDisconnect(); AzFramework::InputChannelEventListener::Disconnect(); NoClipControllerRequestBus::Handler::BusDisconnect(); // Reset the Fov to default. Camera::CameraRequestBus::Event(GetEntityId(), &Camera::CameraRequestBus::Events::SetFovRadians, DefaultFov); } void NoClipControllerComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time) { AZ_UNUSED(time); static const float normalSpeed = 3.0f; static const float sprintSpeed = 10.0f; float speedFactor = m_inputStates[CameraKeys::FastMode] ? sprintSpeed : normalSpeed; float forward = m_properties.m_moveSpeed * speedFactor * ( (m_inputStates[CameraKeys::Forward] ? deltaTime : 0.0f) + (m_inputStates[CameraKeys::Back] ? -deltaTime : 0.0f)); float strafe = m_properties.m_panningSpeed * speedFactor * ( (m_inputStates[CameraKeys::Right] ? deltaTime : 0.0f) + (m_inputStates[CameraKeys::Left] ? -deltaTime : 0.0f)); float ascent = m_properties.m_panningSpeed * speedFactor * ( (m_inputStates[CameraKeys::Up] ? deltaTime : 0.0f) + (m_inputStates[CameraKeys::Down] ? -deltaTime : 0.0f)); ApplyMomentum(m_lastForward, forward, deltaTime); ApplyMomentum(m_lastStrafe, strafe, deltaTime); ApplyMomentum(m_lastAscent, ascent, deltaTime); AZ::Vector3 worldPosition; AZ::TransformBus::EventResult( worldPosition, GetEntityId(), &AZ::TransformBus::Events::GetWorldTranslation); // 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); AZ::Vector3 position = orientation.TransformVector(AZ::Vector3(strafe, forward, ascent)) + worldPosition; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation(orientation, position); AZ::TransformBus::Event( GetEntityId(), &AZ::TransformBus::Events::SetWorldTM, transform); Camera::CameraRequestBus::Event(GetEntityId(), &Camera::CameraRequestBus::Events::SetFovRadians, m_currentFov); } bool NoClipControllerComponent::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) { static const auto KeyCount = static_cast(CameraKeys::Count); static const float PixelToDegree = 1.0 / 360.0f; static const AzFramework::InputChannelId CameraInputMap[KeyCount] = { AzFramework::InputDeviceKeyboard::Key::AlphanumericW, // Forward AzFramework::InputDeviceKeyboard::Key::AlphanumericS, // Back AzFramework::InputDeviceKeyboard::Key::AlphanumericA, // Left AzFramework::InputDeviceKeyboard::Key::AlphanumericD, // Right AzFramework::InputDeviceKeyboard::Key::AlphanumericQ, // Up AzFramework::InputDeviceKeyboard::Key::AlphanumericE, // Down AzFramework::InputDeviceKeyboard::Key::ModifierShiftL, // FastMode }; static const AzFramework::InputChannelId CameraGamepadInputMap[KeyCount] = { AzFramework::InputDeviceGamepad::Button::DU, // Forward AzFramework::InputDeviceGamepad::Button::DD, // Back AzFramework::InputDeviceGamepad::Button::DL, // Left AzFramework::InputDeviceGamepad::Button::DR, // Right AzFramework::InputDeviceGamepad::Button::R1, // Up AzFramework::InputDeviceGamepad::Button::L1, // Down AzFramework::InputDeviceGamepad::Trigger::R2, // FastMode }; uint32_t handledChannels = NoClipControllerChannel_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 (m_mouseLookEnabled && inputChannelId == AzFramework::InputDeviceMouse::Movement::X) { // modify yaw angle m_currentHeading -= inputChannel.GetValue() * m_properties.m_mouseSensitivityX * PixelToDegree; m_currentHeading = NormalizeAngle(m_currentHeading); } else if (m_mouseLookEnabled && inputChannelId == AzFramework::InputDeviceMouse::Movement::Y) { // modify pitch angle m_currentPitch -= inputChannel.GetValue() * m_properties.m_mouseSensitivityY * PixelToDegree; m_currentPitch = AZStd::max(m_currentPitch, -AZ::Constants::HalfPi); m_currentPitch = AZStd::min(m_currentPitch, AZ::Constants::HalfPi); } else if (inputChannelId == AzFramework::InputDeviceMouse::Movement::Z) { // modify field of view m_currentFov = GetClamp(m_currentFov - inputChannel.GetValue() * 0.0005f * m_currentFov, MinFov, MaxFov); handledChannels |= NoClipControllerChannel_Fov; } else if (inputChannelId == AzFramework::InputDeviceMouse::Button::Right) { m_mouseLookEnabled = true; handledChannels |= NoClipControllerChannel_Orientation; } else { for (uint32_t i = 0; i < KeyCount; ++i) { if (inputChannelId == CameraInputMap[i]) { m_inputStates[i] = true; if (i != CameraKeys::FastMode) { handledChannels |= NoClipControllerChannel_Position; } break; } } } // Gamepad if (inputChannelId == AzFramework::InputDeviceGamepad::Trigger::L2) { m_mouseLookEnabled = true; handledChannels |= NoClipControllerChannel_Orientation; } else if (m_mouseLookEnabled) { if (inputChannelId == AzFramework::InputDeviceGamepad::ThumbStickAxis1D::RX) { // modify yaw angle m_currentHeading -= inputChannel.GetValue() * m_properties.m_mouseSensitivityX * PixelToDegree; m_currentHeading = NormalizeAngle(m_currentHeading); } else if (inputChannelId == AzFramework::InputDeviceGamepad::ThumbStickAxis1D::RY) { // modify pitch angle m_currentPitch += inputChannel.GetValue() * m_properties.m_mouseSensitivityY * PixelToDegree; m_currentPitch = AZStd::max(m_currentPitch, -AZ::Constants::HalfPi); m_currentPitch = AZStd::min(m_currentPitch, AZ::Constants::HalfPi); } for (uint32_t i = 0; i < KeyCount; ++i) { if (inputChannelId == CameraGamepadInputMap[i]) { m_inputStates[i] = true; if (i != CameraKeys::FastMode) { handledChannels |= NoClipControllerChannel_Position; } break; } } } // Touch controls works like two virtual joysticks. // The left "joystick" controls forward/backward/left and right movements. // The right "joystick" controls the camera heading and pitch. // There's no control to move up and down. if (inputChannelId == AzFramework::InputDeviceTouch::Touch::Index0 || inputChannelId == AzFramework::InputDeviceTouch::Touch::Index1) { auto* positionData = inputChannel.GetCustomData(); AZ::Vector2 screenPos = positionData->m_normalizedPosition; const float deadZone = 0.07f; if (inputChannelId == m_mouseLookTouch.m_channelId) { // modify yaw angle AZ::Vector2 deltaPos = screenPos - m_mouseLookTouch.m_initialPos; AZ::Vector2 inputValue = AZ::Vector2(fabsf(deltaPos.GetX()) > deadZone ? 1.0f : 0, fabsf(deltaPos.GetY()) > deadZone ? 1.0f : 0); m_currentHeading -= inputValue.GetX() * AZ::GetSign(deltaPos.GetX()) * m_properties.m_touchSensitivity * m_properties.m_mouseSensitivityX * PixelToDegree; m_currentHeading = NormalizeAngle(m_currentHeading); // modify pitch angle m_currentPitch -= inputValue.GetY() * AZ::GetSign(deltaPos.GetY()) * m_properties.m_touchSensitivity * m_properties.m_mouseSensitivityY * PixelToDegree; m_currentPitch = AZStd::max(m_currentPitch, -AZ::Constants::HalfPi); m_currentPitch = AZStd::min(m_currentPitch, AZ::Constants::HalfPi); handledChannels |= NoClipControllerChannel_Orientation; } else if (inputChannelId == m_movementTouch.m_channelId) { AZ::Vector2 deltaPos = screenPos - m_movementTouch.m_initialPos; m_inputStates[Forward] = deltaPos.GetY() < -deadZone ? true : false; m_inputStates[Back] = deltaPos.GetY() > deadZone ? true : false; m_inputStates[Left] = deltaPos.GetX() < -deadZone ? true : false; m_inputStates[Right] = deltaPos.GetX() > deadZone ? true : false; handledChannels |= NoClipControllerChannel_Position; } else { bool isMouseLook = (screenPos.GetX() > 0.5); auto& touchEvent = isMouseLook ? m_mouseLookTouch : m_movementTouch; if (touchEvent.m_channelId == TouchEvent::InvalidTouchChannelId) { touchEvent.m_channelId = inputChannelId; touchEvent.m_initialPos = screenPos; } if (isMouseLook) { handledChannels |= NoClipControllerChannel_Orientation; } else { handledChannels |= NoClipControllerChannel_Position; } } } 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 { if (inputChannelId == AzFramework::InputDeviceMouse::Button::Right) { m_mouseLookEnabled = false; handledChannels |= NoClipControllerChannel_Orientation; } else if (inputChannelId == AzFramework::InputDeviceMouse::Movement::Z) { handledChannels |= NoClipControllerChannel_Fov; } else if (inputChannelId == AzFramework::InputDeviceGamepad::Trigger::L2) { m_mouseLookEnabled = false; handledChannels |= NoClipControllerChannel_Orientation; // On gamepads, Trigger::L2 also indicates positional movement, see above. handledChannels |= NoClipControllerChannel_Position; } else if (inputChannelId == m_movementTouch.m_channelId) { m_movementTouch.m_channelId = TouchEvent::InvalidTouchChannelId; for (uint32_t i = 0; i < KeyCount; ++i) { m_inputStates[i] = false; if (i != CameraKeys::FastMode) { handledChannels |= NoClipControllerChannel_Position; } } } else if (inputChannelId == m_mouseLookTouch.m_channelId) { m_mouseLookTouch.m_channelId = TouchEvent::InvalidTouchChannelId; handledChannels |= NoClipControllerChannel_Orientation; } else { for (uint32_t i = 0; i < KeyCount; ++i) { if (inputChannelId == CameraInputMap[i] || inputChannelId == CameraGamepadInputMap[i]) { m_inputStates[i] = false; if (i != CameraKeys::FastMode) { handledChannels |= NoClipControllerChannel_Position; } break; } } } if (handledChannels) { CameraControllerNotificationBus::Broadcast(&CameraControllerNotifications::OnCameraMoveEnded, RTTI_GetType(), handledChannels); } break; } default: { break; } } return false; } void NoClipControllerComponent::SetMouseSensitivityX(float mouseSensitivityX) { m_properties.m_mouseSensitivityX = mouseSensitivityX; } void NoClipControllerComponent::SetMouseSensitivityY(float mouseSensitivityY) { m_properties.m_mouseSensitivityY = mouseSensitivityY; } void NoClipControllerComponent::SetMoveSpeed(float moveSpeed) { m_properties.m_moveSpeed = moveSpeed; } void NoClipControllerComponent::SetPanningSpeed(float panningSpeed) { m_properties.m_panningSpeed = panningSpeed; } void NoClipControllerComponent::SetControllerProperties(const NoClipControllerProperties& properties) { m_properties = properties; } void NoClipControllerComponent::SetTouchSensitivity([[maybe_unused]] float touchSensitivity) { m_properties.m_touchSensitivity; } void NoClipControllerComponent::SetPosition(AZ::Vector3 position) { AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetWorldTranslation, position); } void NoClipControllerComponent::SetHeading(float heading) { m_currentHeading = heading; m_currentHeading = NormalizeAngle(m_currentHeading); } void NoClipControllerComponent::SetPitch(float pitch) { m_currentPitch = pitch; m_currentPitch = AZStd::max(m_currentPitch, -AZ::Constants::HalfPi); m_currentPitch = AZStd::min(m_currentPitch, AZ::Constants::HalfPi); } void NoClipControllerComponent::SetFov(float fov) { m_currentFov = GetClamp(fov, MinFov, MaxFov); } float NoClipControllerComponent::GetMouseSensitivityX() { return m_properties.m_mouseSensitivityX; } float NoClipControllerComponent::GetMouseSensitivityY() { return m_properties.m_mouseSensitivityY; } float NoClipControllerComponent::GetMoveSpeed() { return m_properties.m_moveSpeed; } float NoClipControllerComponent::GetPanningSpeed() { return m_properties.m_panningSpeed; } float NoClipControllerComponent::GetTouchSensitivity() { return m_properties.m_touchSensitivity; } NoClipControllerProperties NoClipControllerComponent::GetControllerProperties() { return m_properties; } AZ::Vector3 NoClipControllerComponent::GetPosition() { AZ::Vector3 position; AZ::TransformBus::EventResult(position, GetEntityId(), &AZ::TransformBus::Events::GetWorldTranslation); return position; } float NoClipControllerComponent::GetHeading() { return m_currentHeading; } float NoClipControllerComponent::GetPitch() { return m_currentPitch; } float NoClipControllerComponent::GetFov() { return m_currentFov; } } // namespace Debug } // namespace AZ