Android build settings + metaxr

This commit is contained in:
2025-05-14 14:00:02 +03:00
parent 6a2bb7475e
commit d5aa21f55c
594 changed files with 200530 additions and 2 deletions

View File

@@ -0,0 +1,122 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// A class to render the currently connected controller.
// Similar to how hands are tracked.
#include "OculusXRControllerComponent.h"
#include "OculusXRInput.h"
#include "Components/StaticMeshComponent.h"
#include "OculusXRHandTracking.h"
#include <OculusXRInputModule.h>
UOculusXRControllerComponent::UOculusXRControllerComponent()
: Super()
{
_meshLoadingState = MeshLoadingState::None;
PrimaryComponentTick.bCanEverTick = true;
}
void UOculusXRControllerComponent::BeginPlay()
{
Super::BeginPlay();
}
void UOculusXRControllerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// If we're in a capsense mode, we need to offset the controller position so that it's correct / consistent with the hand position.
if (_cachedControllerHandType != OculusXRInput::FOculusHandTracking::ControllerDrivenHandType)
{
_cachedControllerHandType = OculusXRInput::FOculusHandTracking::ControllerDrivenHandType;
const FVector positionOffset = PositionOffsets[static_cast<int>(SkeletonType)][static_cast<int>(_cachedControllerHandType)];
const FVector rotationOffset = RotationOffsets[static_cast<int>(SkeletonType)][static_cast<int>(_cachedControllerHandType)];
SetRelativeLocation(positionOffset);
SetRelativeRotation(FQuat::MakeFromEuler(rotationOffset));
}
bool isHandTrackingEnabled = UOculusXRInputFunctionLibrary::IsHandTrackingEnabled();
bool shouldHide = isHandTrackingEnabled && !(RenderWhenUsingControllerDrivenHands && OculusXRInput::FOculusHandTracking::ControllerDrivenHandType == EOculusXRControllerDrivenHandPoseTypes::Controller);
if (shouldHide && !bHiddenInGame)
{
SetHiddenInGame(true, false);
}
if (!shouldHide && bHiddenInGame)
{
SetHiddenInGame(false, false);
}
if (_meshLoadingState == MeshLoadingState::None || _controllerType != GetControllerType())
{
InitializeMesh();
}
}
EOculusXRControllerType UOculusXRControllerComponent::GetControllerType()
{
EControllerHand controllerHand = EControllerHand::AnyHand;
if (SkeletonType == EOculusXRSide::Left)
{
controllerHand = EControllerHand::Left;
}
else if (SkeletonType == EOculusXRSide::Right)
{
controllerHand = EControllerHand::Right;
}
return UOculusXRFunctionLibrary::GetControllerType(controllerHand);
}
void UOculusXRControllerComponent::InitializeMesh()
{
if (_runtimeMesh != nullptr)
{
SetStaticMesh(nullptr);
_streamableManager.Unload(_runtimeMeshPath);
_runtimeMesh = nullptr;
}
auto left_controller_path = TEXT("none");
auto right_controller_path = TEXT("none");
_controllerType = GetControllerType();
switch (_controllerType)
{
case EOculusXRControllerType::MetaQuestTouch:
left_controller_path = TEXT("/Script/Engine.StaticMesh'/OculusXR/Meshes/LeftTouchForQuest2.LeftTouchForQuest2'");
right_controller_path = TEXT("/Script/Engine.StaticMesh'/OculusXR/Meshes/RightTouchForQuest2.RightTouchForQuest2'");
break;
case EOculusXRControllerType::MetaQuestTouchPlus:
// We don't currently have a model for the touch plus controller, default to the touch pro.
case EOculusXRControllerType::MetaQuestTouchPro:
left_controller_path = TEXT("/Script/Engine.StaticMesh'/OculusXR/Meshes/LeftMetaQuestTouchPro.LeftMetaQuestTouchPro'");
right_controller_path = TEXT("/Script/Engine.StaticMesh'/OculusXR/Meshes/RightMetaQuestTouchPro.RightMetaQuestTouchPro'");
break;
case EOculusXRControllerType::None:
case EOculusXRControllerType::Unknown:
default:
return;
}
auto controllerPath = left_controller_path;
if (SkeletonType == EOculusXRSide::Right)
{
controllerPath = right_controller_path;
}
_runtimeMeshPath = FSoftObjectPath(controllerPath);
_loadAssetHandle = _streamableManager.RequestAsyncLoad(
_runtimeMeshPath,
FStreamableDelegate::CreateUObject(this, &UOculusXRControllerComponent::MeshLoaded));
}
void UOculusXRControllerComponent::MeshLoaded()
{
if (_loadAssetHandle.IsValid() && _loadAssetHandle.Get()->HasLoadCompleted())
{
_runtimeMesh = reinterpret_cast<UStaticMesh*>(_loadAssetHandle.Get()->GetLoadedAsset());
_meshLoadingState = MeshLoadingState::Loaded;
SetStaticMesh(_runtimeMesh);
}
}

View File

@@ -0,0 +1,23 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRControllerLegacyPoseTransformComponent.h"
#include "OpenXR/OculusXROpenXRUtilities.h"
#include "GameFramework/WorldSettings.h"
UOculusXRControllerLegacyPoseTransformComponent::UOculusXRControllerLegacyPoseTransformComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SetComponentTickEnabled(false);
}
void UOculusXRControllerLegacyPoseTransformComponent::BeginPlay()
{
Super::BeginPlay();
USceneComponent* AttachedParentPtr = GetAttachParent();
if (OculusXR::IsOpenXRSystem() && AttachedParentPtr != nullptr)
{
AttachedParentPtr->AddLocalTransform(FTransform(OculusPoseToGripRotation, OculusPoseToGripPosition * GetWorld()->GetWorldSettings()->WorldToMeters).Inverse());
}
}

View File

@@ -0,0 +1,67 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRControllerTracking.h"
#include "OculusXRHMD.h"
#include "OculusXRInput.h"
#include "Misc/CoreDelegates.h"
#include "IOculusXRInputModule.h"
#include "Haptics/HapticFeedbackEffect_Base.h"
namespace OculusXRInput
{
void FOculusXRControllerTracking::PlayHapticEffect(
class UHapticFeedbackEffect_Base* HapticEffect,
EControllerHand Hand,
EOculusXRHandHapticsLocation Location,
bool bAppend,
float Scale,
bool bLoop)
{
#if OCULUS_INPUT_SUPPORTED_PLATFORMS
TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice());
OculusXRInputModule.Get()->PlayHapticEffect(HapticEffect, Hand, Location, bAppend, Scale, bLoop);
#endif
}
void FOculusXRControllerTracking::PlayAmplitudeEnvelopeHapticEffect(EControllerHand Hand, TArray<uint8>& Amplitudes, int SampleRate)
{
#if OCULUS_INPUT_SUPPORTED_PLATFORMS
TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice());
OculusXRInputModule.Get()->PlayAmplitudeEnvelopeHapticEffect(Hand, Amplitudes.Num(), Amplitudes.GetData(), SampleRate);
#endif
}
void FOculusXRControllerTracking::StopHapticEffect(EControllerHand Hand, EOculusXRHandHapticsLocation Location)
{
#if OCULUS_INPUT_SUPPORTED_PLATFORMS
SetHapticsByValue(0.f, 0.f, Hand, Location);
#endif
}
void FOculusXRControllerTracking::SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location)
{
#if OCULUS_INPUT_SUPPORTED_PLATFORMS
TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice());
OculusXRInputModule.Get()->SetHapticsByValue(Frequency, Amplitude, Hand, Location);
#endif
}
float FOculusXRControllerTracking::GetControllerSampleRateHz(EControllerHand Hand)
{
#if OCULUS_INPUT_SUPPORTED_PLATFORMS
TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice());
return OculusXRInputModule.Get()->GetControllerSampleRateHz(Hand);
#endif
return 0;
}
int FOculusXRControllerTracking::GetMaxHapticDuration(EControllerHand Hand)
{
#if OCULUS_INPUT_SUPPORTED_PLATFORMS
TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice());
return OculusXRInputModule.Get()->GetMaxHapticDuration(Hand);
#endif
return 0;
}
} // namespace OculusXRInput

View File

@@ -0,0 +1,43 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "OculusXRInputFunctionLibrary.h"
#define LOCTEXT_NAMESPACE "OculusXRControllerTracking"
DEFINE_LOG_CATEGORY_STATIC(LogOcXRControllerTracking, Log, All);
//-------------------------------------------------------------------------------------------------
// FOculusXRControllerTracking
//-------------------------------------------------------------------------------------------------
class UHapticFeedbackEffect_Base;
namespace OculusXRInput
{
class FOculusXRControllerTracking
{
public:
static void PlayHapticEffect(
UHapticFeedbackEffect_Base* HapticEffect,
EControllerHand Hand,
EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand,
bool bAppend = false,
float Scale = 1.f,
bool bLoop = false);
static void PlayAmplitudeEnvelopeHapticEffect(EControllerHand Hand, TArray<uint8>& Amplitudes, int SampleRate);
static void StopHapticEffect(EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand);
static void SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand);
static float GetControllerSampleRateHz(EControllerHand Hand);
static int GetMaxHapticDuration(EControllerHand Hand);
};
} // namespace OculusXRInput
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,249 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRHandComponent.h"
#include "OculusXRInput.h"
#include "OculusXRInputModule.h"
#include "OpenXR/OculusXROpenXRUtilities.h"
#include "Engine/SkeletalMesh.h"
#include "Components/InputComponent.h"
#include "Materials/MaterialInterface.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
UOculusXRHandComponent::UOculusXRHandComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.bStartWithTickEnabled = true;
PrimaryComponentTick.TickGroup = TG_PrePhysics;
bHasAuthority = false;
bAutoActivate = true;
bWantsInitializeComponent = true;
for (uint8 BoneIndex = 0; BoneIndex < (uint8)EOculusXRBone::Bone_Max; BoneIndex++)
{
BoneNameMappings.Add((EOculusXRBone)BoneIndex, TEXT(""));
}
}
void UOculusXRHandComponent::BeginPlay()
{
Super::BeginPlay();
// Use custom mesh if a skeletal mesh is already set, else try to load the runtime mesh
if (GetSkinnedAsset())
{
bCustomHandMesh = true;
bSkeletalMeshInitialized = true;
}
else
{
RuntimeSkeletalMesh = NewObject<USkeletalMesh>(this, TEXT("OculusHandMesh"));
InitializeSkeletalMesh();
}
}
void UOculusXRHandComponent::InitializeSkeletalMesh()
{
const FOculusXRInputModule* InputModule = static_cast<FOculusXRInputModule*>(&FOculusXRInputModule::Get());
if (OculusXR::IsOpenXRSystem() && !InputModule->GetHandTrackingOpenXRExtension()->bIsInitialized)
{
return;
}
if (RuntimeSkeletalMesh)
{
if (UOculusXRInputFunctionLibrary::GetHandSkeletalMesh(RuntimeSkeletalMesh, SkeletonType, MeshType))
{
SetSkinnedAssetAndUpdate(RuntimeSkeletalMesh, true);
if (MaterialOverride)
{
SetMaterial(0, MaterialOverride);
}
CachedBaseMaterial = GetMaterial(0);
bSkeletalMeshInitialized = true;
// Initialize physics capsules on the runtime mesh
if (bInitializePhysics)
{
CollisionCapsules = UOculusXRInputFunctionLibrary::InitializeHandPhysics(SkeletonType, this);
}
}
}
}
void UOculusXRHandComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
#if WITH_EDITOR
if (!bSkeletalMeshInitialized && !bCustomHandMesh)
{
InitializeSkeletalMesh();
}
#else
// OpenXR session is created after the hand component is initialized
if (OculusXR::IsOpenXRSystem() && !bSkeletalMeshInitialized && !bCustomHandMesh)
{
InitializeSkeletalMesh();
}
#endif
if (IsInGameThread())
{
// Cache state from the game thread for use on the render thread
const AActor* MyOwner = GetOwner();
bHasAuthority = MyOwner->HasLocalNetOwner();
int i = 0;
}
if (bHasAuthority)
{
bool bHidden = false;
if (UOculusXRInputFunctionLibrary::IsHandTrackingEnabled())
{
// Update Visibility based on Confidence
if (ConfidenceBehavior == EOculusXRConfidenceBehavior::HideActor)
{
EOculusXRTrackingConfidence TrackingConfidence = UOculusXRInputFunctionLibrary::GetTrackingConfidence(SkeletonType);
bHidden |= TrackingConfidence != EOculusXRTrackingConfidence::High;
}
// Update Hand Scale
if (bUpdateHandScale)
{
float NewScale = UOculusXRInputFunctionLibrary::GetHandScale(SkeletonType);
SetRelativeScale3D(FVector(NewScale));
}
// Update Bone Pose Rotations
if (GetSkinnedAsset())
{
UpdateBonePose(SkeletonType);
}
#if OCULUS_INPUT_SUPPORTED_PLATFORMS
// Check for system gesture pressed through player controller
if (APawn* Pawn = Cast<APawn>(GetOwner()))
{
if (APlayerController* PC = Pawn->GetController<APlayerController>())
{
if (PC->WasInputKeyJustPressed(SkeletonType == EOculusXRHandType::HandLeft ? OculusXRInput::FOculusKey::OculusHand_Left_SystemGesture : OculusXRInput::FOculusKey::OculusHand_Right_SystemGesture))
{
SystemGesturePressed();
}
if (PC->WasInputKeyJustReleased(SkeletonType == EOculusXRHandType::HandLeft ? OculusXRInput::FOculusKey::OculusHand_Left_SystemGesture : OculusXRInput::FOculusKey::OculusHand_Right_SystemGesture))
{
SystemGestureReleased();
}
}
}
#endif
}
else
{
bHidden = true;
}
if (bHidden != bHiddenInGame)
{
SetHiddenInGame(bHidden);
for (int32 i = 0; i < CollisionCapsules.Num(); i++)
{
CollisionCapsules[i].Capsule->SetCollisionEnabled(bHidden ? ECollisionEnabled::NoCollision : ECollisionEnabled::QueryAndPhysics);
}
}
}
}
void UOculusXRHandComponent::UpdateBonePose(EOculusXRHandType HandType)
{
FQuat HandRootFixupRotation = HandRootFixupRotationOVR;
if (OculusXR::IsOpenXRSystem())
{
check(HandType == EOculusXRHandType::HandLeft || HandType == EOculusXRHandType::HandRight);
if (HandType == EOculusXRHandType::HandLeft)
{
HandRootFixupRotation = LeftHandRootFixupRotationOpenXR;
}
else if (HandType == EOculusXRHandType::HandRight)
{
HandRootFixupRotation = RightHandRootFixupRotationOpenXR;
}
}
if (bCustomHandMesh)
{
for (auto& BoneElem : BoneNameMappings)
{
// Set Root Bone Rotaiton
if (BoneElem.Key == EOculusXRBone::Wrist_Root)
{
FQuat RootBoneRotation = UOculusXRInputFunctionLibrary::GetBoneRotation(SkeletonType, EOculusXRBone::Wrist_Root);
RootBoneRotation *= HandRootFixupRotation;
RootBoneRotation.Normalize();
BoneSpaceTransforms[0].SetRotation(RootBoneRotation);
}
else
{
// Set Remaing Bone Rotations
int32 BoneIndex = GetSkinnedAsset()->GetRefSkeleton().FindBoneIndex(BoneElem.Value);
if (BoneIndex >= 0)
{
FQuat BoneRotation = UOculusXRInputFunctionLibrary::GetBoneRotation(SkeletonType, (EOculusXRBone)BoneElem.Key);
BoneSpaceTransforms[BoneIndex].SetRotation(BoneRotation);
}
}
}
}
else
{
// Set Root Bone Rotation
FQuat RootBoneRotation = UOculusXRInputFunctionLibrary::GetBoneRotation(SkeletonType, EOculusXRBone::Wrist_Root);
RootBoneRotation *= HandRootFixupRotation;
RootBoneRotation.Normalize();
BoneSpaceTransforms[0].SetRotation(RootBoneRotation);
// Set Remaining Bone Rotations
for (uint32 BoneIndex = 1; BoneIndex < (uint32)GetSkinnedAsset()->GetRefSkeleton().GetNum(); BoneIndex++)
{
FQuat BoneRotation = UOculusXRInputFunctionLibrary::GetBoneRotation(SkeletonType, (EOculusXRBone)BoneIndex);
BoneSpaceTransforms[BoneIndex].SetRotation(BoneRotation);
}
}
MarkRefreshTransformDirty();
}
void UOculusXRHandComponent::SystemGesturePressed()
{
if (SystemGestureBehavior == EOculusXRSystemGestureBehavior::SwapMaterial)
{
if (SystemGestureMaterial)
{
SetMaterial(0, SystemGestureMaterial);
}
else
{
UE_LOG(LogTemp, Log, TEXT("System Gesture Behavior was set to Swap Material but no System Gesture Material was provided!"));
}
}
}
void UOculusXRHandComponent::SystemGestureReleased()
{
if (SystemGestureBehavior == EOculusXRSystemGestureBehavior::SwapMaterial)
{
if (CachedBaseMaterial)
{
SetMaterial(0, CachedBaseMaterial);
}
else
{
UE_LOG(LogTemp, Log, TEXT("System Gesture Behavior was set to Swap Material but no System Gesture Material was provided!"));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,64 @@
// @lint-ignore-every LICENSELINT
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "OculusXRHMDModule.h"
#include "OculusXRInput.h"
#include "Engine/SkeletalMesh.h"
#include "Components/CapsuleComponent.h"
#include "OculusXRInputHandTrackingTypes.h"
#include "OculusXRInputFunctionLibrary.h"
#define LOCTEXT_NAMESPACE "OculusHandTracking"
DEFINE_LOG_CATEGORY_STATIC(LogOcHandTracking, Log, All);
//-------------------------------------------------------------------------------------------------
// FOculusHandTracking
//-------------------------------------------------------------------------------------------------
namespace OculusXRInput
{
class FOculusHandTracking
{
public:
// Oculus Hand Tracking
static FQuat GetBoneRotation(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const EOculusXRBone BoneId);
static float GetHandScale(const int32 ControllerIndex, const EOculusXRHandType DeviceHand);
static EOculusXRTrackingConfidence GetTrackingConfidence(const int32 ControllerIndex, const EOculusXRHandType DeviceHand);
static EOculusXRTrackingConfidence GetFingerTrackingConfidence(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const EOculusHandAxes Finger); // OCULUS STRIKE
static FTransform GetPointerPose(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const float WorldToMeters = 100.f);
static bool IsPointerPoseValid(const int32 ControllerIndex, const EOculusXRHandType DeviceHand);
static bool GetHandSkeletalMesh(USkeletalMesh* HandSkeletalMesh, const EOculusXRHandType SkeletonType, const EOculusXRHandType MeshType, const float WorldToMeters = 100.f);
static TArray<FOculusXRCapsuleCollider> InitializeHandPhysics(const EOculusXRHandType SkeletonType, USkinnedMeshComponent* HandComponent, const float WorldToMeters = 100.f);
static EOculusXRTrackingConfidence ToEOculusXRTrackingConfidence(ovrpTrackingConfidence Confidence);
static bool IsHandTrackingEnabled();
static bool IsHandDominant(const int32 ControllerIndex, const EOculusXRHandType DeviceHand);
static bool IsHandPositionValid(int32 ControllerIndex, EOculusXRHandType DeviceHand);
static void SetControllerDrivenHandPoses(const EOculusXRControllerDrivenHandPoseTypes Type);
// Helper functions
static ovrpBoneId ToOvrBone(EOculusXRBone Bone);
static EHandBoneId ToHandBone(EOculusXRBone Bone);
static FString GetBoneName(uint8 Bone);
static bool FindBoneDisplayName(FText& DisplayName, uint8 Bone);
// Converters for converting from ovr bone space (should match up with ovr avatar)
static FVector OvrBoneVectorToFVector(ovrpVector3f ovrpVector, float WorldToMeters);
static FQuat OvrBoneQuatToFQuat(ovrpQuatf ovrpQuat);
static FVector HandBoneVectorToFVector(XrVector3f XrVector, float WorldToMeters);
static FQuat HandBoneQuatToFQuat(XrQuaternionf XrQuat);
static EOculusXRControllerDrivenHandPoseTypes ControllerDrivenHandType;
private:
// Initializers for runtime hand assets
static void InitializeHandMesh(USkeletalMesh* SkeletalMesh, const ovrpMesh* OvrMesh, const float WorldToMeters);
static void InitializeHandSkeleton(USkeletalMesh* SkeletalMesh, const ovrpSkeleton2* OvrSkeleton, const float WorldToMeters);
static void InitializeHandMeshOpenXR(USkeletalMesh* SkeletalMesh, const TSharedPtr<FHandMesh> Mesh, const float WorldToMeters);
static void InitializeHandSkeletonOpenXR(USkeletalMesh* SkeletalMesh, const TSharedPtr<FHandSkeleton> Skeleton, const float WorldToMeters);
};
} // namespace OculusXRInput
#undef LOCTEXT_NAMESPACE

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,172 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "IOculusXRInputModule.h"
#if OCULUS_INPUT_SUPPORTED_PLATFORMS
#include "OculusXRHMDModule.h"
#include "GenericPlatform/IInputInterface.h"
#include "XRMotionControllerBase.h"
#include "IHapticDevice.h"
#include "OculusXRInputState.h"
#if PLATFORM_SUPPORTS_PRAGMA_PACK
#pragma pack(push, 8)
#endif
#include "OculusXRPluginWrapper.h"
#if PLATFORM_SUPPORTS_PRAGMA_PACK
#pragma pack(pop)
#endif
DEFINE_LOG_CATEGORY_STATIC(LogOcInput, Log, All);
#define OVR_HAP_LOGGING 0
class UHapticFeedbackEffect_Base;
struct FActiveHapticFeedbackEffect;
struct FOculusXRHapticsDesc;
namespace OculusXRInput
{
class IOculusXRInputBase
{
public:
virtual ~IOculusXRInputBase() = default;
virtual void PlayHapticEffect(UHapticFeedbackEffect_Base* HapticEffect,
EControllerHand Hand,
EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand,
bool bAppend = false,
float Scale = 1.f,
bool bLoop = false) = 0;
virtual void PlayAmplitudeEnvelopeHapticEffect(EControllerHand Hand, int SamplesCount, void* Samples, int SampleRate = -1) = 0;
virtual void SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand) = 0;
virtual float GetControllerSampleRateHz(EControllerHand Hand) const = 0;
virtual int GetMaxHapticDuration(EControllerHand Hand) const = 0;
};
//-------------------------------------------------------------------------------------------------
// FOculusXRInput
//-------------------------------------------------------------------------------------------------
class FOculusXRInput : public IInputDevice, public FXRMotionControllerBase, public IHapticDevice
{
friend class FOculusHandTracking;
friend class FOculusXRInputOVR;
public:
/** Constructor that takes an initial message handler that will receive motion controller events */
FOculusXRInput(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler);
/** Clean everything up */
virtual ~FOculusXRInput();
static void PreInit();
/** Loads any settings from the config folder that we need */
static void LoadConfig();
// IInputDevice overrides
virtual void Tick(float DeltaTime) override;
virtual void SendControllerEvents() override;
virtual void SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) override;
virtual bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) override;
virtual void SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) override;
virtual void SetChannelValues(int32 ControllerId, const FForceFeedbackValues& Values) override;
virtual bool SupportsForceFeedback(int32 ControllerId) override;
// IMotionController overrides
virtual FName GetMotionControllerDeviceTypeName() const override;
#if UE_VERSION_OLDER_THAN(5, 3, 0)
virtual bool GetControllerOrientationAndPosition(const int32 ControllerIndex, const EControllerHand DeviceHand, FRotator& OutOrientation, FVector& OutPosition, float WorldToMetersScale) const override;
virtual ETrackingStatus GetControllerTrackingStatus(const int32 ControllerIndex, const EControllerHand DeviceHand) const override;
#endif
virtual bool GetControllerOrientationAndPosition(const int32 ControllerIndex, const FName MotionSource, FRotator& OutOrientation, FVector& OutPosition, float WorldToMetersScale) const override;
virtual ETrackingStatus GetControllerTrackingStatus(const int32 ControllerIndex, const FName MotionSource) const override;
// IHapticDevice overrides
IHapticDevice* GetHapticDevice() override { return (IHapticDevice*)this; }
virtual void SetHapticFeedbackValues(int32 ControllerId, int32 Hand, const FHapticFeedbackValues& Values) override;
void PlayHapticEffect(
UHapticFeedbackEffect_Base* HapticEffect,
EControllerHand Hand,
EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand,
bool bAppend = false,
float Scale = 1.f,
bool bLoop = false);
void PlayAmplitudeEnvelopeHapticEffect(EControllerHand Hand, int SamplesCount, void* Samples, int SampleRate = -1);
void SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand);
virtual void GetHapticFrequencyRange(float& MinFrequency, float& MaxFrequency) const override;
virtual float GetHapticAmplitudeScale() const override;
uint32 GetNumberOfTouchControllers() const;
uint32 GetNumberOfHandControllers() const;
float GetControllerSampleRateHz(EControllerHand Hand);
int GetMaxHapticDuration(EControllerHand Hand);
static void ShutdownXRFunctionLibrary();
private:
/** Applies force feedback settings to the controller */
void UpdateForceFeedback(const FOculusControllerPair& ControllerPair, const EControllerHand Hand);
bool OnControllerButtonPressed(const FOculusButtonState& ButtonState, FPlatformUserId UserId, FInputDeviceId DeviceId, bool IsRepeat);
bool OnControllerButtonReleased(const FOculusButtonState& ButtonState, FPlatformUserId UserId, FInputDeviceId DeviceId, bool IsRepeat);
void SetHapticFeedbackValues(int32 ControllerId, int32 Hand, const FHapticFeedbackValues& Values, TSharedPtr<FOculusXRHapticsDesc> HapticsDesc);
ovrpHapticsLocation GetOVRPHapticsLocation(EOculusXRHandHapticsLocation InLocation);
void ProcessHaptics(const float DeltaTime);
bool GetOvrpHapticsDesc(int Hand);
private:
/** The recipient of motion controller input events */
TSharedPtr<FGenericApplicationMessageHandler> MessageHandler;
/** List of the connected pairs of controllers, with state for each controller device */
TArray<FOculusControllerPair> ControllerPairs;
FOculusRemoteControllerState Remote;
ovrpHapticsDesc OvrpHapticsDesc;
int LocalTrackingSpaceRecenterCount;
// Maintain a cache of resampled raw data so we don't resample it on every play. This is a map of OriginalRawData pointers, used only as a key, to ResampledRawData buffers.
// The values are pointers because the map could be reallocated and we cache raw pointers to the uint8 array data elsewhere.
TMap<const uint8*, TSharedPtr<TArray<uint8>>> ResampledRawDataCache;
TSharedPtr<FActiveHapticFeedbackEffect> ActiveHapticEffect_Left;
TSharedPtr<FActiveHapticFeedbackEffect> ActiveHapticEffect_Right;
TSharedPtr<FOculusXRHapticsDesc> HapticsDesc_Left;
TSharedPtr<FOculusXRHapticsDesc> HapticsDesc_Right;
double StartTime = 0.0;
/** Threshold for treating trigger pulls as button presses, from 0.0 to 1.0 */
static float TriggerThreshold;
/** Are Remote keys mapped to gamepad or not. */
static bool bRemoteKeysMappedToGamepad;
/** Repeat key delays, loaded from config */
static float InitialButtonRepeatDelay;
static float ButtonRepeatDelay;
static bool bPulledHapticsDesc;
protected:
static TSharedPtr<IOculusXRInputBase> GetOculusXRInputBaseImpl();
static TSharedPtr<IOculusXRInputBase> FunctionLibraryImpl;
};
} // namespace OculusXRInput
#endif // OCULUS_INPUT_SUPPORTED_PLATFORMS

