// Copyright Epic Games, Inc. All Rights Reserved. #include "PICOOpenXRHandInteraction.h" #include "IPICOOpenXRHandInteractionModule.h" #include "IXRTrackingSystem.h" #include "CoreMinimal.h" #include "UObject/Package.h" #include "UObject/UObjectGlobals.h" #include "UObject/ObjectMacros.h" #include "Engine/Engine.h" #include "OpenXRCore.h" #include "Framework/Application/SlateApplication.h" #include "IOpenXRExtensionPlugin.h" #include "Modules/ModuleManager.h" #include "Features/IModularFeatures.h" #include "HeadMountedDisplayFunctionLibrary.h" #include "IOpenXRHMDModule.h" #include "IOpenXRHMD.h" #include "IOpenXRExtensionPluginDelegates.h" #include "GameFramework/InputSettings.h" #include "EnhancedInputLibrary.h" #include "EnhancedInputSubsystemInterface.h" #include "EnhancedInputModule.h" #include "InputAction.h" #include "InputMappingContext.h" #include "PlayerMappableInputConfig.h" #include "UserSettings/EnhancedInputUserSettings.h" #include "EnhancedInputDeveloperSettings.h" #if WITH_EDITOR #include "Editor/EditorEngine.h" #include "Editor.h" #include "EnhancedInputEditorSubsystem.h" #endif #define LOCTEXT_NAMESPACE "PICOOpenXRHandInteraction" DEFINE_LOG_CATEGORY_STATIC(LogPICOOpenXRHandInteraction, Display, All); const FKey PICOHI_Left_Pinch_Axis("PICOHI_Left_Pinch_Axis"); const FKey PICOHI_Left_Pinch_Click("PICOHI_Left_Pinch_Click"); const FKey PICOHI_Left_Aim_Axis("PICOHI_Left_Aim_Axis"); const FKey PICOHI_Left_Aim_Click("PICOHI_Left_Aim_Click"); const FKey PICOHI_Left_Grasp_Axis("PICOHI_Left_Grasp_Axis"); const FKey PICOHI_Left_Grasp_Click("PICOHI_Left_Grasp_Click"); const FKey PICOHI_Right_Pinch_Axis("PICOHI_Right_Pinch_Axis"); const FKey PICOHI_Right_Pinch_Click("PICOHI_Right_Pinch_Click"); const FKey PICOHI_Right_Aim_Axis("PICOHI_Right_Aim_Axis"); const FKey PICOHI_Right_Aim_Click("PICOHI_Right_Aim_Click"); const FKey PICOHI_Right_Grasp_Axis("PICOHI_Right_Grasp_Axis"); const FKey PICOHI_Right_Grasp_Click("PICOHI_Right_Grasp_Click"); class FPICOOpenXRHandInteractionModule : public IPICOOpenXRHandInteractionModule, public IOpenXRExtensionPlugin { public: FPICOOpenXRHandInteractionModule() : InputDevice(nullptr) {} virtual void StartupModule() override { IPICOOpenXRHandInteractionModule::StartupModule(); RegisterOpenXRExtensionModularFeature(); // HACK: Generic Application might not be instantiated at this point so we create the input device with a // dummy message handler. When the Generic Application creates the input device it passes a valid message // handler to it which is further on used for all the controller events. This hack fixes issues caused by // using a custom input device before the Generic Application has instantiated it. Eg. within BeginPlay() // // This also fixes the warnings that pop up on the custom input keys when the blueprint loads. Those // warnings are caused because Unreal loads the bluerints before the input device has been instantiated // and has added its keys, thus leading Unreal to believe that those keys don't exist. This hack causes // an earlier instantiation of the input device, and consequently, the custom keys. TSharedPtr DummyMessageHandler(new FGenericApplicationMessageHandler()); CreateInputDevice(DummyMessageHandler.ToSharedRef()); } virtual void ShutdownModule() override { IPICOOpenXRHandInteractionModule::ShutdownModule(); } virtual TSharedPtr CreateInputDevice(const TSharedRef& InMessageHandler) override { if (!InputDevice.IsValid()) { TSharedPtr ViveTrackingInputDevice(new FPICOOpenXRHandInteraction(InMessageHandler)); InputDevice = ViveTrackingInputDevice; return InputDevice; } else { InputDevice.Get()->SetMessageHandler(InMessageHandler); return InputDevice; } return nullptr; } virtual TSharedPtr GetInputDevice() override { if (!InputDevice.IsValid()) { CreateInputDevice(FSlateApplication::Get().GetPlatformApplication()->GetMessageHandler()); } return InputDevice; } private: TSharedPtr InputDevice; }; IMPLEMENT_MODULE(FPICOOpenXRHandInteractionModule, PICOOpenXRHandInteraction); FPICOOpenXRHandInteraction::FHandInteraction::FHandInteraction(XrActionSet InActionSet, FOpenXRPath InRolePath, const char* InName) : HandInteractionActionSet(InActionSet) , SubPath(InRolePath) , PoseAction(XR_NULL_HANDLE) , DeviceId(-1) { TArray SubPaths; SubPaths.Add(SubPath); XrActionCreateInfo Info; Info.type = XR_TYPE_ACTION_CREATE_INFO; Info.next = nullptr; Info.countSubactionPaths = SubPaths.Num(); Info.subactionPaths = SubPaths.GetData(); Info.actionType = XR_ACTION_TYPE_POSE_INPUT; FCStringAnsi::Strcpy(Info.localizedActionName, XR_MAX_ACTION_NAME_SIZE, InName); FCStringAnsi::Strcat(Info.localizedActionName, XR_MAX_ACTION_NAME_SIZE, " Pose"); FilterActionName(Info.localizedActionName, Info.actionName); XR_ENSURE(xrCreateAction(HandInteractionActionSet, &Info, &PoseAction)); } void FPICOOpenXRHandInteraction::FHandInteraction::AddTrackedDevices(IOpenXRHMD* HMD) { if (HMD) { DeviceId = HMD->AddTrackedDevice(PoseAction, SubPath); } } void FPICOOpenXRHandInteraction::FHandInteraction::GetSuggestedBindings(EHandController Type, TArray& OutSuggestedBindings) { switch (Type) { case EHandController::LeftGrip: case EHandController::RightGrip: OutSuggestedBindings.Add(XrActionSuggestedBinding{ PoseAction, SubPath / FString("input/grip/pose") }); break; case EHandController::LeftAim: case EHandController::RightAim: OutSuggestedBindings.Add(XrActionSuggestedBinding{ PoseAction, SubPath / FString("input/aim/pose") }); break; case EHandController::LeftPinch: case EHandController::RightPinch: OutSuggestedBindings.Add(XrActionSuggestedBinding{ PoseAction, SubPath / FString("input/pinch_ext/pose") }); break; case EHandController::LeftPoke: case EHandController::RightPoke: OutSuggestedBindings.Add(XrActionSuggestedBinding{ PoseAction, SubPath / FString("input/poke_ext/pose") }); break; default: break; } } FPICOOpenXRHandInteraction::FPICOOpenXRHandInteraction(const TSharedRef& InMessageHandler) : MessageHandler(InMessageHandler) , DeviceIndex(0) , HandInteractionActionSet(XR_NULL_HANDLE) , HandInteractions() , InputMappingContextToPriorityMap() , EnhancedActions() , SubactionPaths() { // Register modular feature manually IModularFeatures::Get().RegisterModularFeature(IMotionController::GetModularFeatureName(), static_cast(this)); IModularFeatures::Get().RegisterModularFeature(IOpenXRExtensionPlugin::GetModularFeatureName(), static_cast(this)); AddKeys(); // We're implicitly requiring that the OpenXRPlugin has been loaded and // initialized at this point. if (!IOpenXRHMDModule::Get().IsAvailable()) { UE_LOG(LogPICOOpenXRHandInteraction, Error, TEXT("Error - OpenXRHMDPlugin isn't available")); } MotionSourceToEControllerHandMap.Add(TEXT("LeftAim"), EHandController::LeftAim); MotionSourceToEControllerHandMap.Add(TEXT("LeftGrip"), EHandController::LeftGrip); MotionSourceToEControllerHandMap.Add(TEXT("LeftPinch"), EHandController::LeftPinch); MotionSourceToEControllerHandMap.Add(TEXT("LeftPoke"), EHandController::LeftPoke); MotionSourceToEControllerHandMap.Add(TEXT("RightAim"), EHandController::RightAim); MotionSourceToEControllerHandMap.Add(TEXT("RightGrip"), EHandController::RightGrip); MotionSourceToEControllerHandMap.Add(TEXT("RightPoke"), EHandController::RightPoke); MotionSourceToEControllerHandMap.Add(TEXT("RightPinch"), EHandController::RightPinch); } FPICOOpenXRHandInteraction::~FPICOOpenXRHandInteraction() { // Unregister modular feature manually IModularFeatures::Get().UnregisterModularFeature(IMotionController::GetModularFeatureName(), static_cast(this)); IModularFeatures::Get().UnregisterModularFeature(IOpenXRExtensionPlugin::GetModularFeatureName(), static_cast(this)); } bool FPICOOpenXRHandInteraction::GetRequiredExtensions(TArray& OutExtensions) { OutExtensions.Add("XR_EXT_hand_interaction"); return true; } void FPICOOpenXRHandInteraction::PostCreateInstance(XrInstance InInstance) { Instance = InInstance; } void FPICOOpenXRHandInteraction::OnDestroySession(XrSession InSession) { HandInteractions.Reset(); EnhancedActions.Reset(); SubactionPaths.Reset(); bActionsAttached = false; } void FPICOOpenXRHandInteraction::AttachActionSets(TSet& OutActionSets) { check(Instance != XR_NULL_HANDLE); if (GEngine && GEngine->XRSystem.IsValid()) { XRTrackingSystem = GEngine->XRSystem.Get(); OpenXRHMD = XRTrackingSystem->GetIOpenXRHMD(); } if (HandInteractionActionSet) { XR_ENSURE(xrDestroyActionSet(HandInteractionActionSet)); } SubactionPaths.Add(FOpenXRPath("/user/hand/left")); SubactionPaths.Add(FOpenXRPath("/user/hand/right")); XrActionSetCreateInfo CreateInfo = { XR_TYPE_ACTION_SET_CREATE_INFO }; FCStringAnsi::Strcpy(CreateInfo.actionSetName, XR_MAX_ACTION_SET_NAME_SIZE, "handinteractions"); FCStringAnsi::Strcpy(CreateInfo.localizedActionSetName, XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE, "Hand Interactions"); XR_ENSURE(xrCreateActionSet(Instance, &CreateInfo, &HandInteractionActionSet)); HandInteractions.Add(EHandController::LeftAim, FHandInteraction(HandInteractionActionSet, SubactionPaths[0], "LeftAim")); HandInteractions.Add(EHandController::LeftGrip, FHandInteraction(HandInteractionActionSet, SubactionPaths[0], "LeftGrip")); HandInteractions.Add(EHandController::LeftPinch, FHandInteraction(HandInteractionActionSet, SubactionPaths[0], "LeftPinch")); HandInteractions.Add(EHandController::LeftPoke, FHandInteraction(HandInteractionActionSet, SubactionPaths[0], "LeftPoke")); HandInteractions.Add(EHandController::RightAim, FHandInteraction(HandInteractionActionSet, SubactionPaths[1], "RightAim")); HandInteractions.Add(EHandController::RightGrip, FHandInteraction(HandInteractionActionSet, SubactionPaths[1], "RightGrip")); HandInteractions.Add(EHandController::RightPinch, FHandInteraction(HandInteractionActionSet, SubactionPaths[1], "RightPinch")); HandInteractions.Add(EHandController::RightPoke, FHandInteraction(HandInteractionActionSet, SubactionPaths[1], "RightPoke")); TArray Bindings; for (TPair Tracker : HandInteractions) { Tracker.Value.GetSuggestedBindings(Tracker.Key, Bindings); } // Attempt to load the default input config from the OpenXR input settings. const UEnhancedInputDeveloperSettings* InputSettings = GetDefault(); if (InputSettings && InputSettings->bEnableDefaultMappingContexts) { for (const auto& Context : InputSettings->DefaultMappingContexts) { if (Context.InputMappingContext) { TStrongObjectPtr Obj(Context.InputMappingContext.LoadSynchronous()); InputMappingContextToPriorityMap.Add(Obj, Context.Priority); } else { UE_LOG(LogHMD, Warning, TEXT("Default Mapping Contexts contains an Input Mapping Context set to \"None\", ignoring while building OpenXR actions.")); } } } if (!InputMappingContextToPriorityMap.IsEmpty() && !bActionsAttached) { for (const auto& MappingContext : InputMappingContextToPriorityMap) { TMap ActionMap; for (const FEnhancedActionKeyMapping& Mapping : MappingContext.Key->GetMappings()) { if (!Mapping.Action) { continue; } FString InputKey = Mapping.Key.ToString().ToLower(); XrPath Path = XR_NULL_PATH; FString SubRole = InputKey.Contains("left") ? "left" : "right"; FString Behavior = InputKey.Contains("axis") ? "value" : "ready_ext"; if (InputKey.Contains("pinch")) { Path = FOpenXRPath("/user/hand/" + SubRole + "/input/pinch_ext/" + Behavior); } else if (InputKey.Contains("aim")) { Path = FOpenXRPath("/user/hand/" + SubRole + "/input/aim_activate_ext/" + Behavior); } else if (InputKey.Contains("grasp")) { Path = FOpenXRPath("/user/hand/" + SubRole + "/input/grasp_ext/" + Behavior); } if (Path == XR_NULL_PATH) { continue; } FName ActionName = Mapping.Action->GetFName(); int32& ActionIndex = ActionMap.FindOrAdd(ActionName, INDEX_NONE); if (ActionIndex == INDEX_NONE) { XrActionType ActionType; switch (Mapping.Action->ValueType) { case EInputActionValueType::Axis1D: ActionType = XrActionType::XR_ACTION_TYPE_FLOAT_INPUT; break; case EInputActionValueType::Boolean: ActionType = XrActionType::XR_ACTION_TYPE_BOOLEAN_INPUT; break; } ActionIndex = EnhancedActions.Emplace(HandInteractionActionSet, ActionType, ActionName, Mapping.Action, SubactionPaths); } XrPath Key = InputKey.Contains("left") ? SubactionPaths[0] : SubactionPaths[1]; for (UInputTrigger* Trigger : Mapping.Triggers) { TObjectPtr Ptr = Trigger; EnhancedActions[ActionIndex].Triggers.AddUnique(Key, Ptr); } for (UInputModifier* Modifier : Mapping.Modifiers) { TObjectPtr Ptr = Modifier; EnhancedActions[ActionIndex].Modifiers.AddUnique(Key, Ptr); } Bindings.Add(XrActionSuggestedBinding{ EnhancedActions[ActionIndex].Handle, FOpenXRPath(Path) }); } } } XrInteractionProfileSuggestedBinding InteractionProfile = { XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING }; InteractionProfile.interactionProfile = FOpenXRPath("/interaction_profiles/ext/hand_interaction_ext"); InteractionProfile.countSuggestedBindings = Bindings.Num(); InteractionProfile.suggestedBindings = Bindings.GetData(); XR_ENSURE(xrSuggestInteractionProfileBindings(Instance, &InteractionProfile)); for (TPair& Tracker : HandInteractions) { Tracker.Value.AddTrackedDevices(OpenXRHMD); } OutActionSets.Add(HandInteractionActionSet); bActionsAttached = true; } void FPICOOpenXRHandInteraction::GetActiveActionSetsForSync(TArray& OutActiveSets) { XrActiveActionSet ActiveTrackerSet{ HandInteractionActionSet, XR_NULL_PATH }; OutActiveSets.Add(ActiveTrackerSet); } bool FPICOOpenXRHandInteraction::GetControllerOrientationAndPosition(const int32 ControllerIndex, const FName MotionSource, FRotator& OutOrientation, FVector& OutPosition, float WorldToMetersScale) const { if (!bActionsAttached || XRTrackingSystem == nullptr) { return false; } if (MotionSource == "AnyHand") { return GetControllerOrientationAndPosition(ControllerIndex, "LeftGrip", OutOrientation, OutPosition, WorldToMetersScale) || GetControllerOrientationAndPosition(ControllerIndex, "RightGrip", OutOrientation, OutPosition, WorldToMetersScale); } else if (MotionSource == "Left") { return GetControllerOrientationAndPosition(ControllerIndex, "LeftGrip", OutOrientation, OutPosition, WorldToMetersScale); } else if (MotionSource == "Right") { return GetControllerOrientationAndPosition(ControllerIndex, "RightGrip", OutOrientation, OutPosition, WorldToMetersScale); } if (GetControllerTrackingStatus(ControllerIndex, MotionSource) == ETrackingStatus::Tracked) { FQuat Orientation; bool Success = XRTrackingSystem->GetCurrentPose(GetDeviceIDForMotionSource(MotionSource), Orientation, OutPosition); OutOrientation = FRotator(Orientation); return Success; } return false; } bool FPICOOpenXRHandInteraction::GetControllerOrientationAndPosition(const int32 ControllerIndex, const FName MotionSource, FRotator& OutOrientation, FVector& OutPosition, bool& OutbProvidedLinearVelocity, FVector& OutLinearVelocity, bool& OutbProvidedAngularVelocity, FVector& OutAngularVelocityAsAxisAndLength, bool& OutbProvidedLinearAcceleration, FVector& OutLinearAcceleration, float WorldToMetersScale) const { // FTimespan initializes to 0 and GetControllerOrientationAndPositionForTime with time 0 will return the latest data. FTimespan Time; bool OutTimeWasUsed = false; return GetControllerOrientationAndPositionForTime(ControllerIndex, MotionSource, Time, OutTimeWasUsed, OutOrientation, OutPosition, OutbProvidedLinearVelocity, OutLinearVelocity, OutbProvidedAngularVelocity, OutAngularVelocityAsAxisAndLength, OutbProvidedLinearAcceleration, OutLinearAcceleration, WorldToMetersScale); } bool FPICOOpenXRHandInteraction::GetControllerOrientationAndPositionForTime(const int32 ControllerIndex, const FName MotionSource, FTimespan Time, bool& OutTimeWasUsed, FRotator& OutOrientation, FVector& OutPosition, bool& OutbProvidedLinearVelocity, FVector& OutLinearVelocity, bool& OutbProvidedAngularVelocity, FVector& OutAngularVelocityAsAxisAndLength, bool& OutbProvidedLinearAcceleration, FVector& OutLinearAcceleration, float WorldToMetersScale) const { if (MotionSource == "AnyHand") { return GetControllerOrientationAndPositionForTime(ControllerIndex, "LeftGrip", Time, OutTimeWasUsed, OutOrientation, OutPosition, OutbProvidedLinearVelocity, OutLinearVelocity, OutbProvidedAngularVelocity, OutAngularVelocityAsAxisAndLength, OutbProvidedLinearAcceleration, OutLinearAcceleration, WorldToMetersScale) || GetControllerOrientationAndPositionForTime(ControllerIndex, "RightGrip", Time, OutTimeWasUsed, OutOrientation, OutPosition, OutbProvidedLinearVelocity, OutLinearVelocity, OutbProvidedAngularVelocity, OutAngularVelocityAsAxisAndLength, OutbProvidedLinearAcceleration, OutLinearAcceleration, WorldToMetersScale); } else if (MotionSource == "Left") { return GetControllerOrientationAndPositionForTime(ControllerIndex, "LeftGrip", Time, OutTimeWasUsed, OutOrientation, OutPosition, OutbProvidedLinearVelocity, OutLinearVelocity, OutbProvidedAngularVelocity, OutAngularVelocityAsAxisAndLength, OutbProvidedLinearAcceleration, OutLinearAcceleration, WorldToMetersScale); } else if (MotionSource == "Right") { return GetControllerOrientationAndPositionForTime(ControllerIndex, "RightGrip", Time, OutTimeWasUsed, OutOrientation, OutPosition, OutbProvidedLinearVelocity, OutLinearVelocity, OutbProvidedAngularVelocity, OutAngularVelocityAsAxisAndLength, OutbProvidedLinearAcceleration, OutLinearAcceleration, WorldToMetersScale); } if (OpenXRHMD && GetControllerTrackingStatus(ControllerIndex, MotionSource) == ETrackingStatus::Tracked) { FQuat Orientation; bool Success = OpenXRHMD->GetPoseForTime(GetDeviceIDForMotionSource(MotionSource), Time, OutTimeWasUsed, Orientation, OutPosition, OutbProvidedLinearVelocity, OutLinearVelocity, OutbProvidedAngularVelocity, OutAngularVelocityAsAxisAndLength, OutbProvidedLinearAcceleration, OutLinearAcceleration, WorldToMetersScale); OutOrientation = FRotator(Orientation); return Success; } return false; } ETrackingStatus FPICOOpenXRHandInteraction::GetControllerTrackingStatus(const int32 ControllerIndex, const FName MotionSource) const { if (MotionSource == "AnyHand") { if (GetControllerTrackingStatus(ControllerIndex, "LeftGrip") == ETrackingStatus::Tracked || GetControllerTrackingStatus(ControllerIndex, "RightGrip") == ETrackingStatus::Tracked) { return ETrackingStatus::Tracked; } else { return ETrackingStatus::NotTracked; } } else if (MotionSource == "Left") { return GetControllerTrackingStatus(ControllerIndex, "LeftGrip"); } else if (MotionSource == "Right") { return GetControllerTrackingStatus(ControllerIndex, "RightGrip"); } if (!bActionsAttached || OpenXRHMD == nullptr) { return ETrackingStatus::NotTracked; } XrSession Session = OpenXRHMD->GetSession(); if (Session == XR_NULL_HANDLE) { return ETrackingStatus::NotTracked; } if (!(ControllerIndex == DeviceIndex && MotionSourceToEControllerHandMap.Contains(MotionSource))) { return ETrackingStatus::NotTracked; } const EHandController DeviceHand = MotionSourceToEControllerHandMap.FindChecked(MotionSource); const FHandInteraction* Tracker = HandInteractions.Find(DeviceHand); if (!Tracker) { return ETrackingStatus::NotTracked; } XrActionStateGetInfo Info = { XR_TYPE_ACTION_STATE_GET_INFO }; Info.action = Tracker->PoseAction; Info.subactionPath = XR_NULL_PATH; XrActionStatePose State = { XR_TYPE_ACTION_STATE_POSE }; if (!XR_ENSURE(xrGetActionStatePose(Session, &Info, &State))) { return ETrackingStatus::NotTracked; } return State.isActive ? ETrackingStatus::Tracked : ETrackingStatus::NotTracked; } FName FPICOOpenXRHandInteraction::GetMotionControllerDeviceTypeName() const { const static FName DefaultName(TEXT("PICOOpenXRHandInteraction")); return DefaultName; } void FPICOOpenXRHandInteraction::EnumerateSources(TArray& SourcesOut) const { check(IsInGameThread()); TArray Sources; MotionSourceToEControllerHandMap.GenerateKeyArray(Sources); SourcesOut.Append(Sources); } void FPICOOpenXRHandInteraction::Tick(float DeltaTime) { } void FPICOOpenXRHandInteraction::SendControllerEvents() { if (!bActionsAttached || OpenXRHMD == nullptr) { return; } if (!OpenXRHMD->IsFocused()) { return; } XrSession Session = OpenXRHMD->GetSession(); for (XrPath Subaction : SubactionPaths) { XrInteractionProfileState Profile; Profile.type = XR_TYPE_INTERACTION_PROFILE_STATE; Profile.next = nullptr; XR_ENSURE(xrGetCurrentInteractionProfile(Session, Subaction, &Profile)); TPair Key(Profile.interactionProfile, Subaction); for (FHandInteractionAction& Action : EnhancedActions) { const UInputAction* InputAction = Action.Object; XrActionStateGetInfo GetInfo; GetInfo.type = XR_TYPE_ACTION_STATE_GET_INFO; GetInfo.next = nullptr; GetInfo.subactionPath = Subaction; GetInfo.action = Action.Handle; FInputActionValue InputValue; switch (Action.Type) { case XR_ACTION_TYPE_BOOLEAN_INPUT: { XrActionStateBoolean State; State.type = XR_TYPE_ACTION_STATE_BOOLEAN; State.next = nullptr; XrResult Result = xrGetActionStateBoolean(Session, &GetInfo, &State); if (XR_SUCCEEDED(Result)) { InputValue = FInputActionValue(State.isActive ? (bool)State.currentState : false); } else { continue; } } break; case XR_ACTION_TYPE_FLOAT_INPUT: { XrActionStateFloat State; State.type = XR_TYPE_ACTION_STATE_FLOAT; State.next = nullptr; XrResult Result = xrGetActionStateFloat(Session, &GetInfo, &State); if (XR_SUCCEEDED(Result)) { InputValue = FInputActionValue(State.isActive ? State.currentState : 0.0f); } else { continue; } } break; default: // Other action types are currently unsupported. continue; } TArray> Triggers; Action.Triggers.MultiFind(Subaction, Triggers, false); TArray> Modifiers; Action.Modifiers.MultiFind(Subaction, Modifiers, false); auto InjectSubsystemInput = [InputAction, InputValue, Triggers, Modifiers](IEnhancedInputSubsystemInterface* Subsystem) { if (Subsystem) { Subsystem->InjectInputForAction(InputAction, InputValue, Modifiers, Triggers); } }; IEnhancedInputModule::Get().GetLibrary()->ForEachSubsystem(InjectSubsystemInput); #if WITH_EDITOR if (GEditor) { // UEnhancedInputLibrary::ForEachSubsystem only enumerates runtime subsystems. InjectSubsystemInput(GEditor->GetEditorSubsystem()); } #endif } } } void FPICOOpenXRHandInteraction::SetMessageHandler(const TSharedRef& InMessageHandler) { MessageHandler = InMessageHandler; } bool FPICOOpenXRHandInteraction::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { return false; } void FPICOOpenXRHandInteraction::SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) { } void FPICOOpenXRHandInteraction::SetChannelValues(int32 ControllerId, const FForceFeedbackValues& values) { } bool FPICOOpenXRHandInteraction::IsGamepadAttached() const { return false; } bool FPICOOpenXRHandInteraction::SetEnhancedInputUserSettings(TObjectPtr InputSettings) { if (bActionsAttached) { UE_LOG(LogHMD, Error, TEXT("Attempted to attach a set of enhanced user input settings when actions are already attached for the current session.")); return false; } const TSet>& MappingContexts = InputSettings->GetRegisteredInputMappingContexts(); for (const auto& Context : MappingContexts) { InputMappingContextToPriorityMap.Add(TStrongObjectPtr(Context), 0); } return true; } bool FPICOOpenXRHandInteraction::SetPlayerMappableInputConfig(TObjectPtr InputConfig) { if (bActionsAttached) { UE_LOG(LogHMD, Error, TEXT("Attempted to attach an input config while one is already attached for the current session.")); return false; } TSet> MappingContexts; InputConfig->GetMappingContexts().GetKeys(MappingContexts); return AttachInputMappingContexts(MappingContexts); } bool FPICOOpenXRHandInteraction::AttachInputMappingContexts(const TSet>& MappingContexts) { if (bActionsAttached) { UE_LOG(LogHMD, Error, TEXT("Attempted to attach input mapping contexts when action sets are already attached for the current session.")); return false; } for (const auto& Context : MappingContexts) { InputMappingContextToPriorityMap.Add(TStrongObjectPtr(Context), 0); } return true; } void FPICOOpenXRHandInteraction::AddKeys() { EKeys::AddMenuCategoryDisplayInfo("PICOHI", LOCTEXT("PICOHISubCategory", "PICO Hand Interaction Controller"), TEXT("GraphEditor.PadEvent_16x")); EKeys::AddKey(FKeyDetails(PICOHI_Left_Pinch_Axis, LOCTEXT("PICOHI_Left_Pinch_Axis", "PICO Hand (L) Pinch Axis"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); EKeys::AddKey(FKeyDetails(PICOHI_Left_Pinch_Click, LOCTEXT("PICOHI_Left_Pinch_Click", "PICO Hand (L) Pinch Ready"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); EKeys::AddKey(FKeyDetails(PICOHI_Left_Aim_Axis, LOCTEXT("PICOHI_Left_Aim_Axis", "PICO Hand (L) Aim Axis"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); EKeys::AddKey(FKeyDetails(PICOHI_Left_Aim_Click, LOCTEXT("PICOHI_Left_Aim_Click", "PICO Hand (L) Aim Ready"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); EKeys::AddKey(FKeyDetails(PICOHI_Left_Grasp_Axis, LOCTEXT("PICOHI_Left_Grasp_Axis", "PICO Hand (L) Grasp Axis"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); EKeys::AddKey(FKeyDetails(PICOHI_Left_Grasp_Click, LOCTEXT("PICOHI_Left_Grasp_Click", "PICO Hand (L) Grasp Ready"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); EKeys::AddKey(FKeyDetails(PICOHI_Right_Pinch_Axis, LOCTEXT("PICOHI_Right_Pinch_Axis", "PICO Hand (R) Pinch Axis"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); EKeys::AddKey(FKeyDetails(PICOHI_Right_Pinch_Click, LOCTEXT("PICOHI_Right_Pinch_Click", "PICO Hand (R) Pinch Ready"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); EKeys::AddKey(FKeyDetails(PICOHI_Right_Aim_Axis, LOCTEXT("PICOHI_Right_Aim_Axis", "PICO Hand (R) Aim Axis"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); EKeys::AddKey(FKeyDetails(PICOHI_Right_Aim_Click, LOCTEXT("PICOHI_Right_Aim_Click", "PICO Hand (R) Aim Ready"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); EKeys::AddKey(FKeyDetails(PICOHI_Right_Grasp_Axis, LOCTEXT("PICOHI_Right_Grasp_Axis", "PICO Hand (R) Grasp Axis"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); EKeys::AddKey(FKeyDetails(PICOHI_Right_Grasp_Click, LOCTEXT("PICOHI_Right_Grasp_Click", "PICO Hand (R) Grasp Ready"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "PICOHI")); } XrAction FPICOOpenXRHandInteraction::GetActionForMotionSource(FName MotionSource) const { const EHandController* Hand = MotionSourceToEControllerHandMap.Find(MotionSource); if (Hand == nullptr) { return XR_NULL_HANDLE; } const FHandInteraction& Tracker = HandInteractions[*Hand]; return Tracker.PoseAction; } int32 FPICOOpenXRHandInteraction::GetDeviceIDForMotionSource(FName MotionSource) const { const FHandInteraction& Tracker = HandInteractions[MotionSourceToEControllerHandMap.FindChecked(MotionSource)]; return Tracker.DeviceId; } XrPath FPICOOpenXRHandInteraction::GetRolePathForMotionSource(FName MotionSource) const { const FHandInteraction& Tracker = HandInteractions[MotionSourceToEControllerHandMap.FindChecked(MotionSource)]; return Tracker.SubPath; } #undef LOCTEXT_NAMESPACE FPICOOpenXRHandInteraction::FHandInteractionAction::FHandInteractionAction(XrActionSet InActionSet, XrActionType InActionType, const FName& InName, const TObjectPtr& InObject, const TArray& InSubactionPaths, IOpenXRHMD* OpenXRHMD) : Set(InActionSet) , Type(InActionType) , Name(InName) , Handle(XR_NULL_HANDLE) , Object(InObject) { char ActionName[NAME_SIZE]; Name.GetPlainANSIString(ActionName); XrActionCreateInfo Info; Info.type = XR_TYPE_ACTION_CREATE_INFO; Info.next = nullptr; FilterActionName(ActionName, Info.actionName); Info.actionType = Type; Info.countSubactionPaths = InSubactionPaths.Num(); Info.subactionPaths = InSubactionPaths.GetData(); FCStringAnsi::Strcpy(Info.localizedActionName, XR_MAX_LOCALIZED_ACTION_NAME_SIZE, ActionName); XR_ENSURE(xrCreateAction(Set, &Info, &Handle)); }