Files
Plugin/Source/SenseGloveTracking/Private/SGTracking/SGXRTracker.cpp
T

2573 lines
91 KiB
C++

/**
* @file
*
* @author Mamadou Babaei <mamadou@senseglove.com>
*
* @section LICENSE
*
* (The MIT License)
*
* Copyright (c) 2020 - 2026 SenseGlove
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @section DESCRIPTION
*
*
*/
#include "SGTracking/SGXRTracker.h"
#include "Async/Async.h"
#include "Async/Future.h"
#include "Async/TaskGraphInterfaces.h"
#include "Containers/Array.h"
#include "Containers/Map.h"
#include "Engine/Engine.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/World.h"
#include "GameFramework/WorldSettings.h"
#include "IOpenXRHMD.h"
#include "IOpenXRHMDModule.h"
#include "IXRTrackingSystem.h"
#include "Misc/Optional.h"
#include "Misc/Timespan.h"
#include "OpenXRCore.h"
#include "UObject/Class.h"
#include "SGCore/SGHapticGlove.h"
#include "SGLog/SGLog.h"
#include "SGSettings/SGSettings.h"
#include "SGTracking/SGGloveTracker.h"
#include "SGTracking/SGHMDTracker.h"
#include "SGTracking/SGXRHandState.h"
#include "SGTypes/SGCoreTypes.h"
#include "SGTypes/SGTrackingTypes.h"
#include "SGUtils/SGEngineUtils.h"
struct FSGXRTracker::FImpl
{
/************************
* Typedefs
************************/
// bool true == left, false == right
typedef TPair<EHandKeypoint, bool> FSGMotionSourceInfo;
/************************
* Static methods
************************/
/**
* Parses the enum name removing the prefix.
*/
static FName GetHandKeypointEnumName(const EHandKeypoint HandKeypoint);
FORCEINLINE static FName GetHandKeypointEnumName(const int32 HandKeypointIndex)
{
return GetHandKeypointEnumName(static_cast<EHandKeypoint>(HandKeypointIndex));
}
static FName GetControllerHandEnumName(const EControllerHand ControllerHand);
static FName GetOpenXRPositionalTrackingProviderEnumName(
const ESGOpenXRPositionalTrackingProvider OpenXRPositionalTrackingProvider);
FORCEINLINE static bool ShouldFallbackToHandTrackingIfNoGloveDetected()
{
const USGSettings* Settings{USGSettings::GetInstance()};
ensureAlwaysMsgf(Settings, TEXT("The settings subsystem has not been initialized!"));
const bool bFallbackToHandTrackingIfNoGloveDetected =
Settings->GetTrackingSettings().bFallbackToHandTrackingIfNoGloveDetected;
return bFallbackToHandTrackingIfNoGloveDetected;
}
FORCEINLINE static bool ShouldUseMoreSpecificMotionSourceNames()
{
const USGSettings* Settings{USGSettings::GetInstance()};
ensureAlwaysMsgf(Settings, TEXT("The settings subsystem has not been initialized!"));
const bool bFallbackToHandTrackingIfNoGloveDetected = ShouldFallbackToHandTrackingIfNoGloveDetected();
const bool bUseMoreSpecificMotionSourceNames =
bFallbackToHandTrackingIfNoGloveDetected
? Settings->GetTrackingSettings().HandTrackingSettings.bUseMoreSpecificMotionSourceNames
: false;
return bUseMoreSpecificMotionSourceNames;
}
FORCEINLINE static bool ShouldSupportLegacyControllerMotionSources()
{
const USGSettings* Settings{USGSettings::GetInstance()};
ensureAlwaysMsgf(Settings, TEXT("The settings subsystem has not been initialized!"));
const bool bFallbackToHandTrackingIfNoGloveDetected = ShouldFallbackToHandTrackingIfNoGloveDetected();
const bool bSupportLegacyControllerMotionSources =
bFallbackToHandTrackingIfNoGloveDetected
? Settings->GetTrackingSettings().HandTrackingSettings.bSupportLegacyControllerMotionSources
: false;
return bSupportLegacyControllerMotionSources;
}
FORCEINLINE static const FSGHandTrackingSettings& GetHandTrackingSettings()
{
const USGSettings* Settings{USGSettings::GetInstance()};
ensureAlwaysMsgf(Settings, TEXT("The settings subsystem has not been initialized!"));
return Settings->GetTrackingSettings().HandTrackingSettings;
}
FORCEINLINE static const FSGVirtualHandSettings& GetVirtualHandSettings()
{
const USGSettings* Settings{USGSettings::GetInstance()};
ensureAlwaysMsgf(Settings, TEXT("The settings subsystem has not been initialized!"));
return Settings->GetVirtualHandSettings();
}
FORCEINLINE static const FSGWristTrackingSettings& GetWristTrackingSettings()
{
const USGSettings* Settings{USGSettings::GetInstance()};
ensureAlwaysMsgf(Settings, TEXT("The settings subsystem has not been initialized!"));
return Settings->GetTrackingSettings().WristTrackingSettings;
}
FORCEINLINE static const FName& GetDefaultLeftHandTrackingMotionSourceName()
{
static const FName Name(TEXT("Left"));
return Name;
}
FORCEINLINE static const FName& GetDefaultRightHandTrackingMotionSourceName()
{
static const FName Name(TEXT("Right"));
return Name;
}
FORCEINLINE static const FName& GetDefaultHandTrackingMotionSourceName(const EControllerHand Hand)
{
const FName& Name{
Hand == EControllerHand::Right
? GetDefaultRightHandTrackingMotionSourceName()
: GetDefaultLeftHandTrackingMotionSourceName()
};
return Name;
}
FORCEINLINE static const FName& GetDefaultHandTrackingMotionSourceName(const bool bRight)
{
const FName& Name{
bRight
? GetDefaultRightHandTrackingMotionSourceName()
: GetDefaultLeftHandTrackingMotionSourceName()
};
return Name;
}
FORCEINLINE static const FName& GetLegacyControllerLeftHandTrackingMotionSourceName()
{
static const FName Name(TEXT("Left"));
return Name;
}
FORCEINLINE static const FName& GetLegacyControllerRightHandTrackingMotionSourceName()
{
static const FName Name(TEXT("Right"));
return Name;
}
FORCEINLINE static const FName& GetLegacyControllerHandTrackingMotionSourceName(const EControllerHand Hand)
{
const FName& Name{
Hand == EControllerHand::Right
? GetLegacyControllerRightHandTrackingMotionSourceName()
: GetLegacyControllerLeftHandTrackingMotionSourceName()
};
return Name;
}
FORCEINLINE static const FName& GetLegacyControllerHandTrackingMotionSourceName(const bool bRight)
{
const FName& Name{
bRight
? GetLegacyControllerRightHandTrackingMotionSourceName()
: GetLegacyControllerLeftHandTrackingMotionSourceName()
};
return Name;
}
FORCEINLINE static const FName& GetMoreSpecificLeftHandTrackingMotionSourceName()
{
static const FName Name(TEXT("HandTrackingLeft"));
return Name;
}
FORCEINLINE static const FName& GetMoreSpecificRightHandTrackingMotionSourceName()
{
static const FName Name(TEXT("HandTrackingRight"));
return Name;
}
FORCEINLINE static const FName& GetMoreSpecificHandTrackingMotionSourceName(const EControllerHand Hand)
{
const FName& Name{
Hand == EControllerHand::Right
? GetMoreSpecificRightHandTrackingMotionSourceName()
: GetMoreSpecificLeftHandTrackingMotionSourceName()
};
return Name;
}
FORCEINLINE static const FName& GetMoreSpecificHandTrackingMotionSourceName(const bool bRight)
{
const FName& Name{
bRight
? GetMoreSpecificRightHandTrackingMotionSourceName()
: GetMoreSpecificLeftHandTrackingMotionSourceName()
};
return Name;
}
FORCEINLINE static const FName& GetLeftHandTrackingMotionSourceName()
{
const bool bUseMoreSpecificMotionSourceNames = ShouldUseMoreSpecificMotionSourceNames();
const FName& Name{
bUseMoreSpecificMotionSourceNames
? GetMoreSpecificLeftHandTrackingMotionSourceName()
: GetDefaultLeftHandTrackingMotionSourceName()
};
return Name;
}
FORCEINLINE static const FName& GetRightHandTrackingMotionSourceName()
{
const bool bUseMoreSpecificMotionSourceNames = ShouldUseMoreSpecificMotionSourceNames();
const FName& Name{
bUseMoreSpecificMotionSourceNames
? GetMoreSpecificRightHandTrackingMotionSourceName()
: GetDefaultRightHandTrackingMotionSourceName()
};
return Name;
}
FORCEINLINE static const FName& GetHandTrackingMotionSourceName(const EControllerHand Hand)
{
const FName& Name{
Hand == EControllerHand::Right
? GetRightHandTrackingMotionSourceName()
: GetLeftHandTrackingMotionSourceName()};
return Name;
}
FORCEINLINE static const FName& GetHandTrackingMotionSourceName(const bool bRight)
{
const FName& Name{
bRight
? GetRightHandTrackingMotionSourceName()
: GetLeftHandTrackingMotionSourceName()
};
return Name;
}
FORCEINLINE static const FName& GetLeftPositionalTrackingHardwareMotionSourceName()
{
const FName& Name{GetWristTrackingSettings().LeftHandMotionSource};
return Name;
}
FORCEINLINE static const FName& GetRightPositionalTrackingHardwareMotionSourceName()
{
const FName& Name{GetWristTrackingSettings().RightHandMotionSource};
return Name;
}
FORCEINLINE static FName GetPositionalTrackingHardwareMotionSourceName(const EControllerHand Hand)
{
const FName Name{
Hand == EControllerHand::Right
? GetRightPositionalTrackingHardwareMotionSourceName()
: GetLeftPositionalTrackingHardwareMotionSourceName()};
return Name;
}
FORCEINLINE static FName GetPositionalTrackingHardwareMotionSourceName(const bool bRight)
{
const FName& Name{
bRight
? GetRightPositionalTrackingHardwareMotionSourceName()
: GetLeftPositionalTrackingHardwareMotionSourceName()
};
return Name;
}
static ESGPositionalTrackingHardware GetPositionalTrackingHardware();
FORCEINLINE static USGHapticGlove* GetGlove(const EControllerHand Hand)
{
const bool bRight = Hand == EControllerHand::Right;
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance()};
return IsValid(GloveTracker) ? GloveTracker->GetGlove(bRight) : nullptr;
}
FORCEINLINE static USGHapticGlove* GetGlove(const bool bRight)
{
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance()};
return IsValid(GloveTracker) ? GloveTracker->GetGlove(bRight) : nullptr;
}
FORCEINLINE static USGHapticGlove* GetLeftGlove()
{
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance()};
return IsValid(GloveTracker) ? GloveTracker->GetLeftGlove() : nullptr;
}
FORCEINLINE static USGHapticGlove* GetRightGlove()
{
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance()};
return IsValid(GloveTracker) ? GloveTracker->GetRightGlove() : nullptr;
}
FORCEINLINE static bool IsGloveConnected(const EControllerHand Hand)
{
const bool bRight = Hand == EControllerHand::Right;
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance()};
return IsValid(GloveTracker) ? GloveTracker->IsGloveConnected(bRight) : false;
}
FORCEINLINE static bool IsGloveConnected(const bool bRight)
{
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance()};
return IsValid(GloveTracker) ? GloveTracker->IsGloveConnected(bRight) : false;
}
FORCEINLINE static bool IsLeftGloveConnected()
{
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance()};
return IsValid(GloveTracker) ? GloveTracker->IsLeftGloveConnected() : false;
}
FORCEINLINE static bool IsRightGloveConnected()
{
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance()};
return IsValid(GloveTracker) ? GloveTracker->IsRightGloveConnected() : false;
}
FORCEINLINE static bool IsAnyGloveConnected()
{
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance()};
return IsValid(GloveTracker) ? GloveTracker->IsAnyGloveConnected() : false;
}
static TArray<float> GetFingerLengths(
bool bRight,
EHandKeypoint Joint2nd, EHandKeypoint Joint3rd,
float DistalPhalanxLength);
static TArray<TArray<float>> GetFingerLengths(bool bRight);
static TArray<FTransform> GetFingerStartTransforms(bool bRight);
static TArray<FVector> GetFingerStartPositions(bool bRight);
static TArray<FQuat> GetFingerStartRotations(bool bRight);
static USGBasicHandModel* GetHandModel(bool bRight);
FORCEINLINE static USGBasicHandModel* GetHandModel(const EControllerHand Hand)
{
const bool bRight = Hand == EControllerHand::Right;
USGBasicHandModel* HandModel{GetHandModel(bRight)};
return HandModel;
}
static USGHandPose* GetHandPose(bool bRight);
FORCEINLINE static USGHandPose* GetHandPose(const EControllerHand Hand)
{
const bool bRight = Hand == EControllerHand::Right;
USGHandPose* HandPose{GetHandPose(bRight)};
return HandPose;
}
FORCEINLINE static const FName& GetLeftMeshBoneName(const int32 JointIndex)
{
const FName& Name{GetVirtualHandSettings().MeshSettings.LeftHandBoneNames[JointIndex]};
return Name;
}
FORCEINLINE static const FName& GetLeftMeshBoneName(const EHandKeypoint HandKeypoint)
{
const int32 JointIndex = static_cast<int32>(HandKeypoint);
const FName& Name{GetLeftMeshBoneName(JointIndex)};
return Name;
}
FORCEINLINE static const FName& GetRightMeshBoneName(const int32 JointIndex)
{
const FName& Name{GetVirtualHandSettings().MeshSettings.RightHandBoneNames[JointIndex]};
return Name;
}
FORCEINLINE static const FName& GetRightMeshBoneName(const EHandKeypoint HandKeypoint)
{
const int32 JointIndex = static_cast<int32>(HandKeypoint);
const FName& Name{GetRightMeshBoneName(JointIndex)};
return Name;
}
FORCEINLINE static const FName& GetMeshBoneName(const bool bRight, const int32 Joint)
{
const FName& Name{
bRight
? GetRightMeshBoneName(Joint)
: GetLeftMeshBoneName(Joint)
};
return Name;
}
FORCEINLINE static const FName& GetMeshBoneName(const bool bRight, const EHandKeypoint HandKeypoint)
{
const int32 JointIndex = static_cast<int32>(HandKeypoint);
const FName& Name{GetMeshBoneName(bRight, JointIndex)};
return Name;
}
FORCEINLINE static const FName& GetMeshBoneName(const EControllerHand Hand, const int32 JointIndex)
{
const bool bRight = Hand == EControllerHand::Right;
const FName& Name{GetMeshBoneName(bRight, JointIndex)};
return Name;
}
static FTransform GetMeshRawBoneRefTransform(bool bRight, const FName& BoneName);
FORCEINLINE static FTransform GetMeshRawBoneRefTransform(const bool bRight, const int32 JointIndex)
{
const FName& BoneName{GetMeshBoneName(bRight, JointIndex)};
const FTransform& Transform{GetMeshRawBoneRefTransform(bRight, BoneName)};
return Transform;
}
FORCEINLINE static FTransform GetMeshRawBoneRefTransform(const bool bRight, const EHandKeypoint Keypoint)
{
const FName& BoneName{GetMeshBoneName(bRight, Keypoint)};
const FTransform& Transform{GetMeshRawBoneRefTransform(bRight, BoneName)};
return Transform;
}
FORCEINLINE static FTransform GetMeshRawBoneRefTransform(const EControllerHand Hand, const FName& BoneName)
{
const bool bRight = Hand == EControllerHand::Right;
const FTransform& Transform{GetMeshRawBoneRefTransform(bRight, BoneName)};
return Transform;
}
FORCEINLINE static FTransform GetMeshRawBoneRefTransform(const EControllerHand Hand, const int32 JointIndex)
{
const bool bRight = Hand == EControllerHand::Right;
const FName& BoneName{GetMeshBoneName(bRight, JointIndex)};
const FTransform& Transform{GetMeshRawBoneRefTransform(bRight, BoneName)};
return Transform;
}
FORCEINLINE static FTransform GetMeshRawBoneRefTransform(
const EControllerHand Hand, const EHandKeypoint Keypoint)
{
const bool bRight = Hand == EControllerHand::Right;
const FName& BoneName{GetMeshBoneName(bRight, Keypoint)};
const FTransform& Transform{GetMeshRawBoneRefTransform(bRight, BoneName)};
return Transform;
}
static FTransform GetMeshWristChildBoneRefTransform(bool bRight, const FName& BoneName);
FORCEINLINE static FTransform GetMeshWristChildBoneRefTransform(
const bool bRight, const int32 JointIndex)
{
const FName& BoneName{GetMeshBoneName(bRight, JointIndex)};
const FTransform Transform{GetMeshWristChildBoneRefTransform(bRight, BoneName)};
return Transform;
}
FORCEINLINE static FTransform GetMeshWristChildBoneRefTransform(
const bool bRight, const EHandKeypoint Keypoint)
{
const FName& BoneName{GetMeshBoneName(bRight, Keypoint)};
const FTransform Transform{GetMeshWristChildBoneRefTransform(bRight, BoneName)};
return Transform;
}
FORCEINLINE static FTransform GetMeshWristChildBoneRefTransform(
const EControllerHand Hand, const FName& BoneName)
{
const bool bRight = Hand == EControllerHand::Right;
const FTransform Transform{GetMeshWristChildBoneRefTransform(bRight, BoneName)};
return Transform;
}
FORCEINLINE static FTransform GetMeshWristChildBoneRefTransform(
const EControllerHand Hand, const int32 JointIndex)
{
const bool bRight = Hand == EControllerHand::Right;
const FTransform Transform{GetMeshWristChildBoneRefTransform(bRight, JointIndex)};
return Transform;
}
FORCEINLINE static FTransform GetMeshWristChildBoneRefTransform(
const EControllerHand Hand, const EHandKeypoint Keypoint)
{
const bool bRight = Hand == EControllerHand::Right;
const FTransform Transform{GetMeshWristChildBoneRefTransform(bRight, Keypoint)};
return Transform;
}
static FTransform GetMeshFingerStartBoneRefTransform(
bool bRight, EHandKeypoint Parent, const FName& BoneName);
FORCEINLINE static FTransform GetMeshFingerStartBoneRefTransform(
const bool bRight, const EHandKeypoint Parent, const int32 JointIndex)
{
const FName& BoneName{GetMeshBoneName(bRight, JointIndex)};
const FTransform Transform{GetMeshFingerStartBoneRefTransform(bRight, Parent, BoneName)};
return Transform;
}
FORCEINLINE static FTransform GetMeshFingerStartBoneRefTransform(
const bool bRight, const EHandKeypoint Parent, const EHandKeypoint Keypoint)
{
const FName& BoneName{GetMeshBoneName(bRight, Keypoint)};
const FTransform Transform{GetMeshFingerStartBoneRefTransform(bRight, Parent, BoneName)};
return Transform;
}
FORCEINLINE static FTransform GetMeshFingerStartBoneRefTransform(
const EControllerHand Hand, const EHandKeypoint Parent, const FName& BoneName)
{
const bool bRight = Hand == EControllerHand::Right;
const FTransform Transform{GetMeshFingerStartBoneRefTransform(bRight, Parent, BoneName)};
return Transform;
}
FORCEINLINE static FTransform GetMeshFingerStartBoneRefTransform(
const EControllerHand Hand, const EHandKeypoint Parent, const int32 JointIndex)
{
const bool bRight = Hand == EControllerHand::Right;
const FTransform Transform{GetMeshFingerStartBoneRefTransform(bRight, Parent, JointIndex)};
return Transform;
}
FORCEINLINE static FTransform GetMeshFingerStartBoneRefTransform(
const EControllerHand Hand, const EHandKeypoint Parent, const EHandKeypoint Keypoint)
{
const bool bRight = Hand == EControllerHand::Right;
const FTransform Transform{GetMeshFingerStartBoneRefTransform(bRight, Parent, Keypoint)};
return Transform;
}
/************************
* Member variables
************************/
bool bHandTrackingAvailable;
PFN_xrCreateHandTrackerEXT XRCreateHandTrackerEXT;
PFN_xrDestroyHandTrackerEXT XRDestroyHandTrackerEXT;
PFN_xrLocateHandJointsEXT XRLocateHandJointsEXT;
IXRTrackingSystem* XRTrackingSystem;
TSharedPtr<FGenericApplicationMessageHandler> MessageHandler;
int32 DeviceIndex;
int32 CurrentHandTrackingDataIndex;
TArray<int32> BoneParents;
TArray<EHandKeypoint> BoneKeypoints;
TMap<FName, FSGMotionSourceInfo> MotionSourceToKeypointMap;
FSGXRHandState HandStates[2];
TArray<FTransform> LeftAnimationTransforms;
TArray<FTransform> RightAnimationTransforms;
/************************
* Owner object
************************/
FSGXRTracker* Owner;
/************************
* Constructor / Destructor
************************/
explicit FImpl(FSGXRTracker* InOwner);
~FImpl();
/************************
* Default copy constructor & copy assignment operator
************************/
FImpl(const FImpl& Rhs) = delete;
FImpl& operator=(const FImpl& Rhs) = delete;
/************************
* Methods
************************/
FORCEINLINE const FSGXRHandState& GetLeftHandState() const
{
return HandStates[0];
}
FORCEINLINE FSGXRHandState& GetLeftHandState()
{
return HandStates[0];
}
FORCEINLINE const FSGXRHandState& GetRightHandState() const
{
return HandStates[1];
}
FORCEINLINE FSGXRHandState& GetRightHandState()
{
return HandStates[1];
}
void BuildMotionSourceToKeypointMap();
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 7
void CreateHandTracker(XrSession InSession, XrHandEXT HandEXT, FSGXRHandState& HandState) const;
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 7 */
bool CanOutputTrackingData(EControllerHand Hand) const;
bool UpdateXRHandTrackingData(FSGXRHandState& OutHandState, const XrHandJointsLocateInfoEXT& LocateInfo) const;
FName GetTargetOpenXRPositionalTrackingProviderName() const;
bool GetControllerTransform(const FName MotionSource, const float WorldToMetersScale,
FTransform& OutTransform, bool& bOutMotionSourceHandTracking) const;
bool GetWristTransform(bool bRight, FTransform& OutTransform) const;
bool UpdateSGJointData(FSGXRHandState& OutHandState, const EHandKeypoint Joint,
const FTransform& JointTransform, const FTransform& WristTransform) const;
bool UpdateSGWristJointData(FSGXRHandState& OutHandState, const FTransform& WristTransform) const;
bool UpdateSGPalmJointData(FSGXRHandState& OutHandState, bool bRight, const FTransform& WristTransform) const;
bool UpdateSGThumbJointData(
FSGXRHandState& OutHandState,
const TArray<TArray<FVector>>& JointLocations, const TArray<TArray<FQuat>>& JointRotations,
const FTransform& WristTransform) const;
bool UpdateSGIndexJointData(
FSGXRHandState& OutHandState, bool bRight,
const TArray<TArray<FVector>>& JointLocations, const TArray<TArray<FQuat>>& JointRotations,
const FTransform& WristTransform) const;
bool UpdateSGMiddleJointData(
FSGXRHandState& OutHandState, bool bRight,
const TArray<TArray<FVector>>& JointLocations, const TArray<TArray<FQuat>>& JointRotations,
const FTransform& WristTransform) const;
bool UpdateSGRingJointData(
FSGXRHandState& OutHandState, bool bRight,
const TArray<TArray<FVector>>& JointLocations, const TArray<TArray<FQuat>>& JointRotations,
const FTransform& WristTransform) const;
bool UpdateSGLittleJointData(
FSGXRHandState& OutHandState, bool bRight,
const TArray<TArray<FVector>>& JointLocations, const TArray<TArray<FQuat>>& JointRotations,
const FTransform& WristTransform) const;
bool UpdateSGHandState(bool bRight, FSGXRHandState& OutHandState) const;
};
FName FSGXRTracker::FImpl::GetHandKeypointEnumName(const EHandKeypoint HandKeypoint)
{
static UEnum* EnumPtr{StaticEnum<EHandKeypoint>()};
ensureAlwaysMsgf(EnumPtr, TEXT("Failed to find the EHandKeypoint enum!"));
static const int32 EnumNameLength = FString{TEXT("EHandKeypoint::")}.Len();
const FName EnumItemName{
EnumPtr->GetNameByValue(static_cast<int64>(HandKeypoint))
};
const FString EnumItemString{EnumItemName.ToString()};
const int32 EnumItemStringLength = EnumItemString.Len();
const int32 EnumItemNameLength = EnumItemStringLength - EnumNameLength;
const FName ParsedName{*EnumItemString.Right(EnumItemNameLength)};
return ParsedName;
}
FName FSGXRTracker::FImpl::GetControllerHandEnumName(const EControllerHand ControllerHand)
{
static UEnum* EnumPtr{StaticEnum<EControllerHand>()};
ensureAlwaysMsgf(EnumPtr, TEXT("Failed to find the EControllerHand enum!"));
static const int32 EnumNameLength = FString{TEXT("EControllerHand::")}.Len();
const FName EnumItemName{
EnumPtr->GetNameByValue(static_cast<int64>(ControllerHand))
};
const FString EnumItemString{EnumItemName.ToString()};
const int32 EnumItemStringLength = EnumItemString.Len();
const int32 EnumItemNameLength = EnumItemStringLength - EnumNameLength;
const FName ParsedName{*EnumItemString.Right(EnumItemNameLength)};
return ParsedName;
}
FName FSGXRTracker::FImpl::GetOpenXRPositionalTrackingProviderEnumName(
const ESGOpenXRPositionalTrackingProvider OpenXRPositionalTrackingProvider)
{
static const int32 EnumNameLength = FString{TEXT("ESGOpenXRPositionalTrackingProvider::")}.Len();
const FName EnumItemName{
UEnum::GetValueAsName(OpenXRPositionalTrackingProvider)
};
const FString EnumItemString{EnumItemName.ToString()};
const int32 EnumItemStringLength = EnumItemString.Len();
const int32 EnumItemNameLength = EnumItemStringLength - EnumNameLength;
const FName ParsedName{*EnumItemString.Right(EnumItemNameLength)};
return ParsedName;
}
ESGPositionalTrackingHardware FSGXRTracker::FImpl::GetPositionalTrackingHardware()
{
ESGPositionalTrackingHardware TrackerHardware = GetWristTrackingSettings().TrackingHardware;
if (TrackerHardware == ESGPositionalTrackingHardware::None)
{
const ESGHeadMountedDisplayDevice HmdDevice = FSGHMDTracker::GetDeviceType();
switch (HmdDevice)
{
case ESGHeadMountedDisplayDevice::MetaQuest2:
TrackerHardware = ESGPositionalTrackingHardware::Quest2Controller;
break;
case ESGHeadMountedDisplayDevice::MetaQuestPro:
TrackerHardware = ESGPositionalTrackingHardware::QuestProController;
break;
case ESGHeadMountedDisplayDevice::MetaQuest3:
TrackerHardware = ESGPositionalTrackingHardware::Quest3Controller;
break;
case ESGHeadMountedDisplayDevice::HtcVivePro:
TrackerHardware = ESGPositionalTrackingHardware::ViveTracker;
break;
case ESGHeadMountedDisplayDevice::HtcViveFocus3:
// Fall through
case ESGHeadMountedDisplayDevice::HtcViveXRElite:
TrackerHardware = ESGPositionalTrackingHardware::ViveFocus3WristTracker;
break;
default:
TrackerHardware = ESGPositionalTrackingHardware::Custom;
break;
}
}
return TrackerHardware;
}
TArray<float> FSGXRTracker::FImpl::GetFingerLengths(
const bool bRight,
const EHandKeypoint Joint2nd, const EHandKeypoint Joint3rd,
const float DistalPhalanxLength)
{
const FTransform& Joint2ndTransform{GetMeshRawBoneRefTransform(bRight, Joint2nd)};
const FVector& Joint2ndLocation{Joint2ndTransform.GetLocation()};
const FTransform& Joint3rdTransform{GetMeshRawBoneRefTransform(bRight, Joint3rd)};
const FVector& Joint3rdLocation{Joint3rdTransform.GetLocation()};
const float PhalanxLength1st = static_cast<float>(Joint2ndLocation.Size());
const float PhalanxLength2nd = static_cast<float>(Joint3rdLocation.Size());
const TArray<float> Lengths{PhalanxLength1st, PhalanxLength2nd, DistalPhalanxLength};
return Lengths;
}
TArray<TArray<float>> FSGXRTracker::FImpl::GetFingerLengths(const bool bRight)
{
const FSGVirtualHandSettings& Settings{GetVirtualHandSettings()};
const TArray<TArray<float>> Lengths{
GetFingerLengths(bRight,
EHandKeypoint::ThumbProximal,
EHandKeypoint::ThumbDistal,
Settings.MeshSettings.DistalPhalangesLengthSettings.Thumb
),
GetFingerLengths(bRight,
EHandKeypoint::IndexIntermediate,
EHandKeypoint::IndexDistal,
Settings.MeshSettings.DistalPhalangesLengthSettings.Index
),
GetFingerLengths(bRight,
EHandKeypoint::MiddleIntermediate,
EHandKeypoint::MiddleDistal,
Settings.MeshSettings.DistalPhalangesLengthSettings.Middle
),
GetFingerLengths(bRight,
EHandKeypoint::RingIntermediate,
EHandKeypoint::RingDistal,
Settings.MeshSettings.DistalPhalangesLengthSettings.Ring
),
GetFingerLengths(bRight,
EHandKeypoint::LittleIntermediate,
EHandKeypoint::LittleDistal,
Settings.MeshSettings.DistalPhalangesLengthSettings.Little
),
};
return Lengths;
}
TArray<FTransform> FSGXRTracker::FImpl::GetFingerStartTransforms(const bool bRight)
{
FTransform ThumbStartTransform{
GetMeshWristChildBoneRefTransform(bRight, EHandKeypoint::ThumbMetacarpal).GetLocation()
};
FTransform IndexStartTransform{
GetMeshFingerStartBoneRefTransform(bRight, EHandKeypoint::IndexMetacarpal, EHandKeypoint::IndexProximal)
};
FTransform MiddleStartTransform{
GetMeshFingerStartBoneRefTransform(bRight, EHandKeypoint::MiddleMetacarpal, EHandKeypoint::MiddleProximal)
};
FTransform RingStartTransform{
GetMeshFingerStartBoneRefTransform(bRight, EHandKeypoint::RingMetacarpal, EHandKeypoint::RingProximal)
};
FTransform LittleStartTransform{
GetMeshFingerStartBoneRefTransform(bRight, EHandKeypoint::LittleMetacarpal, EHandKeypoint::LittleProximal)
};
TArray<FTransform> Transforms{
MoveTemp(ThumbStartTransform),
MoveTemp(IndexStartTransform),
MoveTemp(MiddleStartTransform),
MoveTemp(RingStartTransform),
MoveTemp(LittleStartTransform),
};
return Transforms;
}
TArray<FVector> FSGXRTracker::FImpl::GetFingerStartPositions(const bool bRight)
{
const TArray<FTransform> StartTransforms{GetFingerStartTransforms(bRight)};
ensureAlwaysMsgf(StartTransforms.Num() == 5, TEXT("Invalid number of finger start transforms!"));
const TArray<FVector> StartPositions{
StartTransforms[0].GetLocation(),
StartTransforms[1].GetLocation(),
StartTransforms[2].GetLocation(),
StartTransforms[3].GetLocation(),
StartTransforms[4].GetLocation(),
};
return StartPositions;
}
TArray<FQuat> FSGXRTracker::FImpl::GetFingerStartRotations(const bool bRight)
{
const TArray<FQuat> StartRotations{
FQuat::Identity,
FQuat::Identity,
FQuat::Identity,
FQuat::Identity,
FQuat::Identity,
};
return StartRotations;
}
USGBasicHandModel* FSGXRTracker::FImpl::GetHandModel(const bool bRight)
{
const TArray<TArray<float>> FingerLengths{GetFingerLengths(bRight)};
const TArray<FVector> StartPositions{GetFingerStartPositions(bRight)};
const TArray<FQuat> StartRotations{GetFingerStartRotations(bRight)};
UWorld* World{FSGEngineUtils::GetWorld()};
USGBasicHandModel* HandModel{
USGBasicHandModel::NewBasicHandModel(World, bRight, FingerLengths, StartPositions, StartRotations)
};
return HandModel;
}
USGHandPose* FSGXRTracker::FImpl::GetHandPose(const bool bRight)
{
const USGHapticGlove* Glove{GetGlove(bRight)};
if (!IsValid(Glove))
{
return nullptr;
}
UWorld* World{FSGEngineUtils::GetWorld()};
const USGBasicHandModel* HandModel{GetHandModel(bRight)};
USGHandPose* HandPose{nullptr};
const bool bGotHandPose = Glove->GetHandPose(World, HandModel, HandPose);
if (!bGotHandPose || !IsValid(HandPose))
{
return nullptr;
}
return HandPose;
}
FTransform FSGXRTracker::FImpl::GetMeshRawBoneRefTransform(const bool bRight, const FName& BoneName)
{
const FSGVirtualHandMeshSettings& MeshSettings{GetVirtualHandSettings().MeshSettings};
const TSoftObjectPtr<USkeletalMesh>& HandMesh{
bRight
? MeshSettings.LeftHandReferenceMesh
: MeshSettings.RightHandReferenceMesh
};
if (HandMesh.IsValid())
{
const FReferenceSkeleton& ReferenceSkeleton{HandMesh->GetRefSkeleton()};
const int32 BoneIndex = ReferenceSkeleton.FindBoneIndex(BoneName);
if (BoneIndex == INDEX_NONE)
{
SGLOG_WARNING(FString::Printf(TEXT("Could not find a bone index for the '%s' joint!"),
*BoneName.ToString()));
}
const TArray<FTransform> Transforms{ReferenceSkeleton.GetRefBonePose()};
ensureAlwaysMsgf(Transforms.Num() > 0 && Transforms.Num() > BoneIndex,
TEXT("Invalid bone index '%d' or no transforms found!"), BoneIndex);
const FTransform& Transform{Transforms[BoneIndex]};
return Transform;
}
ensureAlwaysMsgf(
bRight
? MeshSettings.RightHandDefaultReferenceBoneTransforms.Contains(BoneName)
: MeshSettings.LeftHandDefaultReferenceBoneTransforms.Contains(BoneName),
TEXT("Invalid bone name '%s'!"), *BoneName.ToString());
const FTransform& Transform{
bRight
? MeshSettings.RightHandDefaultReferenceBoneTransforms[BoneName]
: MeshSettings.LeftHandDefaultReferenceBoneTransforms[BoneName]
};
return Transform;
}
FTransform FSGXRTracker::FImpl::GetMeshWristChildBoneRefTransform(const bool bRight, const FName& BoneName)
{
const FSGVirtualHandMeshSettings& MeshSettings{GetVirtualHandSettings().MeshSettings};
FQuat RootRotationCorrection{MeshSettings.RootBoneRotationCorrection};
const FTransform& RootBoneRefTransform{GetMeshRawBoneRefTransform(bRight, EHandKeypoint::Wrist)};
const FTransform& RawTransform{GetMeshRawBoneRefTransform(bRight, BoneName)};
FQuat CorrectedRootBoneRefRotation{MoveTemp(RootRotationCorrection) * RootBoneRefTransform.GetRotation()};
FVector CorrectedLocation{MoveTemp(CorrectedRootBoneRefRotation) * RawTransform.GetLocation()};
const FTransform Transform{RawTransform.GetRotation(), MoveTemp(CorrectedLocation)};
return Transform;
}
FTransform FSGXRTracker::FImpl::GetMeshFingerStartBoneRefTransform(
const bool bRight, const EHandKeypoint Parent, const FName& BoneName)
{
const FSGVirtualHandMeshSettings& MeshSettings{GetVirtualHandSettings().MeshSettings};
FQuat RootRotationCorrection{MeshSettings.RootBoneRotationCorrection};
const FTransform& RootBoneRefTransform{GetMeshRawBoneRefTransform(bRight, EHandKeypoint::Wrist)};
FQuat CorrectedRootBoneRefRotation{MoveTemp(RootRotationCorrection) * RootBoneRefTransform.GetRotation()};
FTransform CorrectedRootBoneRefTransform{
MoveTemp(CorrectedRootBoneRefRotation), RootBoneRefTransform.GetLocation()
};
FTransform ParentBoneRefTransform{
GetMeshRawBoneRefTransform(bRight, Parent) * MoveTemp(CorrectedRootBoneRefTransform)
};
const FTransform Transform{GetMeshRawBoneRefTransform(bRight, BoneName) * MoveTemp(ParentBoneRefTransform)};
return Transform;
}
bool FSGXRTracker::GetMotionControllerState(
UObject* WorldContextObject,
const EXRSpaceType XRSpaceType, const EControllerHand Hand, const EXRControllerPoseType XRControllerPoseType,
FXRMotionControllerState& OutMotionControllerState)
{
auto ToMotionSourceName = [](
const EControllerHand Hand, const EXRControllerPoseType XRControllerPoseType) -> FLazyName
{
static FLazyName LeftAim = "LeftAim";
static FLazyName LeftGrip = "LeftGrip";
static FLazyName LeftPalm = "LeftPalm";
static FLazyName RightAim = "RightAim";
static FLazyName RightGrip = "RightGrip";
static FLazyName RightPalm = "RightPalm";
if (Hand == EControllerHand::Left)
{
switch (XRControllerPoseType)
{
case EXRControllerPoseType::Aim:
return LeftAim;
case EXRControllerPoseType::Grip:
return LeftGrip;
case EXRControllerPoseType::Palm:
return LeftPalm;
default:
check(false);
return LeftGrip;
}
}
else
{
switch (XRControllerPoseType)
{
case EXRControllerPoseType::Aim:
return RightAim;
case EXRControllerPoseType::Grip:
return RightGrip;
case EXRControllerPoseType::Palm:
return RightPalm;
default:
check(false);
return RightGrip;
}
}
};
OutMotionControllerState.DeviceName = NAME_None;
OutMotionControllerState.ApplicationInstanceID = FApp::GetInstanceId();
OutMotionControllerState.TrackingStatus = ETrackingStatus::NotTracked;
OutMotionControllerState.Hand = Hand;
OutMotionControllerState.XRSpaceType = XRSpaceType;
OutMotionControllerState.bValid = false;
if (!IsValid(WorldContextObject))
{
return false;
}
if (!IsValid(WorldContextObject))
{
return false;
}
IXRTrackingSystem* XRTrackingSystem{GEngine ? GEngine->XRSystem.Get() : nullptr};
if (!XRTrackingSystem)
{
return false;
}
IOpenXRHMD* OpenXRHMD{XRTrackingSystem->GetIOpenXRHMD()};
if (!OpenXRHMD)
{
return false;
}
TArray<int32> Devices;
const int32 HandIndex = static_cast<int32>(Hand);
const bool bGotDevices =
XRTrackingSystem->EnumerateTrackedDevices(Devices, EXRTrackedDeviceType::Controller);
if (bGotDevices && Devices.IsValidIndex(HandIndex))
{
const XrSession Session{OpenXRHMD->GetSession()};
if (Session)
{
XrInteractionProfileState Profile = {XR_TYPE_INTERACTION_PROFILE_STATE};
const XrPath TopLevelUserPath{OpenXRHMD->GetTrackedDevicePath(Devices[HandIndex])};
const XrResult CurrentInteractionProfile{
xrGetCurrentInteractionProfile(Session, TopLevelUserPath, &Profile)
};
if (XR_SUCCEEDED(CurrentInteractionProfile) && Profile.interactionProfile != XR_NULL_PATH)
{
OutMotionControllerState.DeviceName = FOpenXRPath(Profile.interactionProfile);
}
}
}
if ((Hand == EControllerHand::Left) || (Hand == EControllerHand::Right))
{
static const FName MotionControllerName("OpenXR");
TArray<IMotionController*> MotionControllers{
IModularFeatures::Get().GetModularFeatureImplementations<IMotionController>(
IMotionController::GetModularFeatureName())
};
IMotionController* MotionController{nullptr};
for (IMotionController* MotionControllerIterator: MotionControllers)
{
if (!MotionControllerIterator)
{
continue;
}
const FName DeviceTypeName{MotionControllerIterator->GetMotionControllerDeviceTypeName()};
if (DeviceTypeName != MotionControllerName)
{
continue;
}
MotionController = MotionControllerIterator;
break;
}
if (MotionController)
{
{
// Handle the pose that is actually being requested.
const FName MotionSource{ToMotionSourceName(Hand, XRControllerPoseType)};
const FTransform TrackingToWorld{
XRSpaceType == EXRSpaceType::UnrealWorldSpace
? XRTrackingSystem->GetTrackingToWorldTransform()
: FTransform::Identity
};
const float WorldToMeters = XRSpaceType == EXRSpaceType::UnrealWorldSpace
? XRTrackingSystem->GetWorldToMetersScale()
: 100.0f;
FVector Position{FVector::ZeroVector};
FRotator Rotation{FRotator::ZeroRotator};
bool bGotMotionControllerOrientationAndPosition = MotionController->GetControllerOrientationAndPosition(
0, MotionSource, Rotation, Position, WorldToMeters);
if (bGotMotionControllerOrientationAndPosition)
{
OutMotionControllerState.ControllerLocation = TrackingToWorld.TransformPosition(Position);
OutMotionControllerState.ControllerRotation = TrackingToWorld.TransformRotation(FQuat{Rotation});
}
OutMotionControllerState.bValid |= bGotMotionControllerOrientationAndPosition;
OutMotionControllerState.TrackingStatus =
MotionController->GetControllerTrackingStatus(0, MotionSource);
}
{
// We always provide the grip transform in Unreal space for XRVisualizationFunctionLibrary;
// The bValid and TrackingStatus above are also valid for this pose.
const FName MotionSource = ToMotionSourceName(Hand, EXRControllerPoseType::Grip);
const FTransform TrackingToWorld{XRTrackingSystem->GetTrackingToWorldTransform()};
const float WorldToMeters = XRTrackingSystem->GetWorldToMetersScale();
FVector Position = FVector::ZeroVector;
FRotator Rotation = FRotator::ZeroRotator;
bool bGotMotionControllerOrientationAndPosition = MotionController->GetControllerOrientationAndPosition(
0, MotionSource, Rotation, Position, WorldToMeters);
if (bGotMotionControllerOrientationAndPosition)
{
OutMotionControllerState.GripUnrealSpaceLocation = TrackingToWorld.TransformPosition(Position);
OutMotionControllerState.GripUnrealSpaceRotation =
TrackingToWorld.TransformRotation(FQuat(Rotation));
}
}
}
}
return OutMotionControllerState.bValid;
}
bool FSGXRTracker::GetHandTrackingState(
UObject* WorldContextObject,
const EXRSpaceType XRSpaceType, const EControllerHand Hand,
FXRHandTrackingState& OutHandTrackingState)
{
OutHandTrackingState.ApplicationInstanceID = FApp::GetInstanceId();
OutHandTrackingState.TrackingStatus = ETrackingStatus::NotTracked;
OutHandTrackingState.Hand = Hand;
OutHandTrackingState.XRSpaceType = XRSpaceType;
OutHandTrackingState.bValid = false;
if (!IsValid(WorldContextObject))
{
return false;
}
IXRTrackingSystem* XRTrackingSystem{GEngine ? GEngine->XRSystem.Get() : nullptr};
if (!XRTrackingSystem)
{
return false;
}
IOpenXRHMD* OpenXRHMD{XRTrackingSystem->GetIOpenXRHMD()};
if (!OpenXRHMD)
{
return false;
}
FName HandTrackerName("OpenXRHandTracking");
TArray<IHandTracker*> HandTrackers = IModularFeatures::Get().GetModularFeatureImplementations<IHandTracker>(
IHandTracker::GetModularFeatureName());
IHandTracker* HandTracker = nullptr;
for (auto Itr: HandTrackers)
{
if (Itr->GetHandTrackerDeviceTypeName() == HandTrackerName)
{
HandTracker = Itr;
break;
}
}
if ((Hand == EControllerHand::Left) || (Hand == EControllerHand::Right))
{
const bool bHandTrackingStateValid = HandTracker && HandTracker->IsHandTrackingStateValid();
if (bHandTrackingStateValid)
{
bool bTracked = false;
OutHandTrackingState.bValid = HandTracker->GetAllKeypointStates(
Hand,
OutHandTrackingState.HandKeyLocations,
OutHandTrackingState.HandKeyRotations,
OutHandTrackingState.HandKeyRadii,
bTracked);
if (!ensureAlwaysMsgf(
!OutHandTrackingState.bValid
|| (OutHandTrackingState.HandKeyLocations.Num() == EHandKeypointCount
&& OutHandTrackingState.HandKeyRotations.Num() == EHandKeypointCount
&& OutHandTrackingState.HandKeyRadii.Num() == EHandKeypointCount),
TEXT("Corrupted OutHandTrackingState!")))
{
OutHandTrackingState.bValid = false;
return false;
}
if (OutHandTrackingState.bValid)
{
OutHandTrackingState.TrackingStatus = bTracked ? ETrackingStatus::Tracked : ETrackingStatus::NotTracked;
}
}
}
return OutHandTrackingState.bValid;
}
FSGXRTracker::FSGXRTracker(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)
: Pimpl(TUniquePtr<FImpl, FImplDeleter>(new FImpl{this}, PimplDeleter))
{
Pimpl->MessageHandler = InMessageHandler;
// Register the modular features manually.
IModularFeatures::Get().RegisterModularFeature(IHandTracker::GetModularFeatureName(),
static_cast<IHandTracker*>(this));
IModularFeatures::Get().RegisterModularFeature(IMotionController::GetModularFeatureName(),
static_cast<IMotionController*>(this));
IModularFeatures::Get().RegisterModularFeature(IOpenXRExtensionPlugin::GetModularFeatureName(),
static_cast<IOpenXRExtensionPlugin*>(this));
// We're implicitly requiring that the OpenXRPlugin has been loaded and initialized at this point.
if (!IOpenXRHMDModule::Get().IsAvailable())
{
SGLOG_FATAL("The OpenXRHMD plugin isn't available!");
ensureAlwaysMsgf(false, TEXT("The OpenXRHMD plugin isn't available!"));
}
}
FSGXRTracker::FSGXRTracker(FSGXRTracker&& Rhs) SG_NOEXCEPT = default;
FSGXRTracker::~FSGXRTracker() = default;
FSGXRTracker& FSGXRTracker::operator=(FSGXRTracker&& Rhs) SG_NOEXCEPT = default;
bool FSGXRTracker::IsHandTrackingSupportedByDevice() const
{
const bool bFallbackToHandTrackingIfNoGloveDetected = FImpl::ShouldFallbackToHandTrackingIfNoGloveDetected();
const bool bAnyGloveConnected = FImpl::IsAnyGloveConnected();
const bool bHandTrackingAvailable = Pimpl->bHandTrackingAvailable;
const bool bHandTrackingSupportedByDevice =
bAnyGloveConnected || (bFallbackToHandTrackingIfNoGloveDetected && bHandTrackingAvailable);
return bHandTrackingSupportedByDevice;
}
const FSGXRHandState& FSGXRTracker::GetLeftHandState() const
{
return Pimpl->GetLeftHandState();
}
const FSGXRHandState& FSGXRTracker::GetRightHandState() const
{
return Pimpl->GetRightHandState();
}
FString FSGXRTracker::GetDisplayName()
{
static const FString Name{TEXT("SenseGloveTracking")};
return Name;
}
bool FSGXRTracker::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
TArray<XrApiLayerProperties> Properties;
EnumerateOpenXRApiLayers(Properties);
// Some API layers can crash the loader when enabled, if they're present we shouldn't enable the extension.
for (const XrApiLayerProperties& Layer: Properties)
{
if (FCStringAnsi::Strstr(Layer.layerName, "XR_APILAYER_VIVE_hand_tracking")
&& Layer.layerVersion <= 1)
{
return false;
}
}
OutExtensions.Add(XR_EXT_HAND_TRACKING_EXTENSION_NAME);
return true;
}
const void* FSGXRTracker::OnGetSystem(const XrInstance InInstance, const void* InNext)
{
// Store the extension open xr calls to member function pointers for convenient use.
XR_ENSURE(xrGetInstanceProcAddr(InInstance,
"xrCreateHandTrackerEXT", (PFN_xrVoidFunction*)&Pimpl->XRCreateHandTrackerEXT));
XR_ENSURE(xrGetInstanceProcAddr(InInstance,
"xrDestroyHandTrackerEXT", (PFN_xrVoidFunction*)&Pimpl->XRDestroyHandTrackerEXT));
XR_ENSURE(xrGetInstanceProcAddr(InInstance,
"xrLocateHandJointsEXT", (PFN_xrVoidFunction*)&Pimpl->XRLocateHandJointsEXT));
return InNext;
}
const void* FSGXRTracker::OnCreateSession(const XrInstance InInstance, const XrSystemId InSystem, const void* InNext)
{
// Need to wait until the EHandKeypoint enum has been loaded, so we do this here rather than in the constructor
// which runs too early.
Pimpl->BuildMotionSourceToKeypointMap();
XrSystemHandTrackingPropertiesEXT HandTrackingSystemProperties{
XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT};
XrSystemProperties systemProperties{XR_TYPE_SYSTEM_PROPERTIES,
&HandTrackingSystemProperties};
XR_ENSURE(xrGetSystemProperties(InInstance, InSystem, &systemProperties));
Pimpl->bHandTrackingAvailable = HandTrackingSystemProperties.supportsHandTracking == XR_TRUE;
return InNext;
}
const void* FSGXRTracker::OnBeginSession(const XrSession InSession, const void* InNext)
{
const bool bHandTrackingAvailable = Pimpl->bHandTrackingAvailable;
if (bHandTrackingAvailable)
{
static FName SystemName(TEXT("OpenXR"));
if (IsValid(GEngine) && GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName))
{
Pimpl->XRTrackingSystem = GEngine->XRSystem.Get();
}
// Create a hand tracker for the left hand that tracks default set of hand joints.
FSGXRHandState& LeftHandState{Pimpl->GetLeftHandState()};
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 7
Pimpl->CreateHandTracker(InSession, XR_HAND_LEFT_EXT, LeftHandState);
#else /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 7 */
{
XrHandTrackerCreateInfoEXT CreateInfo{XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT};
CreateInfo.hand = XR_HAND_LEFT_EXT;
CreateInfo.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT;
XR_ENSURE(Pimpl->XRCreateHandTrackerEXT(InSession, &CreateInfo, &LeftHandState.HandTracker));
}
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 7 */
// Create a hand tracker for the right hand that tracks default set of hand joints.
FSGXRHandState& RightHandState{Pimpl->GetRightHandState()};
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 7
Pimpl->CreateHandTracker(InSession, XR_HAND_RIGHT_EXT, RightHandState);
#else /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 7 */
{
XrHandTrackerCreateInfoEXT CreateInfo{XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT};
CreateInfo.hand = XR_HAND_RIGHT_EXT;
CreateInfo.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT;
XR_ENSURE(Pimpl->XRCreateHandTrackerEXT(InSession, &CreateInfo, &RightHandState.HandTracker));
}
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 7 */
}
return InNext;
}
void FSGXRTracker::UpdateDeviceLocations(
const XrSession InSession, const XrTime DisplayTime, const XrSpace TrackingSpace)
{
if (!IsHandTrackingStateValid())
{
return;
}
XrHandJointsLocateInfoEXT LocateInfo{XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT};
LocateInfo.baseSpace = TrackingSpace;
LocateInfo.time = DisplayTime;
const bool bFallbackToHandTrackingIfNoGloveDetected = FImpl::ShouldFallbackToHandTrackingIfNoGloveDetected();
const bool bLeftGloveConnected = FImpl::IsLeftGloveConnected();
if (bLeftGloveConnected)
{
/// NOTE
/// It's necessary to call the UpdateXRHandTrackingData first for the wrist-tracking to work properly when the
/// motion controllers are not active and we rely on the OpenXRHandTracking data for wrist tracking.
/// See the NOTE inside the FSGXRTracker::FImpl::GetWristTransform method.
Pimpl->UpdateXRHandTrackingData(Pimpl->GetLeftHandState(), LocateInfo);
Pimpl->UpdateSGHandState(false, Pimpl->GetLeftHandState());
}
else
{
if (bFallbackToHandTrackingIfNoGloveDetected)
{
Pimpl->UpdateXRHandTrackingData(Pimpl->GetLeftHandState(), LocateInfo);
}
}
const bool bRightGloveConnected = FImpl::IsRightGloveConnected();
if (bRightGloveConnected)
{
/// NOTE
/// It's necessary to call the UpdateXRHandTrackingData first for the wrist-tracking to work properly when the
/// motion controllers are not active and we rely on the OpenXRHandTracking data for wrist tracking.
/// See the NOTE inside the FSGXRTracker::FImpl::GetWristLocationAndRotation method.
Pimpl->UpdateXRHandTrackingData(Pimpl->GetRightHandState(), LocateInfo);
Pimpl->UpdateSGHandState(true, Pimpl->GetRightHandState());
}
else
{
if (bFallbackToHandTrackingIfNoGloveDetected)
{
Pimpl->UpdateXRHandTrackingData(Pimpl->GetRightHandState(), LocateInfo);
}
}
}
bool FSGXRTracker::GetControllerOrientationAndPosition(const int32 ControllerIndex, const FName MotionSource,
FRotator& OutOrientation, FVector& OutPosition,
const float WorldToMetersScale) const
{
if (!IsHandTrackingStateValid())
{
return false;
}
// Hand tracking currently does not support late update. Current hand tracking systems have latency that make it
// pointless.
if (!IsInGameThread())
{
return false;
}
if (ControllerIndex != Pimpl->DeviceIndex)
{
return false;
}
FTransform ControllerTransform;
bool bMotionSourceHandTracking;
const bool bGotTransform = Pimpl->GetControllerTransform(MotionSource, WorldToMetersScale,
ControllerTransform, bMotionSourceHandTracking);
(void) bMotionSourceHandTracking;
OutOrientation = ControllerTransform.GetRotation().Rotator();
OutPosition = ControllerTransform.GetLocation();
return bGotTransform;
}
ETrackingStatus FSGXRTracker::GetControllerTrackingStatus(const int32 ControllerIndex, const FName MotionSource) const
{
if (!IsHandTrackingStateValid())
{
return ETrackingStatus::NotTracked;
}
const FImpl::FSGMotionSourceInfo* KeyPointInfoPtr{Pimpl->MotionSourceToKeypointMap.Find(MotionSource)};
if (!KeyPointInfoPtr)
{
return ETrackingStatus::NotTracked;
}
const bool bRight = !KeyPointInfoPtr->Value;
const bool bGlovePresent =
(bRight && FImpl::IsRightGloveConnected()) || (!bRight && FImpl::IsLeftGloveConnected());
if (bGlovePresent)
{
return ETrackingStatus::Tracked;
}
const bool bFallbackToHandTrackingIfNoGloveDetected = FImpl::ShouldFallbackToHandTrackingIfNoGloveDetected();
if (bFallbackToHandTrackingIfNoGloveDetected)
{
const FSGXRHandState& HandState{bRight ? GetRightHandState() : GetLeftHandState()};
return HandState.bTracked ? ETrackingStatus::Tracked : ETrackingStatus::NotTracked;
}
return ETrackingStatus::NotTracked;
}
FName FSGXRTracker::GetMotionControllerDeviceTypeName() const
{
const static FName DefaultName(TEXT("OpenXRHandTracking"));
return DefaultName;
}
void FSGXRTracker::EnumerateSources(TArray<FMotionControllerSource>& SourcesOut) const
{
ensureAlwaysMsgf(IsInGameThread(), TEXT("Is not in the game thread!"));
SourcesOut.Reserve(SourcesOut.Num() + (EHandKeypointCount * 2));
const FName& Left(FImpl::GetLeftHandTrackingMotionSourceName());
const FName& Right(FImpl::GetRightHandTrackingMotionSourceName());
for (int32 Keypoint = 0; Keypoint < EHandKeypointCount; Keypoint++)
{
const FName EnumName{FImpl::GetHandKeypointEnumName(Keypoint)};
const FString EnumString{EnumName.ToString()};
const FString StringLeft{FString::Printf(TEXT("%s%s"), *Left.ToString(), *EnumString)};
const FString StringRight{FString::Printf(TEXT("%s%s"), *Right.ToString(), *EnumString)};
FName SourceL(*(StringLeft));
FName SourceR(*(StringRight));
SourcesOut.Add(MoveTemp(SourceL));
SourcesOut.Add(MoveTemp(SourceR));
}
}
void FSGXRTracker::Tick(const float DeltaTime)
{
}
void FSGXRTracker::SendControllerEvents()
{
}
void FSGXRTracker::SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)
{
Pimpl->MessageHandler = InMessageHandler;
}
bool FSGXRTracker::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
{
return false;
}
void FSGXRTracker::SetChannelValue(
const int32 ControllerId, const FForceFeedbackChannelType ChannelType, const float Value)
{
}
void FSGXRTracker::SetChannelValues(const int32 ControllerId, const FForceFeedbackValues& Values)
{
}
bool FSGXRTracker::SupportsForceFeedback(const int32 ControllerId)
{
// FIXME
// For now it's true, but when we fall back to the hand tracking we might return false.
(void) ControllerId;
return true;
}
bool FSGXRTracker::IsGamepadAttached() const
{
return false;
}
FName FSGXRTracker::GetHandTrackerDeviceTypeName() const
{
return GetMotionControllerDeviceTypeName();
}
bool FSGXRTracker::IsHandTrackingStateValid() const
{
const bool bFallbackToHandTrackingIfNoGloveDetected = FImpl::ShouldFallbackToHandTrackingIfNoGloveDetected();
const bool bHandTrackingAvailable = Pimpl->bHandTrackingAvailable;
const bool bAnyGloveConnected = FImpl::IsAnyGloveConnected();
const bool bHandTrackingStateValid =
bAnyGloveConnected || (bFallbackToHandTrackingIfNoGloveDetected && bHandTrackingAvailable);
return bHandTrackingStateValid;
}
bool FSGXRTracker::GetKeypointState(
const EControllerHand Hand, const EHandKeypoint Keypoint, FTransform& OutTransform, float& OutRadius) const
{
if (!IsHandTrackingStateValid())
{
return false;
}
if (!Pimpl->CanOutputTrackingData(Hand))
{
return false;
}
const FSGXRHandState& HandState{
Hand == EControllerHand::Left ? GetLeftHandState() : GetRightHandState()
};
const bool bGotTransform = HandState.GetTransform(Keypoint, OutTransform);
OutRadius = HandState.Radii[static_cast<uint32>(Keypoint)];
if (bGotTransform)
{
if (!ensureAlwaysMsgf(Pimpl->XRTrackingSystem, TEXT("Invalid XR tracking system!")))
{
return false;
}
// Convert to UE world space.
const FTransform& TrackingToWorldTransform{Pimpl->XRTrackingSystem->GetTrackingToWorldTransform()};
OutTransform *= TrackingToWorldTransform;
}
return bGotTransform;
}
bool FSGXRTracker::GetAllKeypointStates(
const EControllerHand Hand,
TArray<FVector>& OutPositions, TArray<FQuat>& OutRotations, TArray<float>& OutRadii,
bool& OutIsTracked) const
{
if (!IsHandTrackingStateValid())
{
return false;
}
if (Hand != EControllerHand::Left && Hand != EControllerHand::Right)
{
return false;
}
if (!Pimpl->CanOutputTrackingData(Hand))
{
return false;
}
const FSGXRHandState& HandState{
Hand == EControllerHand::Left ? GetLeftHandState() : GetRightHandState()
};
if (!HandState.bHasReceivedJointPoses)
{
return false;
}
OutIsTracked = HandState.bTracked;
// If we ever received poses we return the last data we received, even if not currently tracking to avoid poping
// back to (0.0f, 0.0f, 0.0f) and the like.
OutPositions.Empty(EHandKeypointCount);
OutRotations.Empty(EHandKeypointCount);
OutRadii.Empty(EHandKeypointCount);
if (!ensureAlwaysMsgf(Pimpl->XRTrackingSystem, TEXT("Invalid XR tracking system!")))
{
return false;
}
const FTransform& TrackingToWorldTransform{Pimpl->XRTrackingSystem->GetTrackingToWorldTransform()};
for (int32 i = 0; i < EHandKeypointCount; ++i)
{
FTransform KeypointWorldTransform{HandState.KeypointTransforms[i] * TrackingToWorldTransform};
OutPositions.Add(KeypointWorldTransform.GetLocation());
OutRotations.Add(KeypointWorldTransform.GetRotation());
}
for (int32 i = 0; i < EHandKeypointCount; ++i)
{
OutRadii.Add(HandState.Radii[i]);
}
return true;
}
FSGXRTracker::FImpl::FImpl(FSGXRTracker* InOwner)
: bHandTrackingAvailable(false),
XRCreateHandTrackerEXT(nullptr),
XRDestroyHandTrackerEXT(nullptr),
XRLocateHandJointsEXT(nullptr),
XRTrackingSystem(nullptr),
DeviceIndex(0),
CurrentHandTrackingDataIndex(0),
Owner(InOwner)
{
}
FSGXRTracker::FImpl::~FImpl() = default;
void FSGXRTracker::FImpl::BuildMotionSourceToKeypointMap()
{
ensureAlwaysMsgf(IsInGameThread(), TEXT("Is not in the game thread!"));
if (!MotionSourceToKeypointMap.IsEmpty())
{
return;
}
const bool bSupportLegacyControllerMotionSources = ShouldSupportLegacyControllerMotionSources();
// There is a motion source that corresponds to each hand keypoint of the form [Left|Right][Keypoint].
// Build a map so we can quickly translate from motion source FName to hand bone EHandKeypoint value.
// We also have the option of using more specific motion sources of the form HandTracking[Left|Right][Keypoint].
// This is useful if one wishes to use hand tracking and controllers simultaneously.
// We also may support more generic legacy motion sources, by default we do support this.
const FName& Left(GetLeftHandTrackingMotionSourceName());
const FName& Right(GetRightHandTrackingMotionSourceName());
for (int32 KeypointIndex = 0; KeypointIndex < EHandKeypointCount; ++KeypointIndex)
{
const EHandKeypoint EnumValue = static_cast<EHandKeypoint>(KeypointIndex);
const FName EnumName{GetHandKeypointEnumName(KeypointIndex)};
const FString EnumString{EnumName.ToString()};
FName LeftName(Left.ToString() + EnumString);
FName RightName(Right.ToString() + EnumString);
MotionSourceToKeypointMap.Add(MoveTemp(LeftName), FSGMotionSourceInfo(EnumValue, true));
MotionSourceToKeypointMap.Add(MoveTemp(RightName), FSGMotionSourceInfo(EnumValue, false));
}
if (bSupportLegacyControllerMotionSources)
{
const FName& LeftLegacyControllerHandTrackingMotionSourceName{
GetLegacyControllerLeftHandTrackingMotionSourceName()};
const FName& RightLegacyControllerHandTrackingMotionSourceName{
GetLegacyControllerRightHandTrackingMotionSourceName()};
MotionSourceToKeypointMap.Add(
LeftLegacyControllerHandTrackingMotionSourceName, FSGMotionSourceInfo(EHandKeypoint::Wrist, true));
MotionSourceToKeypointMap.Add(
RightLegacyControllerHandTrackingMotionSourceName, FSGMotionSourceInfo(EHandKeypoint::Wrist, false));
}
const FSGWristTrackingSettings& WristTrackingSettings{GetWristTrackingSettings()};
const FName& LeftHandSGMotionSourceName{WristTrackingSettings.LeftHandMotionSource};
const FName& RightHandSGMotionSourceName{WristTrackingSettings.RightHandMotionSource};
MotionSourceToKeypointMap.Add(
LeftHandSGMotionSourceName, FSGMotionSourceInfo(EHandKeypoint::Wrist, true));
MotionSourceToKeypointMap.Add(
RightHandSGMotionSourceName, FSGMotionSourceInfo(EHandKeypoint::Wrist, false));
}
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 7
void FSGXRTracker::FImpl::CreateHandTracker(XrSession InSession, XrHandEXT HandEXT, FSGXRHandState& HandState) const
{
XrHandTrackerCreateInfoEXT CreateInfo{XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT};
CreateInfo.hand = HandEXT;
CreateInfo.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT;
IOpenXRHMD* OpenXRHMD{XRTrackingSystem ? XRTrackingSystem->GetIOpenXRHMD() : nullptr};
if (OpenXRHMD)
{
TArray<IOpenXRExtensionPlugin*> ExtensionPlugins{OpenXRHMD->GetExtensionPlugins()};
for (IOpenXRExtensionPlugin* Plugin : ExtensionPlugins)
{
CreateInfo.next = Plugin->OnCreateHandTracker(CreateInfo.next);
}
}
XR_ENSURE(XRCreateHandTrackerEXT(InSession, &CreateInfo, &HandState.HandTracker));
}
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 7 */
bool FSGXRTracker::FImpl::CanOutputTrackingData(const EControllerHand Hand) const
{
const bool bFallbackToHandTrackingIfNoGloveDetected = ShouldFallbackToHandTrackingIfNoGloveDetected();
const bool bIsHandTrackingAvailable = bHandTrackingAvailable;
bool bGloveConnected = false;
if (Hand == EControllerHand::Left)
{
bGloveConnected = IsLeftGloveConnected();
}
else if (Hand == EControllerHand::Right)
{
bGloveConnected = IsRightGloveConnected();
}
const bool bCanOutputTrackingData =
bGloveConnected || (bFallbackToHandTrackingIfNoGloveDetected && bIsHandTrackingAvailable);
return bCanOutputTrackingData;
}
bool FSGXRTracker::FImpl::UpdateXRHandTrackingData(
FSGXRHandState& OutHandState, const XrHandJointsLocateInfoEXT& LocateInfo) const
{
if (!ensureAlwaysMsgf(XRTrackingSystem, TEXT("Invalid XR tracking system!")))
{
return false;
}
const float WorldToMetersScale = XRTrackingSystem->GetWorldToMetersScale();
XR_ENSURE(XRLocateHandJointsEXT(OutHandState.HandTracker, &LocateInfo, &OutHandState.Locations));
OutHandState.bTracked = OutHandState.Locations.isActive == XR_TRUE;
OutHandState.bHasReceivedJointPoses |= OutHandState.bTracked;
if (!OutHandState.bTracked)
{
return false;
}
static_assert(
XR_HAND_JOINT_PALM_EXT == 0 && XR_HAND_JOINT_LITTLE_TIP_EXT == XR_HAND_JOINT_COUNT_EXT - 1,
"XrHandJointEXT enum is not as assumed for the following loop!");
for (int32 j = 0; j < XR_HAND_JOINT_COUNT_EXT; ++j)
{
const XrHandJointLocationEXT& JointLocation{OutHandState.JointLocations[j]};
const XrPosef& Pose{JointLocation.pose};
FTransform Transform{ToFTransform(Pose, WorldToMetersScale)};
OutHandState.KeypointTransforms[j] = MoveTemp(Transform);
OutHandState.Radii[j] = JointLocation.radius * WorldToMetersScale;
// velocities would go here
}
return true;
}
FName FSGXRTracker::FImpl::GetTargetOpenXRPositionalTrackingProviderName() const
{
const FSGWristTrackingSettings& WristTrackingSettings{GetWristTrackingSettings()};
ESGOpenXRPositionalTrackingProvider PositionalTrackingProvider =
WristTrackingSettings.OpenXRPositionalTrackingProvider;
if (PositionalTrackingProvider == ESGOpenXRPositionalTrackingProvider::None)
{
const bool bIsAHtcViveDevice = FSGHMDTracker::IsAHtcViveDevice();
if (bIsAHtcViveDevice)
{
#if PLATFORM_ANDROID
PositionalTrackingProvider = ESGOpenXRPositionalTrackingProvider::OpenXRViveWristTracker;
#else /* PLATFORM_ANDROID */
// TODO
// Revisit this later!
// For now only `Emulate VIVE Wrist Tracker as VIVE Tracker` option in VBE works on Windows
// const bool bIsViveOpenXRPluginLoaded = FSGPluginUtils::IsViveOpenXRPluginAvailable();
// if (bIsViveOpenXRPluginLoaded)
// {
// if (WristTrackingSettings.TrackingHardware == ESGPositionalTrackingHardware::ViveFocus3WristTracker)
// {
// PositionalTrackingProvider = ESGOpenXRPositionalTrackingProvider::OpenXRViveWristTracker;
// }
// else
// {
// PositionalTrackingProvider = ESGOpenXRPositionalTrackingProvider::OpenXRViveTracker;
// }
// }
// else
// {
// PositionalTrackingProvider = ESGOpenXRPositionalTrackingProvider::OpenXRViveTracker;
// }
PositionalTrackingProvider = ESGOpenXRPositionalTrackingProvider::OpenXRViveTracker;
#endif /* PLATFORM_ANDROID */
}
else
{
PositionalTrackingProvider = ESGOpenXRPositionalTrackingProvider::OpenXR;
}
}
const FName PositionalTrackingProviderName{
GetOpenXRPositionalTrackingProviderEnumName(PositionalTrackingProvider)
};
return PositionalTrackingProviderName;
}
bool FSGXRTracker::FImpl::GetControllerTransform(
const FName MotionSource, const float WorldToMetersScale,
FTransform& OutTransform, bool& bOutMotionSourceHandTracking) const
{
const FSGMotionSourceInfo* KeyPointInfoPtr{MotionSourceToKeypointMap.Find(MotionSource)};
if (!KeyPointInfoPtr)
{
return false;
}
const bool bRight = !KeyPointInfoPtr->Value;
const bool bGlovePresent =
(bRight && IsRightGloveConnected()) || (!bRight && IsLeftGloveConnected());
const bool bFallbackToHandTrackingIfNoGloveDetected = ShouldFallbackToHandTrackingIfNoGloveDetected();
if (!bGlovePresent && !bFallbackToHandTrackingIfNoGloveDetected)
{
return false;
}
const FSGWristTrackingSettings& WristTrackingSettings{GetWristTrackingSettings()};
const FName& PositionalTrackingHardwareMotionSource{
bRight
? WristTrackingSettings.RightHandMotionSource
: WristTrackingSettings.LeftHandMotionSource
};
bool bTracked = false;
if (MotionSource == PositionalTrackingHardwareMotionSource)
{
const FName TargetPositionalTrackingProviderName{
GetTargetOpenXRPositionalTrackingProviderName()
};
const TArray<IMotionController*> MotionControllers{
IModularFeatures::Get().GetModularFeatureImplementations<IMotionController>(
IMotionController::GetModularFeatureName())
};
TPromise<TOptional<FTransform>> Promise;
const TFuture<TOptional<FTransform>> Future{Promise.GetFuture()};
AsyncTask(ENamedThreads::ActualRenderingThread,
[
this,
WorldToMetersScale,
&PositionalTrackingHardwareMotionSource,
&TargetPositionalTrackingProviderName,
MotionControllers,
Promise = MoveTemp(Promise)]() mutable
{
for (const IMotionController* MotionController: MotionControllers)
{
if (!MotionController)
{
continue;
}
const ETrackingStatus TrackingStatus = MotionController->GetControllerTrackingStatus(
DeviceIndex, PositionalTrackingHardwareMotionSource);
if (TrackingStatus == ETrackingStatus::NotTracked)
{
continue;
}
const FName DeviceTypeName{MotionController->GetMotionControllerDeviceTypeName()};
if (DeviceTypeName != TargetPositionalTrackingProviderName)
{
continue;
}
FRotator Orientation;
FVector Position;
const bool bGotTransform = MotionController->GetControllerOrientationAndPosition(
DeviceIndex, PositionalTrackingHardwareMotionSource,
Orientation, Position, WorldToMetersScale);
if (bGotTransform)
{
FTransform Transform{Orientation, Position};
Promise.SetValue(MoveTemp(Transform));
return;
}
}
Promise.SetValue(TOptional<FTransform>{});
});
if (Future.WaitFor(FTimespan::FromMilliseconds(100)))
{
TOptional<FTransform> Result{Future.Get()};
if (Result.IsSet())
{
OutTransform = Result.GetValue();
bOutMotionSourceHandTracking = false;
bTracked = true;
}
}
}
if (!bTracked && bFallbackToHandTrackingIfNoGloveDetected)
{
const FSGXRHandState& HandState{bRight ? Owner->GetRightHandState() : Owner->GetLeftHandState()};
if (!HandState.bHasReceivedJointPoses)
{
return false;
}
const EHandKeypoint KeyPoint = KeyPointInfoPtr->Key;
const FTransform& ControllerTransform{HandState.GetTransform(KeyPoint)};
OutTransform = ControllerTransform;
bOutMotionSourceHandTracking = true;
bTracked = HandState.bTracked;
}
return bTracked;
}
bool FSGXRTracker::FImpl::GetWristTransform(const bool bRight, FTransform& OutTransform) const
{
const FSGWristTrackingSettings& WristTrackingSettings{GetWristTrackingSettings()};
const FName& MotionSource{
bRight
? WristTrackingSettings.RightHandMotionSource
: WristTrackingSettings.LeftHandMotionSource
};
if (!ensureAlwaysMsgf(XRTrackingSystem, TEXT("Invalid XR tracking system!")))
{
return false;
}
const float WorldToMetersScale = XRTrackingSystem->GetWorldToMetersScale();
bool bMotionSourceHandTracking = false;
FTransform ControllerTransform;
const bool bGotTransform = GetControllerTransform(
MotionSource, WorldToMetersScale, ControllerTransform, bMotionSourceHandTracking);
if (bGotTransform)
{
if (bMotionSourceHandTracking)
{
OutTransform = MoveTemp(ControllerTransform);
}
else
{
const ESGPositionalTrackingHardware TrackingHardware = GetPositionalTrackingHardware();
if (TrackingHardware == ESGPositionalTrackingHardware::Custom)
{
if (bRight)
{
ControllerTransform.SetRotation(
(ControllerTransform.GetRotation().Rotator()
+ WristTrackingSettings.TrackingHardwareRotationOffsetRightHand).Quaternion());
ControllerTransform.SetLocation(
ControllerTransform.GetLocation()
+ WristTrackingSettings.TrackingHardwareLocationOffsetRightHand);
}
else
{
ControllerTransform.SetRotation(
(ControllerTransform.GetRotation().Rotator()
+ WristTrackingSettings.TrackingHardwareRotationOffsetLeftHand).Quaternion());
ControllerTransform.SetLocation(
ControllerTransform.GetLocation()
+ WristTrackingSettings.TrackingHardwareLocationOffsetLeftHand);
}
}
const USGHapticGlove* Glove{GetGlove(bRight)};
if (!IsValid(Glove))
{
return false;
}
FVector WristLocation;
FRotator WristRotation;
Glove->GetWristLocation(
ControllerTransform.GetLocation(),
ControllerTransform.GetRotation().Rotator(),
TrackingHardware,
WristLocation, WristRotation);
OutTransform = FTransform{MoveTemp(WristRotation), MoveTemp(WristLocation)};
}
}
return bGotTransform;
}
bool FSGXRTracker::FImpl::UpdateSGJointData(
FSGXRHandState& OutHandState, const EHandKeypoint Joint,
const FTransform& JointTransform, const FTransform& WristTransform) const
{
if (!ensureAlwaysMsgf(XRTrackingSystem, TEXT("Invalid XR tracking system!")))
{
return false;
}
const float WorldToMetersScale = XRTrackingSystem->GetWorldToMetersScale();
const int32 JointIndex = static_cast<int32>(Joint);
const FQuat WristRotation{WristTransform.GetRotation()};
const FVector WristLocation{WristTransform.GetLocation()};
const FQuat JointRotation{JointTransform.GetRotation()};
const FVector JointLocation{JointTransform.GetLocation()};
FTransform Transform{
WristRotation * JointRotation,
WristLocation + (WristRotation * JointLocation)
};
OutHandState.KeypointTransforms[JointIndex] = MoveTemp(Transform);
OutHandState.Radii[JointIndex] = 0.01f * WorldToMetersScale;
return true;
}
bool FSGXRTracker::FImpl::UpdateSGWristJointData(
FSGXRHandState& OutHandState, const FTransform& WristTransform) const
{
if (!ensureAlwaysMsgf(XRTrackingSystem, TEXT("Invalid XR tracking system!")))
{
return false;
}
const float WorldToMetersScale = XRTrackingSystem->GetWorldToMetersScale();
static constexpr EHandKeypoint Joint = EHandKeypoint::Wrist;
static constexpr int32 JointIndex = static_cast<int32>(Joint);
OutHandState.KeypointTransforms[JointIndex] = WristTransform;
OutHandState.Radii[JointIndex] = 0.01f * WorldToMetersScale;
return true;
}
bool FSGXRTracker::FImpl::UpdateSGPalmJointData(
FSGXRHandState& OutHandState, const bool bRight, const FTransform& WristTransform) const
{
if (!ensureAlwaysMsgf(XRTrackingSystem, TEXT("Invalid XR tracking system!")))
{
return false;
}
FTransform JointTransform{GetMeshWristChildBoneRefTransform(bRight, EHandKeypoint::Palm)};
if (!UpdateSGJointData(OutHandState, EHandKeypoint::Palm,
MoveTemp(JointTransform), WristTransform))
{
return false;
}
return true;
}
bool FSGXRTracker::FImpl::UpdateSGThumbJointData(
FSGXRHandState& OutHandState,
const TArray<TArray<FVector>>& JointLocations, const TArray<TArray<FQuat>>& JointRotations,
const FTransform& WristTransform) const
{
static constexpr int32 ThumbFingerIndex = 0;
static constexpr int32 ThumbMetacarpalJointIndex = 0;
static constexpr int32 ThumbProximalJointIndex = 1;
static constexpr int32 ThumbDistalJointIndex = 2;
static constexpr int32 ThumbTipJointIndex = 3;
FTransform ThumbMetacarpalTransform{
JointRotations[ThumbFingerIndex][ThumbMetacarpalJointIndex],
JointLocations[ThumbFingerIndex][ThumbMetacarpalJointIndex]
};
FTransform ThumbProximalTransform{
JointRotations[ThumbFingerIndex][ThumbProximalJointIndex],
JointLocations[ThumbFingerIndex][ThumbProximalJointIndex]
};
FTransform ThumbDistalTransform{
JointRotations[ThumbFingerIndex][ThumbDistalJointIndex],
JointLocations[ThumbFingerIndex][ThumbDistalJointIndex]
};
FTransform ThumbTipTransform{
JointRotations[ThumbFingerIndex][ThumbTipJointIndex],
JointLocations[ThumbFingerIndex][ThumbTipJointIndex]
};
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::ThumbMetacarpal,
MoveTemp(ThumbMetacarpalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::ThumbProximal,
MoveTemp(ThumbProximalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::ThumbDistal,
MoveTemp(ThumbDistalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::ThumbTip,
MoveTemp(ThumbTipTransform), WristTransform))
{
return false;
}
return true;
}
bool FSGXRTracker::FImpl::UpdateSGIndexJointData(
FSGXRHandState& OutHandState, const bool bRight,
const TArray<TArray<FVector>>& JointLocations, const TArray<TArray<FQuat>>& JointRotations,
const FTransform& WristTransform) const
{
static constexpr int32 IndexFingerIndex = 1;
static constexpr int32 IndexProximalJointIndex = 0;
static constexpr int32 IndexIntermediateJointIndex = 1;
static constexpr int32 IndexDistalJointIndex = 2;
static constexpr int32 IndexTipJointIndex = 3;
FTransform IndexMetacarpalTransform{
GetMeshWristChildBoneRefTransform(bRight, EHandKeypoint::IndexMetacarpal)
};
FTransform IndexProximalTransform{
JointRotations[IndexFingerIndex][IndexProximalJointIndex],
JointLocations[IndexFingerIndex][IndexProximalJointIndex]
};
FTransform IndexIntermediateTransform{
JointRotations[IndexFingerIndex][IndexIntermediateJointIndex],
JointLocations[IndexFingerIndex][IndexIntermediateJointIndex]
};
FTransform IndexDistalTransform{
JointRotations[IndexFingerIndex][IndexDistalJointIndex],
JointLocations[IndexFingerIndex][IndexDistalJointIndex]
};
FTransform IndexTipTransform{
JointRotations[IndexFingerIndex][IndexTipJointIndex],
JointLocations[IndexFingerIndex][IndexTipJointIndex]
};
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::IndexMetacarpal,
MoveTemp(IndexMetacarpalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::IndexProximal,
MoveTemp(IndexProximalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::IndexIntermediate,
MoveTemp(IndexIntermediateTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::IndexDistal,
MoveTemp(IndexDistalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::IndexTip,
MoveTemp(IndexTipTransform), WristTransform))
{
return false;
}
return true;
}
bool FSGXRTracker::FImpl::UpdateSGMiddleJointData(
FSGXRHandState& OutHandState, const bool bRight,
const TArray<TArray<FVector>>& JointLocations, const TArray<TArray<FQuat>>& JointRotations,
const FTransform& WristTransform) const
{
static constexpr int32 MiddleFingerMiddle = 2;
static constexpr int32 MiddleProximalJointMiddle = 0;
static constexpr int32 MiddleIntermediateJointMiddle = 1;
static constexpr int32 MiddleDistalJointMiddle = 2;
static constexpr int32 MiddleTipJointMiddle = 3;
FTransform MiddleMetacarpalTransform{
GetMeshWristChildBoneRefTransform(bRight, EHandKeypoint::MiddleMetacarpal)
};
FTransform MiddleProximalTransform{
JointRotations[MiddleFingerMiddle][MiddleProximalJointMiddle],
JointLocations[MiddleFingerMiddle][MiddleProximalJointMiddle]
};
FTransform MiddleIntermediateTransform{
JointRotations[MiddleFingerMiddle][MiddleIntermediateJointMiddle],
JointLocations[MiddleFingerMiddle][MiddleIntermediateJointMiddle]
};
FTransform MiddleDistalTransform{
JointRotations[MiddleFingerMiddle][MiddleDistalJointMiddle],
JointLocations[MiddleFingerMiddle][MiddleDistalJointMiddle]
};
FTransform MiddleTipTransform{
JointRotations[MiddleFingerMiddle][MiddleTipJointMiddle],
JointLocations[MiddleFingerMiddle][MiddleTipJointMiddle]
};
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::MiddleMetacarpal,
MoveTemp(MiddleMetacarpalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::MiddleProximal,
MoveTemp(MiddleProximalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::MiddleIntermediate,
MoveTemp(MiddleIntermediateTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::MiddleDistal,
MoveTemp(MiddleDistalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::MiddleTip,
MoveTemp(MiddleTipTransform), WristTransform))
{
return false;
}
return true;
}
bool FSGXRTracker::FImpl::UpdateSGRingJointData(
FSGXRHandState& OutHandState, const bool bRight,
const TArray<TArray<FVector>>& JointLocations, const TArray<TArray<FQuat>>& JointRotations,
const FTransform& WristTransform) const
{
static constexpr int32 RingFingerRing = 3;
static constexpr int32 RingProximalJointRing = 0;
static constexpr int32 RingIntermediateJointRing = 1;
static constexpr int32 RingDistalJointRing = 2;
static constexpr int32 RingTipJointRing = 3;
FTransform RingMetacarpalTransform{
GetMeshWristChildBoneRefTransform(bRight, EHandKeypoint::RingMetacarpal)
};
FTransform RingProximalTransform{
JointRotations[RingFingerRing][RingProximalJointRing],
JointLocations[RingFingerRing][RingProximalJointRing]
};
FTransform RingIntermediateTransform{
JointRotations[RingFingerRing][RingIntermediateJointRing],
JointLocations[RingFingerRing][RingIntermediateJointRing]
};
FTransform RingDistalTransform{
JointRotations[RingFingerRing][RingDistalJointRing],
JointLocations[RingFingerRing][RingDistalJointRing]
};
FTransform RingTipTransform{
JointRotations[RingFingerRing][RingTipJointRing],
JointLocations[RingFingerRing][RingTipJointRing]
};
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::RingMetacarpal,
MoveTemp(RingMetacarpalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::RingProximal,
MoveTemp(RingProximalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::RingIntermediate,
MoveTemp(RingIntermediateTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::RingDistal,
MoveTemp(RingDistalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::RingTip,
MoveTemp(RingTipTransform), WristTransform))
{
return false;
}
return true;
}
bool FSGXRTracker::FImpl::UpdateSGLittleJointData(
FSGXRHandState& OutHandState, const bool bRight,
const TArray<TArray<FVector>>& JointLocations, const TArray<TArray<FQuat>>& JointRotations,
const FTransform& WristTransform) const
{
static constexpr int32 LittleFingerLittle = 4;
static constexpr int32 LittleProximalJointLittle = 0;
static constexpr int32 LittleIntermediateJointLittle = 1;
static constexpr int32 LittleDistalJointLittle = 2;
static constexpr int32 LittleTipJointLittle = 3;
FTransform LittleMetacarpalTransform{
GetMeshWristChildBoneRefTransform(bRight, EHandKeypoint::LittleMetacarpal)
};
FTransform LittleProximalTransform{
JointRotations[LittleFingerLittle][LittleProximalJointLittle],
JointLocations[LittleFingerLittle][LittleProximalJointLittle]
};
FTransform LittleIntermediateTransform{
JointRotations[LittleFingerLittle][LittleIntermediateJointLittle],
JointLocations[LittleFingerLittle][LittleIntermediateJointLittle]
};
FTransform LittleDistalTransform{
JointRotations[LittleFingerLittle][LittleDistalJointLittle],
JointLocations[LittleFingerLittle][LittleDistalJointLittle]
};
FTransform LittleTipTransform{
JointRotations[LittleFingerLittle][LittleTipJointLittle],
JointLocations[LittleFingerLittle][LittleTipJointLittle]
};
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::LittleMetacarpal,
MoveTemp(LittleMetacarpalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::LittleProximal,
MoveTemp(LittleProximalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::LittleIntermediate,
MoveTemp(LittleIntermediateTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::LittleDistal,
MoveTemp(LittleDistalTransform), WristTransform))
{
return false;
}
if (!UpdateSGJointData(
OutHandState, EHandKeypoint::LittleTip,
MoveTemp(LittleTipTransform), WristTransform))
{
return false;
}
return true;
}
bool FSGXRTracker::FImpl::UpdateSGHandState(const bool bRight, FSGXRHandState& OutHandState) const
{
const USGHandPose* HandPose{GetHandPose(bRight)};
if (!IsValid(HandPose))
{
return false;
}
OutHandState.bHasReceivedJointPoses = true;
OutHandState.bTracked = true;
const TArray<TArray<FVector>> JointLocations{HandPose->GetJointPositions()};
const TArray<TArray<FQuat>> JointRotations{HandPose->GetJointRotationsQ()};
FTransform WristTransform;
const bool bGotWristTransform = GetWristTransform(bRight, WristTransform);
/// NOTE
// Even if we fail to get the wrist transform, the SenseGlove data needs to make it to the FXRHandTrackingState!
// So, we just ignore the bGotWristTransform results! Otherwise, even if bFallbackToHandTrackingIfNoGloveDetected
// is disabled it will output the hand-tracking data.
(void) bGotWristTransform;
if (!UpdateSGWristJointData(OutHandState, WristTransform))
{
return false;
}
if (!UpdateSGPalmJointData(OutHandState, bRight, WristTransform))
{
return false;
}
if (!UpdateSGThumbJointData(OutHandState,
JointLocations, JointRotations,
WristTransform))
{
return false;
}
if (!UpdateSGIndexJointData(OutHandState, bRight,
JointLocations, JointRotations,
WristTransform))
{
return false;
}
if (!UpdateSGMiddleJointData(OutHandState, bRight,
JointLocations, JointRotations,
WristTransform))
{
return false;
}
if (!UpdateSGRingJointData(OutHandState, bRight,
JointLocations, JointRotations,
WristTransform))
{
return false;
}
if (!UpdateSGLittleJointData(OutHandState, bRight,
JointLocations, JointRotations,
WristTransform))
{
return false;
}
return true;
}
void FSGXRTracker::FImplDeleter::operator()(const FImpl* P) const
{
delete P;
}