View File

@@ -0,0 +1,449 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRInputExtensionPlugin.h"
#include "OculusXRInputState.h"
#include "OculusXRHMDRuntimeSettings.h"
#include "IOpenXRHMDModule.h"
#include "OpenXRCore.h"
#include "Misc/CoreDelegates.h"
namespace OculusXRInput
{
void FInputExtensionPlugin::SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)
{
MessageHandler = InMessageHandler;
}
bool FInputExtensionPlugin::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
{
return false;
}
#if !UE_VERSION_OLDER_THAN(5, 5, 0)
bool FInputExtensionPlugin::GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add(XR_FB_TOUCH_CONTROLLER_PROXIMITY_EXTENSION_NAME);
return true;
}
const void* FInputExtensionPlugin::OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext)
{
bExtTouchControllerProximityAvailable = InModule->IsExtensionEnabled(XR_FB_TOUCH_CONTROLLER_PROXIMITY_EXTENSION_NAME);
return InNext;
}
void FInputExtensionPlugin::PostCreateInstance(XrInstance InInstance)
{
Instance = InInstance;
GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni);
GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni);
}
void FInputExtensionPlugin::OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader)
{
check(InHeader != nullptr);
if (InHeader->type == XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING)
{
const XrEventDataReferenceSpaceChangePending& SpaceChange =
reinterpret_cast<const XrEventDataReferenceSpaceChangePending&>(*InHeader);
if (SpaceChange.referenceSpaceType == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT)
{
FCoreDelegates::VRControllerRecentered.Broadcast();
}
}
}
void FInputExtensionPlugin::CreateDerivedActions()
{
InitializeDerivedActionsArray();
DerivedActionSet = XR_NULL_HANDLE;
XrActionSetCreateInfo ActionSetInfo{ XR_TYPE_ACTION_SET_CREATE_INFO };
ActionSetInfo.next = nullptr;
// Using max priority since these actions are needed to calculate and send derived inputs
ActionSetInfo.priority = ToXrPriority(MAX_int32);
FCStringAnsi::Strcpy(ActionSetInfo.actionSetName, XR_MAX_ACTION_SET_NAME_SIZE, "oculustouchderivedinputsactionset");
FCStringAnsi::Strcpy(ActionSetInfo.localizedActionSetName, XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE, "OculusTouchDerivedInputsActionSet");
XR_ENSURE(xrCreateActionSet(Instance, &ActionSetInfo, &DerivedActionSet));
for (FDerivedActionProperties& DerivedAction : DerivedActions)
{
XrActionCreateInfo ActionInfo{ XR_TYPE_ACTION_CREATE_INFO };
ActionInfo.next = nullptr;
ActionInfo.actionType = DerivedAction.Type;
ActionInfo.countSubactionPaths = 0;
FCStringAnsi::Strcpy(ActionInfo.actionName, XR_MAX_ACTION_NAME_SIZE, TCHAR_TO_ANSI(*DerivedAction.Name.ToLower()));
FCStringAnsi::Strcpy(ActionInfo.localizedActionName, XR_MAX_LOCALIZED_ACTION_NAME_SIZE, TCHAR_TO_ANSI(*DerivedAction.Name));
XR_ENSURE(xrCreateAction(DerivedActionSet, &ActionInfo, &DerivedAction.Action));
}
}
bool FInputExtensionPlugin::GetSuggestedBindings(XrPath InInteractionProfile, TArray<XrActionSuggestedBinding>& OutBindings)
{
if (DerivedActionSet == XR_NULL_HANDLE)
{
return false;
}
const FString ProfilePath = FOpenXRPath(InInteractionProfile).ToString();
FDerivedActionProfile ActiveProfile = FDerivedActionProfile::OculusTouch;
if (ProfilePath == OculusTouchProfilePath)
{
ActiveProfile = FDerivedActionProfile::OculusTouch;
}
else if (ProfilePath == OculusTouchProProfilePath)
{
ActiveProfile = FDerivedActionProfile::OculusTouchPro;
}
else if (ProfilePath == OculusTouchPlusProfilePath)
{
ActiveProfile = FDerivedActionProfile::OculusTouchPlus;
}
else
{
return false;
}
for (FDerivedActionProperties& DerivedAction : DerivedActions)
{
if (DerivedAction.Profile != FDerivedActionProfile::All && DerivedAction.Profile != ActiveProfile)
{
continue;
}
XrPath Path;
xrStringToPath(Instance, TCHAR_TO_ANSI(*DerivedAction.OpenXRPath), &Path);
OutBindings.Add({ DerivedAction.Action, Path });
}
return true;
}
void FInputExtensionPlugin::AttachActionSets(TSet<XrActionSet>& OutActionSets)
{
if (DerivedActionSet != XR_NULL_PATH)
{
OutActionSets.Add(DerivedActionSet);
}
}
void FInputExtensionPlugin::GetActiveActionSetsForSync(TArray<XrActiveActionSet>& OutActiveSets)
{
if (DerivedActionSet != XR_NULL_PATH)
{
OutActiveSets.Add({ DerivedActionSet, XR_NULL_PATH });
}
}
void FInputExtensionPlugin::PostSyncActions(XrSession InSession)
{
if (DerivedActionSet == XR_NULL_PATH)
{
return;
}
const double CurrentTime = FPlatformTime::Seconds();
IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get();
const FPlatformUserId UserId = DeviceMapper.GetPrimaryPlatformUser();
const FInputDeviceId DeviceId = DeviceMapper.GetDefaultInputDevice();
for (FDerivedActionProperties& DerivedAction : DerivedActions)
{
FDerivedInputState& SavedState = DerivedKeysToState.FindOrAdd(DerivedAction.InputKey);
if (DerivedAction.Type == XrActionType::XR_ACTION_TYPE_FLOAT_INPUT)
{
XrActionStateFloat State{ XR_TYPE_ACTION_STATE_FLOAT };
XrActionStateGetInfo Info{ XR_TYPE_ACTION_STATE_GET_INFO };
Info.action = DerivedAction.Action;
XrResult Result = xrGetActionStateFloat(InSession, &Info, &State);
if (XR_SUCCEEDED(Result) && State.isActive)
{
// handle keys with bool-like float values that need to be inverted
if (KeysToInvert.Contains(DerivedAction.InputKey))
{
State.currentState = FMath::IsNearlyEqual(State.currentState, 0.f) ? 1.f : 0.f;
}
if (State.changedSinceLastSync)
{
MessageHandler->OnControllerAnalog(DerivedAction.InputKey.GetFName(), UserId, DeviceId, State.currentState);
}
SavedState.ValueFloat = State.currentState;
SavedState.ChangedSinceLastSync = (bool)State.changedSinceLastSync;
}
}
else if (DerivedAction.Type == XrActionType::XR_ACTION_TYPE_BOOLEAN_INPUT)
{
XrActionStateBoolean State{ XR_TYPE_ACTION_STATE_BOOLEAN };
XrActionStateGetInfo Info{ XR_TYPE_ACTION_STATE_GET_INFO };
Info.action = DerivedAction.Action;
XrResult Result = xrGetActionStateBoolean(InSession, &Info, &State);
if (XR_SUCCEEDED(Result) && State.isActive)
{
const bool bIsPressed = (bool)State.currentState;
if (State.changedSinceLastSync)
{
SendControllerButtonPressed(DerivedAction.InputKey, bIsPressed, UserId, DeviceId, false);
SavedState.NextRepeatTime = CurrentTime + InitialButtonRepeatDelay;
}
else if (bIsPressed && SavedState.NextRepeatTime <= CurrentTime)
{
SendControllerButtonPressed(DerivedAction.InputKey, bIsPressed, UserId, DeviceId, true);
SavedState.NextRepeatTime = CurrentTime + ButtonRepeatDelay;
}
SavedState.ValueBool = bIsPressed;
SavedState.ChangedSinceLastSync = (bool)State.changedSinceLastSync;
}
}
else
{
checkf(false, TEXT("Invalid XrActionType for handling Oculus Derived Inputs"));
}
}
// Handle derived thumbstick cardinal dpad directions
const UOculusXRHMDRuntimeSettings* Settings = GetMutableDefault<UOculusXRHMDRuntimeSettings>();
if (Settings->bThumbstickDpadEmulationEnabled)
{
for (bool isLeft : { true, false })
{
// Reset changed since last sync state
DerivedKeysToState.FindOrAdd(isLeft ? EKeys::OculusTouch_Left_Thumbstick_Up : EKeys::OculusTouch_Right_Thumbstick_Up).ChangedSinceLastSync = false;
DerivedKeysToState.FindOrAdd(isLeft ? EKeys::OculusTouch_Left_Thumbstick_Down : EKeys::OculusTouch_Right_Thumbstick_Down).ChangedSinceLastSync = false;
DerivedKeysToState.FindOrAdd(isLeft ? EKeys::OculusTouch_Left_Thumbstick_Left : EKeys::OculusTouch_Right_Thumbstick_Left).ChangedSinceLastSync = false;
DerivedKeysToState.FindOrAdd(isLeft ? EKeys::OculusTouch_Left_Thumbstick_Right : EKeys::OculusTouch_Right_Thumbstick_Right).ChangedSinceLastSync = false;
const FDerivedInputState* ThumbstickXState = DerivedKeysToState.Find(isLeft ? EKeys::OculusTouch_Left_Thumbstick_X : EKeys::OculusTouch_Right_Thumbstick_X);
const FDerivedInputState* ThumbstickYState = DerivedKeysToState.Find(isLeft ? EKeys::OculusTouch_Left_Thumbstick_Y : EKeys::OculusTouch_Right_Thumbstick_Y);
if (ThumbstickXState != nullptr && ThumbstickYState != nullptr && (ThumbstickXState->ChangedSinceLastSync || ThumbstickYState->ChangedSinceLastSync))
{
// Calculating quadrants for the thumbstick cardinal directions
float Angle = FMath::Atan2(ThumbstickYState->ValueFloat, ThumbstickXState->ValueFloat);
FVector2D Thumbsticks = FVector2D(ThumbstickXState->ValueFloat, ThumbstickYState->ValueFloat);
bool IsAboveThreshold = Thumbsticks.Size() > 0.7f;
bool IsUpPressed = IsAboveThreshold && Angle >= (1.0f / 8.0f) * PI && Angle <= (7.0f / 8.0f) * PI;
bool IsDownPressed = IsAboveThreshold && Angle >= (-7.0f / 8.0f) * PI && Angle <= (-1.0f / 8.0f) * PI;
bool IsLeftPressed = IsAboveThreshold && Angle <= (-5.0f / 8.0f) * PI || Angle >= (5.0f / 8.0f) * PI;
bool IsRightPressed = IsAboveThreshold && Angle >= (-3.0f / 8.0f) * PI && Angle <= (3.0f / 8.0f) * PI;
SendDerivedDpadButtonPressed(isLeft ? EKeys::OculusTouch_Left_Thumbstick_Up : EKeys::OculusTouch_Right_Thumbstick_Up, IsUpPressed, UserId, DeviceId, CurrentTime);
SendDerivedDpadButtonPressed(isLeft ? EKeys::OculusTouch_Left_Thumbstick_Down : EKeys::OculusTouch_Right_Thumbstick_Down, IsDownPressed, UserId, DeviceId, CurrentTime);
SendDerivedDpadButtonPressed(isLeft ? EKeys::OculusTouch_Left_Thumbstick_Left : EKeys::OculusTouch_Right_Thumbstick_Left, IsLeftPressed, UserId, DeviceId, CurrentTime);
SendDerivedDpadButtonPressed(isLeft ? EKeys::OculusTouch_Left_Thumbstick_Right : EKeys::OculusTouch_Right_Thumbstick_Right, IsRightPressed, UserId, DeviceId, CurrentTime);
}
}
}
}
void FInputExtensionPlugin::SendControllerButtonPressed(FKey InKey, bool bIsPressed, FPlatformUserId UserId, FInputDeviceId DeviceId, bool bIsRepeat)
{
if (bIsPressed)
{
MessageHandler->OnControllerButtonPressed(InKey.GetFName(), UserId, DeviceId, bIsRepeat);
}
else
{
MessageHandler->OnControllerButtonReleased(InKey.GetFName(), UserId, DeviceId, bIsRepeat);
}
}
void FInputExtensionPlugin::SendDerivedDpadButtonPressed(FKey InKey, bool bIsPressed, FPlatformUserId UserId, FInputDeviceId DeviceId, double CurrentTime)
{
FDerivedInputState& SavedState = DerivedKeysToState.FindOrAdd(InKey);
if (SavedState.ValueBool != bIsPressed)
{
SendControllerButtonPressed(InKey, bIsPressed, UserId, DeviceId, false);
SavedState.NextRepeatTime = CurrentTime + InitialButtonRepeatDelay;
SavedState.ChangedSinceLastSync = true;
}
else if (bIsPressed && SavedState.NextRepeatTime <= CurrentTime)
{
SendControllerButtonPressed(InKey, bIsPressed, UserId, DeviceId, true);
SavedState.NextRepeatTime = CurrentTime + ButtonRepeatDelay;
}
SavedState.ValueBool = bIsPressed;
}
void FInputExtensionPlugin::DestroyDerivedActions()
{
xrDestroyActionSet(DerivedActionSet);
DerivedActionSet = XR_NULL_HANDLE;
DerivedActions.Empty();
}
bool FInputExtensionPlugin::GetInputKeyOverrides(TArray<FInputKeyOpenXRProperties>& OutOverrides)
{
// Each time Unreal sets up input, we must recreate the derived action set
if (DerivedActionSet != XR_NULL_HANDLE)
{
DestroyDerivedActions();
}
CreateDerivedActions();
// Input keys compatible with all oculus interaction profiles
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_X_Click, "/user/hand/left/input/x/click");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Y_Click, "/user/hand/left/input/y/click");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_X_Touch, "/user/hand/left/input/x/touch");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Y_Touch, "/user/hand/left/input/y/touch");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Menu_Click, "/user/hand/left/input/menu/click");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Grip_Click, "/user/hand/left/input/squeeze");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Grip_Axis, "/user/hand/left/input/squeeze/value");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Trigger_Click, "/user/hand/left/input/trigger");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Trigger_Axis, "/user/hand/left/input/trigger/value");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Trigger_Touch, "/user/hand/left/input/trigger/touch");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Thumbstick_2D, "/user/hand/left/input/thumbstick");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Thumbstick_X, "/user/hand/left/input/thumbstick/x");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Thumbstick_Y, "/user/hand/left/input/thumbstick/y");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Thumbstick_Click, "/user/hand/left/input/thumbstick/click");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Left_Thumbstick_Touch, "/user/hand/left/input/thumbstick/touch");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_A_Click, "/user/hand/right/input/a/click");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_B_Click, "/user/hand/right/input/b/click");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_A_Touch, "/user/hand/right/input/a/touch");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_B_Touch, "/user/hand/right/input/b/touch");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_Grip_Click, "/user/hand/right/input/squeeze");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_Grip_Axis, "/user/hand/right/input/squeeze/value");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_Trigger_Click, "/user/hand/right/input/trigger");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_Trigger_Axis, "/user/hand/right/input/trigger/value");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_Trigger_Touch, "/user/hand/right/input/trigger/touch");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_Thumbstick_2D, "/user/hand/right/input/thumbstick");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_Thumbstick_X, "/user/hand/right/input/thumbstick/x");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_Thumbstick_Y, "/user/hand/right/input/thumbstick/y");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_Thumbstick_Click, "/user/hand/right/input/thumbstick/click");
CreateForAllProfiles(OutOverrides, EKeys::OculusTouch_Right_Thumbstick_Touch, "/user/hand/right/input/thumbstick/touch");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Left_ThumbRest, "/user/hand/left/input/thumbrest/touch");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Right_ThumbRest, "/user/hand/right/input/thumbrest/touch");
if (bExtTouchControllerProximityAvailable)
{
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Left_IndexPointing, "/user/hand/left/input/trigger/proximity_fb");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Left_Trigger_Proximity, "/user/hand/left/input/trigger/proximity_fb");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Left_ThumbUp, "/user/hand/left/input/thumb_fb/proximity_fb");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Left_Thumb_Proximity, "/user/hand/left/input/thumb_fb/proximity_fb");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Right_IndexPointing, "/user/hand/right/input/trigger/proximity_fb");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Right_Trigger_Proximity, "/user/hand/right/input/trigger/proximity_fb");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Right_ThumbUp, "/user/hand/right/input/thumb_fb/proximity_fb");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Right_Thumb_Proximity, "/user/hand/right/input/thumb_fb/proximity_fb");
}
else
{
OutOverrides.Add({ FOculusKey::OculusTouch_Left_IndexPointing.ToString(), OculusTouchProProfile, "/user/hand/left/input/trigger/proximity_fb" });
OutOverrides.Add({ FOculusKey::OculusTouch_Left_Trigger_Proximity.ToString(), OculusTouchProProfile, "/user/hand/left/input/trigger/proximity_fb" });
OutOverrides.Add({ FOculusKey::OculusTouch_Left_ThumbUp.ToString(), OculusTouchProProfile, "/user/hand/left/input/thumb_fb/proximity_fb" });
OutOverrides.Add({ FOculusKey::OculusTouch_Left_Thumb_Proximity.ToString(), OculusTouchProProfile, "/user/hand/left/input/thumb_fb/proximity_fb" });
OutOverrides.Add({ FOculusKey::OculusTouch_Right_IndexPointing.ToString(), OculusTouchProProfile, "/user/hand/right/input/trigger/proximity_fb" });
OutOverrides.Add({ FOculusKey::OculusTouch_Right_Trigger_Proximity.ToString(), OculusTouchProProfile, "/user/hand/right/input/trigger/proximity_fb" });
OutOverrides.Add({ FOculusKey::OculusTouch_Right_ThumbUp.ToString(), OculusTouchProProfile, "/user/hand/right/input/thumb_fb/proximity_fb" });
OutOverrides.Add({ FOculusKey::OculusTouch_Right_Thumb_Proximity.ToString(), OculusTouchProProfile, "/user/hand/right/input/thumb_fb/proximity_fb" });
}
// These keys are duplicated with what Epic already has. Binding for backwards compatibility.
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Left_Thumbstick, "/user/hand/left/input/thumbstick/touch");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Left_Trigger, "/user/hand/left/input/trigger/touch");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Left_FaceButton1, "/user/hand/left/input/x/touch");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Left_FaceButton2, "/user/hand/left/input/y/touch");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Right_Thumbstick, "/user/hand/right/input/thumbstick/touch");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Right_Trigger, "/user/hand/right/input/trigger/touch");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Right_FaceButton1, "/user/hand/right/input/a/touch");
CreateForAllProfiles(OutOverrides, FOculusKey::OculusTouch_Right_FaceButton2, "/user/hand/right/input/b/touch");
// Input keys compatible with only touch pro
OutOverrides.Add({ FOculusKey::OculusTouch_Left_ThumbRest_Force.ToString(), OculusTouchProProfile, "/user/hand/left/input/thumbrest/force" });
OutOverrides.Add({ FOculusKey::OculusTouch_Left_Stylus_Force.ToString(), OculusTouchProProfile, "/user/hand/left/input/stylus_fb/force" });
OutOverrides.Add({ FOculusKey::OculusTouch_Right_ThumbRest_Force.ToString(), OculusTouchProProfile, "/user/hand/right/input/thumbrest/force" });
OutOverrides.Add({ FOculusKey::OculusTouch_Right_Stylus_Force.ToString(), OculusTouchProProfile, "/user/hand/right/input/stylus_fb/force" });
// Input keys compatible with only touch plus
OutOverrides.Add({ FOculusKey::OculusTouch_Left_IndexTrigger_Force.ToString(), OculusTouchPlusProfile, "/user/hand/left/input/trigger/force" });
OutOverrides.Add({ FOculusKey::OculusTouch_Right_IndexTrigger_Force.ToString(), OculusTouchPlusProfile, "/user/hand/right/input/trigger/force" });
// Input key compatible with a mixed selection
OutOverrides.Add({ FOculusKey::OculusTouch_Left_IndexTrigger_Curl.ToString(), OculusTouchProProfile, "/user/hand/left/input/trigger/curl_fb" });
OutOverrides.Add({ FOculusKey::OculusTouch_Left_IndexTrigger_Curl.ToString(), OculusTouchPlusProfile, "/user/hand/left/input/trigger/curl_meta" });
OutOverrides.Add({ FOculusKey::OculusTouch_Left_IndexTrigger_Slide.ToString(), OculusTouchProProfile, "/user/hand/left/input/trigger/slide_fb" });
OutOverrides.Add({ FOculusKey::OculusTouch_Left_IndexTrigger_Slide.ToString(), OculusTouchPlusProfile, "/user/hand/left/input/trigger/slide_meta" });
OutOverrides.Add({ FOculusKey::OculusTouch_Right_IndexTrigger_Curl.ToString(), OculusTouchProProfile, "/user/hand/right/input/trigger/curl_fb" });
OutOverrides.Add({ FOculusKey::OculusTouch_Right_IndexTrigger_Curl.ToString(), OculusTouchPlusProfile, "/user/hand/right/input/trigger/curl_meta" });
OutOverrides.Add({ FOculusKey::OculusTouch_Right_IndexTrigger_Slide.ToString(), OculusTouchProProfile, "/user/hand/right/input/trigger/slide_fb" });
OutOverrides.Add({ FOculusKey::OculusTouch_Right_IndexTrigger_Slide.ToString(), OculusTouchPlusProfile, "/user/hand/right/input/trigger/slide_meta" });
return true;
}
void FInputExtensionPlugin::CreateForAllProfiles(TArray<FInputKeyOpenXRProperties>& OutOverrides, FKey InKey, FString Path)
{
OutOverrides.Add({ InKey.ToString(), OculusTouchProfile, Path });
OutOverrides.Add({ InKey.ToString(), OculusTouchProProfile, Path });
OutOverrides.Add({ InKey.ToString(), OculusTouchPlusProfile, Path });
}
void FInputExtensionPlugin::InitializeDerivedActionsArray()
{
const UOculusXRHMDRuntimeSettings* Settings = GetMutableDefault<UOculusXRHMDRuntimeSettings>();
if (Settings->bThumbstickDpadEmulationEnabled)
{
// Actions used to derive thumbstick cardinal dpad directions
DerivedActions.Add({ "OculusTouchDerivedLeftThumbstickX", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, EKeys::OculusTouch_Left_Thumbstick_X, "/user/hand/left/input/thumbstick/x", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedLeftThumbstickY", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, EKeys::OculusTouch_Left_Thumbstick_Y, "/user/hand/left/input/thumbstick/y", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightThumbstickX", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, EKeys::OculusTouch_Right_Thumbstick_X, "/user/hand/right/input/thumbstick/x", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightThumbstickY", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, EKeys::OculusTouch_Right_Thumbstick_Y, "/user/hand/right/input/thumbstick/y", XR_NULL_HANDLE, FDerivedActionProfile::All });
// Remaining thumbstick actions since all thumbstick inputs will now be handled by the derived action set
DerivedActions.Add({ "OculusTouchDerivedLeftThumbstickClick", XrActionType::XR_ACTION_TYPE_BOOLEAN_INPUT, EKeys::OculusTouch_Left_Thumbstick_Click, "/user/hand/left/input/thumbstick/click", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightThumbstickClick", XrActionType::XR_ACTION_TYPE_BOOLEAN_INPUT, EKeys::OculusTouch_Right_Thumbstick_Click, "/user/hand/right/input/thumbstick/click", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedLeftThumbstickTouch", XrActionType::XR_ACTION_TYPE_BOOLEAN_INPUT, EKeys::OculusTouch_Left_Thumbstick_Touch, "/user/hand/left/input/thumbstick/touch", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightThumbstickTouch", XrActionType::XR_ACTION_TYPE_BOOLEAN_INPUT, EKeys::OculusTouch_Right_Thumbstick_Touch, "/user/hand/right/input/thumbstick/touch", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedLeftThumbstick", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_Thumbstick, "/user/hand/left/input/thumbstick/touch", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightThumbstick", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_Thumbstick, "/user/hand/right/input/thumbstick/touch", XR_NULL_HANDLE, FDerivedActionProfile::All });
}
// Proximity actions are flipped from OpenXR -> UE, so handle these here
if (bExtTouchControllerProximityAvailable)
{
DerivedActions.Add({ "OculusTouchDerivedLeftIndexPointing", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_IndexPointing, "/user/hand/left/input/trigger/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightIndexPointing", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_IndexPointing, "/user/hand/right/input/trigger/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedLeftTriggerProximity", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_Trigger_Proximity, "/user/hand/left/input/trigger/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightTriggerProximity", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_Trigger_Proximity, "/user/hand/right/input/trigger/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedLeftThumbUp", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_ThumbUp, "/user/hand/left/input/thumb_fb/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightThumbUp", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_ThumbUp, "/user/hand/right/input/thumb_fb/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedLeftThumbProximity", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_Thumb_Proximity, "/user/hand/left/input/thumb_fb/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightThumbProximity", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_Thumb_Proximity, "/user/hand/right/input/thumb_fb/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::All });
}
else
{
DerivedActions.Add({ "OculusTouchDerivedLeftIndexPointing", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_IndexPointing, "/user/hand/left/input/trigger/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
DerivedActions.Add({ "OculusTouchDerivedRightIndexPointing", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_IndexPointing, "/user/hand/right/input/trigger/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
DerivedActions.Add({ "OculusTouchDerivedLeftTriggerProximity", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_Trigger_Proximity, "/user/hand/left/input/trigger/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
DerivedActions.Add({ "OculusTouchDerivedRightTriggerProximity", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_Trigger_Proximity, "/user/hand/right/input/trigger/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
DerivedActions.Add({ "OculusTouchDerivedLeftThumbUp", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_ThumbUp, "/user/hand/left/input/thumb_fb/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
DerivedActions.Add({ "OculusTouchDerivedRightThumbUp", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_ThumbUp, "/user/hand/right/input/thumb_fb/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
DerivedActions.Add({ "OculusTouchDerivedLeftThumbProximity", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_Thumb_Proximity, "/user/hand/left/input/thumb_fb/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
DerivedActions.Add({ "OculusTouchDerivedRightThumbProximity", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_Thumb_Proximity, "/user/hand/right/input/thumb_fb/proximity_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
}
// Remaining trigger actions since all trigger inputs will now be handled by the derived action set
DerivedActions.Add({ "OculusTouchDerivedLeftTriggerClick", XrActionType::XR_ACTION_TYPE_BOOLEAN_INPUT, EKeys::OculusTouch_Left_Trigger_Click, "/user/hand/left/input/trigger", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedLeftTriggerAxis", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, EKeys::OculusTouch_Left_Trigger_Axis, "/user/hand/left/input/trigger/value", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedLeftTriggerTouch", XrActionType::XR_ACTION_TYPE_BOOLEAN_INPUT, EKeys::OculusTouch_Left_Trigger_Touch, "/user/hand/left/input/trigger/touch", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightTriggerClick", XrActionType::XR_ACTION_TYPE_BOOLEAN_INPUT, EKeys::OculusTouch_Right_Trigger_Click, "/user/hand/right/input/trigger", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightTriggerAxis", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, EKeys::OculusTouch_Right_Trigger_Axis, "/user/hand/right/input/trigger/value", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightTriggerTouch", XrActionType::XR_ACTION_TYPE_BOOLEAN_INPUT, EKeys::OculusTouch_Right_Trigger_Touch, "/user/hand/right/input/trigger/touch", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedLeftTrigger", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_Trigger, "/user/hand/left/input/trigger/touch", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchDerivedRightTrigger", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_Trigger, "/user/hand/right/input/trigger/touch", XR_NULL_HANDLE, FDerivedActionProfile::All });
DerivedActions.Add({ "OculusTouchPlusDerivedLeftTriggerForce", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_IndexTrigger_Force, "/user/hand/left/input/trigger/force", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPlus });
DerivedActions.Add({ "OculusTouchPlusDerivedRightTriggerForce", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_IndexTrigger_Force, "/user/hand/right/input/trigger/force", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPlus });
DerivedActions.Add({ "OculusTouchProDerivedLeftTriggerCurl", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_IndexTrigger_Curl, "/user/hand/left/input/trigger/curl_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
DerivedActions.Add({ "OculusTouchPlusDerivedLeftTriggerCurl", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_IndexTrigger_Curl, "/user/hand/left/input/trigger/curl_meta", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPlus });
DerivedActions.Add({ "OculusTouchProDerivedLeftTriggerSlide", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_IndexTrigger_Slide, "/user/hand/left/input/trigger/slide_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
DerivedActions.Add({ "OculusTouchPlusDerivedLeftTriggerSlide", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Left_IndexTrigger_Slide, "/user/hand/left/input/trigger/slide_meta", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPlus });
DerivedActions.Add({ "OculusTouchProDerivedRightTriggerCurl", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_IndexTrigger_Curl, "/user/hand/right/input/trigger/curl_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
DerivedActions.Add({ "OculusTouchPlusDerivedRightTriggerCurl", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_IndexTrigger_Curl, "/user/hand/right/input/trigger/curl_meta", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPlus });
DerivedActions.Add({ "OculusTouchProDerivedRightTriggerSlide", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_IndexTrigger_Slide, "/user/hand/right/input/trigger/slide_fb", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPro });
DerivedActions.Add({ "OculusTouchPlusDerivedRightTriggerSlide", XrActionType::XR_ACTION_TYPE_FLOAT_INPUT, FOculusKey::OculusTouch_Right_IndexTrigger_Slide, "/user/hand/right/input/trigger/slide_meta", XR_NULL_HANDLE, FDerivedActionProfile::OculusTouchPlus });
}
#endif
} // namespace OculusXRInput

View File

@@ -0,0 +1,111 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "khronos/openxr/openxr.h"
#include "CoreMinimal.h"
#include "IOculusXRInputModule.h"
#include "IOpenXRExtensionPlugin.h"
#include "OculusXRInput.h"
#include "Misc/EngineVersionComparison.h"
namespace OculusXRInput
{
enum FDerivedActionProfile
{
All,
OculusTouch,
OculusTouchPro,
OculusTouchPlus
};
struct FDerivedActionProperties
{
FString Name;
XrActionType Type;
FKey InputKey;
FString OpenXRPath;
XrAction Action;
FDerivedActionProfile Profile;
};
struct FDerivedInputState
{
bool ValueBool;
float ValueFloat;
bool ChangedSinceLastSync;
double NextRepeatTime;
FDerivedInputState()
: ValueBool(false), ValueFloat(0.f), ChangedSinceLastSync(false), NextRepeatTime(0.0)
{
}
};
class FInputExtensionPlugin : public IOpenXRExtensionPlugin, public IInputDevice
{
public:
FInputExtensionPlugin()
: InitialButtonRepeatDelay(DefaultInitialButtonRepeatDelay), ButtonRepeatDelay(DefaultButtonRepeatDelay), MessageHandler(nullptr), bExtTouchControllerProximityAvailable(false), Instance(XR_NULL_HANDLE), DerivedActions({}), DerivedKeysToState({}), DerivedActionSet(XR_NULL_HANDLE)
{
}
void RegisterOpenXRExtensionPlugin()
{
#if !UE_VERSION_OLDER_THAN(5, 5, 0)
RegisterOpenXRExtensionModularFeature();
#endif
}
// IInputDevice
virtual void SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) override;
void Tick(float DeltaTime) override {};
void SendControllerEvents() override {};
virtual bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) override;
void SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) override {};
void SetChannelValues(int32 ControllerId, const FForceFeedbackValues& Values) override {};
private:
const FString OculusTouchProfile = TEXT("OculusTouch");
const FString OculusTouchProProfile = TEXT("OculusTouchPro");
const FString OculusTouchPlusProfile = TEXT("OculusTouchPlus");
const FString OculusTouchProfilePath = TEXT("/interaction_profiles/oculus/touch_controller");
const FString OculusTouchProProfilePath = TEXT("/interaction_profiles/facebook/touch_controller_pro");
const FString OculusTouchPlusProfilePath = TEXT("/interaction_profiles/meta/touch_controller_plus");
const TSet<FKey> KeysToInvert = { FOculusKey::OculusTouch_Left_IndexPointing, FOculusKey::OculusTouch_Right_IndexPointing, FOculusKey::OculusTouch_Left_ThumbUp, FOculusKey::OculusTouch_Right_ThumbUp };
float InitialButtonRepeatDelay;
float ButtonRepeatDelay;
TSharedPtr<FGenericApplicationMessageHandler> MessageHandler;
bool bExtTouchControllerProximityAvailable;
XrInstance Instance;
TArray<FDerivedActionProperties> DerivedActions;
TMap<FKey, FDerivedInputState> DerivedKeysToState;
XrActionSet DerivedActionSet;
#if !UE_VERSION_OLDER_THAN(5, 5, 0)
public:
// IOpenXRExtensionPlugin
virtual bool GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual const void* OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) override;
virtual void OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader) override;
virtual bool GetInputKeyOverrides(TArray<FInputKeyOpenXRProperties>& OutOverrides) override;
virtual void PostCreateInstance(XrInstance InInstance) override;
virtual bool GetSuggestedBindings(XrPath InInteractionProfile, TArray<XrActionSuggestedBinding>& OutBindings) override;
virtual void PostSyncActions(XrSession InSession) override;
virtual void AttachActionSets(TSet<XrActionSet>& OutActionSets) override;
virtual void GetActiveActionSetsForSync(TArray<XrActiveActionSet>& OutActiveSets);
private:
virtual void CreateForAllProfiles(TArray<FInputKeyOpenXRProperties>& OutOverrides, FKey InKey, FString Path);
virtual void CreateDerivedActions();
virtual void InitializeDerivedActionsArray();
virtual void SendControllerButtonPressed(FKey InKey, bool bIsPressed, FPlatformUserId UserId, FInputDeviceId DeviceId, bool bIsRepeat);
virtual void SendDerivedDpadButtonPressed(FKey InKey, bool bIsPressed, FPlatformUserId UserId, FInputDeviceId DeviceId, double CurrentTime);
virtual void DestroyDerivedActions();
#endif
};
} // namespace OculusXRInput

View File

@@ -0,0 +1,186 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRInputFunctionLibrary.h"
#include "OculusXRControllerLegacyPoseTransformComponent.h"
#include "OculusXRHandTracking.h"
#include "OculusXRControllerTracking.h"
#include "Logging/MessageLog.h"
#include "Haptics/HapticFeedbackEffect_Buffer.h"
#include "Haptics/HapticFeedbackEffect_Curve.h"
#include "Haptics/HapticFeedbackEffect_SoundWave.h"
//-------------------------------------------------------------------------------------------------
// UOculusHandTrackingFunctionLibrary
//-------------------------------------------------------------------------------------------------
UOculusXRInputFunctionLibrary::UOculusXRInputFunctionLibrary(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
UOculusXRInputFunctionLibrary::FHandMovementFilterDelegate UOculusXRInputFunctionLibrary::HandMovementFilter;
EOculusXRFinger UOculusXRInputFunctionLibrary::ConvertBoneToFinger(const EOculusXRBone Bone)
{
switch (Bone)
{
case EOculusXRBone::Index_1:
case EOculusXRBone::Index_2:
case EOculusXRBone::Index_3:
case EOculusXRBone::Index_Tip:
return EOculusXRFinger::Index;
case EOculusXRBone::Middle_1:
case EOculusXRBone::Middle_2:
case EOculusXRBone::Middle_3:
case EOculusXRBone::Middle_Tip:
return EOculusXRFinger::Middle;
case EOculusXRBone::Pinky_0:
case EOculusXRBone::Pinky_1:
case EOculusXRBone::Pinky_2:
case EOculusXRBone::Pinky_3:
case EOculusXRBone::Pinky_Tip:
return EOculusXRFinger::Pinky;
case EOculusXRBone::Ring_1:
case EOculusXRBone::Ring_2:
case EOculusXRBone::Ring_3:
case EOculusXRBone::Ring_Tip:
return EOculusXRFinger::Ring;
case EOculusXRBone::Thumb_0:
case EOculusXRBone::Thumb_1:
case EOculusXRBone::Thumb_2:
case EOculusXRBone::Thumb_3:
case EOculusXRBone::Thumb_Tip:
return EOculusXRFinger::Thumb;
default:
return EOculusXRFinger::Invalid;
}
}
EOculusXRTrackingConfidence UOculusXRInputFunctionLibrary::GetFingerTrackingConfidence(const EOculusXRHandType DeviceHand, const EOculusXRFinger Finger, const int32 ControllerIndex)
{
return OculusXRInput::FOculusHandTracking::GetFingerTrackingConfidence(ControllerIndex, DeviceHand, (OculusXRInput::EOculusHandAxes)(uint8)Finger);
}
bool UOculusXRInputFunctionLibrary::GetHandSkeletalMesh(USkeletalMesh* HandSkeletalMesh, EOculusXRHandType SkeletonType, EOculusXRHandType MeshType, float WorldToMeters)
{
return OculusXRInput::FOculusHandTracking::GetHandSkeletalMesh(HandSkeletalMesh, SkeletonType, MeshType, WorldToMeters);
}
TArray<FOculusXRCapsuleCollider> UOculusXRInputFunctionLibrary::InitializeHandPhysics(EOculusXRHandType SkeletonType, USkinnedMeshComponent* HandComponent, const float WorldToMeters)
{
return OculusXRInput::FOculusHandTracking::InitializeHandPhysics(SkeletonType, HandComponent, WorldToMeters);
}
FQuat UOculusXRInputFunctionLibrary::GetBoneRotation(const EOculusXRHandType DeviceHand, const EOculusXRBone BoneId, const int32 ControllerIndex)
{
return OculusXRInput::FOculusHandTracking::GetBoneRotation(ControllerIndex, DeviceHand, BoneId);
}
EOculusXRTrackingConfidence UOculusXRInputFunctionLibrary::GetTrackingConfidence(const EOculusXRHandType DeviceHand, const int32 ControllerIndex)
{
return OculusXRInput::FOculusHandTracking::GetTrackingConfidence(ControllerIndex, DeviceHand);
}
FTransform UOculusXRInputFunctionLibrary::GetPointerPose(const EOculusXRHandType DeviceHand, const int32 ControllerIndex)
{
return OculusXRInput::FOculusHandTracking::GetPointerPose(ControllerIndex, DeviceHand);
}
bool UOculusXRInputFunctionLibrary::IsPointerPoseValid(const EOculusXRHandType DeviceHand, const int32 ControllerIndex)
{
return OculusXRInput::FOculusHandTracking::IsPointerPoseValid(ControllerIndex, DeviceHand);
}
float UOculusXRInputFunctionLibrary::GetHandScale(const EOculusXRHandType DeviceHand, const int32 ControllerIndex)
{
return OculusXRInput::FOculusHandTracking::GetHandScale(ControllerIndex, DeviceHand);
}
EOculusXRHandType UOculusXRInputFunctionLibrary::GetDominantHand(const int32 ControllerIndex)
{
EOculusXRHandType DominantHand = EOculusXRHandType::None;
if (OculusXRInput::FOculusHandTracking::IsHandDominant(ControllerIndex, EOculusXRHandType::HandLeft))
{
DominantHand = EOculusXRHandType::HandLeft;
}
else if (OculusXRInput::FOculusHandTracking::IsHandDominant(ControllerIndex, EOculusXRHandType::HandRight))
{
DominantHand = EOculusXRHandType::HandRight;
}
return DominantHand;
}
bool UOculusXRInputFunctionLibrary::IsHandTrackingEnabled()
{
return OculusXRInput::FOculusHandTracking::IsHandTrackingEnabled();
}
bool UOculusXRInputFunctionLibrary::IsHandPositionValid(const EOculusXRHandType DeviceHand, const int32 ControllerIndex)
{
return OculusXRInput::FOculusHandTracking::IsHandPositionValid(ControllerIndex, DeviceHand);
}
FString UOculusXRInputFunctionLibrary::GetBoneName(EOculusXRBone BoneId)
{
const auto ovrBoneId = OculusXRInput::FOculusHandTracking::ToOvrBone(BoneId);
if (ovrBoneId == ovrpBoneId_Invalid)
{
return OculusXRInput::FOculusHandTracking::GetBoneName(static_cast<uint8>(EOculusXRBone::Invalid));
}
return OculusXRInput::FOculusHandTracking::GetBoneName(static_cast<uint8>(ovrBoneId));
}
void UOculusXRInputFunctionLibrary::PlayCurveHapticEffect(class UHapticFeedbackEffect_Curve* HapticEffect, EControllerHand Hand, EOculusXRHandHapticsLocation Location, float Scale, bool bLoop)
{
OculusXRInput::FOculusXRControllerTracking::PlayHapticEffect(HapticEffect, Hand, Location, false, Scale, bLoop);
}
void UOculusXRInputFunctionLibrary::PlayBufferHapticEffect(class UHapticFeedbackEffect_Buffer* HapticEffect, EControllerHand Hand, EOculusXRHandHapticsLocation Location, float Scale, bool bLoop)
{
OculusXRInput::FOculusXRControllerTracking::PlayHapticEffect(HapticEffect, Hand, Location, false, Scale, bLoop);
}
void UOculusXRInputFunctionLibrary::PlayAmplitudeEnvelopeHapticEffect(class UHapticFeedbackEffect_Buffer* HapticEffect, EControllerHand Hand)
{
OculusXRInput::FOculusXRControllerTracking::PlayAmplitudeEnvelopeHapticEffect(Hand, HapticEffect->Amplitudes, HapticEffect->SampleRate);
}
void UOculusXRInputFunctionLibrary::PlaySoundWaveHapticEffect(class UHapticFeedbackEffect_SoundWave* HapticEffect, EControllerHand Hand, bool bAppend, float Scale, bool bLoop)
{
OculusXRInput::FOculusXRControllerTracking::PlayHapticEffect(HapticEffect, Hand, EOculusXRHandHapticsLocation::Hand, bAppend, Scale, bLoop);
}
void UOculusXRInputFunctionLibrary::StopHapticEffect(EControllerHand Hand, EOculusXRHandHapticsLocation Location)
{
OculusXRInput::FOculusXRControllerTracking::StopHapticEffect(Hand, Location);
}
void UOculusXRInputFunctionLibrary::SetHapticsByValue(const float Frequency, const float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location)
{
OculusXRInput::FOculusXRControllerTracking::SetHapticsByValue(Frequency, Amplitude, Hand, Location);
}
float UOculusXRInputFunctionLibrary::GetControllerSampleRateHz(EControllerHand Hand)
{
return OculusXRInput::FOculusXRControllerTracking::GetControllerSampleRateHz(Hand);
}
int UOculusXRInputFunctionLibrary::GetMaxHapticDuration(EControllerHand Hand)
{
return OculusXRInput::FOculusXRControllerTracking::GetMaxHapticDuration(Hand);
}
void UOculusXRInputFunctionLibrary::SetControllerDrivenHandPoses(EOculusXRControllerDrivenHandPoseTypes Type)
{
return OculusXRInput::FOculusHandTracking::SetControllerDrivenHandPoses(Type);
}
EOculusXRControllerDrivenHandPoseTypes UOculusXRInputFunctionLibrary::GetControllerDrivenHandPoses()
{
return OculusXRInput::FOculusHandTracking::ControllerDrivenHandType;
}
FTransform UOculusXRInputFunctionLibrary::GetLegacyOculusPoseTransform(float WorldToMeters)
{
return FTransform(OculusPoseToGripRotation, OculusPoseToGripPosition * WorldToMeters).Inverse();
}

View File

@@ -0,0 +1,90 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "khronos/openxr/openxr.h"
#include "CoreMinimal.h"
#include "IOculusXRInputModule.h"
#include "IOpenXRExtensionPlugin.h"
#include "OculusXRInputHandTrackingTypes.h"
#include "OculusXRInputState.h"
namespace OculusXRInput
{
class FHandTrackingExtensionPlugin : public IOpenXRExtensionPlugin, public IInputDevice
{
public:
FHandTrackingExtensionPlugin()
{
HandControllerStates.Add(EOculusXRHandType::HandLeft, FOculusHandControllerState(EControllerHand::Left));
HandControllerStates.Add(EOculusXRHandType::HandRight, FOculusHandControllerState(EControllerHand::Right));
memset(XrLeftHandJointLocations, 0, sizeof(XrLeftHandJointLocations));
memset(XrRightHandJointLocations, 0, sizeof(XrRightHandJointLocations));
memset(XrLeftHandJointVelocities, 0, sizeof(XrLeftHandJointVelocities));
memset(XrRightHandJointVelocities, 0, sizeof(XrRightHandJointVelocities));
}
void RegisterOpenXRExtensionPlugin()
{
RegisterOpenXRExtensionModularFeature();
}
// IInputDevice
virtual void SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) override;
void Tick(float DeltaTime) override {};
void SendControllerEvents() override {};
virtual bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) override;
void SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) override {};
void SetChannelValues(int32 ControllerId, const FForceFeedbackValues& Values) override {};
// IOpenXRExtensionPlugin
virtual bool GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual bool GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual const void* OnGetSystem(XrInstance InInstance, const void* InNext) override;
virtual const void* OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) override;
virtual void PostCreateInstance(XrInstance InInstance) override;
virtual const void* OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext) override;
virtual const void* OnBeginSession(XrSession InSession, const void* InNext) override;
virtual void UpdateDeviceLocations(XrSession InSession, XrTime DisplayTime, XrSpace TrackingSpace) override;
virtual void OnDestroySession(XrSession InSession) override;
virtual bool IsHandTrackingAvailable();
virtual bool IsHandTrackingActive();
virtual bool GetSkeleton(EOculusXRHandType HandType, TSharedPtr<FHandSkeleton> Skeleton);
virtual bool GetMesh(EOculusXRHandType HandType, TSharedPtr<FHandMesh> Mesh);
TMap<EOculusXRHandType, FOculusHandControllerState> HandControllerStates;
bool bIsInitialized = false;
private:
virtual void SendControllerButtonPressed(FName Key, bool IsPressed, FPlatformUserId UserId, FInputDeviceId DeviceId);
XrHandJointLocationEXT XrLeftHandJointLocations[XR_HAND_JOINT_COUNT_EXT];
XrHandJointLocationEXT XrRightHandJointLocations[XR_HAND_JOINT_COUNT_EXT];
XrHandJointVelocityEXT XrLeftHandJointVelocities[XR_HAND_JOINT_COUNT_EXT];
XrHandJointVelocityEXT XrRightHandJointVelocities[XR_HAND_JOINT_COUNT_EXT];
PFN_xrGetHandMeshFB xrGetHandMeshFB = nullptr;
PFN_xrLocateHandJointsEXT xrLocateHandJointsEXT = nullptr;
PFN_xrCreateHandTrackerEXT xrCreateHandTrackerEXT = nullptr;
PFN_xrDestroyHandTrackerEXT xrDestroyHandTrackerEXT = nullptr;
virtual XrPosef TransformXrHandJointPose(EOculusXRHandType HandType, XrHandJointEXT Joint, XrPosef JointPose, XrPosef RootPose, bool ApplyRootPose);
virtual XrHandJointEXT MapBoneToXrHandJoint(EHandBoneId Bone);
virtual EHandBoneId MapXrHandJointToBone(XrHandJointEXT Joint);
virtual FVector XrPoseVectorToFVector(XrVector3f XrVector);
virtual FQuat XrPoseQuatToFQuat(XrQuaternionf XrQuat);
virtual FQuat XrBoneQuatToFQuat(XrQuaternionf XrQuat);
bool bExtHandTrackingMeshAvailable = false;
bool bHandTrackingAvailable = false;
bool bHandTrackingActive = false;
TSharedPtr<FGenericApplicationMessageHandler> MessageHandler;
TMap<EOculusXRHandType, XrHandTrackerEXT> OculusHandTrackers;
XrInstance Instance = XR_NULL_HANDLE;
FInternalHandsState InternalHandsState;
};
} // namespace OculusXRInput

View File

@@ -0,0 +1,245 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "khronos/openxr/openxr.h"
#include "xr_linear.h"
#include "CoreMinimal.h"
#include "OculusXRInputFunctionLibrary.h"
namespace OculusXRInput
{
const XrPosef XrLeftHandLegacyBindPoseThumb0 = {
{ 0.375387, 0.424584, -0.007779, 0.823864 },
{ 0.020069, 0.011554, -0.010497 }
};
const XrPosef XrRightHandLegacyBindPoseThumb0 = {
{ 0.375387, 0.424584, -0.007779, 0.823864 },
{ -0.020069, -0.011554, 0.010497 }
};
const XrPosef XrLeftHandLegacyBindPoseThumb1 = {
{ 0.260230, 0.024331, 0.125678, 0.957023 },
{ 0.024853, 0.000000, -0.000000 }
};
const XrPosef XrRightHandLegacyBindPoseThumb1 = {
{ 0.260230, 0.024331, 0.125678, 0.957023 },
{ -0.024853, 0.000000, 0.000000 }
};
const float SMALLEST_NON_DENORMAL = 1.1754943508222875e-038f; // ( 1U << 23 )
const int MaxHandCount = 2;
enum class EHandFinger
{
Thumb = 0,
Index = 1,
Middle = 2,
Ring = 3,
Pinky = 4,
Count = 5,
};
enum class EHandBoneId
{
Invalid = -1,
// hand bones
Start = 0,
WristRoot = Start + 0, // root frame of the hand, where the wrist is located
ForearmStub = Start + 1, // frame for user's forearm
Thumb0 = Start + 2, // thumb trapezium bone
Thumb1 = Start + 3, // thumb metacarpal bone
Thumb2 = Start + 4, // thumb proximal phalange bone
Thumb3 = Start + 5, // thumb distal phalange bone
Index1 = Start + 6, // index proximal phalange bone
Index2 = Start + 7, // index intermediate phalange bone
Index3 = Start + 8, // index distal phalange bone
Middle1 = Start + 9, // middle proximal phalange bone
Middle2 = Start + 10, // middle intermediate phalange bone
Middle3 = Start + 11, // middle distal phalange bone
Ring1 = Start + 12, // ring proximal phalange bone
Ring2 = Start + 13, // ring intermediate phalange bone
Ring3 = Start + 14, // ring distal phalange bone
Pinky0 = Start + 15, // pinky metacarpal bone
Pinky1 = Start + 16, // pinky proximal phalange bone
Pinky2 = Start + 17, // pinky intermediate phalange bone
Pinky3 = Start + 18, // pinky distal phalange bone
MaxSkinnable = Start + 19,
// Bone tips are position only. They are not used for skinning but useful for hit-testing.
// NOTE: ThumbTip == MaxSkinnable since the extended tips need to be contiguous
ThumbTip = MaxSkinnable + 0, // tip of the thumb
IndexTip = MaxSkinnable + 1, // tip of the index finger
MiddleTip = MaxSkinnable + 2, // tip of the middle finger
RingTip = MaxSkinnable + 3, // tip of the ring finger
PinkyTip = MaxSkinnable + 4, // tip of the pinky
End = MaxSkinnable + 5,
// Hand Skeleton V2 format
StartV2 = 0,
PalmV2 = StartV2 + 0, // PALM = 0,
WristV2 = StartV2 + 1, // WRIST = 1,
ThumbMetacarpalV2 = StartV2 + 2, // THUMB_METACARPAL = 2,
ThumbProximalV2 = StartV2 + 3, // THUMB_PROXIMAL = 3,
ThumbDistalV2 = StartV2 + 4, // THUMB_DISTAL = 4,
ThumbTipV2 = StartV2 + 5, // THUMB_TIP = 5,
IndexMetacarpalV2 = StartV2 + 6, // INDEX_METACARPAL = 6,
IndexProximalV2 = StartV2 + 7, // INDEX_PROXIMAL = 7,
IndexIntermediateV2 = StartV2 + 8, // INDEX_INTERMEDIATE = 8,
IndexDistalV2 = StartV2 + 9, // INDEX_DISTAL = 9,
IndexTipV2 = StartV2 + 10, // INDEX_TIP = 10,
MiddleMetacarpalV2 = StartV2 + 11, // MIDDLE_METACARPAL = 11,
MiddleProximalV2 = StartV2 + 12, // MIDDLE_PROXIMAL = 12,
MiddleIntermediateV2 = StartV2 + 13, // MIDDLE_INTERMEDIATE = 13,
MiddleDistalV2 = StartV2 + 14, // MIDDLE_DISTAL = 14,
MiddleTipV2 = StartV2 + 15, // MIDDLE_TIP = 15,
RingMetacarpalV2 = StartV2 + 16, // RING_METACARPAL = 16,
RingProximalV2 = StartV2 + 17, // RING_PROXIMAL = 17,
RingIntermediateV2 = StartV2 + 18, // RING_INTERMEDIATE = 18,
RingDistalV2 = StartV2 + 19, // RING_DISTAL = 19,
RingTipV2 = StartV2 + 20, // RING_TIP = 20,
LittleMetacarpalV2 = StartV2 + 21, // LITTLE_METACARPAL = 21,
LittleProximalV2 = StartV2 + 22, // LITTLE_PROXIMAL = 22,
LittleIntermediateV2 = StartV2 + 23, // LITTLE_INTERMEDIATE = 23,
LittleDistalV2 = StartV2 + 24, // LITTLE_DISTAL = 24,
LittleTipV2 = StartV2 + 25, // LITTLE_TIP = 25,
EndV2 = StartV2 + 26,
// add other skeleton bone definitions here...
Max = EndV2,
};
enum class EHandFingerPinch
{
Thumb = (1 << static_cast<int32>(EHandFinger::Thumb)),
Index = (1 << static_cast<int32>(EHandFinger::Index)),
Middle = (1 << static_cast<int32>(EHandFinger::Middle)),
Ring = (1 << static_cast<int32>(EHandFinger::Ring)),
Pinky = (1 << static_cast<int32>(EHandFinger::Pinky)),
Max,
};
enum class EHandSkeletonConstants
{
MaxHandBones = static_cast<int32>(EHandBoneId::End),
MaxHandBones_V2 = static_cast<int32>(EHandBoneId::EndV2),
MaxBones = static_cast<int32>(EHandBoneId::Max),
MaxBoneCapsules = 19,
};
struct FHandBoneCapsule
{
short BoneIndex;
// Points at either end of the cylinder inscribed in the capsule. Also the center points for
// spheres at either end of the capsule. Points A and B in the diagram above.
XrVector3f Points[2];
// The radius of the capsule cylinder and of the half-sphere caps on the ends of the capsule.
float Radius;
};
struct FHandBone
{
EHandBoneId BoneId;
// index of this bone's parent bone (-1 if no parent)
short ParentBoneIndex;
XrPosef Pose;
};
struct FHandSkeleton
{
EOculusXRHandType HandType;
unsigned int NumBones;
unsigned int NumBoneCapsules;
FHandBone Bones[static_cast<int32>(EHandSkeletonConstants::MaxBones)];
FHandBoneCapsule BoneCapsules[static_cast<int32>(EHandSkeletonConstants::MaxBoneCapsules)];
};
enum class EHandMeshConstants
{
MaxVertices = 3000,
MaxIndices = MaxVertices * 6,
};
struct FHandMesh
{
// Type of mesh this data describes.
EOculusXRHandType HandType;
// Number of unique vertices in the mesh.
unsigned int NumVertices;
// Number of unique indices in the mesh.
unsigned int NumIndices;
// An array of count NumVertices positions for each vertex. Always valid.
XrVector3f VertexPositions[static_cast<int32>(EHandMeshConstants::MaxVertices)];
// An array of count NumIndices of vertex indices specifying triangles that make up the mesh. Always valid.
short Indices[static_cast<int32>(EHandMeshConstants::MaxIndices)];
// An array of count NumVertices of normals for each vertex.
// If null, this attribute is not used.
XrVector3f VertexNormals[static_cast<int32>(EHandMeshConstants::MaxVertices)];
// An array of count NumVertices of texture coordinates for each vertex.
// If null, this attribute is not used.
XrVector2f VertexUV0[static_cast<int32>(EHandMeshConstants::MaxVertices)];
// An array of count NumVertices of blend indices for each of the bones that each vertex is weighted to.
// Always valid. An index of < 0 means no blend weight.
XrVector4sFB BlendIndices[static_cast<int32>(EHandMeshConstants::MaxVertices)];
// An array of count NumVertices of weights for each of the bones affecting each vertex. Always valid.
XrVector4f BlendWeights[static_cast<int32>(EHandMeshConstants::MaxVertices)];
};
enum class EHandStatus
{
HandTracked = (1 << 0), // hand is currently tracked by hand tracking
InputValid = (1 << 1), // if this is set the pointer pose and pinch data is usable
SystemGestureInProgress = (1 << 6), // if this is set the user is performing the system gesture
DominantHand = (1 << 7), // if this is set the hand is considered the dominant hand
MenuPressed = (1 << 8), // if this is set the hand performed the system gesture as the non-dominant hand
};
struct FInternalHandState
{
// Hand Status bitfield described by ovrpHandStatus flags.
unsigned int Status;
// Root pose of the hand in world space. Not to be confused with the root bone's transform.
// The root bone can still be offset from this by the skeleton's rest pose.
XrPosef RootPose;
// Current rotation of each bone.
XrQuaternionf BoneRotations[static_cast<int32>(EHandSkeletonConstants::MaxHandBones)];
// Provides a bitmask indicating if each finger is "pinched" or not. Indexable via bitshifting with the ovrpHandFinger
// enum i.e. (1 << ovrpHandFinger_Index)
unsigned int Pinches;
// Provides a 0.0f to 1.0f value of how "pinched" each finger is. Indexable via the ovrpHandFinger enum.
float PinchStrength[static_cast<int32>(EHandFinger::Count)];
// World space position and translation of the pointer attached to the hand.
XrPosef PointerPose;
float HandScale;
// Tracking confidence. Range [0,1], 0.0 = lowest confidence, 1.0 = highest confidence.
// This is useful for smoothly de-emphasizing hands as confidence decreases.
// This is the amount of confidence that the system has that the entire hand pose is correct.
EOculusXRTrackingConfidence HandConfidence;
// Per-finger tracking confidence. Range [0,1], 0.0 = lowest confidence, 1.0 = highest confidence.
// This is the amount of confidence the system has that the individual finger poses are correct.
EOculusXRTrackingConfidence FingerConfidences[static_cast<int32>(EHandFinger::Count)];
// Time stamp for the pose that was requested in global system time.
double RequestedTimeStamp;
// Time stamp of the captured sample that the pose was extrapolated from.
double SampleTimeStamp;
bool PosesGeneratedByControllerData;
};
struct FInternalHandsState
{
FInternalHandState HandState[MaxHandCount];
XrTime PredictedDisplayTime;
bool bHandMenuButtonPressed = false;
};
} // namespace OculusXRInput

View File

@@ -0,0 +1,214 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRInputHapticsExtensionPlugin.h"
#include "IOpenXRHMDModule.h"
#include "OculusXRInputExtensionPlugin.h"
#include "OculusXRInputXRFunctions.h"
#include "OpenXRCore.h"
namespace OculusXRInput
{
XrInstance FInputHapticsExtensionPlugin::GetOpenXRInstance() const
{
return Instance;
}
XrSession FInputHapticsExtensionPlugin::GetOpenXRSession() const
{
return Session;
}
bool FInputHapticsExtensionPlugin::IsPCMExtensionAvailable() const
{
return bExtFBHapticsPcmAvailable;
}
bool FInputHapticsExtensionPlugin::IsAmplitudeEnvelopeExtensionAvailable() const
{
return bExtFBAmplitudeEnvelopeAvailable;
}
bool FInputHapticsExtensionPlugin::IsTouchControllerProExtensionAvailable() const
{
return bExtFBTouchControllerProAvailable;
}
XrAction FInputHapticsExtensionPlugin::GetXrHandHapticVibrationAction() const
{
return XrHandHapticVibrationAction;
}
XrAction FInputHapticsExtensionPlugin::GetXrThumbHapticVibrationAction() const
{
return XrThumbHapticVibrationAction;
}
XrAction FInputHapticsExtensionPlugin::GetXrIndexHapticVibrationAction() const
{
return XrIndexHapticVibrationAction;
}
XrPath* FInputHapticsExtensionPlugin::GetXrHandsSubactionPaths()
{
return XrPathBothHands;
}
XrPath* FInputHapticsExtensionPlugin::GetXrHandsHapticsSubactionPaths()
{
return XrPathBothHandsHaptics;
}
XrPath* FInputHapticsExtensionPlugin::GetXrThumbsHapticsSubactionPaths()
{
return XrPathBothThumbsHaptics;
}
XrPath* FInputHapticsExtensionPlugin::GetXrIndexesHapticsSubactionPaths()
{
return XrPathBothIndexesHaptics;
}
bool FInputHapticsExtensionPlugin::GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add(XR_FB_HAPTIC_PCM_EXTENSION_NAME);
OutExtensions.Add(XR_FB_HAPTIC_AMPLITUDE_ENVELOPE_EXTENSION_NAME);
OutExtensions.Add(XR_FB_TOUCH_CONTROLLER_PRO_EXTENSION_NAME);
return true;
}
const void* FInputHapticsExtensionPlugin::OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext)
{
bExtFBHapticsPcmAvailable = InModule->IsExtensionEnabled(XR_FB_HAPTIC_PCM_EXTENSION_NAME);
bExtFBAmplitudeEnvelopeAvailable = InModule->IsExtensionEnabled(XR_FB_HAPTIC_AMPLITUDE_ENVELOPE_EXTENSION_NAME);
bExtFBTouchControllerProAvailable = InModule->IsExtensionEnabled(XR_FB_TOUCH_CONTROLLER_PRO_EXTENSION_NAME);
return InNext;
}
void FInputHapticsExtensionPlugin::PostCreateInstance(XrInstance InInstance)
{
Instance = InInstance;
InitOpenXRFunctions(Instance);
}
void FInputHapticsExtensionPlugin::PostCreateSession(XrSession InSession)
{
Session = InSession;
}
void FInputHapticsExtensionPlugin::CreateHapticActions()
{
// Create action set
HapticsActionSet = XR_NULL_HANDLE;
XrActionSetCreateInfo ActionSetInfo{ XR_TYPE_ACTION_SET_CREATE_INFO };
ActionSetInfo.next = nullptr;
FCStringAnsi::Strcpy(ActionSetInfo.actionSetName, XR_MAX_ACTION_SET_NAME_SIZE, "oculushapticsactionset");
FCStringAnsi::Strcpy(ActionSetInfo.localizedActionSetName, XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE, "OculusHapticsActionSet");
XR_ENSURE(xrCreateActionSet(Instance, &ActionSetInfo, &HapticsActionSet));
// Create hand haptics paths
XR_ENSURE(xrStringToPath(Instance, "/user/hand/left", &XrPathLeftHand));
XR_ENSURE(xrStringToPath(Instance, "/user/hand/left/output/haptic", &XrPathLeftHandHaptics));
XR_ENSURE(xrStringToPath(Instance, "/user/hand/right", &XrPathRightHand));
XR_ENSURE(xrStringToPath(Instance, "/user/hand/right/output/haptic", &XrPathRightHandHaptics));
XrPathBothHands[0] = XrPathLeftHand;
XrPathBothHands[1] = XrPathRightHand;
XrPathBothHandsHaptics[0] = XrPathLeftHandHaptics;
XrPathBothHandsHaptics[1] = XrPathRightHandHaptics;
// Create localized haptics paths
XR_ENSURE(xrStringToPath(Instance, "/interaction_profiles/facebook/touch_controller_pro", &XrQuestProInteractionProfile));
XR_ENSURE(xrStringToPath(Instance, "/user/hand/left/output/thumb_haptic_fb", &XrPathLeftThumbHaptics));
XR_ENSURE(xrStringToPath(Instance, "/user/hand/left/output/trigger_haptic_fb", &XrPathLeftIndexHaptics));
XR_ENSURE(xrStringToPath(Instance, "/user/hand/right/output/thumb_haptic_fb", &XrPathRightThumbHaptics));
XR_ENSURE(xrStringToPath(Instance, "/user/hand/right/output/trigger_haptic_fb", &XrPathRightIndexHaptics));
XrPathBothThumbsHaptics[0] = XrPathLeftThumbHaptics;
XrPathBothThumbsHaptics[1] = XrPathRightThumbHaptics;
XrPathBothIndexesHaptics[0] = XrPathLeftIndexHaptics;
XrPathBothIndexesHaptics[1] = XrPathRightIndexHaptics;
// Create actions
const auto CreateVibrationOutputAction = [this](const char* ActionName) {
XrActionCreateInfo ActionCreateInfo{ XR_TYPE_ACTION_CREATE_INFO };
ActionCreateInfo.next = nullptr;
ActionCreateInfo.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT;
FCStringAnsi::Strcpy(ActionCreateInfo.actionName, XR_MAX_ACTION_NAME_SIZE, ActionName);
FCStringAnsi::Strcpy(ActionCreateInfo.localizedActionName, XR_MAX_LOCALIZED_ACTION_NAME_SIZE, ActionName);
ActionCreateInfo.countSubactionPaths = sizeof(XrPathBothHands) / sizeof(XrPath);
ActionCreateInfo.subactionPaths = XrPathBothHands;
XrAction Action = XR_NULL_HANDLE;
XR_ENSURE(xrCreateAction(HapticsActionSet, &ActionCreateInfo, &Action));
return Action;
};
XrHandHapticVibrationAction = CreateVibrationOutputAction("hand_haptic_vibration");
XrThumbHapticVibrationAction = CreateVibrationOutputAction("hand_thumb_haptic_vibration");
XrIndexHapticVibrationAction = CreateVibrationOutputAction("hand_index_haptic_vibration");
}
bool FInputHapticsExtensionPlugin::GetSuggestedBindings(XrPath InInteractionProfile, TArray<XrActionSuggestedBinding>& OutBindings)
{
if (HapticsActionSet == XR_NULL_HANDLE)
{
return false;
}
OutBindings.Add({ XrHandHapticVibrationAction, XrPathLeftHandHaptics });
OutBindings.Add({ XrHandHapticVibrationAction, XrPathRightHandHaptics });
if (InInteractionProfile == XrQuestProInteractionProfile)
{
OutBindings.Add({ XrIndexHapticVibrationAction, XrPathLeftIndexHaptics });
OutBindings.Add({ XrIndexHapticVibrationAction, XrPathRightIndexHaptics });
OutBindings.Add({ XrThumbHapticVibrationAction, XrPathLeftThumbHaptics });
OutBindings.Add({ XrThumbHapticVibrationAction, XrPathRightThumbHaptics });
}
return true;
}
void FInputHapticsExtensionPlugin::AttachActionSets(TSet<XrActionSet>& OutActionSets)
{
if (HapticsActionSet != XR_NULL_PATH)
{
OutActionSets.Add(HapticsActionSet);
}
}
void FInputHapticsExtensionPlugin::GetActiveActionSetsForSync(
TArray<XrActiveActionSet>& OutActiveSets)
{
if (HapticsActionSet != XR_NULL_PATH)
{
OutActiveSets.Add({ HapticsActionSet, XR_NULL_PATH });
}
}
bool FInputHapticsExtensionPlugin::GetInteractionProfile(XrInstance InInstance, FString& OutKeyPrefix, XrPath& OutPath,
bool& OutHasHaptics)
{
// Called at the start of Epic's input action creation
if (HapticsActionSet != XR_NULL_HANDLE)
{
DestroyHapticActions();
}
CreateHapticActions();
return true;
}
void FInputHapticsExtensionPlugin::DestroyHapticActions()
{
if (HapticsActionSet != XR_NULL_HANDLE)
{
xrDestroyActionSet(HapticsActionSet);
XrHandHapticVibrationAction = XR_NULL_HANDLE;
XrThumbHapticVibrationAction = XR_NULL_HANDLE;
XrIndexHapticVibrationAction = XR_NULL_HANDLE;
HapticsActionSet = XR_NULL_HANDLE;
}
}
} // namespace OculusXRInput

View File

@@ -0,0 +1,82 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "khronos/openxr/openxr.h"
#include "CoreMinimal.h"
#include "IOpenXRExtensionPlugin.h"
namespace OculusXRInput
{
class FInputHapticsExtensionPlugin : public IOpenXRExtensionPlugin
{
public:
void RegisterOpenXRExtensionPlugin()
{
#if defined(WITH_OCULUS_BRANCH)
RegisterOpenXRExtensionModularFeature();
#endif
}
XrInstance GetOpenXRInstance() const;
XrSession GetOpenXRSession() const;
bool IsPCMExtensionAvailable() const;
bool IsAmplitudeEnvelopeExtensionAvailable() const;
bool IsTouchControllerProExtensionAvailable() const;
XrAction GetXrHandHapticVibrationAction() const;
XrAction GetXrThumbHapticVibrationAction() const;
XrAction GetXrIndexHapticVibrationAction() const;
XrPath* GetXrHandsSubactionPaths();
XrPath* GetXrHandsHapticsSubactionPaths();
XrPath* GetXrThumbsHapticsSubactionPaths();
XrPath* GetXrIndexesHapticsSubactionPaths();
// IOpenXRExtensionPlugin
virtual bool GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual const void* OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) override;
virtual void PostCreateInstance(XrInstance InInstance) override;
virtual void PostCreateSession(XrSession InSession) override;
virtual bool GetSuggestedBindings(XrPath InInteractionProfile, TArray<XrActionSuggestedBinding>& OutBindings) override;
virtual void AttachActionSets(TSet<XrActionSet>& OutActionSets) override;
virtual void GetActiveActionSetsForSync(TArray<XrActiveActionSet>& OutActiveSets) override;
virtual bool GetInteractionProfile(XrInstance InInstance, FString& OutKeyPrefix, XrPath& OutPath, bool& OutHasHaptics) override;
private:
void CreateHapticActions();
void DestroyHapticActions();
bool bExtFBHapticsPcmAvailable = false;
bool bExtFBAmplitudeEnvelopeAvailable = false;
bool bExtFBTouchControllerProAvailable = false;
XrInstance Instance = XR_NULL_HANDLE;
XrSession Session = XR_NULL_HANDLE;
XrActionSet HapticsActionSet = XR_NULL_HANDLE;
XrAction XrHandHapticVibrationAction = XR_NULL_HANDLE;
XrPath XrPathLeftHand = XR_NULL_PATH;
XrPath XrPathLeftHandHaptics = XR_NULL_PATH;
XrPath XrPathRightHand = XR_NULL_PATH;
XrPath XrPathRightHandHaptics = XR_NULL_PATH;
XrPath XrPathBothHands[2] = { XR_NULL_PATH, XR_NULL_PATH };
XrPath XrPathBothHandsHaptics[2] = { XR_NULL_PATH, XR_NULL_PATH };
// Used for localized haptics
XrAction XrThumbHapticVibrationAction = XR_NULL_HANDLE;
XrAction XrIndexHapticVibrationAction = XR_NULL_HANDLE;
XrPath XrQuestProInteractionProfile = XR_NULL_PATH;
XrPath XrPathRightIndexHaptics = XR_NULL_PATH;
XrPath XrPathRightThumbHaptics = XR_NULL_PATH;
XrPath XrPathLeftThumbHaptics = XR_NULL_PATH;
XrPath XrPathLeftIndexHaptics = XR_NULL_PATH;
XrPath XrPathBothThumbsHaptics[2] = { XR_NULL_PATH, XR_NULL_PATH };
XrPath XrPathBothIndexesHaptics[2] = { XR_NULL_PATH, XR_NULL_PATH };
};
} // namespace OculusXRInput

View File

@@ -0,0 +1,105 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRInputModule.h"
#if OCULUS_INPUT_SUPPORTED_PLATFORMS
#include "OculusXRInput.h"
#include "OculusXRHMDModule.h"
#define LOCTEXT_NAMESPACE "OculusXRInput"
//-------------------------------------------------------------------------------------------------
// FOculusXRInputModule
//-------------------------------------------------------------------------------------------------
OculusXRInput::FInputExtensionPlugin* FOculusXRInputModule::GetInputOpenXRExtension() const
{
return InputExtensionPlugin.Get();
}
OculusXRInput::FHandTrackingExtensionPlugin* FOculusXRInputModule::GetHandTrackingOpenXRExtension() const
{
return HandTrackingExtensionPlugin.Get();
}
OculusXRInput::FInputHapticsExtensionPlugin* FOculusXRInputModule::GetHapticsOpenXRExtension() const
{
return HapticsExtensionPlugin.Get();
}
void FOculusXRInputModule::StartupModule()
{
IInputDeviceModule::StartupModule();
OculusXRInput::FOculusXRInput::PreInit();
HapticsExtensionPlugin = MakeShareable(new OculusXRInput::FInputHapticsExtensionPlugin());
HapticsExtensionPlugin->RegisterOpenXRExtensionPlugin();
HandTrackingExtensionPlugin = MakeShareable(new OculusXRInput::FHandTrackingExtensionPlugin());
HandTrackingExtensionPlugin->RegisterOpenXRExtensionPlugin();
InputExtensionPlugin = MakeShareable(new OculusXRInput::FInputExtensionPlugin());
InputExtensionPlugin->RegisterOpenXRExtensionPlugin();
TouchProInputExtensionPlugin = MakeShareable(new OculusXRInput::FTouchProInputExtensionPlugin());
TouchProInputExtensionPlugin->RegisterOpenXRExtensionPlugin();
TouchPlusInputExtensionPlugin = MakeShareable(new OculusXRInput::FTouchPlusInputExtensionPlugin());
TouchPlusInputExtensionPlugin->RegisterOpenXRExtensionPlugin();
}
void FOculusXRInputModule::ShutdownModule()
{
OculusXRInput::FOculusXRInput::ShutdownXRFunctionLibrary();
}
TSharedPtr<class IInputDevice> FOculusXRInputModule::CreateInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)
{
InputExtensionPlugin->SetMessageHandler(InMessageHandler);
HandTrackingExtensionPlugin->SetMessageHandler(InMessageHandler);
if (IOculusXRHMDModule::IsAvailable())
{
if (FOculusXRHMDModule::Get().PreInit())
{
TSharedPtr<OculusXRInput::FOculusXRInput> InputDevice(new OculusXRInput::FOculusXRInput(InMessageHandler));
OculusXRInputDevice = InputDevice;
return InputDevice;
}
// else, they may just not have a oculus headset plugged in (which we have to account for - no need for a warning)
}
else
{
UE_LOG(LogOcInput, Warning, TEXT("OculusXRInput plugin enabled, but OculusXRHMD plugin is not available."));
}
return nullptr;
}
uint32 FOculusXRInputModule::GetNumberOfTouchControllers() const
{
if (OculusXRInputDevice.IsValid())
{
return OculusXRInputDevice.Pin()->GetNumberOfTouchControllers();
}
return 0;
}
uint32 FOculusXRInputModule::GetNumberOfHandControllers() const
{
if (OculusXRInputDevice.IsValid())
{
return OculusXRInputDevice.Pin()->GetNumberOfHandControllers();
}
return 0;
}
TSharedPtr<IInputDevice> FOculusXRInputModule::GetInputDevice() const
{
if (OculusXRInputDevice.IsValid())
{
return OculusXRInputDevice.Pin();
}
return nullptr;
}
#endif // OCULUS_INPUT_SUPPORTED_PLATFORMS
IMPLEMENT_MODULE(FOculusXRInputModule, OculusXRInput)
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,76 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "IInputDevice.h"
#include "IOculusXRInputModule.h"
#include "OculusXRInputExtensionPlugin.h"
#include "OculusXRInputHapticsExtensionPlugin.h"
#include "OculusXRInputHandTrackingExtensionPlugin.h"
#include "OculusXRTouchPlusInputExtensionPlugin.h"
#include "OculusXRTouchProInputExtensionPlugin.h"
#include "Templates/SharedPointer.h"
#define LOCTEXT_NAMESPACE "OculusXRInput"
//-------------------------------------------------------------------------------------------------
// FOculusXRInputModule
//-------------------------------------------------------------------------------------------------
#if OCULUS_INPUT_SUPPORTED_PLATFORMS
namespace OculusXRInput
{
class FOculusXRInput;
}
class FOculusXRInputModule : public IOculusXRInputModule
{
public:
OculusXRInput::FInputExtensionPlugin* GetInputOpenXRExtension() const;
OculusXRInput::FHandTrackingExtensionPlugin* GetHandTrackingOpenXRExtension() const;
OculusXRInput::FInputHapticsExtensionPlugin* GetHapticsOpenXRExtension() const;
TWeakPtr<OculusXRInput::FOculusXRInput> OculusXRInputDevice;
// IInputDeviceModule overrides
virtual void StartupModule() override;
virtual void ShutdownModule() override;
virtual TSharedPtr<class IInputDevice> CreateInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) override;
// IOculusXRInputModule overrides
virtual uint32 GetNumberOfTouchControllers() const override;
virtual uint32 GetNumberOfHandControllers() const override;
virtual TSharedPtr<IInputDevice> GetInputDevice() const override;
private:
typedef TSharedPtr<OculusXRInput::FInputHapticsExtensionPlugin, ESPMode::ThreadSafe> FHapticsExtensionPluginPtr;
FHapticsExtensionPluginPtr HapticsExtensionPlugin;
typedef TSharedPtr<OculusXRInput::FHandTrackingExtensionPlugin, ESPMode::ThreadSafe> FHandTrackingExtensionPluginPtr;
FHandTrackingExtensionPluginPtr HandTrackingExtensionPlugin;
typedef TSharedPtr<OculusXRInput::FInputExtensionPlugin, ESPMode::ThreadSafe> FInputExtensionPluginPtr;
FInputExtensionPluginPtr InputExtensionPlugin;
typedef TSharedPtr<OculusXRInput::FTouchProInputExtensionPlugin, ESPMode::ThreadSafe> FTouchProInputExtensionPluginPtr;
FTouchProInputExtensionPluginPtr TouchProInputExtensionPlugin;
typedef TSharedPtr<OculusXRInput::FTouchPlusInputExtensionPlugin, ESPMode::ThreadSafe> FTouchPlusInputExtensionPluginPtr;
FTouchPlusInputExtensionPluginPtr TouchPlusInputExtensionPlugin;
};
#else // OCULUS_INPUT_SUPPORTED_PLATFORMS
class FOculusXRInputModule : public FDefaultModuleImpl
{
virtual uint32 GetNumberOfTouchControllers() const
{
return 0;
};
virtual uint32 GetNumberOfHandControllers() const
{
return 0;
};
};
#endif // OCULUS_INPUT_SUPPORTED_PLATFORMS
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,125 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRInputOVR.h"
#include "Haptics/HapticFeedbackEffect_Base.h"
namespace OculusXRInput
{
FOculusXRInput* GetOculusXRInput()
{
const TSharedPtr<FOculusXRInput> OculusXRInput = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice());
if (!OculusXRInput)
{
UE_LOG(LogOcInput, Error, TEXT("Failed getting Oculus XR input."));
return nullptr;
}
return OculusXRInput.Get();
}
float FOculusXRInputOVR::GetControllerSampleRateHz(EControllerHand Hand) const
{
float SampleRateHz = 0.f;
const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch;
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetControllerSampleRateHz(OvrpController, &SampleRateHz)))
{
UE_LOG(LogOcInput, Error, TEXT("GetControllerSampleRateHz failed."));
}
return SampleRateHz;
}
int FOculusXRInputOVR::GetMaxHapticDuration(EControllerHand Hand) const
{
const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch;
if (!GetOculusXRInput()->GetOvrpHapticsDesc((int32)Hand))
return 0;
return GetOculusXRInput()->OvrpHapticsDesc.MaximumBufferSamplesCount / GetOculusXRInput()->OvrpHapticsDesc.SampleRateHz;
}
void FOculusXRInputOVR::PlayHapticEffect(
UHapticFeedbackEffect_Base* HapticEffect, EControllerHand Hand,
EOculusXRHandHapticsLocation Location, bool bAppend, float Scale,
bool bLoop)
{
if (HapticEffect)
{
switch (Hand)
{
case EControllerHand::Left:
GetOculusXRInput()->ActiveHapticEffect_Left.Reset();
GetOculusXRInput()->HapticsDesc_Left.Reset();
GetOculusXRInput()->ActiveHapticEffect_Left = MakeShareable(new FActiveHapticFeedbackEffect(HapticEffect, Scale, bLoop));
GetOculusXRInput()->HapticsDesc_Left = MakeShareable(new FOculusXRHapticsDesc(Location, bAppend));
break;
case EControllerHand::Right:
GetOculusXRInput()->ActiveHapticEffect_Right.Reset();
GetOculusXRInput()->HapticsDesc_Right.Reset();
GetOculusXRInput()->ActiveHapticEffect_Right = MakeShareable(new FActiveHapticFeedbackEffect(HapticEffect, Scale, bLoop));
GetOculusXRInput()->HapticsDesc_Right = MakeShareable(new FOculusXRHapticsDesc(Location, bAppend));
break;
default:
UE_LOG(LogOcInput, Warning, TEXT("Invalid hand specified (%d) for haptic feedback effect %s"), (int32)Hand, *HapticEffect->GetName());
break;
}
}
}
void FOculusXRInputOVR::PlayAmplitudeEnvelopeHapticEffect(EControllerHand Hand,
int SamplesCount, void* Samples,
int InSampleRate)
{
int TimeToSend = GetMaxHapticDuration(Hand);
if (TimeToSend == 0)
{
return;
}
const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch;
int SampleRate = (InSampleRate > 0 ? InSampleRate : GetOculusXRInput()->OvrpHapticsDesc.SampleRateHz);
int MaxSamplesCount = TimeToSend * SampleRate;
if (SamplesCount > MaxSamplesCount || SamplesCount < GetOculusXRInput()->OvrpHapticsDesc.MinimumBufferSamplesCount)
{
UE_LOG(LogOcInput, Error, TEXT("Sample count should be between %d and %d which last %d time."),
GetOculusXRInput()->OvrpHapticsDesc.MinimumBufferSamplesCount, MaxSamplesCount, TimeToSend);
}
int WantToSend = FMath::Min(SamplesCount, MaxSamplesCount);
WantToSend = FMath::Max(WantToSend, GetOculusXRInput()->OvrpHapticsDesc.MinimumBufferSamplesCount);
float* BufferToSend = (float*)FMemory::Malloc(WantToSend * sizeof(*BufferToSend));
for (int i = 0; i < WantToSend; i++)
{
float Amplitude = ((uint8_t*)Samples)[i] / 255.0f;
Amplitude = FMath::Min(1.0f, Amplitude);
Amplitude = FMath::Max(0.0f, Amplitude);
BufferToSend[i] = Amplitude;
UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("amplitude, %.3f"), Amplitude);
}
ovrpHapticsAmplitudeEnvelopeVibration HapticsVibration;
HapticsVibration.Duration = WantToSend / SampleRate;
HapticsVibration.AmplitudeCount = WantToSend;
HapticsVibration.Amplitudes = BufferToSend;
FOculusXRHMDModule::GetPluginWrapper().SetControllerHapticsAmplitudeEnvelope(
OvrpController,
HapticsVibration);
UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("HAEHaptics is finished: AmplitudeCount: %d, SampleRate: %d"),
HapticsVibration.AmplitudeCount,
SampleRate);
if (BufferToSend)
{
FMemory::Free(BufferToSend);
}
}
void FOculusXRInputOVR::SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location)
{
const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch;
FOculusXRHMDModule::GetPluginWrapper().SetControllerLocalizedVibration(OvrpController, GetOculusXRInput()->GetOVRPHapticsLocation(Location), Frequency, Amplitude);
UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("LocalizedVibration is finished: Location: %d, Frequency: %f, Amplitude: %f"), (int)(Location), Frequency, Amplitude);
}
} // namespace OculusXRInput

View File

@@ -0,0 +1,23 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRInput.h"
namespace OculusXRInput
{
class FOculusXRInputOVR : public IOculusXRInputBase
{
public:
virtual void PlayHapticEffect(UHapticFeedbackEffect_Base* HapticEffect,
EControllerHand Hand,
EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand,
bool bAppend = false,
float Scale = 1.f,
bool bLoop = false) override;
virtual void PlayAmplitudeEnvelopeHapticEffect(EControllerHand Hand, int SamplesCount, void* Samples, int SampleRate = -1) override;
virtual void SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand) override;
virtual float GetControllerSampleRateHz(EControllerHand Hand) const override;
virtual int GetMaxHapticDuration(EControllerHand Hand) const override;
};
} // namespace OculusXRInput

View File

@@ -0,0 +1,356 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRInputOpenXR.h"
#include "Haptics/HapticFeedbackEffect_Base.h"
#include "OculusXRInputExtensionPlugin.h"
#include "OculusXRInputModule.h"
#include "OculusXRInputXRFunctions.h"
#include "OpenXR/OculusXROpenXRUtilities.h"
#include "OpenXRCore.h"
#include <vector>
namespace OculusXRInput
{
FOculusXRInputModule* GetInputModule()
{
FOculusXRInputModule* InputModule = static_cast<FOculusXRInputModule*>(&FOculusXRInputModule::Get());
if (!InputModule)
{
UE_LOG(LogOcInput, Error, TEXT("Failed getting Oculus XR input module."));
return nullptr;
}
return InputModule;
}
bool InstanceAndSessionAreValid(
const FOculusXRInputModule* InputModule)
{
if (!InputModule->GetHapticsOpenXRExtension()->GetOpenXRInstance() || !InputModule->GetHapticsOpenXRExtension()->GetOpenXRSession())
{
UE_LOG(LogOcInput, Error, TEXT("Failed getting OpenXR instance or session."));
return false;
}
return true;
}
int ControllerHandToHandIndex(EControllerHand Hand)
{
int HandIndex = -1;
switch (Hand)
{
case EControllerHand::Left:
HandIndex = 0;
break;
case EControllerHand::Right:
HandIndex = 1;
break;
default:
UE_LOG(LogOcInput, Error, TEXT("No action defined for %s."), *UEnum::GetValueAsString(Hand));
}
return HandIndex;
}
XrAction LocationToXrAction(EOculusXRHandHapticsLocation Location)
{
const FOculusXRInputModule* InputModule = GetInputModule();
if (Location != EOculusXRHandHapticsLocation::Hand && !InputModule->GetHapticsOpenXRExtension()->IsTouchControllerProExtensionAvailable())
{
UE_LOG(LogOcInput, Warning, TEXT("Touch Controller Pro extension is not available."));
return XR_NULL_HANDLE;
}
switch (Location)
{
case EOculusXRHandHapticsLocation::Hand:
return InputModule->GetHapticsOpenXRExtension()->GetXrHandHapticVibrationAction();
case EOculusXRHandHapticsLocation::Thumb:
return InputModule->GetHapticsOpenXRExtension()->GetXrThumbHapticVibrationAction();
case EOculusXRHandHapticsLocation::Index:
return InputModule->GetHapticsOpenXRExtension()->GetXrIndexHapticVibrationAction();
default:
UE_LOG(LogOcInput, Warning, TEXT("Invalid location specified (%d)"), (int32)Location);
}
return XR_NULL_HANDLE;
}
float FOculusXRInputOpenXR::GetControllerSampleRateHz(EControllerHand Hand) const
{
const FOculusXRInputModule* InputModule = GetInputModule();
if (!InputModule || !InstanceAndSessionAreValid(InputModule))
{
return 0.f;
}
if (!InputModule->GetHapticsOpenXRExtension()->IsPCMExtensionAvailable())
{
UE_LOG(LogOcInput, Warning, TEXT("PCM extension is not available."));
return 0.f;
}
const int HandIndex = ControllerHandToHandIndex(Hand);
if (HandIndex == -1)
{
return 0.f;
}
XrHapticActionInfo HapticActionInfo = { XR_TYPE_HAPTIC_ACTION_INFO };
HapticActionInfo.action = InputModule->GetHapticsOpenXRExtension()->GetXrHandHapticVibrationAction();
HapticActionInfo.subactionPath = InputModule->GetHapticsOpenXRExtension()->GetXrHandsSubactionPaths()[HandIndex];
HapticActionInfo.next = nullptr;
XrDevicePcmSampleRateGetInfoFB DeviceSampleRate = { XR_TYPE_DEVICE_PCM_SAMPLE_RATE_GET_INFO_FB };
const XrResult result = xrGetDeviceSampleRateFB(InputModule->GetHapticsOpenXRExtension()->GetOpenXRSession(), &HapticActionInfo, &DeviceSampleRate);
if (XR_FAILED(result))
{
UE_LOG(LogOcInput, Error, TEXT("xrGetDeviceSampleRateFB failed."));
return 0.f;
}
return DeviceSampleRate.sampleRate;
}
int FOculusXRInputOpenXR::GetMaxHapticDuration(EControllerHand Hand) const
{
const float SampleRate = GetControllerSampleRateHz(Hand);
if (SampleRate == 0.f)
{
UE_LOG(LogOcInput, Warning, TEXT("Sample rate equals 0"));
return 0;
}
return XR_MAX_HAPTIC_PCM_BUFFER_SIZE_FB / SampleRate;
}
void FOculusXRInputOpenXR::Tick(float DeltaTime)
{
if (ActiveHapticEffect_Left.IsValid())
{
FHapticFeedbackValues LeftHaptics;
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();
}
SetHapticFeedbackValues(EControllerHand::Left, LeftHaptics, HapticsDesc_Left.Get());
}
if (ActiveHapticEffect_Right.IsValid())
{
FHapticFeedbackValues RightHaptics;
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();
}
SetHapticFeedbackValues(EControllerHand::Right, RightHaptics, HapticsDesc_Right.Get());
}
}
// Tick will only get called if the object is created in FOculusXRInput::GetOculusXRInputBaseImpl(), so we do not need ETickableTickType::Conditional
ETickableTickType FOculusXRInputOpenXR::GetTickableTickType() const
{
return ETickableTickType::Always;
}
TStatId FOculusXRInputOpenXR::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FOculusXRInputOpenXR, STATGROUP_Tickables);
}
void FOculusXRInputOpenXR::SetHapticFeedbackValues(EControllerHand Hand, const FHapticFeedbackValues& Values, FOculusXRHapticsDesc* HapticsDesc)
{
FHapticFeedbackBuffer* const HapticBuffer = Values.HapticBuffer;
const bool bHasBuffer = HapticBuffer && HapticBuffer->BufferLength > 0;
// UHapticFeedbackEffect_SoundWave
if (bHasBuffer)
{
const FOculusXRInputModule* InputModule = GetInputModule();
if (!InputModule || !InstanceAndSessionAreValid(InputModule))
{
return;
}
if (!InputModule->GetHapticsOpenXRExtension()->IsPCMExtensionAvailable())
{
UE_LOG(LogOcInput, Warning, TEXT("PCM extension is not available."));
return;
}
int SamplesToSend = 0.036f * HapticBuffer->SamplingRate; // Related to the duration that each PCM haptic batch lasts (36ms)
if (SamplesToSend == 0 || HapticBuffer->SamplesSent == HapticBuffer->BufferLength)
{
return;
}
// Makes sure we are not overloading it
SamplesToSend = FMath::Min(SamplesToSend, XR_MAX_HAPTIC_PCM_BUFFER_SIZE_FB);
SamplesToSend = FMath::Min(SamplesToSend, (HapticBuffer->BufferLength - HapticBuffer->SamplesSent) / 2);
uint32_t SamplesConsumed = 0;
std::vector<float> PCMBuffer(SamplesToSend);
for (int i = 0; i < SamplesToSend; i++)
{
const uint32 DataIndex = HapticBuffer->CurrentPtr + (i * 2);
const int16* const RawData = reinterpret_cast<const int16*>(&HapticBuffer->RawData[DataIndex]);
float SampleValue = (*RawData * HapticBuffer->ScaleFactor) / INT16_MAX;
SampleValue = FMath::Min(1.0f, SampleValue);
SampleValue = FMath::Max(-1.0f, SampleValue);
PCMBuffer[i] = SampleValue;
}
XrHapticActionInfo HapticActionInfo = { XR_TYPE_HAPTIC_ACTION_INFO };
HapticActionInfo.action = LocationToXrAction(HapticsDesc->Location);
HapticActionInfo.subactionPath = InputModule->GetHapticsOpenXRExtension()->GetXrHandsSubactionPaths()[ControllerHandToHandIndex(Hand)];
XrHapticPcmVibrationFB HapticPcmVibration = { XR_TYPE_HAPTIC_PCM_VIBRATION_FB };
HapticPcmVibration.buffer = PCMBuffer.data();
HapticPcmVibration.bufferSize = SamplesToSend;
HapticPcmVibration.sampleRate = HapticBuffer->SamplingRate;
HapticPcmVibration.samplesConsumed = &SamplesConsumed;
HapticPcmVibration.append = HapticsDesc->bIsFirstCall ? HapticsDesc->bAppend : true;
const XrResult result = xrApplyHapticFeedback(InputModule->GetHapticsOpenXRExtension()->GetOpenXRSession(), &HapticActionInfo, reinterpret_cast<XrHapticBaseHeader*>(&HapticPcmVibration));
if (XR_FAILED(result))
{
UE_LOG(LogOcInput, Error, TEXT("xrApplyHapticFeedback failed for PCM haptics with result %s"), OpenXRResultToString(result));
}
HapticsDesc->bIsFirstCall = false;
HapticBuffer->CurrentPtr = FMath::Min(HapticBuffer->CurrentPtr + SamplesConsumed * 2, static_cast<uint32>(HapticBuffer->BufferLength));
HapticBuffer->SamplesSent = FMath::Min(HapticBuffer->SamplesSent + SamplesConsumed * 2, static_cast<uint32>(HapticBuffer->BufferLength));
}
// UHapticFeedbackEffect_Curve and UHapticFeedbackEffect_Buffer
else
{
SetHapticsByValue(Values.Frequency, Values.Amplitude, Hand, HapticsDesc ? HapticsDesc->Location : EOculusXRHandHapticsLocation::Hand);
}
}
void FOculusXRInputOpenXR::PlayHapticEffect(
UHapticFeedbackEffect_Base* HapticEffect, EControllerHand Hand,
EOculusXRHandHapticsLocation Location, bool bAppend, float Scale,
bool bLoop)
{
if (!HapticEffect)
{
return;
}
switch (Hand)
{
case EControllerHand::Left:
ActiveHapticEffect_Left.Reset();
ActiveHapticEffect_Left = MakeShareable(new FActiveHapticFeedbackEffect(HapticEffect, Scale, bLoop));
HapticsDesc_Left.Reset();
HapticsDesc_Left = MakeShareable(new FOculusXRHapticsDesc(Location, bAppend));
break;
case EControllerHand::Right:
ActiveHapticEffect_Right.Reset();
ActiveHapticEffect_Right = MakeShareable(new FActiveHapticFeedbackEffect(HapticEffect, Scale, bLoop));
HapticsDesc_Right.Reset();
HapticsDesc_Right = MakeShareable(new FOculusXRHapticsDesc(Location, bAppend));
break;
default:
UE_LOG(LogOcInput, Warning, TEXT("Invalid hand specified (%d) for haptic feedback effect %s"), (int32)Hand, *HapticEffect->GetName());
break;
}
}
void FOculusXRInputOpenXR::PlayAmplitudeEnvelopeHapticEffect(EControllerHand Hand,
int SamplesCount,
void* Samples,
int InSampleRate)
{
const FOculusXRInputModule* InputModule = GetInputModule();
if (!InputModule || !InstanceAndSessionAreValid(InputModule))
{
return;
}
if (!InputModule->GetHapticsOpenXRExtension()->IsAmplitudeEnvelopeExtensionAvailable())
{
UE_LOG(LogOcInput, Warning, TEXT("Amplitude Envelope extension is not available."));
return;
}
const int MaxTimeToSend = GetMaxHapticDuration(Hand);
if (MaxTimeToSend == 0)
{
return;
}
const int SampleRate = InSampleRate > 0 ? InSampleRate : GetControllerSampleRateHz(Hand);
if (SamplesCount > XR_MAX_HAPTIC_AMPLITUDE_ENVELOPE_SAMPLES_FB || SamplesCount < 1)
{
UE_LOG(LogOcInput, Warning, TEXT("Sample count should be between 1 and %d which last %d seconds."), XR_MAX_HAPTIC_PCM_BUFFER_SIZE_FB, MaxTimeToSend);
}
const int AmplitudesCount = FMath::Min(SamplesCount, static_cast<int>(XR_MAX_HAPTIC_AMPLITUDE_ENVELOPE_SAMPLES_FB));
std::vector<float> AmplitudesToSend(AmplitudesCount);
for (int i = 0; i < AmplitudesCount; i++)
{
float Amplitude = static_cast<uint8_t*>(Samples)[i] / 255.0f;
Amplitude = FMath::Min(1.0f, Amplitude);
Amplitude = FMath::Max(0.0f, Amplitude);
AmplitudesToSend[i] = Amplitude;
}
XrHapticActionInfo HapticActionInfo = { XR_TYPE_HAPTIC_ACTION_INFO };
HapticActionInfo.action = InputModule->GetHapticsOpenXRExtension()->GetXrHandHapticVibrationAction();
HapticActionInfo.subactionPath = InputModule->GetHapticsOpenXRExtension()->GetXrHandsSubactionPaths()[ControllerHandToHandIndex(Hand)];
XrHapticAmplitudeEnvelopeVibrationFB HapticAmplitudeEnvelopeVibration = { XR_TYPE_HAPTIC_AMPLITUDE_ENVELOPE_VIBRATION_FB };
HapticAmplitudeEnvelopeVibration.duration = OculusXR::ToXrDuration(static_cast<float>(AmplitudesCount) / SampleRate);
HapticAmplitudeEnvelopeVibration.amplitudeCount = static_cast<uint32_t>(AmplitudesCount);
HapticAmplitudeEnvelopeVibration.amplitudes = AmplitudesToSend.data();
const XrResult result = xrApplyHapticFeedback(InputModule->GetHapticsOpenXRExtension()->GetOpenXRSession(), &HapticActionInfo, reinterpret_cast<XrHapticBaseHeader*>(&HapticAmplitudeEnvelopeVibration));
if (XR_FAILED(result))
{
UE_LOG(LogOcInput, Error, TEXT("xrApplyHapticFeedback failed for amplitude envelope haptics with result %s"), OpenXRResultToString(result));
}
}
void FOculusXRInputOpenXR::SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location)
{
const FOculusXRInputModule* InputModule = GetInputModule();
if (!InputModule || !InstanceAndSessionAreValid(InputModule))
{
return;
}
const int HandIndex = ControllerHandToHandIndex(Hand);
if (HandIndex == -1)
{
return;
}
XrHapticActionInfo HapticActionInfo = { XR_TYPE_HAPTIC_ACTION_INFO };
HapticActionInfo.action = LocationToXrAction(Location);
HapticActionInfo.subactionPath = InputModule->GetHapticsOpenXRExtension()->GetXrHandsSubactionPaths()[ControllerHandToHandIndex(Hand)];
XrHapticVibration Vibration = { XR_TYPE_HAPTIC_VIBRATION };
Vibration.amplitude = Amplitude;
Vibration.frequency = Frequency;
Vibration.duration = 2000000000; // 2 second duration, this is to give enough
// time for a new signal to be received without
// stopping the previous vibration
const XrResult Result = xrApplyHapticFeedback(InputModule->GetHapticsOpenXRExtension()->GetOpenXRSession(), &HapticActionInfo, reinterpret_cast<XrHapticBaseHeader*>(&Vibration));
if (XR_FAILED(Result))
{
UE_LOG(LogOcInput, Error, TEXT("xrApplyHapticFeedback failed with result %s"), OpenXRResultToString(Result));
}
}
} // namespace OculusXRInput

View File

@@ -0,0 +1,39 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRInput.h"
class FOculusXRInputModule;
namespace OculusXRInput
{
class FOculusXRInputOpenXR : public IOculusXRInputBase, public FTickableGameObject
{
public:
// IOculusXRInputBase overrides
virtual void PlayHapticEffect(UHapticFeedbackEffect_Base* HapticEffect,
EControllerHand Hand,
EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand,
bool bAppend = false,
float Scale = 1.f,
bool bLoop = false) override;
virtual void PlayAmplitudeEnvelopeHapticEffect(EControllerHand Hand, int SamplesCount, void* Samples, int SampleRate = -1) override;
virtual void SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand) override;
virtual float GetControllerSampleRateHz(EControllerHand Hand) const override;
virtual int GetMaxHapticDuration(EControllerHand Hand) const override;
// FTickableGameObject overrides
virtual void Tick(float DeltaTime) override;
virtual ETickableTickType GetTickableTickType() const override;
virtual TStatId GetStatId() const override;
private:
void SetHapticFeedbackValues(EControllerHand Hand, const FHapticFeedbackValues& Values, FOculusXRHapticsDesc* HapticsDesc);
TSharedPtr<FActiveHapticFeedbackEffect> ActiveHapticEffect_Left;
TSharedPtr<FActiveHapticFeedbackEffect> ActiveHapticEffect_Right;
TSharedPtr<FOculusXRHapticsDesc> HapticsDesc_Left;
TSharedPtr<FOculusXRHapticsDesc> HapticsDesc_Right;
};
} // namespace OculusXRInput

View File

@@ -0,0 +1,555 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "IOculusXRInputModule.h"
#if OCULUS_INPUT_SUPPORTED_PLATFORMS
#include "IMotionController.h"
#include "InputCoreTypes.h"
#include "OculusXRInputFunctionLibrary.h"
#include "GenericPlatform/GenericApplicationMessageHandler.h"
namespace OculusXRInput
{
//-------------------------------------------------------------------------------------------------
// Button names
//-------------------------------------------------------------------------------------------------
enum class EOculusTouchControllerButton
{
// NOTE: The Trigger and Grip digital buttons are synthetic. Oculus hardware doesn't support a digital press for these
Trigger,
Grip,
XA,
YB,
Thumbstick,
Thumbstick_Up,
Thumbstick_Down,
Thumbstick_Left,
Thumbstick_Right,
Menu,
Thumbstick_Touch,
Trigger_Touch,
XA_Touch,
YB_Touch,
/** Total number of controller buttons */
TotalButtonCount
};
enum class EOculusRemoteControllerButton
{
DPad_Up,
DPad_Down,
DPad_Left,
DPad_Right,
Enter,
Back,
VolumeUp,
VolumeDown,
Home,
/** Total number of controller buttons */
TotalButtonCount
};
enum class EOculusTouchCapacitiveAxes
{
Thumbstick,
Trigger,
XA,
YB,
IndexPointing,
ThumbUp,
ThumbRest,
/** Total number of capacitive axes */
TotalAxisCount
};
enum class EOculusHandButton
{
Thumb,
Index,
Middle,
Ring,
Pinky,
System,
Menu,
TotalButtonCount
};
enum class EOculusHandAxes
{
Thumb,
Index,
Middle,
Ring,
Pinky,
TotalAxisCount
};
//-------------------------------------------------------------------------------------------------
// FOculusKey
//-------------------------------------------------------------------------------------------------
struct FOculusKey
{
static const FKey OculusTouch_Left_Thumbstick;
static const FKey OculusTouch_Left_Trigger;
static const FKey OculusTouch_Left_FaceButton1; // X or A
static const FKey OculusTouch_Left_FaceButton2; // Y or B
static const FKey OculusTouch_Left_IndexPointing;
static const FKey OculusTouch_Left_Trigger_Proximity;
static const FKey OculusTouch_Left_ThumbUp;
static const FKey OculusTouch_Left_Thumb_Proximity;
static const FKey OculusTouch_Left_ThumbRest;
static const FKey OculusTouch_Left_ThumbRest_Force;
static const FKey OculusTouch_Left_Stylus_Force;
static const FKey OculusTouch_Left_IndexTrigger_Curl;
static const FKey OculusTouch_Left_IndexTrigger_Slide;
static const FKey OculusTouch_Left_IndexTrigger_Force;
static const FKey OculusTouch_Right_Thumbstick;
static const FKey OculusTouch_Right_Trigger;
static const FKey OculusTouch_Right_FaceButton1; // X or A
static const FKey OculusTouch_Right_FaceButton2; // Y or B
static const FKey OculusTouch_Right_IndexPointing;
static const FKey OculusTouch_Right_Trigger_Proximity;
static const FKey OculusTouch_Right_ThumbUp;
static const FKey OculusTouch_Right_Thumb_Proximity;
static const FKey OculusTouch_Right_ThumbRest;
static const FKey OculusTouch_Right_ThumbRest_Force;
static const FKey OculusTouch_Right_Stylus_Force;
static const FKey OculusTouch_Right_IndexTrigger_Curl;
static const FKey OculusTouch_Right_IndexTrigger_Slide;
static const FKey OculusTouch_Right_IndexTrigger_Force;
static const FKey OculusRemote_DPad_Up;
static const FKey OculusRemote_DPad_Down;
static const FKey OculusRemote_DPad_Left;
static const FKey OculusRemote_DPad_Right;
static const FKey OculusRemote_Enter;
static const FKey OculusRemote_Back;
static const FKey OculusRemote_VolumeUp;
static const FKey OculusRemote_VolumeDown;
static const FKey OculusRemote_Home;
static const FKey OculusHand_Left_ThumbPinch;
static const FKey OculusHand_Left_IndexPinch;
static const FKey OculusHand_Left_MiddlePinch;
static const FKey OculusHand_Left_RingPinch;
static const FKey OculusHand_Left_PinkyPinch;
static const FKey OculusHand_Right_ThumbPinch;
static const FKey OculusHand_Right_IndexPinch;
static const FKey OculusHand_Right_MiddlePinch;
static const FKey OculusHand_Right_RingPinch;
static const FKey OculusHand_Right_PinkyPinch;
static const FKey OculusHand_Left_SystemGesture;
static const FKey OculusHand_Right_SystemGesture;
static const FKey OculusHand_Left_ThumbPinchStrength;
static const FKey OculusHand_Left_IndexPinchStrength;
static const FKey OculusHand_Left_MiddlePinchStrength;
static const FKey OculusHand_Left_RingPinchStrength;
static const FKey OculusHand_Left_PinkyPinchStrength;
static const FKey OculusHand_Right_ThumbPinchStrength;
static const FKey OculusHand_Right_IndexPinchStrength;
static const FKey OculusHand_Right_MiddlePinchStrength;
static const FKey OculusHand_Right_RingPinchStrength;
static const FKey OculusHand_Right_PinkyPinchStrength;
};
//-------------------------------------------------------------------------------------------------
// FOculusKeyNames
//-------------------------------------------------------------------------------------------------
struct FOculusKeyNames
{
typedef FName Type;
static const FName OculusTouch_Left_Thumbstick;
static const FName OculusTouch_Left_Trigger;
static const FName OculusTouch_Left_FaceButton1; // X or A
static const FName OculusTouch_Left_FaceButton2; // Y or B
static const FName OculusTouch_Left_IndexPointing;
static const FName OculusTouch_Left_Trigger_Proximity;
static const FName OculusTouch_Left_ThumbUp;
static const FName OculusTouch_Left_Thumb_Proximity;
static const FName OculusTouch_Left_ThumbRest;
static const FName OculusTouch_Right_Thumbstick;
static const FName OculusTouch_Right_Trigger;
static const FName OculusTouch_Right_FaceButton1; // X or A
static const FName OculusTouch_Right_FaceButton2; // Y or B
static const FName OculusTouch_Right_IndexPointing;
static const FName OculusTouch_Right_Trigger_Proximity;
static const FName OculusTouch_Right_ThumbUp;
static const FName OculusTouch_Right_Thumb_Proximity;
static const FName OculusTouch_Right_ThumbRest;
static const FName OculusRemote_DPad_Up;
static const FName OculusRemote_DPad_Down;
static const FName OculusRemote_DPad_Left;
static const FName OculusRemote_DPad_Right;
static const FName OculusRemote_Enter;
static const FName OculusRemote_Back;
static const FName OculusRemote_VolumeUp;
static const FName OculusRemote_VolumeDown;
static const FName OculusRemote_Home;
static const FName OculusHand_Left_ThumbPinch;
static const FName OculusHand_Left_IndexPinch;
static const FName OculusHand_Left_MiddlePinch;
static const FName OculusHand_Left_RingPinch;
static const FName OculusHand_Left_PinkyPinch;
static const FName OculusHand_Right_ThumbPinch;
static const FName OculusHand_Right_IndexPinch;
static const FName OculusHand_Right_MiddlePinch;
static const FName OculusHand_Right_RingPinch;
static const FName OculusHand_Right_PinkyPinch;
static const FName OculusHand_Left_SystemGesture;
static const FName OculusHand_Right_SystemGesture;
static const FName OculusHand_Left_ThumbPinchStrength;
static const FName OculusHand_Left_IndexPinchStrength;
static const FName OculusHand_Left_MiddlePinchStrength;
static const FName OculusHand_Left_RingPinchStrength;
static const FName OculusHand_Left_PinkyPinchStrength;
static const FName OculusHand_Right_ThumbPinchStrength;
static const FName OculusHand_Right_IndexPinchStrength;
static const FName OculusHand_Right_MiddlePinchStrength;
static const FName OculusHand_Right_RingPinchStrength;
static const FName OculusHand_Right_PinkyPinchStrength;
};
//-------------------------------------------------------------------------------------------------
// FOculusButtonState - Digital button state
//-------------------------------------------------------------------------------------------------
struct FOculusButtonState
{
/** The Unreal button this maps to. Different depending on whether this is the Left or Right hand controller */
FName Key;
/** The Unreal button this maps to. Different depending on whether this is the Left or Right hand controller */
FName EmulatedKey;
/** Whether we're pressed or not. While pressed, we will generate repeat presses on a timer */
bool bIsPressed;
/** Next time a repeat event should be generated for each button */
double NextRepeatTime;
/** Default constructor that just sets sensible defaults */
FOculusButtonState()
: Key(NAME_None), EmulatedKey(NAME_None), bIsPressed(false), NextRepeatTime(0.0)
{
}
};
//-------------------------------------------------------------------------------------------------
// FOculusTouchCapacitiveState - Capacitive Axis State
//-------------------------------------------------------------------------------------------------
struct FOculusAxisState
{
/** The axis that this button state maps to */
FName Axis;
/** How close the finger is to this button, from 0.f to 1.f */
float State;
FOculusAxisState()
: Axis(NAME_None)
, State(0.f)
{
}
};
struct FOculusControllerState
{
/** True if the device is connected, otherwise false */
bool bIsConnected;
/** True if position is being tracked, otherwise false */
bool bIsPositionTracked;
/** True if position is valid (tracked or estimated), otherwise false */
bool bIsPositionValid;
/** True if orientation is being tracked, otherwise false */
bool bIsOrientationTracked;
/** True if orientation is valid (tracked or estimated), otherwise false */
bool bIsOrientationValid;
FOculusControllerState()
: bIsConnected(false), bIsPositionTracked(false), bIsPositionValid(false), bIsOrientationTracked(false), bIsOrientationValid(false)
{
}
};
//-------------------------------------------------------------------------------------------------
// FOculusTouchControllerState - Input state for an Oculus motion controller
//-------------------------------------------------------------------------------------------------
struct FOculusTouchControllerState : FOculusControllerState
{
/** Analog trigger */
float TriggerAxis;
/** Grip trigger */
float GripAxis;
/** Thumbstick */
FVector2D ThumbstickAxes;
/** Thumbstick */
FVector2D TouchpadAxes;
/** Button states */
FOculusButtonState Buttons[(int32)EOculusTouchControllerButton::TotalButtonCount];
/** Capacitive Touch axes */
FOculusAxisState CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::TotalAxisCount];
/** Thumb Rest Force **/
float ThumbRestForce;
/** Stylus tip**/
float StylusForce;
/** Index trigger Curl**/
float IndexTriggerCurl;
/** Index trigger Slide**/
float IndexTriggerSlide;
/** Second stage Index trigger force **/
float IndexTriggerForce;
/** Whether or not we're playing a haptic effect. If true, force feedback calls will be early-outed in favor of the haptic effect */
bool bPlayingHapticEffect;
/** Haptic frequency (zero to disable) */
float HapticFrequency;
/** Haptic amplitude (zero to disable) */
float HapticAmplitude;
/** Force feedback haptic frequency (zero to disable) */
float ForceFeedbackHapticFrequency;
/** Force feedback haptic amplitude (zero to disable) */
float ForceFeedbackHapticAmplitude;
/** Number of times that controller was recentered (for mobile controllers) */
int RecenterCount;
public:
FHapticFeedbackBuffer ResampledHapticBuffer;
void ResampleHapticBufferData(const FHapticFeedbackBuffer& HapticBuffer, TMap<const uint8*, TSharedPtr<TArray<uint8>>>& ResampledRawDataCache);
/** Explicit constructor sets up sensible defaults */
FOculusTouchControllerState(const EControllerHand Hand)
: TriggerAxis(0.0f), GripAxis(0.0f), ThumbstickAxes(FVector2D::ZeroVector), bPlayingHapticEffect(false), HapticFrequency(0.0f), HapticAmplitude(0.0f), ForceFeedbackHapticFrequency(0.0f), ForceFeedbackHapticAmplitude(0.0f), RecenterCount(0)
{
for (FOculusButtonState& Button : Buttons)
{
Button.bIsPressed = false;
Button.NextRepeatTime = 0.0;
}
Buttons[(int32)EOculusTouchControllerButton::Trigger].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Trigger_Click.GetFName() : EKeys::OculusTouch_Right_Trigger_Click.GetFName();
Buttons[(int32)EOculusTouchControllerButton::Grip].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Grip_Click.GetFName() : EKeys::OculusTouch_Right_Grip_Click.GetFName();
Buttons[(int32)EOculusTouchControllerButton::Thumbstick].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Click.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Click.GetFName();
Buttons[(int32)EOculusTouchControllerButton::XA].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_X_Click.GetFName() : EKeys::OculusTouch_Right_A_Click.GetFName();
Buttons[(int32)EOculusTouchControllerButton::YB].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Y_Click.GetFName() : EKeys::OculusTouch_Right_B_Click.GetFName();
Buttons[(int32)EOculusTouchControllerButton::Thumbstick_Up].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Up.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Up.GetFName();
Buttons[(int32)EOculusTouchControllerButton::Thumbstick_Down].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Down.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Down.GetFName();
Buttons[(int32)EOculusTouchControllerButton::Thumbstick_Left].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Left.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Left.GetFName();
Buttons[(int32)EOculusTouchControllerButton::Thumbstick_Right].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Right.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Right.GetFName();
Buttons[(int32)EOculusTouchControllerButton::Menu].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Menu_Click.GetFName() : FName("OculusTouch_Right_System_Click");
Buttons[(int32)EOculusTouchControllerButton::Thumbstick_Touch].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Touch.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Touch.GetFName();
Buttons[(int32)EOculusTouchControllerButton::Trigger_Touch].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Trigger_Touch.GetFName() : EKeys::OculusTouch_Right_Trigger_Touch.GetFName();
Buttons[(int32)EOculusTouchControllerButton::XA_Touch].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_X_Touch.GetFName() : EKeys::OculusTouch_Right_A_Touch.GetFName();
Buttons[(int32)EOculusTouchControllerButton::YB_Touch].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Y_Touch.GetFName() : EKeys::OculusTouch_Right_B_Touch.GetFName();
CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::Thumbstick].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_Thumbstick : FOculusKeyNames::OculusTouch_Right_Thumbstick;
CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::Trigger].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_Trigger : FOculusKeyNames::OculusTouch_Right_Trigger;
CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::XA].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_FaceButton1 : FOculusKeyNames::OculusTouch_Right_FaceButton1;
CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::YB].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_FaceButton2 : FOculusKeyNames::OculusTouch_Right_FaceButton2;
CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::IndexPointing].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_IndexPointing : FOculusKeyNames::OculusTouch_Right_IndexPointing;
CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::ThumbUp].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_ThumbUp : FOculusKeyNames::OculusTouch_Right_ThumbUp;
CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::ThumbRest].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_ThumbRest : FOculusKeyNames::OculusTouch_Right_ThumbRest;
}
/** Default constructor does nothing. Don't use it. This only exists because we cannot initialize an array of objects with no default constructor on non-C++ 11 compliant compilers (VS 2013) */
FOculusTouchControllerState()
{
}
};
//-------------------------------------------------------------------------------------------------
// FOculusHandControllerState - Input state for an Oculus Hands
//-------------------------------------------------------------------------------------------------
struct FOculusHandControllerState : FOculusControllerState
{
/** True if the pointer pose for hands is valid */
bool bIsPointerPoseValid;
/** True if the current hand is the dominant hand */
bool bIsDominantHand;
/** Scale of the hand */
float HandScale;
/** Pose of the pointer */
FTransform PointerPose;
/** Tracking confidence of hand tracking */
EOculusXRTrackingConfidence TrackingConfidence;
/** Finger Pinch States **/
FOculusButtonState HandButtons[(int32)EOculusHandButton::TotalButtonCount];
/** Finger Pinch Strength States **/
FOculusAxisState HandAxes[(int32)EOculusHandAxes::TotalAxisCount];
/** Finger Confidences **/
EOculusXRTrackingConfidence FingerConfidences[(int32)EOculusHandAxes::TotalAxisCount] = {};
FQuat BoneRotations[(int32)EOculusXRBone::Bone_Max];
FOculusHandControllerState(const EControllerHand Hand)
{
TrackingConfidence = EOculusXRTrackingConfidence::Low;
bIsPointerPoseValid = false;
bIsDominantHand = false;
HandScale = 0.0f;
PointerPose = FTransform::Identity;
for (FOculusButtonState& Button : HandButtons)
{
Button.bIsPressed = false;
Button.NextRepeatTime = 0.0;
}
for (int32 i = 0; i < (int32)EOculusXRBone::Bone_Max; i++)
{
BoneRotations[i] = FQuat::Identity;
}
HandButtons[(int32)EOculusHandButton::Thumb].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_ThumbPinch : FOculusKeyNames::OculusHand_Right_ThumbPinch;
HandButtons[(int32)EOculusHandButton::Index].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_IndexPinch : FOculusKeyNames::OculusHand_Right_IndexPinch;
HandButtons[(int32)EOculusHandButton::Middle].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_MiddlePinch : FOculusKeyNames::OculusHand_Right_MiddlePinch;
HandButtons[(int32)EOculusHandButton::Ring].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_RingPinch : FOculusKeyNames::OculusHand_Right_RingPinch;
HandButtons[(int32)EOculusHandButton::Pinky].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_PinkyPinch : FOculusKeyNames::OculusHand_Right_PinkyPinch;
HandButtons[(int32)EOculusHandButton::System].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_SystemGesture : FOculusKeyNames::OculusHand_Right_SystemGesture;
HandButtons[(int32)EOculusHandButton::Menu].Key = (Hand == EControllerHand::Left) ? FGamepadKeyNames::SpecialLeft : FGamepadKeyNames::SpecialRight;
HandAxes[(int32)EOculusHandAxes::Thumb].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_ThumbPinchStrength : FOculusKeyNames::OculusHand_Right_ThumbPinchStrength;
HandAxes[(int32)EOculusHandAxes::Index].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_IndexPinchStrength : FOculusKeyNames::OculusHand_Right_IndexPinchStrength;
HandAxes[(int32)EOculusHandAxes::Middle].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_MiddlePinchStrength : FOculusKeyNames::OculusHand_Right_MiddlePinchStrength;
HandAxes[(int32)EOculusHandAxes::Ring].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_RingPinchStrength : FOculusKeyNames::OculusHand_Right_RingPinchStrength;
HandAxes[(int32)EOculusHandAxes::Pinky].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_PinkyPinchStrength : FOculusKeyNames::OculusHand_Right_PinkyPinchStrength;
}
FOculusHandControllerState()
{
}
};
//-------------------------------------------------------------------------------------------------
// FOculusRemoteControllerState
//-------------------------------------------------------------------------------------------------
struct FOculusRemoteControllerState
{
/** Button states */
FOculusButtonState Buttons[(int32)EOculusRemoteControllerButton::TotalButtonCount];
FOculusRemoteControllerState()
{
for (FOculusButtonState& Button : Buttons)
{
Button.bIsPressed = false;
Button.NextRepeatTime = 0.0;
}
Buttons[(int32)EOculusRemoteControllerButton::DPad_Up].Key = FOculusKeyNames::OculusRemote_DPad_Up;
Buttons[(int32)EOculusRemoteControllerButton::DPad_Down].Key = FOculusKeyNames::OculusRemote_DPad_Down;
Buttons[(int32)EOculusRemoteControllerButton::DPad_Left].Key = FOculusKeyNames::OculusRemote_DPad_Left;
Buttons[(int32)EOculusRemoteControllerButton::DPad_Right].Key = FOculusKeyNames::OculusRemote_DPad_Right;
Buttons[(int32)EOculusRemoteControllerButton::Enter].Key = FOculusKeyNames::OculusRemote_Enter;
Buttons[(int32)EOculusRemoteControllerButton::Back].Key = FOculusKeyNames::OculusRemote_Back;
Buttons[(int32)EOculusRemoteControllerButton::VolumeUp].Key = FOculusKeyNames::OculusRemote_VolumeUp;
Buttons[(int32)EOculusRemoteControllerButton::VolumeDown].Key = FOculusKeyNames::OculusRemote_VolumeDown;
Buttons[(int32)EOculusRemoteControllerButton::Home].Key = FOculusKeyNames::OculusRemote_Home;
}
void MapKeysToGamepad()
{
Buttons[(int32)EOculusRemoteControllerButton::DPad_Up].EmulatedKey = FGamepadKeyNames::DPadUp;
Buttons[(int32)EOculusRemoteControllerButton::DPad_Down].EmulatedKey = FGamepadKeyNames::DPadDown;
Buttons[(int32)EOculusRemoteControllerButton::DPad_Left].EmulatedKey = FGamepadKeyNames::DPadLeft;
Buttons[(int32)EOculusRemoteControllerButton::DPad_Right].EmulatedKey = FGamepadKeyNames::DPadRight;
Buttons[(int32)EOculusRemoteControllerButton::Enter].EmulatedKey = FGamepadKeyNames::SpecialRight;
Buttons[(int32)EOculusRemoteControllerButton::Back].EmulatedKey = FGamepadKeyNames::SpecialLeft;
}
};
//-------------------------------------------------------------------------------------------------
// FOculusControllerPair - A pair of Oculus controllers, hand/touch, one for either hand
//-------------------------------------------------------------------------------------------------
struct FOculusControllerPair
{
/** The input device ID for this oculus controller */
FInputDeviceId DeviceId;
/** Current device state for either hand */
FOculusTouchControllerState TouchControllerStates[2];
FOculusHandControllerState HandControllerStates[2];
FOculusControllerPair()
: DeviceId(INPUTDEVICEID_NONE), TouchControllerStates(), HandControllerStates()
{
TouchControllerStates[(int32)EControllerHand::Left] = FOculusTouchControllerState(EControllerHand::Left);
TouchControllerStates[(int32)EControllerHand::Right] = FOculusTouchControllerState(EControllerHand::Right);
HandControllerStates[(int32)EControllerHand::Left] = FOculusHandControllerState(EControllerHand::Left);
HandControllerStates[(int32)EControllerHand::Right] = FOculusHandControllerState(EControllerHand::Right);
}
};
const float DefaultInitialButtonRepeatDelay = 0.2f;
const float DefaultButtonRepeatDelay = 0.1f;
} // namespace OculusXRInput
#endif // OCULUS_INPUT_SUPPORTED_PLATFORMS

