Up to Unreal 5.7

This commit is contained in:
Simeon "Waldo" Wallrath 2025-11-20 16:26:22 +01:00
parent 8a63902c06
commit 1a64805d43
239 changed files with 3947 additions and 296 deletions

View file

@ -0,0 +1,61 @@
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
using System.IO;
namespace UnrealBuildTool.Rules
{
public class OpenXRExpansionEditor : ModuleRules
{
public OpenXRExpansionEditor(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
// ... add other public dependencies that you statically link with here ...
"Engine",
"Core",
"CoreUObject"
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"UnrealEd",
"BlueprintGraph",
"AnimGraph",
"AnimGraphRuntime",
"SlateCore",
"Slate",
"InputCore",
"Engine",
"EditorStyle",
"AssetRegistry",
"OpenXRExpansionPlugin"
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}
}

View file

@ -0,0 +1,34 @@
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "AnimGraphNode_ApplyOpenXRHandPose.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimGraphNode_ApplyOpenXRHandPose)
/////////////////////////////////////////////////////
// UAnimGraphNode_ModifyBSHand
UAnimGraphNode_ApplyOpenXRHandPose::UAnimGraphNode_ApplyOpenXRHandPose(const FObjectInitializer& Initializer)
: Super(Initializer)
{
}
//Title Color!
FLinearColor UAnimGraphNode_ApplyOpenXRHandPose::GetNodeTitleColor() const
{
return FLinearColor(12, 12, 0, 1);
}
//Node Category
FString UAnimGraphNode_ApplyOpenXRHandPose::GetNodeCategory() const
{
return FString("OpenXR");
}
FText UAnimGraphNode_ApplyOpenXRHandPose::GetControllerDescription() const
{
return FText::FromString("Apply OpenXR Hand Pose");
}
FText UAnimGraphNode_ApplyOpenXRHandPose::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
FText Result = GetControllerDescription();
return Result;
}

View file

@ -0,0 +1,16 @@
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "OpenXRExpansionEditor.h"
#include "Editor/UnrealEdEngine.h"
#include "UnrealEdGlobals.h"
IMPLEMENT_MODULE(FOpenXRExpansionEditorModule, OpenXRExpansionEditor);
void FOpenXRExpansionEditorModule::StartupModule()
{
}
void FOpenXRExpansionEditorModule::ShutdownModule()
{
}

View file

@ -0,0 +1,34 @@
#pragma once
#include "AnimGraphDefinitions.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Editor/AnimGraph/Public/AnimGraphNode_SkeletalControlBase.h"
#include "AnimNode_ApplyOpenXRHandPose.h"
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "AnimGraphNode_ApplyOpenXRHandPose.generated.h"
UCLASS(MinimalAPI)
class UAnimGraphNode_ApplyOpenXRHandPose : public UAnimGraphNode_SkeletalControlBase
{
GENERATED_UCLASS_BODY()
UPROPERTY(EditAnywhere, Category = Settings)
FAnimNode_ApplyOpenXRHandPose Node;
public:
// UEdGraphNode interface
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual FLinearColor GetNodeTitleColor() const override;
virtual FString GetNodeCategory() const override;
// End of UEdGraphNode interface
protected:
// UAnimGraphNode_SkeletalControlBase protected interface
virtual FText GetControllerDescription() const;
virtual const FAnimNode_SkeletalControlBase* GetNode() const override { return &Node; }
// End of UAnimGraphNode_SkeletalControlBase protected interface
};

View file

@ -0,0 +1,13 @@
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Runtime/Core/Public/Modules/ModuleInterface.h"
class FOpenXRExpansionEditorModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View file

@ -0,0 +1,76 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.IO;
namespace UnrealBuildTool.Rules
{
public class OpenXRExpansionPlugin: ModuleRules
{
public OpenXRExpansionPlugin(ReadOnlyTargetRules Target)
: base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
DefaultBuildSettings = BuildSettingsVersion.Latest;
IncludeOrderVersion = EngineIncludeOrderVersion.Latest;
SetupIrisSupport(Target);
PublicDependencyModuleNames.AddRange(
new string[]
{
//"InputDevice",
//"LiveLink",
//"LiveLinkInterface"
}
);
var EngineDir = Path.GetFullPath(Target.RelativeEnginePath);
PrivateIncludePaths.AddRange(
new string[] {
EngineDir + "Plugins/Runtime/OpenXR/Source/OpenXRHMD/Private",
EngineDir + "/Source/ThirdParty/OpenXR/include",
// ... add other private include paths required here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Core",
"NetCore",
"CoreUObject",
//"ApplicationCore",
"Engine",
//"InputDevice",
"InputCore",
"Slate",
"HeadMountedDisplay",
//"AnimGraph",
"AnimGraphRuntime",
"SlateCore",
"XRBase"
//"LiveLink",
//"LiveLinkInterface",
}
);
if (Target.Platform != UnrealTargetPlatform.Mac && Target.Platform != UnrealTargetPlatform.IOS)
{
PrivateDependencyModuleNames.AddRange(
new string[]
{
"OpenXRHMD"
}
);
PrivateDefinitions.AddRange(new string[] { "OPENXR_SUPPORTED" });
AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenXR");
}
// if (Target.bBuildEditor == true)
// {
// PrivateDependencyModuleNames.Add("UnrealEd");
// }
}
}
}

View file

