// @lint-ignore-every LICENSELINT // Copyright Epic Games, Inc. All Rights Reserved. #include "OculusXRInput.h" #include "OculusXRInputOpenXR.h" #include "OculusXRInputOVR.h" #if OCULUS_INPUT_SUPPORTED_PLATFORMS #include "OculusXRHMD.h" #include "OculusXRHandTracking.h" #include "OculusXRMRFunctionLibrary.h" #include "Misc/CoreDelegates.h" #include "Features/IModularFeatures.h" #include "Misc/ConfigCacheIni.h" #include "Haptics/HapticFeedbackEffect_Base.h" #include "GenericPlatform/GenericPlatformInputDeviceMapper.h" #define OVR_DEBUG_LOGGING 0 #define LOCTEXT_NAMESPACE "OculusXRInput" static TAutoConsoleVariable<int32> CVarOculusPCMBatchDuration( TEXT("r.Mobile.Oculus.PCMBatchDuration"), 36, TEXT("The duration that each PCM haptic batch lasts in ms. Default is 36ms.\n"), ECVF_Scalability | ECVF_RenderThreadSafe); static TAutoConsoleVariable<int32> CVarOculusControllerPose( TEXT("r.Oculus.ControllerPose"), 0, TEXT("0 Default controller pose.\n") TEXT("1 Legacy controller pose.\n") TEXT("2 Grip controller pose.\n") TEXT("3 Aim controller pose.\n"), ECVF_Scalability | ECVF_RenderThreadSafe); static TAutoConsoleVariable<int32> CVarOculusResetUntrackedInputStates( TEXT("r.Mobile.Oculus.ResetUntrackedInputStates"), 0, TEXT("If true, reset input states of input devices if they are untracked (for example, controllers or hands after Oculus button is held to pause the app).\n"), ECVF_Scalability | ECVF_RenderThreadSafe); namespace OculusXRInput { const FKey FOculusKey::OculusTouch_Left_Thumbstick("OculusTouch_Left_Thumbstick"); const FKey FOculusKey::OculusTouch_Left_Trigger("OculusTouch_Left_Trigger"); const FKey FOculusKey::OculusTouch_Left_FaceButton1("OculusTouch_Left_FaceButton1"); const FKey FOculusKey::OculusTouch_Left_FaceButton2("OculusTouch_Left_FaceButton2"); const FKey FOculusKey::OculusTouch_Left_IndexPointing("OculusTouch_Left_IndexPointing"); const FKey FOculusKey::OculusTouch_Left_Trigger_Proximity("OculusTouch_Left_Trigger_Proximity"); const FKey FOculusKey::OculusTouch_Left_ThumbUp("OculusTouch_Left_ThumbUp"); const FKey FOculusKey::OculusTouch_Left_Thumb_Proximity("OculusTouch_Left_Thumb_Proximity"); const FKey FOculusKey::OculusTouch_Left_ThumbRest("OculusTouch_Left_ThumbRest"); const FKey FOculusKey::OculusTouch_Left_ThumbRest_Force("OculusTouch_Left_ThumbRest_Force"); const FKey FOculusKey::OculusTouch_Left_Stylus_Force("OculusTouch_Left_Stylus_Force"); const FKey FOculusKey::OculusTouch_Left_IndexTrigger_Curl("OculusTouch_Left_IndexTrigger_Curl"); const FKey FOculusKey::OculusTouch_Left_IndexTrigger_Slide("OculusTouch_Left_IndexTrigger_Slide"); const FKey FOculusKey::OculusTouch_Left_IndexTrigger_Force("OculusTouch_Left_IndexTrigger_Force"); const FKey FOculusKey::OculusTouch_Right_Thumbstick("OculusTouch_Right_Thumbstick"); const FKey FOculusKey::OculusTouch_Right_Trigger("OculusTouch_Right_Trigger"); const FKey FOculusKey::OculusTouch_Right_FaceButton1("OculusTouch_Right_FaceButton1"); const FKey FOculusKey::OculusTouch_Right_FaceButton2("OculusTouch_Right_FaceButton2"); const FKey FOculusKey::OculusTouch_Right_IndexPointing("OculusTouch_Right_IndexPointing"); const FKey FOculusKey::OculusTouch_Right_Trigger_Proximity("OculusTouch_Right_Trigger_Proximity"); const FKey FOculusKey::OculusTouch_Right_ThumbUp("OculusTouch_Right_ThumbUp"); const FKey FOculusKey::OculusTouch_Right_Thumb_Proximity("OculusTouch_Right_Thumb_Proximity"); const FKey FOculusKey::OculusTouch_Right_ThumbRest("OculusTouch_Right_ThumbRest"); const FKey FOculusKey::OculusTouch_Right_ThumbRest_Force("OculusTouch_Right_ThumbRest_Force"); const FKey FOculusKey::OculusTouch_Right_Stylus_Force("OculusTouch_Right_Stylus_Force"); const FKey FOculusKey::OculusTouch_Right_IndexTrigger_Curl("OculusTouch_Right_IndexTrigger_Curl"); const FKey FOculusKey::OculusTouch_Right_IndexTrigger_Slide("OculusTouch_Right_IndexTrigger_Slide"); const FKey FOculusKey::OculusTouch_Right_IndexTrigger_Force("OculusTouch_Right_IndexTrigger_Force"); const FKey FOculusKey::OculusRemote_DPad_Down("OculusRemote_DPad_Down"); const FKey FOculusKey::OculusRemote_DPad_Up("OculusRemote_DPad_Up"); const FKey FOculusKey::OculusRemote_DPad_Left("OculusRemote_DPad_Left"); const FKey FOculusKey::OculusRemote_DPad_Right("OculusRemote_DPad_Right"); const FKey FOculusKey::OculusRemote_Enter("OculusRemote_Enter"); const FKey FOculusKey::OculusRemote_Back("OculusRemote_Back"); const FKey FOculusKey::OculusRemote_VolumeUp("OculusRemote_VolumeUp"); const FKey FOculusKey::OculusRemote_VolumeDown("OculusRemote_VolumeDown"); const FKey FOculusKey::OculusRemote_Home("OculusRemote_Home"); const FKey FOculusKey::OculusHand_Left_ThumbPinch("OculusHand_Left_ThumbPinch"); const FKey FOculusKey::OculusHand_Left_IndexPinch("OculusHand_Left_IndexPinch"); const FKey FOculusKey::OculusHand_Left_MiddlePinch("OculusHand_Left_MiddlePinch"); const FKey FOculusKey::OculusHand_Left_RingPinch("OculusHand_Left_RingPinch"); const FKey FOculusKey::OculusHand_Left_PinkyPinch("OculusHand_Left_PinkPinch"); const FKey FOculusKey::OculusHand_Right_ThumbPinch("OculusHand_Right_ThumbPinch"); const FKey FOculusKey::OculusHand_Right_IndexPinch("OculusHand_Right_IndexPinch"); const FKey FOculusKey::OculusHand_Right_MiddlePinch("OculusHand_Right_MiddlePinch"); const FKey FOculusKey::OculusHand_Right_RingPinch("OculusHand_Right_RingPinch"); const FKey FOculusKey::OculusHand_Right_PinkyPinch("OculusHand_Right_PinkPinch"); const FKey FOculusKey::OculusHand_Left_SystemGesture("OculusHand_Left_SystemGesture"); const FKey FOculusKey::OculusHand_Right_SystemGesture("OculusHand_Right_SystemGesture"); const FKey FOculusKey::OculusHand_Left_ThumbPinchStrength("OculusHand_Left_ThumbPinchStrength"); const FKey FOculusKey::OculusHand_Left_IndexPinchStrength("OculusHand_Left_IndexPinchStrength"); const FKey FOculusKey::OculusHand_Left_MiddlePinchStrength("OculusHand_Left_MiddlePinchStrength"); const FKey FOculusKey::OculusHand_Left_RingPinchStrength("OculusHand_Left_RingPinchStrength"); const FKey FOculusKey::OculusHand_Left_PinkyPinchStrength("OculusHand_Left_PinkPinchStrength"); const FKey FOculusKey::OculusHand_Right_ThumbPinchStrength("OculusHand_Right_ThumbPinchStrength"); const FKey FOculusKey::OculusHand_Right_IndexPinchStrength("OculusHand_Right_IndexPinchStrength"); const FKey FOculusKey::OculusHand_Right_MiddlePinchStrength("OculusHand_Right_MiddlePinchStrength"); const FKey FOculusKey::OculusHand_Right_RingPinchStrength("OculusHand_Right_RingPinchStrength"); const FKey FOculusKey::OculusHand_Right_PinkyPinchStrength("OculusHand_Right_PinkPinchStrength"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_Thumbstick("OculusTouch_Left_Thumbstick"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_Trigger("OculusTouch_Left_Trigger"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_FaceButton1("OculusTouch_Left_FaceButton1"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_FaceButton2("OculusTouch_Left_FaceButton2"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_IndexPointing("OculusTouch_Left_IndexPointing"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_Trigger_Proximity("OculusTouch_Left_Trigger_Proximity"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_ThumbUp("OculusTouch_Left_ThumbUp"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_Thumb_Proximity("OculusTouch_Left_Thumb_Proximity"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_ThumbRest("OculusTouch_Left_ThumbRest"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_Thumbstick("OculusTouch_Right_Thumbstick"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_Trigger("OculusTouch_Right_Trigger"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_FaceButton1("OculusTouch_Right_FaceButton1"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_FaceButton2("OculusTouch_Right_FaceButton2"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_IndexPointing("OculusTouch_Right_IndexPointing"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_Trigger_Proximity("OculusTouch_Right_Trigger_Proximity"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_ThumbUp("OculusTouch_Right_ThumbUp"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_Thumb_Proximity("OculusTouch_Right_Thumb_Proximity"); const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_ThumbRest("OculusTouch_Right_ThumbRest"); const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_DPad_Down("OculusRemote_DPad_Down"); const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_DPad_Up("OculusRemote_DPad_Up"); const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_DPad_Left("OculusRemote_DPad_Left"); const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_DPad_Right("OculusRemote_DPad_Right"); const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_Enter("OculusRemote_Enter"); const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_Back("OculusRemote_Back"); const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_VolumeUp("OculusRemote_VolumeUp"); const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_VolumeDown("OculusRemote_VolumeDown"); const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_Home("OculusRemote_Home"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_ThumbPinch("OculusHand_Left_ThumbPinch"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_IndexPinch("OculusHand_Left_IndexPinch"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_MiddlePinch("OculusHand_Left_MiddlePinch"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_RingPinch("OculusHand_Left_RingPinch"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_PinkyPinch("OculusHand_Left_PinkPinch"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_ThumbPinch("OculusHand_Right_ThumbPinch"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_IndexPinch("OculusHand_Right_IndexPinch"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_MiddlePinch("OculusHand_Right_MiddlePinch"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_RingPinch("OculusHand_Right_RingPinch"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_PinkyPinch("OculusHand_Right_PinkPinch"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_SystemGesture("OculusHand_Left_SystemGesture"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_SystemGesture("OculusHand_Right_SystemGesture"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_ThumbPinchStrength("OculusHand_Left_ThumbPinchStrength"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_IndexPinchStrength("OculusHand_Left_IndexPinchStrength"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_MiddlePinchStrength("OculusHand_Left_MiddlePinchStrength"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_RingPinchStrength("OculusHand_Left_RingPinchStrength"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_PinkyPinchStrength("OculusHand_Left_PinkPinchStrength"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_ThumbPinchStrength("OculusHand_Right_ThumbPinchStrength"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_IndexPinchStrength("OculusHand_Right_IndexPinchStrength"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_MiddlePinchStrength("OculusHand_Right_MiddlePinchStrength"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_RingPinchStrength("OculusHand_Right_RingPinchStrength"); const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_PinkyPinchStrength("OculusHand_Right_PinkPinchStrength"); /** Threshold for treating trigger pulls as button presses, from 0.0 to 1.0 */ float FOculusXRInput::TriggerThreshold = 0.8f; /** Are Remote keys mapped to gamepad or not. */ bool FOculusXRInput::bRemoteKeysMappedToGamepad = true; float FOculusXRInput::InitialButtonRepeatDelay = DefaultInitialButtonRepeatDelay; float FOculusXRInput::ButtonRepeatDelay = DefaultButtonRepeatDelay; bool FOculusXRInput::bPulledHapticsDesc = false; FOculusXRInput::FOculusXRInput(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) : MessageHandler(InMessageHandler) , ControllerPairs() { // take care of backward compatibility of Remote with Gamepad if (bRemoteKeysMappedToGamepad) { Remote.MapKeysToGamepad(); } FOculusControllerPair& ControllerPair = *new (ControllerPairs) FOculusControllerPair(); // TODO: Map the oculus controllers uniquely instead of using the default ControllerPair.DeviceId = IPlatformInputDeviceMapper::Get().GetDefaultInputDevice(); IModularFeatures::Get().RegisterModularFeature(GetModularFeatureName(), this); LocalTrackingSpaceRecenterCount = 0; UE_LOG(LogOcInput, Log, TEXT("OculusXRInput is initialized")); } FOculusXRInput::~FOculusXRInput() { IModularFeatures::Get().UnregisterModularFeature(GetModularFeatureName(), this); } void FOculusXRInput::PreInit() { // Load the config, even if we failed to initialize a controller LoadConfig(); // Register the FKeys EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_Thumbstick, LOCTEXT("OculusTouch_Left_Thumbstick", "Oculus Touch (L) Thumbstick CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D)); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_FaceButton1, LOCTEXT("OculusTouch_Left_FaceButton1", "Oculus Touch (L) X Button CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D)); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_Trigger, LOCTEXT("OculusTouch_Left_Trigger", "Oculus Touch (L) Trigger CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D)); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_FaceButton2, LOCTEXT("OculusTouch_Left_FaceButton2", "Oculus Touch (L) Y Button CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D)); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_IndexPointing, LOCTEXT("OculusTouch_Left_IndexPointing", "Oculus Touch (L) Pointing CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_Trigger_Proximity, LOCTEXT("OculusTouch_Left_Trigger_Proximity", "Oculus Touch (L) Trigger Proximity CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_ThumbUp, LOCTEXT("OculusTouch_Left_ThumbUp", "Oculus Touch (L) Thumb Up CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_Thumb_Proximity, LOCTEXT("OculusTouch_Left_Thumb_Proximity", "Oculus Touch (L) Thumbstick Proximity CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_ThumbRest, LOCTEXT("OculusTouch_Left_ThumbRest", "Oculus Touch (L) Thumb Rest CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_ThumbRest_Force, LOCTEXT("OculusTouch_Left_ThumbRest_Force", "Oculus Touch (L) Thumb Rest Force"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_Stylus_Force, LOCTEXT("OculusTouch_Left_Stylus_Force", "Oculus Touch (L) Stylus Force"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_IndexTrigger_Curl, LOCTEXT("OculusTouch_Left_IndexTrigger_Curl", "Oculus Touch (L) Trigger Curl CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_IndexTrigger_Slide, LOCTEXT("OculusTouch_Left_IndexTrigger_Slide", "Oculus Touch (L) Trigger Slide CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_IndexTrigger_Force, LOCTEXT("OculusTouch_Left_IndexTrigger_Force", "Oculus Touch (L) Trigger Force"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_Thumbstick, LOCTEXT("OculusTouch_Right_Thumbstick", "Oculus Touch (R) Thumbstick CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey)); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_FaceButton1, LOCTEXT("OculusTouch_Right_FaceButton1", "Oculus Touch (R) A Button CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey)); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_Trigger, LOCTEXT("OculusTouch_Right_Trigger", "Oculus Touch (R) Trigger CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey)); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_FaceButton2, LOCTEXT("OculusTouch_Right_FaceButton2", "Oculus Touch (R) B Button CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey)); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_IndexPointing, LOCTEXT("OculusTouch_Right_IndexPointing", "Oculus Touch (R) Pointing CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_Trigger_Proximity, LOCTEXT("OculusTouch_Right_Trigger_Proximity", "Oculus Touch (R) Trigger Proximity CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_ThumbUp, LOCTEXT("OculusTouch_Right_ThumbUp", "Oculus Touch (R) Thumb Up CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_Thumb_Proximity, LOCTEXT("OculusTouch_Right_Thumb_Proximity", "Oculus Touch (R) Thumbstick Proximity CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_ThumbRest, LOCTEXT("OculusTouch_Right_ThumbRest", "Oculus Touch (R) Thumb Rest CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_ThumbRest_Force, LOCTEXT("OculusTouch_Right_ThumbRest_Force", "Oculus Touch (R) Thumb Rest Force"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_Stylus_Force, LOCTEXT("OculusTouch_Right_Stylus_Force", "Oculus Touch (R) Stylus Force"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_IndexTrigger_Curl, LOCTEXT("OculusTouch_Right_IndexTrigger_Curl", "Oculus Touch (R) Trigger Curl CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_IndexTrigger_Slide, LOCTEXT("OculusTouch_Right_IndexTrigger_Slide", "Oculus Touch (R) Trigger Slide CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_IndexTrigger_Force, LOCTEXT("OculusTouch_Right_IndexTrigger_Force", "Oculus Touch (R) Trigger Force"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); EKeys::AddMenuCategoryDisplayInfo("OculusRemote", LOCTEXT("OculusRemoteSubCategory", "Oculus Remote"), TEXT("GraphEditor.PadEvent_16x")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_DPad_Up, LOCTEXT("OculusRemote_DPad_Up", "Oculus Remote D-pad Up"), FKeyDetails::GamepadKey, "OculusRemote")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_DPad_Down, LOCTEXT("OculusRemote_DPad_Down", "Oculus Remote D-pad Down"), FKeyDetails::GamepadKey, "OculusRemote")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_DPad_Left, LOCTEXT("OculusRemote_DPad_Left", "Oculus Remote D-pad Left"), FKeyDetails::GamepadKey, "OculusRemote")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_DPad_Right, LOCTEXT("OculusRemote_DPad_Right", "Oculus Remote D-pad Right"), FKeyDetails::GamepadKey, "OculusRemote")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_Enter, LOCTEXT("OculusRemote_Enter", "Oculus Remote Enter"), FKeyDetails::GamepadKey, "OculusRemote")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_Back, LOCTEXT("OculusRemote_Back", "Oculus Remote Back"), FKeyDetails::GamepadKey, "OculusRemote")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_VolumeUp, LOCTEXT("OculusRemote_VolumeUp", "Oculus Remote Volume Up"), FKeyDetails::GamepadKey, "OculusRemote")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_VolumeDown, LOCTEXT("OculusRemote_VolumeDown", "Oculus Remote Volume Down"), FKeyDetails::GamepadKey, "OculusRemote")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_Home, LOCTEXT("OculusRemote_Home", "Oculus Remote Home"), FKeyDetails::GamepadKey, "OculusRemote")); EKeys::AddMenuCategoryDisplayInfo("OculusHand", LOCTEXT("OculusHandSubCategory", "Oculus Hand"), TEXT("GraphEditor.PadEvent_16x")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_ThumbPinch, LOCTEXT("OculusHand_Left_ThumbPinch", "Oculus Hand (L) Thumb Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_IndexPinch, LOCTEXT("OculusHand_Left_IndexPinch", "Oculus Hand (L) Index Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_MiddlePinch, LOCTEXT("OculusHand_Left_MiddlePinch", "Oculus Hand (L) Middle Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_RingPinch, LOCTEXT("OculusHand_Left_RingPinch", "Oculus Hand (L) Ring Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_PinkyPinch, LOCTEXT("OculusHand_Left_PinkyPinch", "Oculus Hand (L) Pinky Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_ThumbPinch, LOCTEXT("OculusHand_Right_ThumbPinch", "Oculus Hand (R) Thumb Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_IndexPinch, LOCTEXT("OculusHand_Right_IndexPinch", "Oculus Hand (R) Index Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_MiddlePinch, LOCTEXT("OculusHand_Right_MiddlePinch", "Oculus Hand (R) Middle Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_RingPinch, LOCTEXT("OculusHand_Right_RingPinch", "Oculus Hand (R) Ring Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_PinkyPinch, LOCTEXT("OculusHand_Right_PinkyPinch", "Oculus Hand (R) Pinky Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_SystemGesture, LOCTEXT("OculusHand_Left_SystemGesture", "Oculus Hand (L) System Gesture"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_SystemGesture, LOCTEXT("OculusHand_Right_SystemGesture", "Oculus Hand (R) System Gesture"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_ThumbPinchStrength, LOCTEXT("OculusHand_Left_ThumbPinchStrength", "Oculus Hand (L) Thumb Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_IndexPinchStrength, LOCTEXT("OculusHand_Left_IndexPinchStrength", "Oculus Hand (L) Index Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_MiddlePinchStrength, LOCTEXT("OculusHand_Left_MiddlePinchStrength", "Oculus Hand (L) Middle Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_RingPinchStrength, LOCTEXT("OculusHand_Left_RingPinchStrength", "Oculus Hand (L) Ring Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_PinkyPinchStrength, LOCTEXT("OculusHand_Left_PinkyPinchStrength", "Oculus Hand (L) Pinky Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_ThumbPinchStrength, LOCTEXT("OculusHand_Right_ThumbPinchStrength", "Oculus Hand (R) Thumb Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_IndexPinchStrength, LOCTEXT("OculusHand_Right_IndexPinchStrength", "Oculus Hand (R) Index Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_MiddlePinchStrength, LOCTEXT("OculusHand_Right_MiddlePinchStrength", "Oculus Hand (R) Middle Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_RingPinchStrength, LOCTEXT("OculusHand_Right_RingPinchStrength", "Oculus Hand (R) Ring Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_PinkyPinchStrength, LOCTEXT("OculusHand_Right_PinkyPinchStrength", "Oculus Hand (R) Pinky Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); UE_LOG(LogOcInput, Log, TEXT("OculusXRInput pre-init called")); } void FOculusXRInput::LoadConfig() { const TCHAR* OculusTouchSettings = TEXT("OculusTouch.Settings"); float ConfigThreshold = TriggerThreshold; if (GConfig->GetFloat(OculusTouchSettings, TEXT("TriggerThreshold"), ConfigThreshold, GEngineIni)) { TriggerThreshold = ConfigThreshold; } const TCHAR* OculusRemoteSettings = TEXT("OculusRemote.Settings"); bool bConfigRemoteKeysMappedToGamepad; if (GConfig->GetBool(OculusRemoteSettings, TEXT("bRemoteKeysMappedToGamepad"), bConfigRemoteKeysMappedToGamepad, GEngineIni)) { bRemoteKeysMappedToGamepad = bConfigRemoteKeysMappedToGamepad; } GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni); GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni); } void FOculusXRInput::Tick(float DeltaTime) { // Nothing to do when ticking, for now. SendControllerEvents() handles everything. } void FOculusXRInput::SendControllerEvents() { const double CurrentTime = FPlatformTime::Seconds(); const float AnalogButtonPressThreshold = TriggerThreshold; float DeltaTime = 0.0; if (StartTime < CurrentTime) { DeltaTime = (float)(CurrentTime - StartTime); StartTime = CurrentTime; } if (IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && FApp::HasVRFocus()) { if (MessageHandler.IsValid() && GEngine->XRSystem->GetHMDDevice()) { FPlatformUserId PlatUser = IPlatformInputDeviceMapper::Get().GetPrimaryPlatformUser(); FInputDeviceId DeviceId = IPlatformInputDeviceMapper::Get().GetDefaultInputDevice(); OculusXRHMD::FOculusXRHMD* OculusXRHMD = static_cast<OculusXRHMD::FOculusXRHMD*>(GEngine->XRSystem->GetHMDDevice()); OculusXRHMD->StartGameFrame_GameThread(); ovrpControllerState6 OvrpControllerState; if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetControllerState6(ovrpController_Remote, &OvrpControllerState)) && (OvrpControllerState.ConnectedControllerTypes & ovrpController_Remote)) { for (int32 ButtonIndex = 0; ButtonIndex < (int32)EOculusRemoteControllerButton::TotalButtonCount; ++ButtonIndex) { FOculusButtonState& ButtonState = Remote.Buttons[ButtonIndex]; check(!ButtonState.Key.IsNone()); // is button's name initialized? // Determine if the button is pressed down bool bButtonPressed = false; switch ((EOculusRemoteControllerButton)ButtonIndex) { case EOculusRemoteControllerButton::DPad_Up: bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Up) != 0; break; case EOculusRemoteControllerButton::DPad_Down: bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Down) != 0; break; case EOculusRemoteControllerButton::DPad_Left: bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Left) != 0; break; case EOculusRemoteControllerButton::DPad_Right: bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Right) != 0; break; case EOculusRemoteControllerButton::Enter: bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Start) != 0; break; case EOculusRemoteControllerButton::Back: bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Back) != 0; break; case EOculusRemoteControllerButton::VolumeUp: #ifdef SUPPORT_INTERNAL_BUTTONS bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_VolUp) != 0; #endif break; case EOculusRemoteControllerButton::VolumeDown: #ifdef SUPPORT_INTERNAL_BUTTONS bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_VolDown) != 0; #endif break; case EOculusRemoteControllerButton::Home: #ifdef SUPPORT_INTERNAL_BUTTONS bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Home) != 0; #endif break; default: check(0); // unhandled button, shouldn't happen break; } // Update button state if (bButtonPressed != ButtonState.bIsPressed) { ButtonState.bIsPressed = bButtonPressed; if (ButtonState.bIsPressed) { OnControllerButtonPressed(ButtonState, PlatUser, DeviceId, false); // Set the timer for the first repeat ButtonState.NextRepeatTime = CurrentTime + InitialButtonRepeatDelay; } else { OnControllerButtonReleased(ButtonState, PlatUser, DeviceId, false); } } // Apply key repeat, if its time for that if (ButtonState.bIsPressed && ButtonState.NextRepeatTime <= CurrentTime) { OnControllerButtonPressed(ButtonState, PlatUser, DeviceId, true); // Set the timer for the next repeat ButtonState.NextRepeatTime = CurrentTime + ButtonRepeatDelay; } } } if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetControllerState6((ovrpController)(ovrpController_LTrackedRemote | ovrpController_RTrackedRemote | ovrpController_Touch), &OvrpControllerState))) { UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: ButtonState = 0x%X"), OvrpControllerState.Buttons); UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: Touches = 0x%X"), OvrpControllerState.Touches); // If using touch controllers (Quest) use the local tracking space recentering as a signal for recenter if ((OvrpControllerState.ConnectedControllerTypes & ovrpController_LTouch) != 0 || (OvrpControllerState.ConnectedControllerTypes & ovrpController_RTouch) != 0) { int32 recenterCount = 0; if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetLocalTrackingSpaceRecenterCount(&recenterCount))) { if (LocalTrackingSpaceRecenterCount != recenterCount) { FCoreDelegates::VRControllerRecentered.Broadcast(); LocalTrackingSpaceRecenterCount = recenterCount; } } } for (FOculusControllerPair& ControllerPair : ControllerPairs) { FPlatformUserId PlatformUser = IPlatformInputDeviceMapper::Get().GetUserForInputDevice(ControllerPair.DeviceId); for (int32 HandIndex = 0; HandIndex < UE_ARRAY_COUNT(ControllerPair.TouchControllerStates); ++HandIndex) { FOculusTouchControllerState& State = ControllerPair.TouchControllerStates[HandIndex]; bool bIsLeft = (HandIndex == (int32)EControllerHand::Left); bool bIsMobileController = bIsLeft ? (OvrpControllerState.ConnectedControllerTypes & ovrpController_LTrackedRemote) != 0 : (OvrpControllerState.ConnectedControllerTypes & ovrpController_RTrackedRemote) != 0; bool bIsTouchController = bIsLeft ? (OvrpControllerState.ConnectedControllerTypes & ovrpController_LTouch) != 0 : (OvrpControllerState.ConnectedControllerTypes & ovrpController_RTouch) != 0; bool bIsCurrentlyTracked = bIsMobileController || bIsTouchController; if (bIsCurrentlyTracked) { ovrpNode OvrpNode = (HandIndex == (int32)EControllerHand::Left) ? ovrpNode_HandLeft : ovrpNode_HandRight; State.bIsConnected = true; ovrpBool bResult = true; State.bIsPositionTracked = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePositionTracked2(OvrpNode, &bResult)) && bResult; State.bIsPositionValid = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePositionValid(OvrpNode, &bResult)) && bResult; State.bIsOrientationTracked = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodeOrientationTracked2(OvrpNode, &bResult)) && bResult; State.bIsOrientationValid = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodeOrientationValid(OvrpNode, &bResult)) && bResult; const float OvrTriggerAxis = OvrpControllerState.IndexTrigger[HandIndex]; const float OvrGripAxis = OvrpControllerState.HandTrigger[HandIndex]; const float OvrThumbRestForce = OvrpControllerState.ThumbRestForce[HandIndex]; const float OvrStylusForce = OvrpControllerState.StylusForce[HandIndex]; const float OvrIndexTriggerCurl = OvrpControllerState.IndexTriggerCurl[HandIndex]; const float OvrIndexTriggerSlide = OvrpControllerState.IndexTriggerSlide[HandIndex]; const float OvrIndexTriggerForce = OvrpControllerState.IndexTriggerForce[HandIndex]; UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: IndexTrigger[%d] = %f"), int(HandIndex), OvrTriggerAxis); UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: HandTrigger[%d] = %f"), int(HandIndex), OvrGripAxis); UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: ThumbStick[%d] = { %f, %f }"), int(HandIndex), OvrpControllerState.Thumbstick[HandIndex].x, OvrpControllerState.Thumbstick[HandIndex].y); UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: ThumbRestForce[%d] = %f"), int(HandIndex), OvrThumbRestForce); UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: StylusForce[%d] = %f"), int(HandIndex), OvrStylusForce); UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: IndexTriggerCurl[%d] = %f"), int(HandIndex), OvrIndexTriggerCurl); UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: IndexTriggerSlide[%d] = %f"), int(HandIndex), OvrIndexTriggerSlide); UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: IndexTriggerForce[%d] = %f"), int(HandIndex), OvrIndexTriggerForce); if (bIsMobileController) { if (OvrpControllerState.RecenterCount[HandIndex] != State.RecenterCount) { State.RecenterCount = OvrpControllerState.RecenterCount[HandIndex]; FCoreDelegates::VRControllerRecentered.Broadcast(); } } if (OvrTriggerAxis != State.TriggerAxis) { State.TriggerAxis = OvrTriggerAxis; MessageHandler->OnControllerAnalog(bIsLeft ? EKeys::OculusTouch_Left_Trigger_Axis.GetFName() : EKeys::OculusTouch_Right_Trigger_Axis.GetFName(), PlatformUser, ControllerPair.DeviceId, State.TriggerAxis); } if (OvrGripAxis != State.GripAxis) { State.GripAxis = OvrGripAxis; MessageHandler->OnControllerAnalog(bIsLeft ? EKeys::OculusTouch_Left_Grip_Axis.GetFName() : EKeys::OculusTouch_Right_Grip_Axis.GetFName(), PlatformUser, ControllerPair.DeviceId, State.GripAxis); } ovrpVector2f ThumbstickValue = OvrpControllerState.Thumbstick[HandIndex]; ovrpVector2f TouchpadValue = OvrpControllerState.Touchpad[HandIndex]; if (ThumbstickValue.x != State.ThumbstickAxes.X) { State.ThumbstickAxes.X = ThumbstickValue.x; MessageHandler->OnControllerAnalog(bIsLeft ? EKeys::OculusTouch_Left_Thumbstick_X.GetFName() : EKeys::OculusTouch_Right_Thumbstick_X.GetFName(), PlatformUser, ControllerPair.DeviceId, State.ThumbstickAxes.X); } if (ThumbstickValue.y != State.ThumbstickAxes.Y) { State.ThumbstickAxes.Y = ThumbstickValue.y; MessageHandler->OnControllerAnalog(bIsLeft ? EKeys::OculusTouch_Left_Thumbstick_Y.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Y.GetFName(), PlatformUser, ControllerPair.DeviceId, State.ThumbstickAxes.Y); } if (TouchpadValue.x != State.TouchpadAxes.X) { State.TouchpadAxes.X = TouchpadValue.x; } if (TouchpadValue.y != State.TouchpadAxes.Y) { State.TouchpadAxes.Y = TouchpadValue.y; } if (OvrThumbRestForce != State.ThumbRestForce) { State.ThumbRestForce = OvrThumbRestForce; MessageHandler->OnControllerAnalog(bIsLeft ? FOculusKey::OculusTouch_Left_ThumbRest_Force.GetFName() : FOculusKey::OculusTouch_Right_ThumbRest_Force.GetFName(), PlatformUser, ControllerPair.DeviceId, State.ThumbRestForce); } if (OvrStylusForce != State.StylusForce) { State.StylusForce = OvrStylusForce; MessageHandler->OnControllerAnalog(bIsLeft ? FOculusKey::OculusTouch_Left_Stylus_Force.GetFName() : FOculusKey::OculusTouch_Right_Stylus_Force.GetFName(), PlatformUser, ControllerPair.DeviceId, State.StylusForce); } if (OvrIndexTriggerCurl != State.IndexTriggerCurl) { State.IndexTriggerCurl = OvrIndexTriggerCurl; MessageHandler->OnControllerAnalog(bIsLeft ? FOculusKey::OculusTouch_Left_IndexTrigger_Curl.GetFName() : FOculusKey::OculusTouch_Right_IndexTrigger_Curl.GetFName(), PlatformUser, ControllerPair.DeviceId, State.IndexTriggerCurl); } if (OvrIndexTriggerSlide != State.IndexTriggerSlide) { State.IndexTriggerSlide = OvrIndexTriggerSlide; MessageHandler->OnControllerAnalog(bIsLeft ? FOculusKey::OculusTouch_Left_IndexTrigger_Slide.GetFName() : FOculusKey::OculusTouch_Right_IndexTrigger_Slide.GetFName(), PlatformUser, ControllerPair.DeviceId, State.IndexTriggerSlide); } if (OvrIndexTriggerForce != State.IndexTriggerForce) { State.IndexTriggerForce = OvrIndexTriggerForce; MessageHandler->OnControllerAnalog(bIsLeft ? FOculusKey::OculusTouch_Left_IndexTrigger_Force.GetFName() : FOculusKey::OculusTouch_Right_IndexTrigger_Force.GetFName(), PlatformUser, ControllerPair.DeviceId, State.IndexTriggerForce); } for (int32 ButtonIndex = 0; ButtonIndex < (int32)EOculusTouchControllerButton::TotalButtonCount; ++ButtonIndex) { FOculusButtonState& ButtonState = State.Buttons[ButtonIndex]; check(!ButtonState.Key.IsNone()); // is button's name initialized? // Determine if the button is pressed down bool bButtonPressed = false; switch ((EOculusTouchControllerButton)ButtonIndex) { case EOculusTouchControllerButton::Trigger: bButtonPressed = State.TriggerAxis >= AnalogButtonPressThreshold; break; case EOculusTouchControllerButton::Grip: bButtonPressed = State.GripAxis >= AnalogButtonPressThreshold; break; case EOculusTouchControllerButton::XA: bButtonPressed = bIsLeft ? (OvrpControllerState.Buttons & ovrpButton_X) != 0 : (OvrpControllerState.Buttons & ovrpButton_A) != 0; break; case EOculusTouchControllerButton::YB: bButtonPressed = bIsLeft ? (OvrpControllerState.Buttons & ovrpButton_Y) != 0 : (OvrpControllerState.Buttons & ovrpButton_B) != 0; break; case EOculusTouchControllerButton::Thumbstick: bButtonPressed = bIsLeft ? (OvrpControllerState.Buttons & ovrpButton_LThumb) != 0 : (OvrpControllerState.Buttons & ovrpButton_RThumb) != 0; break; case EOculusTouchControllerButton::Thumbstick_Up: if (bIsTouchController && State.ThumbstickAxes.Size() > 0.7f || bIsMobileController && State.Buttons[(int)EOculusTouchControllerButton::Thumbstick].bIsPressed && State.ThumbstickAxes.Size() > 0.5f) { float Angle = FMath::Atan2(State.ThumbstickAxes.Y, State.ThumbstickAxes.X); bButtonPressed = Angle >= (1.0f / 8.0f) * PI && Angle <= (7.0f / 8.0f) * PI; } break; case EOculusTouchControllerButton::Thumbstick_Down: if (bIsTouchController && State.ThumbstickAxes.Size() > 0.7f || bIsMobileController && State.Buttons[(int)EOculusTouchControllerButton::Thumbstick].bIsPressed && State.ThumbstickAxes.Size() > 0.5f) { float Angle = FMath::Atan2(State.ThumbstickAxes.Y, State.ThumbstickAxes.X); bButtonPressed = Angle >= (-7.0f / 8.0f) * PI && Angle <= (-1.0f / 8.0f) * PI; } break; case EOculusTouchControllerButton::Thumbstick_Left: if (bIsTouchController && State.ThumbstickAxes.Size() > 0.7f || bIsMobileController && State.Buttons[(int)EOculusTouchControllerButton::Thumbstick].bIsPressed && State.ThumbstickAxes.Size() > 0.5f) { float Angle = FMath::Atan2(State.ThumbstickAxes.Y, State.ThumbstickAxes.X); bButtonPressed = Angle <= (-5.0f / 8.0f) * PI || Angle >= (5.0f / 8.0f) * PI; } break; case EOculusTouchControllerButton::Thumbstick_Right: if (bIsTouchController && State.ThumbstickAxes.Size() > 0.7f || bIsMobileController && State.Buttons[(int)EOculusTouchControllerButton::Thumbstick].bIsPressed && State.ThumbstickAxes.Size() > 0.5f) { float Angle = FMath::Atan2(State.ThumbstickAxes.Y, State.ThumbstickAxes.X); bButtonPressed = Angle >= (-3.0f / 8.0f) * PI && Angle <= (3.0f / 8.0f) * PI; } break; case EOculusTouchControllerButton::Menu: bButtonPressed = bIsLeft && (OvrpControllerState.Buttons & ovrpButton_Start); break; case EOculusTouchControllerButton::Thumbstick_Touch: bButtonPressed = bIsLeft ? (OvrpControllerState.Touches & ovrpTouch_LThumb) != 0 : (OvrpControllerState.Touches & ovrpTouch_RThumb) != 0; break; case EOculusTouchControllerButton::Trigger_Touch: bButtonPressed = bIsLeft ? (OvrpControllerState.Touches & ovrpTouch_LIndexTrigger) != 0 : (OvrpControllerState.Touches & ovrpTouch_RIndexTrigger) != 0; break; case EOculusTouchControllerButton::XA_Touch: bButtonPressed = bIsLeft ? (OvrpControllerState.Touches & ovrpTouch_X) != 0 : (OvrpControllerState.Touches & ovrpTouch_A) != 0; break; case EOculusTouchControllerButton::YB_Touch: bButtonPressed = bIsLeft ? (OvrpControllerState.Touches & ovrpTouch_Y) != 0 : (OvrpControllerState.Touches & ovrpTouch_B) != 0; break; default: check(0); break; } // Update button state if (bButtonPressed != ButtonState.bIsPressed) { ButtonState.bIsPressed = bButtonPressed; if (ButtonState.bIsPressed) { OnControllerButtonPressed(ButtonState, PlatformUser, ControllerPair.DeviceId, false); // Set the timer for the first repeat ButtonState.NextRepeatTime = CurrentTime + InitialButtonRepeatDelay; } else { OnControllerButtonReleased(ButtonState, PlatformUser, ControllerPair.DeviceId, false); } } // Apply key repeat, if its time for that if (ButtonState.bIsPressed && ButtonState.NextRepeatTime <= CurrentTime) { OnControllerButtonPressed(ButtonState, PlatformUser, ControllerPair.DeviceId, true); // Set the timer for the next repeat ButtonState.NextRepeatTime = CurrentTime + ButtonRepeatDelay; } } // Handle Capacitive States for (int32 CapTouchIndex = 0; CapTouchIndex < (int32)EOculusTouchCapacitiveAxes::TotalAxisCount; ++CapTouchIndex) { FOculusAxisState& CapState = State.CapacitiveAxes[CapTouchIndex]; float CurrentAxisVal = 0.f; switch ((EOculusTouchCapacitiveAxes)CapTouchIndex) { case EOculusTouchCapacitiveAxes::XA: { const uint32 mask = (bIsLeft) ? ovrpTouch_X : ovrpTouch_A; CurrentAxisVal = (OvrpControllerState.Touches & mask) != 0 ? 1.f : 0.f; break; } case EOculusTouchCapacitiveAxes::YB: { const uint32 mask = (bIsLeft) ? ovrpTouch_Y : ovrpTouch_B; CurrentAxisVal = (OvrpControllerState.Touches & mask) != 0 ? 1.f : 0.f; break; } case EOculusTouchCapacitiveAxes::Thumbstick: { const uint32 mask = bIsMobileController ? ((bIsLeft) ? ovrpTouch_LTouchpad : ovrpTouch_RTouchpad) : ((bIsLeft) ? ovrpTouch_LThumb : ovrpTouch_RThumb); CurrentAxisVal = (OvrpControllerState.Touches & mask) != 0 ? 1.f : 0.f; break; } case EOculusTouchCapacitiveAxes::Trigger: { const uint32 mask = (bIsLeft) ? ovrpTouch_LIndexTrigger : ovrpTouch_RIndexTrigger; CurrentAxisVal = (OvrpControllerState.Touches & mask) != 0 ? 1.f : 0.f; break; } case EOculusTouchCapacitiveAxes::IndexPointing: { const uint32 mask = (bIsLeft) ? ovrpNearTouch_LIndexTrigger : ovrpNearTouch_RIndexTrigger; CurrentAxisVal = (OvrpControllerState.NearTouches & mask) != 0 ? 0.f : 1.f; break; } case EOculusTouchCapacitiveAxes::ThumbUp: { const uint32 mask = (bIsLeft) ? ovrpNearTouch_LThumbButtons : ovrpNearTouch_RThumbButtons; CurrentAxisVal = (OvrpControllerState.NearTouches & mask) != 0 ? 0.f : 1.f; break; } case EOculusTouchCapacitiveAxes::ThumbRest: { const uint32 mask = (bIsLeft) ? ovrpTouch_LThumbRest : ovrpTouch_RThumbRest; CurrentAxisVal = (OvrpControllerState.Touches & mask) != 0 ? 1.f : 0.f; break; } default: check(0); } if (CurrentAxisVal != CapState.State) { MessageHandler->OnControllerAnalog(CapState.Axis, PlatformUser, ControllerPair.DeviceId, CurrentAxisVal); CapState.State = CurrentAxisVal; // handle inverses if ((EOculusTouchCapacitiveAxes)CapTouchIndex == EOculusTouchCapacitiveAxes::IndexPointing) { FKey InverseAxisKey = (bIsLeft) ? FOculusKey::OculusTouch_Left_Trigger_Proximity : FOculusKey::OculusTouch_Right_Trigger_Proximity; MessageHandler->OnControllerAnalog(InverseAxisKey.GetFName(), PlatformUser, ControllerPair.DeviceId, FMath::IsNearlyEqual(CurrentAxisVal, 0.f) ? 1.f : 0.f); } else if ((EOculusTouchCapacitiveAxes)CapTouchIndex == EOculusTouchCapacitiveAxes::ThumbUp) { FKey InverseAxisKey = (bIsLeft) ? FOculusKey::OculusTouch_Left_Thumb_Proximity : FOculusKey::OculusTouch_Right_Thumb_Proximity; MessageHandler->OnControllerAnalog(InverseAxisKey.GetFName(), PlatformUser, ControllerPair.DeviceId, FMath::IsNearlyEqual(CurrentAxisVal, 0.f) ? 1.f : 0.f); } } } ProcessHaptics(DeltaTime); } else { // Controller isn't available right now. if (CVarOculusResetUntrackedInputStates.GetValueOnAnyThread()) { // Zero out input state, so that if controller comes back it will send fresh event deltas State = FOculusTouchControllerState((EControllerHand)HandIndex); UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: Controller for the hand %d is not tracked and input states are reset"), int(HandIndex)); } else { // Cache input state, so that if controller comes back it will send event deltas UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: Controller for the hand %d is not tracked and input states are saved"), int(HandIndex)); } } } } } else { // Controller isn't available right now. Zero out input state, so that if it comes back it will send fresh event deltas for (FOculusControllerPair& ControllerPair : ControllerPairs) { for (int32 HandIndex = 0; HandIndex < UE_ARRAY_COUNT(ControllerPair.TouchControllerStates); ++HandIndex) { FOculusTouchControllerState& State = ControllerPair.TouchControllerStates[HandIndex]; State = FOculusTouchControllerState((EControllerHand)HandIndex); } } } if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetControllerState6((ovrpController)(ovrpController_LHand | ovrpController_RHand | ovrpController_Hands), &OvrpControllerState))) { for (FOculusControllerPair& ControllerPair : ControllerPairs) { FPlatformUserId PlatformUser = IPlatformInputDeviceMapper::Get().GetUserForInputDevice(ControllerPair.DeviceId); for (int32 HandIndex = 0; HandIndex < UE_ARRAY_COUNT(ControllerPair.HandControllerStates); ++HandIndex) { FOculusHandControllerState& State = ControllerPair.HandControllerStates[HandIndex]; bool bIsLeft = (HandIndex == (int32)EControllerHand::Left); bool bIsCurrentlyTracked = bIsLeft ? (OvrpControllerState.ConnectedControllerTypes & ovrpController_LHand) != 0 : (OvrpControllerState.ConnectedControllerTypes & ovrpController_RHand) != 0; if (bIsCurrentlyTracked) { State.bIsConnected = true; ovrpBool bResult = true; // Hand Tracking requires the frame number for accurate results OculusXRHMD::FGameFrame* CurrentFrame; if (IsInGameThread()) { CurrentFrame = OculusXRHMD->GetNextFrameToRender(); } else { CurrentFrame = OculusXRHMD->GetFrame_RenderThread(); } // Poll for Hand Tracking State ovrpHandState HandState; if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetHandState2(ovrpStep_Render, CurrentFrame ? CurrentFrame->FrameNumber : OVRP_CURRENT_FRAMEINDEX, (ovrpHand)HandIndex, &HandState))) { // Update various data about hands State.HandScale = HandState.HandScale; // Update Bone Rotations for (uint32 BoneIndex = 0; BoneIndex < UE_ARRAY_COUNT(State.BoneRotations); BoneIndex++) { ovrpQuatf RawRotation = HandState.BoneRotations[BoneIndex]; FQuat BoneRotation = FOculusHandTracking::OvrBoneQuatToFQuat(RawRotation); BoneRotation.Normalize(); State.BoneRotations[BoneIndex] = BoneRotation; } // Update Pinch State and Pinch Strength bool bTracked = (HandState.Status & ovrpHandStatus_HandTracked) != 0; State.TrackingConfidence = FOculusHandTracking::ToEOculusXRTrackingConfidence(HandState.HandConfidence); State.bIsPositionTracked = bTracked && State.TrackingConfidence == EOculusXRTrackingConfidence::High; State.bIsPositionValid = bTracked; State.bIsOrientationTracked = bTracked && State.TrackingConfidence == EOculusXRTrackingConfidence::High; State.bIsOrientationValid = bTracked; State.bIsPointerPoseValid = (HandState.Status & ovrpHandStatus_InputValid) != 0; ovrpPosef PointerPose = HandState.PointerPose; State.PointerPose.SetTranslation(OculusXRHMD::ToFVector(PointerPose.Position)); State.PointerPose.SetRotation(OculusXRHMD::ToFQuat(PointerPose.Orientation)); State.bIsDominantHand = (HandState.Status & ovrpHandStatus_DominantHand) != 0; // Poll for finger confidence for (uint32 FingerIndex = 0; FingerIndex < (int32)EOculusHandAxes::TotalAxisCount; FingerIndex++) { State.FingerConfidences[FingerIndex] = FOculusHandTracking::ToEOculusXRTrackingConfidence(HandState.FingerConfidences[FingerIndex]); } // Poll for finger pinches for (uint32 FingerIndex = 0; FingerIndex < (uint32)EOculusHandButton::TotalButtonCount; FingerIndex++) { FOculusButtonState& PinchState = State.HandButtons[FingerIndex]; check(!PinchState.Key.IsNone()); bool bPressed = false; if (FingerIndex < (uint32)EOculusHandButton::System) { bPressed = (((uint32)HandState.Pinches & (1 << FingerIndex)) != 0); bPressed &= (HandState.HandConfidence == ovrpTrackingConfidence_High) && (HandState.FingerConfidences[FingerIndex] == ovrpTrackingConfidence_High); } else if (FingerIndex == (uint32)EOculusHandButton::System) { bPressed = (HandState.Status & ovrpHandStatus_SystemGestureInProgress) != 0; } else { bPressed = (OvrpControllerState.Buttons & ovrpButton_Start) != 0 && !State.bIsDominantHand; } if (bPressed != PinchState.bIsPressed) { PinchState.bIsPressed = bPressed; if (PinchState.bIsPressed) { OnControllerButtonPressed(PinchState, PlatformUser, ControllerPair.DeviceId, false); } else { OnControllerButtonReleased(PinchState, PlatformUser, ControllerPair.DeviceId, false); } } } // Poll for finger strength for (uint32 FingerIndex = 0; FingerIndex < (uint32)EOculusHandAxes::TotalAxisCount; FingerIndex++) { FOculusAxisState& PinchStrength = State.HandAxes[FingerIndex]; check(!PinchStrength.Axis.IsNone()); float PinchValue = 0.0f; if (HandState.HandConfidence == ovrpTrackingConfidence_High) { PinchValue = HandState.PinchStrength[FingerIndex]; } if (PinchValue != PinchStrength.State) { MessageHandler->OnControllerAnalog(PinchStrength.Axis, PlatformUser, ControllerPair.DeviceId, PinchValue); PinchStrength.State = PinchValue; } } } } else { // Hand isn't available right now. if (CVarOculusResetUntrackedInputStates.GetValueOnAnyThread()) { // Zero out input state, so that if hand comes back it will send fresh event deltas State = FOculusHandControllerState((EControllerHand)HandIndex); UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: Hand for the hand %d is not tracked and input states are reset"), int32(HandIndex)); } else { // Cache input state, so that if hand comes back it will send event deltas UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: Hand for the hand %d is not tracked and input states are saved"), int(HandIndex)); } } } } } else { // Hands are not availble right now, zero out the hand state for (FOculusControllerPair& ControllerPair : ControllerPairs) { for (int32 HandIndex = 0; HandIndex < UE_ARRAY_COUNT(ControllerPair.HandControllerStates); ++HandIndex) { FOculusHandControllerState& State = ControllerPair.HandControllerStates[HandIndex]; State = FOculusHandControllerState((EControllerHand)HandIndex); } } } } } UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("")); } void FOculusXRInput::SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) { MessageHandler = InMessageHandler; } bool FOculusXRInput::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { // No exec commands supported, for now. return false; } void FOculusXRInput::SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) { const EControllerHand Hand = (ChannelType == FForceFeedbackChannelType::LEFT_LARGE || ChannelType == FForceFeedbackChannelType::LEFT_SMALL) ? EControllerHand::Left : EControllerHand::Right; IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); FPlatformUserId InPlatformUser = FGenericPlatformMisc::GetPlatformUserForUserIndex(ControllerId); FInputDeviceId InDeviceId = INPUTDEVICEID_NONE; DeviceMapper.RemapControllerIdToPlatformUserAndDevice(ControllerId, InPlatformUser, InDeviceId); for (FOculusControllerPair& ControllerPair : ControllerPairs) { if (ControllerPair.DeviceId == InDeviceId) { FOculusTouchControllerState& ControllerState = ControllerPair.TouchControllerStates[(int32)Hand]; if (ControllerState.bPlayingHapticEffect) { continue; } // @todo: The SMALL channel controls frequency, the LARGE channel controls amplitude. This is a bit of a weird fit. if (ChannelType == FForceFeedbackChannelType::LEFT_SMALL || ChannelType == FForceFeedbackChannelType::RIGHT_SMALL) { ControllerState.ForceFeedbackHapticFrequency = Value; } else { ControllerState.ForceFeedbackHapticAmplitude = Value; } UpdateForceFeedback(ControllerPair, Hand); break; } } } void FOculusXRInput::SetChannelValues(int32 ControllerId, const FForceFeedbackValues& Values) { IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); FPlatformUserId InPlatformUser = FGenericPlatformMisc::GetPlatformUserForUserIndex(ControllerId); FInputDeviceId InDeviceId = INPUTDEVICEID_NONE; DeviceMapper.RemapControllerIdToPlatformUserAndDevice(ControllerId, InPlatformUser, InDeviceId); for (FOculusControllerPair& ControllerPair : ControllerPairs) { if (ControllerPair.DeviceId == InDeviceId) { // @todo: The SMALL channel controls frequency, the LARGE channel controls amplitude. This is a bit of a weird fit. FOculusTouchControllerState& LeftControllerState = ControllerPair.TouchControllerStates[(int32)EControllerHand::Left]; if (!LeftControllerState.bPlayingHapticEffect) { LeftControllerState.ForceFeedbackHapticFrequency = Values.LeftSmall; LeftControllerState.ForceFeedbackHapticAmplitude = Values.LeftLarge; UpdateForceFeedback(ControllerPair, EControllerHand::Left); } FOculusTouchControllerState& RightControllerState = ControllerPair.TouchControllerStates[(int32)EControllerHand::Right]; if (!RightControllerState.bPlayingHapticEffect) { RightControllerState.ForceFeedbackHapticFrequency = Values.RightSmall; RightControllerState.ForceFeedbackHapticAmplitude = Values.RightLarge; UpdateForceFeedback(ControllerPair, EControllerHand::Right); } } } } bool FOculusXRInput::SupportsForceFeedback(int32 ControllerId) { IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); FPlatformUserId InPlatformUser = FGenericPlatformMisc::GetPlatformUserForUserIndex(ControllerId); FInputDeviceId InDeviceId = INPUTDEVICEID_NONE; DeviceMapper.RemapControllerIdToPlatformUserAndDevice(ControllerId, InPlatformUser, InDeviceId); for (FOculusControllerPair& ControllerPair : ControllerPairs) { if (ControllerPair.DeviceId == InDeviceId) { const FOculusTouchControllerState& ControllerStateLeft = ControllerPair.TouchControllerStates[(int32)EControllerHand::Left]; const FOculusTouchControllerState& ControllerStateRight = ControllerPair.TouchControllerStates[(int32)EControllerHand::Right]; if (!(ControllerStateLeft.bIsConnected || ControllerStateRight.bIsConnected)) { // neither hand connected, won't be receiving force feedback continue; } if (IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) { // available so could receive feedback return true; } } } // not handling force feedback return false; } void FOculusXRInput::UpdateForceFeedback(const FOculusControllerPair& ControllerPair, const EControllerHand Hand) { const FOculusTouchControllerState& ControllerState = ControllerPair.TouchControllerStates[(int32)Hand]; if (ControllerState.bIsConnected && !ControllerState.bPlayingHapticEffect) { if (IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && FApp::HasVRFocus()) { ovrpControllerState6 OvrpControllerState; if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetControllerState6((ovrpController)(ovrpController_Active | ovrpController_LTrackedRemote | ovrpController_RTrackedRemote), &OvrpControllerState)) && (OvrpControllerState.ConnectedControllerTypes & (ovrpController_Touch | ovrpController_LTrackedRemote | ovrpController_RTrackedRemote))) { float FreqMin, FreqMax = 0.f; GetHapticFrequencyRange(FreqMin, FreqMax); // Map the [0.0 - 1.0] range to a useful range of frequencies for the Oculus controllers const float ActualFrequency = FMath::Lerp(FreqMin, FreqMax, FMath::Clamp(ControllerState.ForceFeedbackHapticFrequency, 0.0f, 1.0f)); // Oculus SDK wants amplitude values between 0.0 and 1.0 const float ActualAmplitude = ControllerState.ForceFeedbackHapticAmplitude * GetHapticAmplitudeScale(); ovrpController OvrController = ovrpController_None; if (OvrpControllerState.ConnectedControllerTypes & (ovrpController_Touch)) { OvrController = (Hand == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch; } else if (OvrpControllerState.ConnectedControllerTypes & (ovrpController_LTrackedRemote | ovrpController_RTrackedRemote)) { OvrController = (Hand == EControllerHand::Left) ? ovrpController_LTrackedRemote : ovrpController_RTrackedRemote; } static float LastAmplitudeSent = -1; if (ActualAmplitude != LastAmplitudeSent) { ovrpHapticsLocation hapticsLocationMask = ovrpHapticsLocation::ovrpHapticsLocation_Hand; FOculusXRHMDModule::GetPluginWrapper().SetControllerLocalizedVibration(OvrController, hapticsLocationMask, ActualFrequency, ActualAmplitude); LastAmplitudeSent = ActualAmplitude; } } } } } bool FOculusXRInput::OnControllerButtonPressed(const FOculusButtonState& ButtonState, FPlatformUserId UserId, FInputDeviceId DeviceId, bool IsRepeat) { bool result = MessageHandler->OnControllerButtonPressed(ButtonState.Key, UserId, DeviceId, IsRepeat); if (!ButtonState.EmulatedKey.IsNone()) { MessageHandler->OnControllerButtonPressed(ButtonState.EmulatedKey, UserId, DeviceId, IsRepeat); } return result; } bool FOculusXRInput::OnControllerButtonReleased(const FOculusButtonState& ButtonState, FPlatformUserId UserId, FInputDeviceId DeviceId, bool IsRepeat) { bool result = MessageHandler->OnControllerButtonReleased(ButtonState.Key, UserId, DeviceId, IsRepeat); if (!ButtonState.EmulatedKey.IsNone()) { MessageHandler->OnControllerButtonReleased(ButtonState.EmulatedKey, UserId, DeviceId, IsRepeat); } return result; } FName FOculusXRInput::GetMotionControllerDeviceTypeName() const { const static FName DefaultName(TEXT("OculusXRInputDevice")); return DefaultName; } void FOculusXRInput::ShutdownXRFunctionLibrary() { FunctionLibraryImpl = nullptr; } struct MotionSourceInfo { ovrpNode Primary; ovrpNode Fallback; }; // Supported motion sources. // When using both controllers and hand tracking, the 'ovrpNode_Hand(Left|Right)' controller transform is overwritten with the wrist transforms // Check the controllers first, then fallback to the hands. const TMap<FName, MotionSourceInfo> MotionSourceMap{ { FName("Left"), { ovrpNode_ControllerLeft, ovrpNode_HandLeft } }, { FName("Right"), { ovrpNode_ControllerRight, ovrpNode_HandRight } }, { FName("LeftGrip"), { ovrpNode_HandLeft, ovrpNode_None } }, { FName("RightGrip"), { ovrpNode_HandRight, ovrpNode_None } }, { FName("LeftAim"), { ovrpNode_HandLeft, ovrpNode_None } }, { FName("RightAim"), { ovrpNode_HandRight, ovrpNode_None } }, // Sometimes we can get an enum as the motion source name { FName("EControllerHand::Left"), { ovrpNode_HandLeft, ovrpNode_None } }, { FName("EControllerHand::Right"), { ovrpNode_HandRight, ovrpNode_None } }, }; #if UE_VERSION_OLDER_THAN(5, 3, 0) bool FOculusXRInput::GetControllerOrientationAndPosition(const int32 ControllerIndex, const EControllerHand DeviceHand, FRotator& OutOrientation, FVector& OutPosition, float WorldToMetersScale) const { FName MotionSource; switch (DeviceHand) { case EControllerHand::Left: MotionSource = FName("Left"); break; case EControllerHand::Right: MotionSource = FName("Right"); break; default: MotionSource = FName("Unknown"); break; } return GetControllerOrientationAndPosition(ControllerIndex, MotionSource, OutOrientation, OutPosition, WorldToMetersScale); } ETrackingStatus FOculusXRInput::GetControllerTrackingStatus(const int32 ControllerIndex, const EControllerHand DeviceHand) const { ETrackingStatus TrackingStatus = ETrackingStatus::NotTracked; if (DeviceHand != EControllerHand::Left && DeviceHand != EControllerHand::Right) { return TrackingStatus; } IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); FPlatformUserId InPlatformUser = FGenericPlatformMisc::GetPlatformUserForUserIndex(ControllerIndex); FInputDeviceId InDeviceId = INPUTDEVICEID_NONE; DeviceMapper.RemapControllerIdToPlatformUserAndDevice(ControllerIndex, InPlatformUser, InDeviceId); for (const FOculusControllerPair& ControllerPair : ControllerPairs) { if (ControllerPair.DeviceId == InDeviceId) { const FOculusTouchControllerState& ControllerState = ControllerPair.TouchControllerStates[(int32)DeviceHand]; if (ControllerState.bIsConnected) { if (ControllerState.bIsPositionTracked && ControllerState.bIsOrientationTracked) { TrackingStatus = ETrackingStatus::Tracked; } else if (ControllerState.bIsPositionValid && ControllerState.bIsOrientationValid) { TrackingStatus = ETrackingStatus::InertialOnly; } break; } const FOculusHandControllerState& HandState = ControllerPair.HandControllerStates[(int32)DeviceHand]; if (HandState.bIsConnected) { if (HandState.bIsPositionTracked && HandState.bIsOrientationTracked) { TrackingStatus = ETrackingStatus::Tracked; } break; } } } return TrackingStatus; } #endif bool FOculusXRInput::GetControllerOrientationAndPosition(const int32 ControllerIndex, const FName MotionSource, FRotator& OutOrientation, FVector& OutPosition, float WorldToMetersScale) const { IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); FPlatformUserId InPlatformUser = FGenericPlatformMisc::GetPlatformUserForUserIndex(ControllerIndex); FInputDeviceId InDeviceId = INPUTDEVICEID_NONE; DeviceMapper.RemapControllerIdToPlatformUserAndDevice(ControllerIndex, InPlatformUser, InDeviceId); // Don't do renderthread pose update if MRC is active due to controller jitter issues with SceneCaptures if (IsInGameThread() || !UOculusXRMRFunctionLibrary::IsMrcActive()) { for (const FOculusControllerPair& ControllerPair : ControllerPairs) { if (ControllerPair.DeviceId == InDeviceId) { if (MotionSourceMap.Contains(MotionSource)) { if (IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) { OculusXRHMD::FOculusXRHMD* OculusXRHMD = static_cast<OculusXRHMD::FOculusXRHMD*>(GEngine->XRSystem->GetHMDDevice()); const MotionSourceInfo& MotionInfo = MotionSourceMap[MotionSource]; ovrpNode Node = MotionInfo.Primary; ovrpBool bResult = true; bool bIsPositionValid = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePositionValid(Node, &bResult)) && bResult; bool bIsOrientationValid = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodeOrientationValid(Node, &bResult)) && bResult; if (!bIsPositionValid && !bIsOrientationValid && MotionInfo.Fallback != ovrpNode_None) { Node = MotionInfo.Fallback; bResult = true; bIsPositionValid = OVRP_SUCCESS( FOculusXRHMDModule::GetPluginWrapper().GetNodePositionValid(Node, &bResult)) && bResult; bIsOrientationValid = OVRP_SUCCESS( FOculusXRHMDModule::GetPluginWrapper().GetNodeOrientationValid(Node, &bResult)) && bResult; } if (bIsPositionValid || bIsOrientationValid) { OculusXRHMD::FSettings* Settings; OculusXRHMD::FGameFrame* CurrentFrame; if (IsInGameThread()) { Settings = OculusXRHMD->GetSettings(); CurrentFrame = OculusXRHMD->GetNextFrameToRender(); } else { Settings = OculusXRHMD->GetSettings_RenderThread(); CurrentFrame = OculusXRHMD->GetFrame_RenderThread(); } if (Settings) { ovrpPoseStatef InPoseState; OculusXRHMD::FPose OutPose; EOculusXRControllerPoseAlignment ControllerPoseAlignment = Settings->ControllerPoseAlignment; switch (CVarOculusControllerPose.GetValueOnAnyThread()) { case 1: ControllerPoseAlignment = EOculusXRControllerPoseAlignment::Default; break; case 2: ControllerPoseAlignment = EOculusXRControllerPoseAlignment::Grip; break; case 3: ControllerPoseAlignment = EOculusXRControllerPoseAlignment::Aim; break; default: break; } if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, CurrentFrame ? CurrentFrame->FrameNumber : OVRP_CURRENT_FRAMEINDEX, Node, &InPoseState)) && OculusXRHMD->ConvertPose_Internal(InPoseState.Pose, OutPose, Settings, WorldToMetersScale)) { FName FinalMotionSource = MotionSource; FString MotionSourceStr = MotionSource.ToString(); // Converting controller hand enum to motion source can leave behind the enum name in the resulting motion source, so just remove that before handling it MotionSourceStr.RemoveFromStart("EControllerHand::"); if (MotionSourceStr.Equals("Left") || MotionSourceStr.Equals("Right")) { switch (ControllerPoseAlignment) { case EOculusXRControllerPoseAlignment::Grip: FinalMotionSource = FName(MotionSourceStr.Append(FString("Grip"))); break; case EOculusXRControllerPoseAlignment::Aim: FinalMotionSource = FName(MotionSourceStr.Append(FString("Aim"))); break; case EOculusXRControllerPoseAlignment::Default: default: break; } } // TODO: Just pass the pose info to OVRPlugin instead of doing the conversion between poses here if (FinalMotionSource == FName("LeftGrip") || FinalMotionSource == FName("RightGrip")) { OutPose = OutPose * OculusXRHMD::FPose(FQuat(FVector(0, 1, 0), -FMath::DegreesToRadians(double(60))), FVector(-0.04, 0, -0.03) * WorldToMetersScale); } else if (FinalMotionSource == FName("LeftAim") || FinalMotionSource == FName("RightAim")) { OutPose = OutPose * OculusXRHMD::FPose(FQuat::Identity, FVector(0.055, 0, 0) * WorldToMetersScale); } if (bIsPositionValid) { OutPosition = OutPose.Position; } if (bIsOrientationValid) { OutOrientation = OutPose.Orientation.Rotator(); } // Avoid any broadcasting in other threads than the game thread because that is undefined behavior if (IsInGameThread()) { auto bSuccess = true; EControllerHand ControllerHand; if (GetHandEnumForSourceName(MotionSource, ControllerHand)) { // TODO: Just use the motion source name here instead of the legacy enum UOculusXRInputFunctionLibrary::HandMovementFilter.Broadcast( ControllerHand, &OutPosition, &OutOrientation, &bSuccess); } return bSuccess; } return true; } } } } } break; } } } // Avoid any broadcasting in other threads than the game thread because that is undefined behavior auto bSuccess = false; if (IsInGameThread()) { EControllerHand ControllerHand; if (GetHandEnumForSourceName(MotionSource, ControllerHand)) { // TODO: Just use the motion source name here instead of the legacy enum UOculusXRInputFunctionLibrary::HandMovementFilter.Broadcast( ControllerHand, &OutPosition, &OutOrientation, &bSuccess); } } return bSuccess; } ETrackingStatus FOculusXRInput::GetControllerTrackingStatus(const int32 ControllerIndex, const FName MotionSource) const { ETrackingStatus TrackingStatus = ETrackingStatus::NotTracked; bool IsLeftHand = MotionSource == FName("Left") || MotionSource == FName("EControllerHand::Left") || MotionSource == FName("LeftGrip") || MotionSource == FName("LeftAim"); bool IsRightHand = MotionSource == FName("Right") || MotionSource == FName("EControllerHand::Right") || MotionSource == FName("RightGrip") || MotionSource == FName("RightAim"); if (!IsLeftHand && !IsRightHand) { return TrackingStatus; } IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); FPlatformUserId InPlatformUser = FGenericPlatformMisc::GetPlatformUserForUserIndex(ControllerIndex); FInputDeviceId InDeviceId = INPUTDEVICEID_NONE; DeviceMapper.RemapControllerIdToPlatformUserAndDevice(ControllerIndex, InPlatformUser, InDeviceId); for (const FOculusControllerPair& ControllerPair : ControllerPairs) { if (ControllerPair.DeviceId == InDeviceId) { const FOculusTouchControllerState& ControllerState = ControllerPair.TouchControllerStates[IsLeftHand ? 0 : 1]; if (ControllerState.bIsConnected) { if (ControllerState.bIsPositionTracked && ControllerState.bIsOrientationTracked) { TrackingStatus = ETrackingStatus::Tracked; } else if (ControllerState.bIsPositionValid && ControllerState.bIsOrientationValid) { TrackingStatus = ETrackingStatus::InertialOnly; } break; } const FOculusHandControllerState& HandState = ControllerPair.HandControllerStates[IsLeftHand ? 0 : 1]; if (HandState.bIsConnected) { if (HandState.bIsPositionTracked && HandState.bIsOrientationTracked) { TrackingStatus = ETrackingStatus::Tracked; } break; } } } return TrackingStatus; } void FOculusXRInput::SetHapticFeedbackValues(int32 ControllerId, int32 Hand, const FHapticFeedbackValues& Values) { SetHapticFeedbackValues(ControllerId, Hand, Values, nullptr); } void FOculusXRInput::SetHapticFeedbackValues(int32 ControllerId, int32 Hand, const FHapticFeedbackValues& Values, TSharedPtr<FOculusXRHapticsDesc> HapticsDesc) { IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); FPlatformUserId InPlatformUser = FGenericPlatformMisc::GetPlatformUserForUserIndex(ControllerId); FInputDeviceId InDeviceId = INPUTDEVICEID_NONE; DeviceMapper.RemapControllerIdToPlatformUserAndDevice(ControllerId, InPlatformUser, InDeviceId); for (FOculusControllerPair& ControllerPair : ControllerPairs) { if (ControllerPair.DeviceId == InDeviceId) { FOculusTouchControllerState& ControllerState = ControllerPair.TouchControllerStates[Hand]; if (ControllerState.bIsConnected) { if (IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && FApp::HasVRFocus()) { ovrpController ControllerTypes = (ovrpController)(ovrpController_Active | ovrpController_LTrackedRemote | ovrpController_RTrackedRemote); #ifdef USE_ANDROID_INPUT ControllerTypes = (ovrpController)(ControllerTypes | ovrpController_Touch); #endif ovrpControllerState6 OvrpControllerState; if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetControllerState6(ControllerTypes, &OvrpControllerState))) { UE_LOG(LogOcInput, Error, TEXT("GetControllerState6 failed.")); return; } if (OvrpControllerState.ConnectedControllerTypes & (ovrpController_Touch | ovrpController_LTrackedRemote | ovrpController_RTrackedRemote)) { // Buffered haptics is currently only supported on Touch FHapticFeedbackBuffer* HapticBuffer = Values.HapticBuffer; bool bHapticBuffer = (HapticBuffer && HapticBuffer->BufferLength > 0); if ((OvrpControllerState.ConnectedControllerTypes & (ovrpController_Touch)) && bHapticBuffer) { const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch; ovrpHapticsState OvrpHapticsState; if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetControllerHapticsState2(OvrpController, &OvrpHapticsState))) { UE_LOG(LogOcInput, Error, TEXT("ControllerHapticsState2 failed.")); return; } double StartTimePCM = FPlatformTime::Seconds(); float TimeToSend = GetMaxHapticDuration(EControllerHand(Hand)); int WantToSend = (int)(TimeToSend * HapticBuffer->SamplingRate); if (WantToSend == 0) return; WantToSend = FMath::Min(WantToSend, OvrpHapticsDesc.MaximumBufferSamplesCount); WantToSend = FMath::Max(WantToSend, OvrpHapticsDesc.MinimumBufferSamplesCount); ovrpUInt32 SamplesSent = 0; if (OvrpHapticsState.SamplesQueued < OvrpHapticsDesc.MinimumSafeSamplesQueued + WantToSend) // trying to minimize latency { WantToSend = (OvrpHapticsDesc.MinimumSafeSamplesQueued + WantToSend - OvrpHapticsState.SamplesQueued); void* BufferToFree = nullptr; ovrpHapticsBuffer OvrpHapticsBuffer; WantToSend = FMath::Min(WantToSend, HapticBuffer->BufferLength - HapticBuffer->SamplesSent); WantToSend = FMath::Min(WantToSend, (int)(0.001f * CVarOculusPCMBatchDuration.GetValueOnAnyThread() * HapticBuffer->SamplingRate)); TimeToSend = 1.f * WantToSend / HapticBuffer->SamplingRate; OvrpHapticsBuffer.SamplesCount = WantToSend; if (OvrpHapticsBuffer.SamplesCount == 0 && OvrpHapticsState.SamplesQueued == 0) { Values.HapticBuffer->bFinishedPlaying = HapticBuffer->bFinishedPlaying = true; ControllerState.bPlayingHapticEffect = false; } else { if (OvrpHapticsDesc.SampleSizeInBytes == 1) { uint8* Samples = (uint8*)FMemory::Malloc(OvrpHapticsBuffer.SamplesCount * sizeof(*Samples)); for (int i = 0; i < OvrpHapticsBuffer.SamplesCount; i++) { Samples[i] = static_cast<uint8>(HapticBuffer->RawData[HapticBuffer->CurrentPtr + i] * HapticBuffer->ScaleFactor); } OvrpHapticsBuffer.Samples = BufferToFree = Samples; } else if (OvrpHapticsDesc.SampleSizeInBytes == 2) { uint16* Samples = (uint16*)FMemory::Malloc(OvrpHapticsBuffer.SamplesCount * sizeof(*Samples)); for (int i = 0; i < OvrpHapticsBuffer.SamplesCount; i++) { const uint32 DataIndex = HapticBuffer->CurrentPtr + (i * 2); const uint16* const RawData = reinterpret_cast<const uint16*>(&HapticBuffer->RawData[DataIndex]); Samples[i] = static_cast<uint16>(*RawData * HapticBuffer->ScaleFactor); } OvrpHapticsBuffer.Samples = BufferToFree = Samples; } else if (OvrpHapticsDesc.SampleSizeInBytes == 4) { uint32* Samples = (uint32*)FMemory::Malloc(OvrpHapticsBuffer.SamplesCount * sizeof(*Samples)); for (int i = 0; i < OvrpHapticsBuffer.SamplesCount; i++) { const uint32 DataIndex = HapticBuffer->CurrentPtr + (i * 4); const uint32* const RawData = reinterpret_cast<const uint32*>(&HapticBuffer->RawData[DataIndex]); Samples[i] = static_cast<uint32>(*RawData * HapticBuffer->ScaleFactor); } OvrpHapticsBuffer.Samples = BufferToFree = Samples; } else { UE_LOG(LogOcInput, Error, TEXT("Unsupported OvrpHapticsDesc.SampleSizeInBytes: %d."), OvrpHapticsDesc.SampleSizeInBytes); return; } ovrpHapticsPcmVibration HapticsVibration; bool bAppend = HapticsDesc ? HapticsDesc->bAppend : false; HapticsVibration.Append = (bAppend || HapticBuffer->SamplesSent > 0); float* PCMBuffer = (float*)FMemory::Malloc(OvrpHapticsBuffer.SamplesCount * sizeof(*PCMBuffer)); for (int i = 0; i < OvrpHapticsBuffer.SamplesCount; i++) { float Amplitude = ((uint8_t*)OvrpHapticsBuffer.Samples)[i] / 255.0f; Amplitude = FMath::Min(1.0f, Amplitude); Amplitude = FMath::Max(-1.0f, Amplitude); PCMBuffer[i] = Amplitude; } HapticsVibration.Buffer = PCMBuffer; HapticsVibration.BufferSize = (ovrpUInt32)OvrpHapticsBuffer.SamplesCount; HapticsVibration.SampleRateHz = HapticBuffer->SamplingRate; HapticsVibration.SamplesConsumed = &SamplesSent; FOculusXRHMDModule::GetPluginWrapper().SetControllerHapticsPcm( OvrpController, HapticsVibration); double EndTimePCM = FPlatformTime::Seconds(); if (PCMBuffer) { FMemory::Free(PCMBuffer); } UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("PCMHaptics is finished: bAppend: %d, BufferSize: %d, SampleRate: %.3f, SamplesConsumed: %d, Total SamplesSent: %d, TimeSpent: %fms"), (int)(HapticsVibration.Append), HapticsVibration.BufferSize, HapticsVibration.SampleRateHz, SamplesSent, HapticBuffer->SamplesSent + SamplesSent, (EndTimePCM - StartTimePCM) * 1000.0); if (BufferToFree) { FMemory::Free(BufferToFree); } HapticBuffer->CurrentPtr += (SamplesSent * OvrpHapticsDesc.SampleSizeInBytes); HapticBuffer->SamplesSent += SamplesSent; ControllerState.bPlayingHapticEffect = true; } } } else { float FreqMin, FreqMax = 0.f; GetHapticFrequencyRange(FreqMin, FreqMax); const float InitialFreq = (Values.Frequency > 0.0f) ? Values.Frequency : 1.0f; const float Frequency = FMath::Lerp(FreqMin, FreqMax, FMath::Clamp(InitialFreq, 0.f, 1.f)); const float Amplitude = Values.Amplitude * GetHapticAmplitudeScale(); if (ControllerState.HapticAmplitude != Amplitude || ControllerState.HapticFrequency != Frequency) { ControllerState.HapticAmplitude = Amplitude; ControllerState.HapticFrequency = Frequency; ovrpController OvrController = ovrpController_None; if (OvrpControllerState.ConnectedControllerTypes & (ovrpController_Touch)) { OvrController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch; } else if (OvrpControllerState.ConnectedControllerTypes & (ovrpController_LTrackedRemote | ovrpController_RTrackedRemote)) { OvrController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTrackedRemote : ovrpController_RTrackedRemote; } ovrpHapticsLocation Loc = (HapticsDesc ? GetOVRPHapticsLocation(HapticsDesc->Location) : ovrpHapticsLocation::ovrpHapticsLocation_Hand); FOculusXRHMDModule::GetPluginWrapper().SetControllerLocalizedVibration(OvrController, Loc, Frequency, Amplitude); UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("LocalizedVibration is finished: Location: %d, Frequency: %f, Amplitude: %f"), (int)(Loc), Frequency, Amplitude); ControllerState.bPlayingHapticEffect = (Amplitude != 0.f) && (Frequency != 0.f); } } } } } break; } } } void FOculusXRInput::PlayHapticEffect( UHapticFeedbackEffect_Base* HapticEffect, EControllerHand Hand, EOculusXRHandHapticsLocation Location, bool bAppend, float Scale, bool bLoop) { TSharedPtr<IOculusXRInputBase> Impl = GetOculusXRInputBaseImpl(); return Impl->PlayHapticEffect(HapticEffect, Hand, Location, bAppend, Scale, bLoop); } void FOculusXRInput::PlayAmplitudeEnvelopeHapticEffect(EControllerHand Hand, int SamplesCount, void* Samples, int InSampleRate) { TSharedPtr<IOculusXRInputBase> Impl = GetOculusXRInputBaseImpl(); Impl->PlayAmplitudeEnvelopeHapticEffect(Hand, SamplesCount, Samples, InSampleRate); } void FOculusXRInput::SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location) { TSharedPtr<IOculusXRInputBase> Impl = GetOculusXRInputBaseImpl(); return Impl->SetHapticsByValue(Frequency, Amplitude, Hand, Location); } void FOculusXRInput::ProcessHaptics(const float DeltaTime) { FHapticFeedbackValues LeftHaptics, RightHaptics; bool bLeftHapticsNeedUpdate = false; bool bRightHapticsNeedUpdate = false; if (ActiveHapticEffect_Left.IsValid()) { const bool bPlaying = ActiveHapticEffect_Left->Update(DeltaTime, LeftHaptics); if (!bPlaying) { ActiveHapticEffect_Left->bLoop ? HapticsDesc_Left->Restart() : HapticsDesc_Left.Reset(); ActiveHapticEffect_Left->bLoop ? ActiveHapticEffect_Left->Restart() : ActiveHapticEffect_Left.Reset(); } bLeftHapticsNeedUpdate = true; } if (ActiveHapticEffect_Right.IsValid()) { const bool bPlaying = ActiveHapticEffect_Right->Update(DeltaTime, RightHaptics); if (!bPlaying) { ActiveHapticEffect_Right->bLoop ? HapticsDesc_Right->Restart() : HapticsDesc_Right.Reset(); ActiveHapticEffect_Right->bLoop ? ActiveHapticEffect_Right->Restart() : ActiveHapticEffect_Right.Reset(); } bRightHapticsNeedUpdate = true; } // Haptic Updates if (bLeftHapticsNeedUpdate) { SetHapticFeedbackValues(0, (int32)(EControllerHand::Left), LeftHaptics, HapticsDesc_Left); } if (bRightHapticsNeedUpdate) { SetHapticFeedbackValues(0, (int32)(EControllerHand::Right), RightHaptics, HapticsDesc_Right); } } void FOculusTouchControllerState::ResampleHapticBufferData(const FHapticFeedbackBuffer& HapticBuffer, TMap<const uint8*, TSharedPtr<TArray<uint8>>>& ResampledRawDataCache) { const uint8* OriginalRawData = HapticBuffer.RawData; TSharedPtr<TArray<uint8>>* ResampledRawDataSharedPtrPtr = ResampledRawDataCache.Find(OriginalRawData); if (ResampledRawDataSharedPtrPtr == nullptr) { // We need to resample and cache the resampled data. ResampledHapticBuffer = HapticBuffer; int32 SampleRate = HapticBuffer.SamplingRate; int TargetFrequency = 320; int TargetBufferSize = (HapticBuffer.BufferLength * TargetFrequency) / (SampleRate * 2) + 1; // 2 because we're only using half of the 16bit source PCM buffer ResampledHapticBuffer.BufferLength = TargetBufferSize; ResampledHapticBuffer.CurrentPtr = 0; ResampledHapticBuffer.SamplingRate = TargetFrequency; TSharedPtr<TArray<uint8>>& NewResampledRawDataSharedPtr = ResampledRawDataCache.Add(OriginalRawData); NewResampledRawDataSharedPtr = MakeShared<TArray<uint8>>(); ResampledRawDataSharedPtrPtr = &NewResampledRawDataSharedPtr; TArray<uint8>& ResampledRawData = *NewResampledRawDataSharedPtr; ResampledRawData.SetNum(TargetBufferSize); const uint8* PCMData = HapticBuffer.RawData; int previousTargetIndex = -1; int currentMin = 0; for (int i = 1; i < HapticBuffer.BufferLength; i += 2) { int targetIndex = i * TargetFrequency / (SampleRate * 2); int val = PCMData[i]; if (val & 0x80) { val = ~val; } currentMin = FMath::Min(currentMin, val); if (targetIndex != previousTargetIndex) { ResampledRawData[targetIndex] = val * 2; // *Scale; previousTargetIndex = targetIndex; currentMin = 0; } } ResampledHapticBuffer.RawData = ResampledRawData.GetData(); } else if (ResampledHapticBuffer.RawData != (*ResampledRawDataSharedPtrPtr)->GetData()) { // If this a cached effect, but not the same one we played last so we need to copy the new one's buffer and reference its cached resampled data. ResampledHapticBuffer = HapticBuffer; ResampledHapticBuffer.RawData = (*ResampledRawDataSharedPtrPtr)->GetData(); } } void FOculusXRInput::GetHapticFrequencyRange(float& MinFrequency, float& MaxFrequency) const { MinFrequency = 0.f; MaxFrequency = 1.f; } float FOculusXRInput::GetHapticAmplitudeScale() const { return 1.f; } uint32 FOculusXRInput::GetNumberOfTouchControllers() const { uint32 RetVal = 0; for (FOculusControllerPair Pair : ControllerPairs) { RetVal += (Pair.TouchControllerStates[0].bIsConnected ? 1 : 0); RetVal += (Pair.TouchControllerStates[1].bIsConnected ? 1 : 0); } return RetVal; } uint32 FOculusXRInput::GetNumberOfHandControllers() const { uint32 RetVal = 0; for (FOculusControllerPair Pair : ControllerPairs) { RetVal += (Pair.HandControllerStates[0].bIsConnected ? 1 : 0); RetVal += (Pair.HandControllerStates[1].bIsConnected ? 1 : 0); } return RetVal; } ovrpHapticsLocation FOculusXRInput::GetOVRPHapticsLocation(EOculusXRHandHapticsLocation Location) { switch (Location) { case EOculusXRHandHapticsLocation::Hand: return ovrpHapticsLocation::ovrpHapticsLocation_Hand; case EOculusXRHandHapticsLocation::Thumb: return ovrpHapticsLocation::ovrpHapticsLocation_Thumb; case EOculusXRHandHapticsLocation::Index: return ovrpHapticsLocation::ovrpHapticsLocation_Index; default: UE_LOG(LogOcInput, Error, TEXT("Unsupported Haptics Location: %d"), Location); return ovrpHapticsLocation::ovrpHapticsLocation_None; } } bool FOculusXRInput::GetOvrpHapticsDesc(int Hand) { if (!bPulledHapticsDesc) { const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch; // Buffered haptics is currently only supported on Touch if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetControllerHapticsDesc2(OvrpController, &OvrpHapticsDesc))) { UE_LOG(LogOcInput, Error, TEXT("ControllerHapticsDesc2 failed.")); return false; } bPulledHapticsDesc = true; if (OvrpHapticsDesc.SampleRateHz == 0) { UE_LOG(LogOcInput, Error, TEXT("GetControllerHapticsDesc2 returns OvrpHapticsDesc.SampleRateHz = %d"), OvrpHapticsDesc.SampleRateHz); OvrpHapticsDesc.SampleRateHz = 2000; return true; } else { UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("GetControllerHapticsDesc2 returns OvrpHapticsDesc.SampleRateHz = %d"), OvrpHapticsDesc.SampleRateHz); } } return true; } TSharedPtr<IOculusXRInputBase> FOculusXRInput::FunctionLibraryImpl = nullptr; TSharedPtr<IOculusXRInputBase> FOculusXRInput::GetOculusXRInputBaseImpl() { if (FunctionLibraryImpl == nullptr) { const FName SystemName(TEXT("OpenXR")); const bool IsOpenXR = GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName); if (OculusXRHMD::FOculusXRHMD::GetOculusXRHMD() != nullptr) { FunctionLibraryImpl = MakeShared<FOculusXRInputOVR>(); } else if (IsOpenXR) { FunctionLibraryImpl = MakeShared<FOculusXRInputOpenXR>(); } } return FunctionLibraryImpl; } float FOculusXRInput::GetControllerSampleRateHz(EControllerHand Hand) { TSharedPtr<IOculusXRInputBase> Impl = GetOculusXRInputBaseImpl(); return Impl->GetControllerSampleRateHz(Hand); } int FOculusXRInput::GetMaxHapticDuration(EControllerHand Hand) { TSharedPtr<IOculusXRInputBase> Impl = GetOculusXRInputBaseImpl(); return Impl->GetMaxHapticDuration(Hand); } } // namespace OculusXRInput #undef LOCTEXT_NAMESPACE #endif // OCULUS_INPUT_SUPPORTED_PLATFORMS