A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
AnimNode_ApplyOpenXRHandPose.cpp
Go to the documentation of this file.
1// Fill out your copyright notice in the Description page of Project Settings.
3//#include "EngineMinimal.h"
4//#include "Engine/Engine.h"
5//#include "CoreMinimal.h"
8#include "AnimationRuntime.h"
9#include "DrawDebugHelpers.h"
11#include "Runtime/Engine/Public/Animation/AnimInstanceProxy.h"
12#include "BoneControllers/AnimNode_SkeletalControlBase.h"
13
25
27{
28 Super::OnInitializeAnimInstance(InProxy, InAnimInstance);
29
30 if (const UOpenXRAnimInstance * animInst = Cast<UOpenXRAnimInstance>(InAnimInstance))
31 {
33 }
34}
35
36void FAnimNode_ApplyOpenXRHandPose::Initialize_AnyThread(const FAnimationInitializeContext& Context)
37{
38 Super::Initialize_AnyThread(Context);
39}
40
41void FAnimNode_ApplyOpenXRHandPose::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
42{
43 Super::CacheBones_AnyThread(Context);
44}
45
46void FAnimNode_ApplyOpenXRHandPose::InitializeBoneReferences(const FBoneContainer& RequiredBones)
47{
48 UObject* OwningAsset = RequiredBones.GetAsset();
49 if (!OwningAsset)
50 return;
51
52 if (!MappedBonePairs.bInitialized || OwningAsset->GetFName() != MappedBonePairs.LastInitializedName)
53 {
54 MappedBonePairs.LastInitializedName = OwningAsset->GetFName();
56
57 USkeleton* AssetSkeleton = RequiredBones.GetSkeletonAsset();
58 if (AssetSkeleton)
59 {
60 // If our bone pairs are empty, then setup our sane defaults
61 if(!MappedBonePairs.BonePairs.Num())
63
64 FBPOpenXRSkeletalPair WristPair;
65 FBPOpenXRSkeletalPair IndexPair;
66 FBPOpenXRSkeletalPair PinkyPair;
67
68 TArray<FTransform> RefBones = AssetSkeleton->GetReferenceSkeleton().GetRefBonePose();
69 TArray<FMeshBoneInfo> RefBonesInfo = AssetSkeleton->GetReferenceSkeleton().GetRefBoneInfo();
70
72 {
73 // Fill in the bone name for the reference
74 BonePair.ReferenceToConstruct.BoneName = BonePair.BoneToTarget;
75
76 // Init the reference
77 BonePair.ReferenceToConstruct.Initialize(AssetSkeleton);
78
79 BonePair.ReferenceToConstruct.CachedCompactPoseIndex = BonePair.ReferenceToConstruct.GetCompactPoseIndex(RequiredBones);
80
81 if ((BonePair.ReferenceToConstruct.CachedCompactPoseIndex != INDEX_NONE))
82 {
83 // Get our parent bones index
84 BonePair.ParentReference = RequiredBones.GetParentBoneIndex(BonePair.ReferenceToConstruct.CachedCompactPoseIndex);
85
86 /*FTransform WristPose = GetRefBoneInCS(RefBones, RefBonesInfo, BonePair.ReferenceToConstruct.BoneIndex);
87
88 FVector WristForward = WristPose.GetRotation().GetForwardVector();
89 FVector WristUpward = WristPose.GetRotation().GetForwardVector();
90 FQuat ForwardFixup = FQuat::FindBetweenNormals(FVector::ForwardVector, WristForward);
91 FQuat UpFixup = FQuat::FindBetweenNormals(ForwardFixup.RotateVector(FVector::UpVector), WristUpward);
92
93 FQuat rotFix = UpFixup * ForwardFixup;
94 rotFix.Normalize();
95 //MappedBonePairs.AdjustmentQuat = rotFix;
96 BonePair.RetargetRot = rotFix;*/
97 }
98
99 if (BonePair.OpenXRBone == EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT)
100 {
101 WristPair = BonePair;
102 }
103 else if (BonePair.OpenXRBone == EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT)
104 {
105 IndexPair = BonePair;
106 }
107 else if (BonePair.OpenXRBone == EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT)
108 {
109 PinkyPair = BonePair;
110 }
111 }
112
114
115 if (WristPair.ReferenceToConstruct.HasValidSetup() && IndexPair.ReferenceToConstruct.HasValidSetup() && PinkyPair.ReferenceToConstruct.HasValidSetup())
116 {
117 //TArray<FTransform> RefBones = AssetSkeleton->GetReferenceSkeleton().GetRefBonePose();
118 //TArray<FMeshBoneInfo> RefBonesInfo = AssetSkeleton->GetReferenceSkeleton().GetRefBoneInfo();
119
120 FTransform WristPose = GetRefBoneInCS(RefBones, RefBonesInfo, WristPair.ReferenceToConstruct.BoneIndex);
121 FTransform MiddleFingerPose = GetRefBoneInCS(RefBones, RefBonesInfo, PinkyPair.ReferenceToConstruct.BoneIndex);
122
123 FVector BoneForwardVector = MiddleFingerPose.GetTranslation() - WristPose.GetTranslation();
124 SetVectorToMaxElement(BoneForwardVector);
125 BoneForwardVector.Normalize();
126
127 FTransform IndexFingerPose = GetRefBoneInCS(RefBones, RefBonesInfo, IndexPair.ReferenceToConstruct.BoneIndex);
128 FTransform PinkyFingerPose = GetRefBoneInCS(RefBones, RefBonesInfo, PinkyPair.ReferenceToConstruct.BoneIndex);
129 FVector BoneUpVector = IndexFingerPose.GetTranslation() - PinkyFingerPose.GetTranslation();
130 SetVectorToMaxElement(BoneUpVector);
131 BoneUpVector.Normalize();
132
133 FVector BoneRightVector = FVector::CrossProduct(BoneUpVector, BoneForwardVector);
134 BoneRightVector.Normalize();
135
136 FQuat ForwardAdjustment = FQuat::FindBetweenNormals(FVector::ForwardVector, BoneForwardVector);
137
138 FVector NewRightVector = ForwardAdjustment * FVector::RightVector;
139 NewRightVector.Normalize();
140
141 FQuat TwistAdjustment = FQuat::FindBetweenNormals(NewRightVector, BoneRightVector);
142 MappedBonePairs.AdjustmentQuat = TwistAdjustment * ForwardAdjustment;
144 }
145
146 }
147 }
148}
149
150void FAnimNode_ApplyOpenXRHandPose::ConvertHandTransformsSpace(TArray<FTransform>& OutTransforms, TArray<FTransform>& WorldTransforms, FTransform AddTrans, bool bMirrorLeftRight, bool bMergeMissingUE4Bones)
151{
152 // Fail if the count is too low
153 if (WorldTransforms.Num() < EHandKeypointCount)
154 return;
155
156 if (OutTransforms.Num() < WorldTransforms.Num())
157 {
158 OutTransforms.Empty(WorldTransforms.Num());
159 OutTransforms.AddUninitialized(WorldTransforms.Num());
160 }
161
162 // Bone/Parent map
163 int32 BoneParents[26] =
164 {
165 // Manually build the parent hierarchy starting at the wrist which has no parent (-1)
166 1, // Palm -> Wrist
167 -1, // Wrist -> None
168 1, // ThumbMetacarpal -> Wrist
169 2, // ThumbProximal -> ThumbMetacarpal
170 3, // ThumbDistal -> ThumbProximal
171 4, // ThumbTip -> ThumbDistal
172
173 1, // IndexMetacarpal -> Wrist
174 6, // IndexProximal -> IndexMetacarpal
175 7, // IndexIntermediate -> IndexProximal
176 8, // IndexDistal -> IndexIntermediate
177 9, // IndexTip -> IndexDistal
178
179 1, // MiddleMetacarpal -> Wrist
180 11, // MiddleProximal -> MiddleMetacarpal
181 12, // MiddleIntermediate -> MiddleProximal
182 13, // MiddleDistal -> MiddleIntermediate
183 14, // MiddleTip -> MiddleDistal
184
185 1, // RingMetacarpal -> Wrist
186 16, // RingProximal -> RingMetacarpal
187 17, // RingIntermediate -> RingProximal
188 18, // RingDistal -> RingIntermediate
189 19, // RingTip -> RingDistal
190
191 1, // LittleMetacarpal -> Wrist
192 21, // LittleProximal -> LittleMetacarpal
193 22, // LittleIntermediate -> LittleProximal
194 23, // LittleDistal -> LittleIntermediate
195 24, // LittleTip -> LittleDistal
196 };
197
198 // Convert transforms to parent space
199 // The hand tracking transforms are in world space.
200
201 for (int32 Index = 0; Index < EHandKeypointCount; ++Index)
202 {
203 WorldTransforms[Index].NormalizeRotation();
204
205 if (bMirrorLeftRight)
206 {
207 WorldTransforms[Index].Mirror(EAxis::Y, EAxis::Y);
208 }
209
210 WorldTransforms[Index].ConcatenateRotation(AddTrans.GetRotation());
211 }
212
213 for (int32 Index = 0; Index < EHandKeypointCount; ++Index)
214 {
215 FTransform& BoneTransform = WorldTransforms[Index];
216 int32 ParentIndex = BoneParents[Index];
217 int32 ParentParent = -1;
218
219 // Thumb keeps the metacarpal intact, we don't skip it
220 if (bMergeMissingUE4Bones)
221 {
222 if (Index != (int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT && ParentIndex > 0)
223 {
224 ParentParent = BoneParents[ParentIndex];
225 }
226 }
227
228 if (ParentIndex < 0)
229 {
230 // We are at the root, so use it.
231 OutTransforms[Index] = BoneTransform;
232 }
233 else
234 {
235 // Merging missing metacarpal bone into the transform
236 if (bMergeMissingUE4Bones && ParentParent == 1) // Wrist
237 {
238 OutTransforms[Index] = BoneTransform.GetRelativeTransform(WorldTransforms[ParentParent]);
239 }
240 else
241 {
242 OutTransforms[Index] = BoneTransform.GetRelativeTransform(WorldTransforms[ParentIndex]);
243 }
244 }
245 }
246}
247
248void FAnimNode_ApplyOpenXRHandPose::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
249{
251 return;
252
253 /*const */FBPOpenXRActionSkeletalData *StoredActionInfoPtr = nullptr;
255 {
256 /*const*/ FOpenXRAnimInstanceProxy* OpenXRAnimInstance = (FOpenXRAnimInstanceProxy*)Output.AnimInstanceProxy;
257 if (OpenXRAnimInstance->HandSkeletalActionData.Num())
258 {
259 for (int i = 0; i <OpenXRAnimInstance->HandSkeletalActionData.Num(); ++i)
260 {
261 EVRSkeletalHandIndex TargetHand = OpenXRAnimInstance->HandSkeletalActionData[i].TargetHand;
262
263 if (OpenXRAnimInstance->HandSkeletalActionData[i].bMirrorLeftRight)
264 {
266 }
267
268 if (TargetHand == MappedBonePairs.TargetHand)
269 {
270 StoredActionInfoPtr = &OpenXRAnimInstance->HandSkeletalActionData[i];
271 break;
272 }
273 }
274 }
275 }
276
277 // If we have an empty hand pose but have a passed in custom one then use that
278 if ((StoredActionInfoPtr == nullptr || !StoredActionInfoPtr->SkeletalTransforms.Num()) && OptionalStoredActionInfo.SkeletalTransforms.Num())
279 {
280 StoredActionInfoPtr = &OptionalStoredActionInfo;
281 }
282
283 //MappedBonePairs.AdjustmentQuat = WristAdjustment;
284
285 // Currently not blending correctly
286 const float BlendWeight = FMath::Clamp<float>(ActualAlpha, 0.f, 1.f);
287 const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();
288 uint8 BoneTransIndex = 0;
289 uint8 NumBones = StoredActionInfoPtr ? StoredActionInfoPtr->SkeletalTransforms.Num() : 0;
290
291 if (NumBones < 1)
292 {
293 // Early out, we don't have a valid data to work with
294 return;
295 }
296
297 FTransform trans = FTransform::Identity;
298 OutBoneTransforms.Reserve(MappedBonePairs.BonePairs.Num());
299 TArray<FBoneTransform> TransBones;
300 FTransform AdditionTransform = StoredActionInfoPtr->AdditionTransform;
301
302 FTransform TempTrans = FTransform::Identity;
303 FTransform ParentTrans = FTransform::Identity;
304 FTransform * ParentTransPtr = nullptr;
305
306 //AdditionTransform.SetRotation(MappedBonePairs.AdjustmentQuat);
307
308 TArray<FTransform> HandTransforms;
309 ConvertHandTransformsSpace(HandTransforms, StoredActionInfoPtr->SkeletalTransforms, AdditionTransform, StoredActionInfoPtr->bMirrorLeftRight, MappedBonePairs.bMergeMissingBonesUE4);
310
311 for (const FBPOpenXRSkeletalPair& BonePair : MappedBonePairs.BonePairs)
312 {
313 BoneTransIndex = (int8)BonePair.OpenXRBone;
314 ParentTrans = FTransform::Identity;
315
316 if (BoneTransIndex >= NumBones || BonePair.ReferenceToConstruct.CachedCompactPoseIndex == INDEX_NONE)
317 continue;
318
319 if (!BonePair.ReferenceToConstruct.IsValidToEvaluate(BoneContainer))
320 {
321 continue;
322 }
323
324 trans = Output.Pose.GetComponentSpaceTransform(BonePair.ReferenceToConstruct.CachedCompactPoseIndex);
325
326 if (BonePair.ParentReference != INDEX_NONE)
327 {
328 ParentTrans = Output.Pose.GetComponentSpaceTransform(BonePair.ParentReference);
329 ParentTrans.SetScale3D(FVector(1.f));
330 }
331
332 EXRHandJointType CurrentBone = (EXRHandJointType)BoneTransIndex;
333 TempTrans = (HandTransforms[BoneTransIndex]);
334
335 /*if (StoredActionInfoPtr->bMirrorHand)
336 {
337 FMatrix M = TempTrans.ToMatrixWithScale();
338 M.Mirror(EAxis::Z, EAxis::X);
339 M.Mirror(EAxis::X, EAxis::Z);
340 TempTrans.SetFromMatrix(M);
341 }*/
342
343 TempTrans = TempTrans * ParentTrans;
344
345 if (StoredActionInfoPtr->bAllowDeformingMesh || bOnlyApplyWristTransform)
346 trans.SetTranslation(TempTrans.GetTranslation());
347
348 trans.SetRotation(TempTrans.GetRotation());
349
350 TransBones.Add(FBoneTransform(BonePair.ReferenceToConstruct.CachedCompactPoseIndex, trans));
351
352 // Need to do it per bone so future bones are correct
353 // Only if in parent space though, can do it all at the end in component space
354 if (TransBones.Num())
355 {
356 Output.Pose.LocalBlendCSBoneTransforms(TransBones, BlendWeight);
357 TransBones.Reset();
358 }
359
361 {
362 break; // Early out of the loop, we only wanted to apply the wrist
363 }
364 }
365}
366
367bool FAnimNode_ApplyOpenXRHandPose::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones)
368{
369 return(/*MappedBonePairs.bInitialized && */MappedBonePairs.BonePairs.Num() > 0);
370}
EVRSkeletalHandIndex
UENUM(BlueprintType)
EXRHandJointType
UENUM(BlueprintType)
@ OXR_HAND_JOINT_INDEX_PROXIMAL_EXT
@ OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT
@ OXR_HAND_JOINT_THUMB_PROXIMAL_EXT
UCLASS(transient, Blueprintable, hideCategories = AnimInstance, BlueprintType)
FTransform GetRefBoneInCS(TArray< FTransform > &RefBones, TArray< FMeshBoneInfo > &RefBonesInfo, int32 BoneIndex)
bool bSkipRootBone
UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault))
bool bOnlyApplyWristTransform
UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault))
EVROpenXRSkeletonType SkeletonType
UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault))
virtual void InitializeBoneReferences(const FBoneContainer &RequiredBones) override
virtual bool IsValidToEvaluate(const USkeleton *Skeleton, const FBoneContainer &RequiredBones) override
virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext &Context) override
virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext &Output, TArray< FBoneTransform > &OutBoneTransforms) override
void ConvertHandTransformsSpace(TArray< FTransform > &OutTransforms, TArray< FTransform > &WorldTransforms, FTransform AddTrans, bool bMirrorLeftRight, bool bMergeMissingUE4Bones)
FBPOpenXRSkeletalMappingData MappedBonePairs
UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinHiddenByDefault))
FBPOpenXRActionSkeletalData OptionalStoredActionInfo
UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault))
virtual void OnInitializeAnimInstance(const FAnimInstanceProxy *InProxy, const UAnimInstance *InAnimInstance) override
virtual void Initialize_AnyThread(const FAnimationInitializeContext &Context) override
USTRUCT(BlueprintType, Category = "VRExpansionFunctions|OpenXR|HandSkeleton")
TArray< FTransform > SkeletalTransforms
UPROPERTY(BlueprintReadOnly, NotReplicated, Transient, Category = Default)
bool bMirrorLeftRight
UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default)
bool bAllowDeformingMesh
UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default)
FTransform AdditionTransform
UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default)
void ConstructDefaultMappings(EVROpenXRSkeletonType SkeletonType, bool bSkipRootBone)
bool bMergeMissingBonesUE4
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default")
TArray< FBPOpenXRSkeletalPair > BonePairs
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default")
EVRSkeletalHandIndex TargetHand
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default")
USTRUCT(BlueprintType, Category = "VRExpansionFunctions|SteamVR|HandSkeleton")
FBoneReference ReferenceToConstruct
TArray< FBPOpenXRActionSkeletalData > HandSkeletalActionData