@ -0,0 +1,488 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AnimNode_ApplyOpenXRHandPose.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_ApplyOpenXRHandPose)
//#include "EngineMinimal.h"
//#include "Engine/Engine.h"
//#include "CoreMinimal.h"
#include "OpenXRExpansionFunctionLibrary.h"
#include "AnimNode_ApplyOpenXRHandPose.h"
#include "AnimationRuntime.h"
#include "DrawDebugHelpers.h"
#include "OpenXRHandPoseComponent.h"
#include "Runtime/Engine/Public/Animation/AnimInstanceProxy.h"
#include "BoneControllers/AnimNode_SkeletalControlBase.h"
FAnimNode_ApplyOpenXRHandPose::FAnimNode_ApplyOpenXRHandPose()
: FAnimNode_SkeletalControlBase()
{
WorldIsGame = false;
Alpha = 1.f;
SkeletonType = EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Right;
bIsOpenInputAnimationInstance = false;
bSkipRootBone = false;
bOnlyApplyWristTransform = false;
//WristAdjustment = FQuat::Identity;
}
void FAnimNode_ApplyOpenXRHandPose::OnInitializeAnimInstance(const FAnimInstanceProxy* InProxy, const UAnimInstance* InAnimInstance)
{
Super::OnInitializeAnimInstance(InProxy, InAnimInstance);
if (const UOpenXRAnimInstance * OpenXRAnimInstance = Cast<UOpenXRAnimInstance>(InAnimInstance))
{
bIsOpenInputAnimationInstance = true;
if (OpenXRAnimInstance->AnimInstanceProxy.HandSkeletalActionData.Num())
{
for (int i = 0; i < OpenXRAnimInstance->AnimInstanceProxy.HandSkeletalActionData.Num(); ++i)
{
EVRSkeletalHandIndex TargetHand = OpenXRAnimInstance->AnimInstanceProxy.HandSkeletalActionData[i].TargetHand;
if (OpenXRAnimInstance->AnimInstanceProxy.HandSkeletalActionData[i].bMirrorLeftRight)
{
TargetHand = (TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left;
}
if (TargetHand == MappedBonePairs.TargetHand)
{
bIsMirroringHand = OpenXRAnimInstance->AnimInstanceProxy.HandSkeletalActionData[i].bMirrorLeftRight;
break;
}
}
}
}
}
void FAnimNode_ApplyOpenXRHandPose::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
Super::Initialize_AnyThread(Context);
}
void FAnimNode_ApplyOpenXRHandPose::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
Super::CacheBones_AnyThread(Context);
}
void FAnimNode_ApplyOpenXRHandPose::InitializeBoneReferences(const FBoneContainer& RequiredBones)
{
UObject* OwningAsset = RequiredBones.GetAsset();
if (!OwningAsset)
return;
USkeleton* AssetSkeleton = RequiredBones.GetSkeletonAsset();
if (!AssetSkeleton)
return;
if (!MappedBonePairs.bInitialized || OwningAsset->GetFName() != MappedBonePairs.LastInitializedName || SkeletonType != MappedBonePairs.LastInitializedSkeleton)
{
// Trigger a full re-build if our asset changed
if (MappedBonePairs.bInitialized && (OwningAsset->GetFName() != MappedBonePairs.LastInitializedName || SkeletonType != MappedBonePairs.LastInitializedSkeleton))
{
MappedBonePairs.ClearMapping();
}
MappedBonePairs.LastInitializedName = OwningAsset->GetFName();
MappedBonePairs.LastInitializedSkeleton = SkeletonType;
MappedBonePairs.bInitialized = false;
if (AssetSkeleton)
{
// If our bone pairs are empty, then setup our sane defaults
if (!MappedBonePairs.BonePairs.Num())
{
MappedBonePairs.ConstructDefaultMappings(SkeletonType, bSkipRootBone);
}
// Construct a reverse map of our joints
MappedBonePairs.ConstructReverseMapping();
TArray<FTransform> RefBones = AssetSkeleton->GetReferenceSkeleton().GetRefBonePose();
TArray<FMeshBoneInfo> RefBonesInfo = AssetSkeleton->GetReferenceSkeleton().GetRefBoneInfo();
for (FBPOpenXRSkeletalPair& BonePair : MappedBonePairs.BonePairs)
{
// Fill in the bone name for the reference
BonePair.ReferenceToConstruct.BoneName = BonePair.BoneToTarget;
// Init the reference
BonePair.ReferenceToConstruct.Initialize(AssetSkeleton);
BonePair.ReferenceToConstruct.CachedCompactPoseIndex = BonePair.ReferenceToConstruct.GetCompactPoseIndex(RequiredBones);
if ((BonePair.ReferenceToConstruct.CachedCompactPoseIndex != INDEX_NONE))
{
// Get our parent bones index
BonePair.ParentReference = RequiredBones.GetParentBoneIndex(BonePair.ReferenceToConstruct.CachedCompactPoseIndex);
}
}
MappedBonePairs.bInitialized = true;
if (SkeletonType == EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Left || SkeletonType == EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Right)
{
// We hard code this for now because I don't like their wrist being a different transform
MappedBonePairs.AdjustmentQuat = FRotator(0.f, 90.f, 180.f).Quaternion(); // Current one is incorrect without wrist
// Maybe do it in relative space?
}
else
{
CalculateSkeletalAdjustment(AssetSkeleton);
}
}
}
}
void FAnimNode_ApplyOpenXRHandPose::CalculateSkeletalAdjustment(USkeleton* AssetSkeleton)
{
TArray<FTransform> RefBones = AssetSkeleton->GetReferenceSkeleton().GetRefBonePose();
TArray<FMeshBoneInfo> RefBonesInfo = AssetSkeleton->GetReferenceSkeleton().GetRefBoneInfo();
if (!MappedBonePairs.bInitialized || MappedBonePairs.BonePairs.Num() < 4 || !RefBones.Num())
{
UE_LOG(LogTemp, Error, TEXT("Empty or incorrect mapping or skeleton data when calculating skeletal adjustment!"));
return;
}
FBPOpenXRSkeletalPair KnuckleIndexPair = MappedBonePairs.BonePairs[MappedBonePairs.ReverseBonePairMap[(int8)EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT]];
FBPOpenXRSkeletalPair KnuckleMiddlePair = MappedBonePairs.BonePairs[MappedBonePairs.ReverseBonePairMap[(int8)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT]];
FBPOpenXRSkeletalPair KnuckleRingPair = MappedBonePairs.BonePairs[MappedBonePairs.ReverseBonePairMap[(int8)EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT]];
FBPOpenXRSkeletalPair KnucklePinkyPair = MappedBonePairs.BonePairs[MappedBonePairs.ReverseBonePairMap[(int8)EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT]];
FBPOpenXRSkeletalPair WristPair = MappedBonePairs.BonePairs[MappedBonePairs.ReverseBonePairMap[(int8)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT]];
FVector KnuckleAverage = GetRefBoneInCS(RefBones, RefBonesInfo, KnuckleIndexPair.ReferenceToConstruct.BoneIndex).GetTranslation();
KnuckleAverage += GetRefBoneInCS(RefBones, RefBonesInfo, KnuckleMiddlePair.ReferenceToConstruct.BoneIndex).GetTranslation();
KnuckleAverage += GetRefBoneInCS(RefBones, RefBonesInfo, KnuckleRingPair.ReferenceToConstruct.BoneIndex).GetTranslation();
KnuckleAverage += GetRefBoneInCS(RefBones, RefBonesInfo, KnucklePinkyPair.ReferenceToConstruct.BoneIndex).GetTranslation();
// Get our average across the knuckles
KnuckleAverage /= 4.f;
// Obtain the UE4 wrist Side & Forward directions from first animation frame and place in cache
FTransform WristTransform_UE = GetRefBoneInCS(RefBones, RefBonesInfo, WristPair.ReferenceToConstruct.BoneIndex);
FVector ToKnuckleAverage_UE = KnuckleAverage - WristTransform_UE.GetTranslation();
ToKnuckleAverage_UE.Normalize();
WristForwardLS_UE = WristTransform_UE.GetRotation().UnrotateVector(ToKnuckleAverage_UE);
SetVectorToMaxElement(WristForwardLS_UE);
WristSideDirectionLS = FVector::CrossProduct(WristForwardLS_UE, FVector::RightVector);
SetVectorToMaxElement(WristSideDirectionLS);
CalculateOpenXRAdjustment();
}
void FAnimNode_ApplyOpenXRHandPose::CalculateOpenXRAdjustment()
{
// Base implementation is like valves
// Forward direction
static FVector OpenXRForwardDirection = FVector(1.0f, 0.f, 0.f);
// Side direction
// Do I need to flip this for left hand?
bool bUseLeftHandOffsets = false;
if ((!bIsMirroringHand && MappedBonePairs.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) ||
(bIsMirroringHand && MappedBonePairs.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Right))
{
bUseLeftHandOffsets = true;
}
//static FVector OpenXRSideDirection = FVector(0.f, 1.f, 0.f);
FVector OpenXRSideDirection = bUseLeftHandOffsets ? FVector(0.f, -1.f, 0.f) : FVector(0.f, 1.f, 0.f);
// Align forward vectors, openXR once in engine is X+ forward
FQuat AlignmentRot = FQuat::FindBetweenNormals(WristForwardLS_UE, OpenXRForwardDirection);
// Rotate about the aligned forward direction to make the side directions align
FVector WristSideDirectionMS_UE = AlignmentRot * WristSideDirectionLS;
// Rotate around, should the Side direction flip for openXR if its the left hand?
FQuat TwistRotation = CalcRotationAboutAxis(WristSideDirectionMS_UE, OpenXRSideDirection, OpenXRForwardDirection);
FRotator Difference = (TwistRotation * AlignmentRot).Rotator();
MappedBonePairs.AdjustmentQuat = (TwistRotation * AlignmentRot).GetNormalized();
}
void FAnimNode_ApplyOpenXRHandPose::ConvertHandTransformsSpace(TArray<FTransform>& OutTransforms, const TArray<FTransform>& WorldTransforms, FTransform AddTrans, bool bMirrorLeftRight, bool bMergeMissingUE4Bones)
{
// Fail if the count is too low
if (WorldTransforms.Num() < EHandKeypointCount)
return;
if (OutTransforms.Num() < WorldTransforms.Num())
{
OutTransforms.Empty(WorldTransforms.Num());
OutTransforms.AddUninitialized(WorldTransforms.Num());
}
TArray<FTransform> TempWorldTransforms = WorldTransforms;
// Ensure add trans is normalized
AddTrans.NormalizeRotation();
// Bone/Parent map
int32 BoneParents[26] =
{
// Manually build the parent hierarchy starting at the wrist which has no parent (-1)
1, // Palm -> Wrist
-1, // Wrist -> None
1, // ThumbMetacarpal -> Wrist
2, // ThumbProximal -> ThumbMetacarpal
3, // ThumbDistal -> ThumbProximal
4, // ThumbTip -> ThumbDistal
1, // IndexMetacarpal -> Wrist
6, // IndexProximal -> IndexMetacarpal
7, // IndexIntermediate -> IndexProximal
8, // IndexDistal -> IndexIntermediate
9, // IndexTip -> IndexDistal
1, // MiddleMetacarpal -> Wrist
11, // MiddleProximal -> MiddleMetacarpal
12, // MiddleIntermediate -> MiddleProximal
13, // MiddleDistal -> MiddleIntermediate
14, // MiddleTip -> MiddleDistal
1, // RingMetacarpal -> Wrist
16, // RingProximal -> RingMetacarpal
17, // RingIntermediate -> RingProximal
18, // RingDistal -> RingIntermediate
19, // RingTip -> RingDistal
1, // LittleMetacarpal -> Wrist
21, // LittleProximal -> LittleMetacarpal
22, // LittleIntermediate -> LittleProximal
23, // LittleDistal -> LittleIntermediate
24, // LittleTip -> LittleDistal
};
bool bUseAutoCalculatedRetarget = AddTrans.Equals(FTransform::Identity);
// Convert transforms to parent space
// The hand tracking transforms are in world space.
for (int32 Index = 0; Index < EHandKeypointCount; ++Index)
{
if (TempWorldTransforms[Index].ContainsNaN() || TempWorldTransforms[Index].Equals(FTransform::Identity))
{
OutTransforms[Index] = FTransform::Identity;
//continue;
}
// Ensure normalization
TempWorldTransforms[Index].NormalizeRotation();
if (bMirrorLeftRight)
{
TempWorldTransforms[Index].Mirror(EAxis::Y, EAxis::Y);
}
if (bUseAutoCalculatedRetarget)
{
TempWorldTransforms[Index].ConcatenateRotation(MappedBonePairs.AdjustmentQuat);
//WorldTransforms[Index].ConcatenateRotation(MappedBonePairs.BonePairs[0].RetargetRot);
}
else
{
TempWorldTransforms[Index].ConcatenateRotation(AddTrans.GetRotation());
}
}
// Make this into a single loop, their structure always has children after parent
for (int32 Index = 0; Index < EHandKeypointCount; ++Index)
{
FTransform& BoneTransform = TempWorldTransforms[Index];
//BoneTransform.NormalizeRotation();
int32 ParentIndex = BoneParents[Index];
int32 ParentParent = -1;
// Thumb keeps the metacarpal intact, we don't skip it
if (bMergeMissingUE4Bones)
{
if (Index != (int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT && ParentIndex > 0)
{
ParentParent = BoneParents[ParentIndex];
}
}
if (ParentIndex < 0)
{
// We are at the root, so use it.
OutTransforms[Index] = BoneTransform;
}
else
{
FTransform ParentTransform = FTransform::Identity;
// Merging missing metacarpal bone into the transform
if (bMergeMissingUE4Bones && ParentParent == 1) // Wrist
{
ParentTransform = TempWorldTransforms[ParentParent];
}
else
{
ParentTransform = TempWorldTransforms[ParentIndex];
}
//ParentTransform.NormalizeRotation();
OutTransforms[Index] = BoneTransform.GetRelativeTransform(ParentTransform);
}
}
}
void FAnimNode_ApplyOpenXRHandPose::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
{
if (!MappedBonePairs.bInitialized)
return;
const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();
UObject* OwningAsset = BoneContainer.GetAsset();
if (!OwningAsset)
return;
// Trigger a full re-build if our asset or target skeleton changed, do it up here before finding the correct hand
if ((OwningAsset->GetFName() != MappedBonePairs.LastInitializedName || SkeletonType != MappedBonePairs.LastInitializedSkeleton))
{
InitializeBoneReferences(BoneContainer);
}
/*const */FBPOpenXRActionSkeletalData *StoredActionInfoPtr = nullptr;
if (bIsOpenInputAnimationInstance)
{
/*const*/ FOpenXRAnimInstanceProxy* OpenXRAnimInstance = (FOpenXRAnimInstanceProxy*)Output.AnimInstanceProxy;
if (OpenXRAnimInstance->HandSkeletalActionData.Num())
{
for (int i = 0; i <OpenXRAnimInstance->HandSkeletalActionData.Num(); ++i)
{
EVRSkeletalHandIndex TargetHand = OpenXRAnimInstance->HandSkeletalActionData[i].TargetHand;
if (OpenXRAnimInstance->HandSkeletalActionData[i].bMirrorLeftRight)
{
TargetHand = (TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left;
}
if (TargetHand == MappedBonePairs.TargetHand)
{
StoredActionInfoPtr = &OpenXRAnimInstance->HandSkeletalActionData[i];
break;
}
}
}
}
// If we have an empty hand pose but have a passed in custom one then use that
if (StoredActionInfoPtr == nullptr || !StoredActionInfoPtr->SkeletalTransforms.Num())
{
StoredActionInfoPtr = &OptionalStoredActionInfo;
}
if (!StoredActionInfoPtr->bHasValidData)
{
return;
}
//MappedBonePairs.AdjustmentQuat = WristAdjustment;
// Currently not blending correctly
const float BlendWeight = FMath::Clamp<float>(ActualAlpha, 0.f, 1.f);
uint8 BoneTransIndex = 0;
uint8 NumBones = StoredActionInfoPtr ? StoredActionInfoPtr->SkeletalTransforms.Num() : 0;
if (NumBones < 1)
{
// Early out, we don't have a valid data to work with
return;
}
FTransform trans = FTransform::Identity;
OutBoneTransforms.Reserve(MappedBonePairs.BonePairs.Num());
TArray<FBoneTransform> TransBones;
FTransform AdditionTransform = StoredActionInfoPtr->AdditionTransform;
FTransform TempTrans = FTransform::Identity;
FTransform ParentTrans = FTransform::Identity;
FTransform * ParentTransPtr = nullptr;
//AdditionTransform.SetRotation(MappedBonePairs.AdjustmentQuat);
TArray<FTransform> HandTransforms;
ConvertHandTransformsSpace(HandTransforms, StoredActionInfoPtr->SkeletalTransforms, AdditionTransform, StoredActionInfoPtr->bMirrorLeftRight, MappedBonePairs.bMergeMissingBonesUE4);
for (const FBPOpenXRSkeletalPair& BonePair : MappedBonePairs.BonePairs)
{
BoneTransIndex = (int8)BonePair.OpenXRBone;
ParentTrans = FTransform::Identity;
if (bSkipRootBone && BonePair.OpenXRBone == EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT)
continue;
if (BoneTransIndex >= NumBones || BonePair.ReferenceToConstruct.CachedCompactPoseIndex == INDEX_NONE)
continue;
if (!BonePair.ReferenceToConstruct.IsValidToEvaluate(BoneContainer))
{
continue;
}
trans = Output.Pose.GetComponentSpaceTransform(BonePair.ReferenceToConstruct.CachedCompactPoseIndex);
if (BonePair.ParentReference != INDEX_NONE)
{
ParentTrans = Output.Pose.GetComponentSpaceTransform(BonePair.ParentReference);
ParentTrans.SetScale3D(FVector(1.f));
}
EXRHandJointType CurrentBone = (EXRHandJointType)BoneTransIndex;
TempTrans = (HandTransforms[BoneTransIndex]);
//TempTrans.ConcatenateRotation(BonePair.RetargetRot);
/*if (StoredActionInfoPtr->bMirrorHand)
{
FMatrix M = TempTrans.ToMatrixWithScale();
M.Mirror(EAxis::Z, EAxis::X);
M.Mirror(EAxis::X, EAxis::Z);
TempTrans.SetFromMatrix(M);
}*/
TempTrans = TempTrans * ParentTrans;
if (StoredActionInfoPtr->bAllowDeformingMesh || bOnlyApplyWristTransform)
trans.SetTranslation(TempTrans.GetTranslation());
trans.SetRotation(TempTrans.GetRotation());
TransBones.Add(FBoneTransform(BonePair.ReferenceToConstruct.CachedCompactPoseIndex, trans));
// Need to do it per bone so future bones are correct
// Only if in parent space though, can do it all at the end in component space
if (TransBones.Num())
{
Output.Pose.LocalBlendCSBoneTransforms(TransBones, BlendWeight);
TransBones.Reset();
}
if (bOnlyApplyWristTransform && CurrentBone == EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT)
{
break; // Early out of the loop, we only wanted to apply the wrist
}
}
}
bool FAnimNode_ApplyOpenXRHandPose::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones)
{
return(/*MappedBonePairs.bInitialized && */MappedBonePairs.BonePairs.Num() > 0);
}

View file

@ -0,0 +1,595 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "OpenXRExpansionFunctionLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(OpenXRExpansionFunctionLibrary)
//#include "EngineMinimal.h"
#include "Engine/Engine.h"
#include <openxr/openxr.h>
#include "CoreMinimal.h"
#include "IXRTrackingSystem.h"
//General Log
DEFINE_LOG_CATEGORY(OpenXRExpansionFunctionLibraryLog);
UOpenXRExpansionFunctionLibrary::UOpenXRExpansionFunctionLibrary(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
//=============================================================================
UOpenXRExpansionFunctionLibrary::~UOpenXRExpansionFunctionLibrary()
{
}
void UOpenXRExpansionFunctionLibrary::GetXRMotionControllerType(FString& TrackingSystemName, EBPOpenXRControllerDeviceType& DeviceType, EBPXRResultSwitch& Result)
{
#if defined(OPENXR_SUPPORTED)
DeviceType = EBPOpenXRControllerDeviceType::DT_UnknownController;
Result = EBPXRResultSwitch::OnFailed;
if (FOpenXRHMD* pOpenXRHMD = GetOpenXRHMD())
{
XrInstance XRInstance = pOpenXRHMD->GetInstance();
XrSystemId XRSysID = pOpenXRHMD->GetSystem();
if (XRSysID && XRInstance)
{
XrSystemProperties systemProperties{ XR_TYPE_SYSTEM_PROPERTIES };
systemProperties.next = nullptr;
if (xrGetSystemProperties(XRInstance, XRSysID, &systemProperties) == XR_SUCCESS)
{
XrSession XRSesh = pOpenXRHMD->GetSession();
if (XRSesh)
{
XrPath myPath;
XrResult PathResult = xrStringToPath(XRInstance, "/user/hand/left", &myPath);
XrInteractionProfileState interactionProfile{ XR_TYPE_INTERACTION_PROFILE_STATE };
interactionProfile.next = nullptr;
XrResult QueryResult = xrGetCurrentInteractionProfile(XRSesh, myPath, &interactionProfile);
if (QueryResult == XR_SUCCESS)
{
char myPathy[XR_MAX_SYSTEM_NAME_SIZE];
uint32_t outputsize;
xrPathToString(XRInstance, interactionProfile.interactionProfile, XR_MAX_SYSTEM_NAME_SIZE, &outputsize, myPathy);
if (interactionProfile.interactionProfile == XR_NULL_PATH || outputsize < 1)
return;
FString InteractionName(ANSI_TO_TCHAR(myPathy));
if (InteractionName.Len() < 1)
return;
/*
* Interaction profile paths [6.4]
An interaction profile identifies a collection of buttons and
other input sources, and is of the form:
/interaction_profiles/<vendor_name>/<type_name>
Paths supported in the core 1.0 release
/interaction_profiles/khr/simple_control
/interaction_profiles/khr/simple_controller
/interaction_profiles/google/daydream_controller
/interaction_profiles/htc/vive_controller
/interaction_profiles/htc/vive_pro
/interaction_profiles/microsoft/motion_controller
/interaction_profiles/microsoft/xbox_controller
/interaction_profiles/oculus/go_controller
/interaction_profiles/oculus/touch_controller
/interaction_profiles/valve/index_controller
*/
// Not working currently?
/*XrInputSourceLocalizedNameGetInfo InputSourceInfo{ XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO };
InputSourceInfo.next = nullptr;
InputSourceInfo.whichComponents = XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT;
InputSourceInfo.sourcePath = interactionProfile.interactionProfile;
char buffer[XR_MAX_SYSTEM_NAME_SIZE];
uint32_t usedBufferCount = 0;
if (xrGetInputSourceLocalizedName(XRSesh, &InputSourceInfo, XR_MAX_SYSTEM_NAME_SIZE, &usedBufferCount, (char*)&buffer) == XR_SUCCESS)
{
int g = 0;
}*/
if (InteractionName.Find("touch_controller", ESearchCase::IgnoreCase) != INDEX_NONE)
{
DeviceType = EBPOpenXRControllerDeviceType::DT_OculusTouchController;
}
else if (InteractionName.Contains("index_controller", ESearchCase::IgnoreCase))
{
DeviceType = EBPOpenXRControllerDeviceType::DT_ValveIndexController;
}
else if (InteractionName.Find("vive_controller", ESearchCase::IgnoreCase) != INDEX_NONE)
{
DeviceType = EBPOpenXRControllerDeviceType::DT_ViveController;
}
else if (InteractionName.Find("vive_pro", ESearchCase::IgnoreCase) != INDEX_NONE)
{
DeviceType = EBPOpenXRControllerDeviceType::DT_ViveProController;
}
else if (InteractionName.Find("simple_controller", ESearchCase::IgnoreCase) != INDEX_NONE)
{
DeviceType = EBPOpenXRControllerDeviceType::DT_SimpleController;
}
else if (InteractionName.Find("daydream_controller", ESearchCase::IgnoreCase) != INDEX_NONE)
{
DeviceType = EBPOpenXRControllerDeviceType::DT_DaydreamController;
}
else if (InteractionName.Find("motion_controller", ESearchCase::IgnoreCase) != INDEX_NONE)
{
DeviceType = EBPOpenXRControllerDeviceType::DT_MicrosoftMotionController;
}
else if (InteractionName.Find("xbox_controller", ESearchCase::IgnoreCase) != INDEX_NONE)
{
DeviceType = EBPOpenXRControllerDeviceType::DT_MicrosoftXboxController;
}
else if (InteractionName.Find("go_controller", ESearchCase::IgnoreCase) != INDEX_NONE)
{
DeviceType = EBPOpenXRControllerDeviceType::DT_OculusGoController;
}
else if (InteractionName.Find("neo3_controller", ESearchCase::IgnoreCase) != INDEX_NONE)
{
DeviceType = EBPOpenXRControllerDeviceType::DT_PicoNeo3Controller;
}
else if (InteractionName.Find("mixed_reality_controller", ESearchCase::IgnoreCase) != INDEX_NONE)
{
DeviceType = EBPOpenXRControllerDeviceType::DT_WMRController;
}
else
{
UE_LOG(OpenXRExpansionFunctionLibraryLog, Warning, TEXT("UNKNOWN OpenXR Interaction profile detected!!!: %s"), *InteractionName);
DeviceType = EBPOpenXRControllerDeviceType::DT_UnknownController;
}
Result = EBPXRResultSwitch::OnSucceeded;
}
TrackingSystemName = FString(ANSI_TO_TCHAR(systemProperties.systemName));// , XR_MAX_SYSTEM_NAME_SIZE);
//VendorID = systemProperties.vendorId;
return;
}
}
}
}
#endif
TrackingSystemName.Empty();
return;
}
bool UOpenXRExpansionFunctionLibrary::GetOpenXRHandPose(FBPOpenXRActionSkeletalData& HandPoseContainer, UOpenXRHandPoseComponent* HandPoseComponent, bool bGetMockUpPose)
{
FXRHandTrackingState HandTrackingData;
FXRMotionControllerState MotionControllerData;
if (bGetMockUpPose)
{
// #TODO 5.7 TEST THIS
UHeadMountedDisplayFunctionLibrary::GetMotionControllerState((UObject*)HandPoseComponent, EXRSpaceType::UnrealWorldSpace, HandPoseContainer.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left ? EControllerHand::Left : EControllerHand::Right, HandPoseContainer.PoseType, MotionControllerData);
GetMockUpControllerData(HandTrackingData,MotionControllerData, HandPoseContainer);
return true;
}
UHeadMountedDisplayFunctionLibrary::GetHandTrackingState((UObject*)HandPoseComponent, EXRSpaceType::UnrealWorldSpace, HandPoseContainer.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left ? EControllerHand::Left : EControllerHand::Right, HandTrackingData);
if (HandTrackingData.bValid)
{
HandPoseContainer.SkeletalTransforms.Empty(HandTrackingData.HandKeyLocations.Num());
FTransform ParentTrans = FTransform::Identity;
// #TODO: 5.7 Preview!!
// Might need to restore all of this, have to test with vive
/*if (MotionControllerData.DeviceVisualType == EXRVisualType::Controller)
{
ParentTrans = FTransform(MotionControllerData.GripRotation, MotionControllerData.GripPosition, FVector(1.f));
}
else // EXRVisualType::Hand visual type*/
{
ParentTrans = FTransform(HandTrackingData.HandKeyRotations[(uint8)EHandKeypoint::Palm], HandTrackingData.HandKeyLocations[(uint8)EHandKeypoint::Palm], FVector(1.f));
}
for (int i = 0; i < HandTrackingData.HandKeyLocations.Num(); ++i)
{
// Convert to component space, we convert then to parent space later when applying it
HandPoseContainer.SkeletalTransforms.Add(FTransform(HandTrackingData.HandKeyRotations[i].GetNormalized(), HandTrackingData.HandKeyLocations[i], FVector(1.f)).GetRelativeTransform(ParentTrans));
}
//if (bGetCurlValues)
{
GetFingerCurlValues(HandPoseContainer.SkeletalTransforms, HandPoseContainer.FingerCurls);
}
HandPoseContainer.bHasValidData = (HandPoseContainer.SkeletalTransforms.Num() == EHandKeypointCount);
return true;
}
HandPoseContainer.bHasValidData = false;
return false;
}
void UOpenXRExpansionFunctionLibrary::GetFingerCurlValues(TArray<FTransform>& TransformArray, TArray<float>& CurlArray)
{
// Fail if the count is too low
if (TransformArray.Num() < EHandKeypointCount)
return;
if (CurlArray.Num() < 5)
{
CurlArray.AddZeroed(5);
}
CurlArray[0] = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::ThumbMetacarpal);
CurlArray[1] = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::IndexProximal);
CurlArray[2] = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::MiddleProximal);
CurlArray[3] = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::RingProximal);
CurlArray[4] = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::LittleProximal);
}
bool UOpenXRExpansionFunctionLibrary::GetOpenXRFingerCurlValuesForHand(
UObject* WorldContextObject,
EControllerHand TargetHand,
float& ThumbCurl,
float& IndexCurl,
float& MiddleCurl,
float& RingCurl,
float& PinkyCurl)
{
FXRHandTrackingState HandTrackingData;
UHeadMountedDisplayFunctionLibrary::GetHandTrackingState(WorldContextObject, EXRSpaceType::UnrealWorldSpace, TargetHand, HandTrackingData);
// Fail if the count is too low
if (HandTrackingData.HandKeyLocations.Num() < EHandKeypointCount)
return false;
FTransform ParentTrans = FTransform::Identity;
// #TODO: 5.7
// TEST THIS and restore if we need too
/*if (MotionControllerData.DeviceVisualType == EXRVisualType::Controller)
{
ParentTrans = FTransform(MotionControllerData.GripRotation, MotionControllerData.GripPosition, FVector(1.f));
}
else // EXRVisualType::Hand visual type*/
{
ParentTrans = FTransform(HandTrackingData.HandKeyRotations[(uint8)EHandKeypoint::Palm], HandTrackingData.HandKeyLocations[(uint8)EHandKeypoint::Palm], FVector(1.f));
}
TArray<FTransform> TransformArray;
TransformArray.AddUninitialized(HandTrackingData.HandKeyLocations.Num());
for (int i = 0; i < HandTrackingData.HandKeyLocations.Num(); ++i)
{
// Convert to component space, we convert then to parent space later when applying it
TransformArray[i] = FTransform(HandTrackingData.HandKeyRotations[i].GetNormalized(), HandTrackingData.HandKeyLocations[i], FVector(1.f)).GetRelativeTransform(ParentTrans);
}
ThumbCurl = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::ThumbMetacarpal);
IndexCurl = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::IndexProximal);
MiddleCurl = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::MiddleProximal);
RingCurl = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::RingProximal);
PinkyCurl = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::LittleProximal);
return true;
}
float UOpenXRExpansionFunctionLibrary::GetCurlValueForBoneRoot(TArray<FTransform>& TransformArray, EHandKeypoint RootBone)
{
float Angle1 = 0.0f;
float Angle2 = 0.0f;
float Angle1Curl = 0.0f;
float Angle2Curl = 0.0f;
if (RootBone == EHandKeypoint::ThumbMetacarpal)
{
FVector Prox = TransformArray[(uint8)RootBone].GetRotation().GetForwardVector();
FVector Inter = TransformArray[(uint8)RootBone + 1].GetRotation().GetForwardVector();
FVector Distal = TransformArray[(uint8)RootBone + 2].GetRotation().GetForwardVector();
Prox = FVector::VectorPlaneProject(Prox, FVector::UpVector);
Inter = FVector::VectorPlaneProject(Inter, FVector::UpVector);
Distal = FVector::VectorPlaneProject(Distal, FVector::UpVector);
Angle1 = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(Inter, Distal)));
Angle2 = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(Prox, Inter)));
Angle1Curl = (Angle1 - 10.0f) / 64.0f;
Angle2Curl = (Angle2 - 20.0f) / 42.0f;
}
else
{
FVector Prox = TransformArray[(uint8)RootBone].GetRotation().GetForwardVector();
FVector Inter = TransformArray[(uint8)RootBone + 1].GetRotation().GetForwardVector();
FVector Distal = TransformArray[(uint8)RootBone + 2].GetRotation().GetForwardVector();
// We don't use the Y (splay) value, only X and Z plane
Prox = FVector::VectorPlaneProject(Prox, FVector::RightVector);
Inter = FVector::VectorPlaneProject(Inter, FVector::RightVector);
Distal = FVector::VectorPlaneProject(Distal, FVector::RightVector);
Angle1 = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(Inter, Distal)));
Angle2 = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(Prox, Inter)));
Angle1Curl = (Angle1 - 10.0f) / 60.0f;
Angle2Curl = (Angle2 - 10.0f) / 100.0f;
}
// Can lower number of variables by doing these
float FinalAngleAvg = FMath::Clamp((Angle1Curl + Angle2Curl) / 2.0f, 0.0f, 1.0f);
//GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("Finger Curl %f"), IndexCurl));
return FinalAngleAvg;
}
void UOpenXRExpansionFunctionLibrary::ConvertHandTransformsSpaceAndBack(TArray<FTransform>& OutTransforms, const TArray<FTransform>& WorldTransforms)
{
// Fail if the count is too low
if (WorldTransforms.Num() < EHandKeypointCount)
return;
if (OutTransforms.Num() < WorldTransforms.Num())
{
OutTransforms.Empty(WorldTransforms.Num());
OutTransforms.AddUninitialized(WorldTransforms.Num());
}
// Bone/Parent map
int32 BoneParents[26] =
{
// Manually build the parent hierarchy starting at the wrist which has no parent (-1)
1, // Palm -> Wrist
-1, // Wrist -> None
1, // ThumbMetacarpal -> Wrist
2, // ThumbProximal -> ThumbMetacarpal
3, // ThumbDistal -> ThumbProximal
4, // ThumbTip -> ThumbDistal
1, // IndexMetacarpal -> Wrist
6, // IndexProximal -> IndexMetacarpal
7, // IndexIntermediate -> IndexProximal
8, // IndexDistal -> IndexIntermediate
9, // IndexTip -> IndexDistal
1, // MiddleMetacarpal -> Wrist
11, // MiddleProximal -> MiddleMetacarpal
12, // MiddleIntermediate -> MiddleProximal
13, // MiddleDistal -> MiddleIntermediate
14, // MiddleTip -> MiddleDistal
1, // RingMetacarpal -> Wrist
16, // RingProximal -> RingMetacarpal
17, // RingIntermediate -> RingProximal
18, // RingDistal -> RingIntermediate
19, // RingTip -> RingDistal
1, // LittleMetacarpal -> Wrist
21, // LittleProximal -> LittleMetacarpal
22, // LittleIntermediate -> LittleProximal
23, // LittleDistal -> LittleIntermediate
24, // LittleTip -> LittleDistal
};
// Convert transforms to parent space
// The hand tracking transforms are in world space.
for (int32 Index = 0; Index < EHandKeypointCount; ++Index)
{
FTransform BoneTransform = WorldTransforms[Index];
BoneTransform.NormalizeRotation();
int32 ParentIndex = BoneParents[Index];
int32 ParentParent = -1;
if (ParentIndex > 0)
{
ParentParent = BoneParents[ParentIndex];
}
if (ParentIndex < 0)
{
// We are at the root, so use it.
OutTransforms[Index] = BoneTransform;
}
else
{
FTransform ParentTransform = FTransform::Identity;
// Merging missing metacarpal bone into the transform
if (ParentParent == 1) // Wrist
{
ParentTransform = WorldTransforms[ParentParent];
}
else
{
ParentTransform = WorldTransforms[ParentIndex];
}
ParentTransform.NormalizeRotation();
OutTransforms[Index] = BoneTransform.GetRelativeTransform(ParentTransform);
}
}
// Check on the easy component space conversion first
{
for (int32 Index = 0; Index < EHandKeypointCount; ++Index)
{
const FTransform& BoneTransform = WorldTransforms[Index];
int32 ParentIndex = BoneParents[Index];
int32 ParentParent = -1;
if (ParentIndex > 0)
{
ParentParent = BoneParents[ParentIndex];
}
if (ParentIndex > 0)
{
if (ParentParent == 1)
{
OutTransforms[Index] = OutTransforms[Index] * OutTransforms[ParentParent];
}
else
{
OutTransforms[Index] = OutTransforms[Index] * OutTransforms[ParentIndex];
}
}
}
}
}
void UOpenXRExpansionFunctionLibrary::GetMockUpControllerData(FXRHandTrackingState& HandTrackingData, FXRMotionControllerState& MotionControllerData, FBPOpenXRActionSkeletalData& SkeletalMappingData, bool bOpenHand)
{
TArray<FQuat> HandRotationsClosed = {
// Closed palm
FQuat(-6.9388939039072284e-18f,-2.7755575615628914e-17f,-5.5511151231257827e-17f,1.0000000181623150),
FQuat(0.0010158333104005046f,-0.031842494413126823f,0.0082646248419453450f,-0.99945823120983279),
FQuat(0.49284713488980170f,-0.22607130273287540f,-0.44054329019960731f,0.71548243409346490),
FQuat(-0.60572821120045273f,-0.017497510794223320f,0.10943807503774633f,-0.78791529705201735),
FQuat(-0.61281359708005512f,-0.23245447006924613f,-0.058766632856478873f,-0.75297472319193537),
FQuat(-0.61281359708005512f,-0.23245447006924613f,-0.058766632856478873f,-0.75297472319193537),
FQuat(-0.11514346379358367f,-0.029229397612833233f,-0.076351329575539195f,0.98997885626789228),
FQuat(0.079333339113826437f,-0.72590009974051883f,0.050346047334316274f,-0.68135202233808378),
FQuat(0.0032539550806410245f,-0.97123336736010268f,0.098921247251489333f,0.21658665949113542),
FQuat(-0.064585069112672477f,-0.57963374972897053f,0.075445998290538954f,0.80880246209406348),
FQuat(0.064585069112672477f,0.57963374972897053f,-0.075445998290538954f,-0.80880246209406348),
FQuat(-7.7702472130181111e-08f,9.8656527815210726e-08f,1.3838491007001075e-06f,1.0000000181613493),
FQuat(0.085300231549708214f,-0.74048187833139134f,0.058532016219761618f,-0.66406663653752407),
FQuat(0.011595964719175678f,-0.98786834549923641f,0.099110835214894707f,0.11899052159928070),
FQuat(-0.063530326287074640f,-0.56012988021281451f,0.076145543493668810f,0.82244775363868539),
FQuat(0.030477923994508188f,0.55662337489051250f,-0.098559802475379960f,-0.82433459003921772),
FQuat(-0.025910339872061101f,0.052311401725670628f,0.042953782692297438f,0.99737013210455761),
FQuat(0.11635892750396379f,-0.74717191584145570f,-0.026909776929005647f,-0.65381238012001353),
FQuat(0.098078041656806447f,-0.98532297068866348f,0.071135591008198620f,0.12024601945419003),
FQuat(0.0028091467060491482f,-0.52817558741956572f,0.11864668261714340f,0.84080060571895254),
FQuat(-0.0028091467060491482f,0.52817558741956572f,-0.11864668261714340f,-0.84080060571895254),
FQuat(-0.11913260527111892f,0.10934177100849747f,0.11664955310670821f,0.97992077106196507),
FQuat(-0.18696185363314571f,0.78123174637415782f,0.043590203318890380f,0.59398834520762267),
FQuat(0.15903913486884827f,-0.97287570092949460f,0.091728860868847406f,0.14073122087670015),
FQuat(0.035141532005084519f,-0.48251853052338572f,0.18910886397987722f,0.85450501128943579),
FQuat(0.035141532005084519f,-0.48251853052338572f,0.18910886397987722f,0.85450501128943579)
};
TArray<FQuat> HandRotationsOpen = {
// Open Hand
FQuat(0.167000905f,-0.670308471f,0.304047525f,-0.656011939f),
FQuat(-0.129862994f,0.736467659f,-0.315623045f,0.584065497f),
FQuat(-0.030090153f,0.121532254f,-0.840178490f,0.527659237f),
FQuat(-0.126470163f,-0.262596279f,0.816956878f,-0.497623593f),
FQuat(-0.102322638f,-0.249194950f,0.821705163f,-0.502227187f),
FQuat(-0.102322638f,-0.249194950f,0.821705163f,-0.502227187f),
FQuat(-0.277370781f,0.686735749f,-0.258646101f,0.620130479f),
FQuat(0.193366051f,-0.808131576f,0.260262072f,-0.491728455f),
FQuat(0.145547777f,-0.854364336f,0.317562312f,-0.384749293f),
FQuat(0.107193023f,-0.879853010f,0.321882188f,-0.332806766f),
FQuat(0.107193023f,-0.879853010f,0.321882188f,-0.332806766f),
FQuat(0.166999936f,-0.670307159f,0.304047883f,-0.656013489f),
FQuat(0.206125781f,-0.815250278f,0.203173012f,-0.501596987f),
FQuat(0.164493740f,-0.890369833f,0.257293820f,-0.337613612f),
FQuat(0.114019498f,-0.937856555f,0.283619940f,-0.164267495f),
FQuat(0.107336335f,-0.925720870f,0.321023613f,-0.168710276f),
FQuat(0.156629071f,-0.719596088f,0.210793152f,-0.642817795f),
FQuat(0.194258988f,-0.858762920f,0.127883837f,-0.456546605f),
FQuat(0.166189745f,-0.930981100f,0.161785051f,-0.281922638f),
FQuat(0.119936436f,-0.970744252f,0.189072192f,-0.086731322f),
FQuat(0.119936436f,-0.970744252f,0.189072192f,-0.086731322f),
FQuat(0.160288095f,-0.812664807f,0.100923792f,-0.551087677f),
FQuat(0.207311243f,-0.870556056f,0.056644741f,-0.442656904f),
FQuat(0.191506147f,-0.944826961f,0.096772499f,-0.247511998f),
FQuat(0.116890728f,-0.981477261f,0.138804480f,-0.061412390f),
FQuat(0.116890728f,-0.981477261f,0.138804480f,-0.061412390f)
};
HandTrackingData.HandKeyRotations = /*SkeletalMappingData.TargetHand != EVRSkeletalHandIndex::EActionHandIndex_Left ? HandRotationsOpen :*/ HandRotationsClosed;
TArray<FVector> HandPositionsClosed = {
// Closed palm - Left
FVector(0.0000000000000000f,0.0000000000000000f,0.0000000000000000f),
FVector(-2.8690212431406792f,0.70708009295073815f,-0.47404338536985718f),
FVector(-1.1322360817697272f,-2.1125772981974671f,-1.2278703475596775f),
FVector(0.92697682070144727f,-5.5601677377459957f,-1.6753327187355360f),
FVector(4.0987554778339428f,-6.0520138168462640f,-2.1960898756852747f),
FVector(5.5854053809842918f,-5.4247506634349065f,-2.6631245417791525f),
FVector(-1.6026417502203387f,-1.4646945203797794f,-0.17057236434820122f),
FVector(5.7352432311721007f,-2.5389617545260998f,0.39061644722637634f),
FVector(5.4801464829170561f,-3.3344912297783416f,-3.8566611419550343f),
FVector(2.9179605371815693f,-3.2311985822561073f,-2.6652727318443148f),
FVector(3.2708935578922342f,-3.0117453368521279f,-1.6311186720587312f),
FVector(-1.1935619377191149f,-1.8034793735494103e-05f,1.4147048846974153e-06f),
FVector(5.9028526610092893f,1.3513666817788206e-05f,-4.3170989212359956e-06f),
FVector(5.4567759872551527f,-0.87968929643487392f,-4.1965100581882382f),
FVector(2.2252652348065158f,-0.87742006725177069f,-3.4067970851427791f),
FVector(2.6916877869696085f,-0.62360084690574125f,-2.2285708116727738f),
FVector(-1.1796822503165614f,1.3653411443775685f,-0.17694011615865479f),
FVector(5.3502831208188670f,1.9121382570769896f,-0.87930289382919313f),
FVector(4.8743654830862742f,1.3526757302541959f,-4.8457258101076217f),
FVector(2.1622015314362244f,0.85068796311660544f,-4.1307752690132205f),
FVector(2.6021369184528194f,1.0596020074600654f,-3.1860412064934174f),
FVector(-1.2905361603753163f,2.6108535683555365f,-0.46423293549223010f),
FVector(4.6820094577722964f,3.8858425146699327f,-1.9880098921962746f),
FVector(4.0115118130532306f,3.1678700881616777f,-4.8092930847360869f),
FVector(2.3757993445967389f,2.6579252395479291f,-4.2645319235961239f),
FVector(2.7329133227289351f,2.8811366857469527f,-3.6179750674182261f)
};
// Open Hand
TArray<FVector> HandPositionsOpen = {
FVector(-1014.001f,-478.278f,212.902f),
FVector(-1013.516f,-476.006f,214.688f),
FVector(-1016.362f,-479.642f,215.119f),
FVector(-1018.145f,-483.254f,214.805f),
FVector(-1019.682f,-485.682f,213.284f),
FVector(-1020.480f,-486.982f,212.581f),
FVector(-1014.360f,-478.927f,215.169f),
FVector(-1014.932f,-484.146f,209.902f),
FVector(-1016.872f,-486.643f,206.852f),
FVector(-1018.771f,-488.058f,205.231f),
FVector(-1019.613f,-488.507f,204.655f),
FVector(-1013.901f,-477.534f,213.831f),
FVector(-1014.494f,-481.954f,208.310f),
FVector(-1016.269f,-484.282f,205.146f),
FVector(-1018.657f,-485.834f,203.427f),
FVector(-1019.846f,-486.231f,203.113f),
FVector(-1013.816f,-476.436f,213.006f),
FVector(-1014.637f,-479.707f,207.344f),
FVector(-1016.703f,-481.540f,204.355f),
FVector(-1018.962f,-482.692f,203.000f),
FVector(-1019.978f,-482.975f,202.870f),
FVector(-1013.845f,-475.325f,212.363f),
FVector(-1015.993f,-477.665f,206.928f),
FVector(-1017.571f,-478.907f,204.670f),
FVector(-1019.033f,-479.652f,203.887f),
FVector(-1019.778f,-479.842f,203.819f)
};
HandTrackingData.HandKeyLocations = /*SkeletalMappingData.TargetHand != EVRSkeletalHandIndex::EActionHandIndex_Left ? HandPositionsOpen : */HandPositionsClosed;
/*if (SkeletalMappingData.TargetHand != EVRSkeletalHandIndex::EActionHandIndex_Left)
{
MotionControllerData.GripPosition = FVector(-1018.305f, -478.019f, 209.872f);
MotionControllerData.GripRotation = FQuat(-0.116352126f, 0.039430488f, -0.757644236f, 0.641001403f);
}
else
{
MotionControllerData.GripPosition = FVector(-1202.619f, -521.077f, 283.076f);
MotionControllerData.GripRotation = FQuat(0.040843058f, 0.116659224f, 0.980030060f, -0.155767411f);
}*/
HandTrackingData.DeviceName = TEXT("OpenXR");
SkeletalMappingData.SkeletalTransforms.Empty(SkeletalMappingData.SkeletalTransforms.Num());
FTransform ParentTrans = FTransform(MotionControllerData.GripUnrealSpaceRotation, MotionControllerData.GripUnrealSpaceLocation, FVector(1.f));
for (int i = 0; i < HandTrackingData.HandKeyLocations.Num(); i++)
{
SkeletalMappingData.SkeletalTransforms.Add(FTransform(HandTrackingData.HandKeyRotations[i], HandTrackingData.HandKeyLocations[i], FVector(1.f)).GetRelativeTransform(ParentTrans));
}
SkeletalMappingData.bHasValidData = (SkeletalMappingData.SkeletalTransforms.Num() == EHandKeypointCount);
}

