October3d55/M/PICOOpenXR/Source/PICOOpenXRHandInteraction/Private/PICOOpenXRHandInteraction.cpp

746 lines
29 KiB
C++

// 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"
#include "PICOOpenXRRuntimeSettings.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<FGenericApplicationMessageHandler> DummyMessageHandler(new FGenericApplicationMessageHandler());
CreateInputDevice(DummyMessageHandler.ToSharedRef());
}
virtual void ShutdownModule() override
{
IPICOOpenXRHandInteractionModule::ShutdownModule();
}
virtual TSharedPtr<class IInputDevice> CreateInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) override
{
if (!InputDevice.IsValid())
{
TSharedPtr<FPICOOpenXRHandInteraction> ViveTrackingInputDevice(new FPICOOpenXRHandInteraction(InMessageHandler));
InputDevice = ViveTrackingInputDevice;
return InputDevice;
}
else
{
InputDevice.Get()->SetMessageHandler(InMessageHandler);
return InputDevice;
}
return nullptr;
}
virtual TSharedPtr<IInputDevice> GetInputDevice() override
{
if (!InputDevice.IsValid())
{
CreateInputDevice(FSlateApplication::Get().GetPlatformApplication()->GetMessageHandler());
}
return InputDevice;
}
private:
TSharedPtr<FPICOOpenXRHandInteraction> 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<XrPath> 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<XrActionSuggestedBinding>& 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<FGenericApplicationMessageHandler>& InMessageHandler)
: MessageHandler(InMessageHandler)
, DeviceIndex(0)
, HandInteractionActionSet(XR_NULL_HANDLE)
, HandInteractions()
, InputMappingContextToPriorityMap()
, EnhancedActions()
, SubactionPaths()
{
// Register modular feature manually
IModularFeatures::Get().RegisterModularFeature(IMotionController::GetModularFeatureName(), static_cast<IMotionController*>(this));
IModularFeatures::Get().RegisterModularFeature(IOpenXRExtensionPlugin::GetModularFeatureName(), static_cast<IOpenXRExtensionPlugin*>(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<IMotionController*>(this));
IModularFeatures::Get().UnregisterModularFeature(IOpenXRExtensionPlugin::GetModularFeatureName(), static_cast<IOpenXRExtensionPlugin*>(this));
}
bool FPICOOpenXRHandInteraction::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
if (UPICOOpenXRRuntimeSettings::GetBoolConfigByKey("bIsHandTrackingUsed"))
{
OutExtensions.Add("XR_EXT_hand_interaction");
return true;
}
return false;
}
void FPICOOpenXRHandInteraction::PostCreateInstance(XrInstance InInstance)
{
Instance = InInstance;
}
void FPICOOpenXRHandInteraction::OnDestroySession(XrSession InSession)
{
HandInteractions.Reset();
EnhancedActions.Reset();
SubactionPaths.Reset();
bActionsAttached = false;
}
void FPICOOpenXRHandInteraction::AttachActionSets(TSet<XrActionSet>& 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<XrActionSuggestedBinding> Bindings;
for (TPair<EHandController, FHandInteraction> Tracker : HandInteractions)
{
Tracker.Value.GetSuggestedBindings(Tracker.Key, Bindings);
}
// Attempt to load the default input config from the OpenXR input settings.
const UEnhancedInputDeveloperSettings* InputSettings = GetDefault<UEnhancedInputDeveloperSettings>();
if (InputSettings && InputSettings->bEnableDefaultMappingContexts)
{
for (const auto& Context : InputSettings->DefaultMappingContexts)
{
if (Context.InputMappingContext)
{
TStrongObjectPtr<const UInputMappingContext> 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<FName, int32> 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<UInputTrigger> Ptr = Trigger;
EnhancedActions[ActionIndex].Triggers.AddUnique(Key, Ptr);
}
for (UInputModifier* Modifier : Mapping.Modifiers)
{
TObjectPtr<UInputModifier> 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<EHandController, FHandInteraction>& Tracker : HandInteractions)
{
Tracker.Value.AddTrackedDevices(OpenXRHMD);
}
OutActionSets.Add(HandInteractionActionSet);
bActionsAttached = true;
}
void FPICOOpenXRHandInteraction::GetActiveActionSetsForSync(TArray<XrActiveActionSet>& 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<FMotionControllerSource>& SourcesOut) const
{
check(IsInGameThread());
TArray<FName> 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<XrPath, XrPath> 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<TObjectPtr<UInputTrigger>> Triggers;
Action.Triggers.MultiFind(Subaction, Triggers, false);
TArray<TObjectPtr<UInputModifier>> 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<UEnhancedInputEditorSubsystem>());
}
#endif
}
}
}
void FPICOOpenXRHandInteraction::SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& 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<class UEnhancedInputUserSettings> 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<TObjectPtr<const UInputMappingContext>>& MappingContexts = InputSettings->GetRegisteredInputMappingContexts();
for (const auto& Context : MappingContexts)
{
InputMappingContextToPriorityMap.Add(TStrongObjectPtr<const UInputMappingContext>(Context), 0);
}
return true;
}
bool FPICOOpenXRHandInteraction::AttachInputMappingContexts(const TSet<TObjectPtr<UInputMappingContext>>& 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<UInputMappingContext>(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<const UInputAction>& InObject, const TArray<XrPath>& 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));
}