View File

@@ -0,0 +1,25 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRInputXRFunctions.h"
#include "OculusXRInputModule.h"
#include "OpenXR/OculusXROpenXRUtilities.h"
namespace OculusXRInput
{
PFN_xrGetDeviceSampleRateFB xrGetDeviceSampleRateFB = nullptr;
void InitOpenXRFunctions(XrInstance InInstance)
{
const FOculusXRInputModule* InputModule = static_cast<FOculusXRInputModule*>(&FOculusXRInputModule::Get());
if (!InputModule)
{
UE_LOG(LogOcInput, Error, TEXT("Failed getting Oculus XR input module."));
return;
}
if (InputModule->GetHapticsOpenXRExtension()->IsPCMExtensionAvailable())
{
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGetDeviceSampleRateFB", &xrGetDeviceSampleRateFB);
}
}
} // namespace OculusXRInput

View File

@@ -0,0 +1,12 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "khronos/openxr/openxr.h"
namespace OculusXRInput
{
extern PFN_xrGetDeviceSampleRateFB xrGetDeviceSampleRateFB;
void InitOpenXRFunctions(XrInstance InInstance);
} // namespace OculusXRInput

View File

@@ -0,0 +1,22 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRLegacyPoseTransformComponent.h"
#include "OpenXR/OculusXROpenXRUtilities.h"
#include "GameFramework/WorldSettings.h"
UOculusXRLegacyPoseTransformComponent::UOculusXRLegacyPoseTransformComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SetComponentTickEnabled(false);
}
void UOculusXRLegacyPoseTransformComponent::BeginPlay()
{
Super::BeginPlay();
if (OculusXR::IsOpenXRSystem())
{
AddLocalTransform(FTransform(OculusPoseToGripRotation, OculusPoseToGripPosition * GetWorld()->GetWorldSettings()->WorldToMeters).Inverse());
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRTouchPlusInputExtensionPlugin.h"
#include "OpenXRCore.h"
namespace OculusXRInput
{
bool FTouchPlusInputExtensionPlugin::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add(XR_META_TOUCH_CONTROLLER_PLUS_EXTENSION_NAME);
return true;
}
void FTouchPlusInputExtensionPlugin::PostCreateInstance(XrInstance InInstance)
{
XrResult Result = xrStringToPath(InInstance, "/interaction_profiles/meta/touch_controller_plus", &InteractionProfile);
check(XR_SUCCEEDED(Result));
}
bool FTouchPlusInputExtensionPlugin::GetInteractionProfile(XrInstance InInstance, FString& OutKeyPrefix, XrPath& OutPath, bool& OutHasHaptics)
{
OutKeyPrefix = "OculusTouchPlus";
OutPath = InteractionProfile;
OutHasHaptics = true;
return true;
}
} // namespace OculusXRInput

View File

@@ -0,0 +1,34 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "khronos/openxr/openxr.h"
#include "CoreMinimal.h"
#include "IOculusXRInputModule.h"
#include "IOpenXRExtensionPlugin.h"
#include "Misc/EngineVersionComparison.h"
namespace OculusXRInput
{
class FTouchPlusInputExtensionPlugin : public IOpenXRExtensionPlugin
{
public:
void RegisterOpenXRExtensionPlugin()
{
#if !UE_VERSION_OLDER_THAN(5, 5, 0)
RegisterOpenXRExtensionModularFeature();
#endif
}
// IOpenXRExtensionPlugin
virtual bool GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual void PostCreateInstance(XrInstance InInstance) override;
virtual bool GetInteractionProfile(XrInstance InInstance, FString& OutKeyPrefix, XrPath& OutPath, bool& OutHasHaptics) override;
private:
XrPath InteractionProfile;
};
} // namespace OculusXRInput

View File

@@ -0,0 +1,29 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRTouchProInputExtensionPlugin.h"
#include "OpenXRCore.h"
namespace OculusXRInput
{
bool FTouchProInputExtensionPlugin::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add(XR_FB_TOUCH_CONTROLLER_PRO_EXTENSION_NAME);
return true;
}
void FTouchProInputExtensionPlugin::PostCreateInstance(XrInstance InInstance)
{
XrResult Result = xrStringToPath(InInstance, "/interaction_profiles/facebook/touch_controller_pro", &InteractionProfile);
check(XR_SUCCEEDED(Result));
}
bool FTouchProInputExtensionPlugin::GetInteractionProfile(XrInstance InInstance, FString& OutKeyPrefix, XrPath& OutPath, bool& OutHasHaptics)
{
OutKeyPrefix = "OculusTouchPro";
OutPath = InteractionProfile;
OutHasHaptics = true;
return true;
}
} // namespace OculusXRInput

View File

@@ -0,0 +1,34 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "khronos/openxr/openxr.h"
#include "CoreMinimal.h"
#include "IOculusXRInputModule.h"
#include "IOpenXRExtensionPlugin.h"
#include "Misc/EngineVersionComparison.h"
namespace OculusXRInput
{
class FTouchProInputExtensionPlugin : public IOpenXRExtensionPlugin
{
public:
void RegisterOpenXRExtensionPlugin()
{
#if !UE_VERSION_OLDER_THAN(5, 5, 0)
RegisterOpenXRExtensionModularFeature();
#endif
}
// IOpenXRExtensionPlugin
virtual bool GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual void PostCreateInstance(XrInstance InInstance) override;
virtual bool GetInteractionProfile(XrInstance InInstance, FString& OutKeyPrefix, XrPath& OutPath, bool& OutHasHaptics) override;
private:
XrPath InteractionProfile;
};
} // namespace OculusXRInput