View file

@ -0,0 +1,24 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "OpenXRExpansionPlugin.h"
#include "OpenXRExpansionFunctionLibrary.h"
#define LOCTEXT_NAMESPACE "FXRExpansionPluginModule"
void FOpenXRExpansionPluginModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
//LoadOpenVRModule();
}
void FOpenXRExpansionPluginModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
// UnloadOpenVRModule();
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FOpenXRExpansionPluginModule, OpenXRExpansionPlugin)

View file

@ -0,0 +1,895 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "OpenXRHandPoseComponent.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(OpenXRHandPoseComponent)
#include "Net/UnrealNetwork.h"
#include "MotionControllerComponent.h"
#include "OpenXRExpansionFunctionLibrary.h"
#include "Engine/NetSerialization.h"
#include "Net/Core/PushModel/PushModel.h"
#include "XRMotionControllerBase.h" // for GetHandEnumForSourceName()
//#include "EngineMinimal.h"
UOpenXRHandPoseComponent::UOpenXRHandPoseComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.bStartWithTickEnabled = true;
ReplicationRateForSkeletalAnimations = 10.f;
bReplicateSkeletalData = false;
bSmoothReplicatedSkeletalData = true;
SkeletalNetUpdateCount = 0.f;
bDetectGestures = true;
SetIsReplicatedByDefault(true);
bGetMockUpPoseForDebugging = false;
}
void UOpenXRHandPoseComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
FDoRepLifetimeParams SkipOwnerParams;
SkipOwnerParams.Condition = COND_SkipOwner;
SkipOwnerParams.bIsPushBased = true;
// Skipping the owner with this as the owner will use the controllers location directly
DOREPLIFETIME_WITH_PARAMS_FAST(UOpenXRHandPoseComponent, LeftHandRep, SkipOwnerParams);
DOREPLIFETIME_WITH_PARAMS_FAST(UOpenXRHandPoseComponent, RightHandRep, SkipOwnerParams);
}
void UOpenXRHandPoseComponent::Server_SendSkeletalTransforms_Implementation(const FBPXRSkeletalRepContainer& SkeletalInfo)
{
for (int i = 0; i < HandSkeletalActions.Num(); i++)
{
if (HandSkeletalActions[i].TargetHand == SkeletalInfo.TargetHand)
{
if (SkeletalInfo.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left)
{
if (bSmoothReplicatedSkeletalData)
{
LeftHandRepManager.PreCopyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing);
}
FBPXRSkeletalRepContainer::CopyReplicatedTo(SkeletalInfo, HandSkeletalActions[i]);
LeftHandRep = SkeletalInfo;
#if WITH_PUSH_MODEL
MARK_PROPERTY_DIRTY_FROM_NAME(UOpenXRHandPoseComponent, LeftHandRep, this);
#endif
if (bSmoothReplicatedSkeletalData)
{
LeftHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing);
}
}
else
{
if (bSmoothReplicatedSkeletalData)
{
RightHandRepManager.PreCopyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing);
}
FBPXRSkeletalRepContainer::CopyReplicatedTo(SkeletalInfo, HandSkeletalActions[i]);
RightHandRep = SkeletalInfo;
#if WITH_PUSH_MODEL
MARK_PROPERTY_DIRTY_FROM_NAME(UOpenXRHandPoseComponent, RightHandRep, this);
#endif
if (bSmoothReplicatedSkeletalData)
{
RightHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing);
}
}
break;
}
}
}
bool UOpenXRHandPoseComponent::Server_SendSkeletalTransforms_Validate(const FBPXRSkeletalRepContainer& SkeletalInfo)
{
return true;
}
void FOpenXRAnimInstanceProxy::PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds)
{
Super::PreUpdate(InAnimInstance, DeltaSeconds);
if (UOpenXRAnimInstance* OwningInstance = Cast<UOpenXRAnimInstance>(InAnimInstance))
{
if (OwningInstance->OwningPoseComp)
{
if (HandSkeletalActionData.Num() != OwningInstance->OwningPoseComp->HandSkeletalActions.Num())
{
HandSkeletalActionData.Empty(OwningInstance->OwningPoseComp->HandSkeletalActions.Num());
for(FBPOpenXRActionSkeletalData& actionInfo : OwningInstance->OwningPoseComp->HandSkeletalActions)
{
HandSkeletalActionData.Add(actionInfo);
}
}
else
{
for (int i = 0; i < OwningInstance->OwningPoseComp->HandSkeletalActions.Num(); ++i)
{
HandSkeletalActionData[i] = OwningInstance->OwningPoseComp->HandSkeletalActions[i];
}
}
}
}
}
FOpenXRAnimInstanceProxy::FOpenXRAnimInstanceProxy(UAnimInstance* InAnimInstance)
: FAnimInstanceProxy(InAnimInstance)
{
}
void UOpenXRHandPoseComponent::BeginPlay()
{
/*if (UMotionControllerComponent * MotionParent = Cast<UMotionControllerComponent>(GetAttachParent()))
{
EControllerHand HandType;
if (!FXRMotionControllerBase::GetHandEnumForSourceName(MotionParent->MotionSource, HandType))
{
HandType = EControllerHand::Left;
}
for (int i = 0; i < HandSkeletalActions.Num(); i++)
{
if (HandType == EControllerHand::Left || HandType == EControllerHand::AnyHand)
HandSkeletalActions[i].SkeletalData.TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Left;
else
HandSkeletalActions[i].SkeletalData.TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Right;
}
}*/
Super::BeginPlay();
}
void UOpenXRHandPoseComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
if (!IsLocallyControlled())
{
if (bReplicateSkeletalData)
{
// Handle bone lerping here if we are replicating
for (FBPOpenXRActionSkeletalData& actionInfo : HandSkeletalActions)
{
if (bSmoothReplicatedSkeletalData)
{
if (actionInfo.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left)
{
LeftHandRepManager.UpdateManager(DeltaTime, actionInfo, this);
}
else
{
RightHandRepManager.UpdateManager(DeltaTime, actionInfo, this);
}
}
}
}
}
else // Get data and process
{
bool bGetCompressedTransforms = false;
if (bReplicateSkeletalData && HandSkeletalActions.Num() > 0)
{
SkeletalNetUpdateCount += DeltaTime;
if (SkeletalNetUpdateCount >= (1.0f / ReplicationRateForSkeletalAnimations))
{
SkeletalNetUpdateCount = 0.0f;
bGetCompressedTransforms = true;
}
}
for (FBPOpenXRActionSkeletalData& actionInfo : HandSkeletalActions)
{
if (UOpenXRExpansionFunctionLibrary::GetOpenXRHandPose(actionInfo, this, bGetMockUpPoseForDebugging))
{
if (bGetCompressedTransforms)
{
if (GetNetMode() == NM_Client)
{
if (actionInfo.bHasValidData)
{
FBPXRSkeletalRepContainer ContainerSend;
ContainerSend.CopyForReplication(actionInfo);
Server_SendSkeletalTransforms(ContainerSend);
}
}
else
{
if (actionInfo.bHasValidData)
{
if (actionInfo.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left)
LeftHandRep.CopyForReplication(actionInfo);
else
RightHandRep.CopyForReplication(actionInfo);
}
}
}
}
if (bDetectGestures && actionInfo.bHasValidData && actionInfo.SkeletalTransforms.Num() > 0 && GesturesDB != nullptr && GesturesDB->Gestures.Num() > 0)
{
DetectCurrentPose(actionInfo);
}
}
}
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
bool UOpenXRHandPoseComponent::SaveCurrentPose(FName RecordingName, EVRSkeletalHandIndex HandToSave)
{
if (!HandSkeletalActions.Num())
return false;
// Default to the first hand element so that single length arrays work as is.
FBPOpenXRActionSkeletalData* HandSkeletalAction = nullptr;
// Now check for the specific passed in hand if this is a multi hand
for (int i = 0; i < HandSkeletalActions.Num(); ++i)
{
if (HandSkeletalActions[i].TargetHand == HandToSave)
{
HandSkeletalAction = &HandSkeletalActions[i];
break;
}
}
if (!HandSkeletalAction || !HandSkeletalAction->bHasValidData || HandSkeletalAction->SkeletalTransforms.Num() < EHandKeypointCount)
return false;
if (GesturesDB)
{
FOpenXRGesture NewGesture;
int32 FingerMap[5] =
{
(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT
};
FVector WristLoc = FVector::ZeroVector;
if (HandToSave == EVRSkeletalHandIndex::EActionHandIndex_Left)
{
WristLoc = HandSkeletalAction->SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation().MirrorByVector(FVector::RightVector);
}
else
{
WristLoc = HandSkeletalAction->SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation();
}
for (int i = 0; i < 5; ++i)
{
if (HandToSave == EVRSkeletalHandIndex::EActionHandIndex_Left)
{
NewGesture.FingerValues[i] = FOpenXRGestureFingerPosition(HandSkeletalAction->SkeletalTransforms[FingerMap[i]].GetLocation().MirrorByVector(FVector::RightVector) - WristLoc, (EXRHandJointType)FingerMap[i]);
}
else
{
NewGesture.FingerValues[i] = FOpenXRGestureFingerPosition(HandSkeletalAction->SkeletalTransforms[FingerMap[i]].GetLocation() - WristLoc, (EXRHandJointType)FingerMap[i]);
}
}
NewGesture.Name = RecordingName;
GesturesDB->Gestures.Add(NewGesture);
return true;
}
return false;
}
bool UOpenXRHandPoseComponent::K2_DetectCurrentPose(UPARAM(ref) FBPOpenXRActionSkeletalData& SkeletalAction, FOpenXRGesture & GestureOut)
{
if (!GesturesDB || GesturesDB->Gestures.Num() < 1)
return false;
int32 FingerMap[5] =
{
(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT
};
FVector WristLoc = FVector::ZeroVector;
if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left)
{
WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation().MirrorByVector(FVector::RightVector);
}
else
{
WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation();
}
// Early fill in an array to keep from performing math for each gesture
TArray<FVector> CurrentTips;
CurrentTips.AddUninitialized(5);
for (int i = 0; i < 5; ++i)
{
if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left)
{
CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation().MirrorByVector(FVector::RightVector) - WristLoc;
}
else
{
CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation() - WristLoc;
}
}
for (const FOpenXRGesture& Gesture : GesturesDB->Gestures)
{
// If not enough indexs to match curl values, or if this gesture requires finger splay and the controller can't do it
if (Gesture.FingerValues.Num() < 5 || SkeletalAction.SkeletalTransforms.Num() < EHandKeypointCount)
continue;
bool bDetectedPose = true;
for (int i = 0; i < 5; ++i)
{
FVector GestureV = Gesture.FingerValues[i].Value;
FVector CurrentV = CurrentTips[i];
FVector Difference = GestureV - CurrentV;
if (!Gesture.FingerValues[i].Value.Equals(CurrentTips[i], Gesture.FingerValues[i].Threshold))
{
bDetectedPose = false;
break;
}
}
if (bDetectedPose)
{
GestureOut = Gesture;
return true;
}
}
return false;
}
bool UOpenXRHandPoseComponent::DetectCurrentPose(FBPOpenXRActionSkeletalData &SkeletalAction)
{
if (!GesturesDB || GesturesDB->Gestures.Num() < 1 || SkeletalAction.SkeletalTransforms.Num() < EHandKeypointCount)
return false;
FTransform BoneTransform = FTransform::Identity;
int32 FingerMap[5] =
{
(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT,
(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT
};
FVector WristLoc = FVector::ZeroVector;
if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left)
{
WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation().MirrorByVector(FVector::RightVector);
}
else
{
WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation();
}
// Early fill in an array to keep from performing math for each gesture
TArray<FVector> CurrentTips;
CurrentTips.AddUninitialized(5);
for (int i = 0; i < 5; ++i)
{
if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left)
{
CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation().MirrorByVector(FVector::RightVector) - WristLoc;
}
else
{
CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation() - WristLoc;
}
}
for (auto GestureIterator = GesturesDB->Gestures.CreateConstIterator(); GestureIterator; ++GestureIterator)
{
const FOpenXRGesture &Gesture = *GestureIterator;
// If not enough indexs to match curl values, or if this gesture requires finger splay and the controller can't do it
if (Gesture.FingerValues.Num() < 5 || SkeletalAction.SkeletalTransforms.Num() < EHandKeypointCount)
continue;
bool bDetectedPose = true;
for (int i = 0; i < 5; ++i)
{
if (Gesture.FingerValues[i].Threshold <= 0.0f)
continue;
if (!Gesture.FingerValues[i].Value.Equals(CurrentTips[i], Gesture.FingerValues[i].Threshold))
{
bDetectedPose = false;
break;
}
}
if (bDetectedPose)
{
if (SkeletalAction.LastHandGesture != Gesture.Name)
{
if (SkeletalAction.LastHandGesture != NAME_None)
OnGestureEnded.Broadcast(SkeletalAction.LastHandGesture, SkeletalAction.LastHandGestureIndex, SkeletalAction.TargetHand);
SkeletalAction.LastHandGesture = Gesture.Name;
SkeletalAction.LastHandGestureIndex = GestureIterator.GetIndex();
OnNewGestureDetected.Broadcast(SkeletalAction.LastHandGesture, SkeletalAction.LastHandGestureIndex, SkeletalAction.TargetHand);
return true;
}
else
return false; // Same gesture
}
}
if (SkeletalAction.LastHandGesture != NAME_None)
{
OnGestureEnded.Broadcast(SkeletalAction.LastHandGesture, SkeletalAction.LastHandGestureIndex, SkeletalAction.TargetHand);
SkeletalAction.LastHandGesture = NAME_None;
SkeletalAction.LastHandGestureIndex = INDEX_NONE;
}
return false;
}
UOpenXRHandPoseComponent::FTransformLerpManager::FTransformLerpManager()
{
bReplicatedOnce = false;
bLerping = false;
UpdateCount = 0.0f;
UpdateRate = 0.0f;
}
void UOpenXRHandPoseComponent::FTransformLerpManager::PreCopyNewData(FBPOpenXRActionSkeletalData& ActionInfo, int NetUpdateRate, bool bExponentialSmoothing)
{
if (!bExponentialSmoothing)
{
if (ActionInfo.SkeletalTransforms.Num())
{
OldTransforms = ActionInfo.SkeletalTransforms;
}
}
}
void UOpenXRHandPoseComponent::FTransformLerpManager::NotifyNewData(FBPOpenXRActionSkeletalData& ActionInfo, int NetUpdateRate, bool bExponentialSmoothing)
{
UpdateRate = (1.0f / NetUpdateRate);
if (bReplicatedOnce)
{
bLerping = true;
UpdateCount = 0.0f;
NewTransforms = ActionInfo.SkeletalTransforms;
ActionInfo.SkeletalTransforms = OldTransforms;
}
else
{
if (bExponentialSmoothing)
{
OldTransforms = ActionInfo.SkeletalTransforms;
}
bReplicatedOnce = true;
}
}
void UOpenXRHandPoseComponent::FTransformLerpManager::UpdateManager(float DeltaTime, FBPOpenXRActionSkeletalData& ActionInfo, UOpenXRHandPoseComponent* ParentComp)
{
if (!ActionInfo.bHasValidData || !OldTransforms.Num())
return;
if (bLerping)
{
bool bExponentialSmoothing = ParentComp->bUseExponentialSmoothing;
float LerpVal = 0.0f;
if (!bExponentialSmoothing || ParentComp->InterpolationSpeed <= 0.f)
{
UpdateCount += DeltaTime;
// Keep LerpVal
LerpVal = FMath::Clamp(UpdateCount / UpdateRate, 0.0f, 1.0f);
}
else
{
LerpVal = FMath::Clamp(DeltaTime * ParentComp->InterpolationSpeed, 0.f, 1.f);
}
if (!bExponentialSmoothing && LerpVal >= 1.0f)
{
bLerping = false;
UpdateCount = 0.0f;
ActionInfo.SkeletalTransforms = NewTransforms;
}
else
{
int32 BoneCountAdjustment = 6 + (ActionInfo.bEnableUE4HandRepSavings ? 4 : 0);
if ((NewTransforms.Num() < (EHandKeypointCount - BoneCountAdjustment)) || (NewTransforms.Num() != ActionInfo.SkeletalTransforms.Num()))
{
return;
}
ActionInfo.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_PALM_EXT] = FTransform::Identity;
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
//BlendBone((uint8)EVROpenXRBones::eBone_Thumb3, ActionInfo, LerpVal); // Technically can be projected instead of blended
if (!ActionInfo.bEnableUE4HandRepSavings)
{
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
}
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
//BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended
if (!ActionInfo.bEnableUE4HandRepSavings)
{
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
}
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
//BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended
if (!ActionInfo.bEnableUE4HandRepSavings)
{
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
}
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
//BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended
if (!ActionInfo.bEnableUE4HandRepSavings)
{
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
}
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing);
//BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended
// These are copied from the 3rd joints as they use the same transform but a different root
// Don't want to waste cpu time blending these
//ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_Thumb] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Thumb2];
//ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_IndexFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_IndexFinger3];
//ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_MiddleFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_MiddleFinger3];
//ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_RingFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_RingFinger3];
//ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_PinkyFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_PinkyFinger3];
}
}
}
void FBPXRSkeletalRepContainer::CopyForReplication(FBPOpenXRActionSkeletalData& Other)
{
TargetHand = Other.TargetHand;
if (!Other.bHasValidData)
return;
bAllowDeformingMesh = Other.bAllowDeformingMesh;
bEnableUE4HandRepSavings = Other.bEnableUE4HandRepSavings;
// Instead of doing this, we likely need to lerp but this is for testing
//SkeletalTransforms = Other.SkeletalData.SkeletalTransforms;
if (Other.SkeletalTransforms.Num() < EHandKeypointCount)
{
SkeletalTransforms.Empty();
return;
}
int32 BoneCountAdjustment = 6 + (bEnableUE4HandRepSavings ? 4 : 0);
if (SkeletalTransforms.Num() != EHandKeypointCount - BoneCountAdjustment)
{
SkeletalTransforms.Reset(EHandKeypointCount - BoneCountAdjustment); // Minus bones we don't need
SkeletalTransforms.AddUninitialized(EHandKeypointCount - BoneCountAdjustment);
}
int32 idx = 0;
// Root is always identity
//SkeletalTransforms[0] = Other.SkeletalData.SkeletalTransforms[(uint8)EVROpenInputBones::eBone_Root]; // This has no pos right? Need to skip pos on it
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT];
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT];
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT];
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT];
if (!bEnableUE4HandRepSavings)
{
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT];
}
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT];
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT];
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT];
if (!bEnableUE4HandRepSavings)
{
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT];
}
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT];
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT];
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT];
if (!bEnableUE4HandRepSavings)
{
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT];
}
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT];
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT];
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT];
if (!bEnableUE4HandRepSavings)
{
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT];
}
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT];
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT];
SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT];
}
void FBPXRSkeletalRepContainer::CopyReplicatedTo(const FBPXRSkeletalRepContainer& Container, FBPOpenXRActionSkeletalData& Other)
{
int32 BoneCountAdjustment = 6 + (Container.bEnableUE4HandRepSavings ? 4 : 0);
if (Container.SkeletalTransforms.Num() < (EHandKeypointCount - BoneCountAdjustment))
{
Other.SkeletalTransforms.Empty();
Other.bHasValidData = false;
return;
}
Other.bAllowDeformingMesh = Container.bAllowDeformingMesh;
Other.bEnableUE4HandRepSavings = Container.bEnableUE4HandRepSavings;
// Instead of doing this, we likely need to lerp but this is for testing
//Other.SkeletalData.SkeletalTransforms = Container.SkeletalTransforms;
if (Other.SkeletalTransforms.Num() != EHandKeypointCount)
{
Other.SkeletalTransforms.Reset(EHandKeypointCount);
Other.SkeletalTransforms.AddUninitialized(EHandKeypointCount);
}
int32 idx = 0;
// Only fill in the ones that we care about
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_PALM_EXT] = FTransform::Identity; // Always identity
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT] = FTransform::Identity;
if (!Container.bEnableUE4HandRepSavings)
{
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT] = Container.SkeletalTransforms[idx++];
}
else
{
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT] = FTransform::Identity;
}
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT] = FTransform::Identity;
if (!Container.bEnableUE4HandRepSavings)
{
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT] = Container.SkeletalTransforms[idx++];
}
else
{
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT] = FTransform::Identity;
}
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT] = FTransform::Identity;
if (!Container.bEnableUE4HandRepSavings)
{
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT] = Container.SkeletalTransforms[idx++];
}
else
{
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT] = FTransform::Identity;
}
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT] = FTransform::Identity;
if (!Container.bEnableUE4HandRepSavings)
{
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT] = Container.SkeletalTransforms[idx++];
}
else
{
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT] = FTransform::Identity;
}
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT] = Container.SkeletalTransforms[idx++];
Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT] = FTransform::Identity;
Other.bHasValidData = true;
}
bool FBPXRSkeletalRepContainer::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
bOutSuccess = true;
Ar.SerializeBits(&TargetHand, 1);
Ar.SerializeBits(&bAllowDeformingMesh, 1);
Ar.SerializeBits(&bEnableUE4HandRepSavings, 1);
int32 BoneCountAdjustment = 6 + (bEnableUE4HandRepSavings ? 4 : 0);
uint8 TransformCount = EHandKeypointCount - BoneCountAdjustment;
bool bHasValidData = SkeletalTransforms.Num() >= TransformCount;
Ar.SerializeBits(&bHasValidData, 1);
//Ar << TransformCount;
if (Ar.IsLoading())
{
SkeletalTransforms.Reset(TransformCount);
}
FVector Position = FVector::ZeroVector;
FRotator Rot = FRotator::ZeroRotator;
if (bHasValidData)
{
for (int i = 0; i < TransformCount; i++)
{
if (Ar.IsSaving())
{
if (bAllowDeformingMesh)
Position = SkeletalTransforms[i].GetLocation();
Rot = SkeletalTransforms[i].Rotator();
}
if (bAllowDeformingMesh)
bOutSuccess &= SerializePackedVector<10, 11>(Position, Ar);
Rot.SerializeCompressed(Ar); // Short? 10 bit?
if (Ar.IsLoading())
{
if (bAllowDeformingMesh)
SkeletalTransforms.Add(FTransform(Rot, Position));
else
SkeletalTransforms.Add(FTransform(Rot));
}
}
}
return bOutSuccess;
}
void UOpenXRAnimInstance::NativeBeginPlay()
{
Super::NativeBeginPlay();
AActor* Owner = GetOwningComponent()->GetOwner();
UActorComponent* HandPoseComp = nullptr;
if (Owner)
{
HandPoseComp = Owner->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass());
if (!HandPoseComp)
{
// We are also checking owner->owner in case hand mesh is in a sub actor
if (Owner->GetOwner())
{
HandPoseComp = Owner->GetOwner()->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass());
}
}
}
if (!HandPoseComp)
{
return;
}
if (UOpenXRHandPoseComponent* HandComp = Cast<UOpenXRHandPoseComponent>(HandPoseComp))
{
OwningPoseComp = HandComp;
}
}
/*void UOpenXRAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
AActor* Owner = GetOwningComponent()->GetOwner();
UActorComponent* HandPoseComp = nullptr;
if (Owner)
{
HandPoseComp = Owner->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass());
if (!HandPoseComp)
{
// We are also checking owner->owner in case hand mesh is in a sub actor
if (Owner->GetOwner())
{
HandPoseComp = Owner->GetOwner()->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass());
}
}
}
if (!HandPoseComp)
{
return;
}
if (UOpenXRHandPoseComponent* HandComp = Cast<UOpenXRHandPoseComponent>(HandPoseComp))
{
OwningPoseComp = HandComp;
}
}*/
void UOpenXRAnimInstance::InitializeCustomBoneMapping(UPARAM(ref) FBPOpenXRSkeletalMappingData& SkeletalMappingData)
{
USkeleton* AssetSkeleton = this->CurrentSkeleton;//RequiredBones.GetSkeletonAsset();
if (AssetSkeleton)
{
FBoneContainer& RequiredBones = this->GetRequiredBones();
for (FBPOpenXRSkeletalPair& BonePair : SkeletalMappingData.BonePairs)
{
// Fill in the bone name for the reference
BonePair.ReferenceToConstruct.BoneName = BonePair.BoneToTarget;
// Init the reference
BonePair.ReferenceToConstruct.Initialize(AssetSkeleton);
BonePair.ReferenceToConstruct.CachedCompactPoseIndex = BonePair.ReferenceToConstruct.GetCompactPoseIndex(RequiredBones);
if ((BonePair.ReferenceToConstruct.CachedCompactPoseIndex != INDEX_NONE))
{
// Get our parent bones index
BonePair.ParentReference = RequiredBones.GetParentBoneIndex(BonePair.ReferenceToConstruct.CachedCompactPoseIndex);
}
}
if (UObject* OwningAsset = RequiredBones.GetAsset())
{
SkeletalMappingData.LastInitializedName = OwningAsset->GetFName();
}
SkeletalMappingData.bInitialized = true;
return;
}
SkeletalMappingData.bInitialized = false;
}

View file

@ -0,0 +1,340 @@
#include "Serializers/FBPXRSkeletalRepContainerNetSerializer.h"
#include "Iris/Serialization/NetSerializerDelegates.h"
#include "Iris/Serialization/NetSerializers.h"
#include "Iris/Serialization/PackedVectorNetSerializers.h"
#include "Iris/ReplicationState/PropertyNetSerializerInfoRegistry.h"
#include "Iris/ReplicationState/ReplicationStateDescriptorBuilder.h"
#include "OpenXRHandPoseComponent.h"
namespace UE::Net
{
// -----------------------------------------------------------------------------
// Iris serializer for FBPXRSkeletalRepContainer
// -----------------------------------------------------------------------------
struct FBPXRSkeletalRepContainerNetSerializer
{
inline static const FVectorNetQuantize100NetSerializerConfig Quantize100SerializerConfig;
inline static const FNetSerializerConfig* VectorNetQuantizeNetSerializerConfig = &Quantize100SerializerConfig;
inline static const FNetSerializer* VectorNetQuantizeNetSerializer;
class FNetSerializerRegistryDelegates final : private UE::Net::FNetSerializerRegistryDelegates
{
public:
virtual ~FNetSerializerRegistryDelegates();
void InitNetSerializer()
{
FBPXRSkeletalRepContainerNetSerializer::VectorNetQuantizeNetSerializer = &UE_NET_GET_SERIALIZER(FVectorNetQuantize100NetSerializer);
}
private:
virtual void OnPreFreezeNetSerializerRegistry() override;
//virtual void OnPostFreezeNetSerializerRegistry() override;
};
inline static FBPXRSkeletalRepContainerNetSerializer::FNetSerializerRegistryDelegates NetSerializerRegistryDelegates;
/** Version is required. */
static constexpr uint32 Version = 0;
struct alignas(8) FSkeletalTransformQuantizedData
{
uint64 Position[4]; // We don't need to store double for tracked device positions, but their forwarded serializer uses it
uint16 Rotation[3];
};
struct alignas(8) FBPXRSkeletalRepContainerQuantizedData
{
uint8 TargetHand;
uint8 bAllowDeformingMesh;
uint8 bEnableUE4HandRepSavings;
uint16 ElementCount;
FSkeletalTransformQuantizedData SkeletalTransforms[EHandKeypointCount];
// List of array data, we know the max elements so we can statically set it to that
// Technically should be -6 since we dont use some but im going to future proof it a bit here
};
typedef FBPXRSkeletalRepContainer SourceType;
typedef FBPXRSkeletalRepContainerQuantizedData QuantizedType;
typedef FBPXRSkeletalRepContainerNetSerializerConfig ConfigType;
inline static const ConfigType DefaultConfig;
/** Set to false when a same value delta compression method is undesirable, for example when the serializer only writes a single bit for the state. */
static constexpr bool bUseDefaultDelta = true;
// Not using delta as this struct is sent on a timer and not changed incrementally
static constexpr uint32 ElementSizeBytes = sizeof(FSkeletalTransformQuantizedData);
// Called to create a "quantized snapshot" of the struct
static void Quantize(FNetSerializationContext& Context, const FNetQuantizeArgs& Args)
{
// Actually do the real quantization step here next instead of just in serialize, will save on memory overall
const SourceType& Source = *reinterpret_cast<const SourceType*>(Args.Source);
QuantizedType& Target = *reinterpret_cast<QuantizedType*>(Args.Target);
Target.TargetHand = (uint8)Source.TargetHand;
Target.bAllowDeformingMesh = Source.bAllowDeformingMesh ? 1 : 0;
Target.bEnableUE4HandRepSavings = Source.bEnableUE4HandRepSavings ? 1 : 0;
// Free data if non null
Target.ElementCount = 0;
const uint32 Num = Source.SkeletalTransforms.Num();
Target.ElementCount = static_cast<uint16>(Num);
if (Num > 0)
{
FRotator TargetRot;
FVector TargetLoc;
for (uint16 i = 0; i < Num; ++i)
{
TargetRot = Source.SkeletalTransforms[i].Rotator();
TargetLoc = Source.SkeletalTransforms[i].GetLocation();
Target.SkeletalTransforms[i].Rotation[0] = FRotator::CompressAxisToShort(TargetRot.Pitch);
Target.SkeletalTransforms[i].Rotation[1] = FRotator::CompressAxisToShort(TargetRot.Yaw);
Target.SkeletalTransforms[i].Rotation[2] = FRotator::CompressAxisToShort(TargetRot.Roll);
const FNetSerializer* Serializer = VectorNetQuantizeNetSerializer;
const FNetSerializerConfig* SerializerConfig = VectorNetQuantizeNetSerializerConfig;
FNetQuantizeArgs MemberArgs = Args;
MemberArgs.NetSerializerConfig = NetSerializerConfigParam(SerializerConfig);
MemberArgs.Source = NetSerializerValuePointer(&TargetLoc);
MemberArgs.Target = NetSerializerValuePointer(&Target.SkeletalTransforms[i].Position[0]);
Serializer->Quantize(Context, MemberArgs);
}
}
}
// Called to apply the quantized snapshot back to gameplay memory
static void Dequantize(FNetSerializationContext& Context, const FNetDequantizeArgs& Args)
{
const QuantizedType& Source = *reinterpret_cast<const QuantizedType*>(Args.Source);
SourceType& Target = *reinterpret_cast<SourceType*>(Args.Target);
Target.TargetHand = (EVRSkeletalHandIndex)Source.TargetHand;
Target.bAllowDeformingMesh = Source.bAllowDeformingMesh != 0;
Target.bEnableUE4HandRepSavings = Source.bEnableUE4HandRepSavings != 0;
const uint16 Count = Source.ElementCount;
Target.SkeletalTransforms.Reset();
if (Count > 0)
{
Target.SkeletalTransforms.AddUninitialized(Count);
const FSkeletalTransformQuantizedData* Data = Source.SkeletalTransforms;
FRotator TargetRot;
FVector TargetLoc;
for (int i = 0; i < Count; ++i)
{
TargetRot.Pitch = FRotator::DecompressAxisFromShort(Data[i].Rotation[0]);
TargetRot.Yaw = FRotator::DecompressAxisFromShort(Data[i].Rotation[1]);
TargetRot.Roll = FRotator::DecompressAxisFromShort(Data[i].Rotation[2]);
TargetRot.Normalize();
const FNetSerializer* Serializer = VectorNetQuantizeNetSerializer;
const FNetSerializerConfig* SerializerConfig = VectorNetQuantizeNetSerializerConfig;
FNetDequantizeArgs MemberArgs = Args;
MemberArgs.NetSerializerConfig = NetSerializerConfigParam(SerializerConfig);
MemberArgs.Source = NetSerializerValuePointer(&TargetLoc);
MemberArgs.Target = NetSerializerValuePointer(&Data[i].Position[0]);
Serializer->Dequantize(Context, MemberArgs);
Target.SkeletalTransforms[i].SetComponents(TargetRot.Quaternion(), TargetLoc, FVector(1.0f));
}
}
}
// Serialize into bitstream
static void Serialize(FNetSerializationContext& Context, const FNetSerializeArgs& Args)
{
const QuantizedType& Source = *reinterpret_cast<const QuantizedType*>(Args.Source);
FNetBitStreamWriter* Writer = Context.GetBitStreamWriter();
// write header bytes (small fields) — do as bytes for compactness
Writer->WriteBits(Source.TargetHand, 8);
Writer->WriteBits(Source.bAllowDeformingMesh, 1);
Writer->WriteBits(Source.bEnableUE4HandRepSavings, 1);
int32 BoneCountAdjustment = 6 + (Source.bEnableUE4HandRepSavings != 0 ? 4 : 0);
uint8 TransformCount = EHandKeypointCount - BoneCountAdjustment;
bool bHasValidData = Source.ElementCount >= TransformCount;
Writer->WriteBits(bHasValidData, 1);
if (bHasValidData)
{
// write element count (16 bits)
uint32 ElemCount = Source.ElementCount;
if (ElemCount < 1)
{
// Write nothing
ElemCount = 0;
}
Writer->WriteBits(ElemCount, 16);
const FSkeletalTransformQuantizedData* Data = Source.SkeletalTransforms;
for (uint16 i = 0; i < ElemCount; ++i)
{
if (Source.bAllowDeformingMesh)
{
const FNetSerializer* Serializer = VectorNetQuantizeNetSerializer;
const FNetSerializerConfig* SerializerConfig = VectorNetQuantizeNetSerializerConfig;
FNetSerializeArgs MemberArgs = Args;
MemberArgs.NetSerializerConfig = NetSerializerConfigParam(SerializerConfig);
MemberArgs.Source = NetSerializerValuePointer(&Data[i].Position[0]);
Serializer->Serialize(Context, MemberArgs);
}
Writer->WriteBits(Data[i].Rotation[0], 16);
Writer->WriteBits(Data[i].Rotation[1], 16);
Writer->WriteBits(Data[i].Rotation[2], 16);
}
}
}
// Deserialize from bitstream
static void Deserialize(FNetSerializationContext& Context, const FNetDeserializeArgs& Args)
{
QuantizedType& Target = *reinterpret_cast<QuantizedType*>(Args.Target);
FNetBitStreamReader* Reader = Context.GetBitStreamReader();
// write header bytes (small fields) — do as bytes for compactness
Target.TargetHand = Reader->ReadBits(8);
Target.bAllowDeformingMesh = Reader->ReadBits(1);
Target.bEnableUE4HandRepSavings = Reader->ReadBits(1);
int32 BoneCountAdjustment = 6 + (Target.bEnableUE4HandRepSavings != 0 ? 4 : 0);
uint8 TransformCount = EHandKeypointCount - BoneCountAdjustment;
bool bHasValidData = Reader->ReadBits(1) != 0;
if (bHasValidData)
{
// write element count (16 bits)
uint32 ElemCount = Reader->ReadBits(16);
Target.ElementCount = static_cast<uint16>(ElemCount);
if (ElemCount > 0)
{
FSkeletalTransformQuantizedData* Data = Target.SkeletalTransforms;
for (uint16 i = 0; i < ElemCount; ++i)
{
if (Target.bAllowDeformingMesh)
{
const FNetSerializer* Serializer = VectorNetQuantizeNetSerializer;
const FNetSerializerConfig* SerializerConfig = VectorNetQuantizeNetSerializerConfig;
FNetDeserializeArgs MemberArgs = Args;
MemberArgs.NetSerializerConfig = NetSerializerConfigParam(SerializerConfig);
MemberArgs.Target = NetSerializerValuePointer(&Data[i].Position[0]);
Serializer->Deserialize(Context, MemberArgs);
}
Data[i].Rotation[0] = Reader->ReadBits(16);
Data[i].Rotation[1] = Reader->ReadBits(16);
Data[i].Rotation[2] = Reader->ReadBits(16);
}
}
}
}
// Compare two instances to see if they differ
static bool IsEqual(FNetSerializationContext& Context, const FNetIsEqualArgs& Args)
{
if (Args.bStateIsQuantized)
{
const QuantizedType& QuantizedValue0 = *reinterpret_cast<const QuantizedType*>(Args.Source0);
const QuantizedType& QuantizedValue1 = *reinterpret_cast<const QuantizedType*>(Args.Source1);
return FPlatformMemory::Memcmp(&QuantizedValue0, &QuantizedValue1, sizeof(QuantizedType)) == 0;
}
else
{
const SourceType& L = *reinterpret_cast<const SourceType*>(Args.Source0);
const SourceType& R = *reinterpret_cast<const SourceType*>(Args.Source1);
if (L.bAllowDeformingMesh != R.bAllowDeformingMesh) return false;
if (L.bEnableUE4HandRepSavings != R.bEnableUE4HandRepSavings) return false;
if (L.SkeletalTransforms.Num() != R.SkeletalTransforms.Num()) return false;
// Using L num is valid as we already checked if they are the same count
return FPlatformMemory::Memcmp(L.SkeletalTransforms.GetData(), R.SkeletalTransforms.GetData(), sizeof(FTransform) * L.SkeletalTransforms.Num()) == 0;
}
}
static void Apply(FNetSerializationContext& Context, const FNetApplyArgs& Args)
{
const SourceType& Source = *reinterpret_cast<const SourceType*>(Args.Source);
SourceType& Target = *reinterpret_cast<SourceType*>(Args.Target);
Target.bAllowDeformingMesh = Source.bAllowDeformingMesh;
Target.bEnableUE4HandRepSavings = Source.bEnableUE4HandRepSavings;
Target.TargetHand = Source.TargetHand;
// Copys it over with new allocations
Target.SkeletalTransforms = Source.SkeletalTransforms;
}
static void CloneDynamicState(FNetSerializationContext& Context, const FNetCloneDynamicStateArgs& Args)
{
const QuantizedType& Source = *reinterpret_cast<QuantizedType*>(Args.Source);
QuantizedType& Target = *reinterpret_cast<QuantizedType*>(Args.Target);
// copy small fields
Target.TargetHand = Source.TargetHand;
Target.bAllowDeformingMesh = Source.bAllowDeformingMesh;
Target.bEnableUE4HandRepSavings = Source.bEnableUE4HandRepSavings;
// copy elements
Target.ElementCount = Source.ElementCount;
const uint32 Count = Target.ElementCount;
if (Count > 0)
{
const SIZE_T Bytes = SIZE_T(Count) * ElementSizeBytes;
FMemory::Memcpy(Target.SkeletalTransforms, Source.SkeletalTransforms, Bytes);
}
}
static void FreeDynamicState(FNetSerializationContext& Context, const FNetFreeDynamicStateArgs& Args)
{
QuantizedType& Target = *reinterpret_cast<QuantizedType*>(Args.Source);
Target.ElementCount = 0;
}
};
static const FName PropertyNetSerializerRegistry_NAME_BPXRSkeletalRepContainer("BPXRSkeletalRepContainer");
UE_NET_IMPLEMENT_NAMED_STRUCT_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_BPXRSkeletalRepContainer, FBPXRSkeletalRepContainerNetSerializer);
FBPXRSkeletalRepContainerNetSerializer::FNetSerializerRegistryDelegates::~FNetSerializerRegistryDelegates()
{
UE_NET_UNREGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_BPXRSkeletalRepContainer);
}
void FBPXRSkeletalRepContainerNetSerializer::FNetSerializerRegistryDelegates::OnPreFreezeNetSerializerRegistry()
{
InitNetSerializer();
UE_NET_REGISTER_NETSERIALIZER_INFO(PropertyNetSerializerRegistry_NAME_BPXRSkeletalRepContainer);
}
UE_NET_IMPLEMENT_SERIALIZER(FBPXRSkeletalRepContainerNetSerializer);
}

View file

@ -0,0 +1,121 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Runtime/AnimGraphRuntime/Public/BoneControllers/AnimNode_SkeletalControlBase.h"
#include "OpenXRExpansionTypes.h"
//#include "Skeleton/BodyStateSkeleton.h"
//#include "BodyStateAnimInstance.h"
#include "AnimNode_ApplyOpenXRHandPose.generated.h"
USTRUCT()
struct OPENXREXPANSIONPLUGIN_API FAnimNode_ApplyOpenXRHandPose : public FAnimNode_SkeletalControlBase
{
GENERATED_USTRUCT_BODY()
public:
FVector WristForwardLS_UE;
FVector WristSideDirectionLS;
// Generally used when not passing in custom bone mappings, defines the auto mapping style
UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault))
EVROpenXRSkeletonType SkeletonType;
// If your hand is part of a full body or arm skeleton and you don't have a proxy bone to retain the position enable this
UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault))
bool bSkipRootBone;
// If you only want to use the wrist transform part of this
// This will also automatically add the deform to the wrist as it doesn't make much sense without it
UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault))
bool bOnlyApplyWristTransform;
// Generally used when not passing in custom bone mappings, defines the auto mapping style
UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault))
FBPOpenXRActionSkeletalData OptionalStoredActionInfo;
// MappedBonePairs, if you leave it blank then they will auto generate based off of the SkeletonType
// Otherwise, fill out yourself.
UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinHiddenByDefault))
FBPOpenXRSkeletalMappingData MappedBonePairs;
bool bIsOpenInputAnimationInstance = false;
bool bIsMirroringHand = false;
void ConvertHandTransformsSpace(TArray<FTransform>& OutTransforms, const TArray<FTransform>& WorldTransforms, FTransform AddTrans, bool bMirrorLeftRight, bool bMergeMissingUE4Bones);
void CalculateSkeletalAdjustment(USkeleton* AssetSkeleton);
void CalculateOpenXRAdjustment();
FQuat CalcRotationAboutAxis(const FVector& FromDirection, const FVector& ToDirection, const FVector& Axis)
{
FVector FromDirectionCp = FVector::CrossProduct(Axis, FromDirection);
FVector ToDirectionCp = FVector::CrossProduct(Axis, ToDirection);
return FQuat::FindBetweenVectors(FromDirectionCp, ToDirectionCp);
}
FTransform GetRefBoneInCS(TArray<FTransform>& RefBones, TArray<FMeshBoneInfo>& RefBonesInfo, int32 BoneIndex)
{
FTransform BoneTransform;
if (BoneIndex >= 0)
{
BoneTransform = RefBones[BoneIndex];
if (RefBonesInfo[BoneIndex].ParentIndex >= 0)
{
BoneTransform *= GetRefBoneInCS(RefBones, RefBonesInfo, RefBonesInfo[BoneIndex].ParentIndex);
}
}
return BoneTransform;
}
void SetVectorToMaxElement(FVector& vec)
{
FVector absVal = vec.GetAbs();
if (absVal.X > absVal.Y && absVal.X > absVal.Z)
{
vec = vec.GetSignVector() * FVector(1.0f, 0.f, 0.f);
vec.Normalize();
}
else if (absVal.Y > absVal.X && absVal.Y > absVal.Z)
{
vec = vec.GetSignVector() * FVector(0.0f, 1.f, 0.f);
vec.Normalize();
}
else if (absVal.Z > absVal.X && absVal.Z > absVal.Y)
{
vec = vec.GetSignVector() * FVector(0.0f, 0.f, 1.f);
vec.Normalize();
}
else
{
vec.Normalize();
}
}
// FAnimNode_SkeletalControlBase interface
//virtual void UpdateInternal(const FAnimationUpdateContext& Context) override;
virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms) override;
virtual bool IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) override;
// End of FAnimNode_SkeletalControlBase interface
virtual void OnInitializeAnimInstance(const FAnimInstanceProxy* InProxy, const UAnimInstance* InAnimInstance) override;
virtual bool NeedsOnInitializeAnimInstance() const override { return true; }
virtual void InitializeBoneReferences(const FBoneContainer& RequiredBones) override;
virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override;
virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) override;
// Constructor
FAnimNode_ApplyOpenXRHandPose();
protected:
bool WorldIsGame;
AActor* OwningActor;
private:
};

View file

@ -0,0 +1,102 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "UObject/Object.h"
#include "Engine/EngineTypes.h"
#include "HeadMountedDisplayTypes.h"
#if defined(OPENXR_SUPPORTED)
#include "OpenXRCore.h"
#include "OpenXRHMD.h"
#endif
#include "OpenXRExpansionTypes.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "OpenXRExpansionFunctionLibrary.generated.h"
#if !defined(OPENXR_SUPPORTED)
class FOpenXRHMD;
#endif
DECLARE_LOG_CATEGORY_EXTERN(OpenXRExpansionFunctionLibraryLog, Log, All);
// This needs to be updated as the original gets changed, that or hope they make the original blueprint accessible.
UENUM(Blueprintable)
enum class EBPOpenXRControllerDeviceType : uint8
{
DT_SimpleController,
DT_ValveIndexController,
DT_ViveController,
DT_ViveProController,
//DT_CosmosController,
DT_DaydreamController,
DT_OculusTouchController,
DT_OculusGoController,
DT_MicrosoftMotionController,
DT_MicrosoftXboxController,
DT_PicoNeo3Controller,
DT_WMRController,
DT_UnknownController
};
UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent))
class OPENXREXPANSIONPLUGIN_API UOpenXRExpansionFunctionLibrary : public UBlueprintFunctionLibrary
{
//GENERATED_BODY()
GENERATED_BODY()
public:
UOpenXRExpansionFunctionLibrary(const FObjectInitializer& ObjectInitializer);
~UOpenXRExpansionFunctionLibrary();
public:
static FOpenXRHMD* GetOpenXRHMD()
{
#if defined(OPENXR_SUPPORTED)
static FName SystemName(TEXT("OpenXR"));
if (GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName))
{
return static_cast<FOpenXRHMD*>(GEngine->XRSystem.Get());
}
#endif
return nullptr;
}
UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true"))
static bool GetOpenXRHandPose(FBPOpenXRActionSkeletalData& HandPoseContainer, UOpenXRHandPoseComponent* HandPoseComponent, bool bGetMockUpPose = false);
//UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true"))
static void GetFingerCurlValues(TArray<FTransform>& TransformArray, TArray<float>& CurlArray);
// Get the estimated curl values from hand tracking
// Will return true if it was able to get the curls, false if it could not (hand tracking not enabled or no data for the tracked index)
UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (WorldContext = "WorldContextObject"))
static bool GetOpenXRFingerCurlValuesForHand(
UObject* WorldContextObject,
EControllerHand TargetHand,
float& ThumbCurl,
float& IndexCurl,
float& MiddleCurl,
float& RingCurl,
float& PinkyCurl);
static float GetCurlValueForBoneRoot(TArray<FTransform>& TransformArray, EHandKeypoint RootBone);
//UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true"))
static void ConvertHandTransformsSpaceAndBack(TArray<FTransform>& OutTransforms, const TArray<FTransform>& WorldTransforms);
UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true"))
static void GetMockUpControllerData(FXRHandTrackingState& HandTrackingData,FXRMotionControllerState& MotionControllerData, FBPOpenXRActionSkeletalData& SkeletalMappingData, bool bOpenHand = false);
// Get a list of all currently tracked devices and their types, index in the array is their device index
// Returns failed if the openXR query failed (no interaction profile yet or openXR is not running)
UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true", ExpandEnumAsExecs = "Result"))
static void GetXRMotionControllerType(FString& TrackingSystemName, EBPOpenXRControllerDeviceType& DeviceType, EBPXRResultSwitch &Result);
};

View file

@ -0,0 +1,18 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Modules/ModuleManager.h"
class FOpenXRExpansionPluginModule : public IModuleInterface
{
public:
FOpenXRExpansionPluginModule()
{
}
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View file

@ -0,0 +1,486 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "HeadMountedDisplayTypes.h"
#include "Animation/BoneReference.h"
#include "UObject/Object.h"
#include "Engine/EngineTypes.h"
#include "OpenXRExpansionTypes.generated.h"
// This makes a lot of the blueprint functions cleaner
UENUM()
enum class EBPXRResultSwitch : uint8
{
// On Success
OnSucceeded,
// On Failure
OnFailed
};
UENUM(BlueprintType)
enum class EVRSkeletalHandIndex : uint8
{
EActionHandIndex_Left = 0,
EActionHandIndex_Right
};
UENUM(BlueprintType)
enum class EXRHandJointType : uint8
{
OXR_HAND_JOINT_PALM_EXT = 0,
OXR_HAND_JOINT_WRIST_EXT = 1,
OXR_HAND_JOINT_THUMB_METACARPAL_EXT = 2,
OXR_HAND_JOINT_THUMB_PROXIMAL_EXT = 3,
OXR_HAND_JOINT_THUMB_DISTAL_EXT = 4,
OXR_HAND_JOINT_THUMB_TIP_EXT = 5,
OXR_HAND_JOINT_INDEX_METACARPAL_EXT = 6,
OXR_HAND_JOINT_INDEX_PROXIMAL_EXT = 7,
OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT = 8,
OXR_HAND_JOINT_INDEX_DISTAL_EXT = 9,
OXR_HAND_JOINT_INDEX_TIP_EXT = 10,
OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT = 11,
OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT = 12,
OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT = 13,
OXR_HAND_JOINT_MIDDLE_DISTAL_EXT = 14,
OXR_HAND_JOINT_MIDDLE_TIP_EXT = 15,
OXR_HAND_JOINT_RING_METACARPAL_EXT = 16,
OXR_HAND_JOINT_RING_PROXIMAL_EXT = 17,
OXR_HAND_JOINT_RING_INTERMEDIATE_EXT = 18,
OXR_HAND_JOINT_RING_DISTAL_EXT = 19,
OXR_HAND_JOINT_RING_TIP_EXT = 20,
OXR_HAND_JOINT_LITTLE_METACARPAL_EXT = 21,
OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT = 22,
OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT = 23,
OXR_HAND_JOINT_LITTLE_DISTAL_EXT = 24,
OXR_HAND_JOINT_LITTLE_TIP_EXT = 25,
OXR_HAND_JOINT_MAX_ENUM_EXT = 0xFF
};
UENUM(BlueprintType)
enum class EVROpenXRSkeletonType : uint8
{
// UE4 Skeletal Right hand
OXR_SkeletonType_UE4Default_Right,
// UE4 Skeletal Left hand
OXR_SkeletonType_UE4Default_Left,
// OpenVR Skeletal Right hand
OXR_SkeletonType_OpenVRDefault_Right,
// OpenVR Skeletal Left hand
OXR_SkeletonType_OpenVRDefault_Left,
// UE5 Skeletal Right hand
OXR_SkeletonType_UE5Default_Right,
// UE5 Skeletal Left hand
OXR_SkeletonType_UE5Default_Left,
// OpenXR Skeletal Right hand
OXR_SkeletonType_OpenXRDefault_Right,
// OpenXR Skeletal Left hand
OXR_SkeletonType_OpenXRDefault_Left,
OXR_SkeletonType_Custom
};
USTRUCT(BlueprintType, Category = "VRExpansionFunctions|OpenXR|HandSkeleton")
struct OPENXREXPANSIONPLUGIN_API FBPOpenXRActionSkeletalData
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default)
EVRSkeletalHandIndex TargetHand;
// Used to select the type of pose to retrieve from OpenXR, default is grip but you will want to use Palm when using the palm source for motion controller
UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default)
EXRControllerPoseType PoseType;
// A world scale override that will replace the engines current value and force into the tracked data if non zero
UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default)
float WorldScaleOverride;
UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default)
bool bAllowDeformingMesh;
// If true then the bones will be mirrored from left/right, to allow you to swap a hand mesh to the other hand
UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default)
bool bMirrorLeftRight;
// List of aproximated curls for each finger
UPROPERTY(BlueprintReadOnly, NotReplicated, Transient, Category = Default)
TArray<float> FingerCurls;
UPROPERTY(BlueprintReadOnly, NotReplicated, Transient, Category = Default)
TArray<FTransform> SkeletalTransforms;
// If true we will assume that the target skeleton does not have the metacarpal bones and we will not replicate them
// Only really used for the old UE4 skeleton
UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default)
bool bEnableUE4HandRepSavings;
//UPROPERTY(BlueprintReadOnly, NotReplicated, Transient, Category = Default)
//TArray<FTransform> OldSkeletalTransforms;
// The rotation required to rotate the finger bones back to X+
// The animation node attempts to auto calculate it, if you have a non standard hand you may need to fill
// This in by yourself
UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default)
FTransform AdditionTransform;
UPROPERTY(NotReplicated, BlueprintReadOnly, Category = Default)
bool bHasValidData;
FName LastHandGesture;
int32 LastHandGestureIndex;
FBPOpenXRActionSkeletalData()
{
//bGetTransformsInParentSpace = false;
AdditionTransform = FTransform::Identity;// FTransform(FRotator(180.f, 0.f, -90.f), FVector::ZeroVector, FVector(1.f));//FTransform(FRotator(0.f, 90.f, 90.f), FVector::ZeroVector, FVector(1.f));
WorldScaleOverride = 0.0f;
bAllowDeformingMesh = true;
bMirrorLeftRight = false;
bEnableUE4HandRepSavings = false;
TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Right;
PoseType = EXRControllerPoseType::Grip;
bHasValidData = false;
LastHandGestureIndex = INDEX_NONE;
LastHandGesture = NAME_None;
}
};
USTRUCT(BlueprintType, Category = "VRExpansionFunctions|SteamVR|HandSkeleton")
struct OPENXREXPANSIONPLUGIN_API FBPOpenXRSkeletalPair
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default")
EXRHandJointType OpenXRBone;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default")
FName BoneToTarget;
FBoneReference ReferenceToConstruct;
FCompactPoseBoneIndex ParentReference;
FQuat RetargetRot;
FBPOpenXRSkeletalPair() :
ParentReference(INDEX_NONE)
{
OpenXRBone = EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT;
BoneToTarget = NAME_None;
RetargetRot = FQuat::Identity;
}
FBPOpenXRSkeletalPair(EXRHandJointType Bone, FString TargetBone) :
ParentReference(INDEX_NONE)
{
OpenXRBone = Bone;
BoneToTarget = FName(*TargetBone);
ReferenceToConstruct.BoneName = BoneToTarget;
RetargetRot = FQuat::Identity;
}
FORCEINLINE bool operator==(const int32& Other) const
{
return ReferenceToConstruct.CachedCompactPoseIndex.GetInt() == Other;
//return ReferenceToConstruct.BoneIndex == Other;
}
};
USTRUCT(BlueprintType, Category = "VRExpansionFunctions|SteamVR|HandSkeleton")
struct OPENXREXPANSIONPLUGIN_API FBPOpenXRSkeletalMappingData
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default")
TArray<FBPOpenXRSkeletalPair> BonePairs;
TArray<int32> ReverseBonePairMap;
// Merge the transforms of bones that are missing from the OpenVR skeleton to the UE4 one.
// This should be always enabled for UE4 skeletons generally.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default")
bool bMergeMissingBonesUE4;
// The hand data to get, if not using a custom bone mapping then this value will be auto filled
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default")
EVRSkeletalHandIndex TargetHand;
FQuat AdjustmentQuat;
bool bInitialized;
FName LastInitializedName;
EVROpenXRSkeletonType LastInitializedSkeleton;
void ClearMapping()
{
bInitialized = false;
LastInitializedName = NAME_None;
AdjustmentQuat = FQuat::Identity;
LastInitializedSkeleton = EVROpenXRSkeletonType::OXR_SkeletonType_Custom;
BonePairs.Empty();
ReverseBonePairMap.Empty();
}
void ConstructReverseMapping()
{
int32 MaxElements = ((uint8)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT) + 1;
ReverseBonePairMap.Empty(MaxElements);
ReverseBonePairMap.AddUninitialized(MaxElements);
FMemory::Memset(ReverseBonePairMap.GetData(), 0, MaxElements * sizeof(int32));
for (int i = 0; i < BonePairs.Num(); ++i)
{
// Just in case someone messed up the mapping file
if (i < MaxElements)
{
ReverseBonePairMap[(uint8)BonePairs[i].OpenXRBone] = i;
}
}
}
void ConstructDefaultMappings(EVROpenXRSkeletonType SkeletonType, bool bSkipRootBone)
{
switch (SkeletonType)
{
case EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Left:
case EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Right:
{
bMergeMissingBonesUE4 = false;
SetDefaultOpenVRInputs(SkeletonType, bSkipRootBone);
}break;
case EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Left:
case EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Right:
{
bMergeMissingBonesUE4 = true;
SetDefaultUE4Inputs(SkeletonType, bSkipRootBone);
}break;
case EVROpenXRSkeletonType::OXR_SkeletonType_UE5Default_Left:
case EVROpenXRSkeletonType::OXR_SkeletonType_UE5Default_Right:
{
bMergeMissingBonesUE4 = false;
SetDefaultUE5Inputs(SkeletonType, bSkipRootBone);
}break;
case EVROpenXRSkeletonType::OXR_SkeletonType_OpenXRDefault_Left:
case EVROpenXRSkeletonType::OXR_SkeletonType_OpenXRDefault_Right:
{
bMergeMissingBonesUE4 = false;
SetDefaultOpenXRInputs(SkeletonType, bSkipRootBone);
}break;
}
}
void SetDefaultOpenVRInputs(EVROpenXRSkeletonType cSkeletonType, bool bSkipRootBone)
{
// Don't map anything if the end user already has
if (BonePairs.Num())
return;
bool bIsRightHand = cSkeletonType != EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Left;
FString HandDelimiterS = !bIsRightHand ? "l" : "r";
const TCHAR* HandDelimiter = *HandDelimiterS;
TargetHand = bIsRightHand ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left;
// Default OpenVR bones mapping
//if (!bSkipRootBone)
//{
//BonePairs.Add(FBPOpenVRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_PALM_EXT, FString::Printf(TEXT("Root"), HandDelimiter)));
//}
//if (!bSkipRootBone)
{
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, FString::Printf(TEXT("wrist_%s"), HandDelimiter)));
}
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, FString::Printf(TEXT("finger_thumb_0_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, FString::Printf(TEXT("finger_thumb_1_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("finger_thumb_2_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT, FString::Printf(TEXT("finger_thumb_%s_end"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT, FString::Printf(TEXT("finger_index_meta_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, FString::Printf(TEXT("finger_index_0_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_index_1_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("finger_index_2_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT, FString::Printf(TEXT("finger_index_%s_end"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT, FString::Printf(TEXT("finger_middle_meta_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, FString::Printf(TEXT("finger_middle_0_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_middle_1_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("finger_middle_2_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT, FString::Printf(TEXT("finger_middle_%s_end"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT, FString::Printf(TEXT("finger_ring_meta_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, FString::Printf(TEXT("finger_ring_0_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_ring_1_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("finger_ring_2_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT, FString::Printf(TEXT("finger_ring_%s_end"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT, FString::Printf(TEXT("finger_pinky_meta_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, FString::Printf(TEXT("finger_pinky_0_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_pinky_1_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("finger_pinky_2_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT, FString::Printf(TEXT("finger_pinky_%s_end"), HandDelimiter)));
// Aux bones share the final knuckles location / rotation
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("finger_thumb_%s_aux"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("finger_index_%s_aux"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("finger_middle_%s_aux"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("finger_ring_%s_aux"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("finger_pinky_%s_aux"), HandDelimiter)));
}
void SetDefaultUE4Inputs(EVROpenXRSkeletonType cSkeletonType, bool bSkipRootBone)
{
// Don't map anything if the end user already has
if (BonePairs.Num())
return;
bool bIsRightHand = cSkeletonType != EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Left;
FString HandDelimiterS = !bIsRightHand ? "l" : "r";
const TCHAR* HandDelimiter = *HandDelimiterS;
TargetHand = bIsRightHand ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left;
// Default ue4 skeleton hand to the OpenVR bones, skipping the extra joint and the aux joints
//if (!bSkipRootBone)
{
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, FString::Printf(TEXT("hand_%s"), HandDelimiter)));
}
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, FString::Printf(TEXT("index_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, FString::Printf(TEXT("index_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("index_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, FString::Printf(TEXT("middle_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, FString::Printf(TEXT("middle_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("middle_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, FString::Printf(TEXT("pinky_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, FString::Printf(TEXT("pinky_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("pinky_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, FString::Printf(TEXT("ring_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, FString::Printf(TEXT("ring_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("ring_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, FString::Printf(TEXT("thumb_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, FString::Printf(TEXT("thumb_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("thumb_03_%s"), HandDelimiter)));
}
void SetDefaultUE5Inputs(EVROpenXRSkeletonType cSkeletonType, bool bSkipRootBone)
{
// Don't map anything if the end user already has
if (BonePairs.Num())
return;
bool bIsRightHand = cSkeletonType != EVROpenXRSkeletonType::OXR_SkeletonType_UE5Default_Left;
FString HandDelimiterS = !bIsRightHand ? "l" : "r";
const TCHAR* HandDelimiter = *HandDelimiterS;
TargetHand = bIsRightHand ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left;
// Default ue5 skeleton hand to the OpenVR bones, skipping the extra joint and the aux joints
//if (!bSkipRootBone)
{
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, FString::Printf(TEXT("hand_%s"), HandDelimiter)));
}
// There are inner and outer wrist elements to this, going to be anoying to map that to a single wrist index....
//OXR_HAND_JOINT_WRIST_EXT = 1,
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, FString::Printf(TEXT("index_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, FString::Printf(TEXT("index_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("index_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, FString::Printf(TEXT("middle_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, FString::Printf(TEXT("middle_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("middle_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, FString::Printf(TEXT("pinky_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, FString::Printf(TEXT("pinky_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("pinky_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, FString::Printf(TEXT("ring_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, FString::Printf(TEXT("ring_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("ring_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, FString::Printf(TEXT("thumb_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, FString::Printf(TEXT("thumb_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("thumb_03_%s"), HandDelimiter)));
}
void SetDefaultOpenXRInputs(EVROpenXRSkeletonType cSkeletonType, bool bSkipRootBone)
{
// Don't map anything if the end user already has
if (BonePairs.Num())
return;
bool bIsRightHand = cSkeletonType != EVROpenXRSkeletonType::OXR_SkeletonType_OpenXRDefault_Left;
FString HandDelimiterS = !bIsRightHand ? "l" : "r";
const TCHAR* HandDelimiter = *HandDelimiterS;
TargetHand = bIsRightHand ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left;
// Default ue5 skeleton hand to the OpenVR bones, skipping the extra joint and the aux joints
//if (!bSkipRootBone)
{
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, FString::Printf(TEXT("hand_%s"), HandDelimiter)));
}
// There are inner and outer wrist elements to this, going to be anoyying to map that to a single wrist index....
//OXR_HAND_JOINT_WRIST_EXT = 1,
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, FString::Printf(TEXT("index_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, FString::Printf(TEXT("index_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("index_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, FString::Printf(TEXT("middle_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, FString::Printf(TEXT("middle_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("middle_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, FString::Printf(TEXT("pinky_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, FString::Printf(TEXT("pinky_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("pinky_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, FString::Printf(TEXT("ring_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, FString::Printf(TEXT("ring_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("ring_03_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, FString::Printf(TEXT("thumb_01_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, FString::Printf(TEXT("thumb_02_%s"), HandDelimiter)));
BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("thumb_03_%s"), HandDelimiter)));
}
FBPOpenXRSkeletalMappingData()
{
AdjustmentQuat = FQuat::Identity;
bInitialized = false;
bMergeMissingBonesUE4 = false;
TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Right;
LastInitializedName = NAME_None;
LastInitializedSkeleton = EVROpenXRSkeletonType::OXR_SkeletonType_Custom;
}
};

View file

@ -0,0 +1,377 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "Engine/Texture.h"
#include "Engine/EngineTypes.h"
#include "HeadMountedDisplayTypes.h"
//#include "Runtime/Launch/Resources/Version.h"
#include "Animation/AnimInstanceProxy.h"
#include "OpenXRExpansionTypes.h"
#include "Engine/DataAsset.h"
#include "OpenXRHandPoseComponent.generated.h"
USTRUCT(BlueprintType, Category = "VRExpansionFunctions|OpenXR|HandSkeleton")
struct OPENXREXPANSIONPLUGIN_API FBPXRSkeletalRepContainer
{
GENERATED_BODY()
public:
UPROPERTY(Transient, NotReplicated)
EVRSkeletalHandIndex TargetHand;
UPROPERTY(Transient, NotReplicated)
bool bAllowDeformingMesh;
// If true we will skip sending the 4 metacarpal bones that ue4 doesn't need, (STEAMVR skeletons need this disabled!)
// Only really used for the older UE4 skeleton
UPROPERTY(Transient, NotReplicated)
bool bEnableUE4HandRepSavings;
UPROPERTY(Transient, NotReplicated)
TArray<FTransform> SkeletalTransforms;
UPROPERTY(Transient, NotReplicated)
uint8 BoneCount;
FBPXRSkeletalRepContainer()
{
TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Left;
bAllowDeformingMesh = false;
bEnableUE4HandRepSavings = false;
BoneCount = 0;
}
bool bHasValidData()
{
return SkeletalTransforms.Num() > 0;
}
void CopyForReplication(FBPOpenXRActionSkeletalData& Other);
static void CopyReplicatedTo(const FBPXRSkeletalRepContainer& Container, FBPOpenXRActionSkeletalData& Other);
bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
};
template<>
struct TStructOpsTypeTraits< FBPXRSkeletalRepContainer > : public TStructOpsTypeTraitsBase2<FBPXRSkeletalRepContainer>
{
enum
{
WithNetSerializer = true
};
};
USTRUCT(BlueprintType, Category = "VRGestures")
struct OPENXREXPANSIONPLUGIN_API FOpenXRGestureFingerPosition
{
GENERATED_BODY()
public:
// The Finger index, not editable
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "VRGesture")
EXRHandJointType IndexType;
// The locational value of this element 0.f - 1.f
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture")
FVector Value;
// The threshold within which this finger value will be detected as matching (1.0 would be always matching, IE: finger doesn't count)
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture", meta = (ClampMin = "0.0", ClampMax = "100.0", UIMin = "0.0", UIMax = "100.0"))
float Threshold;
FOpenXRGestureFingerPosition(FVector TipLoc, EXRHandJointType Type)
{
IndexType = Type;
Value = TipLoc;
Threshold = 5.0f;
}
FOpenXRGestureFingerPosition()
{
IndexType = EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT;
Value = FVector(0.f);
Threshold = 5.0f;
}
};
USTRUCT(BlueprintType, Category = "VRGestures")
struct OPENXREXPANSIONPLUGIN_API FOpenXRGesture
{
GENERATED_BODY()
public:
// Name of the recorded gesture
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture")
FName Name;
// Samples in the recorded gesture
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture")
TArray<FOpenXRGestureFingerPosition> FingerValues;
FOpenXRGesture()
{
InitPoseValues();
Name = NAME_None;
}
void InitPoseValues()
{
FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT));
FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT));
FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT));
FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT));
FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT));
}
};
/**
* Items Database DataAsset, here we can save all of our game items
*/
UCLASS(BlueprintType, Category = "VRGestures")
class OPENXREXPANSIONPLUGIN_API UOpenXRGestureDatabase : public UDataAsset
{
GENERATED_BODY()
public:
// Gestures in this database
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures")
TArray <FOpenXRGesture> Gestures;
UOpenXRGestureDatabase()
{
}
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOpenXRGestureDetected, const FName &, GestureDetected, int32, GestureIndex, EVRSkeletalHandIndex, ActionHandType);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOpenXRGestureEnded, const FName &, GestureEnded, int32, GestureIndex, EVRSkeletalHandIndex, ActionHandType);
UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent))
class OPENXREXPANSIONPLUGIN_API UOpenXRHandPoseComponent : public UActorComponent
{
GENERATED_BODY()
public:
UOpenXRHandPoseComponent(const FObjectInitializer& ObjectInitializer);
// Says whether we should run gesture detection
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures")
bool bDetectGestures;
UFUNCTION(BlueprintCallable, Category = "VRGestures")
void SetDetectGestures(bool bNewDetectGestures)
{
bDetectGestures = bNewDetectGestures;
}
UPROPERTY(BlueprintAssignable, Category = "VRGestures")
FOpenXRGestureDetected OnNewGestureDetected;
UPROPERTY(BlueprintAssignable, Category = "VRGestures")
FOpenXRGestureEnded OnGestureEnded;
// Known sequences
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures")
UOpenXRGestureDatabase *GesturesDB;
UFUNCTION(BlueprintCallable, Category = "VRGestures")
bool SaveCurrentPose(FName RecordingName, EVRSkeletalHandIndex HandToSave = EVRSkeletalHandIndex::EActionHandIndex_Right);
UFUNCTION(BlueprintCallable, Category = "VRGestures", meta = (DisplayName = "DetectCurrentPose"))
bool K2_DetectCurrentPose(UPARAM(ref) FBPOpenXRActionSkeletalData& SkeletalAction, FOpenXRGesture & GestureOut);
// This version throws events
bool DetectCurrentPose(FBPOpenXRActionSkeletalData& SkeletalAction);
// Need this as I can't think of another way for an actor component to make sure it isn't on the server
inline bool IsLocallyControlled() const
{
//#if ENGINE_MAJOR_VERSION > 4 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 22)
const AActor* MyOwner = GetOwner();
return MyOwner->HasLocalNetOwner();
//#else
// I like epics new authority check more than mine
/* const AActor* MyOwner = GetOwner();
const APawn* MyPawn = Cast<APawn>(MyOwner);
return MyPawn ? MyPawn->IsLocallyControlled() : (MyOwner && MyOwner->GetLocalRole() == ENetRole::ROLE_Authority);*/
//#endif
}
// Using tick and not timers because skeletal components tick anyway, kind of a waste to make another tick by adding a timer over that
void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
//virtual void OnUnregister() override;
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SkeletalData|Actions")
bool bGetMockUpPoseForDebugging;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SkeletalData|Actions")
TArray<FBPOpenXRActionSkeletalData> HandSkeletalActions;
UPROPERTY(Replicated, Transient, ReplicatedUsing = OnRep_SkeletalTransformLeft)
FBPXRSkeletalRepContainer LeftHandRep;
UPROPERTY(Replicated, Transient, ReplicatedUsing = OnRep_SkeletalTransformRight)
FBPXRSkeletalRepContainer RightHandRep;
UFUNCTION(Unreliable, Server, WithValidation)
void Server_SendSkeletalTransforms(const FBPXRSkeletalRepContainer& SkeletalInfo);
bool bLerpingPositionLeft;
bool bLerpingPositionRight;
struct FTransformLerpManager
{
bool bReplicatedOnce;
bool bLerping;
float UpdateCount;
float UpdateRate;
TArray<FTransform> OldTransforms;
TArray<FTransform> NewTransforms;
FTransformLerpManager();
void PreCopyNewData(FBPOpenXRActionSkeletalData& ActionInfo, int NetUpdateRate, bool bExponentialSmoothing);
void NotifyNewData(FBPOpenXRActionSkeletalData& ActionInfo, int NetUpdateRate, bool bExponentialSmoothing);
FORCEINLINE void BlendBone(uint8 BoneToBlend, FBPOpenXRActionSkeletalData& ActionInfo, float& LerpVal, bool bExponentialSmoothing)
{
ActionInfo.SkeletalTransforms[BoneToBlend].Blend(OldTransforms[BoneToBlend], NewTransforms[BoneToBlend], LerpVal);
if (bExponentialSmoothing)
{
// Saving base back out for exponential
OldTransforms[BoneToBlend] = ActionInfo.SkeletalTransforms[BoneToBlend];
}
}
void UpdateManager(float DeltaTime, FBPOpenXRActionSkeletalData& ActionInfo, UOpenXRHandPoseComponent * ParentComp);
};
FTransformLerpManager LeftHandRepManager;
FTransformLerpManager RightHandRepManager;
UFUNCTION()
virtual void OnRep_SkeletalTransformLeft()
{
for (int i = 0; i < HandSkeletalActions.Num(); i++)
{
if (HandSkeletalActions[i].TargetHand == LeftHandRep.TargetHand)
{
if (bSmoothReplicatedSkeletalData)
{
LeftHandRepManager.PreCopyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing);
}
FBPXRSkeletalRepContainer::CopyReplicatedTo(LeftHandRep, HandSkeletalActions[i]);
if (bSmoothReplicatedSkeletalData)
{
LeftHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing);
}
break;
}
}
}
UFUNCTION()
virtual void OnRep_SkeletalTransformRight()
{
for (int i = 0; i < HandSkeletalActions.Num(); i++)
{
if (HandSkeletalActions[i].TargetHand == RightHandRep.TargetHand)
{
if (bSmoothReplicatedSkeletalData)
{
RightHandRepManager.PreCopyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing);
}
FBPXRSkeletalRepContainer::CopyReplicatedTo(RightHandRep, HandSkeletalActions[i]);
if (bSmoothReplicatedSkeletalData)
{
RightHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing);
}
break;
}
}
}
// If we should replicate the skeletal transform data
UPROPERTY(EditAnywhere, Category = SkeletalData)
bool bReplicateSkeletalData;
// If true we will lerp between updates of the skeletal mesh transforms and smooth the result
UPROPERTY(EditAnywhere, Category = SkeletalData)
bool bSmoothReplicatedSkeletalData;
// If true then we will use exponential smoothing with buffered correction
UPROPERTY(EditAnywhere, Category = "SkeletalData", meta = (editcondition = "bSmoothReplicatedSkeletalData"))
bool bUseExponentialSmoothing = false;
// Timestep of smoothing translation
UPROPERTY(EditAnywhere, Category = "SkeletalData", meta = (editcondition = "bUseExponentialSmoothing"))
float InterpolationSpeed = 25.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SkeletalData)
float ReplicationRateForSkeletalAnimations;
// Used in Tick() to accumulate before sending updates, didn't want to use a timer in this case, also used for remotes to lerp position
float SkeletalNetUpdateCount;
// Used in Tick() to accumulate before sending updates, didn't want to use a timer in this case, also used for remotes to lerp position
float SkeletalUpdateCount;
};
USTRUCT()
struct OPENXREXPANSIONPLUGIN_API FOpenXRAnimInstanceProxy : public FAnimInstanceProxy
{
public:
GENERATED_BODY()
FOpenXRAnimInstanceProxy() {}
FOpenXRAnimInstanceProxy(UAnimInstance* InAnimInstance);
/** Called before update so we can copy any data we need */
virtual void PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) override;
public:
EVRSkeletalHandIndex TargetHand;
TArray<FBPOpenXRActionSkeletalData> HandSkeletalActionData;
};
UCLASS(transient, Blueprintable, hideCategories = AnimInstance, BlueprintType)
class OPENXREXPANSIONPLUGIN_API UOpenXRAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
UPROPERTY(transient)
UOpenXRHandPoseComponent * OwningPoseComp;
FOpenXRAnimInstanceProxy AnimInstanceProxy;
virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override
{
return new FOpenXRAnimInstanceProxy(this);
//return &AnimInstanceProxy;
}
virtual void NativeBeginPlay() override;
//virtual void NativeInitializeAnimation() override;
UFUNCTION(BlueprintCallable, Category = "BoneMappings")
void InitializeCustomBoneMapping(UPARAM(ref) FBPOpenXRSkeletalMappingData & SkeletalMappingData);
};

View file

@ -0,0 +1,16 @@
#pragma once
#include "Iris/Serialization/NetSerializer.h"
#include "FBPXRSkeletalRepContainerNetSerializer.generated.h"
USTRUCT()
struct FBPXRSkeletalRepContainerNetSerializerConfig : public FNetSerializerConfig
{
GENERATED_BODY()
};
namespace UE::Net
{
UE_NET_DECLARE_SERIALIZER(FBPXRSkeletalRepContainerNetSerializer, OPENXREXPANSIONPLUGIN_API);
}